Landing Fly-Over
Ein fest inszenierter, künstlerisch gestalteter Film einer Landingpage. Jeder Frame ist eine reine Funktion der Zeit (seek(t) in WebGL2), wird mit Playwright erfasst, auf der echten GPU mit 16-Unterframe-Bewegungsunschärfe gerendert und mit ffmpeg kodiert. Kein Videobearbeitungsprogramm, keine Timeline, keine Schnitte.
Du musst Code auf dem Rechner des Benutzers ausführen. Falls du das nicht kannst (reine Chat-Oberfläche), sag es sofort: Du kannst den Film zwar beschreiben, aber die MP4 benötigt einen Coding-Agenten mit einer Shell (Claude Code, Codex, Cursor …).
0. Die Eingaben abfragen
Einmal in einer Nachricht erfragen:
- Link zur Landingpage.
- Markenname und Logomarke (ein quadratisches PNG/SVG; zuerst im Repo suchen:
logo*,favicon*,brand*). - Eine zweiteilige Schlusszeile: eine kurze Aussage, dann ein kurzer kursiver Nachsatz. Zeile 1 unter ~22 Zeichen halten (wird in 74pt über einen 1440px breiten Frame gesetzt). 2–3 Optionen aus dem Text der Seite vorschlagen.
- Einen 15-Sekunden-Track (optional). Falls der Benutzer keinen hat, einen lizenzfreien von Mixkit anbieten (Mixe freie Lizenz erlaubt kommerzielle Nutzung ohne Namensnennung; herunterladen von
https://assets.mixkit.co/music/<id>/<id>.mp3). Nie einen heruntergeladenen Track in ein Repository committen; nur die gerenderte MP4.
1. Einrichtung
cp -R <skill-verzeichnis>/engine ./flyover && cd flyover
npm install && npx playwright install chromium # installiertes Chrome wird automatisch bevorzugt, falls vorhanden
cp config.example.json config.json
Benötigt Node 18+, ffmpeg im PATH und Python 3 mit numpy für die Partitur (opencv-python-headless für die Bewegungsprüfung). Lege das Logo neben config.json und setze url, brand, logo, title.
2. Inspizieren, dann auswählen, was sich hebt
node inspect.mjs # → dom.txt (Abschnittsbaum mit Seitepositionen) + inspect.png
Lies dom.txt und fülle config.json aus:
hero.section: der Hero-Abschnitt.hero.mockup: das Telefon/Produkt/Video-Frame im Hero (der Film startet fokussiert darauf und die Endkarte beschneidet direkt darunter).hero.tileAnchor: der letzte Button im Hero (die vier Logokacheln beginnen daneben).hero.footerTitle: die letzte große Überschrift vor dem Footer (ist bei 8,0s scharf).layers: Stücke, die sich heben, als{ "sel", "role" }, optional mit Überschreibung vonz(Hebehöhe in Ruhe),zMax(Hebehöhe, wenn die Kamera vorbeifährt) undstand(Grad, um die sie sich zur Linse aufrichten). Rollen und Standardwerte:
| Rolle | Verwendung | z → zMax · Stand |
|---|---|---|
button | Hero-/CTA-Buttons | 6 → 28 · 0° |
chip | Badges, Pillen, Tabs, kleine Logos | 4 → 64 · 30° |
mockup | Das Hero-Telefon/Produkt/Video-Frame | 10 → 24 · 0° |
card | Feature-Karten, Tabellen, Terminals, Metrik-Zellen | 6 → 30 · 0° |
photo | Bilder in Karten, Screenshots, Code-Beispiele | 8 → 115 · 46° |
flychip | 1–2 kleine Chips, die an den Bildrändern vorbeifliegen | 4 → 200 · 54° |
testimonial | Ein Zitat-/Terminal-Karte; zeigt einen 34px weißen Rand | 22 → 112 · 30° |
Halte die meisten Hubhöhen klein (20–40px). Ziel: 15–35 Stücke über die gesamte Seite, ein paar pro Abschnitt. Verschachtelte Stücke sind in Ordnung: das innere (photo) vor seiner Karte auflisten.
waitFor: Selektoren von Live-Zahlen, die von einer API gespeist werden (Besucherzähler, Token-Meter). Der Capture wartet, bis jede eine Ziffer ungleich Null anzeigt, bevor die Seite eingefroren wird; sonst könnten sie als „0“ gefilmt werden.hide: Cookie-Banner, Chat-Widgets, Popups.css: zusätzliches CSS, um den Endzustand von scrollgesteuerten Effekten zu erzwingen (z. B. Wörter, die beim Scrollen aufleuchten).routes:[{ "match": "**/broken.jpg*", "file": "/abs/local/copy.jpg" }]für Assets, die die Live-Seite nicht ausliefern kann.
3. Seite erfassen und einfrieren
node capture.mjs
Dies scrollt einmal nach unten (Lazy-Images, Zähler, Revalings), beendet Animationen, friert Videos auf einem Frame ein (oder deren Poster), faltet scroll-pinierte „Runway“-Abschnitte zusammen (Sticky + 200vh Scroll-Abstandshalter würden sonst als leere Streifen gefilmt), entfernt fixierte Leisten, speichert dann die Basisplatte in 4096px großen Stücken (Chrome begrenzt einen Capture auf 16384 Geräte-Pixel: größere Captures wiederholen stillschweigend den oberen Teil der Seite) plus jedes gehobene Stück einzeln auf Transparenz mit 28px Rand, sowie capture/layers.json mit allen Positionen.
Sieh dir den Capture an, bevor du irgendetwas timest: öffne capture/page_*.png und ein paar layer_*.png. Prüfe auf leere Videoframes, halb aufgedeckten Text, duplizierte Regionen, Popups. Behebe mit css/hide/routes und nimm den Capture erneut vor. Seite vor dem Timing einfrieren: jede Änderung der Höhe eines Abschnitts verschiebt alles dahinter.
4. Standbilder zur Freigabe
node render.mjs stills 0,1.6,2.5,5,6.5,9.3,11 # → stills/contact_sheet.png
Zeig dem Benutzer das Kontaktblatt und warte auf ein Ja, bevor der finale Render erfolgt. Was jedes Standbild zeigen sollte:
- 0s: Tief über dem Hero, Mockup scharf, vier 32px Logokacheln neben dem Button.
- 1.6s: Hero fällt zurück, die Überschrift des zweiten Abschnitts kreuzt die Mitte, nahe Inhalte verschwimmen.
- 2.5s: Die Fotos dieses Abschnitts stehen aufgerichtet und unscharf nahe der Linse; ein Flychip verlässt einen Rand.
- 5s / 6.5s: Stetiger Gleitflug; ein Abschnitt scharf in der Mitte, weich darunter; das Testimonial angehoben mit seinem weißen Rand.
- 9.3s: Senkrecht von oben, die gesamte Seite als ein zentrierter Streifen.
- 11s: Die Endkarte: Badge-Pille, Zeile 1 in Geist 500, grauer kursiver Instrument Serif Nachsatz, der Hero als 1018px Karte, Logokacheln an den Ecken, zwei weiche Unschärfen unten.
5. Bewegung prüfen
node render.mjs video preview30.mp4 30 1 && node render.mjs cam
python3 flow.py preview30.mp4 # PASS = der Gleitflug stockt oder kehrt nie um, der Flug zoomt nie hinein
6. Partitur und Render
python3 score.py track.mp3 auto # findet den Drop, landet ihn bei 9,97s; Whooshes bei 8,66s und 9,80s → audio/score.wav
node render.mjs video flyover.mp4 60 16 # → flyover.mp4, plus flyover-audio.mp4 falls audio/score.wav existiert
Etwa 0,1–0,2s pro Frame auf einem Apple M-Serie GPU (870 Frames). Das Harness wählt ANGLE Metal auf macOS, D3D11 auf Windows, Vulkan auf Linux; falls es SwiftShader/llvmpipe meldet, läuft ein Software-Rasterizer und es wird langsam.
Der Film (festgelegt: nicht pro Seite neu timen)
- 14,5s bei 60fps, 1440×1152. Flug 0–8,2s · Aufstieg 7,75–9,25s · Überkopf 9,25–9,47s · Tauchgang 9,47–9,97s · Titelcard 9,97–14,5s.
- Eine Kamera, nie ein Schnitt. Brennweite 1880px, 56° Neigung, Abstand mit Ease-Out 1360→1700px, niemals drehend. Geschwindigkeit entlang der Seite ist eine integrierte Kurve (Anteil an Reisegeschwindigkeit): 0 → 0,5 bis 0,6s, gehalten bis 1,25s, 1,0 bis 1,6s, gehalten bis 7,3s, Ease-Out auf 0,12 bis 8,3s. Die Reisegeschwindigkeit wird so gelöst, dass die Footer-Überschrift bei 8,0s scharf ist; lange Seiten gleiten daher schneller.
- Tiefenschärfe pro Pixel: Unschärfe = 75·(Fokus−Tiefe)/Tiefe vorne, 6·(Tiefe−Fokus)/Tiefe hinten, gedeckelt bei 120px; 40 Golden-Angle-Taps aus Mip-Ebenen, rotiert pro Bewegungsunschärfe-Unterframe; Nahfeld-Unschärfe aufgeweitet, sodass unscharfer Vordergrund über scharfen Hintergrund läuft.
- Gehobene Stücke richten sich um ihre eigene Mitte auf (obere Kante zur Linse) und werfen einen Schatten: das gleiche Bild in flachem #1a2133, versetzt um (0.10z, 4+0.36z), unscharf 7+0.5z, 26%.
- Aufstieg: logarithmisch skalierter Zoom auf einer trapezförmigen Geschwindigkeit, Neigungsabflachung 8,15–8,95s, Übergabe an eine Kamera, die die untere Kante der Seite 42px über dem Frame festhält. Tauchgang: quadratischer Ease-In-Out im logarithmischen Maßstab mit fixierter oberer Kante, dann abgesenkt auf y 283; eine abgerundete Rechteck-Maske wandelt 24px → 38px Ecken. Inhalt unter dem Hero verblasst zuerst in die Dunstfarbe, dann auf transparent.
- Titel: Badge-Pille blendet bei 10,02s ein (y 44); Zeile 1 ab 10,12s ein Wort alle 0,06s; Nachsatz ab 10,36s ein Wort alle 0,05s; jedes Wort steigt 16px aus einer 12px Unschärfe in 0,34s empor. Halten mit einem 2% Kriechen.
Verboten: Schnitte, Überblendungen, Stopps oder Beschleunigungen im Gleitflug, die Kamera drehen oder seitlich schwenken, während des Flugs hineinzoomen, Glüheffekte, jeglicher Text über der Seite vor der Endkarte.
Fallstricke
- Riesige Seiten: Die Basisplatte wird stückweise erfasst, nie ein einzelner Screenshot. Wenn der Hero auf halber Höhe wiederholt erscheint, hat ein Capture 16384 Geräte-Pixel überschritten.
- Scrollgesteuerte Seiten: Sticky-Runways werden automatisch zusammengefaltet; Wort-für-Wort-Scroll-Hervorhebungen benötigen eine
css-Regel, die volle Deckkraft erzwingt. - Videos: Sie werden bei
videoTimeSekunden eingefroren. Falls das Video der Live-Seite mit 404 antwortet, erhältst du eine schwarze Karte; lege ein lokales Poster überroutesaus. - Ausblendende Karten zeigen, was dahinter liegt. Blende daher zuerst zur Dunstfarbe aus (die Engine macht das für alles unterhalb des Hero).
- Zeile 1 zu lang: Wörter laufen bei 74pt aus dem Frame. Verkürze die Zeile, anstatt die Schrift zu verkleinern.
- Lizenzierung: Die Musik bleibt in der Verantwortung des Benutzers; bevorzuge Tracks, deren Lizenz kommerzielle Videos abdeckt.