Folio
Una lámina Folio explica una idea como un museo de historia natural explica un espécimen. Hay un objeto sobre un soporte, una clave numerada a su lado, y textos de pared que te dicen cómo mirarlo. El objeto aquí es un instrumento que inventas, como un banco de pruebas, una balanza, un telar o un alambique, cuyas piezas funcionales ejecutan la idea. Los lectores pueden manipularlo, y cada lectura en él se calcula.
Tres compromisos
- Mecanismo, no metáfora. El instrumento debe realizar la idea, no quedarse a su lado como una ilustración. Antes de dibujar, escribe una línea: "Cuando el lector hace X, la parte Y se mueve, y ese movimiento es Z en la idea."
- Una única fuente de números. Un modelo puro y pequeño lo calcula todo: ángulos de agujas, niveles de fluido, la lectura, la clave, las tablas. La página solo muestra lo que el modelo devuelve, para que no haya dos lugares en desacuerdo.
- Una lectura guiada. Un buen dibujo aún necesita una guía. Cada lámina incluye una clave, una lectura término por término, el funcionamiento completo y un relato honesto de lo que se simplificó.
Cómo empezar
- Se incluyen cuatro láminas de referencia en el repositorio. Lámina I (
examples/attention/, la atención como un banco de ensayos) es el modelo para ideas hechas de pasos discretos y una fórmula. Lámina II (examples/heart/, el corazón humano como una doble bomba) es el modelo para cualquier cosa que funcione en el tiempo: una simulación integrada en cada fotograma, flujo continuo, fases y gráficos redibujados tras cada ciclo. Lámina III (examples/grid/, una red eléctrica en miniatura) es el modelo para electricidad, magnetismo y flujos de energía, y para un banco construido con máquinas independientes en lugar de bandas: una turbina, un generador con rotor giratorio, transformadores, torres y una ciudad. Lámina IV (examples/hashmap/, un mapa hash como una oficina de clasificación) es el modelo para algoritmos y estructuras de datos, donde la idea es una secuencia de operaciones discretas: cada operación (insertar, buscar, redimensionar) se expande en una lista corta de acciones temporizadas (levantar, llevar, sumergir para comparar, volar cada tarjeta a un nuevo lugar), ejecutadas una tras otra, para que el lector observe el algoritmo dando cada paso. Abre primero la más cercana. - La forma más rápida de empezar es copiar una lámina de referencia:
node scripts/new-plate.mjs <nombre>creaexamples/<nombre>/a partir de la Lámina I. Luego reemplaza su modelo, escena y textos. Construir a partir de una lámina funcional es más rápido y seguro que empezar desde cero. - Los estilos de exposición compartidos viven en
kit/folio-page.css; elpage.htmlde una lámina coloca/*CSS*/dentro de su<style>y añade solo sus propias reglas. node scripts/build-site.mjsconstruye cada lámina más la página de portada (site/index.html) dentro dedocs/, un sitio web listo para GitHub Pages. Para añadir una lámina, agrega su nombre aPLATESallí y una tarjeta a la portada.- Construye cualquier lámina en un único archivo independiente con
node scripts/build.mjs examples/<nombre> dist/<nombre>.html. - Si solo este archivo está instalado (sin repositorio), el apéndice al final contiene la biblioteca de dibujo; guárdalo como
folio-kit.js.
Una lámina consta de cinco archivos:
| Archivo | Función |
|---|---|
folio-kit.js | la biblioteca de dibujo (compartida, nunca se edita por lámina) |
folio-live.js | las tres vistas: escenario, capa de luz, órbita, cambio de vista (compartido) |
model.js | el modelo puro; coloca un objeto en window |
plate.js | el instrumento: geometría estática, ranuras en vivo, entrada |
page.js | clave, controles, textos de pared, libro mayor; escucha la lámina |
page.html | el soporte y los estilos, con marcadores de posición /*KIT*/ /*MODEL*/ /*PLATE*/ /*PAGE*/ |
Fase A: el resumen (aún sin dibujo)
A1. Alcance. Dile al usuario cuál de estos está obteniendo y cuánto tiempo llevará aproximadamente, antes de empezar:
| Alcance | Contenido | Tiempo típico |
|---|---|---|
| Boceto | instrumento + clave + lectura, sin textos de pared | menos de una hora |
| Lámina | la página de exposición completa (la opción por defecto para "explicar X") | de una hora y media a tres horas |
| Serie | varias láminas sobre un mismo banco compartido, escala y paleta | acordar la lista primero, luego una lámina a la vez |
A2. El modelo. Escribe model.js antes de cualquier cosa visual. Reúne la fórmula real, las unidades y las magnitudes. Devuelve objetos simples: entradas recibidas, cada cantidad que la página mostrará. Ejecútalo en Node e imprime algunos casos. Busca casos límite ahora (un conjunto vacío, una fila enmascarada, un valor en el límite). Si algo es inventado, como pesos de juguete o datos de muestra, anótalo para el texto de pared "Acerca de este espécimen".
A2b. Modelos que funcionan en el tiempo. Cuando la idea es un proceso (un latido del corazón, una cola que se vacía, una curva de aprendizaje), escríbelo como estado más una función step(s, dt) y un read(s) que devuelva todo lo mostrado. Ajusta los parámetros con valores conocidos en Node antes de dibujar, como hace la Lámina II para un adulto en reposo (aproximadamente 115/80 mmHg, 70 mL por latido, 5 L/min). Mantén la simulación funcionando mientras el dibujo está fuera de pantalla, y salta solo la pintura; de lo contrario, cambiar un control más abajo en la página congelaría los números.
A3. El instrumento y su tabla de correspondencia. Busca un instrumento del mundo de los laboratorios, talleres, observatorios o imprentas. Luego escribe la tabla que vincula cada término con una parte:
| Término en la idea | Parte del instrumento | Lo que el lector puede ver |
|---|---|---|
| consulta | un carro sobre un riel aéreo | se estaciona sobre la palabra que pregunta |
| puntuación escalada | un dial por palabra | posición de la aguja |
| softmax | un depósito sellado | las columnas solo pueden dividir lo que contiene |
| máscara | llaves de paso con tapas | las palabras posteriores están cerradas |
| suma ponderada | una bandeja de capas depositadas | la longitud de cada capa es un peso |
Cada fila necesita una parte, y cada parte importante necesita una fila. Algunas correspondencias fiables: un total fijo se convierte en un depósito, una bolsa o un saco de pesos. La comparación se convierte en un dial, una balanza o un calibre. La selección se convierte en una válvula, un interruptor o una palanca de cambios. La acumulación se convierte en capas, una pila o un contador. La ordenación se convierte en un riel, un bastidor o un conducto.
Fase B: el banco
B1. Inventario. Enumera las piezas antes de colocarlas. Apunta a entre 30 y 60. Agrúpalas por propósito: piezas de término (de la tabla, repetidas por elemento), piezas de soporte (plinto, patas, postes, rieles, collares, soportes), piezas de control (selectores, perillas moleteadas, palancas) y detalles del montaje (placa de catálogo, etiquetas grabadas, tiras de escala, tornillos). Exactamente una cantidad, aquella sobre la que trata la idea, recibe el color rojo.
B2. Bandas. Usa coordenadas del mundo con x a lo largo del banco, y hacia el espectador y z hacia arriba. Divide el banco en bandas de profundidad de atrás hacia adelante, y asigna a cada banda un tipo de pieza. La Lámina I sigue riel, columnas, llaves de paso, colector, diales, bandeja. Deja espacio entre bandas en unidades del mundo, porque una pieza que sobresale en la siguiente banda se pintará en el orden incorrecto. Dentro de una banda, dibuja de lejos a cerca. Configura la cámara (az cerca de -24 para un banco largo, el 30) y ajústala a las esquinas con Folio.fit.
B3. Cadenas estáticas, ranuras en vivo. Recorre las bandas una vez y empuja cadenas SVG a una lista ordenada. Donde algo se mueva, empuja una ranura con nombre en su lugar. Al momento del montaje, la lista se convierte en un SVG con un <g> vacío por ranura. Cada fotograma de animación redibuja solo las ranuras, a partir de valores suavizados que persiguen los objetivos del modelo. El dibujo se detiene una vez que todos los valores han llegado.
B4. Pines. Coloca de 6 a 10 pines numerados directamente sobre las piezas que portan la idea, numerados en el orden en que un recién llegado debería mirarlos. Los pines son las únicas marcas superpuestas al dibujo. Todo el demás texto está grabado en una superficie: nombres en los frentes de las almohadillas, números en las escalas, una placa de catálogo en el plinto.
Fase C: el montaje
La página es parte de la explicación. Constrúyela a partir de estos bloques, de arriba a abajo:
- Cabecera. La serie a la izquierda, la marca de palabra centrada, el número de lámina a la derecha.
- Bloque de título. Un título destacado que nombra la idea como el instrumento ("Atención, como un banco de ensayos."), tres frases sobre lo que sucede y una pequeña lista de catálogo: Objeto, Idea, Modelo, Catálogo.
- La lámina montada. Un soporte de papel con un marco incrustado de línea fina. Sus esquinas llevan el número de lámina, un pie de foto breve, la pista de interacción y una lectura en vivo.
- La clave. Una entrada por cada pin, que da el nombre de la pieza y una línea sobre lo que calcula, con las matemáticas en mono. Al pasar el ratón sobre una entrada, se resalta su pin, y al pasar el ratón sobre un pin, se resalta su entrada.
- En el banco. Botones y deslizadores comunes que hacen lo mismo que las propias perillas del instrumento.
- Texto de pared I: leer la lámina. Una columna para cada término, en el orden en que está escrita la fórmula: un número romano, un verbo, el término, dos frases que se refieran a los pines por número, y una caja con los números actuales.
- Texto de pared II: el libro mayor. El funcionamiento completo para el estado actual, una fila por elemento. Al hacer clic en una fila, se selecciona ese elemento.
- Texto de pared III: la visión más amplia. Lo que el instrumento muestra de un corte (la matriz completa, la secuencia entera, cada cubo), y las variantes entre las que el selector elige.
- Texto de pared IV: acerca de este espécimen. Tres notas: qué se calcula exactamente, qué se eligió a mano y qué se omitió. Inclúyelo siempre.
- Colofón. Quién hizo la lámina y qué tipografías utiliza.
Los controles responden al puntero y al teclado: haz clic en una estación, las flechas avanzan, los dígitos eligen una variante, los corchetes giran una perilla, una letra alterna un interruptor. Cuando nadie interactúa, la lámina se demuestra lentamente a sí misma. Se pausa al pasar el ratón o al enfocar, descansa cuando se desplaza fuera de la vista, y muestra un fotograma fijo bajo movimiento reducido. La lectura usa aria-live.
Fase C2: tres vistas
Cada lámina ofrece el mismo instrumento de tres maneras, elegidas desde un pequeño interruptor en la esquina superior del montaje (SVG · WebGL · 3D). El modelo, la clave y los textos de pared no cambian entre vistas; solo cambia la forma en que se muestra el banco.
SVG es el dibujo grabado descrito anteriormente. Es la vista por defecto y la única que siempre debe funcionar.
WebGL coloca luz sobre el dibujo sin redibujarlo. La luz vive en un canvas sobre el SVG, y sabe lo que es visible a través de una máscara renderizada a partir del propio dibujo: light() clona el SVG, recolorea cada superficie según su función (rojo para el suelo, verde para recipientes y tuberías, azul para vidrio, negro para todo lo demás), mantiene el orden del pintor y sube el resultado como una textura. Cualquier cosa que el dibujo oculte, la luz no puede tocarla, por lo que no se necesita lógica de oclusión separada. Asigna a cada superficie su función con una clase (floor en el plinto, pad en receptores elevados, pipe o las clases sanguíneas en recipientes) y pasa las reglas de color como maskCss.
Dentro del sombreador, onPlane(vb, z) convierte un píxel de nuevo en un punto del mundo en un plano horizontal, project() va en la otra dirección, y fall() encuentra dónde la luz a través de una parte cae sobre el suelo (la lámpara está detrás del banco, por lo que los charcos caen delante de las partes, donde el lector puede verlos). Usa la luz solo para la cantidad temática:
- Lámina I: el fluido de cada columna brilla con su peso, un haz de luz ondulada recorre cada columna a lo largo del banco, y pulsos recorren el colector, más rápidos mientras los pesos se desplazan.
- Lámina II: los pulsos viajan a lo largo de cada vaso al flujo real (el mismo volumen acumulado que mueve las marcas de flujo SVG), cada cámara proyecta luz del color de su sangre, y los ventrículos se encienden al contraerse.
Acumula la luz como color premultiplicado (addLight) y elige colores por tema: reflejos pálidos y charcos saturados sobre papel (mezcla normal), brillos más intensos sobre el suelo oscuro (mezcla de pantalla, establecida por el token --light-blend). Renderiza solo mientras la vista está activa y la lámina está en pantalla, limita la relación de píxeles a 1.5, y dibuja un fotograma fijo bajo movimiento reducido.
3D gira el banco por completo. Como toda la escena es función de la cámara, stage.mount(az, el) simplemente la construye de nuevo desde el nuevo ángulo: cada cara se sombrea nuevamente según su orientación, cada parte redonda se vuelve a siluetear, cada tramado se reespacia, y cada etiqueta grabada se reproyecta. El orden del pintor debe mantenerse desde todos los lados, así que nunca codifiques "de atrás hacia adelante":
- Envuelve cada banda de profundidad como
{ y, draw }y pasa la lista aapi.bands(list); las ordena de lejos a cerca para la cámara actual. Desde atrás, la banda frontal se dibuja primero. - Dentro de una banda, o para un banco de máquinas separadas, da a cada unidad una posición en el mundo y usa
api.units(list)con{ at: [x, y, z], draw }. La Lámina III está construida completamente de esta manera. - Las paredes de un recipiente abierto (una bandeja, un tanque) se ordenan con
api.backIsFaryapi.leftIsFar. - Cualquier cosa montada en una cara del plinto (una placa de catálogo de latón, una leva de sincronización en un panel frontal) se dibuja solo cuando esa cara mira hacia el lector: verifica el signo de
cam.viewprimero.faceTextya se autoelimina.
orbit({ wrap: true }) permite que el azimut supere los 360°, stage reajusta la escala en cada ángulo (limitado por orbitScale, para que el banco nunca salte de tamaño), el doble clic vuelve a la vista por defecto, y Mayús con las teclas de flecha lo gira desde el teclado. Verifica con una cuadrícula de ángulos cada 45° en dos elevaciones. Una reconstrucción completa de la Lámina II toma aproximadamente 27 ms, lo cual es suficientemente fluido para arrastrar; si una lámina es mucho más pesada, memoriza la última salida de cada ranura y repinta solo lo que cambió, como hace la Lámina III.
Salir de la vista 3D devuelve la cámara a la vista por defecto, para que la luz WebGL (que la asume) siempre coincida.
Fase C2b: piezas internas en movimiento
Una lámina nunca debe ser una imagen fija con números a su lado. Encuentra las piezas que realmente se moverían y condúcelas desde el modelo, a una velocidad que el ojo pueda seguir:
- Cosas que giran. Una rueda de flujo en un visor de nivel gira al flujo real (el colector de la Lámina I), las levas de sincronización giran una vez por ciclo y muestran qué fase está activa (Lámina II), y el rotor de un generador, una rueda hidráulica y un regulador centrífugo giran con la velocidad de la máquina (Lámina III). Acumula el ángulo desde el modelo (
rot += ω·dt) en lugar del reloj, para que una máquina que se frena visiblemente se frene. - Cosas que se desplazan. Niveles, pistones, valvas de válvulas, agujas, las bolas de un regulador que se expanden al aumentar la velocidad, una cuchilla de interruptor que se levanta.
- Cosas que interactúan. El movimiento de una parte afecta a otra. Una leva abre una válvula; un pistón empuja fluido a través de un conducto; un engranaje motriz hace girar un dial. Modela estas interacciones en
model.jsy luego encuentra los puntos de contacto visuales. - Cosas que fluyen. Partículas, burbujas o marcas de flujo que se mueven a lo largo de tuberías y conductos a la velocidad del modelo. Las marcas de flujo SVG son simples: una serie de puntos o guiones que se desplazan. Para flujos más complejos, considera usar el canvas WebGL para una animación de partículas.
Fase D: pulido y publicación
D1. Prueba la lámina. Abre el archivo HTML en un navegador. Comprueba:
- El modelo se carga y la lámina se muestra sin errores en la consola.
- Los controles (botones, deslizadores) cambian los valores del modelo y el instrumento responde.
- Las tres vistas (SVG, WebGL, 3D) funcionan y muestran el mismo estado.
- Los pines se resaltan al pasar el ratón sobre las entradas de la clave, y viceversa.
- Los textos de pared se muestran correctamente y se actualizan con los cambios del modelo.
- La lámina se demuestra a sí misma cuando no hay interacción.
- La accesibilidad:
aria-liveanuncia los cambios en la lectura. - El rendimiento: la lámina se carga y responde con fluidez.
D2. Publica la lámina. Si estás en el repositorio, ejecuta node scripts/build-site.mjs para construir todo el sitio en docs/. Luego sube el repositorio a GitHub y activa GitHub Pages desde la carpeta docs/. Si solo tienes un archivo independiente, súbelo a cualquier alojamiento estático (Netlify, Vercel, GitHub Pages, etc.).
D3. Comparte la lámina. Proporciona el enlace a la página publicada. Incluye una breve descripción de lo que hace la lámina y cómo interactuar con ella.
Apéndice: la biblioteca de dibujo (folio-kit.js)
Este archivo contiene las funciones básicas de dibujo SVG que utiliza cada lámina. Normalmente no necesitas editarlo. Sus funciones principales incluyen:
Folio.fit(camera, corners): ajusta la cámara para que las esquinas dadas encajen en la vista.stage.mount(az, el): construye la escena del banco desde el ángulo de cámara dado.api.bands(list): ordena una lista de bandas de profundidad de lejos a cerca.api.units(list): ordena una lista de unidades de lejos a cerca.api.backIsFar, api.leftIsFar: ayudantes para ordenar caras de recipientes abiertos.faceText(text, ...): dibuja texto grabado en una cara, autoeliminándose si la cara no es visible.light(): genera la máscara de luz WebGL a partir del SVG actual.addLight(color): acumula luz premultiplicada en el canvas WebGL.orbit(options): habilita el control de órbita 3D.
Para más detalles, consulta el código fuente en el repositorio.