🔢 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. No aparece ningún valor de tiempo en otra parte del código. Cambia un WAV, actualiza la entrada correspondiente en AUDIO[], ejecuta el generador: todo el video 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 diferentes según el texto y la velocidad (
--speed 0.98) - ✗ Una estimación incorrecta = audio y elementos visuales permanentemente desincronizados
- ✗ El render headless no avisa: el video simplemente queda mal
- ✗ Nunca calcules 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 a todas partes.
Tres constantes controlan todo el ritmo: LEAD=0.5 (el visual entra antes que la voz), TAIL=0.9 (el visual permanece después de la voz) y FADE=0.45 (la duración del fundido de entrada/salida del .scene-inner). Un acumulador t crece con cada escena.
Fórmulas fundamentales: dur = LEAD + a + TAIL · audioStart = t + LEAD · end = t + dur.
Después de 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 incrustarán en el <script> del HTML final. El fade del .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 strings de JavaScript con los tiempos absolutos ya calculados — cuando el browser cargue el HTML, estos strings 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 el 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 fundidos para él: no elimines esas líneas.
🧱 Ensamblaje del HTML completo
Los cuatro streams (scenesHTML, captionsHTML, audioHTML en track 20, animJS) se ensamblan en un template string final con la timeline GSAP pausada registrada en window.__timelines["main"]. El entorno (resplandor/cuadrícula) y la centinela tl.set({},{},TOTAL) también forman parte del montaje.
El montaje final es una enorme template string que ensambla los cuatro streams. Las captions se alternan en tracks (2/4), y el audio está en el track 20 (especial). La timeline de GSAP se crea pausada 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 mediante body.v y flag --vertical
El generador admite dos formatos: 16:9 (1920×1080), el estándar, y 9:16 (1080×1920) para Shorts. La flag --vertical cambia W y H, añade 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 del body.v ajusta las fuentes, el padding y el 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 de 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. El template ya incluye la narración de la CTA (s9.wav) y el HTML; tú 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[]. Eliminarla o reubicarla rompe la sincronización de todo el video y elimina la firma del canal. Al adaptar la plantilla para un nuevo video, 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 verifica 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[]controla 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 el.scene-inner - ✓ Cuatro streams ensamblados en el HTML: scenesHTML, captionsHTML, audioHTML (track 20), animJS
- ✓ Timeline de GSAP pausada en
window.__timelines["main"], centinelatl.set({},{},TOTAL) - ✓ Flag
--verticalcambia W/H y añadebody.v— output siempreindex.html - ✓
scene9()/case 9es la CTA INEMA.CLUB —nunca la elimines