The short version
Five facts and a link. Everything else in this guide is detail.
Open the live Motor hero demo →- Scroll World is a Claude Code skill that builds "fly through a little world" landing pages: AI stills become AI camera flights, stitched seamlessly, scrubbed by scroll. The fork at
ElmsPark/scroll-worldis the hardened one from the video: budget tiers, spend gates, automated seam checking, honest mobile support, SEO copy. - The full experience is expensive and niche. A six-scene world is ~17 paid generations (the video's example ran ~800 credits). Right for luxury brands, launches and portfolios; wrong for most business sites, where people come to do a task and leave.
- The valuable part is the pipeline, not the spectacle. Image → video → frames → scroll mapping, with machine-checked quality gates. That pipeline works just as well at one-tenth scale.
- Two proven shapes. The restrained recipe (one clip, one animated moment, still page below) for task-driven sites; and the full continuous scroll film, now live on The Motor: nine chained generations, 483 frames, the entire story from duct-tape chaos to golden morning with no visible cut anywhere.
- The hard-won seam truth: Seedance does NOT pin a supplied start image as its literal first frame (measured 0.49 to 0.85 SSIM across every conditioning mode on the MCP path), so "chain by handing off the last frame" is not seamless by itself. The fix that ships: an automated match-cut finder (search the outgoing tail against the incoming head for the best pair, cut there) plus a few motion-compensated optical-flow tweens per seam, and a dedicated connector generation when two scenes share no visual overlap at all. No crossfades anywhere.
Where things stand: the demo and this guide are live on the guides hub. The d=72 scoring board itself is untouched; the Phase 2 motion pass has since been RUN on Kenn's green light (12 July, evening): the full 30-second cut lives in the storyboard project folder. This build spent 90 Higgsfield credits (ledger-verified; two unrelated task-reward cashbacks landed alongside it).
Inside the repo
What ElmsPark/scroll-world contains and why the fork exists.
The lineage
oso95/scroll-world is the original open-source skill (GitHub user oso95, "cyw"; the YouTube summary credits a "Peter Wang", a name the repo itself never uses). cth9191/scroll-world is the hardened fork the video demonstrates, and the copy in the ElmsPark GitHub mirrors that fork. Upstream builds the same page; the fork hardens the four things that cost real money and real visitors: credit spend, automated verification, mobile, and SEO. Engine configs stay backward compatible.
What is actually in it
| File | What it is |
|---|---|
SKILL.md | The procedure: interview → budget tier → anchor-gated stills → camera architecture → connectors → encode → assemble → QA. The one make-or-break rule lives here. |
references/prompts.md | Intake checklist plus every prompt template (scene still, dive, connector) with a shared style preamble that keeps the world cohesive. |
references/pipeline.md | Copy-paste bash scripts for the whole run. Idempotent: every step skips finished outputs, so a crash or re-roll never repays for done work. |
references/scrub-engine.js | A 500-line vanilla-JS engine: blob-loaded video scrubbing, lazy prefetch, seam crossfades, device-class clip tiering, iOS fallbacks, an SEO copy block. |
references/index-template.html | A minimal standalone page that mounts the engine, including the crawlable data-sw-seo copy block. |
references/gotchas.md | Symptom → cause → fix for every production failure seen so far, plus the canvas frame-sequence upgrade path (which the Motor demo uses). |
references/knockout.py | Background knockout for floating diorama scenes. |
The one rule that makes or breaks it
Seams must be frame-identical. Every AI generation renders the "same" scene slightly differently, so a connector clip must start and end on the actual rendered frames of its neighbour clips, extracted with ffmpeg, never on the original stills. The fork machine-enforces this with an SSIM check on every seam: ≥0.90 pass, 0.75–0.90 warn, below 0.75 means someone used a still as an endpoint and the clip is a redo, not a QA note.
Camera architecture: the biggest quality lever
- A, continuous forward take (recommended for anything grounded): one camera glides forward the whole way; each leg starts from the previous leg's actual last frame; there are no connectors at all. The camera never reverses, so there is no rewind stutter.
- B, dive plus aerial connector (diorama and miniature worlds only): dive into each scene, pull up and out, fly to the next. The pull-out reverses camera direction at every seam, which reads as intentional in a god's-eye miniature world and as a jarring rewind anywhere else.
The spend controls (the fork's big addition)
- Budget tiers asked before the journey is designed: lean ≈ 8 generations (4 scenes), standard ≈ 11–14 (5 scenes), showcase 17+ (6–7 scenes). Fewer scenes get longer per-scene dwell so a 4-beat world still reads complete.
- Anchor-still gate: ONE still is generated first and approved before the rest batch style-locked to it. A style miss costs 1 generation, not N.
- Previz by default on bigger runs: the whole chain renders on
seedance_2_0_minifirst (frame-locking intact), the page is reviewed from drafts, and full-model credits are only spent after approval. - Idempotent pipeline: the output files are the run state; re-running only regenerates what is missing.
Mobile and SEO hardening
- Clip tier by device class (screen short side ≤600 CSS px), never by pointer type: an iPad Pro reports a coarse pointer but has a desktop-class screen and decoder, so it gets the 1080p master.
- iOS Low Power Mode rejects even muted
play()and breakscurrentTimescrubbing; the engine detects the rejected prime on first touch and flips the page to stills-with-crossfades instead of showing frozen video. - Data-saver and 2g/3g signals (Chromium only) downgrade to stills or a shrunken prefetch window; iOS exposes no signals, so the baseline stays conservative for everyone.
- All copy renders client-side in the engine, so the template ships a
data-sw-seoblock of real markup that crawlers and no-JS visitors read; the engine hides it on mount.
The restrained recipe
The same pipeline at one-tenth the spend: one scroll-scrubbed hero, a still page below it.
When to use which
| Situation | Right tool |
|---|---|
| Luxury brand, product launch, portfolio, campaign microsite | Full scroll-world (lean or standard tier) |
| Business site where visitors come to find, book, buy or ask | Restrained hero: one scrubbed moment, still page below |
| Content site, docs, anything task-first | No scrub at all; a good still image and fast copy |
The thesis: heavy scroll animation on a task-driven site raises load times, hurts accessibility and usually costs conversions. But one cinematic moment at the top, with everything after it calm, borrows all of the premium feel and none of the cost. The stillness below is what makes the hero land.
Why canvas frames instead of video scrubbing
The skill's engine scrubs video.currentTime over a blob. That works, but it drags along a tail of video-element quirks: hosts without byte-range support, iOS blank-until-played priming, Low Power Mode killing playback, seek latency piling up on fast flicks. For a single hero clip there is a cleaner primitive, and it is the one Apple actually ships on its scroll pages: extract the frames once, draw them to a canvas.
- Frame paint is deterministic: no decoder seeks, no seek coalescing needed, scrubbing backwards is free.
- Frames are plain images, so iOS Low Power Mode, autoplay policy and priming quirks simply do not apply.
- Loading is progressive: fetch every 8th frame first and the scrub works immediately at coarse resolution, then the gaps fill in.
- The loading poster is literally frame 1 of the same extraction, so the poster-to-animation handoff cannot pop. Same doctrine as the fork's extracted-frame posters, applied to seam zero.
The build, step by step
- Pick the story arc, not a wow effect. The scrub should mean something read in either direction. The Motor: closed machine becomes working website as you scroll down; website folds back into the machine as you scroll up.
- Bridge two approved stills. Generate one video with
start_image= the "before" still andend_image= the "after" still (Seedance 2.0, 10 s, 1080p, audio off). If the brand already has approved art, this costs exactly one generation and inherits the locked style for free. - Extract and encode the frames.
ffmpeg -i clip.mp4 -vf fps=12,scale=1600:-2 f-%03d.png, thencwebpeach at q≈68. Do a 960 px sibling set for phone-class screens. About 12 fps of extraction (121 frames from a 10-second clip) is a comfortable scrub density. Eyeball the extracted frames before shipping: Seedance can bake a hard CUT into one clip when the start and end images have irreconcilable backgrounds; bridge it with a short ffmpeg xfade dissolve band (this build used 12 tween frames) rather than re-rolling. - Map scroll to frame index. A sticky viewport inside a tall section (300–350 vh); progress = scroll into the section ÷ its run; lerp the current frame toward the target each rAF for weight; draw cover-cropped.
- Sync the copy to the arc. Two or three copy phases crossfaded at fixed progress bands: greet, mid-transform, payoff with the CTA. The payoff line should land exactly as the animation settles.
- Honour the visitor first.
prefers-reduced-motionand data-saver get a static hero (the final frame plus the payoff copy; the poster is a<picture>whose reduce-motion source serves the final frame even before JS runs). No JS gets the same calm layout via<noscript>, with the opening still. All copy is real server-rendered markup, so SEO needs no special block. - Keep everything below still. Editorial sections with existing stills, a fade-and-12px-rise reveal at most, and one CTA at the end. If the page below the hero moves, the hero stops being special.
Cost comparison
| Build | Generations | Credits (approx.) |
|---|---|---|
| Showcase scroll-world, 6 scenes, architecture B | ~17 plus re-rolls | ~800 |
| Lean scroll-world, 4 scenes, architecture A | ~8 | ~350–450 |
| Restrained hero (this recipe) | 1 (plus a re-roll buffer you probably won't use) | ~90 |
The Motor demo
The recipe proven on real, already-approved brand art.
Open the demo →What it is
The whole Motor story as ONE continuous scroll film, built from The Motor storyboard (d=72, Chris's locked paper-craft style). Nine chained Seedance generations, 483 frames on one canvas: the overwhelmed owner's clutter folds itself away, the motor arrives, the light widens, the site blooms, the camera slides behind the page to the working parts, sinks below the bench, rises into the dusk shop and drifts out to the golden-morning welcome. The advert's voice-over lines ride each scene as copy phases; a route rail jumps between scenes; the finale is real HTML. The editorial layout is still in the page and serves reduced-motion, data-saver and no-JS visitors.
What it cost and touched
- The film: nine generations (six story legs, one relit bloom pair after a hard-cut re-plan, one connector), ~432 credits. Plus the earlier one-generation hero study (90) it replaced.
- No new stills. Every other image is the storyboard's existing WebP set.
- This demo is a website-hero study, not the advert. Separately, on Kenn's green light later the same evening, the advert's Phase 2 motion pass was run in full: eight clips, VO and the exact-30s cut, in the storyboard project folder. The d=72 scoring board is unchanged.
What to look at when you open it
- Scroll the whole story slowly, then flick fast: the seek-free canvas keeps up, and every seam is a match-cut with optical-flow in-betweens, not a fade. Scroll back up: the world reassembles in reverse.
- Use the route rail (right edge) to jump straight to a scene; each line of the advert script peaks as its scene settles.
- Try it with Reduce Motion on: the hero becomes a single calm view of the bloomed site with the payoff line. With JS off entirely, the same layout holds with the opening still. Nothing breaks, nothing is blank.
If it earns its keep
The same hero block drops into any PageMotor site as a theme section: the frames folder, one canvas, and the copy phases. And for client work, the recipe reads as a productised line item: "premium animated hero, one generation, still page below". If Chris should see it as part of the wider Motor conversation, it can be published to the gated strategy hub alongside d=72.
Gotchas
The fork's hard-won list, plus what this build added. Symptom → cause → fix.
Carried over from the fork (still apply to any scrub build)
- Seam pop → connector endpoints were stills, not the neighbours' rendered frames → extract real frames; SSIM-gate every seam. (A single-clip hero usually has no seams, but Seedance can put a hard cut INSIDE one clip; this demo bridged one with a 12-frame dissolve. The full doctrine matters the day a second clip joins.)
- NSFW false positives (Seedance flags innocent interiors: "bed", "pool", "wine") → re-roll first (often passes 2nd try), then strip trigger words and add "empty, unoccupied, architectural", then switch that one clip to Kling 3.0.
- Soft output → downscaled or over-compressed → work at native 1080p; sharpen lightly on extraction if needed.
- Portrait crops the subject → 16:9 drawn cover on a tall phone shows the centre only → compose focal subjects centre-safe (the storyboard frames already are), or generate a 9:16 hero variant.
- Concurrent generation 503s / credit races → transient; re-roll the individual failure, it is not really out of credits.
Hard-won on the film build (13 July)
- Seedance does not pin start images on the MCP path: frame 0 scored 0.49 to 0.85 SSIM against the supplied start image across all conditioning modes. Never assume handed-off frames make seams; measure them.
- Match-cut, then tween. Search the outgoing leg's tail against the incoming leg's head for the best-matching pair and cut there; bridge the residue with 2 to 6 motion-compensated frames (ffmpeg minterpolate, mci mode). Optical flow between near-matching frames reads as camera motion; a crossfade reads as an edit.
- minterpolate emits nothing for a two-frame input. Pad to p1 p1 p2 p2 (concat demuxer) and take the middle interval's frames.
- When two scenes share no overlap (best pair below ~0.4), no tween can save it: generate a connector whose start AND end images are the two real film frames, then match-cut its own two seams.
- Environment changes need their own leg. Seedance hard-cuts when start and end images live in irreconcilable worlds; give the light/location change a leg with NO end anchor, then complete the move against the locked target.
Specific to the canvas-frames hero
- Memory, not bandwidth, is the ceiling. Do not hold 120 decoded 1080p bitmaps; keep compressed
Imageobjects and let the browser manage its decode cache. Draw the nearest loaded frame while gaps fill. - Patch the frame count. The page's
FRAMESconstant must match the extraction exactly, or the scrub snaps to the nearest neighbour at the end of the run. - Poster = frame 1 of the extraction, not the source still. The source still is a different render and crops differently; the handoff would pop on first paint.
- Width-gate resizes on touch. Re-laying-out on the URL-bar show/hide resize yanks the scroll position; only relayout when the width changes.
- Reduced motion and data-saver are not edge cases. They are the fallback design: final frame plus payoff copy is a complete, dignified hero on its own.
Estate-specific notes
- Generation ran through the Higgsfield MCP (the M1 MBP has no Higgsfield CLI): upload start/end frames via presigned PUT, confirm, then
generate_videowithstart_image/end_imageroles andget_cost:truefirst to preflight the spend. - Deploy to the guides hub is the standard watcher route (write into
guides-elmspark-com/site/, touchDEPLOY-NOW); the M5 watcher rsyncs and sha-verifies. Many small frame files ride the same rsync, but confirm iCloud has finished syncing before touching the marker.