Você dá a URL. O Claude Code navega o app de verdade, captura as telas reais passo a passo e monta um vídeo com moldura de navegador, cursor animado e narração. Tudo na máquina, sem chave de API.

Não é motion graphics explicando um conceito (isso é a skill video-explicativo): aqui as telas do vídeo são o seu app, capturadas de verdade com navegador automatizado. O resultado é um walkthrough em PT-BR de 35–50s, 16:9, que termina na CTA do INEMA.CLUB.
Na captura, a skill pega a bounding box real de cada alvo com getBoundingClientRect. O cursor animado cai no centro do controle — com pulse e ripple no clique. É isso que faz parecer gravação profissional, e não print com seta por cima.
Captura via agent-browser (Playwright), render HTML→MP4 via HyperFrames, narração PT-BR via TTS Kokoro (voz pf_dora) rodando na própria máquina. Nenhuma chamada paga, nenhum serviço externo.
O gerador lê o steps.json e mede a duração de cada WAV com ffprobe. Não existe tabela de tempos na mão: áudio e animação nascem batidos, e os loops de ambiente não deixam cauda muda no fim.
O render do HyperFrames é determinístico — sem rede durante a renderização. Por isso o site nunca é carregado ao vivo dentro do vídeo: primeiro captura-se os screenshots reais, depois anima-se por cima. O viewport fixo da captura vira o espaço de coordenadas onde o cursor mira.
5–8 passos + CTA (≈35–50s). Cada frase tem duas formas: tela (PT-BR acentuado, botões do app na grafia original — Generate, Upload) e fala (números expandidos e inglês reescrito foneticamente — upload → "âploud"). A revisão vem antes de capturar e narrar.
O capture.mjs lê um actions.json, dirige o agent-browser pelo app, tira 1 screenshot por estado e grava a bounding box de cada alvo. Saída: assets/shots/*.png + steps.json. Dá pra fazer na mão quando o app é imprevisível (login, estados dinâmicos).
O build-demo.mjs lê o steps.json, mede os WAVs e monta moldura de navegador + cursor global + destaque + zoom no resultado + CTA. Depois: lint, inspect, draft pra conferir frames e render final em high / 30fps.
A skill é auto-contida (já traz as fontes em assets/fonts/) e não depende de nenhum outro projeto. Só precisa do runtime local e do app-alvo no ar.
Base do HyperFrames (render HTML→MP4) e do ffprobe, que mede as durações da narração.
node --version # precisa ser 22+ ffmpeg -version
Navegador headless usado pelo render. Baixa uma vez e fica cacheado.
npx hyperframes browser ensureNarração local, voz pf_dora. A primeira execução baixa ~340MB de modelo.
pip install kokoro-onnx soundfileSkill de navegação (Playwright) que executa as ações e tira os screenshots reais.
agent-browser set viewport 1280 800A URL precisa responder durante a captura — localhost ou público. App com login exige credenciais de teste.
# ex.: seu app servindo em http://localhost:8000/
Copie skills/video-demonstrativo/ deste repo para as suas skills do Claude Code.
cp -r skills/video-demonstrativo \ ~/.claude/skills/
Na prática você pede o vídeo ao Claude Code e ele conduz o fluxo. Abaixo, o que acontece por baixo — os comandos reais, na ordem em que a skill os executa.
A lista de ações a demonstrar, com 1 frase de narração por passo. Arco: abrir o app → ação 1 → ação 2 → … → resultado → CTA. O 1º passo é a tela inicial (intro:true); o último de conteúdo é o resultado (zoom:true).
# 5–8 passos + CTA ≈ 35–50s de vídeo # ex.: escrever prompt → escolher 512² → ajustar altura → Gerar → salvar
Varra a acentuação PT-BR palavra a palavra. Fixe as duas formas de cada frase: tela (caption + labels, inglês na grafia original) e fala (txt/sN.txt, inglês foneticamente). O Kokoro fonemiza pela grafia escrita — acento errado contamina tela e locução.
# tela: "2 · Escolha o tamanho — 512²" (Generate, Upload na grafia original) # fala: "Depois, escolha o tamanho. Vamos de quinhentos e doze." # léxico inglês→PT: upload→âploud · deploy→deplói · Generate→djenereit
Descreva URL, viewport e passos num actions.json; o script abre o app, executa cada ação, tira o screenshot do estado e pega a bounding box real do alvo. Ações: fill, click, clickText, setValue, wait.
node capture.mjs actions.json # -> assets/shots/*.png + steps.json # ou na mão, quando o app é imprevisível (login, estado dinâmico): agent-browser set viewport 1280 800 agent-browser open http://localhost:8000/ agent-browser snapshot -i # descobre refs @e1, @e2... agent-browser fill @e6 "texto" agent-browser screenshot assets/shots/01-prompt.png
Tudo vive numa pasta única em ~/projetos/output/<nome>/: projeto, captures, áudios, index.html e o MP4 final. Copie as fontes embutidas na skill (ou rode o fetch-fonts.mjs) — sem CDN no render.
cd ~/projetos/output npx hyperframes init <nome> --example blank --non-interactive # fontes: copie assets/fonts/ da skill, ou: node fetch-fonts.mjs
O template lê o steps.json, escreve assets/txt/sN.txt (1 por passo + CTA, já na forma-fala revisada) e gera os WAVs. Ao final imprime a duração de cada faixa.
bash narration-template.sh # -> assets/txt/sN.txt + assets/audio/sN.wav # por baixo, faixa a faixa: npx hyperframes tts assets/txt/s1.txt --voice pf_dora --speed 0.98 --output assets/audio/s1.wav
Copie scripts/composition-template.mjs como build-demo.mjs. Ele lê o steps.json e mede os WAVs com ffprobe — moldura de navegador, cursor global animado, destaque, zoom no resultado e CTA do INEMA.CLUB já vêm prontos.
cp ~/.claude/skills/video-demonstrativo/scripts/composition-template.mjs build-demo.mjs node build-demo.mjs # -> index.html (16:9). Não editar à mão.
Meta: 0 erros no lint e 0 problemas no inspect. Anime o .scene-inner (nunca o wrapper .clip), cenas e captions em tracks alternados, decorativos e moldura com data-layout-ignore.
npx hyperframes lint # 0 erros npx hyperframes inspect --samples 14 # 0 problemas
Primeiro um draft: extraia 1 frame por passo e mostre ao usuário — você não escuta o áudio, então quem valida a locução é ele. Aprovado, vai pro render final. O MP4 sai na raiz do próprio projeto.
# confere npx hyperframes render --quality draft ffmpeg -nostdin -y -ss 12 -i video.mp4 -vframes 1 -update 1 frame.png # final npx hyperframes render --quality high --fps 30 --output <nome>-16x9.mp4
O actions.json é a entrada da captura; o steps.json é o que sai dela e alimenta a composição. Exemplos completos em scripts/actions.example.json e scripts/steps.example.json — o caso de referência é um walkthrough do inemaimg (playground de geração de imagem) gerando uma imagem do zero.
Você descreve a ação e o alvo do cursor. O script resolve o resto.
{
"url": "http://localhost:8000/",
"viewport": [1280, 800],
"window": { "urlLabel": "localhost:8000" },
"steps": [{
"id": "size",
"do": { "type": "clickText", "tag": "button", "text": "512²" },
"target": { "tag": "button", "text": "512²" },
"click": true,
"caption": "2 · Escolha o tamanho — 512²",
"narration": "Depois, escolha o tamanho. Vamos de quinhentos e doze."
}]
}O alvo virou coordenada real, no espaço do screenshot. É o que o cursor persegue.
{
"viewport": [1280, 800],
"steps": [
{ "shot": "00-home.png", "intro": true, "target": null },
{ "shot": "02-size.png", "click": true,
"target": { "x": 179, "y": 501, "w": 44, "h": 26 } },
{ "shot": "04-result.png", "zoom": true,
"target": { "x": 668, "y": 128, "w": 484, "h": 324 } }
]
}Existe um curso INEMA.CLUB sobre esta skill — 3 trilhas, 10 módulos, do princípio "capturar antes, animar depois" até o render final: inematds.github.io/skill-video-demonstrativo.
Honestidade antes do render: se o seu caso cair aqui, é melhor saber agora.
Estado dinâmico do app (animações, vídeo, dados ao vivo) vira print estático. Movimento real exigiria gravar vídeo da tela — outro caminho, com sincronia de narração mais difícil.
App com autenticação precisa de credenciais de teste. Sem elas, capture só as telas públicas.
Formato natural, porque telas de app são landscape. 9:16 exigiria recortar e reenquadrar cada shot.
O Kokoro é bom, mas não atua. E você não escuta o resultado — quem valida a locução é sempre o usuário.
Versionamento v1.yy.xxx — yy = recurso, xxx = correção. Histórico completo no CHANGELOG.
agent-browser + HyperFrames + TTS Kokoro, sem chave de API. Moldura de navegador, cursor global mirando a bounding box real, destaque/zoom e CTA do INEMA.CLUB. Princípio "capturar antes, animar depois". Saída única em ~/projetos/output/<nome>/, sem cauda muda (ambientRepeat).revisao-texto.md.agent-browser record em vez de screenshots, pra capturar animações e dados ao vivo. O desafio é a sincronia da narração.