Animação do cartão Open Graph (OG Image)
Todo arquivo usado por esta skill reside em sua própria pasta, ao lado deste SKILL.md, e os caminhos aqui são relativos a essa pasta. Ela não precisa de nada de outras skills ou pastas, portanto não procure por elas. A menos que o usuário especifique outro local, salve tudo o que ela produzir em out/og-image-animated/ dentro do projeto atual.
O cartão de compartilhamento estático animado como uma revelação curta e calma:
- Cartão vazio: apenas fundo e brilho suave.
- Marca e título descem do topo (fade + deslizar para baixo).
- A janela do aplicativo sobe do fundo (fade + deslizar para cima) e se estabiliza exatamente onde estava no cartão estático.
- Pausa no quadro final, que deve corresponder exatamente à imagem OG estática.
Exemplos: references/pulse.gif (o resultado) e references/pulse-frames.png (quatro quadros: início, título entrando, aplicativo subindo, final).
Regras
- Comece pelo cartão estático. Se o projeto ainda não tiver um componente de cartão OG, construa-o primeiro seguindo
references/static-card.md(produto real, mensagens do site atual, dados realistas). A animação adiciona apenas movimento: mesma marcação, mesmo layout final, nenhum conteúdo novo. - Um componente, dois modos. Adicione uma prop
animatedao componente de cartão existente em vez de copiá-lo, para que o PNG estático e a animação nunca se desalinhem. Sem a prop, nada anima. - Movimento, não efeitos. Apenas
opacity+transform: translateY, com uma única curva de easing compartilhada (cubic-bezier(0.16, 1, 0.3, 1)). Sem bounce, desfoque, escala, texto digitado ou elementos girando. - Curto. Toda a linha do tempo dura cerca de 2–2,5s, depois uma pausa de ~1,5s. O título deve estar totalmente legível por volta de ~1,3s.
- Apenas animações CSS (keyframes +
animationcom preenchimentoboth), sem tweens ou temporizadores acionados por JavaScript dentro do cartão. O gravador busca animações CSS através da Web Animations API; movimentos acionados por JS não seriam capturados deterministicamente.
Linha do tempo
| Elemento | Keyframes | Duração | Atraso |
|---|---|---|---|
| Marca | de opacity: 0; translateY(-48px) | 900ms | 100ms |
| Título | de opacity: 0; translateY(-48px) | 1000ms | 250ms |
| Janela do app | de opacity: 0; translateY(320px) | 1300ms | 700ms |
Ajuste as distâncias para o cartão: o aplicativo deve começar majoritariamente abaixo da borda inferior (cerca de metade da altura do cartão) e o título cerca de sua própria altura acima de sua posição final. Se houver um painel flutuante, deixe-o chegar por último (+200ms após o início da janela).
Etapas
1. Descobrir
- Encontre o componente de cartão estático e sua rota de renderização (ex.:
og-image.tsx,/og/<page>), e o PNG atual empublic/. - Anote os wrappers da marca, título e janela do aplicativo. Se a janela usar
zoompara ajustar, anime um wrapper interno, não o elemento com zoom, para que os deslocamentos permaneçam corretos.
2. Adicionar o modo animado
No componente do cartão:
const KEYFRAMES = `
@keyframes og-drop { from { opacity: 0; transform: translateY(-48px); } to { opacity: 1; transform: none; } }
@keyframes og-rise { from { opacity: 0; transform: translateY(320px); } to { opacity: 1; transform: none; } }
`;
const EASE = 'cubic-bezier(0.16, 1, 0.3, 1)';
const enter = (name: string, ms: number, delay: number) => ({ animation: `${name} ${ms}ms ${EASE} ${delay}ms both` });
// <div data-og-frame …>{animated && <style>{KEYFRAMES}</style>} …
// marca: style={animated ? enter('og-drop', 900, 100) : undefined}
// titulo: style={animated ? enter('og-drop', 1000, 250) : undefined}
// janela: <div style={{ zoom }}><div className="h-full" style={animated ? enter('og-rise', 1300, 700) : undefined}>…
Coloque os keyframes no componente (uma tag <style>) ou no CSS global do projeto; com Tailwind v4, keyframes dentro de @theme só são emitidos quando um token --animate-* correspondente é usado, então prefira o componente ou CSS puro.
3. Adicionar uma rota web que reproduza a animação
Uma rota irmã da estática, ex.: /og/<page>-animated (noindex, sem chrome do aplicativo):
- Renderiza
<Card animated />dentro do frame 1200×630. - Reproduz para visualização: desmonta e remonta o cartão com uma
keydiferente a cada ~4,5s. ?loop=0reproduz uma vez (o gravador usa isso). Normalize o parâmetro de busca para string primeiro: roteadores como TanStack interpretam?loop=0como o número0, e uma verificação incorreta continuaria remontando o cartão durante a gravação.
Abra no navegador e assista algumas vezes. Verifique a ordem de entrada, se nada pisca antes do início da animação e se o estado final corresponde ao cartão estático.
4. Gravar MP4 + GIF
Com o servidor de desenvolvimento em execução:
node <pasta desta skill>/scripts/record.mjs \
--url "http://localhost:<porta>/og/<page>-animated?loop=0" \
--out out/og-image-animated/og-<page>
Gera og-<page>.mp4 (2400×1260, H.264, 30fps), og-<page>.gif (1200px de largura, paleta otimizada, looping) e og-<page>.png (quadro final). Como funciona: pausa cada animação na página, lê a duração da linha do tempo, então avança todas as animações quadro a quadro e captura uma região fixa de [data-og-frame]. O resultado é idêntico a cada execução, sem quadros perdidos. Opções: --fps, --hold, --lead, --scale, --formats mp4,gif,png, --selector.
Requer ffmpeg no PATH. O Playwright é instalado na pasta scripts/ desta skill na primeira execução, com seu Chromium se nenhum for encontrado.
Armadilhas:
- A página não deve forçar movimento reduzido. Muitos estilos globais encurtam animações em
prefers-reduced-motion; o gravador não emula isso e falha alto se não encontrar animações. - Capturas de tela de elementos esperam por "estabilidade", que nunca chega durante uma animação. Por isso o script recorta uma região da página.
- Certifique-se de que a porta está realmente servindo este aplicativo (outro servidor local pode responder em
localhost).
5. Revisar
Extraia uma folha de contato e examine:
ffmpeg -y -loglevel error -i og-<page>.mp4 \
-vf "select='eq(n\,8)+eq(n\,22)+eq(n\,40)+eq(n\,<último>)',scale=1000:-1,tile=2x2" -frames:v 1 sheet.png
- Quadro ~0,25s: cartão vazio (apenas brilho), sem salto de layout.
- ~0,7s: título entrando ou quase entrou, aplicativo ainda não visível.
- ~1,3s: aplicativo no meio da subida, título totalmente legível.
- Último quadro: conteúdo idêntico ao PNG estático.
Mantenha o GIF razoavelmente pequeno (abaixo de ~3MB). Se for maior, reduza --fps (20–24) ou encurte --hold.
Saída
Informe ao usuário:
- a URL da rota de visualização (com loop) e os arquivos gerados (MP4, GIF, PNG)
- o comando para regenerar
- a linha do tempo (o que se move, durações)
- que
og:imagedeve permanecer o PNG estático: plataformas sociais não reproduzem imagens OG animadas. Use o MP4/GIF para posts, README, páginas de lançamento ouog:videoonde houver suporte.