애니메이션 OG 이미지
이 스킬이 사용하는 모든 파일은 이 SKILL.md 바로 옆에 있는 자체 폴더에 있으며, 여기서 사용하는 경로는 모두 해당 폴더를 기준으로 합니다. 다른 스킬이나 폴더에서 가져올 것이 없으니 찾지 마세요. 사용자가 다른 위치를 지정하지 않는 한, 생성된 모든 파일은 현재 프로젝트의 out/og-image-animated/ 아래에 저장합니다.
정적 공유 카드를 짧고 조용한 리빌(Reveal)로 애니메이션화합니다:
- 빈 카드: 배경과 부드러운 글로우(glow)만 표시.
- 브랜드 마크와 제목이 위에서 떨어짐 (페이드 + 슬라이드 다운).
- 앱 창이 아래에서 올라옴 (페이드 + 슬라이드 업) 정적 카드에서 정확히 있던 위치에 안착.
- 마지막 프레임에서 정지하며, 정적 OG 이미지와 일치해야 함.
예시: references/pulse.gif (결과물) 및 references/pulse-frames.png (네 프레임: 시작, 제목 들어옴, 앱 상승, 최종).
규칙
- 정적 카드에서 시작하세요. 프로젝트에 OG 카드 컴포넌트가 아직 없다면, 먼저
references/static-card.md를 따라 빌드하세요 (실제 제품, 현재 사이트의 메시지, 현실적인 데이터). 애니메이션은 움직임만 추가합니다: 동일한 마크업, 동일한 최종 레이아웃, 새로운 콘텐츠 없음. - 하나의 컴포넌트, 두 가지 모드. 컴포넌트를 복사하지 말고 기존 카드 컴포넌트에
animatedprop을 추가하세요. 그래야 정적 PNG와 애니메이션이 서로 떨어져 나가지 않습니다. prop이 없으면 아무것도 애니메이션되지 않습니다. - 움직임이지, 효과가 아닙니다.
opacity+transform: translateY만 사용하며, 하나의 공유 이즈 곡선(cubic-bezier(0.16, 1, 0.3, 1))을 사용합니다. 바운스, 블러, 확대 팝, 타자기 텍스트 또는 회전 요소는 없습니다. - 짧게. 전체 타임라인 약 2~2.5초, 이후 약 1.5초 정지. 제목은 약 1.3초까지 완전히 읽을 수 있어야 합니다.
- CSS 애니메이션만 사용 (키프레임 +
animation속성bothfill 사용), 카드 내부에서 JS 기반 트윈이나 타이머 사용 금지. 레코더가 Web Animations API를 통해 CSS 애니메이션을 탐색합니다. JS 기반 움직임은 결정적으로 캡처되지 않습니다.
타임라인
| 요소 | 키프레임 | 지속 시간 | 지연 시간 |
|---|---|---|---|
| 브랜드 마크 | opacity: 0; translateY(-48px)에서 시작 | 900ms | 100ms |
| 제목 | opacity: 0; translateY(-48px)에서 시작 | 1000ms | 250ms |
| 앱 창 | opacity: 0; translateY(320px)에서 시작 | 1300ms | 700ms |
카드에 맞게 거리를 조정하세요: 앱은 대부분 아래쪽 가장자리 아래(카드 높이의 절반 정도)에서 시작하고, 제목은 정지 위치 위로 자신의 높이만큼 떠 있어야 합니다. 떠 있는 패널이 있다면 앱 시작보다 200ms 늦게 도착하게 하세요.
단계
1. 발견
- 정적 카드 컴포넌트와 렌더 라우트(예:
og-image.tsx,/og/<page>) 및public/의 현재 PNG를 찾으세요. - 브랜드 마크, 제목 및 앱 창 래퍼를 확인하세요. 창이
zoom을 사용하여 맞춰지면, 줌된 요소가 아닌 내부 래퍼를 애니메이션 처리하세요(오프셋이 올바르게 유지되도록).
2. 애니메이션 모드 추가
카드 컴포넌트에서:
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>} …
// 브랜드: style={animated ? enter('og-drop', 900, 100) : undefined}
// 제목: style={animated ? enter('og-drop', 1000, 250) : undefined}
// 창: <div style={{ zoom }}><div className="h-full" style={animated ? enter('og-rise', 1300, 700) : undefined}>…
키프레임은 컴포넌트(<style> 태그) 또는 프로젝트의 전역 CSS에 넣으세요. Tailwind v4의 경우 @theme 내부의 키프레임은 일치하는 --animate-* 토큰이 사용될 때만 방출되므로, 컴포넌트 또는 일반 CSS를 사용하세요.
3. 재생 웹 라우트 추가
정적 라우트의 형제 경로로, 예: /og/<page>-animated (noindex, 앱 크롬 없음):
- 1200×630 프레임 내부에
<Card animated />렌더링. - 미리보기용으로 약 4.5초마다
key를 변경하여 카드를 다시 마운트하여 재생. ?loop=0이면 한 번 재생(레코더가 사용). 검색 파라미터를 먼저 문자열로 정규화하세요. TanStack과 같은 라우터가?loop=0을 숫자0으로 파싱할 수 있으며, 체크를 놓치면 녹화 중에도 카드가 계속 리마운트됩니다.
브라우저에서 열어 여러 번 시청하세요. 진입 순서, 애니메이션 시작 전 깜빡임이 없는지, 최종 상태가 정적 카드와 일치하는지 확인하세요.
4. MP4 + GIF 녹화
개발 서버가 실행 중일 때:
node <이 스킬 폴더>/scripts/record.mjs \
--url "http://localhost:<port>/og/<page>-animated?loop=0" \
--out out/og-image-animated/og-<page>
og-<page>.mp4 (2400×1260, H.264, 30fps), og-<page>.gif (1200px 너비, 팔레트 최적화, 반복) 및 og-<page>.png (마지막 프레임)를 생성합니다. 작동 방식: 페이지의 모든 애니메이션을 일시 중지하고, 타임라인 길이를 읽은 다음, 모든 애니메이션을 프레임별로 탐색하고 고정된 [data-og-frame] 클립의 스크린샷을 찍습니다. 결과는 매 실행마다 동일하며, 드롭된 프레임이 없습니다. 옵션: --fps, --hold, --lead, --scale, --formats mp4,gif,png, --selector.
ffmpeg가 PATH에 있어야 합니다. Playwright는 이 스킬의 scripts/ 폴더에 첫 실행 시 설치되며, Chromium이 없으면 함께 설치됩니다.
주의사항:
- 페이지가 reduced motion을 강제해서는 안 됩니다. 많은 글로벌 스타일시트가
prefers-reduced-motion에서 애니메이션을 단축합니다. 레코더는 이를 에뮬레이트하지 않으며, 애니메이션을 찾지 못하면 큰 오류를 발생시킵니다. - 요소 스크린샷은 "안정성"을 기다리는데, 이는 애니메이션 중간에는 절대 오지 않습니다. 그래서 스크립트가 페이지 영역을 클리핑하는 것입니다.
- 포트가 실제로 이 앱을 제공하고 있는지 확인하세요 (다른 로컬 서버가
localhost에서 응답할 수 있습니다).
5. 검토
콘택트 시트(contact sheet)를 추출하여 확인하세요:
ffmpeg -y -loglevel error -i og-<page>.mp4 \
-vf "select='eq(n\,8)+eq(n\,22)+eq(n\,40)+eq(n\,<last>)',scale=1000:-1,tile=2x2" -frames:v 1 sheet.png
- 프레임 ~0.25초: 빈 카드 (글로우만), 레이아웃 팝 없음.
- ~0.7초: 제목이 들어오거나 거의 들어옴, 앱 아직 보이지 않음.
- ~1.3초: 앱이 올라오는 중, 제목 완전히 읽을 수 있음.
- 마지막 프레임: 정적 PNG와 내용이 동일.
GIF 크기는 적절히 작게 유지하세요 (약 3MB 미만). 더 크다면 --fps를 낮추거나(20–24) --hold를 줄이세요.
출력
사용자에게 다음을 알리세요:
- 미리보기 라우트 URL (반복 재생) 및 생성된 파일 (MP4, GIF, PNG)
- 재생성 명령어
- 타임라인 (무엇이 움직이는지, 지속 시간)
og:image는 정적 PNG로 유지해야 함: 소셜 플랫폼은 애니메이션 OG 이미지를 재생하지 않습니다. MP4/GIF는 게시물, README, 런칭 페이지 또는og:video(지원되는 경우)에 사용하세요.