Scroll World Landing Page
oso95/scroll-world
Builds a scroll-driven landing page where a pre-rendered camera flies through connected AI-generated scenes, using Higgsfield for stills and video clips.
Build an immersive scroll-scrubbed "fly through the world" landing page for any industry or brand using Higgsfield.
$ npx skills add AIwithhassan/lets-scroll --skill lets-scroll -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install AIwithhassan/lets-scroll lets-scroll --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/AIwithhassan/lets-scroll.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/lets-scroll .claude/skills/lets-scroll && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "lets-scroll" agent skill from https://github.com/AIwithhassan/lets-scroll/tree/main/skills/lets-scroll into .claude/skills/lets-scroll/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "lets-scroll", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/AIwithhassan/lets-scroll/tree/main/skills/lets-scrollType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add AIwithhassan/lets-scroll --skill lets-scroll -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install AIwithhassan/lets-scroll lets-scroll --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/AIwithhassan/lets-scroll.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/lets-scroll .agents/skills/lets-scroll && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "lets-scroll" agent skill from https://github.com/AIwithhassan/lets-scroll/tree/main/skills/lets-scroll into .agents/skills/lets-scroll/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "lets-scroll", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add AIwithhassan/lets-scroll --skill lets-scroll -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install AIwithhassan/lets-scroll lets-scroll --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/AIwithhassan/lets-scroll.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/lets-scroll .cursor/skills/lets-scroll && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "lets-scroll" agent skill from https://github.com/AIwithhassan/lets-scroll/tree/main/skills/lets-scroll into .cursor/skills/lets-scroll/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "lets-scroll", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/AIwithhassan/lets-scroll.git --path skills/lets-scroll--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add AIwithhassan/lets-scroll --skill lets-scroll -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install AIwithhassan/lets-scroll lets-scroll --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/AIwithhassan/lets-scroll.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/lets-scroll .gemini/skills/lets-scroll && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "lets-scroll" agent skill from https://github.com/AIwithhassan/lets-scroll/tree/main/skills/lets-scroll into .gemini/skills/lets-scroll/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "lets-scroll", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install AIwithhassan/lets-scroll lets-scrollInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add AIwithhassan/lets-scroll --skill lets-scroll -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/AIwithhassan/lets-scroll.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/lets-scroll .github/skills/lets-scroll && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "lets-scroll" agent skill from https://github.com/AIwithhassan/lets-scroll/tree/main/skills/lets-scroll into .github/skills/lets-scroll/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "lets-scroll", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add AIwithhassan/lets-scroll --skill lets-scroll -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install AIwithhassan/lets-scroll lets-scroll --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/AIwithhassan/lets-scroll.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/lets-scroll .opencode/skills/lets-scroll && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "lets-scroll" agent skill from https://github.com/AIwithhassan/lets-scroll/tree/main/skills/lets-scroll into .opencode/skills/lets-scroll/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "lets-scroll", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
lets-scrollBuild an immersive scroll-scrubbed "fly through the world" landing page for any industry or brand using Higgsfield.
Lets Scroll is an agent skill from AIwithhassan/lets-scroll. 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…
Its SKILL.md is about 14k tokens, which your agent loads only when the skill is triggered. The skill folder holds 6 other files, including reference files (for example `references/knockout.py`, `references/pipeline.md` and `references/prompts.md`).
It sits in Frontend & Design, covering Landing pages, AI video generation and Brand strategy and identity. It works with Seedance. The repository describes itself as: Agent skill that builds scroll-scrubbed "fly through the world" landing pages, AI-generated scenes + camera flights chained into one seamless continuous shot, driven by scroll… The licence is MIT.
9 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit a060536. It shows what the files ask for, not the result of running them.
Pre-approves these tools, so the agent can use them without asking each time:
BashReadWriteEditAskUserQuestionSkillFrom allowed-tools in the SKILL.md frontmatter.
Ships script files (Python and JavaScript), which the agent can run.
Shell commands in SKILL.md call:
codexffmpegcurlpython3pythonbashFrom the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md. Its commands use curl, which can reach the network depending on how they are called.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Lets Scroll loads about 14k tokens when it runs, and up to ~30k if it reads all its reference files. Until then it costs about 249 tokens; SKILL.md has 7,695 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
allowed-tools: Bash, Read, Write, Edit, AskUserQuestion, SkillAutomated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from AIwithhassan/lets-scroll at commit a060536, republished under its MIT licence (© AIwithhassan). 7,695 words, ~14,156 tokens.
.claude/skills/lets-scroll/SKILL.md (or your agent's skills folder). This skill also uses 5 other files; get the full folder from GitHub.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. A manual asset path (Step 1.7) swaps the render calls for a prompt + file handoff: the user generates every still and clip in tools of their choice and drops the files into the work folder; everything downstream (frame extraction, encode, engine, QA) is identical. 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.
monid --version,
monid keys list (active key) and monid balance — the chain is billed per
clip in USD (Step 1.8 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).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.$PATH (frame extraction + encoding).python3 -c "import PIL"), or cwebp/sips. Optional — see Step 3.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.8, command in Step 2. Absence just removes the option.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.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:
higgsfield marketing-studio brand-kits fetch --url <site> --wait
(pulls name, colours, tone). Then read it back with brand-kits list --json.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:
The journey (sections) — start with the LEVEL OF ANIMATION; always ask it as a
structured choice (AskUserQuestion in Claude Code; a plain question elsewhere).
Three sizes, 6 is the maximum — record the count as N:
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:
-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.Asset source — automatic or manual. ALWAYS ask; it decides who renders.
Two options (AskUserQuestion in Claude Code; a plain either/or elsewhere):
"How do you want to produce the stills and clips — should I generate them, or
do you want the prompts to render in tools of your choice?" Record as
ASSET_SOURCE.
gpt_image_2 (or Codex image_gen), the video chain via Monid
with Higgsfield as fallback biller. Item 8 prices this path.still_<name>.txt at
Step 2, dive_<name>.txt at Step 4, conn_<i>.txt at Step 5) plus the exact
conditioning frames each clip must start/end on; the user copy-pastes the
prompts into tools of their choice (any image tool for the stills; a
start/end-frame-capable video tool for the clips) and drops the finished
files into $WORK. The skill validates what comes back (count, dimensions,
aspect, duration, seam frames) and continues the same downstream pipeline —
only the render calls change. State the one hard requirement up front: the
seamless rule still applies, so their video tool must accept a
first/start frame (both architectures) and, for architecture B connectors, a
last/end frame — if it can't, steer CAMERA to architecture A or have them
pick a tool that can (Steps 4–5).Budget — engines shown by cost, decided before anything renders. On the
manual path (item 7) there is nothing to bill here — the spend happens in
the user's own tools — so skip the tier/backend picks below; just state the
workload they're signing up to produce (N stills + (2N−1) clips [×2 if mobile], plus re-roll headroom) and get a go. On the automatic path:
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.
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 — already carries the solid <bg> + palette>.
Render a wide 3:2 landscape image (≥1536 px). The background stays plain solid <bg>
across the whole frame — empty backdrop: no sky, no clouds, no horizon, no gradient.
Centered composition, nothing essential at the far edges. No text, no letters, no logos.
Subject: <what is in THIS diorama>.Self-contained on purpose: aspect, background lock and palette live in the prompt text,
because on the manual path each still may be rendered in a different tool or session
(full template + rationale in references/prompts.md).
Manual stills path (ASSET_SOURCE = manual, Step 1.7) — skip the generate
commands below. Write the same prompt files ($WORK/still_<name>.txt, identical
preamble, from the prompts.md templates) and hand them over with the spec: one
image per file, 3:2 landscape, ≥1536 px wide, solid <bg> background, no
text — any image tool the user likes. They save the results as
$WORK/still_<name>.png and tell you when the set is in. Validate before
continuing: all N present, each opens, aspect ≈ 3:2 (a few % tolerance), width
≥ ~1200 px (ffprobe or PIL). Then run the same cohesion review as below;
off-style stills go back to the user for a re-roll (suggest re-running the same
prompt with an approved sibling as style reference). The one-source rule applies
here too — don't mix manual stills with CLI-rendered ones in one build.
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.8 — 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/nullSingle-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.
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.
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:
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:
seedance_2_0, rendered through Monid by default (per-clip USD —
next section) with Higgsfield credits as the fallback biller (Step 0.1/1.8); 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.$VMODEL with per-model flags already cased
out (references/pipeline.md).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):
first_frame image): output frame 0 ≡ input still
(PSNR 31.6 dB), forward-glide prompt obeyed, billed the advertised cell
($0.279 / 480p 4s).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:
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://<id> 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).ratio explicitly (16:9, or 9:16 for the mobile chain) — the
adaptive default follows the input image's aspect instead.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).
ASSET_SOURCE = manual — Step 1.7)Skip the model roster and the Monid backend above; the architecture choice (A vs
B) still stands — it decides which clips exist and which frames chain them. The
frame-lock rule is unchanged, so confirm the user's video tool accepts a
first/start frame (both architectures) and, for B's connectors, a last/end
frame. If it can't take an end frame, either re-confirm CAMERA as
architecture A with the user (no connectors) or have them use a tool that can
(e.g. Kling's start/end-frame mode) — never ship unseamed connectors. Per clip:
$WORK/dive_<name>.txt /
conn_<i>.txt) and hand it over with the conditioning image(s) it assumes:
arch A leg i → the previous leg's actual last frame; B dive → the scene's
solid-bg still; B connector → the extracted last_/first_ pair (Step 5).$WORK/dive_<name>.mp4 / $WORK/conn_<i>.mp4.Arch A stays logically sequential even though rendering is manual — each leg's start frame is extracted from the previous leg's returned file. Everything downstream (boundary-frame extraction, encode, engine, QA) is identical to the automatic path.
Every manual handoff — stills, dives, connectors — is ALWAYS presented as a spec table, never a prose list: prompt file, conditioning frame(s), the exact output filename, and a status column you keep current (pending / rendered / accepted) as files arrive and pass validation. The user works through it offline, in any order, and the table is the contract for what you're still waiting on. The connector table in Step 5 is the canonical example.
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).
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.
"Forward only" is the seam rule, not the leg rule. The physics of the chain:
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.
<scene> from outside … descend and fly inside toward <focal point> … the
roof/walls gently open to reveal the interior. <style>, smooth graceful slow motion.
No text." (Template in references/prompts.md.)--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)..result_url. Re-roll individual
failures. Keep the raw 1080p sources — you need their frames next.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 videoNow 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+1Generate 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
<scene i>, 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.
<style>. No text." (Template in `references/prompts.md`.)
Insurance: Seedance lands *close* to the end-image but not always pixel-perfect, so the
engine still applies a **short crossfade** (a few frames) at each seam. Frame-matched
endpoints + a small crossfade = no visible cut. Never skip the actual-frame handoff and
rely on the crossfade alone; a big content jump can't be hidden by a crossfade.
**Manual path (Step 1.7):** the frame extraction above is yours to run either way —
hand the user `conn_<i>.txt` plus the two extracted PNGs and wait for
`$WORK/conn_<i>.mp4`. Accept it only if frame 0 matches `last_<prev>.png` and the
final frame lands on the `first_<next>.png` composition (a Seedance-style near-miss
is fine — the crossfade covers it).
The handoff is ALWAYS a spec table, one row per connector, filled with this build's
real extracted frames:
| Prompt file | Start frame | End frame | Save as | Status |
|---|---|---|---|---|
| `conn_1.txt` | `last_<scene1>.png` | `first_<scene2>.png` | `$WORK/conn_1.mp4` | pending |
| `conn_2.txt` | `last_<scene2>.png` | `first_<scene3>.png` | `$WORK/conn_2.mp4` | pending |
| … | … | … | … | … |
State the acceptance rule with it: the start frame must be obeyed exactly; the end
frame only needs to land on the same composition.
---
## Step 6 — Encode for smooth scrubbing
Scrubbing = setting `video.currentTime` from scroll. Two things matter, and they are
often gotten wrong:
1. **Seekability, not keyframe density, is what makes scrubbing work.** Many static
hosts (and `python -m http.server`) don't serve HTTP byte-range requests, which pins
`video.seekable` to `[0,0]` and clamps *every* seek to frame 0 — the video looks
frozen. The robust fix is to **fetch each clip as a `Blob` and play it from an
in-memory object URL** (blobs are always fully seekable). The engine does this.
Because of it, you do **not** need all-intra video.
2. **Don't shrink quality to get smooth seeks.** Encode at the **native resolution**
(1080p from Seedance — don't downscale), `crf ~20`, a **small GOP** (`-g 8`) rather
than all-intra (all-intra bloats an 8s clip to ~25 MB; GOP 8 is ~8 MB and scrubs
fine via blob). Strip audio, add faststart, and a light `unsharp` counters video
softness:
```bash
ffmpeg -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
```
Encode all 2N-1 clips (dives + connectors) with the same settings for uniform quality.
**Mobile encodes (only if the user opted in at Step 1.6).** The mobile version is
the **native 9:16 portrait chain** (pipeline.md §6b): portrait renders of every dive and
connector, encoded **720 wide (`scale=720:-2`), `-g 4`** (more keyframes = cheaper seeks —
phone decoders' seek cost scales with GOP length), crf 23 — wired as `clipMobile` /
`connectorsMobile`, with each portrait dive's first frame extracted as the section's
`stillMobile` poster (Step 7). The engine serves them automatically on phones and falls
back to the desktop clip when absent. The 16:9 centre-crop `encm()` encodes
(pipeline.md §6) are a **fallback only** — for when credits can't cover the portrait
chain — and shipping them must be called out to the user, never silent. If the user chose
desktop-only, skip this — the engine still hardens phone scrubbing regardless
(seek-coalescing, iOS priming), so the page degrades gracefully rather than breaking.
---
## Step 7 — Assemble the page
Copy `references/scrub-engine.js` (and, if you want a fully standalone page, the tiny
`references/index-template.html`) into the user's project — or adapt into their
framework. It's config-driven and self-contained:
```js
mountLetsScroll(document.getElementById('world'), {
brand: { name: 'Pearl & Co.' },
diveScroll: 1.3, connScroll: 0.9, // viewport-heights of scroll per clip
sections: [
{ id:'farm', label:'The Farms', still:'assets/farm.webp',
clip:'assets/vid/farm.mp4',
clipMobile:'assets/vid/farm-m.mp4', // mobile opt-in only: native 9:16 render
stillMobile:'assets/farm-m.webp', // its first frame as the portrait poster
scroll: 1.6, linger: 0.45, // optional pacing: longer dwell + camera settles mid-scene
accent:'#8FB98A', eyebrow:'From leaf to last sip', title:'It starts in the hills.',
body:'…', tags:['Single-origin','Hand-picked'] },
// …one per section; last may carry a `cta`
],
connectors: ['assets/vid/conn1.mp4','assets/vid/conn2.mp4', /* … length = sections-1 */],
connectorsMobile: ['assets/vid/conn1-m.mp4','assets/vid/conn2-m.mp4' /* … same length; mobile opt-in only */],
});
```
The engine handles: the ordered dive/connector chain, scroll→currentTime with rAF
smoothing, blob loading, lazy prefetch of nearby clips, frame-matched crossfades, pinned
per-section copy (first section greets on landing, last holds its CTA), a route rail,
`prefers-reduced-motion`, and mobile. **Pacing per section:** `scroll` overrides
`diveScroll` for that scene (more scroll = longer dwell) and `linger` (0–1, keep ≤ 0.6)
remaps time so the camera settles mid-scene — exactly while the copy peaks — then speeds
up toward the seam; seam frames are untouched (f(0)=0, f(1)=1). Give the hero and finale
scenes a higher `scroll` + some `linger`; keep transit scenes brisk. Theme it with CSS variables (`--accent`,
`--sw-bg`, `--sw-ink`, …) — the visual identity comes from the generated clips, so the
chrome stays quiet. See the header of `scrub-engine.js` for the full config + CSS vars.
**On phones the engine adapts automatically** (coarse pointer or ≤860px): it serves
`clipMobile` / `connectorsMobile` when present, **coalesces seeks** (never queues a new
`currentTime` while the decoder is still seeking — this is what stops a fast flick from
freezing the clip), **keeps the still as a poster until the clip paints its first frame**
and **primes each video on first touch** (fixes iOS's blank-until-played video), drops the
drifting particles, ignores URL-bar-only resizes (no scroll jump), and uses safe-area
insets so copy clears the notch/home indicator. All of this hardening is on by default —
no config needed. The `clipMobile`/`connectorsMobile` encodes are the opt-in part
(Step 1.6): only wire them when the user asked for the mobile version.
For non-JS backends (Python/Rails/etc.): serve the assets and drop the engine `<script>`
into the rendered HTML; nothing about it is framework-specific.
---
## Step 8 — QA the seams (don't skip)
Drive the page in a headless browser and **verify frame continuity at the seams**, which
is the thing most likely to be wrong:
- Screenshot at scroll positions just before and just after each seam. The two frames
must be near-identical (the dive's last frame == the connector's first frame). If they
pop, you used the diorama still instead of the actual rendered frame (redo Step 5), or
the crossfade band is too short. Calibration: judge seams by *composition*, not raw
PSNR — at 720p/1080p a correctly frame-locked seam can read ~18–25 dB from detail
shimmer alone (observed on a verified-good build); a real mismatch shows as different
composition/props, not just softness.
- Check the console for errors, confirm `video.seekable.end(0) > 0` (blob working), and
that `currentTime` tracks scroll across each clip's band.
- **Mobile — full checklist only if the user opted into the mobile version (Step 1.6).**
For a desktop-only build, just sanity-check a phone viewport once: page loads, still
posters show, nothing overlaps — the engine's hardening covers graceful degradation.
For the mobile build (do this on a real phone or an emulated one, portrait + landscape):
- Emulate a phone viewport **with CPU throttled 4–6×** and scroll fast — the clip should
track without freezing (the seek-coalescing + `-m.mp4` encodes are what make this hold).
- Confirm the first scene shows immediately (its still is the poster) and the video takes
over the instant you scroll — no blank/black scene (the iOS priming fix). Test iOS Safari
specifically; it's the one that goes blank if this regresses.
- Verify the `-m.mp4` variant is actually served on mobile (Network panel), and the
heavy 1080p master on desktop. The mobile clips must be **natively portrait**
(`videoWidth < videoHeight` — not a downscaled 16:9 file), and the `stillMobile`
posters must be served and match each portrait clip's first frame (no
landscape→portrait flash when the video paints).
- Slowly scroll so the URL bar collapses — the page must **not jump** (height-only resizes
are ignored on touch). Rotate the device — layout should recompose cleanly.
- Only if the crop **fallback** shipped (no credits for the portrait chain): portrait
crops a 16:9 clip to its centre — confirm the focal subject still reads, and remind
the user this is the stopgap, not the mobile version.
- Check reduced-motion (should fall back to the stills, no video, no particles).
---
## Gotchas (hard-won)
- **Seam pop** → connector endpoints were the diorama stills, not the neighbouring
clips' actual frames. Always extract real frames (Step 5).
- **Seam stutter / camera "jumps backward"** → even with frame-matched seams, if the
camera *velocity reverses* (forward dive, then a connector that pulls back out) it
reads as a rewind. This is inherent to architecture B. For any grounded walkthrough use
architecture A (one continuous forward take — legs chained from actual last frames, no
pull-back, no `--end-image`); see Step 4.
- **Frozen video / stuck at frame 0** → `seekable=[0,0]`; the host isn't serving byte
ranges. Use blob URLs (engine does).
- **Huge files** → you used all-intra. Use `-g 8` + blob instead.
- **Soft / low quality** → you downscaled or over-compressed. Encode native 1080p,
crf ≤ 20, add `unsharp`. Video is inherently softer than the stills — keep the stills
as the lite fallback for max fidelity.
- **Concurrent gens 503 / "not_enough_credits" race** → transient when many launch at
once; re-roll the individual failure, it's not really out of credits (verify with
`higgsfield workspace list`).
- **NSFW false-positives (Seedance `status "nsfw"`)** → the video content filter flags
perfectly innocuous clips, especially **bedroom, pool, spa/wellness** contexts and
trigger words like "bed", "pool", "waterfall", "wine", "swim". It's partly the prompt
wording and partly the reference frames. Fixes, in order: (1) re-roll — it's often
non-deterministic and passes on the 2nd–3rd try; (2) strip trigger words and add
"empty, unoccupied, no people, no figures, architectural, tasteful"; (3) regenerate
just that clip on **`kling3_0`** with the same start/end frames — a different
provider's filter often passes what Seedance blocks. Expect a slight render-character
shift on that one clip (each model has its own grain/motion feel); for a 5s connector
behind a crossfade that usually beats option (4): set the connector slot to `null` —
the engine crossfades that seam directly (optional connectors), so the page still
completes. Budget extra credits/time for these re-rolls on interiors/real-estate content.
- **Dark / custom theme** → the engine wraps its default tokens in `@layer sw`, so a
page-level `:root` / `.sw-root { --sw-bg; --sw-ink; --sw-accent; --sw-font-* }` block
wins cleanly (no specificity hacks). `--sw-ink` is your primary **text/heading** colour;
the **accent** fills the primary button and active nav. For a dark theme, set `--sw-bg`
dark and `--sw-ink` light — the copy scrim and title shadow follow `--sw-bg` automatically.
- **Phone scrub stutters / freezes on a fast flick** → the 1080p master is too heavy for a
phone decoder and seeks pile up. Ship the `-m.mp4` mobile encodes (720p, `-g 4`) and wire
`clipMobile`/`connectorsMobile` (Step 6/7). The engine already coalesces seeks; the lighter
encode is the other half. Still choppy on a low-end device? Tighten GOP (`-g 2` / all-intra).
- **Blank / black scene on iOS (desktop was fine)** → an iOS Safari quirk: a muted video that
was never played won't paint a seeked frame. The engine fixes this by keeping the still as a
poster until the clip paints and priming each video on first touch — so **don't** hide the
still on `loadedmetadata` or strip the `playsinline`/`muted` attributes if you adapt the
engine into a framework.
- **Page jumps while scrolling on mobile** → something is re-running layout on the URL-bar
show/hide `resize`. The engine ignores height-only resizes on touch; if you ported it, gate
your resize handler on a width change (keep the `orientationchange` path for rotation).
- **Copy hidden behind the URL bar / notch on mobile** → use the engine's safe-area-aware
bottom offset (`env(safe-area-inset-bottom)` + `dvh`); make sure the page's
`<meta viewport>` includes `viewport-fit=cover` (the template does).
- **Portrait crops the scene** → a 16:9 clip on a tall phone shows only its centre — which
is why the mobile version is the native 9:16 chain (§6b), never the crop. If you're seeing
this on a mobile build, either the crop fallback shipped (call it out to the user) or the
9:16 encodes aren't actually being served (check `videoWidth < videoHeight`). Keeping each
scene's focal subject centred (prompts.md) still matters for the desktop film itself.
- **`--generate-audio` errors on seedance** → omit it; mute in HTML and `-an` on encode.
- **Kling rejects your flags** → `kling3_0` has **no `--resolution` param** (don't pass
one; encode at whatever native res ffprobe reports) and **sound defaults on** — pass
`--sound off`. Duration default is 5; legs/dives want 10.
- **Seam pop only where you "saved credits"** → you swapped models mid-chain, or used a
start-image-only model where a connector needs an `--end-image`. One model for the whole
chain; the only cheap tier is `seedance_2_0_mini`, which keeps frame-locking so it stays
seamless. (Any model with reference-only inputs can't hold a seam at all — Step 4.)
- **Manual-path clip pops at its seam** → the user's tool ignored the start frame,
or they generated from the still instead of the extracted handoff frame. Diff
frame 0 of every manual clip against the handed-over PNG before accepting it —
drifted means it goes back for re-generation; no crossfade fixes a wrong start.
Off-world manual stills have the same fix: one tool + the byte-identical style
preamble for all N, no mixing with CLI renders.
- **Monid seedance rejects inline images** → "Must be a public https:// URL or an
asset://<id> reference": frames go through the free `sfs` file system
(put → `curl -T` → cat → signed URL; pipeline.md → Monid backend), never base64.
Quirk: `/put` echoes back `home/<path>`, but `/cat` and `/ls` want the **original
relative path** you gave `/put` — using the echoed path 404s.
- **Monid clip wrong aspect** → the `ratio` default is adaptive and follows the input
image (a 3:2 still → a 4:3-ish video). Pass `--ratio` — `16:9` desktop, `9:16`
mobile chain — explicitly on every chained clip.
- **Monid CLI "Polling timed out after 120s"** → only the local wait died; the run
continues server-side. Re-poll with `monid runs get -r <runId> -w 120` (find the id
in `monid runs list`). Result URLs expire (~24–48 h) — download immediately.
- **Monid minimax drops the image when a prompt is present** → `prompt` +
`first_frame_image` together returns an unrelated t2v clip AND bills the wrong matrix
cell ($0.56 vs $0.28 observed). Image-only frame-locks but has no camera control.
Until the wrapper is fixed, that endpoint can't chain — use Monid's seedance-2.0.
(The model itself is fine — the same prompt+image via Higgsfield `minimax_hailuo`
frame-locks.)
- **Monid billing surprises** → matrix/token-priced endpoints bill by selector match:
pass every selector field explicitly (`model`, `resolution`, `duration`) and read
`cost.value` off the run result after each clip. A big base64 field in any body can
also bounce as an HTML error page ("Unexpected token '<'") — another reason frames
travel by sfs URL.
- **Monid schema changed since last build** → it happens (seedance was t2v-only until
late July 2026, then gained first/last-frame support). `monid inspect` before each
build; re-run the Step 4 qualification probes when the Input schema differs from
what pipeline.md documents.
- **Codex stills hang at "Reading additional input from stdin..."** → parallel
`codex exec` calls launched from one script share the parent's stdin; one wins it,
the rest block forever (observed: 1 of 3 completed, 2 hung, the second batch never
started). Always append `< /dev/null` to every backgrounded `codex exec` — the
pipeline's `gen_still_codex` has it; keep it if you adapt the command.
- **White-box scenes** → `gpt_image_2` returns a solid bg; either match the page bg to it
or knock it out (Step 3).
- **bash 3.2** on macOS → no associative arrays in scripts.
- **Connector grabs the wrong scene's frames** (or errors on a frame that doesn't exist
yet) → the array loop ran in **zsh** (macOS default interactive shell), where arrays are
1-indexed, not bash's 0-indexed. Keep every array-driven chain step in a `#!/bin/bash`
script run via `bash script.sh` — never inline array loops in the interactive shell.
## References
- `references/prompts.md` — the intake checklist, style-preamble pattern, and every
prompt template (scene still, dive, connector) with fill-in slots.
- `references/pipeline.md` — copy-paste batch scripts for the whole run (generate →
extract frames → connectors → encode → mobile encode), bash-3.2-safe.
- `references/scrub-engine.js` — the portable, config-driven scrub engine (builds DOM +
injects CSS; blob-seek, lazy load, seam crossfade, copy, route rail, reduced-motion, and
phone hardening: mobile encodes, seek-coalescing, iOS priming, safe-area, no-jump resize).
- `references/index-template.html` — a minimal standalone page that mounts the engine.
- `references/knockout.py` — border-connected background knockout for floating scenes.
© AIwithhassan, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 5 other files (references) in skills/lets-scroll of AIwithhassan/lets-scroll.
Open the folder on GitHubat commit a060536
Lets Scroll next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Lets Scroll this skillAIwithhassan/lets-scroll | 130 | — | ~14k | Automated safety check: Notes | MIT | |
| Scroll World Landing Pageoso95/scroll-world | 9.8k | 1 repos | ~12k | Automated safety check: Notes | MIT | |
| Scroll Promo Site Builderkangarooking/kangarooking-skills | 657 | — | ~2.6k | Automated safety check: Pass | MIT | |
| Scroll World VideoKubeezMedia/kubeez-scroll-world-video | 510 | — | ~10k | Automated safety check: Notes | None | |
| Frontend Blueprinttech-leads-club/agent-skills | 7k | 1 repos | ~5.3k | Automated safety check: Pass | CC-BY-4.0 | |
| Landing CraftEliasOulkadi/shokunin | 114 | — | ~4.5k | Automated safety check: Pass | MIT |
oso95/scroll-world
Builds a scroll-driven landing page where a pre-rendered camera flies through connected AI-generated scenes, using Higgsfield for stills and video clips.
kangarooking/kangarooking-skills
Create a scroll-controlled cinematic product website with rich motion (动效网站) from product materials, reference pages or videos, and brand assets.
KubeezMedia/kubeez-scroll-world-video
Kubeez's own skill for building an immersive scroll-scrubbed "fly through the world" landing page for any industry or brand, generated end-to-end on the Kubeez platform.
tech-leads-club/agent-skills
AI frontend specialist and design consultant that guides users through a structured discovery process before generating any code.
EliasOulkadi/shokunin
Build conversion-optimized landing pages with CRO frameworks (Conversion Research, LIFT Model), scroll effects, A/B testing, personalization, form optimization, and Core Web Vitals (INP, LCP, CLS).
OSideMedia/higgsfield-ai-prompt-skill
Turns a brief, script, scene breakdown, treatment, or story idea into ONE connected director's shotlist for Seedance 2.0 — a single editable HTML artifact with a global Style Prefix, an @-asset…
Works with
Categories
Build an immersive scroll-scrubbed "fly through the world" landing page for any industry or brand using Higgsfield. Lets Scroll is an agent skill from AIwithhassan/lets-scroll. Build an immersive scroll-scrubbed "fly through the world" landing page for any industry or brand using Higgsfield.
Lets Scroll fits situations like: the user wants a 3D world / browse-through-the-industry hero; A scroll cinematic; A diorama landing; turn a business into a scrollable world.
Run `npx skills add AIwithhassan/lets-scroll --skill lets-scroll -a claude-code`. Or copy the skill folder (skills/lets-scroll in AIwithhassan/lets-scroll) into .claude/skills/lets-scroll in your project. Claude Code loads it when a task matches its description.
Run `npx skills add AIwithhassan/lets-scroll --skill lets-scroll -a codex`. Or copy the skill folder (skills/lets-scroll in AIwithhassan/lets-scroll) into .agents/skills/lets-scroll in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add AIwithhassan/lets-scroll --skill lets-scroll -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/lets-scroll, .gemini/skills/lets-scroll, .github/skills/lets-scroll and .opencode/skills/lets-scroll in your project.
Going by SKILL.md and its folder, Lets Scroll needs Python and JavaScript for the scripts in its folder and the command-line tools its instructions call (codex, ffmpeg, curl, python3, python and bash). Our summary lists: Python 3; Node.js. Its frontmatter pre-approves these tools: Bash, Read, Write, Edit, AskUserQuestion, Skill.
SKILL.md contains no URLs. Its commands use curl, which can reach the network depending on how they are called. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (pre-approves every shell command (allowed-tools: bash)), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
Lets Scroll is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 14k tokens (SKILL.md is roughly 57k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 16k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with Lets Scroll: Scroll World Landing Page (oso95/scroll-world, 9.8k stars), Scroll Promo Site Builder (kangarooking/kangarooking-skills, 657 stars), Scroll World Video (KubeezMedia/kubeez-scroll-world-video, 510 stars) and Frontend Blueprint (tech-leads-club/agent-skills, 7k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
AIwithhassan (a GitHub user) maintains it in AIwithhassan/lets-scroll, which has 130 GitHub stars. The repository was last updated on August 15, 2026.
Source: AIwithhassan/lets-scroll on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.