La historia
Cuaderno

Cuaderno

EspañolEnglish

Cómo está hecho

Primero, un storyboard

Astro construye cada página de antemano, en HTML y CSS simples. El home es primero un storyboard: cada capítulo un cuadro quieto, sus tarjetas en orden, cada resultado por escrito. Un script lo anima con el scroll; sin él, la historia completa sigue ahí. La lógica vive en módulos puros con sus pruebas, y JavaScript corre solo en la historia, el panel del cuaderno y en /finding.

Un motor de escenas propio

La historia se mueve con un motor de escenas escrito para ella, en TypeScript. Las pistas guardan valores ligados al scroll; el motor suaviza el paso entre ellos, para que nada arranque ni pare de golpe, y encuadra la cámara para cada pantalla. Un ciclo lee el scroll nativo y pinta lo que dicen las pistas: nada captura la rueda ni el dedo, y solo cambian transformaciones, opacidad y colores.

/** Quadratic ease-in-out: starts and ends at rest, so no move begins or stops with a jolt. */
export function easeInOut(t: number): number {
  const x = clamp(t, 0, 1);
  return x < 0.5 ? 2 * x * x : 1 - (-2 * x + 2) ** 2 / 2;
}

Mezcla los colores por tono, en OKLCH, nunca canal por canal. Por eso el día de la historia tiene una salida del sol:

El cielo de la historia, del amanecer al anochecer, como lo pinta el motor

Del amanecer directo a la mañana, cada canal de color mezclado por separado

Del amanecer directo a la mañana, girando el tono, como lo hace el motor

Cada muestra es un color que el motor calculó mientras se construía esta página.

Una prueba falla si el cielo se vuelve gris, o si alguna superficie cambia demasiado rápido para verse suave.

El candado

Parte del sitio estuvo cerrada hasta que el documento de trabajo fue público en SSRN: se quedaba fuera del build, no escondida. Un plugin cambiaba cada componente bloqueado por un stub vacío, y un script fallaba si se colaba algún rastro. Ahora está abierta, y los dos siguen corriendo en cada build. La regla es una línea:

export function findingUnlocked(ssrn: string, dev: boolean): boolean {
  return !isPending(ssrn) || dev;
}

Fracciones, no decimales

Los pagos y las probabilidades se guardan como fracciones de enteros: 5/6 sigue siendo 5/6 en cada paso. Solo se redondea al final, al mostrar una proporción sobre 100, y aun eso con enteros:

/** A non-negative fraction on a 0–100 scale, rounded half up to an integer, in integer arithmetic. */
export function outOf100(f: Fraction): number {
  if (f.num < 0) throw new RangeError('outOf100 takes a non-negative fraction');
  return Math.floor((200 * f.num + f.den) / (2 * f.den));
}

Revisiones que detienen el build

Dos idiomas, un solo juego de claves. Una clave que falta en un idioma hace que el verificador de tipos rechace el código:

const esCoversEn: Record<keyof typeof en, string> = es;
const enCoversEs: Record<keyof typeof es, string> = en;

Un registro de cifras. Cada número que la página puede decir está en una lista con su fuente; las leyendas de la historia no llevan ninguno, solo nombres que el build llena desde el código. Una prueba falla con cualquier otro número o con una cita sin fuente.

Solo agregar. Una prueba falla si este cuaderno repite una oración de la historia.

Frases prohibidas. Otra prueba falla con una lista corta de frases que la página nunca debe decir.

Accesibilidad

Cada elección es un botón nativo que funciona con el teclado, y cada resultado se anuncia en una región en vivo. Con movimiento reducido, la historia corta entre cuadros quietos y todos los juegos funcionan. El contraste se prueba en cada punto de la luz del día, y el elenco se distingue con los dos daltonismos más comunes.

Peso y velocidad

El script del home, sus tipografías y su primera carga tienen un techo, fijado antes de escribir la historia; un script revisa cada build.

La primera carga de la historia, frente a sus techos
Qué se mideTechoMedido
Su script40 KiB21,1 KiB
Sus fuentes160 KiB118,6 KiB
Todo lo que carga450 KiB184,9 KiB
El mayor elemento pintado (LCP)2,5 s2,1 s

Los tamaños, como viajan: comprimidos, pesados por el mismo script que revisa cada build. El mayor elemento pintado: Lighthouse 13.5.0, 10 de octubre de 2026: un moto g power (2022) emulado en una red 4G lenta simulada, con el procesador 4 veces más lento; la mediana de 3 corridas.

Una bitácora de decisiones

Cada decisión que le dio forma a la página está escrita en un registro corto: qué, por qué y qué reemplazó. Una decisión cambia solo con un registro nuevo; el anterior se archiva, nunca se reescribe.

El motor

La curva sale de un motor de simulación propio, escrito en TypeScript y sin dependencias en tiempo de ejecución. Cuatro decisiones le dan forma:

/** Rolls only if it is strictly better. At a tie, Don't. */
export function choose(spec, sens, match, cap) {
  const roll = utility(spec, "roll", sens, match, cap);
  const dont = utility(spec, "dont", sens, match, cap);
  return roll > dont ? "roll" : "dont";
}

El motor exporta la curva con su procedencia; de este lado, una prueba falla si la curva cambia o si falta el archivo. github.com/Montse2308/promises-to-whom-engine (se abre en otra pestaña) (DOI 10.5281/zenodo.23222610 (se abre en otra pestaña))

← Volver a «Cierre»