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.
FAL_KEY; never expose it in browser code.terminal:
bash
uv venv .venv && .venv/bin/pip install fal-clientffmpeg and ffprobe available through terminal.terminal when needed:
bash
git clone https://github.com/oso95/scroll-world.gitimage_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.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.
| 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 |
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
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.
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.
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.
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.
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.
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.
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.
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
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.
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.
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.
image_url; Kling V3 Pro requires start_image_url. Both use end_image_url for the last frame.720p and does not expose bitrate_mode.resolution or aspect_ratio inputs on this endpoint; do not send them.muted, playsinline, still poster, and first-gesture priming.FAL_KEY only server-side or through terminal; never put it in the shipped client JavaScript.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.