🧩 build-index.mjs — el generador del proyecto
Cada video comienza copiando scripts/composition-template.mjs como build-index.mjs en la raíz del proyecto. Este archivo es el generador: se ejecuta una vez y escribe index.html, y tú renderizas.
La plantilla ya incluye CSS dark premium, background persistente (glow/grid/grain), caption layer, barra de progreso y overrides para 9:16. Solo tienes que completar 4 cosas: AUDIO[], CAPTIONS[], sceneN() e anim().
Ejecuta node build-index.mjs para 16:9 y node build-index.mjs --vertical para 9:16. Ambos escriben index.html — renderiza justo después de cada generación.
- ✓ Copia la plantilla completa; no la crees desde cero
- ✓ Mantén la escena 9 (CTA INEMA.CLUB) sin cambios
- ✓ Renderiza inmediatamente después de cada
node build-index.mjs - ✓ Usa
writeFileSyncparaindex.html— el template ya hace esto
- ✗ No edites
index.htmldirectamente — se sobrescribirá - ✗ No uses Google Fonts CDN: desaparece en el render headless
- ✗ No elimines la escena 9 de CTA: es la firma estándar
- ✗ No mezcles
--verticaly 16:9 sin volver a ejecutar el generador
--bg:#0D1321, variables de color, fuentes locales, overrides para 9:16.data-layout-ignore.🔢 AUDIO[] — duraciones REALES medidas por ffprobe
El array AUDIO[] es la única entrada de datos de timing de todo el proyecto. Cada elemento es la duración en segundos del WAV de narración de la escena correspondiente, incluida la escena 9 de CTA.
La duración de un WAV generado por Kokoro varía según el texto, la velocidad (--speed 0.98) y la fonética. Si calculaste mal, el audio y el visual quedan desincronizados para siempre. Mide siempre.
El comando ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 assets/audio/sN.wav imprime el número exacto en segundos — pégalo directamente en el array.
Usa for i in 1 2 3 4 5 6 7 8 9; do echo "s$i: $(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 assets/audio/s$i.wav)"; done para generar la lista completa de una sola vez y pegarla en el AUDIO[].
🎨 sceneN() — HTML de cada escena
Cada escena es una función JavaScript que devuelve una cadena HTML. El contenido se inserta en <div class="scene-inner">, dentro de un <section class="scene clip"> con atributos de timing calculados a partir de AUDIO[].
El montaje transforma cada elemento de S (array derivado de AUDIO[]) en un <section> con tres atributos críticos: data-start, data-duration e data-track-index. Las tracks alternadas (1/3) evitan la superposición en los bordes de escena.
- ✓ Reutiliza las clases CSS de la plantilla (
.kicker,.h2,.grid2) - ✓ Da
idúnico para cada elemento animable (s1-word, etc.) - ✓ Elementos decorativos fuera del canvas: usa
data-layout-ignore - ✓ Animar el
.scene-inner, nunca el.clipwrapper
- ✗ IDs duplicados entre escenas — GSAP animará la equivocada
- ✗ Animaciones en el
.clip— el framework obliga aopacity:1en clip activo - ✗ Reordenar BODIES sin ajustar los ID correspondientes
- ✗ Eliminar o reubicar
scene9(CTA fija al final)
✨ anim(i,t) — tweens GSAP por escena
La función anim(i, t) recibe el índice de la escena y el instante de inicio absoluto. Añade cadenas de código GSAP a un array local que después se concatena en el <script> del HTML generado.
El generador se ejecuta en Node.js, pero GSAP se ejecuta en el navegador (Chrome headless). anim() construye cadenas de código que se insertarán en el HTML; cuando el navegador cargue, esas cadenas ya tendrán los tiempos absolutos calculados a partir de AUDIO[].
La helper at(d) (atajo para round(t + d)) calcula el offset relativo al inicio de la escena. Úsala en todos los tweens para mantener el código legible.
1. Anima siempre el .scene-inner, nunca el .clip wrapper. 2. Usar tiempos absolutos calculados por at(d) — jamás codifiques números directamente. 3. El FADE=0.45 es el mismo para la entrada y la salida de todas las escenas, lo que garantiza una transición uniforme.
Las escenas impares quedan en data-track-index="1" y pares en data-track-index="3". Los captions siguen el mismo patrón (2/4). Esto evita el «solapamiento de borde», donde aparecen simultáneamente dos frames de escenas adyacentes: un error clásico descrito en references/gotchas.md.
💬 CAPTIONS[] — un subtítulo corto por escena
El array CAPTIONS[] tiene exactamente la misma longitud que AUDIO[]. Cada string se muestra en la parte inferior de la escena correspondiente como un subtítulo accesible, sincronizado mediante data-start/duration heredados del mismo S[idx].
Una buena caption captura la idea central de la escena, en 5–10 palabras; no transcribe el audio. Quien mire el video sin sonido debe entender el punto de cada escena solo con los subtítulos y los elementos visuales.
La caption se ejecuta en data-track-index="2" (escenas impares) o "4" (pares) — tracks alternados para evitar la superposición con la escena anterior en el borde de transición.
- ✓ Frase corta que resume el concepto central
- ✓ Incluye términos técnicos exactos (nombres de archivo, comandos)
- ✓ Legible para quienes ven el video sin audio
- ✓ La caption de s9 menciona el sitio:
inema.club
- ✗ Transcripción literal del audio (demasiado larga)
- ✗ Vagas: "una idea importante sobre el tema"
- ✗ Todo en mayúsculas — dificulta la lectura rápida
- ✗ Longitud diferente de AUDIO[] (causará un error)
En YouTube, el buscador indexa los subtítulos. Los subtítulos precisos con términos técnicos correctos (ffprobe, GSAP, build-index.mjs) mejoran el posicionamiento del video en las búsquedas técnicas.
⏱️ Timing de fuente única — audio y animación siempre sincronizados
El principio más importante de composition-template: AUDIO[] genera todo — data-start, data-duration, los tiempos de los tweens GSAP e los atributos del <audio>. Ningún número se duplica ni se define de forma hardcoded en otro lugar.
Las tres constantes LEAD=0.5, TAIL=0.9 e FADE=0.45 combinadas con los valores en AUDIO[] determinan el tiempo de inicio, la duración y el fade de cada escena — y, por lo tanto, el tiempo exacto de todos los tweens GSAP. Cambiar un valor en AUDIO[] se propaga automáticamente a todo.
Fórmulas: dur = LEAD + audioDur + TAIL · audioStart = start + LEAD · Tween de entrada = t · Tween de salida = end - FADE.
- ✓ Cambia un WAV → ejecuta el generador → todo se sincroniza
- ✓ Agrega una escena → agrega una entrada en AUDIO[], CAPTIONS[], sceneN(), anim() case N
- ✓ Ningún número hardcoded fuera de AUDIO[]/LEAD/TAIL/FADE
- ✓
npx hyperframes lintdetecta desincronizaciones de la pista
- ✗ Codificar el tiempo directamente en el tween:
gsap.to(..., 14.5) - ✗ Ajustar
data-starten el HTML generado — se sobrescribirá - ✗ Arrays AUDIO[] y CAPTIONS[] de longitudes diferentes
- ✗ Usar
round()inconsistente → desfase de frames
📋 Resumen del Módulo 2.4
- ✓ Copiar la plantilla como
build-index.mjsy ejecutar connode - ✓ Completar
AUDIO[]con duraciones reales de ffprobe (incluye CTA s9) - ✓ Escribir
sceneN()reutilizando las clases CSS de la plantilla - ✓ Codificar tweens GSAP en
anim(i,t)animando siempre el.scene-inner - ✓ Completar
CAPTIONS[]con frases cortas y técnicas - ✓ Comprender las fórmulas de sincronización:
LEAD=0.5,TAIL=0.9,FADE=0.45
npx hyperframes lint para 0 errores, inspect --samples 16 para 0 problemas de diseño, y genera el MP4 final en --quality high para 16:9 y 9:16.