scroll-world
Produces a landing page where scroll drives a camera: it dives from outside a scene
into its interior, then flies out and into the next scene, continuously, with no visible
cuts. The visuals are AI-generated — stills via Higgsfield (or Codex), the video chain
via Monid by default (pay-per-clip Seedance 2.0; Higgsfield credits as fallback) —
and the page just scrubs pre-rendered video by scroll position. This is the same technique behind Apple's scroll-through product
pages — the camera genuinely moves, scroll only drives time.
What you generate: N scene stills → N "dive-in" camera clips → N-1 "connector" clips
that join consecutive scenes seamlessly → a portable scrub engine that plays the whole
chain as one flight.
The one rule that makes or breaks it: seams must be frame-identical. Read
The seamless chain before generating any
connector. Getting this wrong is the single most common failure and produces a visible
"pop" between scenes.
Do not assume a frontend framework. The scrub engine in references/scrub-engine.js is
self-contained vanilla JS (it builds its own DOM + injects its own CSS into a container
you give it), so it drops into plain HTML, Next.js, Vue, a Python-served page, anything.
The value of this skill is the Higgsfield pipeline, the prompts, and the seam method —
not the framework.
Step 0 — Bootstrap
- Monid CLI — the default video-chain backend. Check
monid --version,
monid keys list (active key) and monid balance — the chain is billed per
clip in USD (Step 1.7 has the numbers; a 1080p N=6 chain ≈ $27). If the CLI is
missing or the balance can't cover the chain, say so and fall back to
rendering the chain on Higgsfield credits instead — same model, same
pipeline, different biller (Step 4 → Monid backend).
- Higgsfield CLI — still required even on the Monid path: it renders the
scene stills (
gpt_image_2) and is the home of the kling3_0 NSFW fallback
and the fallback chain. If higgsfield is not on $PATH, install per the
higgsfield-generate skill. If higgsfield workspace list fails auth, ask the user
to run higgsfield auth login (interactive OAuth — you cannot run it) and, if needed,
higgsfield workspace set <id>. Confirm credits cover the stills (~N image
gens) — plus (2N-1) video gens if the chain falls back here.
- ffmpeg / ffprobe on
$PATH (frame extraction + encoding).
- An image tool for background knockout if you want floating scenes: PIL
(
python3 -c "import PIL"), or cwebp/sips. Optional — see Step 3.
- (Optional) Codex CLI — if
codex is on $PATH (≥ 0.125) and
codex login status reports a ChatGPT login, the scene stills can be generated
through Codex's built-in image_gen (the same gpt-image-2 model) billed to the
user's ChatGPT subscription instead of Higgsfield credits — offer it at
Step 1.7, command in Step 2. Absence just removes the option.
- Caveats: macOS ships bash 3.2 (no
declare -A); don't use associative arrays in
scripts. Higgsfield generations take 3–8 min each — always run them detached
(background) and poll, never a foreground blocking call. Reference-by-job-UUID is
rejected by media flags — pass local file paths to --image/--start-image/--end-image.
Video models differ in accepted params (e.g. Kling has no --resolution) and in whether
they support start/end-image conditioning at all — before batching, confirm the chosen
model's schema with higgsfield model get <job_type> and see the Step 4 model table.
Step 1 — Interview the user
The subject is the user's to state — ask it as an open question in plain prose, never a
fabricated multiple-choice. A made-up list of industries biases them and reads as you
deciding their business for them; let them answer in their own words (their real business,
a client's, or any idea). Reserve structured multiple-choice (AskUserQuestion in Claude
Code; a plain either/or question elsewhere) for the genuinely
enumerable, lower-stakes choices below — art direction, camera style, and brand-kit
approach — and even
there, signal they can go their own way ("Other"). Ask only what you can't sensibly
default. Cover:
Subject (ask openly, not multiple-choice) — "What should this world be about? Your
business, a client's, or any idea — a word or a sentence is fine." Capture the
industry/product + a one-line pitch (e.g. "a bubble tea company, from leaf to last
sip"), and a brand name if they have one; otherwise you'll propose one below.
Brand kit — offer three paths, pick one:
- Import from a URL:
higgsfield marketing-studio brand-kits fetch --url <site> --wait
(pulls name, colours, tone). Then read it back with brand-kits list --json.
- The user hands you palette + name + tone directly.
- You propose a palette + name and let them approve.
Capture 4–6 named hex values, a display name, and a tone word or two.
Art direction — default is "soft matte low-poly clay diorama, isometric,
tilt-shift miniature, warm light." Offer alternatives (flat papercraft, glossy toy,
claymation, neon night). Whatever is chosen becomes the shared style preamble
reused verbatim in every scene prompt (this is what makes the world cohesive).
Camera style — ALWAYS ask; it's the film's personality, not a technical
detail. Ask by feel (AskUserQuestion in Claude Code; a plain question
elsewhere) and record the answer as CAMERA. The options map to the Step 4
architectures — Step 4 then implements the choice, it never re-decides it:
- "Fly through the world" — the camera dives into each scene, pulls up and
out, and hops across the miniature world to the next; angles change
constantly, big expressive aerial moves (this is the flagship-demo look).
→ Architecture B. Recommend as the default for diorama/miniature art
directions.
- "One continuous walkthrough" — a single forward flight that glides
through each scene straight into the next, never pulling back; expressive
but always-forward moves per scene (camera grammar table). → Architecture A.
Recommend as the default for grounded/photoreal art directions.
- "Locked isometric glide" — the camera keeps one fixed angle for the whole
film, Emons-style; the world slides past/toward it, no rotation, no reveals.
→ Architecture A + the locked-iso clause in every leg prompt (prompts.md).
State the trade-off in one line each (B reverses direction at seams — charming
in miniature, jarring in realism; locked-iso is the calmest and cheapest to
re-roll; walkthrough sits between).
The journey (sections) — the ordered scenes the camera flies through. Propose a
set derived from the subject's own value chain and let the user edit. 5–7 works well.
Boba example: farms → pearl kitchen → flagship shop → delivery → community plaza →
the hero product. Each section needs: a short subject description (what's IN the
diorama), an eyebrow, a headline, one line of body, and 0–3 tag pills. The last
section is usually the hero product + the CTA.
Mobile version — ALWAYS ask this; never silently generate both. Ask as a
two-option choice (AskUserQuestion in Claude Code; a plain question elsewhere):
"Want a mobile-optimized version too? The mobile version is a second camera chain
rendered natively in 9:16 portrait — composed for phones, not a crop of the
landscape film — which roughly doubles the Higgsfield credit spend (state the
estimated number)."
Options: "Desktop only" / "Desktop + mobile (native 9:16 — ~2× credits)". The
credit cost must be stated to the user, not just implied.
What the answer gates:
- Yes → render the parallel 9:16 portrait chain and ship it as the mobile variants
(Step 6 / pipeline.md §6b): portrait start canvases → 9:16 dives + connectors
frame-locked against their own renders → 720-wide
-m.mp4 encodes → stillMobile
portrait posters. Wire clipMobile/connectorsMobile/stillMobile (Step 7); run
the full mobile QA (Step 8). Budget ~2N-1 extra video gens + NSFW re-rolls.
Never ship the centre-crop as the mobile version by default — if credits can't
cover the portrait chain, say so and offer the crop encodes (pipeline.md §6) as an
explicitly-labelled stopgap the user must approve.
- No → skip the mobile encodes and wiring entirely. The engine's phone hardening
(seek-coalescing, iOS priming, safe-area CSS) is always on regardless — that's not
a "mobile version," it's just the page not breaking when a phone visits — so a
desktop-only build still degrades gracefully.
Budget — engines shown by cost, decided before anything renders. Present the
render tiers (AskUserQuestion), then compute and state the estimated total for
the user's N scenes — N stills + (2N−1) videos [videos ×2 if mobile] + ~15% re-roll headroom — and get a go before generating.
Video tier (roster only — every option frame-locks seams, Step 4):
| Tier |
Model |
Rough cost |
| Draft / previz |
seedance_2_0_mini (720p) |
~¼ of Standard |
| Standard (default) |
seedance_2_0 (1080p) |
baseline |
| Alternate |
kling3_0 (720p native) |
≈ Standard; different look + content filter |
Draft doubles as the previz path: run the whole chain cheap, approve the
journey, re-render final legs on Standard (pipeline.md Notes) — suggest it
unprompted when the balance reads tight.
Backend — Monid is the DEFAULT biller for the chain (Step 0.1; wiring
in pipeline.md → Monid backend). Same Seedance 2.0, per-clip USD instead of
credits. Token-priced width × height × 24 × seconds / 1024 at $7/1M
(480p/720p) or $7.7/1M (1080p) — measured: 1080p 8s dive ≈ $2.99, 5s
connector ≈ $1.87; 720p ≈ $1.21 / $0.76; 480p ≈ $0.28 / $0.35. An N=6
desktop chain ≈ $27 at 1080p / ~$11 at 720p vs Higgsfield Plus-monthly ≈
$32 / $16 — ~15% cheaper per clip, parity with Plus-annual; structurally
better for one-off builds (pay-per-use, no monthly expiry). On Monid the
Draft/previz tier is simply the same endpoint at 480p — no model swap,
so previz→final stays one-model by construction. State monid balance
against the estimate; fall back to Higgsfield credits (per-model tiers
above) when the user prefers their subscription, the balance is short, or
the model must be kling3_0 (Higgsfield-only). It's the same underlying
model (seedance_2_0 ≙ Monid's seedance-2.0), so finishing a stranded
chain on the other biller is a reasonable rescue — but the serving stacks
differ and cross-provider seam character is untested: eyeball the first
rescued seam before rendering the rest, same as any model swap.
Stills source (only offer if the Codex CLI is present, Step 0.5):
Higgsfield gpt_image_2 (spends credits) vs Codex image_gen — the same
gpt-image-2 model billed to the ChatGPT subscription (zero credits; counts
toward Codex usage limits; 1536×1024 output — exactly 3:2, slightly under
Higgsfield's 2k). Stills are plain PNGs handed to --start-image, so the
video chain is indifferent to their source. Command in Step 2. One source
for all N stills of a build — the two render with slightly different
character (verified: Codex runs warmer/lighter), and mixing sources across
scenes reads as style drift, same reason the video chain uses one model.
Calibrate costs, don't guess. The CLI exposes no pricing and plans differ.
Run ONE still and ONE video first, diff higgsfield workspace list before/
after, extrapolate to the full run, and warn the user whenever the estimate
exceeds ~70% of the balance. (Observed on a plus plan, 2026-07: Standard
video ≈ 40–55 credits, still ≈ 15.) A real not_enough_credits mid-run is
recoverable (finished clips survive; resume after top-up) but ugly — the
whole point of this step is that the user decides before the spend.
If the user names a video model outside the roster, honor it only if it can
frame-lock seams (Step 4). This skill only ships seamless output, so a model that
can't frame-lock is declined with a one-line why, not substituted in — use a roster
model instead.
Keep the scroll mechanic fixed (continuous fly-through) — that's the point of the skill.
See references/prompts.md for the intake checklist and copy structure.
Step 2 — Generate the scene stills
One image per section, all sharing the same style preamble for cohesion. Default
model gpt_image_2 (crisp, great at isometric illustration; returns a solid/white
background which is perfect for floating diorama "islands"). Use nano_banana_2 only if
the brief is character/cartoon-heavy (note: nano_banana_2 is a CLI alias — it resolves
to nano_banana_pro; it won't appear under that name in higgsfield model list).
Prompt shape (full templates in references/prompts.md):
<STYLE PREAMBLE, identical every time>. On a plain solid <bg> background with a soft
contact shadow. <PALETTE hexes>. No text, no letters, no logos, centered, 3:2.
Subject: <what is in THIS diorama>.
Run all N concurrently, detached. Command per scene:
higgsfield generate create gpt_image_2 --prompt "$(cat scene_i.txt)" --aspect_ratio 3:2 --resolution 2k --quality high --wait --wait-timeout 15m --json > scene_i.json 2>scene_i.err
Result URL is .[]0.result_url in the --wait --json output. curl it down.
Codex stills variant (if chosen at Step 1.7 — subscription-billed, zero
credits): same prompt files, same byte-identical preamble, generated by Codex's
built-in image_gen:
codex exec -C "$WORK" -s workspace-write --skip-git-repo-check \
'Use the image generation tool ($imagegen) to generate: '"$(cat "$WORK/still_i.txt")"' Wide 3:2 landscape, high resolution. Save it as ./still_i.png. Do not do anything else.' \
< /dev/null
Single-quote the $imagegen segment (the shell must not expand it); if editing
with reference images, the prompt goes BEFORE any -i flag (it's variadic).
~1–3 min per image; run a few in parallel, not all N at once — and keep the
< /dev/null: parallel codex exec calls sharing a script's stdin hang
waiting for input (Gotchas). Output lands at
1536×1024 (3:2) — fine for --start-image and posters. Everything downstream
(cohesion review, knockout, dives) is unchanged.
A generation may fail transiently (HTTP 503) — re-roll that one individually; don't
restart the batch.
Review the stills before continuing. They must read as one cohesive world (same
angle, palette, light). If one is off-style, regenerate it, optionally passing an
approved scene as --image to lock style.
See references/pipeline.md for the exact batch script.
Step 3 — (Optional) Float the scenes
If you want the dioramas to float over an atmospheric background instead of sitting in a
solid box, knock out the flat background to transparency with
references/knockout.py (border-connected flood fill — preserves interior colour that
matches the bg, e.g. cream walls). Then encode to webp. If you'd rather keep it simple,
just make the page background the same colour as the scene background and skip this.
These stills double as video posters and lazy-load fallbacks, so keep them.
Step 4 — Camera architecture (implements the Step 1.4 choice)
How the camera moves between scenes is the single biggest quality lever. The user
already chose the style at the interview (CAMERA, Step 1.4): fly-through → B,
walkthrough → A, locked-iso → A + the locked-iso leg clause (prompts.md). If
the interview somehow skipped it, ask now — never silently pick for them. The two
shapes, and the grammar that colors them:
Video model — pick ONE for the whole chain
This skill only ships seamless output, so the only usable models are ones that can
frame-lock a seam: every chained clip must accept --start-image, and connectors also
need --end-image. That capability — not preference — is the selection rule. Check any
model with higgsfield model get <job_type> and skip anything whose media inputs are
reference-only (no start/end image): it can only condition a generation, not
continue a shot, so it physically can't hold a seam. Schemas below were confirmed
against the CLI:
| Model |
start/end image |
Notes |
seedance_2_0 (default) |
✓ / ✓ |
Full chain (legs + connectors). --mode std --resolution 1080p. Its NSFW filter is the touchy one (see Gotchas). |
kling3_0 |
✓ / ✓ |
Full chain — tested: --mode std --sound off --duration 5 with start+end images accepted, seams frame-lock cleanly. No --resolution param (don't pass one; --mode std returns 720p native — encode what ffprobe reports, never upscale). Sound defaults on → --sound off. --duration default 5, try 10 for legs. Different content filter than Seedance — the sanctioned NSFW fallback. |
seedance_2_0_mini |
✓ / ✓ |
Cheap draft tier that keeps frame-locking (720p). The previz tier: run the whole chain here first, then re-render final legs on the full model — still seamless, so it translates directly. |
Those three are the roster — all do both architectures. (kling3_0_turbo also frame-locks
via --start-image, but has no --end-image, so it's architecture-A-only and can't make
connectors; it also takes a different flag set — no --mode, has --resolution — so it
doesn't drop into the pipeline as-is. It's not in the default roster; only reach for it, and
wire it by hand, if architecture A's sequential render time is a proven bottleneck and you've
benchmarked it as actually faster.)
One more architecture-A-only candidate, worth knowing because it is by far the cheapest
probe: minimax_hailuo (Hailuo-2.3, ~6 credits per 768p/6s clip vs 22–72 for the
roster). Verified 2026-07: --start-image + prompt frame-locks (output frame 0 ≡ input,
PSNR 33 dB) and a forward-glide prompt was obeyed, gently. Constraints: the 2.3 variant
rejects end_image (no connectors → arch A only), output aspect follows the input image
(hand it a 16:9 canvas, not a bare 3:2 still), motion runs subtler than seedance, and
don't pass --resolution (the CLI mis-types the enum; the 768 default works — 1080
supports 6s only). One clip ≠ a chain: qualify a leg-to-leg handoff before betting a
full build on it.
Rules:
- One model for all chained clips. Each renderer has its own motion/color/grain
character; mixing models mid-chain keeps position continuity (frames still hand off)
but the render-character shift reads as a subtle pop. The one sanctioned exception is
the NSFW fallback for a single stubborn clip (Gotchas) — a slight character shift on
one 5s connector beats a missing connector.
- Default to
seedance_2_0, rendered through Monid by default (per-clip USD —
next section) with Higgsfield credits as the fallback biller (Step 0.1/1.6); honor
a user's stated preference only if the model qualifies (frame-locking). If it
doesn't, say so and use a supported model — never ship a non-seamless build to
satisfy a model request. kling3_0 and seedance_2_0_mini exist only on the
Higgsfield side.
- The pipeline scripts take the model as
$VMODEL with per-model flags already cased
out (references/pipeline.md).
Monid backend — the DEFAULT chain biller (qualified 2026-07-25)
Monid's bytedance /v1/video/seedance-2.0 passed both paid probes on
2026-07-25 and is the default way this skill renders the chain, for both
architectures — it is the roster's seedance_2_0 served pay-per-USD (wiring in
pipeline.md → "Monid backend"; Higgsfield renders the chain only as the fallback
biller or for Higgsfield-only models):
- Leg probe (prompt +
first_frame image): output frame 0 ≡ input still
(PSNR 31.6 dB), forward-glide prompt obeyed, billed the advertised cell
($0.279 / 480p 4s).
- Connector probe (prompt +
first_frame + last_frame): start locked
(31.6 dB); the end lands close but not pixel-perfect (27.5 dB, same
composition, prop-level drift) — the exact end-image behavior Seedance shows
on Higgsfield, covered by the engine's seam crossfade and by using the next
dive's ACTUAL first frame as the end-image (Step 5 law, unchanged).
The I/O contract differs from the Higgsfield CLI — three rules:
- Images go by URL, never inline.
content items are
{"type":"image_url","image_url":{"url":…},"role":"first_frame"|"last_frame"};
base64 data URLs are rejected ("Must be a public https:// URL or an
asset:// reference"). Local frames travel through Monid's free workspace
file system: sfs /put → curl -T the bytes → sfs /cat returns a signed
public URL to paste into the body ($0, explicitly built for this).
- Pass
ratio explicitly (16:9, or 9:16 for the mobile chain) — the
adaptive default follows the input image's aspect instead.
- Bill-check every clip: cost is token-priced
(
w × h × 24 × sec / 1024 at $7–7.7/1M); read cost.value off each run.
History that shaped these rules (still true as of 2026-07-25): the seedance
endpoints were text-to-video-only until late July 2026 — re-inspect before
each build; the catalog moves in both directions. minimax /v1/video_generation (Hailuo-2.3) remains disqualified: sending prompt +
first_frame_image together silently drops the image (unrelated t2v output,
wrong price cell); image-only frame-locks (33 dB) but has no camera control.
Qualification protocol for any new/changed Monid endpoint (each probe is one
cheap 480p clip): (1) prompt + first-frame from a real still — frame 0 must
match the input to codec noise (PSNR ≳ 30 dB) and cost.value must match the
advertised cell; (2) for connector duty, add a last_frame from a different
still — the end must land on that composition (Seedance-style near-miss is fine,
the crossfade covers it). Pass → pay-per-clip tier (arch A if start-only; full
roster if start+end).
A) Continuous forward take — RECOMMENDED for grounded / realistic / walkthrough
One camera that only ever glides forward, first scene through last, as a single take.
Generate the legs sequentially: leg 0 from scene-0's still (glide forward into it);
then each leg's --start-image = the previous leg's ACTUAL last frame (extract with
ffmpeg), prompt "continue gliding smoothly FORWARD into [scene i], never pulling back"
(or an expressive mid-leg move under the motion-handoff contract — see Camera grammar
below), and no --end-image — an end-image of a wide establishing shot forces the
camera to pull back, which is the #1 cause of stutter. Extract each leg's last frame to feed the
next. Result: every seam is frame-identical and the camera never reverses. There are
no connectors (skip Step 5) — the legs ARE the journey. Wire each leg as a section
clip with connectors: [] and a small crossfade (~0.08). Even without an --end-image
the legs still arrive at distinct rooms (the prompt steers the content). Cost: strictly
sequential (can't parallelize) and slower; interiors trip the NSFW filter, so build in
re-rolls (3 attempts/leg).
B) Dive-in + aerial connector — only for diorama / miniature / god's-eye worlds
A "dive into each scene" clip + a connector that pulls up and out and flies over to the
next scene (Step 5). The pull-out reverses camera direction at every seam (forward dive
→ backward pull-out). In a miniature/diorama world that reads as an intentional "zoom out
to the map, fly to the next island"; in a grounded first-person walkthrough it reads as a
jarring rewind/stutter. Use B only for the map-like aesthetic — which is exactly
what the "fly through the world" interview answer opts into; the reversal reads as
intentional there. If the user picked B against a grounded/photoreal direction, say
why it will read as a stutter and confirm before rendering.
Camera grammar — the move should fit the concept (A is NOT "forward only")
"Forward only" is the seam rule, not the leg rule. The physics of the chain:
- Position continuity at a seam comes from the frame handoff (next leg starts from the
previous leg's actual last frame).
- Velocity continuity at a seam means the camera must never reverse across a seam —
that's the rewind stutter.
- Inside a single leg the camera is free. One leg is one continuous render — there is
no seam to break mid-leg, so orbits, crane-ups, lateral tracking, even a push-in that
eases back out are all safe within the clip. Reversals are only fatal across seams.
So give each leg an expressive move chosen from the scene's own logic, under a motion
handoff contract: every leg ends by settling into a slow, steady forward drift toward
the next destination (final ~1 s), and every leg begins by continuing that same drift.
Keep both clauses in the prompts verbatim (templates in references/prompts.md).
Pick the grammar from the concept:
| Concept / tone |
Mid-leg move |
| Product / luxury retail |
slow half-orbit around the hero object, then continue past it |
| Real estate / hospitality |
steadicam glide through doorways; gentle crane-up in atria |
| Industrial / process / logistics |
low lateral track alongside the line, foreground parallax |
| Travel / outdoors / campus |
drone-style rise-and-reveal, then a descending swoop |
| Food / craft / detail-driven |
push in close to the craft moment, ease back, carry on |
| Playful miniature (arch. B) |
dives + aerial hops — the connector IS the grammar |
Honest costs: expressive mid-leg moves raise re-roll odds — the model can end a fancy move
in a state that isn't a clean forward drift. Mitigations: keep the final-second settle
clause verbatim; eyeball each leg's last frame before chaining the next (it should look
like a frame from a gentle forward glide — if not, re-roll before wasting the next leg);
budget ~1 extra re-roll per expressive leg. A plain forward glide stays the zero-risk
default — use it for legs where the scene itself is the show.
Locked-iso variant (CAMERA = locked isometric glide): architecture A where every
leg pins the view instead of taking a mid-leg move — "the camera keeps exactly the same
high isometric angle throughout, no rotation, no orbit, no tilt; it only travels
straight and level, the world sliding past beneath the same view" (verbatim clause in
prompts.md). The handoff contract is unchanged. Seedance drifts the angle slightly on
long legs — the existing eyeball-each-last-frame rule is the catch; re-roll a leg whose
view has rotated. Calmest look, cheapest re-rolls, and the closest to the Emons
reference.
Two related pacing knobs live in the engine (Step 7): per-section scroll (more scroll
distance = longer dwell in that scene) and linger (the camera settles mid-scene exactly
while the copy peaks, then picks up speed toward the seam). Prefer expressive motion in the
clip and restraint in the scrub mapping — they compound.
And remember scroll is a scrubber: visitors can scroll up, so every move also plays in
reverse. That's free and expected — no extra work — but it's another reason seam velocity
must be consistent in both directions (a seam that reads fine forward reads as a stutter
backward too if velocity flips).
For B, one camera flight per scene: starts high/outside, descends into the interior,
structure opens. Model: the chain model you picked above (default seedance_2_0),
--start-image = the scene still.
- Use the solid-background still (not the knocked-out transparent one) as the
start image, so the video has a full frame.
- Prompt: "Single continuous cinematic camera move, no cuts. Begin high and far looking
at the whole from outside … descend and fly inside toward … the
roof/walls gently open to reveal the interior. , smooth graceful slow motion.
No text." (Template in
references/prompts.md.)
- Params (seedance):
--mode std --resolution 1080p --aspect_ratio 16:9 --duration 8.
For Kling: drop --resolution (no such param), add --sound off, --duration 10.
Do not pass --generate-audio (it errors on seedance; audio is wasted anyway —
you'll mute).
- Run concurrently, detached, then download each
.result_url. Re-roll individual
failures. Keep the raw 1080p sources — you need their frames next.
Step 5 — Connectors (architecture B only)
Skip this whole step for architecture A — the forward take has no connectors; its legs
already chain seamlessly. This step applies to B (diorama/miniature), and note the
reversal caveat from Step 4.
The connector clips are what make the world feel connected instead of cut. A connector
flies from the end of scene i out and into the start of scene i+1. Both of its
endpoints must be the ACTUAL RENDERED FRAMES of the neighbouring clips — never the
original diorama still.
Why: every Higgsfield generation renders slightly differently. If a connector ends on
a fresh render of "the kitchen diorama," but the next dive clip starts on its own
different render of that same diorama, the two won't match and you get a pop at the seam.
The fix is to hand off the exact pixels:
For each connector between dive_i and dive_{i+1}:
start-image = the LAST frame extracted from dive_i's rendered video
end-image = the FIRST frame extracted from dive_{i+1}'s rendered video
Now every seam is frame-identical on both sides:
dive_i.end == connector.start and connector.end == dive_{i+1}.start.
Extract the boundary frames from the rendered dives (not the stills):
ffmpeg -sseof -0.15 -i dive_i.mp4 -frames:v 1 -q:v 2 dive_i_last.png # interior of i
ffmpeg -ss 0 -i dive_{i+1}.mp4 -frames:v 1 -q:v 2 dive_next_first.png # establishing of i+1
Generate the connector (--duration 5 is plenty). Connectors need --end-image, so the
model must accept it — any roster model does (seedance_2_0, seedance_2_0_mini,
kling3_0):
higgsfield generate create "$VMODEL" \
--prompt "$(cat connector_i.txt)" \
--start-image dive_i_last.png --end-image dive_next_first.png \
$VOPTS --aspect_ratio 16:9 --duration 5 --wait --json
# seedance: VOPTS="--mode std --resolution 1080p"; kling3_0: VOPTS="--mode std --sound off"
Connector prompt: "Single continuous camera move, no cuts. Pull up and back out of
, rise into the sky, glide across the connected miniature world, and arrive
above <scene i+1>, beginning to descend toward it. Seamless flowing aerial transition.
1---2name: scroll-world3description: Build an immersive scroll-scrubbed "fly through the world" landing page for any industry or brand using Higgsfield. As the visitor scrolls, a pre-rendered camera flies from outside each scene into its interior, then flows on to the next scene with NO cuts — one continuous connected flight (Emons-style isometric diorama world, or any art direction you pick). The skill interviews the user for the topic, the story beats/sections, and brand kit, then generates cohesive scenes + seamless camera clips with Higgsfield and wires a portable, framework-agnostic scroll-scrub engine. The video chain renders through Monid by default (Seedance 2.0, pay-per-clip USD — capability re-checked each build, see Step 4) with Higgsfield credits as the fallback biller. Use when the user wants a "3D world" / "browse-through-the-industry" hero, a scroll cinematic, a diorama landing, or to turn a business into a scrollable world.4---5
6# scroll-world
7
8Produces a landing page where **scroll drives a camera**: it dives from outside a scene
9into its interior, then flies out and into the next scene, continuously, with no visible
10cuts. The visuals are AI-generated — stills via Higgsfield (or Codex), the video chain
11via **Monid by default** (pay-per-clip Seedance 2.0; Higgsfield credits as fallback) —
12and the page just scrubs pre-rendered video by scroll position. This is the same technique behind Apple's scroll-through product
13pages — the camera genuinely moves, scroll only drives time.
14
15**What you generate:** N scene stills → N "dive-in" camera clips → N-1 "connector" clips
16that join consecutive scenes seamlessly → a portable scrub engine that plays the whole
17chain as one flight.
18
19**The one rule that makes or breaks it:** seams must be *frame-identical*. Read
20[The seamless chain](#step-5--the-seamless-chain-the-critical-part) before generating any
21connector. Getting this wrong is the single most common failure and produces a visible
22"pop" between scenes.
23
24Do not assume a frontend framework. The scrub engine in `references/scrub-engine.js` is
25self-contained vanilla JS (it builds its own DOM + injects its own CSS into a container
26you give it), so it drops into plain HTML, Next.js, Vue, a Python-served page, anything.
27The value of this skill is the Higgsfield pipeline, the prompts, and the seam method —
28not the framework.
29
30---
31
32## Step 0 — Bootstrap
33
341. **Monid CLI — the default video-chain backend.** Check `monid --version`,
35 `monid keys list` (active key) and `monid balance` — the chain is billed per
36 clip in USD (Step 1.7 has the numbers; a 1080p N=6 chain ≈ $27). If the CLI is
37 missing or the balance can't cover the chain, say so and fall back to
38 rendering the chain on Higgsfield credits instead — same model, same
39 pipeline, different biller (Step 4 → Monid backend).
402. **Higgsfield CLI — still required even on the Monid path**: it renders the
41 scene stills (`gpt_image_2`) and is the home of the `kling3_0` NSFW fallback
42 and the fallback chain. If `higgsfield` is not on `$PATH`, install per the
43 `higgsfield-generate` skill. If `higgsfield workspace list` fails auth, ask the user
44 to run `higgsfield auth login` (interactive OAuth — you cannot run it) and, if needed,
45 `higgsfield workspace set <id>`. Confirm credits cover the stills (~N image
46 gens) — plus `(2N-1)` video gens if the chain falls back here.
473. **ffmpeg / ffprobe** on `$PATH` (frame extraction + encoding).
484. **An image tool** for background knockout if you want floating scenes: PIL
49 (`python3 -c "import PIL"`), or `cwebp`/`sips`. Optional — see Step 3.
505. **(Optional) Codex CLI** — if `codex` is on `$PATH` (≥ 0.125) and
51 `codex login status` reports a ChatGPT login, the scene stills can be generated
52 through Codex's built-in `image_gen` (the same gpt-image-2 model) billed to the
53 user's ChatGPT subscription instead of Higgsfield credits — offer it at
54 Step 1.7, command in Step 2. Absence just removes the option.
556. Caveats: macOS ships **bash 3.2** (no `declare -A`); don't use associative arrays in
56 scripts. Higgsfield generations take **3–8 min each** — always run them detached
57 (background) and poll, never a foreground blocking call. Reference-by-job-UUID is
58 rejected by media flags — pass **local file paths** to `--image/--start-image/--end-image`.
59 Video models differ in accepted params (e.g. Kling has no `--resolution`) and in whether
60 they support start/end-image conditioning at all — before batching, confirm the chosen
61 model's schema with `higgsfield model get <job_type>` and see the Step 4 model table.
62
63---
64
65## Step 1 — Interview the user
66
67The **subject is the user's to state — ask it as an open question in plain prose**, never a
68fabricated multiple-choice. A made-up list of industries biases them and reads as you
69deciding their business for them; let them answer in their own words (their real business,
70a client's, or any idea). Reserve structured multiple-choice (`AskUserQuestion` in Claude
71Code; a plain either/or question elsewhere) for the genuinely
72enumerable, lower-stakes choices below — art direction, camera style, and brand-kit
73approach — and even
74there, signal they can go their own way ("Other"). Ask only what you can't sensibly
75default. Cover:
76
771. **Subject** (ask openly, not multiple-choice) — "What should this world be about? Your
78 business, a client's, or any idea — a word or a sentence is fine." Capture the
79 industry/product + a one-line pitch (e.g. "a bubble tea company, from leaf to last
80 sip"), and a brand name if they have one; otherwise you'll propose one below.
812. **Brand kit** — offer three paths, pick one:
82 - Import from a URL: `higgsfield marketing-studio brand-kits fetch --url <site> --wait`
83 (pulls name, colours, tone). Then read it back with `brand-kits list --json`.
84 - The user hands you palette + name + tone directly.
85 - You propose a palette + name and let them approve.
86 Capture **4–6 named hex values**, a display name, and a tone word or two.
873. **Art direction** — default is "soft matte low-poly **clay diorama**, isometric,
88 tilt-shift miniature, warm light." Offer alternatives (flat papercraft, glossy toy,
89 claymation, neon night). Whatever is chosen becomes the shared **style preamble**
90 reused verbatim in every scene prompt (this is what makes the world cohesive).
914. **Camera style — ALWAYS ask; it's the film's personality, not a technical
92 detail.** Ask by feel (`AskUserQuestion` in Claude Code; a plain question
93 elsewhere) and record the answer as `CAMERA`. The options map to the Step 4
94 architectures — Step 4 then *implements* the choice, it never re-decides it:
95 - **"Fly through the world"** — the camera dives into each scene, pulls up and
96 out, and hops across the miniature world to the next; angles change
97 constantly, big expressive aerial moves (this is the flagship-demo look).
98 → Architecture B. Recommend as the default for diorama/miniature art
99 directions.
100 - **"One continuous walkthrough"** — a single forward flight that glides
101 through each scene straight into the next, never pulling back; expressive
102 but always-forward moves per scene (camera grammar table). → Architecture A.
103 Recommend as the default for grounded/photoreal art directions.
104 - **"Locked isometric glide"** — the camera keeps one fixed angle for the whole
105 film, Emons-style; the world slides past/toward it, no rotation, no reveals.
106 → Architecture A + the locked-iso clause in every leg prompt (prompts.md).
107 State the trade-off in one line each (B reverses direction at seams — charming
108 in miniature, jarring in realism; locked-iso is the calmest and cheapest to
109 re-roll; walkthrough sits between).
1105. **The journey (sections)** — the ordered scenes the camera flies through. Propose a
111 set derived from the subject's own value chain and let the user edit. 5–7 works well.
112 Boba example: farms → pearl kitchen → flagship shop → delivery → community plaza →
113 the hero product. Each section needs: a short subject description (what's IN the
114 diorama), an eyebrow, a headline, one line of body, and 0–3 tag pills. The last
115 section is usually the hero product + the CTA.
1166. **Mobile version — ALWAYS ask this; never silently generate both.** Ask as a
117 two-option choice (`AskUserQuestion` in Claude Code; a plain question elsewhere):
118 *"Want a mobile-optimized version too? The mobile version is a second camera chain
119 rendered natively in **9:16 portrait** — composed for phones, not a crop of the
120 landscape film — which roughly doubles the Higgsfield credit spend (state the
121 estimated number)."*
122 Options: "Desktop only" / "Desktop + mobile (native 9:16 — ~2× credits)". The
123 credit cost must be stated to the user, not just implied.
124 What the answer gates:
125 - **Yes** → render the parallel 9:16 portrait chain and ship it as the mobile variants
126 (Step 6 / pipeline.md §6b): portrait start canvases → 9:16 dives + connectors
127 frame-locked against their own renders → 720-wide `-m.mp4` encodes → `stillMobile`
128 portrait posters. Wire `clipMobile`/`connectorsMobile`/`stillMobile` (Step 7); run
129 the full mobile QA (Step 8). Budget ~2N-1 extra video gens + NSFW re-rolls.
130 **Never ship the centre-crop as the mobile version by default** — if credits can't
131 cover the portrait chain, say so and offer the crop encodes (pipeline.md §6) as an
132 explicitly-labelled stopgap the user must approve.
133 - **No** → skip the mobile encodes and wiring entirely. The engine's phone hardening
134 (seek-coalescing, iOS priming, safe-area CSS) is always on regardless — that's not
135 a "mobile version," it's just the page not breaking when a phone visits — so a
136 desktop-only build still degrades gracefully.
137
1387. **Budget — engines shown by cost, decided before anything renders.** Present the
139 render tiers (`AskUserQuestion`), then compute and state the estimated total for
140 the user's N scenes — `N stills + (2N−1) videos [videos ×2 if mobile] + ~15%
141 re-roll headroom` — and get a go before generating.
142 - **Video tier** (roster only — every option frame-locks seams, Step 4):
143
144 | Tier | Model | Rough cost |
145 |---|---|---|
146 | Draft / previz | `seedance_2_0_mini` (720p) | ~¼ of Standard |
147 | Standard (default) | `seedance_2_0` (1080p) | baseline |
148 | Alternate | `kling3_0` (720p native) | ≈ Standard; different look + content filter |
149
150 Draft doubles as the previz path: run the whole chain cheap, approve the
151 journey, re-render final legs on Standard (pipeline.md Notes) — suggest it
152 unprompted when the balance reads tight.
153 - **Backend — Monid is the DEFAULT biller for the chain** (Step 0.1; wiring
154 in pipeline.md → Monid backend). Same Seedance 2.0, per-clip USD instead of
155 credits. Token-priced `width × height × 24 × seconds / 1024` at $7/1M
156 (480p/720p) or $7.7/1M (1080p) — measured: 1080p 8s dive ≈ $2.99, 5s
157 connector ≈ $1.87; 720p ≈ $1.21 / $0.76; 480p ≈ $0.28 / $0.35. An N=6
158 desktop chain ≈ $27 at 1080p / ~$11 at 720p vs Higgsfield Plus-monthly ≈
159 $32 / $16 — ~15% cheaper per clip, parity with Plus-annual; structurally
160 better for one-off builds (pay-per-use, no monthly expiry). On Monid the
161 Draft/previz tier is simply the **same endpoint at 480p** — no model swap,
162 so previz→final stays one-model by construction. State `monid balance`
163 against the estimate; **fall back to Higgsfield credits** (per-model tiers
164 above) when the user prefers their subscription, the balance is short, or
165 the model must be `kling3_0` (Higgsfield-only). It's the same underlying
166 model (`seedance_2_0` ≙ Monid's `seedance-2.0`), so finishing a stranded
167 chain on the other biller is a reasonable rescue — but the serving stacks
168 differ and cross-provider seam character is **untested**: eyeball the first
169 rescued seam before rendering the rest, same as any model swap.
170 - **Stills source** (only offer if the Codex CLI is present, Step 0.5):
171 Higgsfield `gpt_image_2` (spends credits) vs **Codex `image_gen`** — the same
172 gpt-image-2 model billed to the ChatGPT subscription (zero credits; counts
173 toward Codex usage limits; 1536×1024 output — exactly 3:2, slightly under
174 Higgsfield's 2k). Stills are plain PNGs handed to `--start-image`, so the
175 video chain is indifferent to their source. Command in Step 2. **One source
176 for all N stills of a build** — the two render with slightly different
177 character (verified: Codex runs warmer/lighter), and mixing sources across
178 scenes reads as style drift, same reason the video chain uses one model.
179 - **Calibrate costs, don't guess.** The CLI exposes no pricing and plans differ.
180 Run ONE still and ONE video first, diff `higgsfield workspace list` before/
181 after, extrapolate to the full run, and warn the user whenever the estimate
182 exceeds ~70% of the balance. (Observed on a plus plan, 2026-07: Standard
183 video ≈ 40–55 credits, still ≈ 15.) A real `not_enough_credits` mid-run is
184 recoverable (finished clips survive; resume after top-up) but ugly — the
185 whole point of this step is that the user decides *before* the spend.
186
187If the user names a video model outside the roster, honor it **only if it can
188frame-lock seams** (Step 4). This skill only ships seamless output, so a model that
189can't frame-lock is declined with a one-line why, not substituted in — use a roster
190model instead.
191
192Keep the scroll mechanic fixed (continuous fly-through) — that's the point of the skill.
193See `references/prompts.md` for the intake checklist and copy structure.
194
195---
196
197## Step 2 — Generate the scene stills
198
199One image per section, **all sharing the same style preamble** for cohesion. Default
200model **`gpt_image_2`** (crisp, great at isometric illustration; returns a solid/white
201background which is perfect for floating diorama "islands"). Use `nano_banana_2` only if
202the brief is character/cartoon-heavy (note: `nano_banana_2` is a CLI alias — it resolves
203to `nano_banana_pro`; it won't appear under that name in `higgsfield model list`).
204
205Prompt shape (full templates in `references/prompts.md`):
206
207```
208<STYLE PREAMBLE, identical every time>. On a plain solid <bg> background with a soft
209contact shadow. <PALETTE hexes>. No text, no letters, no logos, centered, 3:2.
210Subject: <what is in THIS diorama>.
211```
212
213- Run all N concurrently, detached. Command per scene:
214 `higgsfield generate create gpt_image_2 --prompt "$(cat scene_i.txt)" --aspect_ratio 3:2 --resolution 2k --quality high --wait --wait-timeout 15m --json > scene_i.json 2>scene_i.err`
215- Result URL is `.[]0.result_url` in the `--wait --json` output. `curl` it down.
216- **Codex stills variant** (if chosen at Step 1.7 — subscription-billed, zero
217 credits): same prompt files, same byte-identical preamble, generated by Codex's
218 built-in `image_gen`:
219
220 ```bash
221 codex exec -C "$WORK" -s workspace-write --skip-git-repo-check \
222 'Use the image generation tool ($imagegen) to generate: '"$(cat "$WORK/still_i.txt")"' Wide 3:2 landscape, high resolution. Save it as ./still_i.png. Do not do anything else.' \
223 < /dev/null
224 ```
225
226 Single-quote the `$imagegen` segment (the shell must not expand it); if editing
227 with reference images, the prompt goes BEFORE any `-i` flag (it's variadic).
228 ~1–3 min per image; run a few in parallel, not all N at once — and keep the
229 `< /dev/null`: parallel `codex exec` calls sharing a script's stdin hang
230 waiting for input (Gotchas). Output lands at
231 1536×1024 (3:2) — fine for `--start-image` and posters. Everything downstream
232 (cohesion review, knockout, dives) is unchanged.
233- A generation may fail transiently (HTTP 503) — re-roll that one individually; don't
234 restart the batch.
235- **Review the stills before continuing.** They must read as one cohesive world (same
236 angle, palette, light). If one is off-style, regenerate it, optionally passing an
237 approved scene as `--image` to lock style.
238
239See `references/pipeline.md` for the exact batch script.
240
241---
242
243## Step 3 — (Optional) Float the scenes
244
245If you want the dioramas to float over an atmospheric background instead of sitting in a
246solid box, knock out the flat background to transparency with
247`references/knockout.py` (border-connected flood fill — preserves interior colour that
248matches the bg, e.g. cream walls). Then encode to webp. If you'd rather keep it simple,
249just make the page background the same colour as the scene background and skip this.
250
251These stills double as **video posters and lazy-load fallbacks**, so keep them.
252
253---
254
255## Step 4 — Camera architecture (implements the Step 1.4 choice)
256
257How the camera moves *between* scenes is the single biggest quality lever. The user
258already chose the style at the interview (`CAMERA`, Step 1.4): fly-through → **B**,
259walkthrough → **A**, locked-iso → **A + the locked-iso leg clause** (prompts.md). If
260the interview somehow skipped it, ask now — never silently pick for them. The two
261shapes, and the grammar that colors them:
262
263### Video model — pick ONE for the whole chain
264
265**This skill only ships seamless output**, so the only usable models are ones that can
266frame-lock a seam: every chained clip must accept `--start-image`, and connectors also
267need `--end-image`. That capability — not preference — is the selection rule. Check any
268model with `higgsfield model get <job_type>` and **skip anything whose media inputs are
269reference-only** (no start/end image): it can only *condition* a generation, not
270*continue* a shot, so it physically can't hold a seam. Schemas below were confirmed
271against the CLI:
272
273| Model | start/end image | Notes |
274|---|---|---|
275| `seedance_2_0` (default) | ✓ / ✓ | Full chain (legs + connectors). `--mode std --resolution 1080p`. Its NSFW filter is the touchy one (see Gotchas). |
276| `kling3_0` | ✓ / ✓ | Full chain — tested: `--mode std --sound off --duration 5` with start+end images accepted, seams frame-lock cleanly. **No `--resolution` param** (don't pass one; `--mode std` returns **720p native** — encode what ffprobe reports, never upscale). Sound defaults **on** → `--sound off`. `--duration` default 5, try 10 for legs. Different content filter than Seedance — the sanctioned NSFW fallback. |
277| `seedance_2_0_mini` | ✓ / ✓ | Cheap draft tier that keeps frame-locking (720p). The previz tier: run the whole chain here first, then re-render final legs on the full model — still seamless, so it translates directly. |
278
279Those three are the roster — all do both architectures. (`kling3_0_turbo` also frame-locks
280via `--start-image`, but has no `--end-image`, so it's architecture-A-only and can't make
281connectors; it also takes a different flag set — no `--mode`, has `--resolution` — so it
282doesn't drop into the pipeline as-is. It's not in the default roster; only reach for it, and
283wire it by hand, if architecture A's sequential render time is a proven bottleneck and you've
284benchmarked it as actually faster.)
285
286One more architecture-A-only candidate, worth knowing because it is by far the cheapest
287probe: **`minimax_hailuo`** (Hailuo-2.3, ~6 credits per 768p/6s clip vs 22–72 for the
288roster). Verified 2026-07: `--start-image` + prompt frame-locks (output frame 0 ≡ input,
289PSNR 33 dB) and a forward-glide prompt was obeyed, gently. Constraints: the 2.3 variant
290rejects `end_image` (no connectors → arch A only), output aspect follows the input image
291(hand it a 16:9 canvas, not a bare 3:2 still), motion runs subtler than seedance, and
292don't pass `--resolution` (the CLI mis-types the enum; the 768 default works — 1080
293supports 6s only). One clip ≠ a chain: qualify a leg-to-leg handoff before betting a
294full build on it.
295
296Rules:
297- **One model for all chained clips.** Each renderer has its own motion/color/grain
298 character; mixing models mid-chain keeps *position* continuity (frames still hand off)
299 but the render-character shift reads as a subtle pop. The one sanctioned exception is
300 the NSFW fallback for a single stubborn clip (Gotchas) — a slight character shift on
301 one 5s connector beats a missing connector.
302- Default to `seedance_2_0`, rendered through **Monid by default** (per-clip USD —
303 next section) with Higgsfield credits as the fallback biller (Step 0.1/1.6); honor
304 a user's stated preference **only if the model qualifies** (frame-locking). If it
305 doesn't, say so and use a supported model — never ship a non-seamless build to
306 satisfy a model request. `kling3_0` and `seedance_2_0_mini` exist only on the
307 Higgsfield side.
308- The pipeline scripts take the model as `$VMODEL` with per-model flags already cased
309 out (`references/pipeline.md`).
310
311### Monid backend — the DEFAULT chain biller (qualified 2026-07-25)
312
313Monid's **`bytedance /v1/video/seedance-2.0`** passed both paid probes on
3142026-07-25 and is the **default** way this skill renders the chain, for **both
315architectures** — it is the roster's `seedance_2_0` served pay-per-USD (wiring in
316pipeline.md → "Monid backend"; Higgsfield renders the chain only as the fallback
317biller or for Higgsfield-only models):
318
319- **Leg probe** (prompt + `first_frame` image): output frame 0 ≡ input still
320 (PSNR 31.6 dB), forward-glide prompt obeyed, billed the advertised cell
321 ($0.279 / 480p 4s).
322- **Connector probe** (prompt + `first_frame` + `last_frame`): start locked
323 (31.6 dB); the end **lands close but not pixel-perfect** (27.5 dB, same
324 composition, prop-level drift) — the exact end-image behavior Seedance shows
325 on Higgsfield, covered by the engine's seam crossfade and by using the next
326 dive's ACTUAL first frame as the end-image (Step 5 law, unchanged).
327
328The I/O contract differs from the Higgsfield CLI — three rules:
329
3301. **Images go by URL, never inline.** `content` items are
331 `{"type":"image_url","image_url":{"url":…},"role":"first_frame"|"last_frame"}`;
332 base64 data URLs are **rejected** ("Must be a public https:// URL or an
333 asset://<id> reference"). Local frames travel through Monid's free workspace
334 file system: `sfs /put` → `curl -T` the bytes → `sfs /cat` returns a signed
335 public URL to paste into the body ($0, explicitly built for this).
3362. **Pass `ratio` explicitly** (`16:9`, or `9:16` for the mobile chain) — the
337 adaptive default follows the input image's aspect instead.
3383. **Bill-check every clip**: cost is token-priced
339 (`w × h × 24 × sec / 1024` at $7–7.7/1M); read `cost.value` off each run.
340
341History that shaped these rules (still true as of 2026-07-25): the seedance
342endpoints were text-to-video-only until late July 2026 — **re-`inspect` before
343each build; the catalog moves in both directions.** `minimax
344/v1/video_generation` (Hailuo-2.3) remains disqualified: sending `prompt` +
345`first_frame_image` together silently drops the image (unrelated t2v output,
346wrong price cell); image-only frame-locks (33 dB) but has no camera control.
347
348**Qualification protocol for any new/changed Monid endpoint** (each probe is one
349cheap 480p clip): (1) prompt + first-frame from a real still — frame 0 must
350match the input to codec noise (PSNR ≳ 30 dB) and `cost.value` must match the
351advertised cell; (2) for connector duty, add a `last_frame` from a different
352still — the end must land on that composition (Seedance-style near-miss is fine,
353the crossfade covers it). Pass → pay-per-clip tier (arch A if start-only; full
354roster if start+end).
355
356### A) Continuous forward take — RECOMMENDED for grounded / realistic / walkthrough
357One camera that only ever glides **forward**, first scene through last, as a single take.
358Generate the legs **sequentially**: leg 0 from scene-0's still (glide forward into it);
359then each leg's `--start-image` = the **previous leg's ACTUAL last frame** (extract with
360ffmpeg), prompt *"continue gliding smoothly FORWARD into [scene i], never pulling back"*
361(or an expressive mid-leg move under the motion-handoff contract — see **Camera grammar**
362below), and **no `--end-image`** — an end-image of a wide establishing shot forces the
363camera to pull back, which is the #1 cause of stutter. Extract each leg's last frame to feed the
364next. Result: every seam is frame-identical **and** the camera never reverses. There are
365**no connectors** (skip Step 5) — the legs ARE the journey. Wire each leg as a section
366clip with `connectors: []` and a small `crossfade` (~0.08). Even without an `--end-image`
367the legs still arrive at distinct rooms (the prompt steers the content). Cost: strictly
368**sequential** (can't parallelize) and slower; interiors trip the NSFW filter, so build in
369re-rolls (3 attempts/leg).
370
371### B) Dive-in + aerial connector — only for diorama / miniature / god's-eye worlds
372A "dive into each scene" clip + a connector that pulls **up and out** and flies over to the
373next scene (Step 5). The pull-out **reverses camera direction at every seam** (forward dive
374→ backward pull-out). In a miniature/diorama world that reads as an intentional "zoom out
375to the map, fly to the next island"; in a grounded first-person walkthrough it reads as a
376jarring **rewind/stutter**. Use B only for the map-like aesthetic — which is exactly
377what the "fly through the world" interview answer opts into; the reversal reads as
378intentional there. If the user picked B against a grounded/photoreal direction, say
379why it will read as a stutter and confirm before rendering.
380
381### Camera grammar — the move should fit the concept (A is NOT "forward only")
382
383"Forward only" is the *seam* rule, not the *leg* rule. The physics of the chain:
384
385- **Position continuity** at a seam comes from the frame handoff (next leg starts from the
386 previous leg's actual last frame).
387- **Velocity continuity** at a seam means the camera must never *reverse across a seam* —
388 that's the rewind stutter.
389- **Inside a single leg the camera is free.** One leg is one continuous render — there is
390 no seam to break mid-leg, so orbits, crane-ups, lateral tracking, even a push-in that
391 eases back out are all safe *within* the clip. Reversals are only fatal *across* seams.
392
393So give each leg an expressive move chosen from the scene's own logic, under a **motion
394handoff contract**: every leg **ends by settling into a slow, steady forward drift** toward
395the next destination (final ~1 s), and every leg **begins by continuing that same drift**.
396Keep both clauses in the prompts verbatim (templates in `references/prompts.md`).
397
398Pick the grammar from the concept:
399
400| Concept / tone | Mid-leg move |
401|---|---|
402| Product / luxury retail | slow half-orbit around the hero object, then continue past it |
403| Real estate / hospitality | steadicam glide through doorways; gentle crane-up in atria |
404| Industrial / process / logistics | low lateral track alongside the line, foreground parallax |
405| Travel / outdoors / campus | drone-style rise-and-reveal, then a descending swoop |
406| Food / craft / detail-driven | push in close to the craft moment, ease back, carry on |
407| Playful miniature (arch. B) | dives + aerial hops — the connector IS the grammar |
408
409Honest costs: expressive mid-leg moves raise re-roll odds — the model can end a fancy move
410in a state that isn't a clean forward drift. Mitigations: keep the final-second settle
411clause verbatim; **eyeball each leg's last frame before chaining the next** (it should look
412like a frame from a gentle forward glide — if not, re-roll before wasting the next leg);
413budget ~1 extra re-roll per expressive leg. A plain forward glide stays the zero-risk
414default — use it for legs where the scene itself is the show.
415
416**Locked-iso variant** (`CAMERA` = locked isometric glide): architecture A where every
417leg pins the view instead of taking a mid-leg move — "the camera keeps exactly the same
418high isometric angle throughout, no rotation, no orbit, no tilt; it only travels
419straight and level, the world sliding past beneath the same view" (verbatim clause in
420prompts.md). The handoff contract is unchanged. Seedance drifts the angle slightly on
421long legs — the existing eyeball-each-last-frame rule is the catch; re-roll a leg whose
422view has rotated. Calmest look, cheapest re-rolls, and the closest to the Emons
423reference.
424
425Two related pacing knobs live in the engine (Step 7): per-section `scroll` (more scroll
426distance = longer dwell in that scene) and `linger` (the camera settles mid-scene exactly
427while the copy peaks, then picks up speed toward the seam). Prefer expressive motion in the
428*clip* and restraint in the *scrub mapping* — they compound.
429
430And remember scroll is a scrubber: visitors can scroll **up**, so every move also plays in
431reverse. That's free and expected — no extra work — but it's another reason seam velocity
432must be consistent in both directions (a seam that reads fine forward reads as a stutter
433backward too if velocity flips).
434
435**For B**, one camera flight per scene: starts high/outside, descends into the interior,
436structure opens. Model: the chain model you picked above (default **`seedance_2_0`**),
437`--start-image = the scene still`.
438
439- Use the **solid-background still** (not the knocked-out transparent one) as the
440 start image, so the video has a full frame.
441- Prompt: "Single continuous cinematic camera move, no cuts. Begin high and far looking
442 at the whole <scene> from outside … descend and fly inside toward <focal point> … the
443 roof/walls gently open to reveal the interior. <style>, smooth graceful slow motion.
444 No text." (Template in `references/prompts.md`.)
445- Params (seedance): `--mode std --resolution 1080p --aspect_ratio 16:9 --duration 8`.
446 For Kling: drop `--resolution` (no such param), add `--sound off`, `--duration 10`.
447 Do **not** pass `--generate-audio` (it errors on seedance; audio is wasted anyway —
448 you'll mute).
449- Run concurrently, detached, then download each `.result_url`. Re-roll individual
450 failures. Keep the raw 1080p sources — you need their frames next.
451
452---
453
454## Step 5 — Connectors (architecture B only)
455
456Skip this whole step for architecture **A** — the forward take has no connectors; its legs
457already chain seamlessly. This step applies to **B** (diorama/miniature), and note the
458reversal caveat from Step 4.
459
460The connector clips are what make the world feel *connected* instead of cut. A connector
461flies from the end of scene i out and into the start of scene i+1. **Both of its
462endpoints must be the ACTUAL RENDERED FRAMES of the neighbouring clips — never the
463original diorama still.**
464
465Why: every Higgsfield generation renders slightly differently. If a connector *ends* on
466a fresh render of "the kitchen diorama," but the next dive clip *starts* on its own
467different render of that same diorama, the two won't match and you get a pop at the seam.
468The fix is to hand off the exact pixels:
469
470```
471For each connector between dive_i and dive_{i+1}:
472 start-image = the LAST frame extracted from dive_i's rendered video
473 end-image = the FIRST frame extracted from dive_{i+1}'s rendered video
474```
475
476Now every seam is frame-identical on *both* sides:
477`dive_i.end == connector.start` and `connector.end == dive_{i+1}.start`.
478
479Extract the boundary frames from the rendered dives (not the stills):
480
481```bash
482ffmpeg -sseof -0.15 -i dive_i.mp4 -frames:v 1 -q:v 2 dive_i_last.png # interior of i
483ffmpeg -ss 0 -i dive_{i+1}.mp4 -frames:v 1 -q:v 2 dive_next_first.png # establishing of i+1
484```
485
486Generate the connector (`--duration 5` is plenty). Connectors need `--end-image`, so the
487model must accept it — any roster model does (`seedance_2_0`, `seedance_2_0_mini`,
488`kling3_0`):
489
490```bash
491higgsfield generate create "$VMODEL" \
492 --prompt "$(cat connector_i.txt)" \
493 --start-image dive_i_last.png --end-image dive_next_first.png \
494 $VOPTS --aspect_ratio 16:9 --duration 5 --wait --json
495# seedance: VOPTS="--mode std --resolution 1080p"; kling3_0: VOPTS="--mode std --sound off"
496```
497
498Connector prompt: "Single continuous camera move, no cuts. Pull up and back out of
499<scene i>, rise into the sky, glide across the connected miniature world, and arrive
500above <scene i+1>, beginning to descend toward it. Seamless flowing aerial transition.
501<style>. No text." (Template in `references/prompts.md`.)
502
503Insurance: Seedance lands *close* to the end-image but not always pixel-perfect, so the
504engine still applies a **short crossfade** (a few frames) at each seam. Frame-matched
505endpoints + a small crossfade = no visible cut. Never skip the actual-frame handoff and
506rely on the crossfade alone; a big content jump can't be hidden by a crossfade.
507
508---
509
510## Step 6 — Encode for smooth scrubbing
511
512Scrubbing = setting `video.currentTime` from scroll. Two things matter, and they are
513often gotten wrong:
514
5151. **Seekability, not keyframe density, is what makes scrubbing work.** Many static
516 hosts (and `python -m http.server`) don't serve HTTP byte-range requests, which pins
517 `video.seekable` to `[0,0]` and clamps *every* seek to frame 0 — the video looks
518 frozen. The robust fix is to **fetch each clip as a `Blob` and play it from an
519 in-memory object URL** (blobs are always fully seekable). The engine does this.
520 Because of it, you do **not** need all-intra video.
5212. **Don't shrink quality to get smooth seeks.** Encode at the **native resolution**
522 (1080p from Seedance — don't downscale), `crf ~20`, a **small GOP** (`-g 8`) rather
523 than all-intra (all-intra bloats an 8s clip to ~25 MB; GOP 8 is ~8 MB and scrubs
524 fine via blob). Strip audio, add faststart, and a light `unsharp` counters video
525 softness:
526
527```bash
528ffmpeg -i src.mp4 -an -vf "unsharp=5:5:0.8:5:5:0.0" \
529 -c:v libx264 -preset slow -crf 20 -pix_fmt yuv420p \
530 -g 8 -keyint_min 8 -sc_threshold 0 -movflags +faststart out.mp4
531```
532
533Encode all 2N-1 clips (dives + connectors) with the same settings for uniform quality.
534
535**Mobile encodes (only if the user opted in at Step 1.6).** The mobile version is
536the **native 9:16 portrait chain** (pipeline.md §6b): portrait renders of every dive and
537connector, encoded **720 wide (`scale=720:-2`), `-g 4`** (more keyframes = cheaper seeks —
538phone decoders' seek cost scales with GOP length), crf 23 — wired as `clipMobile` /
539`connectorsMobile`, with each portrait dive's first frame extracted as the section's
540`stillMobile` poster (Step 7). The engine serves them automatically on phones and falls
541back to the desktop clip when absent. The 16:9 centre-crop `encm()` encodes
542(pipeline.md §6) are a **fallback only** — for when credits can't cover the portrait
543chain — and shipping them must be called out to the user, never silent. If the user chose
544desktop-only, skip this — the engine still hardens phone scrubbing regardless
545(seek-coalescing, iOS priming), so the page degrades gracefully rather than breaking.
546
547---
548
549## Step 7 — Assemble the page
550
551Copy `references/scrub-engine.js` (and, if you want a fully standalone page, the tiny
552`references/index-template.html`) into the user's project — or adapt into their
553framework. It's config-driven and self-contained:
554
555```js
556mountScrollWorld(document.getElementById('world'), {
557 brand: { name: 'Pearl & Co.' },
558 diveScroll: 1.3, connScroll: 0.9, // viewport-heights of scroll per clip
559 sections: [
560 { id:'farm', label:'The Farms', still:'assets/farm.webp',
561 clip:'assets/vid/farm.mp4',
562 clipMobile:'assets/vid/farm-m.mp4', // mobile opt-in only: native 9:16 render
563 stillMobile:'assets/farm-m.webp', // its first frame as the portrait poster
564 scroll: 1.6, linger: 0.45, // optional pacing: longer dwell + camera settles mid-scene
565 accent:'#8FB98A', eyebrow:'From leaf to last sip', title:'It starts in the hills.',
566 body:'…', tags:['Single-origin','Hand-picked'] },
567 // …one per section; last may carry a `cta`
568 ],
569 connectors: ['assets/vid/conn1.mp4','assets/vid/conn2.mp4', /* … length = sections-1 */],
570 connectorsMobile: ['assets/vid/conn1-m.mp4','assets/vid/conn2-m.mp4' /* … same length; mobile opt-in only */],
571});
572```
573
574The engine handles: the ordered dive/connector chain, scroll→currentTime with rAF
575smoothing, blob loading, lazy prefetch of nearby clips, frame-matched crossfades, pinned
576per-section copy (first section greets on landing, last holds its CTA), a route rail,
577`prefers-reduced-motion`, and mobile. **Pacing per section:** `scroll` overrides
578`diveScroll` for that scene (more scroll = longer dwell) and `linger` (0–1, keep ≤ 0.6)
579remaps time so the camera settles mid-scene — exactly while the copy peaks — then speeds
580up toward the seam; seam frames are untouched (f(0)=0, f(1)=1). Give the hero and finale
581scenes a higher `scroll` + some `linger`; keep transit scenes brisk. Theme it with CSS variables (`--accent`,
582`--sw-bg`, `--sw-ink`, …) — the visual identity comes from the generated clips, so the
583chrome stays quiet. See the header of `scrub-engine.js` for the full config + CSS vars.
584
585**On phones the engine adapts automatically** (coarse pointer or ≤860px): it serves
586`clipMobile` / `connectorsMobile` when present, **coalesces seeks** (never queues a new
587`currentTime` while the decoder is still seeking — this is what stops a fast flick from
588freezing the clip), **keeps the still as a poster until the clip paints its first frame**
589and **primes each video on first touch** (fixes iOS's blank-until-played video), drops the
590drifting particles, ignores URL-bar-only resizes (no scroll jump), and uses safe-area
591insets so copy clears the notch/home indicator. All of this hardening is on by default —
592no config needed. The `clipMobile`/`connectorsMobile` encodes are the opt-in part
593(Step 1.6): only wire them when the user asked for the mobile version.
594
595For non-JS backends (Python/Rails/etc.): serve the assets and drop the engine `<script>`
596into the rendered HTML; nothing about it is framework-specific.
597
598---
599
600## Step 8 — QA the seams (don't skip)
601
602Drive the page in a headless browser and **verify frame continuity at the seams**, which
603is the thing most likely to be wrong:
604
605- Screenshot at scroll positions just before and just after each seam. The two frames
606 must be near-identical (the dive's last frame == the connector's first frame). If they
607 pop, you used the diorama still instead of the actual rendered frame (redo Step 5), or
608 the crossfade band is too short. Calibration: judge seams by *composition*, not raw
609 PSNR — at 720p/1080p a correctly frame-locked seam can read ~18–25 dB from detail
610 shimmer alone (observed on a verified-good build); a real mismatch shows as different
611 composition/props, not just softness.
612- Check the console for errors, confirm `video.seekable.end(0) > 0` (blob working), and
613 that `currentTime` tracks scroll across each clip's band.
614- **Mobile — full checklist only if the user opted into the mobile version (Step 1.6).**
615 For a desktop-only build, just sanity-check a phone viewport once: page loads, still
616 posters show, nothing overlaps — the engine's hardening covers graceful degradation.
617 For the mobile build (do this on a real phone or an emulated one, portrait + landscape):
618 - Emulate a phone viewport **with CPU throttled 4–6×** and scroll fast — the clip should
619 track without freezing (the seek-coalescing + `-m.mp4` encodes are what make this hold).
620 - Confirm the first scene shows immediately (its still is the poster) and the video takes
621 over the instant you scroll — no blank/black scene (the iOS priming fix). Test iOS Safari
622 specifically; it's the one that goes blank if this regresses.
623 - Verify the `-m.mp4` variant is actually served on mobile (Network panel), and the
624 heavy 1080p master on desktop. The mobile clips must be **natively portrait**
625 (`videoWidth < videoHeight` — not a downscaled 16:9 file), and the `stillMobile`
626 posters must be served and match each portrait clip's first frame (no
627 landscape→portrait flash when the video paints).
628 - Slowly scroll so the URL bar collapses — the page must **not jump** (height-only resizes
629 are ignored on touch). Rotate the device — layout should recompose cleanly.
630 - Only if the crop **fallback** shipped (no credits for the portrait chain): portrait
631 crops a 16:9 clip to its centre — confirm the focal subject still reads, and remind
632 the user this is the stopgap, not the mobile version.
633- Check reduced-motion (should fall back to the stills, no video, no particles).
634
635---
636
637## Gotchas (hard-won)
638
639- **Seam pop** → connector endpoints were the diorama stills, not the neighbouring
640 clips' actual frames. Always extract real frames (Step 5).
641- **Seam stutter / camera "jumps backward"** → even with frame-matched seams, if the
642 camera *velocity reverses* (forward dive, then a connector that pulls back out) it
643 reads as a rewind. This is inherent to architecture B. For any grounded walkthrough use
644 architecture A (one continuous forward take — legs chained from actual last frames, no
645 pull-back, no `--end-image`); see Step 4.
646- **Frozen video / stuck at frame 0** → `seekable=[0,0]`; the host isn't serving byte
647 ranges. Use blob URLs (engine does).
648- **Huge files** → you used all-intra. Use `-g 8` + blob instead.
649- **Soft / low quality
650
651…(truncated)