HyperFrames
HTML es la fuente de verdad para el video. Una composición es un archivo HTML con atributos data-* para la temporización, una línea de tiempo de GSAP para la animación y CSS para la apariencia. El framework maneja la visibilidad de los clips, la reproducción de medios y la sincronización de la línea de tiempo.
Enfoque
Antes de escribir HTML, piensa a alto nivel:
- Qué — ¿qué debe experimentar el espectador? Identifica el arco narrativo, los momentos clave y los latidos emocionales.
- Estructura — cuántas composiciones, cuáles son subcomposiciones vs. en línea, qué pistas llevan qué (video, audio, superposiciones, subtítulos).
- Temporización — qué clips determinan la duración, dónde caen las transiciones, cuál es el ritmo.
- Diseño — primero construye el estado final. Ver "Diseño Antes de la Animación" más abajo.
- Animar — luego añade movimiento usando las reglas a continuación.
Para ediciones pequeñas (corregir un color, ajustar la temporización, agregar un elemento), ve directamente a las reglas.
Puerta de Identidad Visual
Verifica en este orden:
- ¿Existe DESIGN.md en el proyecto? → Léelo. Usa sus colores, fuentes, reglas de movimiento y restricciones de "Qué NO Hacer" exactos.
- ¿Existe visual-style.md? → Léelo. Aplica su
style_prompt_fully campos estructurados. (Nota:visual-style.mdes un archivo específico del proyecto.visual-styles.mdes la biblioteca de estilos con 8 presets con nombre — son archivos diferentes.) - ¿El usuario nombró un estilo (p. ej., "Swiss Pulse", "oscuro y tecnológico", "marca de lujo")? → Lee visual-styles.md para los 8 presets con nombre. Genera un DESIGN.md mínimo con:
## Style Prompt(un párrafo),## Colors(3-5 valores hex con roles),## Typography(1-2 familias de fuentes),## What NOT to Do(3-5 antipatrones). - ¿Ninguno de los anteriores? → Haz 3 preguntas antes de escribir cualquier HTML:
- ¿Cuál es el estado de ánimo? (explosivo / cinematográfico / fluido / técnico / caótico / cálido)
- ¿Lienzo claro u oscuro?
- ¿Algún color, fuente o referencia visual específica de la marca? Luego genera un DESIGN.md mínimo a partir de las respuestas.
Cada composición debe rastrear su paleta y tipografía hasta un DESIGN.md, visual-style.md o dirección explícita del usuario. Si estás usando #333, #3b82f6 o Roboto — te saltaste este paso.
Para valores predeterminados de movimiento, tamaño, patrones de entrada y easing — sigue house-style.md. El estilo de la casa maneja CÓMO se mueven las cosas. El DESIGN.md maneja QUÉ aspecto tienen.
Diseño Antes de la Animación
Posiciona cada elemento donde debería estar en su momento más visible — el fotograma en el que ha entrado completamente, está colocado correctamente y aún no está saliendo. Escríbelo como HTML+CSS estático primero. Aún no uses GSAP.
Por qué es importante: Si posicionas los elementos en su estado inicial animado (fuera de pantalla, escalados a 0, opacidad 0) y los interpolas a donde crees que deberían aterrizar, estás adivinando el diseño final. Las superposiciones son invisibles hasta que el video se renderiza. Al construir el estado final primero, puedes ver y solucionar problemas de diseño antes de agregar cualquier movimiento.
El proceso
- Identifica el fotograma héroe para cada escena — el momento en que la mayoría de los elementos son visibles simultáneamente. Este es el diseño que construyes.
- Escribe CSS estático para ese fotograma. El contenedor
.scene-contentDEBE llenar toda la escena usandowidth: 100%; height: 100%; padding: Npx;condisplay: flex; flex-direction: column; gap: Npx; box-sizing: border-box. Usa padding para empujar el contenido hacia adentro — NUNCA usesposition: absolute; top: Npxen un contenedor de contenido. Los contenedores de contenido con posición absoluta se desbordan cuando el contenido es más alto que el espacio restante. Reservaposition: absolutesolo para elementos decorativos. - Agrega entradas con
gsap.from()— anima DESDE fuera de pantalla/invisible HASTA la posición CSS. La posición CSS es la verdad fundamental; la interpolación describe el viaje para llegar allí. - Agrega salidas con
gsap.to()— anima HASTA fuera de pantalla/invisible DESDE la posición CSS.
Ejemplo
/* scene-content llena la escena, el padding posiciona el contenido */
.scene-content {
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
height: 100%;
padding: 120px 160px;
gap: 24px;
box-sizing: border-box;
}
.title {
font-size: 120px;
}
.subtitle {
font-size: 42px;
}
/* El contenedor se adapta a cualquier tamaño de escena (1920x1080, 1080x1920, etc.).
El padding posiciona el contenido. Flex + gap manejan el espaciado. */
INCORRECTO — dimensiones fijas y posicionamiento absoluto:
.scene-content {
position: absolute;
top: 200px;
left: 160px;
width: 1920px;
height: 1080px;
display: flex; /* ... */
}
// Paso 3: Anima HACIA esas posiciones
tl.from(".title", { y: 60, opacity: 0, duration: 0.6, ease: "power3.out" }, 0);
tl.from(".subtitle", { y: 40, opacity: 0, duration: 0.5, ease: "power3.out" }, 0.2);
tl.from(".logo", { scale: 0.8, opacity: 0, duration: 0.4, ease: "power2.out" }, 0.3);
// Paso 4: Anima FUERA desde esas posiciones
tl.to(".title", { y: -40, opacity: 0, duration: 0.4, ease: "power2.in" }, 3);
tl.to(".subtitle", { y: -30, opacity: 0, duration: 0.3, ease: "power2.in" }, 3.1);
tl.to(".logo", { scale: 0.9, opacity: 0, duration: 0.3, ease: "power2.in" }, 3.2);
Cuando los elementos comparten espacio a lo largo del tiempo
Si el elemento A sale antes de que el elemento B entre en la misma área, ambos deben tener posiciones CSS correctas para sus respectivos fotogramas héroe. El orden de la línea de tiempo garantiza que nunca coexistan visualmente, pero si te saltas el paso de diseño, no detectarás el caso en que se superponen accidentalmente debido a un error de temporización.
Qué cuenta como superposición intencional
Los efectos en capas (resplandor detrás del texto, elementos de sombra, patrones de fondo) y los diseños apilados en z (pilas de tarjetas, capas de profundidad) son intencionales. El paso de diseño sirve para detectar superposiciones no intencionales — dos títulos que caen uno encima del otro, una estadística que cubre una etiqueta, contenido que se sale del marco.
Atributos de Datos
Todos los Clips
| Atributo | Requerido | Valores |
|---|---|---|
id | Sí | Identificador único |
data-start | Sí | Segundos o referencia de ID de clip ("el-1", "intro + 2") |
data-duration | Requerido para img/div/composiciones | Segundos. Video/audio predeterminado a la duración del medio. |
data-track-index | Sí | Entero. Los clips de la misma pista no pueden superponerse. |
data-media-start | No | Desplazamiento de recorte en la fuente (segundos) |
data-volume | No | 0-1 (predeterminado 1) |
data-track-index no afecta la superposición visual — usa CSS z-index.
Clips de Composición
| Atributo | Requerido | Valores |
|---|---|---|
data-composition-id | Sí | ID de composición único |
data-start | Sí | Tiempo de inicio (composición raíz: usa "0") |
data-duration | Sí | Tiene prioridad sobre la duración de la línea de tiempo de GSAP |
data-width / data-height | Sí | Dimensiones en píxeles (1920x1080 o 1080x1920) |
data-composition-src | No | Ruta al archivo HTML externo |
Estructura de la Composición
Las subcomposiciones cargadas a través de data-composition-src usan un envoltorio <template>. Las composiciones independientes (el index.html principal) NO usan <template> — colocan el div data-composition-id directamente en <body>. Usar <template> en un archivo independiente oculta todo el contenido del navegador y rompe el renderizado.
Estructura de subcomposición:
<template id="my-comp-template">
<div data-composition-id="my-comp" data-width="1920" data-height="1080">
<!-- contenido -->
<style>
[data-composition-id="my-comp"] {
/* estilos encapsulados */
}
</style>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script>
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
// interpolaciones...
window.__timelines["my-comp"] = tl;
</script>
</div>
</template>
Cargar en la raíz: <div id="el-1" data-composition-id="my-comp" data-composition-src="compositions/my-comp.html" data-start="0" data-duration="10" data-track-index="1"></div>
Video y Audio
El video debe tener muted playsinline. El audio siempre es un elemento <audio> separado:
<video
id="el-v"
data-start="0"
data-duration="30"
data-track-index="0"
src="video.mp4"
muted
playsinline
></video>
<audio
id="el-a"
data-start="0"
data-duration="30"
data-track-index="2"
src="video.mp4"
data-volume="1"
></audio>
Contrato de Línea de Tiempo
- Todas las líneas de tiempo comienzan
{ paused: true }— el reproductor controla la reproducción. - Registra cada línea de tiempo:
window.__timelines["<composition-id>"] = tl - El framework anida automáticamente las sublíneas de tiempo — NO las agregues manualmente.
- La duración proviene de
data-duration, no de la longitud de la línea de tiempo de GSAP. - Nunca crees interpolaciones vacías para establecer la duración.
Reglas (No Negociables)
Determinista: Sin Math.random(), Date.now() o lógica basada en el tiempo. Usa un PRNG con semilla si necesitas valores pseudoaleatorios (por ejemplo, mulberry32).
GSAP: Solo anima propiedades visuales (opacity, x, y, scale, rotation, color, backgroundColor, borderRadius, transformaciones). NO animes visibility, display ni llames a video.play() / audio.play().
Conflictos de animación: Nunca animes la misma propiedad en el mismo elemento desde múltiples líneas de tiempo simultáneamente.
Sin repeat: -1: Las líneas de tiempo con repetición infinita rompen el motor de captura. Calcula el número exacto de repeticiones a partir de la duración de la composición: repeat: Math.ceil(duration / cycleDuration) - 1.
Construcción síncrona de líneas de tiempo: Nunca construyas líneas de tiempo dentro de async/await, setTimeout o Promesas. El motor de captura lee window.__timelines sincrónicamente después de cargar la página. Las fuentes están incrustadas por el compilador, por lo que están disponibles de inmediato, no es necesario esperar a que se carguen.
Nunca hagas:
- Olvidar el registro de
window.__timelines - Usar video para audio — siempre video silenciado +
<audio>separado - Anidar video dentro de un div temporizado — usa un envoltorio no temporizado
- Usar
data-layer(usadata-track-index) odata-end(usadata-duration) - Animar las dimensiones del elemento de video — anima un div envoltorio
- Llamar a play/pause/seek en medios — el framework posee la reproducción
- Crear un contenedor de nivel superior sin
data-composition-id - Usar
repeat: -1en cualquier línea de tiempo o interpolación — siempre repeticiones finitas - Construir líneas de tiempo de forma asíncrona (dentro de
async,setTimeout,Promise) - Usar
gsap.set()en elementos de clip de escenas posteriores — no existen en el DOM al cargar la página. En su lugar, usatl.set(selector, vars, timePosition)dentro de la línea de tiempo en o después del tiempodata-startdel clip. - Usar
<br>en el texto del contenido — los saltos de línea forzados no tienen en cuenta el ancho real de la fuente renderizada. El texto que se ajusta naturalmente + un<br>produce un salto adicional no deseado, causando superposición. Deja que el texto se ajuste usandomax-width. Excepción: títulos cortos donde cada palabra está deliberadamente en su propia línea (por ejemplo, "LA\N PARTIDA\N INMORTAL" a 130px).
Transiciones de Escena (No Negociables)
Cada composición de varias escenas DEBE seguir TODAS estas reglas. Violar cualquiera de ellas es una composición rota.
- SIEMPRE usa transiciones entre escenas. Sin cortes. Sin excepciones.
- SIEMPRE usa animaciones de entrada en cada escena. Cada elemento anima ENTRANDO mediante
gsap.from(). Ningún elemento puede aparecer completamente formado. Si una escena tiene 5 elementos, necesita 5 interpolaciones de entrada. - NUNCA uses animaciones de salida excepto en la escena final. Esto significa: NO uses
gsap.to()que anime opacidad a 0, y fuera de pantalla, escala a 0, o cualquier otra animación de "salida" antes de que se active una transición. La transición ES la salida. El contenido de la escena saliente DEBE estar completamente visible en el momento en que comienza la transición. - Solo escena final: La última escena puede desvanecer elementos (por ejemplo, fundido a negro). Esta es la ÚNICA escena donde se permite
gsap.to(..., { opacity: 0 }).
INCORRECTO — animación de salida antes de la transición:
// PROHIBIDO — esto vacía la escena antes de que la transición pueda usarla
tl.to("#s1-title", { opacity: 0, y: -40, duration: 0.4 }, 6.5);
tl.to("#s1-subtitle", { opacity: 0, duration: 0.3 }, 6.7);
// la transición se activa en un fotograma vacío
CORRECTO — solo entrada, la transición maneja la salida:
// Animaciones de entrada de la escena 1
tl.from("#s1-title", { y: 50, opacity: 0, duration: 0.7, ease: "power3.out" }, 0.3);
tl.from("#s1-subtitle", { y: 30, opacity: 0, duration: 0.5, ease: "power2.out" }, 0.6);
// SIN interpolaciones de salida — la transición a los 7.2s maneja el cambio de escena
// Animaciones de entrada de la escena 2
tl.from("#s2-heading", { x: -40, opacity: 0, duration: 0.6, ease: "expo.out" }, 8.0);
Barreras de Seguridad de Animación
- Desfasa la primera animación 0.1-0.3s (no t=0)
- Varía los easings entre las interpolaciones de entrada — usa al menos 3 easings diferentes por escena
- No repitas un patrón de entrada dentro de una escena
- Evita los degradados lineales de pantalla completa sobre fondos oscuros (bandas H.264 — usa radial o sólido + resplandor localizado)
- Titulares de 60px+, cuerpo de 20px+, etiquetas de datos de 16px+ para video renderizado
font-variant-numeric: tabular-numsen columnas de números
Cuando no se proporcionen visual-style.md o dirección de animación, sigue house-style.md para los valores predeterminados estéticos.
Tipografía y Recursos
- Fuentes: Solo escribe la
font-familyque desees en CSS — el compilador incrusta las fuentes compatibles automáticamente. Si una fuente no es compatible, el compilador advierte. - Añade
crossorigin="anonymous"a los medios externos. - Para…
[truncado para traducción]