Product Demo Video
Demos de produto que parecem vídeo de lançamento de SaaS, não screencast.
A ideia central: não grave a tela. Capture screenshots em alta resolução + as coordenadas reais dos elementos com Playwright, e depois anime esse material no Remotion. Isso dá controle total sobre câmera, ritmo e legendas — e permite re-render em 10s quando o usuário disser "mais rápido", "zoom maior", "tira essa etapa", "faz uma versão de 10s". Regravar seria lento e daria um resultado pior.
Fluxo
- Setup (uma vez por projeto) →
node <skill>/scripts/setup.mjs - Roteiro → escreva
demo.json(verreferences/roteiro.md) - Captura →
node .demo-video/tools/capture.mjs demo.json capture/(Playwright) - Render →
node .demo-video/tools/render.mjs --capture capture/ --out out/(Remotion) - Entrega →
out/<nome>.mp4(1080p) +out/<nome>.gif(embedável em HTML) - Iterar → edite
demo.jsone rode só o passo que mudou
Mudou tempo/câmera/legenda? Só re-render (passo 3, ~30s). Mudou o fluxo no app? Re-capture também (passo 2).
Do pedido em português ao roteiro
O usuário descreve o fluxo em linguagem natural ("mostra como criar um agente");
quem traduz isso em seletores é você, olhando o app — nunca chutando. Chutar
#submit custa uma captura inteira que falha na cena 4.
- Abra a tela com as ferramentas de browser e leia o DOM.
- Colete os seletores na ordem de robustez:
[data-testid=...]>#id> texto visível (text=Novo agente) > caminho CSS. Caminho CSS quebra na primeira mudança de layout; texto quebra quando traduzem a interface. - Confirme o fluxo com o usuário antes de capturar — mostre o roteiro e pergunte se a ordem das etapas é essa. Ele conhece o produto; você não.
- Se o app exige login, peça para ele rodar o
auth.mjsprimeiro.
Quando não houver como abrir o app (produto interno, sem acesso), peça os seletores ou um print da tela com o HTML — é mais rápido que adivinhar.
O roteiro é a fonte da verdade
Um único JSON dirige o Playwright e o Remotion. Antes de capturar qualquer
coisa, escreva o roteiro e mostre ao usuário — é barato corrigir ali e caro
corrigir depois. Esquema completo em references/roteiro.md.
{
"name": "criar-agente",
"url": "https://app.exemplo.com",
"viewport": { "width": 1600, "height": 900 },
"scenes": [
{ "action": "goto", "value": "/dashboard", "camera": "wide", "caption": "Seu painel", "duration": 2 },
{ "action": "click", "target": "[data-testid='new-agent']", "camera": "zoom", "caption": "Crie seu agente", "duration": 2 },
{ "action": "type", "target": "#agent-name", "value": "Analista Financeiro", "camera": "focus", "duration": 2.5 },
{ "action": "click", "target": "button[type=submit]", "camera": "zoom", "duration": 1.5 },
{ "action": "wait", "value": 1200, "camera": "wide", "caption": "Pronto em 3 cliques", "duration": 2.5 }
]
}
Setup (primeira vez no projeto)
node "$HOME/.claude/skills/product-demo-video/scripts/setup.mjs"
Cria .demo-video/ (composição Remotion + as ferramentas em .demo-video/tools/),
instala as dependências e baixa o Chromium — ~300MB. Confirme com o usuário antes
de rodar; depois disso, capturar e renderizar levam segundos. Sugira adicionar
.demo-video/, capture/ e .auth/ ao .gitignore.
Para afinar o resultado vendo ao vivo, cd .demo-video && npx remotion studio abre
um preview com scrubber — muito melhor que renderizar para conferir timing.
Direção de câmera
O que separa uma demo boa de uma ruim é ritmo, não efeito. Regras que funcionam:
- Abra e feche em
wide. O espectador precisa se situar antes do zoom e ver o resultado depois. Zoom o filme inteiro cansa e desorienta. - Um zoom por intenção, não por clique. Cliques seguidos na mesma região =
uma cena só em
zoom. Zoom-in/zoom-out a cada clique dá enjoo. focussó para digitação. Campo de texto precisa estar legível; botão não.- 2 a 3 segundos por cena. Menos que 1.5s ninguém lê a legenda; mais que 4s vira tempo morto. Uma demo boa tem 15–25s e 5–8 cenas.
- Legenda é rótulo, não frase. "Crie seu agente", "Conecte seus dados". No máximo ~4 palavras, verbo no início, sem ponto final. Nem toda cena precisa de uma.
- Corte tempo morto na origem. Loading, redirect, animação de entrada: use
settlena cena em vez de deixar segundos parados no vídeo.
A câmera é contínua entre cenas (interpolada com easing), então o movimento já sai suave — não tente compensar isso no roteiro com cenas extras.
A camada de texto é a narração
Não existe áudio (GIF não tem, e vídeo em feed roda mudo). Quem conta a história é a tipografia, e ela só funciona se tiver hierarquia: se todo texto tem o mesmo peso, o espectador não sabe o que é passo e o que é argumento.
| estilo | papel | quando |
|---|---|---|
title (cena) | abertura/fechamento em tela cheia, com sub | 1 no começo, às vezes 1 no fim |
hero | a virada — grande, centralizado, sobre gradiente | no máximo 1 por demo |
label | o rótulo do passo (padrão), com sub opcional | na maioria das cenas |
stat | número que justifica o produto: "22s", "3 cliques" | 1 no fecho |
note | detalhe secundário, discreto | quando o passo precisa de ressalva |
Duas regras que sustentam o resto: um hero por demo (dois viram nenhum), e
o stat fecha — número grande é a última coisa que fica na cabeça de quem
assiste. Para uma demo executiva, o arco costuma ser title → labels → hero
no momento decisivo → stat no fim.
"highlight": true escurece a tela inteira menos o elemento da cena. É a arma
mais forte do conjunto e por isso a mais fácil de estragar: use quando o
espectador precisa levar aquele elemento embora, não como decoração. Ele entra
depois que a câmera assenta e sai sozinho no instante da ação, para não esconder
o resultado.
Credenciais
Nunca coloque senha no demo.json. Duas opções, nessa ordem de preferência:
"auth": { "storageState": ".auth/state.json" }— sessão já logada, gerada uma vez comnode .demo-video/tools/auth.mjs <url>(abre o browser, o usuário loga à mão)."auth": { "login": { "user": "#email", "pass": "#password", "submit": "button[type=submit]" } }com as credenciais emDEMO_USER/DEMO_PASSno ambiente.
Se a demo mostra dados reais, use "redact": ["selector"] no roteiro para borrar
elementos sensíveis antes da captura — sai borrado já no PNG, não dá para recuperar.
Saídas
| Formato | Uso | Como |
|---|---|---|
demo.mp4 1920×1080 | landing, LinkedIn, YouTube | padrão |
demo.gif 960×540 | README, docs, e-mail, HTML | --formats mp4,gif (padrão) |
demo-square.mp4 1080×1080 | Instagram/feed | --formats mp4,square |
GIF pesa. O render.mjs já usa palettegen (paleta dedicada ao vídeo) através do
ffmpeg que vem embutido no Remotion — não precisa instalar nada. Ainda assim, 15s
a 960px dão ~3.5MB; para README prefira --gif-width 720 --gif-fps 12, que corta
para menos de 1.5MB.
Para embedar no HTML: <img src="demo.gif" width="720" alt="...">. Se o usuário
aceitar MP4, <video autoplay loop muted playsinline> pesa ~10x menos.
Testar a instalação sem tocar no produto
assets/exemplo/ traz um app fake (app-exemplo.html) e o roteiro correspondente,
exercitando todas as ações — title, shot, click, type, select, hold,
redact. Rodar isso primeiro separa "a skill está quebrada" de "o seletor do app
está errado", que é a dúvida cara quando algo falha na primeira demo real.
Quando algo dá errado
- Seletor não encontrado →
capture.mjsfalha na cena e diz qual. Peça ao usuário a URL/tela e inspecione com o browser antes de chutar outro seletor. - Zoom borrado → aumente
scalepara 3 no roteiro (screenshots 3×) ou reduza o zoom. - Vídeo dessincronizado da legenda →
durationda cena é o que manda; ajuste lá. - Detalhes de composição, câmera, cursor e customização visual:
references/remotion.md.
