Gerador de Vídeo 2D VidTSX
Gere arquivos TSX 2D prontos para produção para VidTSX a partir de uma descrição de um plano ou vídeo. Os arquivos são composições Remotion que o VidTSX renderiza quadro a quadro, então a correção é mais importante que a criatividade: um único intervalo de interpolate não monotônico ou um useState perdido irá quebrar a renderização.
Esta skill cobre gráficos 2D em movimento. Trabalhos 3D (@remotion/three / React Three Fiber) estão fora do escopo.
Como usar esta skill
- Plano único claro → gere o arquivo
.tsxdiretamente. - Pedido complexo, com múltiplos planos ou visualmente ambíguo → confirme a divisão dos planos ou a direção visual em uma mensagem curta primeiro, depois gere. Não pergunte demais; uma confirmação de uma linha é suficiente.
- Entregue como um arquivo
.tsxreal, não como código inline. Esses componentes TSX sempre excedem 20 linhas, e o usuário edita esses arquivos diretamente. Padrão: um plano por arquivo (atômico, fácil de editar), a menos que o usuário peça um único entregável combinado. - O arquivo contém apenas o código do componente — os comentários de seção estrutural abaixo são bem-vindos, mas sem fences markdown, sem prosa explicativa dentro do arquivo.
- Renderize e veja antes de considerar pronto — veja Verificar a renderização. Um plano sobre o qual você apenas raciocinou não está finalizado; uma imagem estática (e, para revelações/rolagens, quadros em cada ponto-chave) é o padrão.
Especificação de saída
Presets de dimensão
| Formato | Largura | Altura | Uso |
|---|---|---|---|
| horizontal | 1920 | 1080 | YouTube, apresentações |
| vertical | 1080 | 1920 | TikTok, Reels, Shorts |
| quadrado | 1080 | 1080 | Feed do Instagram |
Padrões
- Formato: horizontal (1920×1080)
- Duração: 5 segundos
- FPS: 30
- Estilo: minimalista (veja
references/style-presets.md)
Estrutura obrigatória do arquivo
Cada arquivo gerado segue este esqueleto. Os comentários de seção rotulados são intencionais — eles mantêm arquivos grandes navegáveis.
import React from 'react';
import {
useCurrentFrame,
useVideoConfig,
interpolate,
Easing,
AbsoluteFill,
Sequence,
} from 'remotion';
// =============================================================================
// CONFIGURAÇÃO DA COMPOSIÇÃO
// =============================================================================
export const compositionConfig = {
id: 'NomeDoComponente', // PascalCase apenas — SEM hífens ou underscores
durationInSeconds: 5,
fps: 30,
width: 1920,
height: 1080,
};
// =============================================================================
// CONSTANTES DE ESTILO
// =============================================================================
const CORES = {
primary: '#6366f1',
secondary: '#8b5cf6',
accent: '#06b6d4',
background: '#0f0f23',
text: '#ffffff',
} as const;
const TIPOGRAFIA = {
fontFamily: 'Inter, system-ui, sans-serif',
} as const;
const EASINGS = {
easeOut: Easing.bezier(0.33, 1, 0.68, 1),
easeIn: Easing.bezier(0.32, 0, 0.67, 0),
easeInOut: Easing.bezier(0.37, 0, 0.63, 1),
overshoot: Easing.bezier(0.34, 1.56, 0.64, 1),
} as const;
// =============================================================================
// DADOS PRÉ-GERADOS (calculados uma vez no nível do módulo, NÃO durante a renderização)
// =============================================================================
const seededRandom = (seed: number): number => {
const x = Math.sin(seed * 9999) * 10000;
return x - Math.floor(x);
};
// =============================================================================
// COMPONENTE PRINCIPAL
// =============================================================================
const NomeDoComponente: React.FC = () => {
const frame = useCurrentFrame();
const { fps, durationInFrames, width, height } = useVideoConfig();
return (
<AbsoluteFill style={{ backgroundColor: CORES.background }}>
{/* Conteúdo */}
</AbsoluteFill>
);
};
export default NomeDoComponente;
Substitua NomeDoComponente por um nome em PascalCase descrevendo o plano, defina o bloco CORES a partir do preset de estilo escolhido e ajuste width/height/durationInSeconds/fps conforme a solicitação.
Regras de correção
Estas não são preferências estilísticas — são o que faz uma renderização ter sucesso. VidTSX (via Remotion) renderiza cada quadro chamando o componente em um número de quadro fixo. Não há loop de eventos, persistência entre quadros ou tempo de relógio. Qualquer coisa que assuma essas coisas quebrará.
Animação é baseada em quadros, sempre
- Conduza todo o movimento a partir de
useCurrentFrame()+interpolate(). - Nunca use
useState,useEffect,setTimeout,setIntervalou animações/transições CSS. Eles não se encaixam no modelo quadro a quadro e produzem saída quebrada ou não determinística. - Para qualquer coisa aleatória ou procedural, use
seededRandom(ou uma função seed equivalente) para que cada renderização de um determinado quadro seja idêntica. Pré-gerar arrays de partículas/aleatórios no nível do módulo, nunca dentro da função de renderização — recomputar por quadro causa cintilação. - Animações escalonadas para que os elementos entrem em sequência, não todos de uma vez. Fica melhor e é a aparência esperada.
interpolate — intervalos de entrada devem ser estritamente crescentes
O intervalo de entrada do interpolate deve ser estritamente monotônico crescente. Valores duplicados ou decrescentes geram erro em tempo de execução.
// ✅ Correto
interpolate(frame, [0, 30, 60], [0, 1, 0]);
// ❌ Gera erro — intervalo de entrada decresce
interpolate(frame, [60, 30, 0], [0, 1, 0]);
Para inverter um mapeamento, inverta o intervalo de saída, nunca o intervalo de entrada:
// ✅ Correto — mapeia 0→100, 1→0
interpolate(value, [0, 1], [100, 0]);
// ❌ Errado
interpolate(value, [1, 0], [100, 0]);
Para temporização baseada em índice, certifique-se de que startFrame < endFrame e sempre use clamp:
const startFrame = index * 30;
const endFrame = startFrame + 30;
interpolate(frame, [startFrame, endFrame], [0, 1], {
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
});
Sempre passe extrapolateLeft: 'clamp' e extrapolateRight: 'clamp' a menos que um valor ilimitado seja genuinamente desejado — sem eles, os valores ultrapassam seu intervalo antes/depois dos keyframes.
Easing — use Easing.bezier(), nunca sintaxe wrapper
Formas wrapper como Easing.out(Easing.cubic) quebram. Defina beziers nomeados uma vez (veja o bloco EASINGS no esqueleto) e referencie-os:
// ❌ Quebra
Easing.out(Easing.cubic);
Easing.in(Easing.quad);
// ✅ Correto
interpolate(frame, [0, 30], [0, 1], {
easing: EASINGS.easeOut,
extrapolateRight: 'clamp',
});
chroma-js — apenas importação de namespace
// ✅ Correto
import * as chroma from 'chroma-js';
const color = chroma('#00bfff').brighten(0.5).hex();
// ❌ Errado — "chroma is not a function"
import chroma from 'chroma-js';
@remotion/paths — apenas as funções reais existem
Estes helpers não existem e nunca devem ser usados:
makeCircle(), makeRect(), makeTriangle(), makeLine(), makePie(), makePolygon(), makeEllipse(), makeStar().
Apenas estes imports são válidos:
import { evolvePath, getLength, getPointAtLength, getTangentAtLength } from '@remotion/paths';
Escreva strings de caminho SVG manualmente e anime-as com evolvePath:
const circlePath = 'M 50 10 A 40 40 0 1 1 49.99 10 Z';
const rectPath = 'M 0 0 L 100 0 L 100 50 L 0 50 Z';
const linePath = 'M 0 0 L 100 100';
const { strokeDasharray, strokeDashoffset } = evolvePath(progress, rectPath);
ID da composição
compositionConfig.id é apenas PascalCase — sem hífens, sem underscores (ex.: ProductReveal, não product-reveal ou product_reveal).
Layout
Zonas seguras
- Top 10%: reserve para a UI da plataforma.
- Bottom 15%: reserve para legendas/botões.
- Mantenha o conteúdo principal entre 25%–75% verticalmente.
Auxiliar de centralização
const centered: React.CSSProperties = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
};
Tipografia
| Elemento | Tamanho | Peso |
|---|---|---|
| Manchetes | 72–120px | 700–900 |
| Subtítulos | 36–48px | 500–700 |
| Corpo | 28–36px | 400–500 |
Sempre defina margin: 0 em elementos de texto — valores padrão do navegador deslocam layouts do centro.
Presets de estilo
Neste repositório o padrão é a marca da casa, não um preset. Planos importam CORES, GRADIENTE, RAIO, SOMBRA, EASINGS de remotion/src/brand.ts e FONTE_MANCHETE, FONTE_EXIBIÇÃO, FONTE_CORPO, FONTE_MONO de remotion/src/fonts.ts em vez dos blocos locais CORES/TIPOGRAFIA do esqueleto, e seguem brand.md. FONTE_MANCHETE (Instrument Serif) tem um peso: nunca dê a ela fontWeight. Os planos ficam no fundo dia a menos que seu compositionConfig diga ground: 'night'; os componentes do kit (lib/shorts.tsx, BrandBg) escolhem suas cores a partir dele, então leia useGround() de brand.ts para qualquer cor que você definir.
Quando o usuário nomear um estilo, carregue sua paleta e características de references/style-presets.md e insira o objeto CORES no esqueleto. Os seis presets são: minimalista, memphis, neo-brutalismo, glassmorfismo, neon/cyberpunk, e corporativo. Se nenhum estilo for nomeado, use a marca da casa acima.
Verificar a renderização (não pule)
Um plano não está pronto até que você tenha olhado para ele. Após escrever o .tsx, renderize pelo menos uma imagem estática e abra-a — nunca entregue um plano sobre o qual você apenas raciocinou. A renderização pode ter sucesso e os números parecerem corretos, mas o texto pode transbordar seu cartão, uma imagem pode ser cortada no lugar errado, um elemento pode estar fora da tela, ou duas coisas podem se sobrepor. Apenas uma captura de tela pega isso.
- Renderize uma imagem estática (ex.:
node scripts/render-all.mjs --still --scale=1 <ShotId>) e Leia o PNG. - Para revelações baseadas em tempo (elementos entrando em um ponto-chave, rolagens, fases encenadas), uma única imagem estática com 60% da duração não é suficiente — renderize o clipe completo e extraia quadros em cada momento chave (
ffmpeg -ss <t> -i out/<id>.mp4 -frames:v 1 f.jpg), ou renderize imagens estáticas em vários deslocamentos. Verifique o estado em cada ponto-chave, não apenas em um quadro. - Verifique especificamente: imagens cabem em seu espaço (nenhum conteúdo importante cortado; prefira
objectFit: containem um fundo correspondente para imagens reais heterogêneas), texto não está cortado ou transbordando, nada está fora da área segura, e cada elemento animado está realmente visível quando deveria. - Se estiver sobreposto a outras filmagens, também verifique um quadro do resultado final finalizado, não apenas o plano isolado.
Corrija o que a captura de tela revelar, depois re-renderize e olhe novamente. Trate "Eu renderizei e parece correto" — com o quadro mostrado — como o padrão para pronto.
Saída final
Gere o arquivo .tsx completo e nada mais dentro dele — sem markdown ao redor, sem comentários antes ou depois do código no próprio arquivo. Uma pequena nota de uma linha no chat ao entregar o arquivo é aceitável.