Calculadora de diseño

Un intento esquelomórfico de recrear una calculadora solar en la Web con la API de Window Controls Overlay y la API de Ambient Light Sensor.

El desafío

Soy un hijo de los 80. Cuando estaba en la secundaria, las calculadoras solares eran lo más. La escuela nos dio a todos una TI-30X SOLAR, y recuerdo con cariño cuando comparábamos nuestras calculadoras calculando el factorial de 69, el número más alto que la TI-30X podía manejar. (La variación de velocidad era muy medible, y aún no sé por qué).

Ahora, casi 28 años después, pensé que sería un desafío divertido de Designcember recrear la calculadora en HTML, CSS y JavaScript. Como no soy muy diseñador, no comencé desde cero, sino con un CodePen de Sassja Ceballos.

Vista de CodePen con paneles apilados de HTML, CSS y JS a la izquierda, y la vista previa de la calculadora a la derecha.

Cómo hacer que se pueda instalar

Si bien no es un mal comienzo, decidí mejorarlo para que fuera completamente esqueuomórfico. El primer paso fue convertirla en una AWP para que se pudiera instalar.

self.addEventListener('install', (event) => {
  self.skipWaiting();
});

self.addEventListener('activate', (event) => {
  self.clients.claim();
  event.waitUntil(
    (async () => {
      if ('navigationPreload' in self.registration) {
        await self.registration.navigationPreload.enable();
      }
    })(),
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    (async () => {
      try {
        const response = await event.preloadResponse;
        if (response) {
          return response;
        }
        return fetch(event.request);
      } catch {
        return new Response('Offline');
      }
    })(),
  );
});

Combinación con dispositivos móviles

Ahora que la app se puede instalar, el siguiente paso es hacer que se integre con las apps del sistema operativo lo más posible. En dispositivos móviles, puedo hacerlo configurando el modo de visualización en fullscreen en el manifiesto de la app web.

{
  "display": "fullscreen"
}

En los dispositivos con un orificio o una muesca para la cámara, ajustar la ventana gráfica para que el contenido cubra toda la pantalla hace que la app se vea increíble.

<meta name="viewport" content="initial-scale=1, viewport-fit=cover" />

La calculadora de Designcember se ejecuta en pantalla completa en un teléfono Pixel 6 Pro.

Combinación con la versión para computadoras

En computadoras, hay una función genial que puedo usar: Window Controls Overlay, que me permite colocar contenido en la barra de título de la ventana de la app. El primer paso es anular la secuencia de resguardo del modo de visualización para que intente usar window-controls-overlay primero cuando esté disponible.

{
  "display_override": ["window-controls-overlay"]
}

Esto hace que la barra de título desaparezca de manera efectiva y que el contenido se mueva hacia el área de la barra de título como si esta no estuviera allí. Mi idea es mover la celda solar esceumórfica a la barra de título y el resto de la IU de la calculadora hacia abajo según corresponda, lo que puedo hacer con algo de CSS que use las variables de entorno titlebar-area-*. Notarás que todos los selectores tienen una clase wco, que será relevante en un par de párrafos más abajo.

#calc_solar_cell.wco {
  position: fixed;
  left: calc(0.25rem + env(titlebar-area-x, 0));
  top: calc(0.75rem + env(titlebar-area-y, 0));
  width: calc(env(titlebar-area-width, 100%) - 0.5rem);
  height: calc(env(titlebar-area-height, 33px) - 0.5rem);
}

#calc_display_surface.wco {
  margin-top: calc(env(titlebar-area-height, 33px) - 0.5rem);
}

A continuación, debo decidir qué elementos se pueden arrastrar, ya que la barra de título que suelo usar para arrastrar no está disponible. Con el estilo de un widget clásico, incluso puedo hacer que toda la calculadora se pueda arrastrar aplicando (-webkit-)app-region: drag, excepto los botones, que obtienen (-webkit-)app-region: no-drag para que no se puedan usar para arrastrar.

#calc_inside.wco,
#calc_solar_cell.wco {
  -webkit-app-region: drag;
  app-region: drag;
}

button {
  -webkit-app-region: no-drag;
  app-region: no-drag;
}

El último paso es hacer que la app reaccione a los cambios en la superposición de los controles de ventanas. En un verdadero enfoque de mejora progresiva, solo cargo el código de esta función cuando el navegador la admite.

