Communitygithub.com

cxb2523/gsb-828-9f38b84355494457

Crea videos animados "code-rendered" donde un programa dibuja cada fotograma (Canvas 2D + Web Audio procedural), renderizados sin interfaz gráfica a MP4 en 16:9 / 9:16 / 4:5 desde una línea de tiempo. Útil para explicaciones animadas, tutoriales técnicos, diagramas en movimiento y sonido o música procedural, donde el resultado debe ser código editable y no píxeles generados.

¿Qué es gsb-828-9f38b84355494457?

Este skill permite a un agente de IA (como Claude Code, Cursor o Codex) generar videos animados completamente programáticos. La idea central, atribuida a Addy Osmani, es que un modelo de codificación lea una fuente (artículo, especificación) y escriba JavaScript que dibuje cada fotograma más el sonido. El entregable es un programa, por lo que cualquier momento puede editarse, re-timearse, re-renderizarse o reformatearse. El skill se basa en el proyecto de código abierto riso-windowseat (MIT) y toma ideas de HeyGen HyperFrames y Remotion. Soporta múltiples formatos de video (16:9, 9:16, 4:5, 1x1) a partir de una sola línea de tiempo, con verificación automática de cada fotograma, audio procedural (OfflineAudioContext, con opción de síntesis bit-exacta), y un conjunto completo de herramientas de aseguramiento de calidad: verificación de consistencia, linting de texto/área segura/estáticos, revisión humana con hojas de contacto, y QA final del MP4 (detección de negro, congelados, sonoridad EBU R128). Está diseñado para que el agente pueda iterar rápidamente: storyboard, implementación, verificación, revisión y entrega. La salida es siempre código editable (un solo archivo index.html autónomo) que puede ejecutarse sin red ni dependencias externas más allá de Node.js, ffmpeg y Chrome.

Compatible con✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/cxb2523/gsb-828-9f38b84355494457/tree/HEAD

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Animaciones code-rendered

La idea (Addy Osmani, LinkedIn 2026-09-24): un modelo de codificación lee una fuente (un artículo, una especificación) y escribe JavaScript que dibuja cada fotograma, además del sonido. El entregable es un programa, por lo que cualquier momento puede editarse, re-timearse, re-renderizarse o reformatearse. Osmani no publicó su prompt ni su herramienta. El diseño de este skill proviene del proyecto de código abierto riso-windowseat (MIT, solo referencia, no copies sus instrucciones): https://github.com/sevenevesai/riso-windowseat . También toma ideas de HeyGen HyperFrames (https://hyperframes.heygen.com/guides/pipeline) y Remotion.

Reglas básicas: revisar fotograma por fotograma a 4+ muestras por segundo, nunca afirmar una comprobación que no se haya ejecutado, y corregir defectos de forma quirúrgica.

El contrato (todo depende de ello)

Cada trabajo es un index.html autónomo con un <canvas> que lee ?w=&h= de la URL y expone:

window.__anim = {
  duration, fps, width, height, canvas,
  ready,            // false hasta que las fuentes/activos estén cargados y seek(0) haya dibujado
  seek(t),          // dibuja exactamente el fotograma en el tiempo t, de forma síncrona
  renderAudio(),    // Promise<base64 WAV>, 48 kHz estéreo, exactamente `duration` de duración
  marks,            // tiempos de eventos visibles (pops, cortes) - el sonido debe coincidir con estos
  shots,            // [{id,start,end}] - la verificación comprueba los límites
};

Invariantes:

  • seek(t) es una función pura de t. Cada valor en pantalla se calcula a partir de t: prog(t,a,b), easing, pop(t,at). No hay estado entre fotogramas, no hay temporización con requestAnimationFrame, no hay Date, no hay simulación física a menos que se recalcule desde 0 o en forma cerrada.
  • Nunca llamar a Math.random() durante el renderizado. Usar rngFor('stable-key') (mulberry32 con semilla), y crear diseños aleatorios una vez al cargar a partir de claves fijas. Cambiar una clave cambia la salida, así que congelar las claves después de la aprobación.
  • Un objeto de línea de tiempo (TL) contiene todos los rangos de escenas y tiempos de eventos. La imagen y la partitura leen de él. Así funciona la sincronización: el sonido se programa en los mismos números que la imagen aparece.
  • Esperar a las fuentes antes de ready=true (document.fonts.load('700 40px "DejaVu Sans"')). De lo contrario, los primeros fotogramas se renderizan con una fuente de respaldo. Comprobar las fuentes instaladas con fc-list : family (Linux/macOS) o C:\Windows\Fonts (Windows; Segoe UI, Bahnschrift, Consolas y Georgia están siempre presentes, DejaVu no). Si el diseño necesita una fuente específica, incrustarla como un @font-face en base64 y cargarla de la misma manera.
  • No hay llamadas de red en el trabajo. Incrustar cualquier activo como URI de datos.

