--- name: scroll-world-landing-pages description: Build scroll-scrubbed cinematic world landing pages. version: 0.2.0 author: Hermes metadata: hermes: tags: [Scroll, LandingPages, FalAI, Video, OpenAI] --- # Fal Scroll-World Landing Pages Build a landing page in which scroll scrubs a pre-rendered, continuous camera flight across a series of scenes. This skill uses the current Fal.ai equivalents of the original Scroll World video roster and uses Hermes `image_generate` for GPT Image 2 stills under the configured OpenAI subscription; it does not create real-time 3D. The scrub engine remains framework-agnostic vanilla JavaScript from the MIT upstream `oso95/scroll-world` project. ## When to Use - “Make a scroll-through world landing page.” - “Turn this brand or value chain into a 3D diorama journey.” - “Build an Apple-style scroll-scrubbed cinematic page.” - “Create a continuous fly-through of our product, process, or place.” ## Prerequisites - A server-side Fal API key as `FAL_KEY`; never expose it in browser code. - A virtual environment with the Fal Python client, installed through `terminal`: ```bash uv venv .venv && .venv/bin/pip install fal-client ``` - `ffmpeg` and `ffprobe` available through `terminal`. - Clone the upstream engine/template through `terminal` when needed: ```bash git clone https://github.com/oso95/scroll-world.git ``` - For scene stills, use `image_generate`, not Fal’s `openai/gpt-image-2` endpoint. Hermes is configured with `gpt-image-2-medium`, so these images are covered by the configured OpenAI subscription rather than consuming Fal credits. ## How to Run Use `image_generate` for cohesive scene stills. Invoke `scripts/fal_i2v.py` through the `terminal` tool for each Fal video clip; it uploads local frames to Fal CDN, calls the exact endpoint, waits for completion, and writes the resulting MP4. When featuring a real product, render its phone/UI state as a controlled DOM/canvas overlay or screen capture—not as generated video—and reserve a blank screen recess in the environment stills. Copy the upstream `skills/scroll-world/references/scrub-engine.js` into the target project using `read_file` and `write_file`. See `references/transition-and-product-overlay-pattern.md`. ## Quick Reference | Original roster name | Exact Fal endpoint | Start/end fields | Supported settings relevant here | |---|---|---|---| | `seedance_2_0` | `bytedance/seedance-2.0/image-to-video` | `image_url`, `end_image_url` | resolution `480p/720p/1080p/4k`; duration `4–15` or `auto`; aspect ratio; `generate_audio`; `bitrate_mode` | | `seedance_2_0_mini` | `bytedance/seedance-2.0/mini/image-to-video` | `image_url`, `end_image_url` | resolution `480p/720p`; duration `4–15` or `auto`; aspect ratio; `generate_audio` | | `kling3_0` | `fal-ai/kling-video/v3/pro/image-to-video` | `start_image_url`, `end_image_url` | duration `3–15`; `generate_audio`; `negative_prompt`; `cfg_scale` `0–1`; optional `elements` / `multi_prompt` | ```bash export FAL_KEY='…' python3 /home/avalon/.hermes/skills/creative/scroll-world-landing-pages/scripts/fal_i2v.py \ --model seedance --start assets/stills/farm.png --out assets/raw/farm.mp4 \ --duration 8 --resolution 1080p --aspect-ratio 16:9 \ --prompt "Single continuous cinematic camera move, no cuts. Begin high and far…" ffmpeg -v error -sseof -0.15 -i assets/raw/farm.mp4 -frames:v 1 -q:v 2 assets/frames/farm-last.png ffmpeg -v error -ss 0 -i assets/raw/shop.mp4 -frames:v 1 -q:v 2 assets/frames/shop-first.png ffmpeg -v error -y -i src.mp4 -an -vf "unsharp=5:5:0.8:5:5:0.0" -c:v libx264 -preset slow -crf 20 -pix_fmt yuv420p -g 8 -keyint_min 8 -sc_threshold 0 -movflags +faststart out.mp4 ``` ## Procedure 1. **Collect the brief.** Ask openly for subject, one-line pitch, brand name, 4–6 named palette hex values, tone, and an ordered 5–7 scene journey. Each scene needs `id`, `label`, subject, eyebrow, title, body, and up to three tags. Ask whether the user wants the mobile beta: it adds 720p/GOP-4 assets and must be described as a performance improvement, not a guarantee for low-end phones. 2. **Generate and review stills directly.** Reuse one art-direction preamble verbatim for every scene; the source default is a warm, isometric low-poly clay diorama on a plain solid background, centred with no text or logos. Use `image_generate` once per still with `aspect_ratio="landscape"`. Preserve full-resolution PNGs as solid-background video inputs and posters. Review every still before video work; re-generate only off-style assets. 3. **Choose the camera architecture.** - **A — continuous forward take:** for a grounded walkthrough. Leg 0 begins from the first still; every later leg begins from the prior rendered leg’s actual last frame. Do not send an end frame. Each prompt must finish in a slow, steady forward drift. Render legs serially and set `connectors: []`. - **B — dive plus aerial connector:** only for miniature or map-like worlds. Every scene has a dive, and each pair is joined with a pull-up aerial connector. This deliberately reverses camera direction, so it is unsuitable for a first-person walkthrough. 4. **Use a single Fal model for the chain.** Default to `seedance`; use `seedance-mini` for a cheap seam-validating previz; use `kling-v3-pro` only as the documented same-roster alternative or for an individual moderation fallback. Pass `--audio` only if native audio is intentionally part of the deliverable; for scroll-scrub pages, leave it off and strip audio at encode. Use `--bitrate-mode high` only when quality justifies larger Fal output files. 5. **Render architecture-A legs.** Invoke the script via `terminal`. For the first clip, pass the first still as `--start`; for every later clip, extract the previous clip’s last frame and pass that as `--start`. Do not use `--end`. Inspect the last frame before generating the next leg; it must read as a calm forward continuation. 6. **Frame-lock architecture-B seams.** A connector’s endpoints must be pixels extracted from its neighboring rendered clips—not original diorama stills: ```bash ffmpeg -v error -sseof -0.15 -i dive_i.mp4 -frames:v 1 -q:v 2 dive_i_last.png ffmpeg -v error -ss 0 -i dive_next.mp4 -frames:v 1 -q:v 2 dive_next_first.png python3 /home/avalon/.hermes/skills/creative/scroll-world-landing-pages/scripts/fal_i2v.py \ --model seedance --start dive_i_last.png --end dive_next_first.png --out connector_i.mp4 \ --duration 5 --resolution 1080p --aspect-ratio 16:9 --prompt "Single continuous camera move, no cuts. Pull up and back out…" ``` The required invariant is `dive_i.end == connector.start` and `connector.end == dive_next.start`. Use a short engine crossfade only as insurance. 7. **Prove one transition before the paid full chain.** Generate two dive clips plus one connector, then use `terminal` to concatenate them and extract a bounded contact sheet for `vision_analyze`. Exact rendered boundary frames are necessary but not sufficient: inspect the connector middle for invented objects, lighting shifts, or reversed velocity. If it fails direction twice, stop rerolling by default; omit that connector and use the engine's direct dive-to-dive crossfade through a shared aperture, doorway, screen recess, or rail. See `references/transition-and-product-overlay-pattern.md`. 8. **Encode for scrubbing.** Invoke the Quick Reference ffmpeg master command through `terminal` for every clip at its native source resolution. Do not default to all-intra video: the upstream engine fetches clips as Blob URLs, avoiding host byte-range limitations. For opted-in mobile variants, create 720p, GOP-4 siblings: ```bash ffmpeg -v error -y -i src.mp4 -an -vf "scale=-2:720,unsharp=5:5:0.6:5:5:0.0" -c:v libx264 -preset slow -crf 23 -pix_fmt yuv420p -g 4 -keyint_min 4 -sc_threshold 0 -movflags +faststart out-m.mp4 ``` 8. **Integrate and configure the engine.** Copy upstream `scrub-engine.js` and optionally its `index-template.html` with `read_file` / `write_file`, then mount it after the container exists: ```js mountScrollWorld(document.getElementById('world'), { brand: { name: 'BRAND', href: '#top' }, diveScroll: 1.3, connScroll: 0.9, sections: [{ id: 'farm', label: 'The Farms', still: 'assets/farm.webp', clip: 'assets/vid/farm.mp4', accent: '#8FB98A', eyebrow: '...', title: '...', body: '...', tags: [] }], connectors: [] }); ``` For architecture B, `connectors` must be ordered and contain exactly `sections.length - 1` entries. Add `clipMobile` and `connectorsMobile` only when mobile beta was selected. 9. **QA the real page.** Use `browser_navigate`, `browser_scroll`, `browser_vision`, and `browser_console`. Check frames immediately before and after every seam, console errors, `video.seekable.end(0) > 0`, reduced-motion behavior, and a phone viewport. For mobile beta, verify the `-m.mp4` source loads and a fast scroll does not freeze. ## Creative Reference For a grounded pattern that combines generated environment video with real astrology-product screens, see `references/astral-hermes-north-star-scroll-world.md`. Use it whenever a scroll world must showcase Time Wave, a chart renderer, rulership-chain storytelling, or a North Star 3-style celestial instrument direction without hallucinated UI. ## Pitfalls - **Using the wrong Fal field names:** Seedance requires `image_url`; Kling V3 Pro requires `start_image_url`. Both use `end_image_url` for the last frame. - **A visible connector pop:** its start/end frames came from stills or newly generated frames, not neighboring rendered clips. - **Rewind-like stutter:** architecture B reverses velocity by design; use architecture A for grounded scenes. - **Seedance mini limits:** it tops out at `720p` and does not expose `bitrate_mode`. - **Kling V3 Pro differs from Seedance:** it has no `resolution` or `aspect_ratio` inputs on this endpoint; do not send them. - **Video fixed at frame zero:** retain the upstream Blob loader rather than relying on host range requests. - **Blank iOS frame:** preserve the engine’s `muted`, `playsinline`, still poster, and first-gesture priming. - **Fal key exposure:** use `FAL_KEY` only server-side or through `terminal`; never put it in the shipped client JavaScript. ## Verification Run `python3 -m py_compile /home/avalon/.hermes/skills/creative/scroll-world-landing-pages/scripts/fal_i2v.py`, then use `browser_scroll` on the deployed page and confirm in `browser_console` that no errors occur and every loaded video reports `video.seekable.end(0) > 0`.