Calculadora de diseño

Un intento skeuomó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 niño de la década de 1980. Cuando yo estaba en la escuela secundaria, lo que más causaron eran las calculadoras solares. La escuela nos proporcionó una TI-30X SOLAR, y tengo lindos recuerdos de nosotros comparando nuestras calculadoras entre sí calculando el factorial de 69, el número más alto que el TI-30X podría manejar. (La variación de la velocidad era muy medible, aún no tengo idea de por qué).

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 era mucho diseñadora, no empecé desde cero, sino con un CodePen de Sassja Ceballos.

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

Permite la instalación.

Aunque no fue un mal comienzo, decidí animarlo para lograr una genialidad skeuomórfica. El primer paso fue convertirla en una AWP para que pudiera instalarse. Tengo una plantilla de AWP de referencia en Glitch que creo remixes cada vez que necesito una demostración rápida. Su service worker no te otorgará ningún premio de programación y definitivamente no está listo para la producción, pero basta con activar la minibarra de información de Chromium para que se pueda instalar la app.

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 tanto como sea posible. En dispositivos móviles, puedo hacerlo configurando el modo de visualización en fullscreen en el manifiesto de apps web.

{
  "display": "fullscreen"
}

En los dispositivos con un orificio o una muesca para la cámara, ajusta el viewport de modo que el contenido cubra toda la pantalla para que la app se vea genial.

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

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

Combinación con computadoras de escritorio

En las computadoras de escritorio, hay una función interesante que puedo usar: la superposición de controles de ventana, 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 primero window-controls-overlay cuando esté disponible.

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

Esto hace que la barra de título desaparezca efectivamente y el contenido se mueva al área de la barra de título como si la barra de título no estuviera allí. Mi idea es mover la celda solar eskeuomórfica hacia la barra de título y el resto de la IU de la calculadora hacia abajo según corresponda, lo que puedo hacer con algunas CSS que usan las variables de entorno titlebar-area-*. Notarás que todos los selectores llevan 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 hacer que sean arrastrables, ya que la barra de título que normalmente usaría para arrastrar no está disponible. Al estilo de un widget clásico, incluso puedo hacer que toda la calculadora sea arrastrable si aplicas (-webkit-)app-region: drag, excepto los botones, que obtienen (-webkit-)app-region: no-drag de modo 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 de superposición de los controles de ventana. En un verdadero enfoque de mejora progresiva, solo cargo el código para esta función cuando el navegador lo admite.

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

Cada vez que cambia la geometría de superposiciones de los controles de la ventana, modifico la app para que se vea lo más natural posible. Se recomienda desactivar este evento, ya que puede activarse con frecuencia cuando el usuario cambia el tamaño de la ventana. Por lo tanto, aplico la clase wco a algunos elementos para que se active la CSS anterior y también cambio el color del tema. Para detectar si la superposición de controles de la ventana es visible, verifica 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 que funciona todo esto, obtengo un widget de calculadora que se parece casi a la versión clásica de Winamp con uno de los temas de Winamp tradicionales. Ahora puedo colocar libremente la calculadora en mi escritorio y activar la función de controles de las ventanas con solo hacer clic en el corchete angular de la esquina superior derecha.

La Calculadora de Designcember se ejecuta en modo independiente con la función Window Controls Overlay activa. En la pantalla, se escribe &quot;Google&quot; en el alfabeto de la calculadora.

Una célula solar que funciona realmente

Para ser geek, por supuesto que necesitaba hacer que la célula solar realmente funcionara. La calculadora solo debería funcionar si hay suficiente luz. Modelé esto a través de la configuración del opacity de CSS de los dígitos en la pantalla a través de una variable de CSS --opacity que controlo a través de JavaScript.

:root {
  --opacity: 0.75;
}

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

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

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

El sensor devuelve la luz ambiente en unidades de lux cuando hay una nueva lectura disponible. En función de una tabla de valores de situaciones de luz típicas, se me ocurrió una fórmula muy simple para convertir el valor lux en un valor entre 0 y 1 que asigno de manera 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 cuando se ilumina lo suficiente la habitación. Y ahí lo tienes: una calculadora solar eskeuomórfica que realmente funciona. Mi buen TI-30X SOLAR, que ya estaba probado, ha recorrido un largo camino.

Demostración

Asegúrate de jugar con la demostración de Designcember Calculator y consulta el código fuente en Glitch. (Para instalar la app, debes abrirla en su propia ventana. La versión incorporada que aparece a continuación no activará la minibarra de información).

¡Feliz Designcember!