MÓDULO 3.4

🧯 Gotchas & correções

As seis armadilhas mais comuns que quebram silenciosamente renders, lint e inspect — e como corrigi-las de uma vez por todas.

6
Tópicos
25
Minutos
Avançado
Nível
Debug
Tipo
🎭
Tópico 1

Animar .scene-inner, nunca o .clip

O framework HyperFrames força opacity:1 em todo .clip que está ativo. Se você animar o wrapper diretamente, o fade não ocorre — o engine sobrescreve sua animação quadro a quadro. A solução é sempre animar um filho interno.

Mecânica do framework

O HyperFrames itera sobre os clips ativos a cada frame e aplica element.style.opacity = "1" diretamente no .clip. Qualquer animação GSAP que tente fazer gsap.to(".clip", {opacity:0}) será sobrescrita no próximo tick de render — resultando em fade fantasma que nunca acontece.

PROBLEMA — animar o .clip
// ❌ ERRADO: o engine força opacity:1
tl.to("#clip-cena-2", {
  opacity: 0,
  duration: 0.45
}, 4.5);
  • Opacity sobrescrita no próximo frame
  • Fade nunca visível no vídeo renderizado
  • Difícil de debugar (parece funcionar no preview)
CORREÇÃO — animar o .scene-inner
// ✅ CORRETO: filho interno
tl.to("#scene-inner-2", {
  opacity: 0,
  duration: 0.45
}, 4.5);
// hard-kill obrigatório:
tl.set("#scene-inner-2", {
  opacity: 0
}, 4.95);
  • Fade executado no DOM filho — framework não interfere
  • Hard-kill com tl.set garante opacity:0 no fim
  • Cobre o gotcha gsap_exit_missing_hard_kill
💡
Gotcha adjacente: gsap_exit_missing_hard_kill

Mesmo usando scene-inner, se o GSAP tween terminar antes do clip desaparecer, a opacidade pode voltar. Sempre adicione um tl.set(target, {opacity:0}, tempoFinal) como sentinela. O composition-template.mjs já inclui essa linha automaticamente.

Estrutura HTML recomendada
<!-- wrapper gerenciado pelo framework -->
<div id="clip-cena-2"
     class="clip"
     data-start="4.0"
     data-duration="3.0"
     data-track-index="1">
  <!-- filho animável -->
  <div id="scene-inner-2" class="scene-inner">
    <!-- conteúdo da cena -->
  </div>
</div>
Conceitos-chave
.clip
wrapper controlado pelo engine
.scene-inner
filho animável pelo GSAP
opacity:1
forçado no clip ativo
tl.set
hard-kill no fim da cena
🔀
Tópico 2

Tracks alternados para evitar overlapping_clips_same_track

Cenas adjacentes que tocam na borda temporal — mesmo por frações de float — disparam o erro overlapping_clips_same_track durante o lint. A correção canônica é alternar os data-track-index: cenas em 1/3, captions em 2/4.

Fluxo de tracks na timeline
Track 1
Cena 1
0s → 4s
Cena 3
8s → 12s
Cena 5
16s → 20s
Track 2
Caption 1
0s → 4s
Caption 3
8s → 12s
Caption 5
16s → 20s
Track 3
Cena 2
4s → 8s
Cena 4
12s → 16s
Cena 6
20s → 24s
Track 4
Caption 2
4s → 8s
Caption 4
12s → 16s
Caption 6
20s → 24s
Cenas ímpares (1,3,5…) → track 1 · Cenas pares (2,4,6…) → track 3 · Captions espelham nos tracks 2/4
PROBLEMA — mesmo track, borda tocando
// ❌ cena 1 e cena 2 no track 1
data-start="0" data-duration="4.0"
data-track-index="1"

data-start="4.0" data-duration="4.0"
data-track-index="1"
// → lint: overlapping_clips_same_track
CORREÇÃO — tracks alternados
// ✅ template: s.i%2===1 ? 1 : 3
data-start="0" data-duration="4.0"
data-track-index="1" // cena 1 (ímpar)

data-start="4.0" data-duration="4.0"
data-track-index="3" // cena 2 (par)
// → lint: ok
⚠️
NÃO crie gaps entre cenas

A tentação é adicionar um pequeno gap (data-start="4.001") para evitar a colisão. Isso cria um frame preto visível no vídeo e ainda pode disparar outros erros do lint. A solução correta é sempre alterar o track, nunca o tempo.

