Communitygithub.com

pedrojaques99/site-reels

Grava as melhores cenas de um site ou aplicativo web (WebGL hero, 3D, scroll) como MP4 1080p para decks, faz upload para o Figma Slides e monta showreels de marca ou produto no Remotion.

O que é site-reels?

O `site-reels` é uma skill para agentes de IA (como Claude Code, Cursor, Codex) que transforma sites e aplicativos web em vídeos de alta qualidade (MP4 1080p, 30fps) prontos para apresentações, decks e showreels. Ela grava cenas programadas de um site rodando localmente, utilizando `puppeteer-core` com `puppeteer-screen-recorder` para capturar screencasts headful (nunca headless, para garantir WebGL suave) e ffmpeg para codificação. O resultado pode ser carregado automaticamente no Figma Slides (via MCP e `use_figma`) como slides ocultos com placeholders de vídeo, ou composto em showreels de marca/estúdio usando o Remotion. A skill suporta múltiplos cenários: gravação rápida de cenas (com scripts de cena customizáveis), captura determinística a 60fps para placas 3D/WebGL (usando `timesnap`), showreels de produto com gravação real de tela e cursor, e cartões de portfólio com loops. Ela funciona em Windows, macOS e Linux com GPU e área de trabalho visível, Node.js 18+, ffmpeg, e um navegador Chromium/Edge.

Funciona com~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/pedrojaques99/visant-skills-public/tree/HEAD/site-reels

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Site Reels: site → MP4 → deck

Requisitos

Necessário paraO quêVerificar
tudoWindows, macOS ou Linux com GPU e área de trabalho visível (Chrome roda com interface)n/a
tudoNode.js 18+node -v
tudoffmpeg + ffprobe no PATH (ou FFMPEG=<caminho completo>)ffmpeg -version
tudoChrome, Chromium ou Edge (detectado automaticamente, veja abaixo)node -e "import('./scripts/find-chrome.mjs').then(m=>console.log(m.findChrome()))"
record.mjspuppeteer-core + puppeteer-screen-recorder (npm, no projeto)npm ls puppeteer-core puppeteer-screen-recorder
snap.mjspuppeteer-core + timesnap (traz timeweb)npm ls timesnap
showreelProjeto Remotionnpx remotion --version
auditorias (opcional)ImageMagickmagick -version
Upload FigmaFigma MCP (use_figma, upload_assets) + skills figma-use, figma-use-slidesLista de ferramentas MCP

Detecção do Chrome (scripts/find-chrome.mjs, usado por record.mjs e snap.mjs): CHROME variável de ambiente, se definida; senão Chrome/Edge instalado (Windows: Program Files e LocalAppData; macOS: /Applications e ~/Applications; Linux: google-chrome, chromium, microsoft-edge no PATH); senão o mais novo ms-playwright/chromium-<N> (ou PLAYWRIGHT_BROWSERS_PATH); senão para e informa como definir CHROME ou rodar npx playwright install chromium. No Windows adiciona --use-angle=d3d11; em outros SOs o Chrome mantém seu backend GPU padrão.

O que faz

Grava cenas programadas de um site rodando localmente e entrega MP4s prontos para o Figma (1920×1080, 30fps, x264, < 100MB). Motor validado: puppeteer-core + puppeteer-screen-recorder (screencast CDP + ffmpeg). Nunca grava tela.

Executar

mkdir <proj>-reels && cd <proj>-reels && npm init -y && npm i puppeteer-core puppeteer-screen-recorder
mkdir -p scripts && cp <skill-dir>/scripts/{record,find-chrome}.mjs scripts/
cp <skill-dir>/scripts/scenes.example.mjs scenes.mjs   # edite as cenas
# no repositório do site: npx next build && npx next start -p 3100   (build de produção: WebGL mais suave)
OUT="<pasta de saída>/Videos" node scripts/record.mjs scenes.mjs [scene ...]

Execute os scripts copiados para o projeto: ESM resolve puppeteer-core a partir da pasta do script, não do diretório de trabalho.