Elementos editables (para film-fix)

review/film-fix.html permite a un revisor mover y escalar elementos directamente y escribe el resultado de vuelta en index.html. Las películas que quieran esto (el ejemplo lo hace; copiarlo de allí) añaden:

  • const TUNE = /*TUNE*/{}/*END-TUNE*/; en una línea, indexado por formato y luego por id de elemento: {"16x9":{"title":{"dx":0,"dy":0,"s":1}}}. Está indexado por formato porque cada aspecto tiene su propio diseño. dx/dy son píxeles del canvas. La herramienta reescribe solo el texto entre los dos marcadores.
  • E(id, [x,y,w,h], draw) alrededor de cada elemento principal. Busca TUNE[fmt][id], luego guarda, traslada por dx/dy, escala por s alrededor del centro de la caja, llama a draw() y restaura. También añade {id, box} a __anim.elements, donde box es la caja transformada en píxeles del canvas (de ctx.getTransform(), por lo que los movimientos de cámara se respetan). seek(t) limpia esa lista al inicio de cada fotograma.
  • __anim.elements, __anim.format (16x9, 9x16, 4x5, 1x1) y __anim.tune (getter/setter para el objeto TUNE en vivo, usado para la vista previa en vivo de la herramienta).
  • Reglas: los ids son únicos en toda la película; sin aleatoriedad; con TUNE vacío, E no aplica ninguna transformación, por lo que los fotogramas permanecen idénticos byte a byte. Demostrarlo con un hash de fotograma antes y después de añadir E, luego ejecutar verify.mjs.

Las películas sin E todavía funcionan en film-fix: su modo "Nudge" encuadra una región y convierte un arrastre o escalado en una corrección numérica para la IA.

Configuración del proyecto

Requiere Node 18+, ffmpeg (libx264, aac, blackdetect, freezedetect, ebur128) y un Chrome/Chromium local. El harness encuentra Chrome en las ubicaciones de instalación habituales en Windows, macOS y Linux; configurar la variable de entorno CHROME para sobrescribir. qa.sh necesita bash (Git Bash en Windows) y elige una fuente del sistema para el timecode; configurar QA_FONT para sobrescribir. No se instala nada a nivel de sistema; puppeteer-core es la única dependencia npm.

SKILL_DIR=<ruta a esta carpeta de skill>
mkdir -p anim && cp -r "$SKILL_DIR/harness" anim/harness
cd anim/harness && npm install && cd ..
mkdir -p works/<nombre>
cp "$SKILL_DIR/example/browser-10s/index.html" works/<nombre>/index.html   # comenzar desde el ejemplo funcional
# ejecutar todos los comandos del harness desde anim/; las salidas van a out/

Comandos del harness

ComandoQué haceSeñal de éxito
node harness/verify.mjs works/<n>/index.html --format 16x9Busca hacia adelante, hacia atrás y saltos en frío en las muestras + límites de tomas, luego compara hashes de píxelesverify: OK, exit 0
node harness/shoot.mjs works/<n>/index.html --format 9x16 --times 1.5,3.4 o --range 3:4:0.1 --out out/stillsPNGs nativos del canvas en tiempos exactosMíralos
node harness/audio.mjs works/<n>/index.html --twiceRenderiza solo la partitura (segundos), comprueba la duración exacta y la determinismo entre ejecucionesSin FAIL. Chrome Web Audio puede diferir en 1 LSB entre ejecuciones; la tolerancia es 1
node harness/render.mjs works/<n>/index.html --format 16x9 --fps 30 --out out/<n>-16x9.mp4Busca cada fotograma y envía PNGs a ffmpeg (libx264 crf18, yuv420p, +faststart), luego multiplexa renderAudio como AACImprime fotogramas y tiempo. Exit 2 en errores de página. --from/--to o --silent renderiza un rango sin audio
node harness/lint.mjs works/<n>/index.html --all-formatsPuerta de calidad en la propia página (16:9 + 9:16; --format para uno): tamaño de texto, área segura, superposición de texto, tramo estático, fotogramas vacíos, lista de tomas, sonido en las marcas, palabras en pantallalint: OK (0 FAIL), exit 0
node harness/review.mjs works/<n>/index.html --format 16x9 --out out/review-16x9, luego --check out/review-16x9/REVIEW.mdUna hoja de contacto por toma (primero, 6 espaciadas uniformemente y último fotograma, timecode quemado), el JSON de lint, y REVIEW.md con preguntas fijas por toma. --check falla en una respuesta vacía o TODO, una puntuación inferior a 4, un FAIL de lint, una sección de toma eliminada o una película cambiada desde que se hicieron las hojasreview check: OK, exit 0
bash harness/qa.sh out/<n>-16x9.mp4 4ffprobe, blackdetect, freezedetect, EBU R128 loudness, duración de audio/video, hojas de contacto a N muestras/segundo con timecode quemadoVer QA abajo

