🔢 AUDIO[] como fuente única del timing
El array AUDIO[] contiene las duraciones REALES (medidas con ffprobe) de cada narración WAV. Es la única variable que necesitas completar para que todo el timing del video —HTML, GSAP y audio— se sincronice automáticamente.
El array de duraciones lo controla todo. Ningún valor de tiempo aparece en otro lugar del código. Cambia un WAV, actualiza la entrada correspondiente en AUDIO[], ejecuta el generador: el video completo se recalibra automáticamente.
Esto es posible porque el generador se ejecuta en Node.js y produce un HTML estático con todos los tiempos ya calculados. El browser recibe números listos — no necesita calcular nada en runtime.
- ✓
data-startedata-durationde cada.scene.clip - ✓ Tiempos absolutos de todos los tweens GSAP mediante
anim(i, s.start) - ✓
data-startde los elementos<audio>en track 20 - ✓
TOTALde la composición y barra de progreso
- ✗ Kokoro genera duraciones distintas según el texto y la velocidad (
--speed 0.98) - ✗ Una estimación equivocada = audio y elementos visuales desincronizados para siempre
- ✗ El render headless no avisa: el video simplemente queda mal
- ✗ Nunca escribas las duraciones de memoria: usa siempre
ffprobe
⏱️ El cálculo de S[] — la serie de timing
La serie S se genera con un único AUDIO.map(). Cada objeto contiene todos los tiempos necesarios para escenas, captions, audio y tweens: se calculan una vez y se propagan por todas partes.
Tres constantes controlan todo el ritmo: LEAD=0.5 (el visual entra antes que la voz), TAIL=0.9 (el visual se mantiene después de la voz) y FADE=0.45 (duración del fade-in/out de .scene-inner). Un acumulador t crece con cada escena.
Fórmulas fundamentales: dur = LEAD + a + TAIL · audioStart = t + LEAD · end = t + dur.
Tras generar el HTML, la plantilla muestra en la consola: OUT gerado · W×H · TOTAL = Xs · N cenas, seguido de una línea por escena con start, dur, audio@ y audioDur. Verifica estos números antes de renderizar.
🎬 sceneN() y anim(i,t) por escena
Cada escena tiene dos funciones: sceneN() devuelve el HTML interno de .scene-inner, e anim(i,t) genera cadenas de código GSAP que se insertarán en el <script> del HTML final. El fade de .scene-inner siempre lo genera la parte común de anim().
El generador se ejecuta en Node.js, pero GSAP se ejecuta en el navegador (Chrome headless). anim() construye cadenas de JavaScript con los tiempos absolutos ya calculados; cuando el navegador cargue el HTML, esas cadenas se ejecutan con números exactos, sin recalcular. El fade de .scene-inner es común a todas las escenas; el switch interno agrega los tweens específicos de cada una.
HyperFrames fuerza opacity:1 en clip activo. Si intentas animar el .clip o el .scene, la animación de fade no funciona. El wrapper animable siempre es el hijo .scene-inner. La plantilla ya genera los fades para él: no elimines estas líneas.
🧱 Montaje del HTML completo
Los cuatro streams (scenesHTML, captionsHTML, audioHTML en track 20, animJS) se ensamblan en una template string final con la timeline de GSAP pausada registrada en window.__timelines["main"]. El ambiente (glow/grid) y la centinela tl.set({},{},TOTAL) también forman parte del montaje.
El montaje final es una template string gigante que ensambla los cuatro streams. Las captions van en tracks alternados (2/4) y el audio, en el track 20 (especial). La timeline de GSAP se crea en pausa y se registra en window.__timelines["main"] — el reproductor de HyperFrames lo controla externamente.
La centinela tl.set({}, {}, TOTAL) extiende la línea de tiempo hasta el final de la composición, lo que garantiza que la barra de progreso llegue hasta el final incluso sin tweens después de la última escena.
yoyo:true cubriendo todo el TOTAL. data-layout-ignore — no interfiere con el diseño de HyperFrames.backgroundPositionY+=128 a lo largo de todo el video. Crea una sensación de movimiento.SKILL.md decorativo. Ambos con data-layout-ignore.📱 Overrides 9:16 vía body.v y flag --vertical
El generador admite dos formatos: 16:9 (1920×1080) estándar y 9:16 (1080×1920) para Shorts. La flag --vertical cambia W y H, agrega la clase body.v en el HTML generado y aplica overrides CSS automáticos. El output siempre es index.html.
El mismo build-index.mjs genera 16:9 y 9:16. La lógica de timing es idéntica: solo cambian las dimensiones W/H y el CSS de body.v ajusta fuentes, padding y diseño. Renderizas dos veces con la misma fuente y obtienes dos formatos sincronizados.
Flujo típico: node build-index.mjs → renderiza 16:9 → node build-index.mjs --vertical → renderiza 9:16. Ambos sobrescriben index.html — nunca edites el HTML directamente.
v en body. CSS estándar con fuentes grandes para pantalla de 1080p.class="v" al body. Los overrides CSS ajustan las fuentes y el diseño para móviles.🏁 CTA scene9() / case 9 — firma invariable
La escena 9 es la firma predeterminada de todos los videos de INEMA.CLUB. Muestra «CONTINÚA EN» + INEMA.CLUB con glow y la URL 🌐 inema.club. Nunca debe eliminarse, reubicarse ni modificarse: es parte de la identidad del canal.
La escena 9 funciona como una firma de marca: quien ve cualquier video de INEMA.CLUB siempre ve el mismo cierre. Esto genera reconocimiento y dirige al espectador al sitio. La plantilla ya incluye la narración de la CTA (s9.wav) y el HTML: solo proporcionas la duración real en el AUDIO[].
Regla: AUDIO[8] (índice 8, escena 9) siempre es la duración de assets/audio/s9.wav. Narración predeterminada: "Esto es contenido de INEMA punto CLUB. Accede: inema punto club."
La CTA está en la posición 9 de BODIES[] y en el índice 8 de AUDIO[]. Eliminarlo o reubicarlo rompe el timing de todo el video y elimina la firma del canal. Al adaptar la plantilla para un video nuevo, cambia solo las escenas 1–8 y actualiza las duraciones en AUDIO[0..7]. AUDIO[8] siempre es el WAV de la CTA.
- ✓ Copia la plantilla completa como
build-index.mjs - ✓ Completa
AUDIO[0..7]con ffprobe — manténAUDIO[8] - ✓ Escribe
scene1()–scene8()— manténscene9() - ✓ Codifica
case 1–case 8enanim()— manténcase 9 - ✓ Ejecuta el generador y revisa el registro de timing antes de renderizar
- ✗ Eliminar
scene9()deBODIES[] - ✗ Eliminar
AUDIO[8]o dejar el array con menos de 9 entradas - ✗ Reemplazar la escena 9 por contenido de otro video
- ✗ Cambiar los colores
.cta-inema/.cta-club
📋 Resumen del Módulo 3.3
- ✓
AUDIO[]rige el 100% del timing — HTML, GSAP y audio - ✓
S[] = AUDIO.map()con LEAD/TAIL/FADE calcula start, dur, audioStart, end - ✓
sceneN()devuelve HTML;anim(i,t)genera cadenas GSAP con fade en.scene-inner - ✓ Cuatro streams ensamblados en el HTML: scenesHTML, captionsHTML, audioHTML (track 20), animJS
- ✓ Timeline GSAP pausada en
window.__timelines["main"], centinelatl.set({},{},TOTAL) - ✓ Flag
--verticalcambia W/H y agregabody.v— el output siempreindex.html - ✓
scene9()/case 9es la CTA de INEMA.CLUB; nunca la elimines