O que HyperFrames faz?
HTML é a fonte única da verdade para vídeo. Uma composição é um arquivo HTML com atributos data-* para temporização, uma timeline GSAP para animação e CSS para aparência. O framework gerencia visibilidade de clipes, reprodução de mídia e sincronização de timeline.
Abordagem
Antes de escrever HTML, pense em um nível alto:
- O quê — o que o espectador deve experimentar? Identifique o arco narrativo, momentos-chave e batidas emocionais.
- Estrutura — quantas composições, quais são sub-composições vs inline, quais trilhas carregam o quê (vídeo, áudio, sobreposições, legendas).
- Temporização — quais clipes determinam a duração, onde ficam as transições, qual é o ritmo.
- Layout — construa o estado final primeiro. Veja "Layout Antes da Animação" abaixo.
- Animar — depois adicione movimento usando as regras abaixo.
Para pequenas edições (corrigir uma cor, ajustar temporização, adicionar um elemento), pule direto para as regras.
Portão de Identidade Visual
Verifique nesta ordem:
- Existe DESIGN.md no projeto? → Leia-o. Use suas cores exatas, fontes, regras de movimento e restrições "O que NÃO fazer".
- Existe visual-style.md? → Leia-o. Aplique seu
style_prompt_fulle campos estruturados. (Nota:visual-style.mdé um arquivo específico do projeto.visual-styles.mdé a biblioteca de estilos com 8 presets nomeados — são arquivos diferentes.) - Usuário nomeou um estilo (ex.: "Swiss Pulse", "dark and techy", "luxury brand")? → Leia visual-styles.md para os 8 presets nomeados. Gere um DESIGN.md mínimo com:
## Prompt de Estilo(um parágrafo),## Cores(3-5 valores hex com funções),## Tipografia(1-2 famílias de fonte),## O que NÃO fazer(3-5 anti-padrões). - Nenhum dos acima? → Faça 3 perguntas antes de escrever qualquer HTML:
- Qual é o clima? (explosivo / cinematográfico / fluido / técnico / caótico / acolhedor)
- Tela clara ou escura?
- Alguma cor, fonte ou referência visual específica da marca? Em seguida, gere um DESIGN.md mínimo a partir das respostas.
Toda composição deve rastrear sua paleta e tipografia de volta a um DESIGN.md, visual-style.md ou direção explícita do usuário. Se você estiver usando #333, #3b82f6 ou Roboto — você pulou esta etapa.
Para padrões de movimento, dimensionamento, padrões de entrada e easing — siga house-style.md. O estilo da casa lida com COMO as coisas se movem. O DESIGN.md lida com a APARÊNCIA das coisas.
Layout Antes da Animação
Posicione cada elemento onde ele deve estar em seu momento mais visível — o quadro em que ele entrou completamente, está corretamente posicionado e ainda não está saindo. Escreva isso como HTML+CSS estático primeiro. Nada de GSAP ainda.
Por que isso importa: Se você posicionar elementos em seu estado inicial animado (fora da tela, escalados para 0, opacidade 0) e animá-los para onde você acha que deveriam chegar, você está adivinhando o layout final. Sobreposições ficam invisíveis até o vídeo ser renderizado. Construindo o estado final primeiro, você pode ver e corrigir problemas de layout antes de adicionar qualquer movimento.
O processo
- Identifique o quadro herói de cada cena — o momento em que o maior número de elementos está simultaneamente visível. Este é o layout que você constrói.
- Escreva CSS estático para esse quadro. O contêiner
.scene-contentDEVE preencher a cena inteira usandowidth: 100%; height: 100%; padding: Npx;comdisplay: flex; flex-direction: column; gap: Npx; box-sizing: border-box. Use padding para empurrar o conteúdo para dentro — NUNCAposition: absolute; top: Npxem um contêiner de conteúdo. Contêineres de conteúdo com posicionamento absoluto transbordam quando o conteúdo é mais alto que o espaço restante. Reserveposition: absoluteapenas para elementos decorativos. - Adicione entradas com
gsap.from()— anime DE fora da tela/invisível PARA a posição CSS. A posição CSS é a verdade absoluta; o tween descreve a jornada para chegar lá. - Adicione saídas com
gsap.to()— anime PARA fora da tela/invisível DA posição CSS.
Exemplo
/* scene-content preenche a cena, padding posiciona o conteúdo */
.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;
}
/* O contêiner preenche qualquer tamanho de cena (1920x1080, 1080x1920, etc).
Padding posiciona o conteúdo. Flex + gap cuidam do espaçamento. */
ERRADO — dimensões fixas e posicionamento absoluto:
.scene-content {
position: absolute;
top: 200px;
left: 160px;
width: 1920px;
height: 1080px;
display: flex; /* ... */
}
// Passo 3: Animar PARA dentro dessas posições
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);
// Passo 4: Animar PARA fora dessas posições
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);
Quando elementos compartilham espaço ao longo do tempo
Se o elemento A sai antes do elemento B entrar na mesma área, ambos devem ter posições CSS corretas para seus respectivos quadros herói. A ordenação da timeline garante que eles nunca coexistam visualmente — mas se você pular a etapa de layout, não perceberá o caso em que eles acidentalmente se sobrepõem devido a um erro de temporização.
O que conta como sobreposição intencional
Efeitos em camadas (brilho atrás do texto, elementos de sombra, padrões de fundo) e designs empilhados em z (pilhas de cartões, camadas de profundidade) são intencionais. A etapa de layout é para detectar sobreposições não intencionais — dois títulos caindo um em cima do outro, uma estatística cobrindo um rótulo, conteúdo vazando para fora do quadro.
Atributos de Dados
Todos os Clipes
| Atributo | Obrigatório | Valores |
|---|---|---|
id | Sim | Identificador único |
data-start | Sim | Segundos ou referência de ID de clipe ("el-1", "intro + 2") |
data-duration | Obrigatório para img/div/composições | Segundos. Vídeo/áudio usa duração da mídia como padrão. |
data-track-index | Sim | Inteiro. Clipes na mesma trilha não podem se sobrepor. |
data-media-start | Não | Deslocamento de corte na fonte (segundos) |
data-volume | Não | 0-1 (padrão 1) |
data-track-index não afeta a camada visual — use CSS z-index.
Clipes de Composição
| Atributo | Obrigatório | Valores |
|---|---|---|
data-composition-id | Sim | ID único da composição |
data-start | Sim | Tempo de início (composição raiz: use "0") |
data-duration | Sim | Tem precedência sobre duração da timeline GSAP |
data-width / data-height | Sim | Dimensões em pixels (1920x1080 ou 1080x1920) |
data-composition-src | Não | Caminho para arquivo HTML externo |
Estrutura de Composição
Sub-composições carregadas via data-composition-src usam um wrapper <template>. Composições autônomas (o index.html principal) NÃO usam <template> — elas colocam o div data-composition-id diretamente no <body>. Usar <template> em um arquivo autônomo esconde todo o conteúdo do navegador e quebra a renderização.
Estrutura de sub-composição:
<template id="my-comp-template">
<div data-composition-id="my-comp" data-width="1920" data-height="1080">
<!-- conteúdo -->
<style>
[data-composition-id="my-comp"] {
/* estilos escopados */
}
</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 });
// tweens...
window.__timelines["my-comp"] = tl;
</script>
</div>
</template>
Carregue na raiz: <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>
Vídeo e Áudio
O vídeo deve ter muted playsinline. O áudio é sempre um 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 Timeline
- Todas as timelines começam com
{ paused: true }— o jogador controla a reprodução - Registre cada timeline:
window.__timelines["<composition-id>"] = tl - O framework aninha sub-timelines automaticamente — NÃO as adicione manualmente
- A duração vem de
data-duration, não do comprimento da timeline GSAP - Nunca crie tweens vazios para definir duração
Regras (Não Negociáveis)
Determinístico: Sem Math.random(), Date.now() ou lógica baseada em tempo. Use um PRNG com semente se precisar de valores pseudo-aleatórios (ex.: mulberry32).
GSAP: Anime apenas propriedades visuais (opacity, x, y, scale, rotation, color, backgroundColor, borderRadius, transformações). NÃO anime visibility, display ou chame video.play()/audio.play().
Conflitos de animação: Nunca anime a mesma propriedade no mesmo elemento a partir de múltiplas timelines simultaneamente.
Sem repeat: -1: Timelines com repetição infinita quebram o mecanismo de captura. Calcule a contagem exata de repetições a partir da duração da composição: repeat: Math.ceil(duration / cycleDuration) - 1.
Construção síncrona de timelines: Nunca construa timelines dentro de async/await, setTimeout ou Promises. O mecanismo de captura lê window.__timelines de forma síncrona após o carregamento da página. As fontes são incorporadas pelo compilador, então estão disponíveis imediatamente — não é necessário esperar pelo carregamento de fontes.
Nunca faça:
- Esquecer o registro em
window.__timelines - Usar vídeo para áudio — sempre vídeo mudo +
<audio>separado - Aninhar vídeo dentro de um div temporizado — use um wrapper não temporizado
- Usar
data-layer(usedata-track-index) oudata-end(usedata-duration) - Animar dimensões de elemento de vídeo — anime um div wrapper
- Chamar play/pause/seek em mídia — o framework controla a reprodução
- Criar um contêiner de nível superior sem
data-composition-id - Usar
repeat: -1em qualquer timeline ou tween — sempre repetições finitas - Construir timelines de forma assíncrona (dentro de
async,setTimeout,Promise) - Usar
gsap.set()em elementos de clipe de cenas posteriores — eles não existem no DOM no carregamento da página. Usetl.set(selector, vars, timePosition)dentro da timeline no horáriodata-startdo clipe ou depois. - Usar
<br>no texto do conteúdo — quebra de linha forçada