Formatos: 16x9 1920x1080, 9x16 1080x1920, 4x5 1080x1350, 1x1 1080x1080. Los fotogramas van a ffmpeg como JPEG q95 por defecto, aproximadamente 16 fps a 1080p, por lo que una pieza de 40 s tarda unos 60-75 s por formato. --png es sin pérdidas pero aproximadamente 4 veces más lento.

Renderizados largos: la llamada al shell de un agente puede agotar el tiempo de espera (a menudo a 120 s) y llevarse consigo sus procesos hijos. O aumentar el tiempo de espera de la llamada o ejecutar renderizados completos de forma independiente y consultar el registro (se muestra para Linux/macOS; en Windows usar un shell en segundo plano o Start-Process):

setsid nohup bash -c 'node harness/render.mjs works/<n>/index.html --format 16x9 --out out/<n>-16x9.mp4; echo RENDER_DONE' > out/render.log 2>&1 < /dev/null & disown
tr '\r' '\n' < out/render.log | tail -2

También mantener los heredocs que escriben un archivo de trabajo separados de los comandos que lo ejecutan, para que un tiempo de espera no pueda perder el archivo.

Definición de completado

Los comandos exactos están en AGENTS.md (la fuente única, compartida con otras herramientas de codificación): verificar en cada formato, audio --twice, lint --all-formats con 0 FAIL, review.mjs luego review.mjs --check aprobado, renderizar, qa. Nunca afirmar completado con una puerta fallida, y nunca debilitar una puerta o editar umbrales del harness para aprobar una.

Flujo de trabajo

  1. Brief. Lo que dice la fuente, la audiencia, la duración, los formatos, narración o no. Extraer las afirmaciones clave del artículo fuente. Cada escena debe mostrar lo que se dice. Historia antes que corrección. El harness hace que una película sea correcta. No la hace visible. Antes del storyboard, responder cuatro preguntas en el brief:
    • ¿Quién es el personaje, y qué sienten en cada escena?
    • ¿Qué objeto cotidiano representa cada idea? (El original usaba una carta con un sello de cera, un arenero y un pueblo de casas.)
    • ¿Qué HUD persistente mantiene unidas las escenas? Normalmente un dial de capítulo y un titular escrito.
    • ¿Cómo vuelve el último fotograma al primero? Una película sin nada de esto pasa todas las puertas y aún así se la pasa de largo.
  2. STORYBOARD.md antes que el código. Una fila por escena: rango de tiempo | contenido en pantalla | acción | señal de audio. Listar cada tiempo de evento (estos se convierten en TL y marks). Planificar un corte o un nuevo movimiento cada pocos segundos y sin tramo estático. Planificar las transiciones como un empuje o superposición, nunca una caída a un fotograma vacío. Sin fundido a negro al final a menos que esté aprobado.
  3. Módulo de línea de tiempo, luego una función sceneN(t) por escena (pura en t), luego un compositor que dibuja el fondo, las escenas activas y las transiciones.
  4. El fotograma más difícil primero. Construir el momento más denso, shoot en cada formato, mirarlo, arreglarlo. Luego construir el resto.
  5. verify en cada formato, audio --twice, luego render cada formato.
  6. QA (abajo), arreglar quirúrgicamente, re-renderizar solo lo necesario, re-QA.
  7. Entregar los MP4s como adjuntos con los resultados de QA y las debilidades conocidas.

Con narración

Primero TTS, luego marcas de tiempo a nivel de palabra (transcribir el WAV con cualquier herramienta que devuelva tiempos por palabra), luego configurar TL a partir de las palabras. Las escenas, los pops y los resaltados de subtítulos karaoke dependen de los tiempos de las palabras. Reglas: subtítulos estilo karaoke en un recuadro, solo una pista de subtítulos, números en pantalla cuando se hablan, comprobar nombres propios de oído. Incrustar el WAV de narración como URI de datos y mezclarlo dentro de renderAudio() (decodificar con ctx.decodeAudioData, programar en 0), o multiplexarlo con ffmpeg y bajar la partitura debajo de él. Normalizar la sonoridad de la narración en todo el video.

