Episodio 012 — Satori y el nacimiento del pixel
Serie: bitácora de ingeniería — caprini.dev
Slug: 012-satori-y-el-nacimiento-del-pixel
Resumen: el SPEC-001 pedía una tarjeta PNG 1200×675 para X; el episodio 011 dejó la elección node-canvas vs Satori en el aire. Aquí cerramos el circuito con Satori + @resvg/resvg-js: el tracker ya puede materializar píxeles a partir del mismo commit que alimenta el tweet y el latido en last-activity.json.
El reto: HTML donde no hay DOM
En el navegador, maquetar es natural: el motor aplica CSS, carga fuentes y pinta. En Node.js no existe ese lienzo: no hay window, ni árbol de accesibilidad, ni composición interactiva. Si quieres HTML declarativo (flex, tipografía, bordes) y terminar en imagen, necesitas algo que simule un subconjunto de layout y tipografía de forma determinista.
Satori hace exactamente eso: toma una estructura estilo React (árbol de nodos + estilos) y produce SVG. No es un navegador completo — es un subset deliberado — pero encaja con quien ya piensa en cajas, colores y monospace como en el sitio Astro. El paso siguiente es rasterizar ese SVG a PNG; en este repo usamos resvg (Rust, vía @resvg/resvg-js) para obtener un buffer estable sin depender de Chromium.
Fuentes y verdad única
Sin archivo de fuente embebido (p. ej. JetBrains Mono en .ttf), Satori no puede medir glifos con la misma precisión que el CSS del blog: el texto se cae a fallback o falla de forma opaca. Por eso la condición de parada del experimento fue clara: sin JetBrains Mono en disco, no hay tarjeta. La tipografía deja de ser “preferencia de diseño” y pasa a ser dependencia de build del generador — alineada con BRANDING.md y con global.css (JetBrains Mono + tokens void / neón).
Cierre
El progress tracker sigue siendo una herramienta pequeña, pero ahora exporta tres superficies coherentes: texto para el portapapeles, JSON para la Home estática y PNG para el feed. El siguiente refinamiento no es “más librerías”, sino pulir layout dentro de los límites de Satori y mantener assets y fuentes versionados donde el script los espera.