if ('windowControlsOverlay' in navigator) {
  import('/wco.js');
}

Cada vez que cambia la geometría de la superposición de controles de ventana, modifico la app para que se vea lo más natural posible. Es una buena idea aplicar una función de eliminación de rebotes a este evento, ya que se puede activar con frecuencia cuando el usuario cambia el tamaño de la ventana. Específicamente, aplico la clase wco a algunos elementos, de modo que se active mi CSS anterior y también cambio el color del tema. Puedo detectar si la superposición de controles de ventana está visible verificando la propiedad navigator.windowControlsOverlay.visible.

const meta = document.querySelector('meta[name="theme-color"]');
const nodes = document.querySelectorAll(
  '#calc_display_surface, #calc_solar_cell, #calc_outside, #calc_inside',
);

const toggleWCO = () => {
  if (!navigator.windowControlsOverlay.visible) {
    meta.content = '';
  } else {
    meta.content = '#385975';
  }
  nodes.forEach((node) => {
    node.classList.toggle('wco', navigator.windowControlsOverlay.visible);
  });
};

const debounce = (func, wait) => {
  let timeout;
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(timeout);
      func(...args);
    };
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
  };
};

navigator.windowControlsOverlay.ongeometrychange = debounce((e) => {
  toggleWCO();
}, 250);

toggleWCO();

Ahora, con todo esto en su lugar, obtengo un widget de calculadora que se siente casi como el clásico Winamp con uno de los temas de Winamp de la vieja escuela. Ahora puedo colocar la calculadora libremente en mi escritorio y activar la función de controles de ventana haciendo clic en el chevron de la esquina superior derecha.

La calculadora de Designcember se ejecuta en modo independiente con la función de superposición de controles de ventana activa. La pantalla muestra la palabra &quot;Google&quot; en el alfabeto de la calculadora.

Una celda solar que funciona

Para lograr el máximo nivel de geekismo, por supuesto, necesitaba que la celda solar funcionara de verdad. La calculadora solo debe funcionar si hay suficiente luz. La forma en que modelé esto fue estableciendo el opacity de CSS de los dígitos en la pantalla a través de una variable de CSS --opacity que controlo con JavaScript.

:root {
  --opacity: 0.75;
}

#calc_expression,
#calc_result {
  opacity: var(--opacity);
}

Para detectar si hay suficiente luz para que funcione la calculadora, uso la API de AmbientLightSensor. Para que esta API esté disponible, tuve que establecer la marca #enable-generic-sensor-extra-classes en about:flags y solicitar el permiso 'ambient-light-sensor'. Al igual que antes, uso la mejora progresiva para cargar solo el código pertinente cuando se admite la API.

if ('AmbientLightSensor' in window) {
  import('/als.js');
}

El sensor devuelve la luz ambiente en unidades de lux cada vez que hay una nueva lectura disponible. Según una tabla de valores de situaciones de luz típicas, creé una fórmula muy simple para convertir el valor de lux en un valor entre 0 y 1 que asigno de forma programática a la variable --opacity.

const luxToOpacity = (lux) => {
  if (lux > 250) {
    return 1;
  }
  return lux / 250;
};

const sensor = new window.AmbientLightSensor();
sensor.onreading = () => {
  console.log('Current light level:', sensor.illuminance);
  document.documentElement.style.setProperty(
    '--opacity',
    luxToOpacity(sensor.illuminance),
  );
};
sensor.onerror = (event) => {
  console.log(event.error.name, event.error.message);
};

(async () => {
  const {state} = await navigator.permissions.query({
    name: 'ambient-light-sensor',
  });
  if (state === 'granted') {
    sensor.start();
  }
})();

En el siguiente video, puedes ver cómo la calculadora comienza a funcionar una vez que enciendo la luz de la habitación lo suficiente. Y ahí lo tienes: una calculadora solar esquelomórfica que realmente funciona. Mi buena y vieja TI-30X SOLAR, probada con el tiempo, ha recorrido un largo camino.

Demostración

Asegúrate de probar la demostración de la calculadora de Designcember y consultar el código fuente en GitHub. (Para instalar la app, debes abrirla en su propia ventana. La versión incorporada que se muestra a continuación no activará la minibarra de información).

¡Feliz Designcember!