Landing fly-over
랜딩 페이지의 고정된 아트 디렉션 영화입니다. 모든 프레임은 시간의 순수 함수(seek(t) in WebGL2)이며, Playwright로 캡처하고, 실제 GPU에서 16-서브프레임 모션 블러로 렌더링하며, ffmpeg로 인코딩합니다. 비디오 편집기, 타임라인, 컷이 없습니다.
사용자 머신에서 코드를 실행해야 합니다. 실행할 수 없는 경우(채팅 전용 환경), 바로 알리세요: 영화를 설명할 수는 있지만, MP4 파일을 만들기 위해서는 셸이 있는 코딩 에이전트(Claude Code, Codex, Cursor…)가 필요합니다.
0. 입력 요청
한 번에 하나의 메시지로 요청하세요:
- 랜딩 페이지 링크.
- 브랜드 이름과 로고 마크(정사각형 PNG/SVG 경로; 먼저 레포지토리에서 찾아보세요:
logo*,favicon*,brand*). - 두 부분으로 된 클로징 라인: 짧은 문장, 그 다음 짧은 이탤릭체 꼬리. 첫 번째 줄은
22자 이내로 유지하세요(1440px 프레임에서 74px로 설정됨). 페이지의 원문에서 23개의 옵션을 제공하세요. - 15초 트랙(선택 사항). 없으면 로열티 프리 음악을 찾아 제공하세요(Mixkit의 무료 라이선스는 출처 표시 없이 상업적 사용을 허용함;
https://assets.mixkit.co/music/<id>/<id>.mp3에서 다운로드). 다운로드한 트랙을 레포지토리에 커밋하지 마세요; 렌더링된 MP4만 커밋하세요.
1. 설정
cp -R <this-skill-dir>/engine ./flyover && cd flyover
npm install && npx playwright install chromium # 설치된 Chrome이 있을 경우 자동으로 선호됨
cp config.example.json config.json
Node 18+, PATH에 ffmpeg, 점수 계산을 위한 Python 3와 numpy가 필요합니다(흐름 검사를 위해 opencv-python-headless 필요). 로고를 config.json 옆에 두고 url, brand, logo, title을 설정하세요.
2. 검사 후, 떠오를 요소 선택
node inspect.mjs # → dom.txt (페이지 위치와 함께 섹션 트리) + inspect.png
dom.txt를 읽고 config.json을 채우세요:
hero.section: 히어로 섹션.hero.mockup: 히어로의 핸드폰/제품/비디오 프레임(영화가 이 부분에 초점을 맞추며 시작하고, 엔드 카드는 이 바로 아래를 자릅니다).hero.tileAnchor: 히어로의 마지막 버튼(네 개의 로고 타일이 그 옆에서 시작됩니다).hero.footerTitle: 푸터 바로 전의 마지막 큰 제목(8.0초에 선명해집니다).layers: 떠오르는 조각들,{ "sel", "role" }형식, 선택적으로z(기본 리프트),zMax(카메라가 지나갈 때 리프트),stand(렌즈 쪽으로 서는 각도)를 재정의할 수 있습니다. 역할과 기본값:
| 역할 | 용도 | z → zMax · stand |
|---|---|---|
button | 히어로 / CTA 버튼 | 6 → 28 · 0° |
chip | 배지, 알약, 탭, 작은 로고 | 4 → 64 · 30° |
mockup | 히어로 핸드폰 / 제품 / 비디오 프레임 | 10 → 24 · 0° |
card | 기능 카드, 표, 터미널, 메트릭 셀 | 6 → 30 · 0° |
photo | 카드 내부 이미지, 스크린샷, 코드 샘플 | 8 → 115 · 46° |
flychip | 프레임 가장자리를 스쳐 지나가는 1-2개의 작은 칩 | 4 → 200 · 54° |
testimonial | 하나의 인용문/터미널 카드; 34px 흰색 가장자리 표시 | 22 → 112 · 30° |
대부분의 리프트는 작게 유지하세요(20–40px). 전체 페이지에 걸쳐 15–35개 조각, 섹션당 몇 개씩. 중첩된 조각도 괜찮습니다: 내부 조각(photo)을 카드 앞에 나열하세요.
waitFor: API에서 제공하는 실시간 숫자(방문자 카운터, 토큰 미터)의 선택자. 캡처는 페이지를 고정하기 전에 각각 0이 아닌 숫자가 표시될 때까지 기다립니다; 그렇지 않으면 "0"으로 촬영될 수 있습니다.hide: 쿠키 배너, 채팅 위젯, 팝업.css: 스크롤 기반 효과의 최종 상태를 강제하는 추가 CSS(예: 스크롤 시 밝아지는 단어).routes:[{ "match": "**/broken.jpg*", "file": "/abs/local/copy.jpg" }]라이브 사이트가 제공에 실패하는 에셋용.
3. 페이지 캡처 및 고정
node capture.mjs
이것은 한 번 아래로 스크롤하여(지연 로딩 이미지, 카운터, 리빌), 애니메이션을 완료하고, 비디오를 한 프레임(또는 포스터)에 고정시키고, 스크롤 고정 "런웨이" 섹션을 축소(고정 + 200vh 스크롤 스페이서는 그렇지 않으면 빈 공간으로 촬영됨)하고, 고정된 바를 해제한 다음, 기본 플레이트를 4096px 청크로 저장하고(Chrome은 한 번에 16384 장치 픽셀까지만 캡처: 더 큰 캡처는 페이지 상단을 조용히 반복함), 투명 배경에 28px 패딩이 있는 각 리프트 조각을 개별적으로 저장하며, 모든 위치가 있는 capture/layers.json을 저장합니다.
타이밍을 정하기 전에 캡처를 확인하세요: capture/page_*.png와 몇 개의 layer_*.png를 엽니다. 빈 비디오 프레임, 반쯤 드러난 텍스트, 중복된 영역, 팝업이 있는지 확인하세요. css/hide/routes로 수정하고 다시 캡처하세요. 타이밍 전에 페이지를 고정하세요: 섹션 높이가 변경되면 이후 모든 것이 이동합니다.
4. 승인용 스틸
node render.mjs stills 0,1.6,2.5,5,6.5,9.3,11 # → stills/contact_sheet.png
사용자에게 콘택트 시트를 보여주고 최종 렌더링 전에 승인을 기다리세요. 각 스틸이 보여줘야 하는 것:
- 0초: 히어로 위를 낮게, 목업 선명, 버튼 옆에 32px 로고 타일 네 개.
- 1.6초: 히어로가 뒤로 넘어가고, 두 번째 섹션의 제목이 중앙을 가로지르며, 가까운 콘텐츠가 녹아내림.
- 2.5초: 해당 섹션의 사진이 세워져 렌즈 근처에서 흐려짐; 플라이칩이 가장자리를 떠남.
- 5초 / 6.5초: 안정적인 글라이드; 한 섹션이 중앙에서 선명하고, 아래는 부드럽게; 증언 카드가 흰색 가장자리와 함께 올라옴.
- 9.3초: 바로 위에서, 전체 페이지가 하나의 중앙 스트립으로.
- 11초: 엔드 카드: 배지 알약, 1번 라인은 Geist 500, 회색 Instrument Serif 이탤릭 꼬리, 히어로가 1018px 카드로, 모서리에 로고 타일, 아래쪽에 두 개의 부드러운 블러.
5. 모션 확인
node render.mjs video preview30.mp4 30 1 && node render.mjs cam
python3 flow.py preview30.mp4 # PASS = 글라이드가 멈추거나 역전되지 않고, 비행이 확대되지 않음
6. 점수 및 렌더링
python3 score.py track.mp3 auto # 드롭을 찾아 9.97초에 배치; 8.66초와 9.80초에 워시 → audio/score.wav
node render.mjs video flyover.mp4 60 16 # → flyover.mp4, audio/score.wav가 있으면 flyover-audio.mp4도 생성됨
Apple M 시리즈 GPU에서 프레임당 약 0.1–0.2초(총 870 프레임). 하네스는 macOS에서 ANGLE Metal, Windows에서 D3D11, Linux에서 Vulkan을 선택합니다; SwiftShader/llvmpipe가 보고되면 소프트웨어 래스터라이저에서 실행 중이며 느릴 것입니다.
영화 (고정: 페이지별로 다시 타이밍하지 마세요)
- 14.5초, 60fps, 1440x1152. 비행 0–8.2초 · 상승 7.75–9.25초 · 상공 9.25–9.47초 · 다이브 9.47–9.97초 · 타이틀 카드 9.97–14.5초.
- 하나의 카메라, 컷 없음. 초점 거리 1880px, 56° 기울기, 거리 이징 아웃 1360→1700px, 회전하지 않음. 페이지 아래로의 속도는 하나의 통합 곡선(순항 비율): 0 → 0.6초까지 0.5, 1.25초까지 유지, 1.6초까지 1.0, 7.3초까지 유지, 8.3초까지 0.12로 이징. 순항은 푸터 제목이 8.0초에 선명하도록 조정되므로 긴 페이지는 더 빠르게 글라이드합니다.
- 픽셀당 피사계 심도: 전경 블러 = 75·(초점−깊이)/깊이, 배경 블러 = 6·(깊이−초점)/깊이, 최대 120px; 밉 레벨에서 40 황금각 탭, 모션 블러 서브프레임마다 회전; 근거리 블러가 팽창되어 초점이 맞지 않은 전경이 선명한 배경 위로 흘러나옴.
- 리프트된 조각은 자신의 중앙을 기준으로 세워지고(상단 가장자리가 렌즈 쪽으로) 그림자를 드리움: 동일한 그림자 평면 #1a2133, 오프셋 (0.10z, 4+0.36z), 블러 7+0.5z, 26%.
- 상승: 사다리꼴 속도에 로그 스케일 줌, 8.15–8.95초에 기울기 평탄화, 프레임 하단 가장자리를 상단에서 42px 위에 고정하는 카메라로 전환. 다이브: 상단 가장자리가 고정된 로그 스케일의 2차 이징 인-아웃, 그런 다음 y 283으로 드롭; 둥근 직사각형 마스크가 24px → 38px 모서리로 변환. 히어로 아래 콘텐츠는 먼저 헤이즈 색상으로 페이드 아웃된 다음 투명하게 자름.
- 타이틀: 배지 알약 10.02초에 페이드 인 (y 44); 1번 라인 10.12초부터 단어당 0.06초; 꼬리 10.36초부터 단어당 0.05초; 각 단어는 12px 블러에서 16px 위로 0.34초에 걸쳐 상승. 2% 크립으로 유지.
금지: 컷, 크로스페이드, 글라이드의 정지 또는 급등, 카메라 회전 또는 측면 흔들림, 비행 중 줌 인, 광선 효과, 엔드 카드 전에 페이지 위에 텍스트.
주의사항
- 거대한 페이지: 기본 플레이트는 청크로 분할되며, 하나의 스크린샷이 아닙니다. 히어로가 중간쯤에 반복되면 캡처가 16384 장치 픽셀을 초과한 것입니다.
- 스크롤 기반 사이트: 고정 런웨이는 자동으로 축소됩니다; 단어별 스크롤 하이라이트는 전체 불투명도를 강제하는
css규칙이 필요합니다. - 비디오:
videoTime초에 고정됩니다. 라이브 사이트의 비디오가 404면 검은 카드가 표시됩니다;routes를 통해 로컬 포스터를 제공하세요. - 페이딩 카드는 뒤에 있는 것을 보여주므로 먼저 헤이즈 색상으로 페이드 아웃하세요(엔진이 히어로 아래의 모든 것에 대해 이 작업을 수행).
- 1번 라인이 너무 긺: 단어가 74px에서 프레임 밖으로 나갑니다. 글자 크기를 줄이지 말고 라인을 짧게 하세요.
- 라이선싱: 음악은 사용자의 책임입니다; 상업용 비디오를 포함하는 라이선스의 트랙을 선호하세요.