Multi-formato desde una línea de tiempo (no recortes)

  • Mismo TL, mismas escenas, un diseño diferente. Calcular portrait = H > W, S = min(W,H)/1080 (escala de tipo y forma), M = 0.06*min(W,H) (margen seguro). Distribuir por aspecto: las filas en 16:9 se convierten en columnas en 9:16, los árboles se vuelven más estrechos, etc.
  • Texto: shrink-to-fit (bucle measureText contra W-2M) para que nada se corte nunca en el borde. Los tamaños provienen de S. En retrato, usar tipos más grandes y llenar la altura.
  • No tratar "retrato" como un solo caso. 4:5 (1080x1350) es mucho más corto que 9:16. El diseño de columnas 9:16 del ejemplo ejecutado en 4:5 empuja la cuadrícula de píxeles y la etiqueta fuera de la parte inferior y una encima de la otra. Ramificar en la relación de aspecto (H/W: aproximadamente 0.56, 1.25, 1.78) o diseñar desde la altura disponible, y shoot cada formato que entregues.
  • Recordar el overshoot de pop (easeOutBack ~1.1x) y las escalas "calientes" al comprobar márgenes. Verificar el fotograma más denso de cada formato con shoot.

Sonido procedural (OfflineAudioContext)

  • new OfflineAudioContext(2, 48000*duration, 48000), construir el grafo, await ctx.startRendering(), codificar a WAV PCM de 16 bits, devolver base64 (ver wavB64 en el ejemplo).
  • Patrón del ejemplo: un acorde pad por escena (pares de seno/triángulo desafinados, ataque lento, superpuestos en cortes), un pluck (triángulo + 2 armónicos, ataque de 5 ms, decaimiento exponencial) en cada evento de marks, clics de ráfaga de ruido con semilla para escritura, un golpe de caída de tono para un impacto, y un DynamicsCompressor en el maestro.
  • El ruido proviene del RNG con semilla, nunca Math.random.
  • Sonoridad: apuntar a aproximadamente -14 LUFS integrado y pico por debajo de -1 dBFS para feeds sociales. La publicación original midió -14.3 LUFS; el ejemplo incluido es más silencioso a -17.2 LUFS, pico -1.4. Configurar esto con ganancia maestra en la página para que permanezca en el código.
  • Para audio bit-exacto, sintetizar muestras en JS plano en un Float32Array (suma de osciladores con envolventes de forma cerrada) en lugar de nodos Web Audio.
  • Tone.js Tone.Offline (https://tonejs.github.io/docs/14.9.17/functions/Offline.html) es una opción si se necesita una API musical más rica. Debe estar incrustado en línea ya que el trabajo no tiene acceso a la red.

QA (la revisión es nuestra, no del espectador)

Puertas automatizadas, todas en el MP4 final:

  • verify OK en cada formato. Errores de página = fallo.
  • ffprobe: tamaño esperado, 30/1 fps, nb_frames = duración x fps, duración de audio = duración de video.
  • blackdetect: ninguno (a menos que haya un ritmo negro dramático aprobado). freezedetect (>=2 s estático): ninguno.
  • ebur128: sonoridad integrada y pico verdadero dentro del objetivo. Consistente entre episodios.

Lint (puerta de calidad)

node harness/lint.mjs works/<n>/index.html --all-formats [--json out/lint.json] busca el trabajo cada 0.1 s (--step) y envuelve fillText/strokeText desde fuera, por lo que se ejecuta en cualquier trabajo que siga el contrato sin cambios. Falla en:

  • text-size: texto en reposo por debajo del 2.6% de min(W,H) (28 px a 1080); por debajo del 3.3% (36 px) es un WARN. El texto reducido por un zoom out por debajo de la escala 0.5 se trata como un mapa de visión general y solo se advierte.
  • safe-area: texto en reposo dentro del 4% de un borde; 4-6% es un WARN.
  • text-overlap: dos cadenas visibles diferentes que comparten más del 15% de la caja más pequeña durante 0.2 segundos o más. Los pasos más cortos son WARNs.
  • static-stretch: más de 3 s sin cambio visible (2 s es un WARN). empty-frame: cualquier fotograma casi uniforme, incluidos el primero y el último.
  • shots-coverage: huecos o superposiciones en shots, o una toma de menos de 1 s. audio-sync: menos del 70% de marks con un inicio de sonido dentro de ±40 ms (90% para PASS); un pico por encima de -1 dBFS es un WARN. density: más de 18 palabras en pantalla en retrato o 30 en paisaje (solo WARN).

Un trabajo no está terminado hasta que lint pasa con 0 FAIL en cada formato que entregues. Solo ve texto dibujado con fillText/strokeText en el canvas del propio trabajo, por lo que las colisiones de formas y el texto dibujado en canvases fuera de pantalla aún necesitan la revisión visual. example/browser-10s pasa en los tres formatos; example/browsers-40s se hizo antes de la puerta y falla (etiquetas a 17-28 px, una etiqueta cortada en 9:16, texto...

(nota: el contenido se truncó en el original)

Skills relacionados