Un intento de diseño esqueuomórfico para recrear una calculadora solar en la Web con la API de Window Controls Overlay y la API de sensor de luz ambiente.
El desafío
Soy un niño de la década de 1980. Cuando estaba en el bachillerato, las calculadoras solares estaban de moda. La escuela nos dio a todos una TI-30X SOLAR, y tengo buenos recuerdos de nosotros comparando nuestras calculadoras entre sí 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, pero 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 un gran diseñador, no empecé desde cero, sino con un CodePen de Sassja Ceballos.

Haz que se pueda instalar
Si bien no es un mal comienzo, decidí mejorarlo para lograr un diseño esqueuomórfico increíble. El primer paso fue convertirla en una PWA 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 lo más posible con las apps del sistema operativo. En dispositivos móviles, puedo hacerlo configurando el modo de visualización en fullscreen en el manifiesto de la app web.
{
"display": "fullscreen"
}
En dispositivos con un recorte o un agujero para la cámara, ajustar el viewport 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" />

Cómo combinar con el escritorio
En computadoras de escritorio, hay una función genial que puedo usar:
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 window-controls-overlay primero cuando esté disponible.
{
"display_override": ["window-controls-overlay"]
}
Esto hace que la barra de título desaparezca de manera eficaz 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 esqueuomórfica hacia arriba, a la barra de título,
y el resto de la IU de la calculadora hacia abajo, lo que puedo hacer con un poco 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 podrán arrastrar, ya que la barra de título que suelo usar para arrastrar no está disponible. Al 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 de la superposición de controles de ventana. En un enfoque de mejora progresiva real, 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 usar el debounce en este evento, ya que se puede activar con frecuencia cuando el usuario cambia el tamaño de la ventana. En particular, aplico la clase wco a algunos elementos, por lo que mi CSS de arriba se activa 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 la vieja escuela de Winamp. Ahora puedo colocar la calculadora libremente en mi escritorio y activar la función de controles de ventana haciendo clic en la flecha de la esquina superior derecha.

Una celda solar que realmente funciona
Para que fuera el colmo de lo geek, por supuesto, necesitaba que la celda solar funcionara de verdad. La calculadora
solo debería funcionar si hay suficiente luz. La forma en que modelé esto es configurando la propiedad 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 la calculadora funcione, uso la API de AmbientLightSensor. Para
que esta API esté disponible, tuve que configurar 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 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 lectura nueva disponible. Según una tabla de valores de situaciones de luz típicas, se me ocurrió una fórmula muy simple para convertir el valor de 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 una vez que enciendo la luz de la habitación lo suficiente. Y ahí lo tienes: una calculadora solar esqueuomórfica que realmente funciona. Mi vieja y confiable TI-30X SOLAR ha recorrido un largo camino.
Demostración
Asegúrate de jugar con la demostración de la calculadora de Designcember y consulta 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 barra de información pequeña).
¡Feliz Designcember!