Product Demo Video (Vídeo de Demonstração de Produto)
Transforme capturas de tela estáticas ou designs de produtos em uma demonstração animada e polida — sem gravador de tela, sem cursor real trêmulo, sem desvio de versão da UI. Moldura cada tela, deslize um cursor sintético até o pixel exato, faça o ripple do clique, amplie a parte que importa e legende a funcionalidade. Como a entrada é estática e o movimento é código, cada valor é determinístico e re-renderiza identicamente quando a captura de tela muda.
Quando usar
- Demo de app / SaaS, anúncio de funcionalidade, walkthrough de integração (15–90s).
- Mostrar um caminho de clique por uma UI da qual você só tem capturas de tela/designs.
- Destacar uma região focal de um painel denso (zoom-na-região + chamada).
Por que capturas de tela vencem gravações de tela
| Gravação de tela | Baseada em captura de tela (esta skill) |
|---|---|
| Cursor real deriva, ultrapassa, treme | Cursor segue um caminho suavizado até um alvo exato |
| Regravar toda a tomada para corrigir um passo | Troque um PNG, re-renderize o mesmo código |
| UI muda no meio da tomada, mouse procura | Cada tela é um ativo limpo e de estado final |
| Difícil ampliar sem pixelização | Exporte capturas de tela em 2× (DPR 2) → zooms nítidos |
Capture as entradas com o dobro do tamanho de exibição (retina / deviceScaleFactor: 2) para que um zoom de 2× permaneça nítido. Consulte references/screenshot-demo.md para um script de captura com Playwright.
O loop da demonstração
Cada demonstração segue o mesmo ritmo, repetido por funcionalidade. Segure o tempo suficiente para ler, mova com intenção, execute o clique, revele o resultado.
| Ritmo | Duração | Função |
|---|---|---|
| Pousar na tela | 0,5–1s | Deixe o espectador se orientar antes que algo se mova |
| Cursor viaja ao alvo | 0,4–0,8s | Movimento suavizado (ease-in-out), não linear |
| Ripple de clique + mudança de estado | 0,2–0,3s | Feedback tátil; a tela reage no mesmo quadro |
| Zoom para região focal | 1–2s para entrar, segurar 2–4s | 2× é o ponto ideal — detalhe suficiente, mantém o contexto |
| Legendar a funcionalidade | segurar junto com o zoom | Uma frase de benefício, ex. "Exportação com um clique" |
Mova a câmera ou o cursor, raramente ambos ao mesmo tempo — dois movimentos simultâneos dividem a atenção.
Moldura a captura de tela
Uma captura de tela crua parece um relatório de bug; uma emoldurada parece produto. Envolva cada tela em uma moldura de navegador ou dispositivo sobre um fundo com marca.
// Moldura de navegador — coloque uma captura de tela, obtenha uma janela crível
const BrowserFrame = ({ url, children }) => (
<div style={{ borderRadius: 12, overflow: "hidden", background: "#1e1e22",
boxShadow: "0 40px 80px rgba(0,0,0,.35)" }}>
<div style={{ height: 40, display: "flex", alignItems: "center", gap: 8, padding: "0 14px" }}>
{["#ff5f57", "#febc2e", "#28c840"].map((c) => (
<span key={c} style={{ width: 12, height: 12, borderRadius: "50%", background: c }} />
))}
<div style={{ marginLeft: 12, flex: 1, height: 24, borderRadius: 6, background: "#2b2b30",
color: "#9aa", fontSize: 13, display: "flex", alignItems: "center", padding: "0 10px" }}>
{url}
</div>
</div>
<div style={{ background: "#fff" }}>{children}</div>
</div>
);
Coloque uma URL real na barra de endereços (autenticidade), adicione uma sombra suave e um fundo gradiente. Para dispositivos móveis, troque para uma moldura de telefone com notch e cantos arredondados. Mantenha o estilo da moldura em um único objeto de tema para que todas as telas combinem.
Cursor animado + ripple de clique (o movimento central)
Direcione a posição a partir do quadro atual, nunca de uma transição CSS ou relógio de parede — um renderizador de vídeo precisa que cada quadro seja uma função pura do número do quadro, ou o cursor dessincroniza e pisca.
import { useCurrentFrame, interpolate, spring, useVideoConfig } from "remotion";
// Viagem suavizada de `from` para `to` entre startFrame e startFrame+dur
const useCursor = (from, to, startFrame, dur) => {
const frame = useCurrentFrame();
const t = interpolate(frame, [startFrame, startFrame + dur], [0, 1],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: (x) => x * x * (3 - 2 * x) }); // smoothstep
return { x: from.x + (to.x - from.x) * t, y: from.y + (to.y - from.y) * t };
};
export const Cursor = ({ from, to, startFrame, dur, clickAt }) => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const { x, y } = useCursor(from, to, startFrame, dur);
// O ripple se expande uma vez, no quadro do clique
const since = frame - clickAt;
const showRipple = since >= 0 && since < 18;
const r = spring({ frame: since, fps, config: { damping: 18 }, durationInFrames: 18 });
return (
<>
{showRipple && (
<div style={{ position: "absolute", left: x, top: y, transform: "translate(-50%,-50%)",
width: 12 + r * 60, height: 12 + r * 60, borderRadius: "50%",
border: "2px solid rgba(80,140,255,.9)", opacity: 1 - r }} />
)}
<svg width="28" height="28" viewBox="0 0 28 28"
style={{ position: "absolute", left: x, top: y, filter: "drop-shadow(0 2px 3px rgba(0,0,0,.4))" }}>
<path d="M3 2 L3 21 L9 15 L13 24 L16 23 L12 14 L20 14 Z" fill="#fff" stroke="#222" strokeWidth="1.4" />
</svg>
</>
);
};
O cursor usa smoothstep para acelerar a partir do repouso e desacelerar até o alvo (um cursor linear parece robótico). Dispare o ripple e a mudança de estado da tela no mesmo quadro clickAt para que causa e efeito coincidam. Consulte references/screenshot-demo.md para a composição completa conectando múltiplos cliques em múltiplas telas.
Zoom para uma região (Ken Burns para UI)
Para ampliar um elemento focal, escale a tela e defina transform-origin para o centro desse elemento, para que o zoom convirja para ele em vez do centro do quadro.
const ZoomToRegion = ({ target, scale = 2, startFrame, dur, children }) => {
const frame = useCurrentFrame();
const s = interpolate(frame, [startFrame, startFrame + dur], [1, scale],
{ extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: (x) => 1 - Math.pow(1 - x, 3) }); // easeOutCubic
return (
<div style={{ transform: `scale(${s})`, transformOrigin: `${target.x}px ${target.y}px`,
willChange: "transform" }}>
{children}
</div>
);
};
Limite o zoom a cerca de 2× (acima de 3× perde contexto e desorienta). Segure o estado ampliado por 2–4s — tempo suficiente para ler o detalhe — depois saia suavemente antes de passar para a próxima tela. Acompanhe o zoom com uma chamada, nunca sem.
Anotações de chamada e legendas de funcionalidades
Anote o ponto de inflexão, depois deixe-o limpar. Uma chamada é um rótulo + um conector (seta, círculo ou sublinhado) que aponta para o elemento focal.
- Destaque o elemento com um anel suave ou um entorno escurecido (
box-shadow: 0 0 0 9999px rgba(0,0,0,.45)abre um buraco de holofote). - Legende um benefício por batida — frase nominal curta ("Analytics em tempo real"), não uma sentença. Entre no zoom, saia antes do próximo passo.
- Mantenha as legendas no terço inferior e dentro da área segura para título, para que sobrevivam a cortes para 9:16.
Consulte references/annotation-and-transitions.md para máscaras de holofote, conectores de seta/círculo e uma trilha de legendas digitadas.
Transições entre telas
Entre as etapas, mova as telas com uma linguagem consistente. Escolha uma e mantenha-a:
| Transição | Sensação | Uso |
|---|---|---|
| Empurrar / deslizar | Movimento "próximo passo" para frente | Walkthroughs lineares |
| Cross-zoom | Momentum, estilo app | Entrando em uma visualização de detalhes |
| Crossfade | Calmo, neutro | Mudando de contexto (configurações → painel) |
| Match-cut em um elemento compartilhado | Contínuo | Mesmo cartão persiste entre telas |
Segure a tela de saída até que seu clique seja resolvido, depois faça a transição — nunca corte no meio do movimento. Consulte references/annotation-and-transitions.md.
Lista de verificação de saída
- Entradas capturadas em 2× (DPR 2); zooms permanecem nítidos em 2×.
- Cada valor de cursor/zoom é uma função pura de
useCurrentFrame()— sem transições CSS para estado animado. - Cursor viaja em um caminho suavizado (smoothstep); ripple e mudança de estado disparam no mesmo quadro de clique.
- Cada tela emoldurada (navegador/dispositivo) com uma URL real e tema consistente.
- Um movimento por vez (câmera ou cursor); zoom ≤ 2×, segurado 2–4s.
- Uma legenda de benefício por batida, no terço inferior seguro para título; transições usam uma linguagem consistente.
Entregue e verifique (quadros estáticos renderizados → MP4)
Helper empacotado (
scripts/): organize seus quadros estáticos comscripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png, depois verifique a codificação comscripts/probe-mp4.sh out.mp4 [WxH] [fps]. Consultescripts/README.md.
Este é um nível pesado: a entrega é um MP4, não uma página ao vivo. A demonstração consome ativos do usuário — capturas de tela/designs, logotipo opcional — então a verificação é principalmente a imagem correta realmente carregou e pousou no quadro.
Contrato de saída:
- Um projeto Remotion com a composição registrada (
<Composition>+ esquema zod +defaultProps); cada valor de cursor/zoom/transição é uma função pura deuseCurrentFrame()(sem transições CSS, temporizadores,Date.now()). - Capturas de tela carregadas via
staticFile(), controladas comdelayRender/continueRenderpara que o PNG 2× (e qualquer fonte web) esteja presente antes da renderização do quadro — caso contrário, a tela surge em branco no meio da demonstração. - Entregável = o
out/*.mp4renderizado mais o projeto (re-renderize quando a captura de tela mudar).
Loop de verificação — renderize quadros estáticos → inspecione → codifique. PNGs baratos primeiro, vídeo apenas quando estiverem corretos.
# Quadros estáticos exatos COM AS PROPS QUE VOCÊ VAI ENVIAR (caminhos reais de captura de tela), não defaultProps
npx remotion still ProductDemo out/f-settle.png --frame=15 --props=demo.json # tela emoldurada, antes do movimento
npx remotion still ProductDemo out/f-click.png --frame=90 --props=demo.json # cursor no alvo + ripple
npx remotion still ProductDemo out/f-zoom.png --frame=150 --props=demo.json # ampliou para região focal + legenda
# quadro final = durationInFrames - 1 (npx remotion compositions lê isso)
Inspecione cada PNG quanto a fidelidade (captura de tela correta carregada; cursor no pixel exato; ripple de clique e mudança de estado no mesmo quadro; texto da legenda correto) E artefatos (imagem em branco/não carregada, tela fora da área de trabalho, zoom pixelizado ou ultrapassando o quadro, legenda fora do terço inferior seguro para título, proporção incorreta/letterboxing).
# Apenas depois que os quadros estáticos estiverem limpos:
npx remotion render ProductDemo out/demo.mp4 --props=demo.json
npx remotion render ProductDemo out/demo.gif --props=demo.json --codec=gif # Prova da primeira tela para o README
Lote (um modelo, muitos produtos): ao re-skinizar a demonstração por produto, verifique as props de UM produto representativo por meio de quadros estáticos antes de renderizar em lote o catálogo — pegue um bug de imagem em branco ou fora da área de trabalho uma vez, não N vezes.
Antes de finalizar:
- Quadros estáticos renderizam limpos no pouso / clique / fim — sem erros, capturas de tela realmente carregadas (não em branco).
- Cursor pousa no alvo exato; ripple + mudança de estado no mesmo quadro; texto da legenda correto e no terço inferior seguro para título.
- Todo movimento é orientado a quadro — sem transições CSS / temporizadores /
Date.now()/Math.random(). - As props enviadas (caminhos reais de captura de tela) renderizam corretamente, não apenas
defaultProps. - MP4 completo codificado e reproduzido; (opcional) GIF renderizado para o README.
Arquivos de referência
references/screenshot-demo.md— uma demonstração Remotion completa e executável: capturando capturas de tela em 2× com Playwright, os componentes de moldura de navegador/telefone, uma composição de várias etapas que percorre um cursor por várias telas com ripples de clique e um comando de renderização.references/annotation-and-transitions.md— máscaras de holofote, conectores de seta/círculo, uma trilha de legendas de funcionalidades digitadas e as quatro transições entre telas com implementações orientadas a quadro e notas de área segura para múltiplas proporções (16:9 / 9:16).