O que Opus Visual Motion Engine faz?
Um pipeline disciplinado para entregáveis visuais: planejar → construir a partir do template → renderizar → QA → corrigir → exportar. A qualidade vem das portas aplicadas antes e depois da renderização, não do gosto. Siga as etapas em ordem. Não pule uma etapa porque a tarefa "parece simples".
Os caminhos abaixo são relativos ao diretório desta skill (a base skill_resources que você recebeu). Chame-o de $SKILL.
0. Leia primeiro (incondicional)
Antes de escrever qualquer código, leia as referências que correspondem ao entregável. Elas são autoritativas para o tópico:
| Entregável | Leia |
|---|---|
| vídeo, motion graphic, explicador, vídeo de produto | references/video-generation.md, references/motion-design.md, references/javascript-animation.md |
| tipografia cinética / peça liderada por tipo | o conjunto de vídeo acima + references/typography.md |
| landing page, dashboard, mockup, UI | references/visual-design.md, references/typography.md |
| diagrama, esquema, visualização de dados | references/visual-design.md §4 |
| decisões de enquadramento / câmera | references/cinematography.md |
| durações e easings | references/animation-primitives.md |
| música ou voz para sincronizar | SKILL.md §10 + references/video-generation.md §6 |
Exemplos trabalhados estão em examples/*.md. Leia o mais próximo do briefing.
1. Planeje antes do código (escreva-o)
Escreva o plano em sua resposta (ou em um PLAN.md ao lado da saída) antes de criar o HTML. Um plano que existe apenas em sua cabeça não conta.
UI / página
- Mensagem principal: uma frase. O que o espectador deve entender em 3 segundos?
- Direção nomeada: um nome, 4–6 tokens hexadecimais nomeados com significados atribuídos, tipografia com papéis, tratamento de forma. Derive do setor do assunto, materiais e linguagem. Uma loja de brinquedos e um dashboard fintech nunca compartilham uma direção.
- Hierarquia: marque cada bloco como PRIMÁRIO / SECUNDÁRIO / TERCIÁRIO. Apenas o PRIMÁRIO recebe o único gesto ousado.
- Progressão de seções: uma linha por seção, dando seu trabalho e como ela faz a transição para a próxima.
- Matemática pré-render: largura do texto ≈
chars × 0.55 × font-size(sans) ou× 0.5(serif); largura da caixa =max(title_chars × 8, subtitle_chars × 7) + 24a 14px; encaixe de linha = Σlarguras + gaps ≤ contêiner. Trace cada conector contra cada caixa. Se cruzar um, roteie um caminho em forma de L.
Vídeo / motion graphic
- Mensagem central: uma frase. Se precisar de duas, são dois vídeos.
- Batidas: 3–7. Cada batida responde "o que o espectador agora sabe?"
- Cartão de cena por batida: início/fim (s), composição, câmera, tamanhos de tipo, primitivas (2–4), saída. Use o template em
references/motion-design.md§2. - Momento de assinatura: nomeie o ÚNICO quadro onde o mecanismo é mostrado, não rotulado (ex.: ruído filtrado em sinal). Se a história só funciona com suas legendas, redesenhe a batida.
- Linha do tempo global: timestamps absolutos. A SAÍDA de uma cena é concluída antes da próxima cena reivindicar o espaço. Defina
DURATIONpara o verdadeiro fim (10–20s por padrão; 30–45s apenas com narração).
Uma cena sem cartão não pertence ao vídeo.
2. Construa a partir do template (vídeo)
Copie assets/composition-template.html ao lado do entregável e preencha os blocos EDIT. Não reescreva os blocos ENGINE. Eles já fornecem:
- um
#stage1920×1080 (altereSTAGE_W/Hpara 1080×1920 ou 1080×1080), ajustado a qualquer janela; window.DURATION,window.seek(t),window.render(t),window.SCENES: o contrato de captura/exportação (QA captura cada cena deSCENES);at(),ease.*,lerp,mulberry32(PRNG seedado) elayer()para visibilidade de cena mais SAÍDA;- auxiliares de áudio
audioAt(t, "rms"|"low"),since(t, list),hit(t, list, dur)que leemwindow.AUDIO(§10) e retornam 0 sem ele; - um player de pré-visualização (espaço, ←/→, Home/End) que desliga automaticamente sob captura e movimento reduzido;
- tokens de cor nas 3 camadas necessárias (claro, protegido por
prefers-color-scheme,[data-theme]).
Para páginas, escreva um único arquivo HTML normal seguindo §6.
3. Tempo determinístico (vídeo)
render(t)é o único motor de estado. Cada posição, opacidade, tamanho, número e dashoffset é computado a partir detem segundos.seek(x)renderiza o quadro idêntico independentemente do que foi renderizado antes.testá em segundos. Mantenhatem[0, DURATION], nunca em[0, 1]: prender segundos a 0..1 congela silenciosamente toda a linha do tempo.- Proibido:
setTimeout,setInterval, animações/transições CSS como linha do tempo,Math.random()não seedado,Date.now()no estado.requestAnimationFramesó pode avançar o relógio de reprodução e chamarrender(clock). O template já faz isso. - Aleatoriedade: gere em tempo de construção a partir de
mulberry32(seed), nunca dentro derender. - Desenho SVG precisa de todos os três:
pathLength="1",stroke-dasharray="1"estroke-dashoffsetde 1 a 0. Sem um dasharray, a linha aparece totalmente desenhada desde o quadro 0. inline-blockem spans de palavras engole espaços finais. Coloque espaçamento emmargin-right.
4. Gramática de movimento
Cinco movimentos, cada um com um trabalho. Se um movimento não é nenhum destes, delete-o.
- ENTRADA: uma sequência orquestrada por página ou cena, em ordem de leitura, irmãos escalonados em 60–120ms. Fade-up em toda seção é a marca registrada de IA.
- ÊNFASE: um elemento de cada vez: um aumento de escala (≤1.2), um brilho, ou uma mudança de cor que carrega significado.
- TRANSIÇÃO: uma mudança de estado. A parte mudada se move; todo o resto permanece parado. 0.2–0.6s.
- CÂMERA: uma escala/translação no nível do stage, no máximo uma vez por cena, 0.8–1.2s, nunca durante a leitura de texto.
- SAÍDA: 0.3–0.5s, completamente concluída. Nenhum elemento sobrevive em meio-estado através de um limite de cena, exceto uma âncora de continuidade deliberada que persiste identicamente ou morfoses (o mesmo objeto mudando de estado, que é a transição mais forte disponível).
Transições carregam significado: corte duro = mudança de categoria, morph = mesma coisa mudando, pan = travessia. Nunca um crossfade padrão em todo lugar. Durações e easings estão em references/animation-primitives.md. Vizinhos que se movem juntos recebem durações diferentes ou um deslocamento.
5. Decisão de stack
| Situação | Stack |
|---|---|
| micro-movimento de página | Transições CSS em transform/opacity, 0.2–0.6s; loops ≤2s dentro de @media (prefers-reduced-motion: no-preference) |
| vídeo / motion graphic / tipografia cinética | template + vanilla render(t) (padrão) |
| >6 faixas escalonadas ou orquestração de morph SVG | GSAP UMD fixado, UMA linha do tempo mestre pausada, seek(t) → tl.seek(t) |
| gráficos | SVG inline (padrão) ou Chart.js UMD com animation:false, cores hexadecimais (canvas não pode ler variáveis CSS) |
| arte generativa | p5.js com randomSeed/noiseSeed |
| 3D, apenas quando 3D é a mensagem | Three.js UMD fixado; câmera roteirizada em render(t) |
Use o stack mais simples que atinja o resultado. Scripts externos devem ser versões exatas fixadas de cdnjs.cloudflare.com ou cdn.jsdelivr.net. Embuta todo o resto. Cada fonte recebe um stack de fallback real.
6. Piso técnico (não negociável)
- Único
.htmlcom CSS e JS inline, abaixo de 16 MB, imagens/dados como URIsdata:. - Cores como tokens
:root; camada escura sob@media (prefers-color-scheme: dark)protegida por:root:not([data-theme="light"])e repetida sob:root[data-theme="dark"]; fundobodyexplícito. meta viewportcomviewport-fit=cover;env(safe-area-inset-*)no padding;height:100%em html/body, nunca100vh; blocos largos rolam dentro de seu própriooverflow-x:auto; grids usamminmax(0,1fr).- Números computados exibidos passam por
Math.round,toFixedouIntl.NumberFormat. Contagens pousam exatamente no valor final. - Acessibilidade: contraste ≥4.5:1 (≥3:1 para texto ≥24px), foco visível,
prefers-reduced-motionrespeitado (o template mostra o quadro final estabilizado). - Tipografia legível: em um quadro de vídeo 1080p, nenhum texto abaixo de 20px (H/54). Em páginas móveis, nenhum texto do corpo abaixo de 14px.
- Armazenamento do navegador nunca retém estado da linha do tempo. Onde o armazenamento é usado, envolva-o em try/catch.
7. Evite design genérico de IA
Onde o briefing deixa liberdade, nunca gaste-a nesses padrões (qa.mjs sinaliza vários automaticamente):
| Marca registrada | Correção |
|---|---|
creme #F4F1EA-ish + serifa display + acento terracota #D97757-ish | uma paleta tirada dos próprios materiais do assunto |
| quase-preto + um acento ácido, usado independentemente do assunto | permitido apenas quando a direção justifica por escrito |
| cards idênticos arredondados, um raio em todo lugar, a mesma sombra suave | tokens de raio por hierarquia; apenas objetos limitados por card |
| gradientes como decoração, gradientes roxos, glassmorfismo | preenchimentos planos; no máximo um gradiente, apenas para uma propriedade física contínua |
| sobrancelhas em ALL-CAPS com tracking, strings de meta com ponto médio, "→" em links | letras minúsculas; delete rótulos que repetem o conteúdo |
| Inter / fonte do sistema por padrão | tipografia escolhida para este assunto, com um stack de fallback |
| uma única palavra acentuada em um título | permitido APENAS quando a cor de acento é um token semântico usado para o mesmo significado em outro lugar na peça (ex.: coral = ruído) |
| herói genérico: título + subtítulo + CTA + screenshot | comece com a coisa mais característica no mundo do assunto |
| tudo centralizado, a mesma grade em cada seção | um sistema de alinhamento por seção; varie o ritmo |
| tudo animando ao mesmo tempo, 300ms em todo lugar | o mapa de duração + escalonamento (§4) |
Gaste ousadia em um lugar, depois remova um acessório.
8. Render + QA (obrigatório antes de entregar)
Execute o QA automatizado. Ele renderiza o arquivo headless e escreve evidências mais um veredito. Precisa de Node ≥18. A primeira execução em uma cópia nova da skill precisa de npm install em $SKILL/scripts (pule se scripts/node_modules existir).
node "$SKILL/scripts/qa.mjs" caminho/para/entregavel.html
No DSH no Windows, execute-o com a ferramenta pwsh usando o mesmo comando. Opções: --step 0.25 (uma grade mais fina para cenas rápidas), --out <dir>, --mode page|video.
Modo vídeo (automático quando DURATION + seek existem) captura um quadro a cada passo e verifica: APIs proibidas, determinismo em revisitas fora de ordem, uma linha do tempo congelada, ar morto, spans quase vazios, um quadro final instável, colisões de texto, texto sobre outros gráficos, clipping, contraste, tamanho mínimo de tipo, linhas dash-offset que nunca desenham, loops de movimento reduzido, erros de console/página e marcas registradas do template.
Modo página renderiza desktop 1440, mobile 390 e esquema escuro, e verifica as mesmas regras de layout além de transbordo horizontal móvel.
As saídas vão para qa-<nome>/: report.md (veredito e descobertas), report.json (cobertura de tinta por quadro e mudança), contact-sheet.png (cada quadro em uma imagem), moments.png (entrada, meio e fim de cada cena, retirados de window.SCENES), key_000..100.png e frames/.
- Passe o momento de assinatura e qualquer ponto de sincronização explicitamente:
--at 5.4,9.1. - Camadas deliberadas (tipo cinético empilhado intencionalmente, um título sobre filmagem) vão dentro de um elemento com
data-qa-allow-overlap. Isso pula as verificações de colisão e texto-sobre-gráficos apenas para aquela subárvore. Nunca use para silenciar uma colisão acidental. - Um WARN
fontsignifica que a família de primeira escolha não carregou e o texto está em um fallback. Corrija antes de julgar layout, porque toda largura está errada.
Código de saída 0 = nenhum FAIL. FAIL é bloqueante. Cada WARN é corrigido ou justificado em uma linha em sua crítica.
9. Loop de crítica
- Se seu modelo pode ler imagens: abra
contact-sheet.png, depois qualquerkey_*.pngque parecer errada. Julgue os pixels, não suas intenções. - Se seu modelo é apenas texto: use
report.mdmais a tabela por quadro emreport.json.coverage< 0.015 significa que o quadro parece vazio. Umdiffde 0 através de uma cena significa que nada se move. Um pico emdiffmarca um corte. Verifique cruzadamente cada cartão de cena com timestamps contra esses números. - Responda cada pergunta com uma observação concreta, não "✓":
- Composição: em cada quadro-chave, onde o olho pousa primeiro? Isso é a mensagem?
- Hierarquia: o PRIMÁRIO é inconfundível? Há exatamente uma coisa em movimento que importa por quadro?
- Mecanismo: o momento de assinatura mostra a ideia com as legendas removidas?
- Continuidade: todas as transições estão limpas, sem nada em meio-estado em um limite e âncoras idênticas ou morfando?
- Tempo: há deslocamentos presentes, sem durações padrão, e tempo de leitura ≥1.2s por 8 palavras?
- Tipografia / contraste / densidade: tamanhos conforme o plano, ≤5 elementos concorrentes por quadro, legível no modo escuro?
- Narrativa: um espectador poderia repetir a mensagem central após uma assistida?
- Corrija → reexecute
qa.mjs→ re-critique. Repita até QA passar e a crítica não ter item aberto. - Pergunta final, respondida honestamente: "Isso parece intencionalmente projetado, ou meramente gerado?" Se "gerado": nomeie o que é genérico, revise o plano (§1) e reconstrua. Uma primeira renderização quase nunca é o entregável. Espere 2–3 gerações.
10. Sincronização de áudio (quando há música ou voz)
Nunca adivinhe o tempo pelo ouvido. Transforme o áudio em dados e deixe render(t) lê-los:
node "$SKILL/scripts/audio-data.mjs" music.mp3 --inject composition.html # escreve window.AUDIO no HTML
AUDIO.beats(uma grade uniformemente espaçada no tempo estimado),AUDIO.onsets(batidas, notas, sílabas) e por quadroAUDIO.rms/AUDIO.low(0..1;lowé a banda de kick/baixo <150 Hz).- Dirija o movimento com os auxiliares do template:
hit(t, AUDIO.beats, 0.2)para um pulso de batida (ÊNFASE),audioAt(t, "low")para escala reativa ao kick,since(t, AUDIO.onsets)para revelações por batida. Coloque cortes de cena nas batidas: escolha inícios de cena deAUDIO.beats. - A sincronização é exata ao quadro por construção: um pulso pousa no primeiro quadro em ou após sua batida (≤1 quadro, ≤33 ms a 30 fps). Se o áudio começar depois de t=0, passe
--offset <s>. - Os níveis são verificados antes de alguém ouvir. Amostras cortadas saem com código 1. Um pico de amostra acima de −1 dBFS avisa, porque a codificação AAC vai cortá-lo. Volume fora de −18..−12 LUFS é relatado (plataformas normalizam para cerca de −14).
--bpmsubstitui a estimativa automática.- Um arquivo inalterado usa
--dry-run --printpara inspecionar beats, onsets, níveis RMS e frequências sem modificar o HTML.