Conceitos-chave
Track 1/3
cenas ímpares e pares
Track 2/4
captions alternados
Sem gaps
nunca altere o data-start
s.i%2
lógica do template
👻
Tópico 3

data-layout-ignore em decorativos off-canvas

Ghost text, glows e bg-layers posicionados fora do canvas visível (translateX negativo, left:-200px etc.) fazem o inspect acusar overflow — mesmo que visualmente estejam invisíveis. O atributo data-layout-ignore sinaliza ao engine que esses elementos são intencionalmente off-canvas.

🔴
O inspect vai acusar overflow — mesmo sendo intencional

O inspect do HyperFrames calcula o bounding box de todos os elementos visíveis. Elementos decorativos off-canvas — mesmo com overflow:hidden no pai — podem vazar e ampliar o bounding box medido. Resultado: vídeo renderizado com barras pretas nas bordas.

PROBLEMA — decorativo sem atributo
<!-- ghost text vazando -->
<div
  class="bg-layer"
  style="left:-240px;top:0">
  GHOST TEXT
</div>
// → inspect: overflow detectado
CORREÇÃO — data-layout-ignore
<!-- ignora no cálculo de layout -->
<div
  class="bg-layer"
  style="left:-240px;top:0"
  data-layout-ignore>
  GHOST TEXT
</div>
// → inspect: ok
Quando adicionar data-layout-ignore
Ghost Text
Texto grande e semitransparente posicionado parcial ou totalmente fora do canvas como efeito decorativo.
Glow / Bloom
Divs de filtro blur() que excedem as bordas do frame para criar halo suave.
BG-Layer
Camada de fundo que sai do canvas para cobrir bordas com gradiente, sem criar barra preta.
Conceitos-chave
data-layout-ignore
atributo HTML puro, sem valor
bounding box
calculado pelo inspect engine
overflow visual
barra preta no vídeo final
🔤
Tópico 4

Fontes locais: nunca Google Fonts via <link> ou @import

O HyperFrames renderiza em ambiente offline (Chrome headless sem rede). Um <link> do Google Fonts falha silenciosamente — o vídeo é renderizado com a fonte fallback do sistema, sem erro visível no terminal. O lint acusa google_fonts_import e font_family_without_font_face.

Por que falha silenciosamente

O Chrome headless não reporta falhas de rede como erros GSAP ou JS. A página simplesmente cai para a fonte de fallback (sans-serif → Arial/Helvetica). O vídeo renderiza normalmente — mas com tipografia errada. Você pode não notar até assistir o resultado final em alta resolução.

PROBLEMA — Google Fonts via link
<!-- ❌ ERRADO: requer rede -->
<link href="https://fonts.googleapis.com/
css2?family=Inter:wght@400;700"
rel="stylesheet">
// → lint: google_fonts_import
CORREÇÃO — @font-face local
/* ✅ CORRETO: woff2 local */
@font-face {
  font-family: 'Inter';
  src: url('../fonts/inter-400.woff2')
       format('woff2');
  font-weight: 400;
  font-display: block;
}
Baixando com fetch-fonts.mjs
# baixa subset latin (cobre PT-BR)
node scripts/fetch-fonts.mjs \
  --family Inter \
  --weights 400,500,600,700,800 \
  --subset latin \
  --out assets/fonts/

# resultado: assets/fonts/inter-400.woff2
# assets/fonts/inter-700.woff2
# assets/fonts/fonts.css (import pronto)
💡
Subset latin cobre PT-BR

O subset latin inclui todos os caracteres usados em português brasileiro (ã, ç, õ, á, é, etc.). Não é necessário baixar o subset completo. Use font-display: block para garantir que o Chrome headless aguarde a fonte antes de capturar o frame.

Conceitos-chave
@font-face
declaração local no CSS
.woff2
formato otimizado local
fetch-fonts.mjs
script do skill
font-display: block
aguarda carregamento
⌨️
Tópico 5

ffmpeg -nostdin no Windows / git-bash

No Windows executando ffmpeg via git-bash (ou qualquer emulador POSIX sobre Win32), o ffmpeg pode ler stdin inadvertidamente, interpretar EOF como entrada válida e retornar exit 0 sem gerar nenhum arquivo de saída. O problema é completamente silencioso.

🚨
Exit 0 sem arquivo gerado — o pior tipo de erro

O pipeline continua sem falhar. O próximo passo tenta ler um arquivo que não existe. O erro real aparece vários passos depois, difícil de rastrear. A flag -nostdin resolve completamente — e não tem custo em ambientes normais.