Env: BASE (padrão http://localhost:3100), OUT, CHROME, FFMPEG, W, H, DPR, MOBILE=1 (emulação de toque), KEEP_UI=1 (mantém a navegação), MIN_FPS (24). Celular 4:5: W=360 H=450 DPR=2 MOBILE=1 resulta em 720×900.

Cada cena = { name, path, theme?, trim?, crf?, css?, prepare(page, ctx), act(page, ctx) }. prepare executa antes da gravação (rolar até a seção, aguardar carregamento do 3D); act é o que vai para o vídeo. ctx tem wait, scrollTo(page, y|selector, ms) e scrollThrough(page, selector|null, ms, from, to) com easing. Introdução que roda ao carregar: page.reload() dentro de act e use trim para cortar o carregamento.

Roteiro de marca que funciona: logo/hero intro (claro e escuro), troca de mídia hero, seção 3D com a câmera entrando no scroll, rota imersiva (walk), wordmark no rodapé e uma passada de página inteira. 5–7 cenas de 12–55s.

Auditar antes de entregar (não confie no código de saída)

  • Folha de contato por vídeo: ffmpeg -i x.mp4 -vf "fps=6/<dur>,scale=480:-1,tile=3x2" -frames:v 1 sheet.png e olhe.
  • 1MB para 19s = cena em branco. > 100MB = não entra no Figma (o crf da cena corrige; 24 para uma cena com granulação + trilhas).
  • Faixa superior (crop=1920:120:0:0) em vários momentos para capturar uma navegação vazando.
  • LOW-FPS com uma pausa longa no início/fim é um falso alarme (o medidor conta quadros parados); verifique a folha de contato.

Armadilhas conhecidas

  1. Janela oculta congela rAF → quadros congelados. O lançamento já passa --disable-backgrounding-occluded-windows --disable-renderer-backgrounding --disable-background-timer-throttling --disable-features=CalculateNativeWinOcclusion. headless: false propositalmente: headless cai no SwiftShader e o WebGL engasga.
  2. CSS de ocultar UI desaparece ao recarregar e quando a hidratação do React limpa <head> → o script reanexa um <style id="reels-hide"> ao body a cada 100ms.
  3. Um seletor inválido quebra toda a regra: \[ em um template literal JS vira [ e .z-[60] invalida header, nav, .z-[60]. Uma regra por seletor arriscado.
  4. Não oculte [data-cursor]: sites marcam o canvas 3D e carrosséis com ele. Mire no elemento de cursor personalizado (fixed + mix-blend + pointer-events-none).
  5. 3D carregado sob demanda (IntersectionObserver + import dinâmico): aguarde 5–7s após alcançar a seção. Ler o canvas WebGL retorna vazio sem preserveDrawingBuffer: use uma espera fixa ou uma captura de tela.
  6. Screencast entrega ~32 quadros únicos/s → grave a 30fps (60 dá quadros repetidos irregulares). O trim padrão de 0.3s descarta o primeiro quadro em cache.
  7. Screencast engasga em uma máquina carregada (Photoshop aberto, render paralelo): o MP4 diz "30fps" com 9–18 quadros únicos/s. record.mjs mede (mpdecimate=hi=64:lo=24:frac=0.05) e regrava até 2x abaixo de MIN_FPS (24). Para placas WebGL/3D prefira snap.mjs.
  8. Banner de cookies: aceite em prepare (clique genérico em "Aceitar/Accept/OK") antes de gravar.
  9. Uma página de ferramenta sem scroll (gerador, editor de efeitos) grava parada: a cena precisa interagir (fazer upload de um arquivo para input[type=file], clicar em predefinições pelo texto do botão). Um slider personalizado não responde a um input sintético.

Placas travadas a 60fps: snap.mjs (determinístico)

scripts/snap.mjs injeta timeweb (relógio virtual do timesnap: rAF, performance.now, setTimeout e vídeo), avança 1/60s por quadro e captura telas. Independente da carga da máquina. Cena: { name, path, theme, css, dur, wait (ms reais para carregar 3D), pre (ms virtuais antes de rolar, 2000; **0 para uma introdução que roda ao carregar**), start (s virtuais pulados), scroll: { sel, from, to, at, len } | { bottom: [from, to] em alturas de tela }, frame(page, t) (ex.: mouse) }. A rolagem é uma função do tempo virtual.

npm i puppeteer-core timesnap && mkdir -p scripts
cp <skill-dir>/scripts/{snap,find-chrome,patch-timeweb}.mjs scripts/
npm pkg set scripts.postinstall="node scripts/patch-timeweb.mjs" && npm run postinstall
FPS=60 OUT=... node scripts/snap.mjs scenes-snap.mjs [scene]
  • Não use timesnap() em si: com Next + R3F + Lenis ele gravou uma página em branco e deixou trechos congelados no hero WebGL (rAF não executa). O próprio loop (goTo + scrollTo + screenshot) funcionou em todos os lugares: 60/60 quadros únicos.
  • Patch do timeweb é obrigatório: ele substitui video.play() por uma versão sem Promise e sem evento playing → video.play().catch() lança erro e a página fica em branco.
  • preserveDrawingBuffer: true forçado no getContext (desligue com preserve: false).
  • Quadros vão para o diretório temporário do SO, nunca para uma unidade sincronizada (milhares de jpgs sincronizando).
  • Nenhum trabalho GPU em paralelo (render Remotion, Photoshop): o WebGL perde seu contexto e grava segundos apenas de fundo. O script avisa FLASH (quadro piscando por luminância); um longo trecho em branco só aparece na folha de contato. Olhe.
  • Auditoria de suavidade real: magick compare -metric MAE entre quadros vizinhos em resolução total. 0 = quadro repetido.
  • Entrega: scripts/deliver.example.sh (copie e renomeie as placas; V= obrigatório) constrói revelações, loops (loop.sh) e 3D limpo, aumentando crf em 3 até < 95MB.

Upload para o Figma Slides (oculto, sem perturbar a equipe)

O MP4 não entra via MCP: use_figma não tem rede e a Plugin API não cria vídeo a partir de bytes; upload_assets só aceita imagens (≤ 10MB). Então:

  1. Capa de cada vídeo: ffmpeg -ss <t> -i x.mp4 -frames:v 1 -q:v 2 _capas/x.jpg.
  2. use_figma (skills figma-use + figma-use-slides): createSlideRow(idx) antes do slide final, row.name = 'WIP · Motion (hidden)', um createSlide(idx, i) por vídeo com isSkippedSlide = true, fundo preto, um retângulo 1920×1080 chamado VIDEO · x.mp4 e a instrução em speakerNotes.
  3. upload_assets com os nodeIds dos retângulos → multipart POST das capas.
  4. O usuário arrasta o MP4 para o retângulo (substitui a capa pelo vídeo). Diga isso claramente; nunca prometa um vídeo inserido.

getSlideGrid() omite slides ocultos: verifique a estrutura via SLIDE_GRID.children[i].children.

Showreel de marca (Remotion): placas + cenas projetadas

Para um showreel de branding (logo, cores, tipografia, layouts, manifesto, mockups), grave apenas placas limpas do site (sem texto: CSS por cena oculta título/navegação) e componha o resto no Remotion (render quadro a quadro, sem tremulação). Blocos de construção reutilizáveis que valem a pena ter em src/fx.tsx: Mist, MistWords, Grain, KenBurns, Plate, uma transição nevoa() (névoa), com dados da marca em brand.ts.

  • A identidade visual vem do deck, não do site: verifique a tipografia oficial e a paleta nos slides (os sites se desviam do manual da marca).
  • Cenas que funcionam: frase de abertura na névoa, revelação gravada, construção do logo (evolvePath + guias medidas no raster do logo), manifesto frase por frase, pilares com eixo wght variável, paleta em faixas, tipografia, gráficos em camadas, 3D com título em mix-blend-difference, colunas de layout com scroll, montagem de mockups 1/2/3-up, produto sincronizado com o nome, encerramento no wordmark.
  • Revise por stills (npx remotion still ... --frame=N + montagem) antes de renderizar.
  • Renderize com --concurrency=3: em 8, com Photoshop aberto, uma máquina de 32GB ficou sem memória.
  • Loops: scripts/loop.sh in out [start] [dur] [crossfade]. Verifique a emenda com magick compare -metric RMSE entre o primeiro e o último quadro (< 0,5% = invisível).
  • Placa ruim é descarte: um sólido de revolução girando não mostra movimento; uma câmera enquadrada para um canvas retrato parece pequena em 16:9.
  • Reel frenético (ritmo aprovado): 25–35s, 60fps, Series com cortes secos em uma grade de 120 BPM (30 quadros), rajadas de imagem a cada 3–6 quadros, recorte enorme de logo sobre uma grade, pesos de fonte piscando, blocos de paleta se reorganizando, retrato em uma máscara circular, rajada gráfica, frase cinética, mockups de um tempo, logo final mantido ~2,5s. Zero rótulos: texto apenas como gráficos. Um showreel lento de 1:40 foi rejeitado.
  • Reel suave/esfumaçado: grave a fumaça hero sem logo ou foto, preto sobre branco puro (cena com storage = params em localStorage e block: ['assets/logo.png']; o PNG em branco deve ser transparente; não bloqueie o vídeo: um vídeo quebrado congela o timeweb). Use a placa como uma sobreposição mix-blend-mode: multiply: abre no início da cena, uma versão invertida (ffmpeg -vf reverse) a fecha; o corte cai no preto. Transição em ~1,1s (taxa 2), ou metade da cena vai para preto.
  • Montagem de layouts do Figma Slides: clone o slide (oculto), duplique cada cartão 3x com apenas fundo / logo / texto visível, baixe cada nó com download_assets (escala 2) e exclua os clones. Slides não exporta transparência: exporte logo/texto com o slide preto e depois branco e extraia o alfa por diferença (ImageMagick). Nunca retorne base64 via use_figma (estoura o contexto).

Showreel de produto (aplicativo real filmado)

Telas são filmadas de verdade (Playwright + CDP screencast a 2x, etapas goto/move/click/type/scroll/wait, trilha do cursor salva como <id>.cursor.json) e o cursor é redesenhado no Remotion por cima.

Rejeitado, não repita:

  • Colar um reel projetado na tela real (abertura ilustrada + meio filmado + CTA desenhado): duas linguagens, o personagem desaparece e retorna, o cartão preto entre os atos quebra o ritmo. Uma linguagem por vídeo.
  • Tela de produto pequena e acelerada em uma janela 16:9: feed e ranking viram uma parede ilegível.
  • Zoom que corta a tela e texto carimbado sobre o vídeo (verbo gigante, contador 01/07, gradiente): intrusivo.
  • Espera vazia na tela ("aguardando…"): corte direto para a ação.
  • Cópia vaga: tem que ser óbvia e na linguagem simples do público.

A forma que funciona: por ação, apenas texto primeiro, centralizado e curto (2 batidas), depois a tela inteira, sem cortes, em uma janela centralizada na proporção de captura, sobre um fundo limpo. Texto discreto (peso 700, entrada palavra por palavra com leve desfoque), sem contador, sem gradiente. Cortes na grade rítmica (14 quadros = 128,57 BPM). Máximo 40s.

Técnica que valeu a pena:

  • Acelerar uma captura: <OffthreadVideo trimBefore={from} playbackRate={rate}> e recalcular os quadros de clique / rate (som e círculo do cursor).
  • Iniciar outra composição no meio: <Sequence from={X}><Sequence from={-start}><Comp/></Sequence></Sequence>. Verifique o primeiro quadro do trecho: um elemento compartilhado "voando" aparece solto. Pule alguns quadros e reposicione o som de impacto que caiu antes.
  • Idioma por composição com uma timeline em nível de módulo: uma flag globalThis definida por seu próprio arquivo de entrada importado antes do Root.
  • Faixa contínua entre cortes: trimBefore na base para que continue de onde o trecho anterior parou.
  • Loudness: renderize, meça e normalize com 2-pass loudnorm (-14 LUFS / -1 dBTP), copiando o vídeo. Meça apenas no final: a mixagem bruta do Remotion ultrapassa 0 dBFS.
  • Auditoria: folha de contato com timestamp (fps=2,drawtext=%{pts\:hms},tile=8x6) de cada render, e uma folha densa (fps=6) em cada junção de trecho.
  • Um projeto sem git, editado por outra sessão ao mesmo tempo: novo corte em um novo arquivo, backup *.bak-* antes de editar um arquivo compartilhado, e avise o usuário.

Cartões de portfólio e reel de estúdio

  • record.mjs com KEEP_UI=1 (o cartão mostra o site como ele é) e o preset de celular acima.
  • Cenas geradas a partir de um arquivo de inventário (apenas itens públicos com URL ativa); sem URL ativa = Ken Burns da captura de tela.
  • Pacote do cartão: loop crossfade do final ao início, bitrate 2-pass para um limite KB, VP9 WebM junto, poster WebP do quadro 1.
  • Logo com alfa para a web: `-

…

[truncado para tradução]

Habilidades Relacionadas