HyperFrames
El HTML es la fuente de verdad para el video. Una composición es un archivo HTML con atributos data-* para el tiempo, una línea de tiempo 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, piense a alto nivel:
- Qué — ¿qué debería experimentar el espectador? Identifique el arco narrativo, los momentos clave y los latidos emocionales.
- Estructura — cuántas composiciones, cuáles son subcomposiciones versus en línea, qué pistas transportan qué (video, audio, superposiciones, subtítulos).
- Tiempo — qué clips determinan la duración, dónde caen las transiciones, cuál es el ritmo.
- Diseño — construya primero el estado final. Vea "Diseño antes de la animación" a continuación.
- Animar — luego añada movimiento usando las reglas a continuación.
Para ediciones pequeñas (corregir un color, ajustar el tiempo, añadir un elemento), vaya directamente a las reglas.
Puerta de identidad visual
Verifique en este orden:
- ¿Existe DESIGN.md en el proyecto? → Léalo. Use sus colores, fuentes, reglas de movimiento y restricciones "Qué NO Hacer" exactos.
- ¿Existe visual-style.md? → Léalo. Aplique 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 nombrados — archivos diferentes). - ¿El usuario nombró un estilo (ej., "Swiss Pulse", "oscuro y técnico", "marca de lujo")? → Lea visual-styles.md para los 8 presets nombrados. Genere un DESIGN.md mínimo con:
## Prompt de Estilo(un párrafo),## Colores(3-5 valores hex con roles),## Tipografía(1-2 familias de fuentes),## Qué NO Hacer(3-5 antipatrones). - ¿Ninguno de los anteriores? → Haga 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 de marca, fuente o referencia visual específica? Luego genere 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 una dirección explícita del usuario. Si está usando #333, #3b82f6 o Roboto — se saltó este paso.
Para valores predeterminados de movimiento, tamaño, patrones de entrada y suavizado — siga house-style.md. El estilo de la casa maneja CÓMO se mueven las cosas. El DESIGN.md maneja QUÉ aspecto tienen las cosas.
Diseño antes de la animación
Coloque cada elemento donde debería estar en su momento más visible — el fotograma donde ha entrado completamente, está correctamente colocado y aún no está saliendo. Escriba esto como HTML+CSS estático primero. Aún no hay GSAP.
Por qué esto importa: Si coloca los elementos en su estado inicial animado (fuera de pantalla, escalados a 0, opacidad 0) y los interpolas hacia donde cree que deberían estar, está adivinando el diseño final. Las superposiciones son invisibles hasta que el video se renderiza. Al construir el estado final primero, puede ver y solucionar problemas de diseño antes de añadir cualquier movimiento.
El proceso
- Identifique el fotograma heroico para cada escena — el momento en que la mayoría de los elementos son visibles simultáneamente. Este es el diseño que construye.
- Escriba CSS estático para ese fotograma. El contenedor
.scene-contentDEBE llenar la escena completa usandowidth: 100%; height: 100%; padding: Npx;condisplay: flex; flex-direction: column; gap: Npx; box-sizing: border-box. Use padding para empujar el contenido hacia adentro — NUNCA useposition: absolute; top: Npxen un contenedor de contenido. Los contenedores de contenido con posicionamiento absoluto se desbordan cuando el contenido es más alto que el espacio restante. Reserveposition: absolutesolo para elementos decorativos. - Añada entradas con
gsap.from()— anime DESDE fuera de pantalla/invisible HACIA la posición CSS. La posición CSS es la verdad fundamental; la interpolación describe el viaje para llegar allí. - Añada salidas con
gsap.to()— anime HACIA 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 llena cualquier tamaño de escena (1920x1080, 1080x1920, etc.).
El padding posiciona el contenido. Flex + gap maneja el espaciado. */
INCORRECTO — dimensiones fijas y posicionamiento absoluto:
.scene-content {
position: absolute;
top: 200px;
left: 160px;
width: 1920px;
height: 1080px;
display: flex; /* ... */
}
// Paso 3: Anime 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: Anime FUERA de 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 través 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 heroicos. El orden de la línea de tiempo garantiza que nunca coexistan visualmente — pero si omite el paso de diseño, no detectará el caso en que se superpongan accidentalmente debido a un error de tiempo.
Qué cuenta como superposición intencional
Los efectos en capas (brillo detrás del texto, elementos de sombra, patrones de fondo) y los diseños en pila z (pilas de tarjetas, capas de profundidad) son intencionales. El paso de diseño es para detectar superposiciones no intencionales — dos titulares cayendo uno encima del otro, una estadística cubriendo una etiqueta, contenido sangrando fuera del fotograma.
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 por defecto 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 el origen (segundos) |
data-volume | No | 0-1 (por defecto 1) |
data-track-index NO afecta la superposición visual — use 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: use "0") |
data-duration | Sí | Tiene prioridad sobre la duración de la línea de tiempo 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 la renderización.
Estructura de la 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 con ámbito */
}
</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 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 ser 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 - Registre cada línea de tiempo:
window.__timelines["<composition-id>"] = tl - El framework anida automáticamente sublíneas de tiempo — NO las añada manualmente
- La duración proviene de
data-duration, no de la longitud de la línea de tiempo GSAP - Nunca cree interpolaciones vacías para establecer la duración
Reglas (No Negociables)
Determinista: Sin Math.random(), Date.now() o lógica basada en tiempo. Use un PRNG con semilla si necesita valores pseudoaleatorios (ej., mulberry32).
GSAP: Solo anime propiedades visuales (opacity, x, y, scale, rotation, color, backgroundColor, borderRadius, transformaciones). NO anime visibility, display ni llame a video.play()/audio.play().
Conflictos de animación: Nunca anime 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. Calcule 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 construya líneas de tiempo dentro de async/await, setTimeout o Promesas. El motor de captura lee window.__timelines sincrónicamente después de la carga de la página. Las fuentes son incrustadas por el compilador, por lo que están disponibles inmediatamente — no es necesario esperar la carga de fuentes.
Nunca haga:
- Olvide el registro de
window.__timelines - Use video para audio — siempre video silenciado +
<audio>separado - Anide video dentro de un div con tiempo — use un envoltorio sin tiempo
- Use
data-layer(usedata-track-index) odata-end(usedata-duration) - Anime las dimensiones del elemento de video — anime un div envoltorio
- Llame a play/pause/seek en medios — el framework posee la reproducción
- Cree un contenedor de nivel superior sin
data-composition-id - Use
repeat: -1en cualquier línea de tiempo o interpolación — siempre repeticiones finitas - Construya líneas de tiempo asíncronamente (dentro de
async,setTimeout,Promise) - Use
gsap.set()en elementos de clip de escenas posteriores — no existen en el DOM en la carga de la página. Usetl.set(selector, vars, timePosition)dentro de la línea de tiempo en o después del tiempodata-startdel clip. - Use
<br>en texto de contenido — los saltos de línea forzados no tienen en cuenta el ancho de fuente real renderizado. El texto que se ajusta naturalmente + un<br>produce un salto adicional no deseado, causando superposición. Deje que el texto se ajuste mediantemax-width. Excepción: títulos cortos donde cada palabra está deliberadamente en su propia línea (ej., "THE\nIMMORTAL\nGAME" a 130px).
Transiciones de Escena (No Negociables)
Cada composición de múltiples escenas DEBE seguir TODAS estas reglas. Violar alguna de ellas es una composición rota.
- SIEMPRE use transiciones entre escenas. Sin cortes bruscos. Sin excepciones.
- SIEMPRE use animaciones de entrada en cada escena. Cada elemento anima ENTRADA a través de
gsap.from(). Ningún elemento puede aparecer completamente formado. Si una escena tiene 5 elementos, necesita 5 interpolaciones de entrada. - NUNCA use animaciones de salida excepto en la escena final. Esto significa: NO
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 (ej., 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 en 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);
Guardarraíles de Animación
- Desplace la primera animación 0.1-0.3s (no t=0)
- Varíe los suavizados entre las interpolaciones de entrada — use al menos 3 suavizados diferentes por escena
- No repita un patrón de entrada dentro de una escena
- Evite degradados lineales de pantalla completa sobre fondos oscuros (bandeado H.264 — use radial o sólido + brillo 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 proporcione visual-style.md ni dirección de animación, siga house-style.md para los valores predeterminados estéticos.
Tipografía y Recursos
- Fuentes: Simplemente escriba la
font-familyque desee en CSS — el compilador incrusta las fuentes compatibles automáticamente. Si una fuente no es compatible, el compilador advierte. - Añada
crossorigin="anonymous"a medios externos - Para ...
[truncado para traducción]