PROBLEMA — sem -nostdin
# ❌ Windows/git-bash sem -nostdin
ffmpeg -y \
  -framerate 30 \
  -i frames/%04d.png \
  -i audio.wav \
  output.mp4
# → exit 0, output.mp4 não existe
CORREÇÃO — -nostdin sempre
# ✅ -nostdin como primeiro argumento
ffmpeg -nostdin -y \
  -framerate 30 \
  -i frames/%04d.png \
  -i audio.wav \
  output.mp4
# → output.mp4 gerado corretamente
💡
Extraindo frame único com -nostdin
ffmpeg -nostdin -y \
  -ss 2.5 \
  -i renders/video-16x9.mp4 \
  -vframes 1 \
  -update 1 \
  thumbnail.png

Se precisar de caminho absoluto no Windows: /c/ffmpeg/bin/ffmpeg.exe -nostdin ...

Conceitos-chave
-nostdin
primeiro argumento sempre
exit 0 falso
sem arquivo gerado
git-bash
emulador POSIX/Win32
-update 1
para frame único PNG
🎲
Tópico 6

Determinismo: proibido Date.now(), Math.random() e fetch()

O render do HyperFrames é determinístico por design: o mesmo HTML deve produzir exatamente os mesmos frames em qualquer máquina, em qualquer momento. Qualquer fonte de não-determinismo — tempo real, aleatoriedade, dados externos — quebra essa garantia e produz frames inconsistentes ou erro de render.

Por que o render é quadro a quadro

O engine captura frames individuais navegando pelo tempo da timeline GSAP de forma sintética — não executa o vídeo em tempo real. Isso significa que Date.now() retorna valores diferentes para cada frame capturado, Math.random() nunca é seeded de forma reproduzível e fetch() pode falhar ou retornar dados diferentes a cada execução.

PROBLEMA — fontes não-determinísticas
  • Date.now() — retorna timestamp diferente por frame
  • Math.random() — sequência não-reproduzível
  • fetch() — dados externos podem mudar ou falhar
  • setInterval() — baseado em tempo real do SO
  • new Date() — mesmo problema do Date.now()
CORREÇÃO — tudo via GSAP timeline
  • Posições calculadas: gsap.utils.mapRange()
  • Dados externos: pré-baked no HTML em tempo de build
  • Pseudo-random: seededRand(n) com semente fixa
  • Contadores: variáveis JS atualizadas por tl.call()
  • Timeline registrada: window.__timelines["main"]
Pseudo-random determinístico com semente fixa
// ✅ seeded PRNG — reproduzível
function seededRand(seed) {
  let s = seed;
  return () => {
    s = (s * 1664525 + 1013904223) & 0xffffffff;
    return (s >>> 0) / 0xffffffff;
  };
}
const rand = seededRand(42);
// rand() sempre retorna a mesma sequência
💡
Gotcha adicional: multiple_root_compositions

Só pode existir um único arquivo com data-composition-id na raiz do projeto. Se você deixar index-vertical.html, index-backup.html ou qualquer variante, o gerador build-index.mjs conflita e o lint falha. Sempre use apenas index.html como raiz da composição.

Conceitos-chave
Determinismo
mesmo input → mesmo output
Seeded PRNG
aleatorio reproduzível
Sem fetch()
dados pré-baked no HTML
tl.call()
callbacks na timeline GSAP
📋
Resumo

O que você aprendeu neste módulo

Animar .scene-inner, não .clip
O framework força opacity:1 no wrapper — fades vão para o filho + hard-kill com tl.set
Tracks alternados 1/3 e 2/4
Evita overlapping_clips_same_track sem criar gaps ou frames pretos
data-layout-ignore em decorativos
Ghost text e glows off-canvas precisam do atributo para não vazar o bounding box
@font-face com .woff2 local
Google Fonts via link falha silenciosamente no Chrome headless offline
ffmpeg -nostdin no Windows
Sem a flag, exit 0 sem arquivo — o erro mais silencioso do pipeline
Render determinístico
Proibido Date.now(), Math.random(), fetch() — use GSAP timeline e seeded PRNG
💡
Próxima trilha
Trilha 4: Aplicações

Você concluiu a Trilha 3 — Por dentro. Agora que entende a estrutura interna, os gotchas e como o gerador funciona, é hora de aplicar: YouTube & Shorts, onboarding, aulas e a biblioteca de prompts para acelerar sua produção.

4.1
📺 YouTube & Shorts
4.2
🚀 Onboarding & aulas
4.3
🧰 Biblioteca de prompts