{"owner":"oso95","repo":"scroll-world","hasSkills":true,"totalSkillsCount":5,"totalTokensCount":21020,"categories":["marketplace","plugin-manifest","anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":[".claude-plugin/marketplace.json",".claude-plugin/plugin.json","skills/scroll-world/references/prompts.md","skills/scroll-world/SKILL.md","skills/scroll-world/references/pipeline.md"],"skills":{".claude-plugin/marketplace.json":"{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"scroll-world\",\n  \"description\": \"Immersive scroll-scrubbed 'fly through the world' landing pages, generated with Higgsfield.\",\n  \"owner\": {\n    \"name\": \"cyw\",\n    \"email\": \"cyw@cywang.me\"\n  },\n  \"plugins\": [\n    {\n      \"name\": \"scroll-world\",\n      \"source\": \"./\",\n      \"skills\": [\"./skills/scroll-world\"],\n      \"description\": \"Build an immersive scroll-scrubbed landing page where a pre-rendered camera flies from outside each scene into its interior, then flows on to the next with no cuts. Interviews the user for topic, story beats, and brand kit; generates cohesive scenes + seamless camera clips with Higgsfield; wires a portable, framework-agnostic scrub engine.\",\n      \"category\": \"design\"\n    }\n  ]\n}\n",".claude-plugin/plugin.json":"{\n  \"name\": \"scroll-world\",\n  \"description\": \"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. Interviews the user for the topic, story beats, and brand kit, then generates cohesive scenes + seamless camera clips and wires a portable, framework-agnostic scroll-scrub engine. Use for a '3D world' / 'browse-through-the-industry' hero, a scroll cinematic, or a diorama landing.\",\n  \"version\": \"0.8.0\",\n  \"author\": {\n    \"name\": \"cyw\",\n    \"email\": \"cyw@cywang.me\"\n  },\n  \"homepage\": \"https://github.com/oso95/scroll-world\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"higgsfield\",\n    \"monid\",\n    \"landing-page\",\n    \"scroll\",\n    \"video\",\n    \"seedance\",\n    \"diorama\",\n    \"webgl-alternative\",\n    \"cinematic\"\n  ]\n}\n","skills/scroll-world/references/prompts.md":"# Prompt templates & intake\n\nEverything here is fill-in-the-slots. Keep the **style preamble** byte-for-byte identical\nacross all scene stills — that identical text is what makes the world feel like one place.\n\n## Intake checklist (Step 1)\n\nCollect and write down:\n\n- `SUBJECT` — the business + one-line pitch.\n- `BRAND_NAME` — display name.\n- `PALETTE` — 4–6 named hexes, e.g. `taro #9B7EBD, cream #F5EDE0, caramel #C88A5A, matcha #8FB98A, plum #3A2E48`. Pick ONE as the scene **background** colour (usually the lightest) and one as the primary **accent**.\n- `TONE` — a word or two (cozy/premium, playful, industrial…).\n- `STYLE` — the art direction (default below).\n- `SECTIONS[]` — ordered list; for each: `id`, `label`, `subject` (what's in the diorama), `eyebrow`, `title`, `body` (≤ 1 sentence), `tags[]` (0–3). Last section = hero product + CTA.\n- `CAMERA` — fly-through (arch B: dives + aerial hops — the flagship-demo look) |\n  walkthrough (arch A: one continuous forward flight) | locked-iso (arch A + the\n  locked-iso clause below: one fixed angle for the whole film). **Always asked**\n  (SKILL Step 1.4), presented by feel with a one-line trade-off each.\n- `MOBILE` — yes/no. **Always asked** (SKILL Step 1.6), presented to the user\n  with the ~2× credit cost stated. Yes = the **native 9:16 portrait chain**\n  (pipeline §6b): portrait renders of every dive/connector +\n  `clipMobile`/`connectorsMobile`/`stillMobile` wiring + the full mobile QA. The\n  §6 crop encodes are a no-credits stopgap only.\n- `VIDEO_TIER` — default is `seedance_2_0` **via Monid** (pay-per-clip; previz =\n  same endpoint at 480p, final at 1080p). Higgsfield-credit alternates: draft\n  (`seedance_2_0_mini`) | standard (`seedance_2_0`) | alternate (`kling3_0`).\n  Chosen by cost at SKILL Step 1.7, with the estimated total stated before\n  anything renders.\n- `STILLS_SOURCE` — higgsfield (`gpt_image_2`, spends credits) | codex\n  (`image_gen`, subscription-billed; only offer when the Codex CLI is present).\n\n## Style preamble (default: clay diorama)\n\nReuse verbatim in every scene prompt. Swap the bracketed bits for the brand's palette/bg.\n\n```\nIsometric low-poly 3D diorama floating as a small rounded island on a plain solid\n[BG_HEX] background with a soft contact shadow beneath it. Soft matte clay 3D render,\nrounded toy-model shapes, gentle warm studio lighting, soft long shadows, tilt-shift\nminiature look. Cohesive color palette of [PALETTE]. Highly detailed, centered\ncomposition, absolutely no text, no letters, no numbers, no logos.\n```\n\nAlternate directions (swap the first two sentences, keep the palette/no-text tail):\n- **Flat papercraft:** \"Isometric layered paper-craft diorama, matte cardstock, clean die-cut edges, subtle drop shadows between layers.\"\n- **Glossy toy:** \"Isometric glossy vinyl-toy diorama, smooth plastic shading, soft rim light, collectible figurine look.\"\n- **Claymation:** \"Isometric stop-motion clay set, visible thumbprints, handmade plasticine texture, soft studio softbox light.\"\n- **Neon night:** \"Isometric miniature at night, warm interior glow and neon signage, moody rim light, wet reflective ground.\"\n- **Photoreal architectural** (real estate, hospitality, premium/luxury): \"Ultra-photorealistic architectural photography of a single cohesive [subject], cinematic wide-angle, warm golden-hour light, natural materials, restrained designer furnishings, a breathtaking view, editorial magazine quality (Architectural Digest), shallow depth of field, no people.\" For photoreal, drop the floating-island framing and the knockout (Step 3) — the scenes are **full-bleed** (a dark page background reads premium), the \"dive\" glides *through doorways/glass* rather than opening a roof, and cohesion comes entirely from the identical preamble (do NOT pass an `--image` reference — it clones the same room). Interiors trip Seedance's NSFW filter often; see SKILL Gotchas.\n\n## Scene still prompt (Step 2)\n\n```\n[STYLE PREAMBLE]\nSubject: [SECTION.subject — describe the miniature scene: the building/space, a few\ncharacters doing the work, the props that signal this stage of the business].\n```\n\nTips:\n- Name concrete props (they anchor the scene): tanks, cauldrons, conveyor, crates, awning, string lights, benches, scooters, map pins.\n- For the final \"hero product\" section, drop the diorama-island framing and prompt a\n  single oversized product centerpiece floating on the same background with a few small\n  orbiting props.\n- **Compose for the centre.** The page renders every clip `object-fit:cover`. Keep the\n  focal subject horizontally centred with a little headroom, and don't park anything\n  essential at the far left/right edges. Mobile ships its own native 9:16 chain\n  (pipeline §6b), so this is not about surviving a crop — but a centred composition makes\n  the portrait renders open cleanly from the same still, and it keeps the dive's focal\n  point where the camera actually flies.\n- Aspect `3:2`, `--resolution 2k --quality high`.\n\n## Leg prompt — architecture A, continuous forward take (Step 4)\n\n`--start-image = previous leg's ACTUAL last frame` (leg 0: the first scene's still).\n**No `--end-image`.** The bolded clauses are the motion-handoff contract — keep them\nverbatim; the mid-leg move is where the expression goes.\n\n```\nSingle continuous cinematic camera move, no cuts. **Continue the same slow, steady\nforward glide.** [MID-LEG MOVE — optional, from the library below.] The camera moves\ninto [SCENE i] toward [FOCAL POINT]. **In the final second, settle back into a slow,\nsteady forward glide toward [the doorway / opening / direction of the next scene].**\n[STYLE tail + PALETTE]. Smooth, graceful, slow motion, subtle parallax. No text, no captions.\n```\n\n### Mid-leg move library (pick by concept; omit for a plain glide)\n\nReversals are safe *inside* a leg (it's one continuous render) — only a seam may never\nreverse. That's why \"ease back out\" is fine mid-leg.\n\n**Locked-iso clause** (`CAMERA` = locked isometric glide — SKILL Step 1.4/Step 4):\nskip this library entirely and put this clause, verbatim, in the mid-leg slot of\nEVERY leg:\n\n```\nThe camera keeps exactly the same high isometric angle throughout — no rotation, no\norbit, no tilt. It only travels straight and level, the world sliding past beneath\nthe same view.\n```\n\nKeep the handoff-contract clauses around it unchanged. When checking each leg's last\nframe, also check the ANGLE hasn't drifted (Seedance rotates slightly on long legs) —\nre-roll the leg if the view has turned.\n\n- **Half-orbit** (product, luxury): \"sweeping in a slow half-orbit around [the hero\n  object], keeping it centered, then continuing past it\"\n- **Crane-up reveal** (scale, atriums, campuses): \"rising smoothly as the full scale of\n  [the space] reveals below\"\n- **Low lateral track** (production lines, counters, shelves): \"tracking low and level\n  alongside [the line], foreground objects sliding past in parallax\"\n- **Push-in + ease back** (craft, detail): \"pushing in close to [the craft moment] until\n  it nearly fills the frame, then easing gently back out\"\n- **Rise-and-swoop** (travel, outdoors): \"climbing in a gentle arc over [the terrain],\n  then swooping down toward [the next focal point]\"\n\nAfter rendering each leg, **check its last frame** before generating the next: it should\nread as a frame from a calm forward glide (no motion blur sideways, no half-finished\norbit). If it doesn't, re-roll this leg — a bad handoff frame poisons every leg after it.\n\n## Dive-in clip prompt (Step 4)\n\n`--start-image = the scene still` (solid-bg version).\n\n```\nSingle continuous cinematic camera move, no cuts. Begin high and far, looking down at the\nwhole [SECTION.subject] from outside like a tiny model. The camera slowly glides forward\nand descends toward it, sweeping in toward [FOCAL POINT — the counter/the cauldrons/the\npeople], as if flying inside. As the camera pushes in, the roof and upper structure\ngently lift and open away to reveal the warm interior. [STYLE tail: soft matte clay\ndiorama, tilt-shift miniature, warm light, [PALETTE]]. Smooth, graceful, slow motion,\nsubtle parallax. No text, no captions.\n```\n\nFor scenes with no building to open (a field, a plaza, a road), replace the roof clause\nwith \"the camera flies low across [the scene] toward [focal point].\"\n\nParams by chain model (SKILL Step 4 table): seedance —\n`--mode std --resolution 1080p --aspect_ratio 16:9 --duration 8`, no audio flag;\nkling3_0 — `--mode std --sound off --aspect_ratio 16:9 --duration 10` (no `--resolution`\nparam). Same for architecture-A legs.\n\n## Connector clip prompt (Step 5)\n\n`--start-image = dive_i LAST frame` (extracted), `--end-image = dive_{i+1} FIRST frame`\n(extracted). Both from the RENDERED videos, not the stills.\n\n```\nSingle continuous cinematic camera move, no cuts. The camera smoothly pulls up and back\nout of [SCENE i], rising into the sky, then glides forward across the connected miniature\nworld and arrives above [SCENE i+1], beginning to descend toward it. One connected\nminiature clay world, seamless flowing aerial transition. [STYLE tail + PALETTE]. Smooth\ngraceful slow motion. No text, no captions.\n```\n\nFor the last connector into a hero-product finale: \"…glides forward and the world\ndissolves toward a single giant [PRODUCT] floating in soft [BG] space, arriving in front\nof it.\"\n\nseedance: `--mode std --resolution 1080p --aspect_ratio 16:9 --duration 5`; kling3_0:\n`--mode std --sound off --aspect_ratio 16:9 --duration 5`. Connectors need `--end-image`\n→ use a roster model that accepts it (Step 4).\n\n## Copy per section (for the engine config)\n\n- `eyebrow` — 2–4 words, uppercase feel (a value-prop label).\n- `title` — 3–6 words, the beat's headline. First section = the site's hero line; last =\n  the payoff + it carries the CTA.\n- `body` — one sentence, plain-spoken, from the visitor's side.\n- `tags` — 0–3 short proof chips (e.g. \"Fresh-cooked\", \"30-min delivery\").\n","skills/scroll-world/SKILL.md":"---\nname: scroll-world\ndescription: >\n  Build an immersive scroll-scrubbed \"fly through the world\" landing page for any\n  industry or brand using Higgsfield. As the visitor scrolls, a pre-rendered camera\n  flies from outside each scene into its interior, then flows on to the next scene\n  with NO cuts — one continuous connected flight (Emons-style isometric diorama world,\n  or any art direction you pick). The skill interviews the user for the topic, the\n  story beats/sections, and brand kit, then generates cohesive scenes + seamless camera\n  clips with Higgsfield and wires a portable, framework-agnostic scroll-scrub engine.\n  The video chain renders through Monid by default (Seedance 2.0, pay-per-clip\n  USD — capability re-checked each build, see Step 4) with Higgsfield credits as\n  the fallback biller. Use when the user wants a \"3D world\" /\n  \"browse-through-the-industry\" hero, a scroll cinematic, a diorama landing, or to\n  turn a business into a scrollable world.\nallowed-tools: Bash, Read, Write, Edit, AskUserQuestion, Skill\n---\n\n# scroll-world\n\nProduces a landing page where **scroll drives a camera**: it dives from outside a scene\ninto its interior, then flies out and into the next scene, continuously, with no visible\ncuts. The visuals are AI-generated — stills via Higgsfield (or Codex), the video chain\nvia **Monid by default** (pay-per-clip Seedance 2.0; Higgsfield credits as fallback) —\nand the page just scrubs pre-rendered video by scroll position. This is the same technique behind Apple's scroll-through product\npages — the camera genuinely moves, scroll only drives time.\n\n**What you generate:** N scene stills → N \"dive-in\" camera clips → N-1 \"connector\" clips\nthat join consecutive scenes seamlessly → a portable scrub engine that plays the whole\nchain as one flight.\n\n**The one rule that makes or breaks it:** seams must be *frame-identical*. Read\n[The seamless chain](#step-5--the-seamless-chain-the-critical-part) before generating any\nconnector. Getting this wrong is the single most common failure and produces a visible\n\"pop\" between scenes.\n\nDo not assume a frontend framework. The scrub engine in `references/scrub-engine.js` is\nself-contained vanilla JS (it builds its own DOM + injects its own CSS into a container\nyou give it), so it drops into plain HTML, Next.js, Vue, a Python-served page, anything.\nThe value of this skill is the Higgsfield pipeline, the prompts, and the seam method —\nnot the framework.\n\n---\n\n## Step 0 — Bootstrap\n\n1. **Monid CLI — the default video-chain backend.** Check `monid --version`,\n   `monid keys list` (active key) and `monid balance` — the chain is billed per\n   clip in USD (Step 1.7 has the numbers; a 1080p N=6 chain ≈ $27). If the CLI is\n   missing or the balance can't cover the chain, say so and fall back to\n   rendering the chain on Higgsfield credits instead — same model, same\n   pipeline, different biller (Step 4 → Monid backend).\n2. **Higgsfield CLI — still required even on the Monid path**: it renders the\n   scene stills (`gpt_image_2`) and is the home of the `kling3_0` NSFW fallback\n   and the fallback chain. If `higgsfield` is not on `$PATH`, install per the\n   `higgsfield-generate` skill. If `higgsfield workspace list` fails auth, ask the user\n   to run `higgsfield auth login` (interactive OAuth — you cannot run it) and, if needed,\n   `higgsfield workspace set <id>`. Confirm credits cover the stills (~N image\n   gens) — plus `(2N-1)` video gens if the chain falls back here.\n3. **ffmpeg / ffprobe** on `$PATH` (frame extraction + encoding).\n4. **An image tool** for background knockout if you want floating scenes: PIL\n   (`python3 -c \"import PIL\"`), or `cwebp`/`sips`. Optional — see Step 3.\n5. **(Optional) Codex CLI** — if `codex` is on `$PATH` (≥ 0.125) and\n   `codex login status` reports a ChatGPT login, the scene stills can be generated\n   through Codex's built-in `image_gen` (the same gpt-image-2 model) billed to the\n   user's ChatGPT subscription instead of Higgsfield credits — offer it at\n   Step 1.7, command in Step 2. Absence just removes the option.\n6. Caveats: macOS ships **bash 3.2** (no `declare -A`); don't use associative arrays in\n   scripts. Higgsfield generations take **3–8 min each** — always run them detached\n   (background) and poll, never a foreground blocking call. Reference-by-job-UUID is\n   rejected by media flags — pass **local file paths** to `--image/--start-image/--end-image`.\n   Video models differ in accepted params (e.g. Kling has no `--resolution`) and in whether\n   they support start/end-image conditioning at all — before batching, confirm the chosen\n   model's schema with `higgsfield model get <job_type>` and see the Step 4 model table.\n\n---\n\n## Step 1 — Interview the user\n\nThe **subject is the user's to state — ask it as an open question in plain prose**, never a\nfabricated multiple-choice. A made-up list of industries biases them and reads as you\ndeciding their business for them; let them answer in their own words (their real business,\na client's, or any idea). Reserve structured multiple-choice (`AskUserQuestion` in Claude\nCode; a plain either/or question elsewhere) for the genuinely\nenumerable, lower-stakes choices below — art direction, camera style, and brand-kit\napproach — and even\nthere, signal they can go their own way (\"Other\"). Ask only what you can't sensibly\ndefault. Cover:\n\n1. **Subject** (ask openly, not multiple-choice) — \"What should this world be about? Your\n   business, a client's, or any idea — a word or a sentence is fine.\" Capture the\n   industry/product + a one-line pitch (e.g. \"a bubble tea company, from leaf to last\n   sip\"), and a brand name if they have one; otherwise you'll propose one below.\n2. **Brand kit** — offer three paths, pick one:\n   - Import from a URL: `higgsfield marketing-studio brand-kits fetch --url <site> --wait`\n     (pulls name, colours, tone). Then read it back with `brand-kits list --json`.\n   - The user hands you palette + name + tone directly.\n   - You propose a palette + name and let them approve.\n   Capture **4–6 named hex values**, a display name, and a tone word or two.\n3. **Art direction** — default is \"soft matte low-poly **clay diorama**, isometric,\n   tilt-shift miniature, warm light.\" Offer alternatives (flat papercraft, glossy toy,\n   claymation, neon night). Whatever is chosen becomes the shared **style preamble**\n   reused verbatim in every scene prompt (this is what makes the world cohesive).\n4. **Camera style — ALWAYS ask; it's the film's personality, not a technical\n   detail.** Ask by feel (`AskUserQuestion` in Claude Code; a plain question\n   elsewhere) and record the answer as `CAMERA`. The options map to the Step 4\n   architectures — Step 4 then *implements* the choice, it never re-decides it:\n   - **\"Fly through the world\"** — the camera dives into each scene, pulls up and\n     out, and hops across the miniature world to the next; angles change\n     constantly, big expressive aerial moves (this is the flagship-demo look).\n     → Architecture B. Recommend as the default for diorama/miniature art\n     directions.\n   - **\"One continuous walkthrough\"** — a single forward flight that glides\n     through each scene straight into the next, never pulling back; expressive\n     but always-forward moves per scene (camera grammar table). → Architecture A.\n     Recommend as the default for grounded/photoreal art directions.\n   - **\"Locked isometric glide\"** — the camera keeps one fixed angle for the whole\n     film, Emons-style; the world slides past/toward it, no rotation, no reveals.\n     → Architecture A + the locked-iso clause in every leg prompt (prompts.md).\n   State the trade-off in one line each (B reverses direction at seams — charming\n   in miniature, jarring in realism; locked-iso is the calmest and cheapest to\n   re-roll; walkthrough sits between).\n5. **The journey (sections)** — the ordered scenes the camera flies through. Propose a\n   set derived from the subject's own value chain and let the user edit. 5–7 works well.\n   Boba example: farms → pearl kitchen → flagship shop → delivery → community plaza →\n   the hero product. Each section needs: a short subject description (what's IN the\n   diorama), an eyebrow, a headline, one line of body, and 0–3 tag pills. The last\n   section is usually the hero product + the CTA.\n6. **Mobile version — ALWAYS ask this; never silently generate both.** Ask as a\n   two-option choice (`AskUserQuestion` in Claude Code; a plain question elsewhere):\n   *\"Want a mobile-optimized version too? The mobile version is a second camera chain\n   rendered natively in **9:16 portrait** — composed for phones, not a crop of the\n   landscape film — which roughly doubles the Higgsfield credit spend (state the\n   estimated number).\"*\n   Options: \"Desktop only\" / \"Desktop + mobile (native 9:16 — ~2× credits)\". The\n   credit cost must be stated to the user, not just implied.\n   What the answer gates:\n   - **Yes** → render the parallel 9:16 portrait chain and ship it as the mobile variants\n     (Step 6 / pipeline.md §6b): portrait start canvases → 9:16 dives + connectors\n     frame-locked against their own renders → 720-wide `-m.mp4` encodes → `stillMobile`\n     portrait posters. Wire `clipMobile`/`connectorsMobile`/`stillMobile` (Step 7); run\n     the full mobile QA (Step 8). Budget ~2N-1 extra video gens + NSFW re-rolls.\n     **Never ship the centre-crop as the mobile version by default** — if credits can't\n     cover the portrait chain, say so and offer the crop encodes (pipeline.md §6) as an\n     explicitly-labelled stopgap the user must approve.\n   - **No** → skip the mobile encodes and wiring entirely. The engine's phone hardening\n     (seek-coalescing, iOS priming, safe-area CSS) is always on regardless — that's not\n     a \"mobile version,\" it's just the page not breaking when a phone visits — so a\n     desktop-only build still degrades gracefully.\n\n7. **Budget — engines shown by cost, decided before anything renders.** Present the\n   render tiers (`AskUserQuestion`), then compute and state the estimated total for\n   the user's N scenes — `N stills + (2N−1) videos [videos ×2 if mobile] + ~15%\n   re-roll headroom` — and get a go before generating.\n   - **Video tier** (roster only — every option frame-locks seams, Step 4):\n\n     | Tier | Model | Rough cost |\n     |---|---|---|\n     | Draft / previz | `seedance_2_0_mini` (720p) | ~¼ of Standard |\n     | Standard (default) | `seedance_2_0` (1080p) | baseline |\n     | Alternate | `kling3_0` (720p native) | ≈ Standard; different look + content filter |\n\n     Draft doubles as the previz path: run the whole chain cheap, approve the\n     journey, re-render final legs on Standard (pipeline.md Notes) — suggest it\n     unprompted when the balance reads tight.\n   - **Backend — Monid is the DEFAULT biller for the chain** (Step 0.1; wiring\n     in pipeline.md → Monid backend). Same Seedance 2.0, per-clip USD instead of\n     credits. Token-priced `width × height × 24 × seconds / 1024` at $7/1M\n     (480p/720p) or $7.7/1M (1080p) — measured: 1080p 8s dive ≈ $2.99, 5s\n     connector ≈ $1.87; 720p ≈ $1.21 / $0.76; 480p ≈ $0.28 / $0.35. An N=6\n     desktop chain ≈ $27 at 1080p / ~$11 at 720p vs Higgsfield Plus-monthly ≈\n     $32 / $16 — ~15% cheaper per clip, parity with Plus-annual; structurally\n     better for one-off builds (pay-per-use, no monthly expiry). On Monid the\n     Draft/previz tier is simply the **same endpoint at 480p** — no model swap,\n     so previz→final stays one-model by construction. State `monid balance`\n     against the estimate; **fall back to Higgsfield credits** (per-model tiers\n     above) when the user prefers their subscription, the balance is short, or\n     the model must be `kling3_0` (Higgsfield-only). It's the same underlying\n     model (`seedance_2_0` ≙ Monid's `seedance-2.0`), so finishing a stranded\n     chain on the other biller is a reasonable rescue — but the serving stacks\n     differ and cross-provider seam character is **untested**: eyeball the first\n     rescued seam before rendering the rest, same as any model swap.\n   - **Stills source** (only offer if the Codex CLI is present, Step 0.5):\n     Higgsfield `gpt_image_2` (spends credits) vs **Codex `image_gen`** — the same\n     gpt-image-2 model billed to the ChatGPT subscription (zero credits; counts\n     toward Codex usage limits; 1536×1024 output — exactly 3:2, slightly under\n     Higgsfield's 2k). Stills are plain PNGs handed to `--start-image`, so the\n     video chain is indifferent to their source. Command in Step 2. **One source\n     for all N stills of a build** — the two render with slightly different\n     character (verified: Codex runs warmer/lighter), and mixing sources across\n     scenes reads as style drift, same reason the video chain uses one model.\n   - **Calibrate costs, don't guess.** The CLI exposes no pricing and plans differ.\n     Run ONE still and ONE video first, diff `higgsfield workspace list` before/\n     after, extrapolate to the full run, and warn the user whenever the estimate\n     exceeds ~70% of the balance. (Observed on a plus plan, 2026-07: Standard\n     video ≈ 40–55 credits, still ≈ 15.) A real `not_enough_credits` mid-run is\n     recoverable (finished clips survive; resume after top-up) but ugly — the\n     whole point of this step is that the user decides *before* the spend.\n\nIf the user names a video model outside the roster, honor it **only if it can\nframe-lock seams** (Step 4). This skill only ships seamless output, so a model that\ncan't frame-lock is declined with a one-line why, not substituted in — use a roster\nmodel instead.\n\nKeep the scroll mechanic fixed (continuous fly-through) — that's the point of the skill.\nSee `references/prompts.md` for the intake checklist and copy structure.\n\n---\n\n## Step 2 — Generate the scene stills\n\nOne image per section, **all sharing the same style preamble** for cohesion. Default\nmodel **`gpt_image_2`** (crisp, great at isometric illustration; returns a solid/white\nbackground which is perfect for floating diorama \"islands\"). Use `nano_banana_2` only if\nthe brief is character/cartoon-heavy (note: `nano_banana_2` is a CLI alias — it resolves\nto `nano_banana_pro`; it won't appear under that name in `higgsfield model list`).\n\nPrompt shape (full templates in `references/prompts.md`):\n\n```\n<STYLE PREAMBLE, identical every time>. On a plain solid <bg> background with a soft\ncontact shadow. <PALETTE hexes>. No text, no letters, no logos, centered, 3:2.\nSubject: <what is in THIS diorama>.\n```\n\n- Run all N concurrently, detached. Command per scene:\n  `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`\n- Result URL is `.[]0.result_url` in the `--wait --json` output. `curl` it down.\n- **Codex stills variant** (if chosen at Step 1.7 — subscription-billed, zero\n  credits): same prompt files, same byte-identical preamble, generated by Codex's\n  built-in `image_gen`:\n\n  ```bash\n  codex exec -C \"$WORK\" -s workspace-write --skip-git-repo-check \\\n    '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.' \\\n    < /dev/null\n  ```\n\n  Single-quote the `$imagegen` segment (the shell must not expand it); if editing\n  with reference images, the prompt goes BEFORE any `-i` flag (it's variadic).\n  ~1–3 min per image; run a few in parallel, not all N at once — and keep the\n  `< /dev/null`: parallel `codex exec` calls sharing a script's stdin hang\n  waiting for input (Gotchas). Output lands at\n  1536×1024 (3:2) — fine for `--start-image` and posters. Everything downstream\n  (cohesion review, knockout, dives) is unchanged.\n- A generation may fail transiently (HTTP 503) — re-roll that one individually; don't\n  restart the batch.\n- **Review the stills before continuing.** They must read as one cohesive world (same\n  angle, palette, light). If one is off-style, regenerate it, optionally passing an\n  approved scene as `--image` to lock style.\n\nSee `references/pipeline.md` for the exact batch script.\n\n---\n\n## Step 3 — (Optional) Float the scenes\n\nIf you want the dioramas to float over an atmospheric background instead of sitting in a\nsolid box, knock out the flat background to transparency with\n`references/knockout.py` (border-connected flood fill — preserves interior colour that\nmatches the bg, e.g. cream walls). Then encode to webp. If you'd rather keep it simple,\njust make the page background the same colour as the scene background and skip this.\n\nThese stills double as **video posters and lazy-load fallbacks**, so keep them.\n\n---\n\n## Step 4 — Camera architecture (implements the Step 1.4 choice)\n\nHow the camera moves *between* scenes is the single biggest quality lever. The user\nalready chose the style at the interview (`CAMERA`, Step 1.4): fly-through → **B**,\nwalkthrough → **A**, locked-iso → **A + the locked-iso leg clause** (prompts.md). If\nthe interview somehow skipped it, ask now — never silently pick for them. The two\nshapes, and the grammar that colors them:\n\n### Video model — pick ONE for the whole chain\n\n**This skill only ships seamless output**, so the only usable models are ones that can\nframe-lock a seam: every chained clip must accept `--start-image`, and connectors also\nneed `--end-image`. That capability — not preference — is the selection rule. Check any\nmodel with `higgsfield model get <job_type>` and **skip anything whose media inputs are\nreference-only** (no start/end image): it can only *condition* a generation, not\n*continue* a shot, so it physically can't hold a seam. Schemas below were confirmed\nagainst the CLI:\n\n| Model | start/end image | Notes |\n|---|---|---|\n| `seedance_2_0` (default) | ✓ / ✓ | Full chain (legs + connectors). `--mode std --resolution 1080p`. Its NSFW filter is the touchy one (see Gotchas). |\n| `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. |\n| `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. |\n\nThose three are the roster — all do both architectures. (`kling3_0_turbo` also frame-locks\nvia `--start-image`, but has no `--end-image`, so it's architecture-A-only and can't make\nconnectors; it also takes a different flag set — no `--mode`, has `--resolution` — so it\ndoesn't drop into the pipeline as-is. It's not in the default roster; only reach for it, and\nwire it by hand, if architecture A's sequential render time is a proven bottleneck and you've\nbenchmarked it as actually faster.)\n\nOne more architecture-A-only candidate, worth knowing because it is by far the cheapest\nprobe: **`minimax_hailuo`** (Hailuo-2.3, ~6 credits per 768p/6s clip vs 22–72 for the\nroster). Verified 2026-07: `--start-image` + prompt frame-locks (output frame 0 ≡ input,\nPSNR 33 dB) and a forward-glide prompt was obeyed, gently. Constraints: the 2.3 variant\nrejects `end_image` (no connectors → arch A only), output aspect follows the input image\n(hand it a 16:9 canvas, not a bare 3:2 still), motion runs subtler than seedance, and\ndon't pass `--resolution` (the CLI mis-types the enum; the 768 default works — 1080\nsupports 6s only). One clip ≠ a chain: qualify a leg-to-leg handoff before betting a\nfull build on it.\n\nRules:\n- **One model for all chained clips.** Each renderer has its own motion/color/grain\n  character; mixing models mid-chain keeps *position* continuity (frames still hand off)\n  but the render-character shift reads as a subtle pop. The one sanctioned exception is\n  the NSFW fallback for a single stubborn clip (Gotchas) — a slight character shift on\n  one 5s connector beats a missing connector.\n- Default to `seedance_2_0`, rendered through **Monid by default** (per-clip USD —\n  next section) with Higgsfield credits as the fallback biller (Step 0.1/1.6); honor\n  a user's stated preference **only if the model qualifies** (frame-locking). If it\n  doesn't, say so and use a supported model — never ship a non-seamless build to\n  satisfy a model request. `kling3_0` and `seedance_2_0_mini` exist only on the\n  Higgsfield side.\n- The pipeline scripts take the model as `$VMODEL` with per-model flags already cased\n  out (`references/pipeline.md`).\n\n### Monid backend — the DEFAULT chain biller (qualified 2026-07-25)\n\nMonid's **`bytedance /v1/video/seedance-2.0`** passed both paid probes on\n2026-07-25 and is the **default** way this skill renders the chain, for **both\narchitectures** — it is the roster's `seedance_2_0` served pay-per-USD (wiring in\npipeline.md → \"Monid backend\"; Higgsfield renders the chain only as the fallback\nbiller or for Higgsfield-only models):\n\n- **Leg probe** (prompt + `first_frame` image): output frame 0 ≡ input still\n  (PSNR 31.6 dB), forward-glide prompt obeyed, billed the advertised cell\n  ($0.279 / 480p 4s).\n- **Connector probe** (prompt + `first_frame` + `last_frame`): start locked\n  (31.6 dB); the end **lands close but not pixel-perfect** (27.5 dB, same\n  composition, prop-level drift) — the exact end-image behavior Seedance shows\n  on Higgsfield, covered by the engine's seam crossfade and by using the next\n  dive's ACTUAL first frame as the end-image (Step 5 law, unchanged).\n\nThe I/O contract differs from the Higgsfield CLI — three rules:\n\n1. **Images go by URL, never inline.** `content` items are\n   `{\"type\":\"image_url\",\"image_url\":{\"url\":…},\"role\":\"first_frame\"|\"last_frame\"}`;\n   base64 data URLs are **rejected** (\"Must be a public https:// URL or an\n   asset://<id> reference\"). Local frames travel through Monid's free workspace\n   file system: `sfs /put` → `curl -T` the bytes → `sfs /cat` returns a signed\n   public URL to paste into the body ($0, explicitly built for this).\n2. **Pass `ratio` explicitly** (`16:9`, or `9:16` for the mobile chain) — the\n   adaptive default follows the input image's aspect instead.\n3. **Bill-check every clip**: cost is token-priced\n   (`w × h × 24 × sec / 1024` at $7–7.7/1M); read `cost.value` off each run.\n\nHistory that shaped these rules (still true as of 2026-07-25): the seedance\nendpoints were text-to-video-only until late July 2026 — **re-`inspect` before\neach build; the catalog moves in both directions.** `minimax\n/v1/video_generation` (Hailuo-2.3) remains disqualified: sending `prompt` +\n`first_frame_image` together silently drops the image (unrelated t2v output,\nwrong price cell); image-only frame-locks (33 dB) but has no camera control.\n\n**Qualification protocol for any new/changed Monid endpoint** (each probe is one\ncheap 480p clip): (1) prompt + first-frame from a real still — frame 0 must\nmatch the input to codec noise (PSNR ≳ 30 dB) and `cost.value` must match the\nadvertised cell; (2) for connector duty, add a `last_frame` from a different\nstill — the end must land on that composition (Seedance-style near-miss is fine,\nthe crossfade covers it). Pass → pay-per-clip tier (arch A if start-only; full\nroster if start+end).\n\n### A) Continuous forward take — RECOMMENDED for grounded / realistic / walkthrough\nOne camera that only ever glides **forward**, first scene through last, as a single take.\nGenerate the legs **sequentially**: leg 0 from scene-0's still (glide forward into it);\nthen each leg's `--start-image` = the **previous leg's ACTUAL last frame** (extract with\nffmpeg), prompt *\"continue gliding smoothly FORWARD into [scene i], never pulling back\"*\n(or an expressive mid-leg move under the motion-handoff contract — see **Camera grammar**\nbelow), and **no `--end-image`** — an end-image of a wide establishing shot forces the\ncamera to pull back, which is the #1 cause of stutter. Extract each leg's last frame to feed the\nnext. Result: every seam is frame-identical **and** the camera never reverses. There are\n**no connectors** (skip Step 5) — the legs ARE the journey. Wire each leg as a section\nclip with `connectors: []` and a small `crossfade` (~0.08). Even without an `--end-image`\nthe legs still arrive at distinct rooms (the prompt steers the content). Cost: strictly\n**sequential** (can't parallelize) and slower; interiors trip the NSFW filter, so build in\nre-rolls (3 attempts/leg).\n\n### B) Dive-in + aerial connector — only for diorama / miniature / god's-eye worlds\nA \"dive into each scene\" clip + a connector that pulls **up and out** and flies over to the\nnext scene (Step 5). The pull-out **reverses camera direction at every seam** (forward dive\n→ backward pull-out). In a miniature/diorama world that reads as an intentional \"zoom out\nto the map, fly to the next island\"; in a grounded first-person walkthrough it reads as a\njarring **rewind/stutter**. Use B only for the map-like aesthetic — which is exactly\nwhat the \"fly through the world\" interview answer opts into; the reversal reads as\nintentional there. If the user picked B against a grounded/photoreal direction, say\nwhy it will read as a stutter and confirm before rendering.\n\n### Camera grammar — the move should fit the concept (A is NOT \"forward only\")\n\n\"Forward only\" is the *seam* rule, not the *leg* rule. The physics of the chain:\n\n- **Position continuity** at a seam comes from the frame handoff (next leg starts from the\n  previous leg's actual last frame).\n- **Velocity continuity** at a seam means the camera must never *reverse across a seam* —\n  that's the rewind stutter.\n- **Inside a single leg the camera is free.** One leg is one continuous render — there is\n  no seam to break mid-leg, so orbits, crane-ups, lateral tracking, even a push-in that\n  eases back out are all safe *within* the clip. Reversals are only fatal *across* seams.\n\nSo give each leg an expressive move chosen from the scene's own logic, under a **motion\nhandoff contract**: every leg **ends by settling into a slow, steady forward drift** toward\nthe next destination (final ~1 s), and every leg **begins by continuing that same drift**.\nKeep both clauses in the prompts verbatim (templates in `references/prompts.md`).\n\nPick the grammar from the concept:\n\n| Concept / tone | Mid-leg move |\n|---|---|\n| Product / luxury retail | slow half-orbit around the hero object, then continue past it |\n| Real estate / hospitality | steadicam glide through doorways; gentle crane-up in atria |\n| Industrial / process / logistics | low lateral track alongside the line, foreground parallax |\n| Travel / outdoors / campus | drone-style rise-and-reveal, then a descending swoop |\n| Food / craft / detail-driven | push in close to the craft moment, ease back, carry on |\n| Playful miniature (arch. B) | dives + aerial hops — the connector IS the grammar |\n\nHonest costs: expressive mid-leg moves raise re-roll odds — the model can end a fancy move\nin a state that isn't a clean forward drift. Mitigations: keep the final-second settle\nclause verbatim; **eyeball each leg's last frame before chaining the next** (it should look\nlike a frame from a gentle forward glide — if not, re-roll before wasting the next leg);\nbudget ~1 extra re-roll per expressive leg. A plain forward glide stays the zero-risk\ndefault — use it for legs where the scene itself is the show.\n\n**Locked-iso variant** (`CAMERA` = locked isometric glide): architecture A where every\nleg pins the view instead of taking a mid-leg move — \"the camera keeps exactly the same\nhigh isometric angle throughout, no rotation, no orbit, no tilt; it only travels\nstraight and level, the world sliding past beneath the same view\" (verbatim clause in\nprompts.md). The handoff contract is unchanged. Seedance drifts the angle slightly on\nlong legs — the existing eyeball-each-last-frame rule is the catch; re-roll a leg whose\nview has rotated. Calmest look, cheapest re-rolls, and the closest to the Emons\nreference.\n\nTwo related pacing knobs live in the engine (Step 7): per-section `scroll` (more scroll\ndistance = longer dwell in that scene) and `linger` (the camera settles mid-scene exactly\nwhile the copy peaks, then picks up speed toward the seam). Prefer expressive motion in the\n*clip* and restraint in the *scrub mapping* — they compound.\n\nAnd remember scroll is a scrubber: visitors can scroll **up**, so every move also plays in\nreverse. That's free and expected — no extra work — but it's another reason seam velocity\nmust be consistent in both directions (a seam that reads fine forward reads as a stutter\nbackward too if velocity flips).\n\n**For B**, one camera flight per scene: starts high/outside, descends into the interior,\nstructure opens. Model: the chain model you picked above (default **`seedance_2_0`**),\n`--start-image = the scene still`.\n\n- Use the **solid-background still** (not the knocked-out transparent one) as the\n  start image, so the video has a full frame.\n- Prompt: \"Single continuous cinematic camera move, no cuts. Begin high and far looking\n  at the whole <scene> from outside … descend and fly inside toward <focal point> … the\n  roof/walls gently open to reveal the interior. <style>, smooth graceful slow motion.\n  No text.\" (Template in `references/prompts.md`.)\n- Params (seedance): `--mode std --resolution 1080p --aspect_ratio 16:9 --duration 8`.\n  For Kling: drop `--resolution` (no such param), add `--sound off`, `--duration 10`.\n  Do **not** pass `--generate-audio` (it errors on seedance; audio is wasted anyway —\n  you'll mute).\n- Run concurrently, detached, then download each `.result_url`. Re-roll individual\n  failures. Keep the raw 1080p sources — you need their frames next.\n\n---\n\n## Step 5 — Connectors (architecture B only)\n\nSkip this whole step for architecture **A** — the forward take has no connectors; its legs\nalready chain seamlessly. This step applies to **B** (diorama/miniature), and note the\nreversal caveat from Step 4.\n\nThe connector clips are what make the world feel *connected* instead of cut. A connector\nflies from the end of scene i out and into the start of scene i+1. **Both of its\nendpoints must be the ACTUAL RENDERED FRAMES of the neighbouring clips — never the\noriginal diorama still.**\n\nWhy: every Higgsfield generation renders slightly differently. If a connector *ends* on\na fresh render of \"the kitchen diorama,\" but the next dive clip *starts* on its own\ndifferent render of that same diorama, the two won't match and you get a pop at the seam.\nThe fix is to hand off the exact pixels:\n\n```\nFor each connector between dive_i and dive_{i+1}:\n  start-image = the LAST frame extracted from dive_i's rendered video\n  end-image   = the FIRST frame extracted from dive_{i+1}'s rendered video\n```\n\nNow every seam is frame-identical on *both* sides:\n`dive_i.end == connector.start` and `connector.end == dive_{i+1}.start`.\n\nExtract the boundary frames from the rendered dives (not the stills):\n\n```bash\nffmpeg -sseof -0.15 -i dive_i.mp4   -frames:v 1 -q:v 2 dive_i_last.png    # interior of i\nffmpeg -ss 0      -i dive_{i+1}.mp4 -frames:v 1 -q:v 2 dive_next_first.png # establishing of i+1\n```\n\nGenerate the connector (`--duration 5` is plenty). Connectors need `--end-image`, so the\nmodel must accept it — any roster model does (`seedance_2_0`, `seedance_2_0_mini`,\n`kling3_0`):\n\n```bash\nhiggsfield generate create \"$VMODEL\" \\\n  --prompt \"$(cat connector_i.txt)\" \\\n  --start-image dive_i_last.png --end-image dive_next_first.png \\\n  $VOPTS --aspect_ratio 16:9 --duration 5 --wait --json\n# seedance: VOPTS=\"--mode std --resolution 1080p\"; kling3_0: VOPTS=\"--mode std --sound off\"\n```\n\nConnector prompt: \"Single continuous camera move, no cuts. Pull up and back out of\n<scene i>, rise into the sky, glide across the connected miniature world, and arrive\nabove <scene i+1>, beginning to descend toward it. Seamless flowing aerial transition.\n<style>. No text.\" (Template in `references/prompts.md`.)\n\nInsurance: Seedance lands *close* to the end-image but not always pixel-perfect, so the\nengine still applies a **short crossfade** (a few frames) at each seam. Frame-matched\nendpoints + a small crossfade = no visible cut. Never skip the actual-frame handoff and\nrely on the crossfade alone; a big content jump can't be hidden by a crossfade.\n\n---\n\n## Step 6 — Encode for smooth scrubbing\n\nScrubbing = setting `video.currentTime` from scroll. Two things matter, and they are\noften gotten wrong:\n\n1. **Seekability, not keyframe density, is what makes scrubbing work.** Many static\n   hosts (and `python -m http.server`) don't serve HTTP byte-range requests, which pins\n   `video.seekable` to `[0,0]` and clamps *every* seek to frame 0 — the video looks\n   frozen. The robust fix is to **fetch each clip as a `Blob` and play it from an\n   in-memory object URL** (blobs are always fully seekable). The engine does this.\n   Because of it, you do **not** need all-intra video.\n2. **Don't shrink quality to get smooth seeks.** Encode at the **native resolution**\n   (1080p from Seedance — don't downscale), `crf ~20`, a **small GOP** (`-g 8`) rather\n   than all-intra (all-intra bloats an 8s clip to ~25 MB; GOP 8 is ~8 MB and scrubs\n   fine via blob). Strip audio, add faststart, and a light `unsharp` counters video\n   softness:\n\n```bash\nffmpeg -i src.mp4 -an -vf \"unsharp=5:5:0.8:5:5:0.0\" \\\n  -c:v libx264 -preset slow -crf 20 -pix_fmt yuv420p \\\n  -g 8 -keyint_min 8 -sc_threshold 0 -movflags +faststart out.mp4\n```\n\nEncode all 2N-1 clips (dives + connectors) with the same settings for uniform quality.\n\n**Mobile encodes (only if the user opted in at Step 1.6).** The mobile version is\nthe **native 9:16 portrait chain** (pipeline.md §6b): portrait renders of every dive and\nconnector, encoded **720 wide (`scale=720:-2`), `-g 4`** (more keyframes = cheaper seeks —\nphone decoders' seek cost scales with GOP length), crf 23 — wired as `clipMobile` /\n`connectorsMobile`, with each portrait dive's first frame extracted as the section's\n`stillMobile` poster (Step 7). The engine serves them automatically on phones and falls\nback to the desktop clip when absent. The 16:9 centre-crop `encm()` encodes\n(pipeline.md §6) are a **fallback only** — for when credits can't cover the portrait\nchain — and shipping them must be called out to the user, never silent. If the user chose\ndesktop-only, skip this — the engine still hardens phone scrubbing regardless\n(seek-coalescing, iOS priming), so the page degrades gracefully rather than breaking.\n\n---\n\n## Step 7 — Assemble the page\n\nCopy `references/scrub-engine.js` (and, if you want a fully standalone page, the tiny\n`references/index-template.html`) into the user's project — or adapt into their\nframework. It's config-driven and self-contained:\n\n```js\nmountScrollWorld(document.getElementById('world'), {\n  brand: { name: 'Pearl & Co.' },\n  diveScroll: 1.3, connScroll: 0.9,          // viewport-heights of scroll per clip\n  sections: [\n    { id:'farm', label:'The Farms', still:'assets/farm.webp',\n      clip:'assets/vid/farm.mp4',\n      clipMobile:'assets/vid/farm-m.mp4',      // mobile opt-in only: native 9:16 render\n      stillMobile:'assets/farm-m.webp',        // its first frame as the portrait poster\n      scroll: 1.6, linger: 0.45,   // optional pacing: longer dwell + camera settles mid-scene\n      accent:'#8FB98A', eyebrow:'From leaf to last sip', title:'It starts in the hills.',\n      body:'…', tags:['Single-origin','Hand-picked'] },\n    // …one per section; last may carry a `cta`\n  ],\n  connectors:       ['assets/vid/conn1.mp4','assets/vid/conn2.mp4',   /* … length = sections-1 */],\n  connectorsMobile: ['assets/vid/conn1-m.mp4','assets/vid/conn2-m.mp4' /* … same length; mobile opt-in only */],\n});\n```\n\nThe engine handles: the ordered dive/connector chain, scroll→currentTime with rAF\nsmoothing, blob loading, lazy prefetch of nearby clips, frame-matched crossfades, pinned\nper-section copy (first section greets on landing, last holds its CTA), a route rail,\n`prefers-reduced-motion`, and mobile. **Pacing per section:** `scroll` overrides\n`diveScroll` for that scene (more scroll = longer dwell) and `linger` (0–1, keep ≤ 0.6)\nremaps time so the camera settles mid-scene — exactly while the copy peaks — then speeds\nup toward the seam; seam frames are untouched (f(0)=0, f(1)=1). Give the hero and finale\nscenes a higher `scroll` + some `linger`; keep transit scenes brisk. Theme it with CSS variables (`--accent`,\n`--sw-bg`, `--sw-ink`, …) — the visual identity comes from the generated clips, so the\nchrome stays quiet. See the header of `scrub-engine.js` for the full config + CSS vars.\n\n**On phones the engine adapts automatically** (coarse pointer or ≤860px): it serves\n`clipMobile` / `connectorsMobile` when present, **coalesces seeks** (never queues a new\n`currentTime` while the decoder is still seeking — this is what stops a fast flick from\nfreezing the clip), **keeps the still as a poster until the clip paints its first frame**\nand **primes each video on first touch** (fixes iOS's blank-until-played video), drops the\ndrifting particles, ignores URL-bar-only resizes (no scroll jump), and uses safe-area\ninsets so copy clears the notch/home indicator. All of this hardening is on by default —\nno config needed. The `clipMobile`/`connectorsMobile` encodes are the opt-in part\n(Step 1.6): only wire them when the user asked for the mobile version.\n\nFor non-JS backends (Python/Rails/etc.): serve the assets and drop the engine `<script>`\ninto the rendered HTML; nothing about it is framework-specific.\n\n---\n\n## Step 8 — QA the seams (don't skip)\n\nDrive the page in a headless browser and **verify frame continuity at the seams**, which\nis the thing most likely to be wrong:\n\n- Screenshot at scroll positions just before and just after each seam. The two frames\n  must be near-identical (the dive's last frame == the connector's first frame). If they\n  pop, you used the diorama still instead of the actual rendered frame (redo Step 5), or\n  the crossfade band is too short. Calibration: judge seams by *composition*, not raw\n  PSNR — at 720p/1080p a correctly frame-locked seam can read ~18–25 dB from detail\n  shimmer alone (observed on a verified-good build); a real mismatch shows as different\n  composition/props, not just softness.\n- Check the console for errors, confirm `video.seekable.end(0) > 0` (blob working), and\n  that `currentTime` tracks scroll across each clip's band.\n- **Mobile — full checklist only if the user opted into the mobile version (Step 1.6).**\n  For a desktop-only build, just sanity-check a phone viewport once: page loads, still\n  posters show, nothing overlaps — the engine's hardening covers graceful degradation.\n  For the mobile build (do this on a real phone or an emulated one, portrait + landscape):\n  - Emulate a phone viewport **with CPU throttled 4–6×** and scroll fast — the clip should\n    track without freezing (the seek-coalescing + `-m.mp4` encodes are what make this hold).\n  - Confirm the first scene shows immediately (its still is the poster) and the video takes\n    over the instant you scroll — no blank/black scene (the iOS priming fix). Test iOS Safari\n    specifically; it's the one that goes blank if this regresses.\n  - Verify the `-m.mp4` variant is actually served on mobile (Network panel), and the\n    heavy 1080p master on desktop. The mobile clips must be **natively portrait**\n    (`videoWidth < videoHeight` — not a downscaled 16:9 file), and the `stillMobile`\n    posters must be served and match each portrait clip's first frame (no\n    landscape→portrait flash when the video paints).\n  - Slowly scroll so the URL bar collapses — the page must **not jump** (height-only resizes\n    are ignored on touch). Rotate the device — layout should recompose cleanly.\n  - Only if the crop **fallback** shipped (no credits for the portrait chain): portrait\n    crops a 16:9 clip to its centre — confirm the focal subject still reads, and remind\n    the user this is the stopgap, not the mobile version.\n- Check reduced-motion (should fall back to the stills, no video, no particles).\n\n---\n\n## Gotchas (hard-won)\n\n- **Seam pop** → connector endpoints were the diorama stills, not the neighbouring\n  clips' actual frames. Always extract real frames (Step 5).\n- **Seam stutter / camera \"jumps backward\"** → even with frame-matched seams, if the\n  camera *velocity reverses* (forward dive, then a connector that pulls back out) it\n  reads as a rewind. This is inherent to architecture B. For any grounded walkthrough use\n  architecture A (one continuous forward take — legs chained from actual last frames, no\n  pull-back, no `--end-image`); see Step 4.\n- **Frozen video / stuck at frame 0** → `seekable=[0,0]`; the host isn't serving byte\n  ranges. Use blob URLs (engine does).\n- **Huge files** → you used all-intra. Use `-g 8` + blob instead.\n- **Soft / low quality** → you downscaled or over-compressed. Encode native 1080p,\n  crf ≤ 20, add `unsharp`. Video is inherently softer than the stills — keep the stills\n  as the lite fallback for max fidelity.\n- **Concurrent gens 503 / \"not_enough_credits\" race** → transient when many launch at\n  once; re-roll the individual failure, it's not really out of credits (verify with\n  `higgsfield workspace list`).\n- **NSFW false-positives (Seedance `status \"nsfw\"`)** → the video content filter flags\n  perfectly innocuous clips, especially **bedroom, pool, spa/wellness** contexts and\n  trigger words like \"bed\", \"pool\", \"waterfall\", \"wine\", \"swim\". It's partly the prompt\n  wording and partly the reference frames. Fixes, in order: (1) re-roll — it's often\n  non-deterministic and passes on the 2nd–3rd try; (2) strip trigger words and add\n  \"empty, unoccupied, no people, no figures, architectural, tasteful\"; (3) regenerate\n  just that clip on **`kling3_0`** with the same start/end frames — a different\n  provider's filter often passes what Seedance blocks. Expect a slight render-character\n  shift on that one clip (each model has its own grain/motion feel); for a 5s connector\n  behind a crossfade that usually beats option (4): set the connector slot to `null` —\n  the engine crossfades that seam directly (optional connectors), so the page still\n  completes. Budget extra credits/time for these re-rolls on interiors/real-estate content.\n- **Dark / custom theme** → the engine wraps its default tokens in `@layer sw`, so a\n  page-level `:root` / `.sw-root { --sw-bg; --sw-ink; --sw-accent; --sw-font-* }` block\n  wins cleanly (no specificity hacks). `--sw-ink` is your primary **text/heading** colour;\n  the **accent** fills the primary button and active nav. For a dark theme, set `--sw-bg`\n  dark and `--sw-ink` light — the copy scrim and title shadow follow `--sw-bg` automatically.\n- **Phone scrub stutters / freezes on a fast flick** → the 1080p master is too heavy for a\n  phone decoder and seeks pile up. Ship the `-m.mp4` mobile encodes (720p, `-g 4`) and wire\n  `clipMobile`/`connectorsMobile` (Step 6/7). The engine already coalesces seeks; the lighter\n  encode is the other half. Still choppy on a low-end device? Tighten GOP (`-g 2` / all-intra).\n- **Blank / black scene on iOS (desktop was fine)** → an iOS Safari quirk: a muted video that\n  was never played won't paint a seeked frame. The engine fixes this by keeping the still as a\n  poster until the clip paints and priming each video on first touch — so **don't** hide the\n  still on `loadedmetadata` or strip the `playsinline`/`muted` attributes if you adapt the\n  engine into a framework.\n- **Page jumps while scrolling on mobile** → something is re-running layout on the URL-bar\n  show/hide `resize`. The engine ignores height-only resizes on touch; if you ported it, gate\n  your resize handler on a width change (keep the `orientationchange` path for rotation).\n- **Copy hidden behind the URL bar / notch on mobile** → use the engine's safe-area-aware\n  bottom offset (`env(safe-area-inset-bottom)` + `dvh`); make sure the page's\n  `<meta viewport>` includes `viewport-fit=cover` (the template does).\n- **Portrait crops the scene** → a 16:9 clip on a tall phone shows only its centre — which\n  is why the mobile version is the native 9:16 chain (§6b), never the crop. If you're seeing\n  this on a mobile build, either the crop fallback shipped (call it out to the user) or the\n  9:16 encodes aren't actually being served (check `videoWidth < videoHeight`). Keeping each\n  scene's focal subject centred (prompts.md) still matters for the desktop film itself.\n- **`--generate-audio` errors on seedance** → omit it; mute in HTML and `-an` on encode.\n- **Kling rejects your flags** → `kling3_0` has **no `--resolution` param** (don't pass\n  one; encode at whatever native res ffprobe reports) and **sound defaults on** — pass\n  `--sound off`. Duration default is 5; legs/dives want 10.\n- **Seam pop only where you \"saved credits\"** → you swapped models mid-chain, or used a\n  start-image-only model where a connector needs an `--end-image`. One model for the whole\n  chain; the only cheap tier is `seedance_2_0_mini`, which keeps frame-locking so it stays\n  seamless. (Any model with reference-only inputs can't hold a seam at all — Step 4.)\n- **Monid seedance rejects inline images** → \"Must be a public https:// URL or an\n  asset://<id> reference\": frames go through the free `sfs` file system\n  (put → `curl -T` → cat → signed URL; pipeline.md → Monid backend), never base64.\n  Quirk: `/put` echoes back `home/<path>`, but `/cat` and `/ls` want the **original\n  relative path** you gave `/put` — using the echoed path 404s.\n- **Monid clip wrong aspect** → the `ratio` default is adaptive and follows the input\n  image (a 3:2 still → a 4:3-ish video). Pass `--ratio` — `16:9` desktop, `9:16`\n  mobile chain — explicitly on every chained clip.\n- **Monid CLI \"Polling timed out after 120s\"** → only the local wait died; the run\n  continues server-side. Re-poll with `monid runs get -r <runId> -w 120` (find the id\n  in `monid runs list`). Result URLs expire (~24–48 h) — download immediately.\n- **Monid minimax drops the image when a prompt is present** → `prompt` +\n  `first_frame_image` together returns an unrelated t2v clip AND bills the wrong matrix\n  cell ($0.56 vs $0.28 observed). Image-only frame-locks but has no camera control.\n  Until the wrapper is fixed, that endpoint can't chain — use Monid's seedance-2.0.\n  (The model itself is fine — the same prompt+image via Higgsfield `minimax_hailuo`\n  frame-locks.)\n- **Monid billing surprises** → matrix/token-priced endpoints bill by selector match:\n  pass every selector field explicitly (`model`, `resolution`, `duration`) and read\n  `cost.value` off the run result after each clip. A big base64 field in any body can\n  also bounce as an HTML error page (\"Unexpected token '<'\") — another reason frames\n  travel by sfs URL.\n- **Monid schema changed since last build** → it happens (seedance was t2v-only until\n  late July 2026, then gained first/last-frame support). `monid inspect` before each\n  build; re-run the Step 4 qualification probes when the Input schema differs from\n  what pipeline.md documents.\n- **Codex stills hang at \"Reading additional input from stdin...\"** → parallel\n  `codex exec` calls launched from one script share the parent's stdin; one wins it,\n  the rest block forever (observed: 1 of 3 completed, 2 hung, the second batch never\n  started). Always append `< /dev/null` to every backgrounded `codex exec` — the\n  pipeline's `gen_still_codex` has it; keep it if you adapt the command.\n- **White-box scenes** → `gpt_image_2` returns a solid bg; either match the page bg to it\n  or knock it out (Step 3).\n- **bash 3.2** on macOS → no associative arrays in scripts.\n- **Connector grabs the wrong scene's frames** (or errors on a frame that doesn't exist\n  yet) → the array loop ran in **zsh** (macOS default interactive shell), where arrays are\n  1-indexed, not bash's 0-indexed. Keep every array-driven chain step in a `#!/bin/bash`\n  script run via `bash script.sh` — never inline array loops in the interactive shell.\n\n## References\n\n- `references/prompts.md` — the intake checklist, style-preamble pattern, and every\n  prompt template (scene still, dive, connector) with fill-in slots.\n- `references/pipeline.md` — copy-paste batch scripts for the whole run (generate →\n  extract frames → connectors → encode → mobile encode), bash-3.2-safe.\n- `references/scrub-engine.js` — the portable, config-driven scrub engine (builds DOM +\n  injects CSS; blob-seek, lazy load, seam crossfade, copy, route rail, reduced-motion, and\n  phone hardening: mobile encodes, seek-coalescing, iOS priming, safe-area, no-jump resize).\n- `references/index-template.html` — a minimal standalone page that mounts the engine.\n- `references/knockout.py` — border-connected background knockout for floating scenes.\n","skills/scroll-world/references/pipeline.md":"# Pipeline: copy-paste scripts (bash 3.2 safe)\n\nSet these once. `NAMES` is the ordered section ids; the last is the hero/finale.\n\n```bash\nWORK=/tmp/scroll-world           # scratch dir for prompts, sources, frames\nASSETS=./assets                  # where the site reads stills (webp) + clips (mp4)\nmkdir -p \"$WORK\" \"$ASSETS/vid\"\nNAMES=\"farm kitchen shop delivery plaza finale\"   # <-- your section ids, in order\n\n# Chain video model — ONE for every chained clip (SKILL Step 4 roster).\n# Must accept --start-image AND --end-image (verify: higgsfield model get <model>):\n# seedance_2_0 | kling3_0 | seedance_2_0_mini (draft tier). Reference-only models can't\n# hold a seam; models without --mode (e.g. kling3_0_turbo) need their own flag branch below.\n# DEFAULT backend: Monid pay-per-clip (§7 — gen_dive_monid/gen_conn_monid with\n# VRES=1080p|720p|480p instead of VOPTS). The gen_dive/gen_conn functions in §2/§4\n# below are the Higgsfield-credits FALLBACK (and the only home of kling3_0/mini).\nVMODEL=seedance_2_0\ncase \"$VMODEL\" in                                  # per-model flags + durations (bash 3.2 safe)\n  kling3_0)          VOPTS=\"--mode std --sound off\";          DIVE_DUR=10; CONN_DUR=5 ;;  # no --resolution param on Kling\n  seedance_2_0_mini) VOPTS=\"--mode std --resolution 720p\";    DIVE_DUR=8;  CONN_DUR=5 ;;  # cheap frame-locked previz\n  *)                 VOPTS=\"--mode std --resolution 1080p\";   DIVE_DUR=8;  CONN_DUR=5 ;;  # seedance_2_0 default\nesac\n```\n\nHiggsfield generations take minutes — every `higgsfield ... --wait` call below is meant\nto run inside a **backgrounded** script. Launch the whole script with your tool's\nbackground/detached mode and poll the progress log; never block the foreground.\n\n## 1. Scene stills (Step 2)\n\nWrite one prompt file per section to `$WORK/still_<name>.txt` (see prompts.md), then:\n\n```bash\ngen_still() { # name\n  higgsfield generate create gpt_image_2 --prompt \"$(cat \"$WORK/still_$1.txt\")\" \\\n    --aspect_ratio 3:2 --resolution 2k --quality high --wait --wait-timeout 15m --json \\\n    > \"$WORK/still_$1.json\" 2> \"$WORK/still_$1.err\"\n  url=$(jq -r '.[0].result_url // empty' \"$WORK/still_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/still_$1.png\" && echo \"still $1 ok\" || echo \"still $1 FAIL\"\n}\nfor n in $NAMES; do gen_still \"$n\" & done ; wait\n```\n\nCodex variant (STILLS_SOURCE=codex, SKILL Step 1.7 — subscription-billed, zero\ncredits; ~1–3 min each, parallelize in small batches):\n\n```bash\ngen_still_codex() { # name   (< /dev/null is REQUIRED for parallel calls — see SKILL Gotchas)\n  codex exec -C \"$WORK\" -s workspace-write --skip-git-repo-check \\\n    'Use the image generation tool ($imagegen) to generate: '\"$(cat \"$WORK/still_$1.txt\")\"' Wide 3:2 landscape, high resolution. Save it as ./still_'\"$1\"'.png. Do not do anything else.' \\\n    > \"$WORK/still_$1.codex.log\" 2>&1 < /dev/null\n  [ -f \"$WORK/still_$1.png\" ] && echo \"still $1 ok (codex)\" || echo \"still $1 FAIL (see .codex.log)\"\n}\n```\n\nConvert to webp for the site (and optionally run knockout.py first for transparency):\n\n```bash\nfor n in $NAMES; do cwebp -quiet -q 84 -resize 1800 0 \"$WORK/still_$n.png\" -o \"$ASSETS/$n.webp\"; done\n```\n\nReview the stills for cohesion before continuing. Re-roll any off-style one (optionally\nadd `--image \"$WORK/still_<good>.png\"` to lock style).\n\n## 2. Dive-in clips (Step 4)\n\nPrompt files at `$WORK/dive_<name>.txt`. Start image = the solid-bg still PNG.\n\n```bash\ngen_dive() { # name                       ($VOPTS is unquoted on purpose — word-split flags)\n  higgsfield generate create \"$VMODEL\" --prompt \"$(cat \"$WORK/dive_$1.txt\")\" \\\n    --start-image \"$WORK/still_$1.png\" \\\n    $VOPTS --aspect_ratio 16:9 --duration \"$DIVE_DUR\" \\\n    --wait --wait-timeout 20m --json > \"$WORK/dive_$1.json\" 2> \"$WORK/dive_$1.err\"\n  url=$(jq -r '.[0].result_url // empty' \"$WORK/dive_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/dive_$1.mp4\" && echo \"dive $1 ok\" || echo \"dive $1 FAIL\"\n}\nfor n in $NAMES; do gen_dive \"$n\" & done ; wait\n```\n\nRe-roll individual failures (503 / credit race are transient):\n`gen_dive shop`  (just that one).\n\n## 3. Extract boundary frames — the seam handoff (Step 5)\n\nFor each adjacent pair, the connector's start = dive_i's LAST frame, end = dive_{i+1}'s\nFIRST frame — extracted from the **rendered videos**, never the stills.\n\n```bash\nset -- $NAMES\nprev=\"\"\nfor n in \"$@\"; do\n  ffmpeg -v error -ss 0 -i \"$WORK/dive_$n.mp4\" -frames:v 1 -q:v 2 \"$WORK/first_$n.png\"      # establishing\n  ffmpeg -v error -sseof -0.15 -i \"$WORK/dive_$n.mp4\" -frames:v 1 -q:v 2 \"$WORK/last_$n.png\" # interior\ndone\n```\n\n## 4. Connector clips (Step 5)\n\nPrompt files at `$WORK/conn_<i>.txt` (i = 1..N-1). Iterate adjacent pairs:\n\n```bash\ngen_conn() { # i startPng endPng          (end-image required → seedance/kling3_0 only)\n  higgsfield generate create \"$VMODEL\" --prompt \"$(cat \"$WORK/conn_$1.txt\")\" \\\n    --start-image \"$2\" --end-image \"$3\" \\\n    $VOPTS --aspect_ratio 16:9 --duration \"$CONN_DUR\" \\\n    --wait --wait-timeout 20m --json > \"$WORK/conn_$1.json\" 2> \"$WORK/conn_$1.err\"\n  url=$(jq -r '.[0].result_url // empty' \"$WORK/conn_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/conn_$1.mp4\" && echo \"conn $1 ok\" || echo \"conn $1 FAIL\"\n}\nset -- $NAMES ; i=0 ; prev=\"\"\nfor n in \"$@\"; do\n  if [ -n \"$prev\" ]; then i=$((i+1)); gen_conn \"$i\" \"$WORK/last_$prev.png\" \"$WORK/first_$n.png\" & fi\n  prev=\"$n\"\ndone ; wait\n```\n\n## 5. Encode everything for scrubbing (Step 6)\n\nNative resolution (1080p from seedance std; kling3_0 std returned **720p** in testing —\nnever upscale, encode what ffprobe reports), crf 20, GOP 8, light sharpen, no audio,\nfaststart. Same for dives + connectors.\n\n```bash\nenc() { ffmpeg -v error -y -i \"$1\" -an -vf \"unsharp=5:5:0.8:5:5:0.0\" \\\n  -c:v libx264 -preset slow -crf 20 -pix_fmt yuv420p \\\n  -g 8 -keyint_min 8 -sc_threshold 0 -movflags +faststart \"$2\"; echo \"enc $2 $(du -h \"$2\"|cut -f1)\"; }\n\nfor n in $NAMES; do enc \"$WORK/dive_$n.mp4\" \"$ASSETS/vid/$n.mp4\"; done\ni=0; for f in \"$WORK\"/conn_*.mp4; do i=$((i+1)); enc \"$f\" \"$ASSETS/vid/conn$i.mp4\"; done\n```\n\nNow the engine config's `sections[k].clip = assets/vid/<name>.mp4` and\n`connectors = [assets/vid/conn1.mp4, …]` (length N-1, in order).\n\n## 6. Centre-crop mobile encodes — FALLBACK ONLY, not the mobile version\n\n**The mobile version is the native 9:16 portrait chain (§6b).** This section's crop\nencodes exist for one case: the user opted into mobile but credits can't cover the\nportrait chain — and shipping them must be called out and approved, never silent\n(portrait phones will see the landscape film's centre ~26%). The encode mechanics\nmatter either way: scrubbing sets `currentTime` every frame, and a phone decoder's\n**seek cost scales with how many frames it must decode from the nearest keyframe** — so\na 1080p `-g 8` master that scrubs fine on a laptop stutters on a phone. A **smaller\nframe + tighter GOP** fixes that (and halves the bytes on cellular). The crop `-m.mp4`\nsibling per clip:\n\n```bash\n# 720p, GOP 4 (twice the keyframes = ~half the seek-decode work), crf 23, same sharpen/faststart.\nencm() { ffmpeg -v error -y -i \"$1\" -an -vf \"scale=-2:720,unsharp=5:5:0.6:5:5:0.0\" \\\n  -c:v libx264 -preset slow -crf 23 -pix_fmt yuv420p \\\n  -g 4 -keyint_min 4 -sc_threshold 0 -movflags +faststart \"$2\"; echo \"encm $2 $(du -h \"$2\"|cut -f1)\"; }\n\nfor n in $NAMES; do encm \"$WORK/dive_$n.mp4\" \"$ASSETS/vid/$n-m.mp4\"; done\ni=0; for f in \"$WORK\"/conn_*.mp4; do i=$((i+1)); encm \"$f\" \"$ASSETS/vid/conn$i-m.mp4\"; done\n```\n\nWire the variants in the engine config — the engine serves them automatically on phones,\nfalling back to the desktop `clip` when a mobile one is absent:\n\n```js\nsections[k].clipMobile = 'assets/vid/<name>-m.mp4';\nconnectorsMobile = ['assets/vid/conn1-m.mp4', …];   // length N-1, in order\n```\n\nIf phone scrubbing still stutters, tighten the GOP further (`-g 2`, or `-g 1` for all-intra\n= instant seeks at the cost of larger files); if cellular weight is the bigger worry, raise\n`crf` (24–26) or drop to `scale=-2:600`. If the master is already 720p (e.g. kling3_0 std),\nthe mobile encode still pays off — the tighter GOP is what makes phone seeks cheap. All-mobile encodes stay 16:9 — the engine\ncentre-crops them; see the portrait note in SKILL Step 8 / prompts.md.\n\n## 6b. Native 9:16 portrait chain — THE mobile version (Step 1.6 opt-in)\n\nWhen the user opts into mobile, this is what they get: a **parallel 9:16 chain** rendered\nnatively for phones and shipped as the mobile variants — never the §6 crops (those are the\nno-credits stopgap). Same seam laws as the main chain — the portrait chain frame-locks\nagainst its own rendered frames, never the landscape ones. Budget ~2N-1 video gens +\nre-rolls (interiors trip the NSFW filter in portrait too); state the credit cost at the\nStep 1.6 interview.\n\n1. **Portrait start canvases.** Don't hand the video model a 3:2 still and hope: composite\n   each scene onto a 1080×1920 canvas in the page bg colour (island at ~94% width, visual\n   centre at ~45% height). The render then opens exactly on what the portrait poster shows.\n   For knocked-out stills, composite the RGBA over the bg colour first.\n2. **Dives/legs**: same prompt templates with a portrait clause up front (\"Vertical\n   portrait composition, the diorama centered with generous [bg] space above and below\"),\n   `--aspect_ratio 9:16`, same model/params as the main chain. Review each last frame\n   before chaining, as ever.\n3. **Connectors**: extract first/last frames **from the 9:16 renders** and generate 9:16\n   connectors between them. A native 9:16 scene mixed into cropped-16:9 neighbours pops at\n   both seams — the portrait chain must be complete, not partial.\n4. **Encode** with the §6 settings but portrait-oriented scale: `scale=720:-2` (720 wide),\n   `-g 4`, crf 23 → these ARE the `-m.mp4` mobile files (and they replace any §6 crop\n   stopgaps that shipped earlier).\n5. **Posters**: extract each 9:16 dive's first frame → webp → wire as the section's\n   `stillMobile` so the poster matches the portrait video's frame 0 (no landscape→portrait\n   flash when the clip paints). Engine support: `sections[k].stillMobile`.\n\n## 7. Monid backend — Seedance 2.0 pay-per-clip (the DEFAULT; qualified 2026-07-25)\n\n`bytedance /v1/video/seedance-2.0` via Monid is the roster's `seedance_2_0` billed\nper clip in USD, and the **default** chain backend (SKILL Step 4 → Monid backend;\nboth probes passed) — use these functions in the §2/§4 loops unless the build fell\nback to Higgsfield credits. Same chain laws as everywhere else — only the I/O\ndiffers: **frames ride Monid's free `sfs` file\nsystem** (inline base64 is rejected), **`ratio` must be explicit** (the adaptive\ndefault follows the input image's aspect), and runs are fire-and-poll. Token-priced\n`w×h×24×sec/1024` at $7/1M (480p/720p), $7.7/1M (1080p) — measured: 1080p ≈ $2.99\ndive / $1.87 connector; 720p ≈ $1.21 / $0.76; 480p previz ≈ $0.28 / $0.35.\n\n```bash\n# helper: upload a local frame to sfs, print a signed public URL for it ($0).\n# NB: /cat and /ls take the SAME relative path you gave /put — not the\n# \"home/...\"-prefixed path /put echoes back (that one 404s).\nmonid_frame_url() { # localPng remoteName  (JPEG-compresses on the way up)\n  jpg=\"$WORK/sfs_$2.jpg\"\n  ffmpeg -v error -y -i \"$1\" -vf \"scale='min(1536,iw)':-2\" -q:v 2 \"$jpg\"\n  size=$(stat -f%z \"$jpg\")\n  up=$(NO_COLOR=1 monid run -p sfs -e /put \\\n    -i \"{\\\"path\\\":\\\"chain/$2.jpg\\\",\\\"sizeBytes\\\":$size,\\\"ttl\\\":\\\"1h\\\"}\" -w 60 -j \\\n    | jq -r '.output.uploadUrl')\n  curl -fsS -T \"$jpg\" \"$up\" > /dev/null\n  NO_COLOR=1 monid run -p sfs -e /cat -i \"{\\\"path\\\":\\\"chain/$2.jpg\\\",\\\"ttl\\\":\\\"1d\\\"}\" \\\n    -w 60 -j | jq -r '.output.url'\n}\n\n# fire-and-poll (the CLI's -w caps at 120s and seedance can exceed it)\nmonid_wait() { # runId outJson\n  while :; do\n    NO_COLOR=1 monid runs get -r \"$1\" -j > \"$2\" 2>/dev/null\n    case \"$(jq -r '.status // empty' \"$2\")\" in\n      COMPLETED|FAILED|BLOCKED|STOPPED|TIME_OUT) break ;;\n    esac\n    sleep 8\n  done\n}\n\ngen_dive_monid() { # name   (VRES=1080p|720p|480p; DIVE_DUR as usual)\n  furl=$(monid_frame_url \"$WORK/still_$1.png\" \"still_$1\")\n  jq -n --arg p \"$(cat \"$WORK/dive_$1.txt\")\" --arg u \"$furl\" --arg r \"$VRES\" \\\n    '{content:[{type:\"text\",text:$p},\n               {type:\"image_url\",image_url:{url:$u},role:\"first_frame\"}],\n      resolution:$r, duration:'\"$DIVE_DUR\"', ratio:\"16:9\", generate_audio:false}' \\\n    > \"$WORK/dive_$1.body.json\"\n  rid=$(NO_COLOR=1 monid run -p bytedance -e /v1/video/seedance-2.0 \\\n    -f \"$WORK/dive_$1.body.json\" -j | jq -r '.runId')\n  monid_wait \"$rid\" \"$WORK/dive_$1.json\"\n  url=$(jq -r '.output.content.video_url // empty' \"$WORK/dive_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/dive_$1.mp4\" \\\n    && echo \"dive $1 ok (\\$$(jq -r '.cost.value' \"$WORK/dive_$1.json\"))\" \\\n    || echo \"dive $1 FAIL ($(jq -r '.status' \"$WORK/dive_$1.json\"))\"\n}\n\ngen_conn_monid() { # i startPng endPng\n  su=$(monid_frame_url \"$2\" \"conn$1_start\"); eu=$(monid_frame_url \"$3\" \"conn$1_end\")\n  jq -n --arg p \"$(cat \"$WORK/conn_$1.txt\")\" --arg s \"$su\" --arg e \"$eu\" --arg r \"$VRES\" \\\n    '{content:[{type:\"text\",text:$p},\n               {type:\"image_url\",image_url:{url:$s},role:\"first_frame\"},\n               {type:\"image_url\",image_url:{url:$e},role:\"last_frame\"}],\n      resolution:$r, duration:'\"$CONN_DUR\"', ratio:\"16:9\", generate_audio:false}' \\\n    > \"$WORK/conn_$1.body.json\"\n  rid=$(NO_COLOR=1 monid run -p bytedance -e /v1/video/seedance-2.0 \\\n    -f \"$WORK/conn_$1.body.json\" -j | jq -r '.runId')\n  monid_wait \"$rid\" \"$WORK/conn_$1.json\"\n  url=$(jq -r '.output.content.video_url // empty' \"$WORK/conn_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/conn_$1.mp4\" \\\n    && echo \"conn $1 ok (\\$$(jq -r '.cost.value' \"$WORK/conn_$1.json\"))\" \\\n    || echo \"conn $1 FAIL ($(jq -r '.status' \"$WORK/conn_$1.json\"))\"\n}\n```\n\nDefault usage in §2/§4: same loops, `gen_dive_monid`/`gen_conn_monid` in place of\nthe Higgsfield `gen_dive`/`gen_conn` (mobile chain: `ratio:\"9:16\"` and the §6b\nportrait canvases). Previz: same functions with `VRES=480p`.\nResult URLs expire (~24–48 h) — the functions download immediately. Read the billed\n`cost.value` per clip (echoed in the ok-line) and `monid balance` between phases; a\n`BLOCKED` status is a workspace budget/run cap — terminal, surface it to the user.\nFrame extraction, encoding, QA: identical to the Higgsfield path.\n\n**Qualification harness for future/changed endpoints** (each probe = one cheap 480p\nclip): (1) prompt + first_frame from a real still → downloaded video's frame 0 must\nmatch the still (eyeball + PSNR ≳ 30 dB) and `cost.value` must match the advertised\ncell; (2) add a last_frame from a *different* still → the final frame must land on\nthat composition (Seedance-style near-miss ok — the crossfade covers it). Known\nfailure to watch for (it's why the harness exists): `minimax /v1/video_generation`\nstill silently drops the image when `prompt` is present — image-only output proves\nnothing about steerability.\n\n## Notes\n\n- `.[0].result_url` is the field on the `--wait --json` job object. `.min_result_url` is\n  a lower-res preview if you ever want it.\n- **NSFW fallback across models**: if one clip keeps getting flagged on seedance after\n  re-rolls + prompt scrubbing, regenerate just that clip on `kling3_0` with the SAME\n  start/end frames: `VMODEL=kling3_0; VOPTS=\"--mode std --sound off\"; gen_conn 3 …` —\n  then restore your chain model. See SKILL Gotchas for the trade-off.\n- **Previz on the cheap**: run the whole chain once with `VMODEL=seedance_2_0_mini`\n  (frame-locking intact, ~720p) to validate the journey and seams before spending\n  full-model credits — because it's still seamless, the previz translates directly to the\n  final render. Don't reach for reference-only models here: without `--start/--end-image`\n  they can't hold a seam, so their output can't be chained (Step 4 rule).\n- If a whole batch stalls, check `higgsfield workspace list` for credits and\n  `$WORK/*.err` for the reason.\n- Concurrency: launching ~5–6 gens at once is fine; much more can trigger transient\n  credit/race errors — stagger or re-roll.\n"},"files":{".claude-plugin/marketplace.json":"{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"scroll-world\",\n  \"description\": \"Immersive scroll-scrubbed 'fly through the world' landing pages, generated with Higgsfield.\",\n  \"owner\": {\n    \"name\": \"cyw\",\n    \"email\": \"cyw@cywang.me\"\n  },\n  \"plugins\": [\n    {\n      \"name\": \"scroll-world\",\n      \"source\": \"./\",\n      \"skills\": [\"./skills/scroll-world\"],\n      \"description\": \"Build an immersive scroll-scrubbed landing page where a pre-rendered camera flies from outside each scene into its interior, then flows on to the next with no cuts. Interviews the user for topic, story beats, and brand kit; generates cohesive scenes + seamless camera clips with Higgsfield; wires a portable, framework-agnostic scrub engine.\",\n      \"category\": \"design\"\n    }\n  ]\n}\n",".claude-plugin/plugin.json":"{\n  \"name\": \"scroll-world\",\n  \"description\": \"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. Interviews the user for the topic, story beats, and brand kit, then generates cohesive scenes + seamless camera clips and wires a portable, framework-agnostic scroll-scrub engine. Use for a '3D world' / 'browse-through-the-industry' hero, a scroll cinematic, or a diorama landing.\",\n  \"version\": \"0.8.0\",\n  \"author\": {\n    \"name\": \"cyw\",\n    \"email\": \"cyw@cywang.me\"\n  },\n  \"homepage\": \"https://github.com/oso95/scroll-world\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"higgsfield\",\n    \"monid\",\n    \"landing-page\",\n    \"scroll\",\n    \"video\",\n    \"seedance\",\n    \"diorama\",\n    \"webgl-alternative\",\n    \"cinematic\"\n  ]\n}\n","skills/scroll-world/references/prompts.md":"# Prompt templates & intake\n\nEverything here is fill-in-the-slots. Keep the **style preamble** byte-for-byte identical\nacross all scene stills — that identical text is what makes the world feel like one place.\n\n## Intake checklist (Step 1)\n\nCollect and write down:\n\n- `SUBJECT` — the business + one-line pitch.\n- `BRAND_NAME` — display name.\n- `PALETTE` — 4–6 named hexes, e.g. `taro #9B7EBD, cream #F5EDE0, caramel #C88A5A, matcha #8FB98A, plum #3A2E48`. Pick ONE as the scene **background** colour (usually the lightest) and one as the primary **accent**.\n- `TONE` — a word or two (cozy/premium, playful, industrial…).\n- `STYLE` — the art direction (default below).\n- `SECTIONS[]` — ordered list; for each: `id`, `label`, `subject` (what's in the diorama), `eyebrow`, `title`, `body` (≤ 1 sentence), `tags[]` (0–3). Last section = hero product + CTA.\n- `CAMERA` — fly-through (arch B: dives + aerial hops — the flagship-demo look) |\n  walkthrough (arch A: one continuous forward flight) | locked-iso (arch A + the\n  locked-iso clause below: one fixed angle for the whole film). **Always asked**\n  (SKILL Step 1.4), presented by feel with a one-line trade-off each.\n- `MOBILE` — yes/no. **Always asked** (SKILL Step 1.6), presented to the user\n  with the ~2× credit cost stated. Yes = the **native 9:16 portrait chain**\n  (pipeline §6b): portrait renders of every dive/connector +\n  `clipMobile`/`connectorsMobile`/`stillMobile` wiring + the full mobile QA. The\n  §6 crop encodes are a no-credits stopgap only.\n- `VIDEO_TIER` — default is `seedance_2_0` **via Monid** (pay-per-clip; previz =\n  same endpoint at 480p, final at 1080p). Higgsfield-credit alternates: draft\n  (`seedance_2_0_mini`) | standard (`seedance_2_0`) | alternate (`kling3_0`).\n  Chosen by cost at SKILL Step 1.7, with the estimated total stated before\n  anything renders.\n- `STILLS_SOURCE` — higgsfield (`gpt_image_2`, spends credits) | codex\n  (`image_gen`, subscription-billed; only offer when the Codex CLI is present).\n\n## Style preamble (default: clay diorama)\n\nReuse verbatim in every scene prompt. Swap the bracketed bits for the brand's palette/bg.\n\n```\nIsometric low-poly 3D diorama floating as a small rounded island on a plain solid\n[BG_HEX] background with a soft contact shadow beneath it. Soft matte clay 3D render,\nrounded toy-model shapes, gentle warm studio lighting, soft long shadows, tilt-shift\nminiature look. Cohesive color palette of [PALETTE]. Highly detailed, centered\ncomposition, absolutely no text, no letters, no numbers, no logos.\n```\n\nAlternate directions (swap the first two sentences, keep the palette/no-text tail):\n- **Flat papercraft:** \"Isometric layered paper-craft diorama, matte cardstock, clean die-cut edges, subtle drop shadows between layers.\"\n- **Glossy toy:** \"Isometric glossy vinyl-toy diorama, smooth plastic shading, soft rim light, collectible figurine look.\"\n- **Claymation:** \"Isometric stop-motion clay set, visible thumbprints, handmade plasticine texture, soft studio softbox light.\"\n- **Neon night:** \"Isometric miniature at night, warm interior glow and neon signage, moody rim light, wet reflective ground.\"\n- **Photoreal architectural** (real estate, hospitality, premium/luxury): \"Ultra-photorealistic architectural photography of a single cohesive [subject], cinematic wide-angle, warm golden-hour light, natural materials, restrained designer furnishings, a breathtaking view, editorial magazine quality (Architectural Digest), shallow depth of field, no people.\" For photoreal, drop the floating-island framing and the knockout (Step 3) — the scenes are **full-bleed** (a dark page background reads premium), the \"dive\" glides *through doorways/glass* rather than opening a roof, and cohesion comes entirely from the identical preamble (do NOT pass an `--image` reference — it clones the same room). Interiors trip Seedance's NSFW filter often; see SKILL Gotchas.\n\n## Scene still prompt (Step 2)\n\n```\n[STYLE PREAMBLE]\nSubject: [SECTION.subject — describe the miniature scene: the building/space, a few\ncharacters doing the work, the props that signal this stage of the business].\n```\n\nTips:\n- Name concrete props (they anchor the scene): tanks, cauldrons, conveyor, crates, awning, string lights, benches, scooters, map pins.\n- For the final \"hero product\" section, drop the diorama-island framing and prompt a\n  single oversized product centerpiece floating on the same background with a few small\n  orbiting props.\n- **Compose for the centre.** The page renders every clip `object-fit:cover`. Keep the\n  focal subject horizontally centred with a little headroom, and don't park anything\n  essential at the far left/right edges. Mobile ships its own native 9:16 chain\n  (pipeline §6b), so this is not about surviving a crop — but a centred composition makes\n  the portrait renders open cleanly from the same still, and it keeps the dive's focal\n  point where the camera actually flies.\n- Aspect `3:2`, `--resolution 2k --quality high`.\n\n## Leg prompt — architecture A, continuous forward take (Step 4)\n\n`--start-image = previous leg's ACTUAL last frame` (leg 0: the first scene's still).\n**No `--end-image`.** The bolded clauses are the motion-handoff contract — keep them\nverbatim; the mid-leg move is where the expression goes.\n\n```\nSingle continuous cinematic camera move, no cuts. **Continue the same slow, steady\nforward glide.** [MID-LEG MOVE — optional, from the library below.] The camera moves\ninto [SCENE i] toward [FOCAL POINT]. **In the final second, settle back into a slow,\nsteady forward glide toward [the doorway / opening / direction of the next scene].**\n[STYLE tail + PALETTE]. Smooth, graceful, slow motion, subtle parallax. No text, no captions.\n```\n\n### Mid-leg move library (pick by concept; omit for a plain glide)\n\nReversals are safe *inside* a leg (it's one continuous render) — only a seam may never\nreverse. That's why \"ease back out\" is fine mid-leg.\n\n**Locked-iso clause** (`CAMERA` = locked isometric glide — SKILL Step 1.4/Step 4):\nskip this library entirely and put this clause, verbatim, in the mid-leg slot of\nEVERY leg:\n\n```\nThe camera keeps exactly the same high isometric angle throughout — no rotation, no\norbit, no tilt. It only travels straight and level, the world sliding past beneath\nthe same view.\n```\n\nKeep the handoff-contract clauses around it unchanged. When checking each leg's last\nframe, also check the ANGLE hasn't drifted (Seedance rotates slightly on long legs) —\nre-roll the leg if the view has turned.\n\n- **Half-orbit** (product, luxury): \"sweeping in a slow half-orbit around [the hero\n  object], keeping it centered, then continuing past it\"\n- **Crane-up reveal** (scale, atriums, campuses): \"rising smoothly as the full scale of\n  [the space] reveals below\"\n- **Low lateral track** (production lines, counters, shelves): \"tracking low and level\n  alongside [the line], foreground objects sliding past in parallax\"\n- **Push-in + ease back** (craft, detail): \"pushing in close to [the craft moment] until\n  it nearly fills the frame, then easing gently back out\"\n- **Rise-and-swoop** (travel, outdoors): \"climbing in a gentle arc over [the terrain],\n  then swooping down toward [the next focal point]\"\n\nAfter rendering each leg, **check its last frame** before generating the next: it should\nread as a frame from a calm forward glide (no motion blur sideways, no half-finished\norbit). If it doesn't, re-roll this leg — a bad handoff frame poisons every leg after it.\n\n## Dive-in clip prompt (Step 4)\n\n`--start-image = the scene still` (solid-bg version).\n\n```\nSingle continuous cinematic camera move, no cuts. Begin high and far, looking down at the\nwhole [SECTION.subject] from outside like a tiny model. The camera slowly glides forward\nand descends toward it, sweeping in toward [FOCAL POINT — the counter/the cauldrons/the\npeople], as if flying inside. As the camera pushes in, the roof and upper structure\ngently lift and open away to reveal the warm interior. [STYLE tail: soft matte clay\ndiorama, tilt-shift miniature, warm light, [PALETTE]]. Smooth, graceful, slow motion,\nsubtle parallax. No text, no captions.\n```\n\nFor scenes with no building to open (a field, a plaza, a road), replace the roof clause\nwith \"the camera flies low across [the scene] toward [focal point].\"\n\nParams by chain model (SKILL Step 4 table): seedance —\n`--mode std --resolution 1080p --aspect_ratio 16:9 --duration 8`, no audio flag;\nkling3_0 — `--mode std --sound off --aspect_ratio 16:9 --duration 10` (no `--resolution`\nparam). Same for architecture-A legs.\n\n## Connector clip prompt (Step 5)\n\n`--start-image = dive_i LAST frame` (extracted), `--end-image = dive_{i+1} FIRST frame`\n(extracted). Both from the RENDERED videos, not the stills.\n\n```\nSingle continuous cinematic camera move, no cuts. The camera smoothly pulls up and back\nout of [SCENE i], rising into the sky, then glides forward across the connected miniature\nworld and arrives above [SCENE i+1], beginning to descend toward it. One connected\nminiature clay world, seamless flowing aerial transition. [STYLE tail + PALETTE]. Smooth\ngraceful slow motion. No text, no captions.\n```\n\nFor the last connector into a hero-product finale: \"…glides forward and the world\ndissolves toward a single giant [PRODUCT] floating in soft [BG] space, arriving in front\nof it.\"\n\nseedance: `--mode std --resolution 1080p --aspect_ratio 16:9 --duration 5`; kling3_0:\n`--mode std --sound off --aspect_ratio 16:9 --duration 5`. Connectors need `--end-image`\n→ use a roster model that accepts it (Step 4).\n\n## Copy per section (for the engine config)\n\n- `eyebrow` — 2–4 words, uppercase feel (a value-prop label).\n- `title` — 3–6 words, the beat's headline. First section = the site's hero line; last =\n  the payoff + it carries the CTA.\n- `body` — one sentence, plain-spoken, from the visitor's side.\n- `tags` — 0–3 short proof chips (e.g. \"Fresh-cooked\", \"30-min delivery\").\n","skills/scroll-world/SKILL.md":"---\nname: scroll-world\ndescription: >\n  Build an immersive scroll-scrubbed \"fly through the world\" landing page for any\n  industry or brand using Higgsfield. As the visitor scrolls, a pre-rendered camera\n  flies from outside each scene into its interior, then flows on to the next scene\n  with NO cuts — one continuous connected flight (Emons-style isometric diorama world,\n  or any art direction you pick). The skill interviews the user for the topic, the\n  story beats/sections, and brand kit, then generates cohesive scenes + seamless camera\n  clips with Higgsfield and wires a portable, framework-agnostic scroll-scrub engine.\n  The video chain renders through Monid by default (Seedance 2.0, pay-per-clip\n  USD — capability re-checked each build, see Step 4) with Higgsfield credits as\n  the fallback biller. Use when the user wants a \"3D world\" /\n  \"browse-through-the-industry\" hero, a scroll cinematic, a diorama landing, or to\n  turn a business into a scrollable world.\nallowed-tools: Bash, Read, Write, Edit, AskUserQuestion, Skill\n---\n\n# scroll-world\n\nProduces a landing page where **scroll drives a camera**: it dives from outside a scene\ninto its interior, then flies out and into the next scene, continuously, with no visible\ncuts. The visuals are AI-generated — stills via Higgsfield (or Codex), the video chain\nvia **Monid by default** (pay-per-clip Seedance 2.0; Higgsfield credits as fallback) —\nand the page just scrubs pre-rendered video by scroll position. This is the same technique behind Apple's scroll-through product\npages — the camera genuinely moves, scroll only drives time.\n\n**What you generate:** N scene stills → N \"dive-in\" camera clips → N-1 \"connector\" clips\nthat join consecutive scenes seamlessly → a portable scrub engine that plays the whole\nchain as one flight.\n\n**The one rule that makes or breaks it:** seams must be *frame-identical*. Read\n[The seamless chain](#step-5--the-seamless-chain-the-critical-part) before generating any\nconnector. Getting this wrong is the single most common failure and produces a visible\n\"pop\" between scenes.\n\nDo not assume a frontend framework. The scrub engine in `references/scrub-engine.js` is\nself-contained vanilla JS (it builds its own DOM + injects its own CSS into a container\nyou give it), so it drops into plain HTML, Next.js, Vue, a Python-served page, anything.\nThe value of this skill is the Higgsfield pipeline, the prompts, and the seam method —\nnot the framework.\n\n---\n\n## Step 0 — Bootstrap\n\n1. **Monid CLI — the default video-chain backend.** Check `monid --version`,\n   `monid keys list` (active key) and `monid balance` — the chain is billed per\n   clip in USD (Step 1.7 has the numbers; a 1080p N=6 chain ≈ $27). If the CLI is\n   missing or the balance can't cover the chain, say so and fall back to\n   rendering the chain on Higgsfield credits instead — same model, same\n   pipeline, different biller (Step 4 → Monid backend).\n2. **Higgsfield CLI — still required even on the Monid path**: it renders the\n   scene stills (`gpt_image_2`) and is the home of the `kling3_0` NSFW fallback\n   and the fallback chain. If `higgsfield` is not on `$PATH`, install per the\n   `higgsfield-generate` skill. If `higgsfield workspace list` fails auth, ask the user\n   to run `higgsfield auth login` (interactive OAuth — you cannot run it) and, if needed,\n   `higgsfield workspace set <id>`. Confirm credits cover the stills (~N image\n   gens) — plus `(2N-1)` video gens if the chain falls back here.\n3. **ffmpeg / ffprobe** on `$PATH` (frame extraction + encoding).\n4. **An image tool** for background knockout if you want floating scenes: PIL\n   (`python3 -c \"import PIL\"`), or `cwebp`/`sips`. Optional — see Step 3.\n5. **(Optional) Codex CLI** — if `codex` is on `$PATH` (≥ 0.125) and\n   `codex login status` reports a ChatGPT login, the scene stills can be generated\n   through Codex's built-in `image_gen` (the same gpt-image-2 model) billed to the\n   user's ChatGPT subscription instead of Higgsfield credits — offer it at\n   Step 1.7, command in Step 2. Absence just removes the option.\n6. Caveats: macOS ships **bash 3.2** (no `declare -A`); don't use associative arrays in\n   scripts. Higgsfield generations take **3–8 min each** — always run them detached\n   (background) and poll, never a foreground blocking call. Reference-by-job-UUID is\n   rejected by media flags — pass **local file paths** to `--image/--start-image/--end-image`.\n   Video models differ in accepted params (e.g. Kling has no `--resolution`) and in whether\n   they support start/end-image conditioning at all — before batching, confirm the chosen\n   model's schema with `higgsfield model get <job_type>` and see the Step 4 model table.\n\n---\n\n## Step 1 — Interview the user\n\nThe **subject is the user's to state — ask it as an open question in plain prose**, never a\nfabricated multiple-choice. A made-up list of industries biases them and reads as you\ndeciding their business for them; let them answer in their own words (their real business,\na client's, or any idea). Reserve structured multiple-choice (`AskUserQuestion` in Claude\nCode; a plain either/or question elsewhere) for the genuinely\nenumerable, lower-stakes choices below — art direction, camera style, and brand-kit\napproach — and even\nthere, signal they can go their own way (\"Other\"). Ask only what you can't sensibly\ndefault. Cover:\n\n1. **Subject** (ask openly, not multiple-choice) — \"What should this world be about? Your\n   business, a client's, or any idea — a word or a sentence is fine.\" Capture the\n   industry/product + a one-line pitch (e.g. \"a bubble tea company, from leaf to last\n   sip\"), and a brand name if they have one; otherwise you'll propose one below.\n2. **Brand kit** — offer three paths, pick one:\n   - Import from a URL: `higgsfield marketing-studio brand-kits fetch --url <site> --wait`\n     (pulls name, colours, tone). Then read it back with `brand-kits list --json`.\n   - The user hands you palette + name + tone directly.\n   - You propose a palette + name and let them approve.\n   Capture **4–6 named hex values**, a display name, and a tone word or two.\n3. **Art direction** — default is \"soft matte low-poly **clay diorama**, isometric,\n   tilt-shift miniature, warm light.\" Offer alternatives (flat papercraft, glossy toy,\n   claymation, neon night). Whatever is chosen becomes the shared **style preamble**\n   reused verbatim in every scene prompt (this is what makes the world cohesive).\n4. **Camera style — ALWAYS ask; it's the film's personality, not a technical\n   detail.** Ask by feel (`AskUserQuestion` in Claude Code; a plain question\n   elsewhere) and record the answer as `CAMERA`. The options map to the Step 4\n   architectures — Step 4 then *implements* the choice, it never re-decides it:\n   - **\"Fly through the world\"** — the camera dives into each scene, pulls up and\n     out, and hops across the miniature world to the next; angles change\n     constantly, big expressive aerial moves (this is the flagship-demo look).\n     → Architecture B. Recommend as the default for diorama/miniature art\n     directions.\n   - **\"One continuous walkthrough\"** — a single forward flight that glides\n     through each scene straight into the next, never pulling back; expressive\n     but always-forward moves per scene (camera grammar table). → Architecture A.\n     Recommend as the default for grounded/photoreal art directions.\n   - **\"Locked isometric glide\"** — the camera keeps one fixed angle for the whole\n     film, Emons-style; the world slides past/toward it, no rotation, no reveals.\n     → Architecture A + the locked-iso clause in every leg prompt (prompts.md).\n   State the trade-off in one line each (B reverses direction at seams — charming\n   in miniature, jarring in realism; locked-iso is the calmest and cheapest to\n   re-roll; walkthrough sits between).\n5. **The journey (sections)** — the ordered scenes the camera flies through. Propose a\n   set derived from the subject's own value chain and let the user edit. 5–7 works well.\n   Boba example: farms → pearl kitchen → flagship shop → delivery → community plaza →\n   the hero product. Each section needs: a short subject description (what's IN the\n   diorama), an eyebrow, a headline, one line of body, and 0–3 tag pills. The last\n   section is usually the hero product + the CTA.\n6. **Mobile version — ALWAYS ask this; never silently generate both.** Ask as a\n   two-option choice (`AskUserQuestion` in Claude Code; a plain question elsewhere):\n   *\"Want a mobile-optimized version too? The mobile version is a second camera chain\n   rendered natively in **9:16 portrait** — composed for phones, not a crop of the\n   landscape film — which roughly doubles the Higgsfield credit spend (state the\n   estimated number).\"*\n   Options: \"Desktop only\" / \"Desktop + mobile (native 9:16 — ~2× credits)\". The\n   credit cost must be stated to the user, not just implied.\n   What the answer gates:\n   - **Yes** → render the parallel 9:16 portrait chain and ship it as the mobile variants\n     (Step 6 / pipeline.md §6b): portrait start canvases → 9:16 dives + connectors\n     frame-locked against their own renders → 720-wide `-m.mp4` encodes → `stillMobile`\n     portrait posters. Wire `clipMobile`/`connectorsMobile`/`stillMobile` (Step 7); run\n     the full mobile QA (Step 8). Budget ~2N-1 extra video gens + NSFW re-rolls.\n     **Never ship the centre-crop as the mobile version by default** — if credits can't\n     cover the portrait chain, say so and offer the crop encodes (pipeline.md §6) as an\n     explicitly-labelled stopgap the user must approve.\n   - **No** → skip the mobile encodes and wiring entirely. The engine's phone hardening\n     (seek-coalescing, iOS priming, safe-area CSS) is always on regardless — that's not\n     a \"mobile version,\" it's just the page not breaking when a phone visits — so a\n     desktop-only build still degrades gracefully.\n\n7. **Budget — engines shown by cost, decided before anything renders.** Present the\n   render tiers (`AskUserQuestion`), then compute and state the estimated total for\n   the user's N scenes — `N stills + (2N−1) videos [videos ×2 if mobile] + ~15%\n   re-roll headroom` — and get a go before generating.\n   - **Video tier** (roster only — every option frame-locks seams, Step 4):\n\n     | Tier | Model | Rough cost |\n     |---|---|---|\n     | Draft / previz | `seedance_2_0_mini` (720p) | ~¼ of Standard |\n     | Standard (default) | `seedance_2_0` (1080p) | baseline |\n     | Alternate | `kling3_0` (720p native) | ≈ Standard; different look + content filter |\n\n     Draft doubles as the previz path: run the whole chain cheap, approve the\n     journey, re-render final legs on Standard (pipeline.md Notes) — suggest it\n     unprompted when the balance reads tight.\n   - **Backend — Monid is the DEFAULT biller for the chain** (Step 0.1; wiring\n     in pipeline.md → Monid backend). Same Seedance 2.0, per-clip USD instead of\n     credits. Token-priced `width × height × 24 × seconds / 1024` at $7/1M\n     (480p/720p) or $7.7/1M (1080p) — measured: 1080p 8s dive ≈ $2.99, 5s\n     connector ≈ $1.87; 720p ≈ $1.21 / $0.76; 480p ≈ $0.28 / $0.35. An N=6\n     desktop chain ≈ $27 at 1080p / ~$11 at 720p vs Higgsfield Plus-monthly ≈\n     $32 / $16 — ~15% cheaper per clip, parity with Plus-annual; structurally\n     better for one-off builds (pay-per-use, no monthly expiry). On Monid the\n     Draft/previz tier is simply the **same endpoint at 480p** — no model swap,\n     so previz→final stays one-model by construction. State `monid balance`\n     against the estimate; **fall back to Higgsfield credits** (per-model tiers\n     above) when the user prefers their subscription, the balance is short, or\n     the model must be `kling3_0` (Higgsfield-only). It's the same underlying\n     model (`seedance_2_0` ≙ Monid's `seedance-2.0`), so finishing a stranded\n     chain on the other biller is a reasonable rescue — but the serving stacks\n     differ and cross-provider seam character is **untested**: eyeball the first\n     rescued seam before rendering the rest, same as any model swap.\n   - **Stills source** (only offer if the Codex CLI is present, Step 0.5):\n     Higgsfield `gpt_image_2` (spends credits) vs **Codex `image_gen`** — the same\n     gpt-image-2 model billed to the ChatGPT subscription (zero credits; counts\n     toward Codex usage limits; 1536×1024 output — exactly 3:2, slightly under\n     Higgsfield's 2k). Stills are plain PNGs handed to `--start-image`, so the\n     video chain is indifferent to their source. Command in Step 2. **One source\n     for all N stills of a build** — the two render with slightly different\n     character (verified: Codex runs warmer/lighter), and mixing sources across\n     scenes reads as style drift, same reason the video chain uses one model.\n   - **Calibrate costs, don't guess.** The CLI exposes no pricing and plans differ.\n     Run ONE still and ONE video first, diff `higgsfield workspace list` before/\n     after, extrapolate to the full run, and warn the user whenever the estimate\n     exceeds ~70% of the balance. (Observed on a plus plan, 2026-07: Standard\n     video ≈ 40–55 credits, still ≈ 15.) A real `not_enough_credits` mid-run is\n     recoverable (finished clips survive; resume after top-up) but ugly — the\n     whole point of this step is that the user decides *before* the spend.\n\nIf the user names a video model outside the roster, honor it **only if it can\nframe-lock seams** (Step 4). This skill only ships seamless output, so a model that\ncan't frame-lock is declined with a one-line why, not substituted in — use a roster\nmodel instead.\n\nKeep the scroll mechanic fixed (continuous fly-through) — that's the point of the skill.\nSee `references/prompts.md` for the intake checklist and copy structure.\n\n---\n\n## Step 2 — Generate the scene stills\n\nOne image per section, **all sharing the same style preamble** for cohesion. Default\nmodel **`gpt_image_2`** (crisp, great at isometric illustration; returns a solid/white\nbackground which is perfect for floating diorama \"islands\"). Use `nano_banana_2` only if\nthe brief is character/cartoon-heavy (note: `nano_banana_2` is a CLI alias — it resolves\nto `nano_banana_pro`; it won't appear under that name in `higgsfield model list`).\n\nPrompt shape (full templates in `references/prompts.md`):\n\n```\n<STYLE PREAMBLE, identical every time>. On a plain solid <bg> background with a soft\ncontact shadow. <PALETTE hexes>. No text, no letters, no logos, centered, 3:2.\nSubject: <what is in THIS diorama>.\n```\n\n- Run all N concurrently, detached. Command per scene:\n  `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`\n- Result URL is `.[]0.result_url` in the `--wait --json` output. `curl` it down.\n- **Codex stills variant** (if chosen at Step 1.7 — subscription-billed, zero\n  credits): same prompt files, same byte-identical preamble, generated by Codex's\n  built-in `image_gen`:\n\n  ```bash\n  codex exec -C \"$WORK\" -s workspace-write --skip-git-repo-check \\\n    '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.' \\\n    < /dev/null\n  ```\n\n  Single-quote the `$imagegen` segment (the shell must not expand it); if editing\n  with reference images, the prompt goes BEFORE any `-i` flag (it's variadic).\n  ~1–3 min per image; run a few in parallel, not all N at once — and keep the\n  `< /dev/null`: parallel `codex exec` calls sharing a script's stdin hang\n  waiting for input (Gotchas). Output lands at\n  1536×1024 (3:2) — fine for `--start-image` and posters. Everything downstream\n  (cohesion review, knockout, dives) is unchanged.\n- A generation may fail transiently (HTTP 503) — re-roll that one individually; don't\n  restart the batch.\n- **Review the stills before continuing.** They must read as one cohesive world (same\n  angle, palette, light). If one is off-style, regenerate it, optionally passing an\n  approved scene as `--image` to lock style.\n\nSee `references/pipeline.md` for the exact batch script.\n\n---\n\n## Step 3 — (Optional) Float the scenes\n\nIf you want the dioramas to float over an atmospheric background instead of sitting in a\nsolid box, knock out the flat background to transparency with\n`references/knockout.py` (border-connected flood fill — preserves interior colour that\nmatches the bg, e.g. cream walls). Then encode to webp. If you'd rather keep it simple,\njust make the page background the same colour as the scene background and skip this.\n\nThese stills double as **video posters and lazy-load fallbacks**, so keep them.\n\n---\n\n## Step 4 — Camera architecture (implements the Step 1.4 choice)\n\nHow the camera moves *between* scenes is the single biggest quality lever. The user\nalready chose the style at the interview (`CAMERA`, Step 1.4): fly-through → **B**,\nwalkthrough → **A**, locked-iso → **A + the locked-iso leg clause** (prompts.md). If\nthe interview somehow skipped it, ask now — never silently pick for them. The two\nshapes, and the grammar that colors them:\n\n### Video model — pick ONE for the whole chain\n\n**This skill only ships seamless output**, so the only usable models are ones that can\nframe-lock a seam: every chained clip must accept `--start-image`, and connectors also\nneed `--end-image`. That capability — not preference — is the selection rule. Check any\nmodel with `higgsfield model get <job_type>` and **skip anything whose media inputs are\nreference-only** (no start/end image): it can only *condition* a generation, not\n*continue* a shot, so it physically can't hold a seam. Schemas below were confirmed\nagainst the CLI:\n\n| Model | start/end image | Notes |\n|---|---|---|\n| `seedance_2_0` (default) | ✓ / ✓ | Full chain (legs + connectors). `--mode std --resolution 1080p`. Its NSFW filter is the touchy one (see Gotchas). |\n| `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. |\n| `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. |\n\nThose three are the roster — all do both architectures. (`kling3_0_turbo` also frame-locks\nvia `--start-image`, but has no `--end-image`, so it's architecture-A-only and can't make\nconnectors; it also takes a different flag set — no `--mode`, has `--resolution` — so it\ndoesn't drop into the pipeline as-is. It's not in the default roster; only reach for it, and\nwire it by hand, if architecture A's sequential render time is a proven bottleneck and you've\nbenchmarked it as actually faster.)\n\nOne more architecture-A-only candidate, worth knowing because it is by far the cheapest\nprobe: **`minimax_hailuo`** (Hailuo-2.3, ~6 credits per 768p/6s clip vs 22–72 for the\nroster). Verified 2026-07: `--start-image` + prompt frame-locks (output frame 0 ≡ input,\nPSNR 33 dB) and a forward-glide prompt was obeyed, gently. Constraints: the 2.3 variant\nrejects `end_image` (no connectors → arch A only), output aspect follows the input image\n(hand it a 16:9 canvas, not a bare 3:2 still), motion runs subtler than seedance, and\ndon't pass `--resolution` (the CLI mis-types the enum; the 768 default works — 1080\nsupports 6s only). One clip ≠ a chain: qualify a leg-to-leg handoff before betting a\nfull build on it.\n\nRules:\n- **One model for all chained clips.** Each renderer has its own motion/color/grain\n  character; mixing models mid-chain keeps *position* continuity (frames still hand off)\n  but the render-character shift reads as a subtle pop. The one sanctioned exception is\n  the NSFW fallback for a single stubborn clip (Gotchas) — a slight character shift on\n  one 5s connector beats a missing connector.\n- Default to `seedance_2_0`, rendered through **Monid by default** (per-clip USD —\n  next section) with Higgsfield credits as the fallback biller (Step 0.1/1.6); honor\n  a user's stated preference **only if the model qualifies** (frame-locking). If it\n  doesn't, say so and use a supported model — never ship a non-seamless build to\n  satisfy a model request. `kling3_0` and `seedance_2_0_mini` exist only on the\n  Higgsfield side.\n- The pipeline scripts take the model as `$VMODEL` with per-model flags already cased\n  out (`references/pipeline.md`).\n\n### Monid backend — the DEFAULT chain biller (qualified 2026-07-25)\n\nMonid's **`bytedance /v1/video/seedance-2.0`** passed both paid probes on\n2026-07-25 and is the **default** way this skill renders the chain, for **both\narchitectures** — it is the roster's `seedance_2_0` served pay-per-USD (wiring in\npipeline.md → \"Monid backend\"; Higgsfield renders the chain only as the fallback\nbiller or for Higgsfield-only models):\n\n- **Leg probe** (prompt + `first_frame` image): output frame 0 ≡ input still\n  (PSNR 31.6 dB), forward-glide prompt obeyed, billed the advertised cell\n  ($0.279 / 480p 4s).\n- **Connector probe** (prompt + `first_frame` + `last_frame`): start locked\n  (31.6 dB); the end **lands close but not pixel-perfect** (27.5 dB, same\n  composition, prop-level drift) — the exact end-image behavior Seedance shows\n  on Higgsfield, covered by the engine's seam crossfade and by using the next\n  dive's ACTUAL first frame as the end-image (Step 5 law, unchanged).\n\nThe I/O contract differs from the Higgsfield CLI — three rules:\n\n1. **Images go by URL, never inline.** `content` items are\n   `{\"type\":\"image_url\",\"image_url\":{\"url\":…},\"role\":\"first_frame\"|\"last_frame\"}`;\n   base64 data URLs are **rejected** (\"Must be a public https:// URL or an\n   asset://<id> reference\"). Local frames travel through Monid's free workspace\n   file system: `sfs /put` → `curl -T` the bytes → `sfs /cat` returns a signed\n   public URL to paste into the body ($0, explicitly built for this).\n2. **Pass `ratio` explicitly** (`16:9`, or `9:16` for the mobile chain) — the\n   adaptive default follows the input image's aspect instead.\n3. **Bill-check every clip**: cost is token-priced\n   (`w × h × 24 × sec / 1024` at $7–7.7/1M); read `cost.value` off each run.\n\nHistory that shaped these rules (still true as of 2026-07-25): the seedance\nendpoints were text-to-video-only until late July 2026 — **re-`inspect` before\neach build; the catalog moves in both directions.** `minimax\n/v1/video_generation` (Hailuo-2.3) remains disqualified: sending `prompt` +\n`first_frame_image` together silently drops the image (unrelated t2v output,\nwrong price cell); image-only frame-locks (33 dB) but has no camera control.\n\n**Qualification protocol for any new/changed Monid endpoint** (each probe is one\ncheap 480p clip): (1) prompt + first-frame from a real still — frame 0 must\nmatch the input to codec noise (PSNR ≳ 30 dB) and `cost.value` must match the\nadvertised cell; (2) for connector duty, add a `last_frame` from a different\nstill — the end must land on that composition (Seedance-style near-miss is fine,\nthe crossfade covers it). Pass → pay-per-clip tier (arch A if start-only; full\nroster if start+end).\n\n### A) Continuous forward take — RECOMMENDED for grounded / realistic / walkthrough\nOne camera that only ever glides **forward**, first scene through last, as a single take.\nGenerate the legs **sequentially**: leg 0 from scene-0's still (glide forward into it);\nthen each leg's `--start-image` = the **previous leg's ACTUAL last frame** (extract with\nffmpeg), prompt *\"continue gliding smoothly FORWARD into [scene i], never pulling back\"*\n(or an expressive mid-leg move under the motion-handoff contract — see **Camera grammar**\nbelow), and **no `--end-image`** — an end-image of a wide establishing shot forces the\ncamera to pull back, which is the #1 cause of stutter. Extract each leg's last frame to feed the\nnext. Result: every seam is frame-identical **and** the camera never reverses. There are\n**no connectors** (skip Step 5) — the legs ARE the journey. Wire each leg as a section\nclip with `connectors: []` and a small `crossfade` (~0.08). Even without an `--end-image`\nthe legs still arrive at distinct rooms (the prompt steers the content). Cost: strictly\n**sequential** (can't parallelize) and slower; interiors trip the NSFW filter, so build in\nre-rolls (3 attempts/leg).\n\n### B) Dive-in + aerial connector — only for diorama / miniature / god's-eye worlds\nA \"dive into each scene\" clip + a connector that pulls **up and out** and flies over to the\nnext scene (Step 5). The pull-out **reverses camera direction at every seam** (forward dive\n→ backward pull-out). In a miniature/diorama world that reads as an intentional \"zoom out\nto the map, fly to the next island\"; in a grounded first-person walkthrough it reads as a\njarring **rewind/stutter**. Use B only for the map-like aesthetic — which is exactly\nwhat the \"fly through the world\" interview answer opts into; the reversal reads as\nintentional there. If the user picked B against a grounded/photoreal direction, say\nwhy it will read as a stutter and confirm before rendering.\n\n### Camera grammar — the move should fit the concept (A is NOT \"forward only\")\n\n\"Forward only\" is the *seam* rule, not the *leg* rule. The physics of the chain:\n\n- **Position continuity** at a seam comes from the frame handoff (next leg starts from the\n  previous leg's actual last frame).\n- **Velocity continuity** at a seam means the camera must never *reverse across a seam* —\n  that's the rewind stutter.\n- **Inside a single leg the camera is free.** One leg is one continuous render — there is\n  no seam to break mid-leg, so orbits, crane-ups, lateral tracking, even a push-in that\n  eases back out are all safe *within* the clip. Reversals are only fatal *across* seams.\n\nSo give each leg an expressive move chosen from the scene's own logic, under a **motion\nhandoff contract**: every leg **ends by settling into a slow, steady forward drift** toward\nthe next destination (final ~1 s), and every leg **begins by continuing that same drift**.\nKeep both clauses in the prompts verbatim (templates in `references/prompts.md`).\n\nPick the grammar from the concept:\n\n| Concept / tone | Mid-leg move |\n|---|---|\n| Product / luxury retail | slow half-orbit around the hero object, then continue past it |\n| Real estate / hospitality | steadicam glide through doorways; gentle crane-up in atria |\n| Industrial / process / logistics | low lateral track alongside the line, foreground parallax |\n| Travel / outdoors / campus | drone-style rise-and-reveal, then a descending swoop |\n| Food / craft / detail-driven | push in close to the craft moment, ease back, carry on |\n| Playful miniature (arch. B) | dives + aerial hops — the connector IS the grammar |\n\nHonest costs: expressive mid-leg moves raise re-roll odds — the model can end a fancy move\nin a state that isn't a clean forward drift. Mitigations: keep the final-second settle\nclause verbatim; **eyeball each leg's last frame before chaining the next** (it should look\nlike a frame from a gentle forward glide — if not, re-roll before wasting the next leg);\nbudget ~1 extra re-roll per expressive leg. A plain forward glide stays the zero-risk\ndefault — use it for legs where the scene itself is the show.\n\n**Locked-iso variant** (`CAMERA` = locked isometric glide): architecture A where every\nleg pins the view instead of taking a mid-leg move — \"the camera keeps exactly the same\nhigh isometric angle throughout, no rotation, no orbit, no tilt; it only travels\nstraight and level, the world sliding past beneath the same view\" (verbatim clause in\nprompts.md). The handoff contract is unchanged. Seedance drifts the angle slightly on\nlong legs — the existing eyeball-each-last-frame rule is the catch; re-roll a leg whose\nview has rotated. Calmest look, cheapest re-rolls, and the closest to the Emons\nreference.\n\nTwo related pacing knobs live in the engine (Step 7): per-section `scroll` (more scroll\ndistance = longer dwell in that scene) and `linger` (the camera settles mid-scene exactly\nwhile the copy peaks, then picks up speed toward the seam). Prefer expressive motion in the\n*clip* and restraint in the *scrub mapping* — they compound.\n\nAnd remember scroll is a scrubber: visitors can scroll **up**, so every move also plays in\nreverse. That's free and expected — no extra work — but it's another reason seam velocity\nmust be consistent in both directions (a seam that reads fine forward reads as a stutter\nbackward too if velocity flips).\n\n**For B**, one camera flight per scene: starts high/outside, descends into the interior,\nstructure opens. Model: the chain model you picked above (default **`seedance_2_0`**),\n`--start-image = the scene still`.\n\n- Use the **solid-background still** (not the knocked-out transparent one) as the\n  start image, so the video has a full frame.\n- Prompt: \"Single continuous cinematic camera move, no cuts. Begin high and far looking\n  at the whole <scene> from outside … descend and fly inside toward <focal point> … the\n  roof/walls gently open to reveal the interior. <style>, smooth graceful slow motion.\n  No text.\" (Template in `references/prompts.md`.)\n- Params (seedance): `--mode std --resolution 1080p --aspect_ratio 16:9 --duration 8`.\n  For Kling: drop `--resolution` (no such param), add `--sound off`, `--duration 10`.\n  Do **not** pass `--generate-audio` (it errors on seedance; audio is wasted anyway —\n  you'll mute).\n- Run concurrently, detached, then download each `.result_url`. Re-roll individual\n  failures. Keep the raw 1080p sources — you need their frames next.\n\n---\n\n## Step 5 — Connectors (architecture B only)\n\nSkip this whole step for architecture **A** — the forward take has no connectors; its legs\nalready chain seamlessly. This step applies to **B** (diorama/miniature), and note the\nreversal caveat from Step 4.\n\nThe connector clips are what make the world feel *connected* instead of cut. A connector\nflies from the end of scene i out and into the start of scene i+1. **Both of its\nendpoints must be the ACTUAL RENDERED FRAMES of the neighbouring clips — never the\noriginal diorama still.**\n\nWhy: every Higgsfield generation renders slightly differently. If a connector *ends* on\na fresh render of \"the kitchen diorama,\" but the next dive clip *starts* on its own\ndifferent render of that same diorama, the two won't match and you get a pop at the seam.\nThe fix is to hand off the exact pixels:\n\n```\nFor each connector between dive_i and dive_{i+1}:\n  start-image = the LAST frame extracted from dive_i's rendered video\n  end-image   = the FIRST frame extracted from dive_{i+1}'s rendered video\n```\n\nNow every seam is frame-identical on *both* sides:\n`dive_i.end == connector.start` and `connector.end == dive_{i+1}.start`.\n\nExtract the boundary frames from the rendered dives (not the stills):\n\n```bash\nffmpeg -sseof -0.15 -i dive_i.mp4   -frames:v 1 -q:v 2 dive_i_last.png    # interior of i\nffmpeg -ss 0      -i dive_{i+1}.mp4 -frames:v 1 -q:v 2 dive_next_first.png # establishing of i+1\n```\n\nGenerate the connector (`--duration 5` is plenty). Connectors need `--end-image`, so the\nmodel must accept it — any roster model does (`seedance_2_0`, `seedance_2_0_mini`,\n`kling3_0`):\n\n```bash\nhiggsfield generate create \"$VMODEL\" \\\n  --prompt \"$(cat connector_i.txt)\" \\\n  --start-image dive_i_last.png --end-image dive_next_first.png \\\n  $VOPTS --aspect_ratio 16:9 --duration 5 --wait --json\n# seedance: VOPTS=\"--mode std --resolution 1080p\"; kling3_0: VOPTS=\"--mode std --sound off\"\n```\n\nConnector prompt: \"Single continuous camera move, no cuts. Pull up and back out of\n<scene i>, rise into the sky, glide across the connected miniature world, and arrive\nabove <scene i+1>, beginning to descend toward it. Seamless flowing aerial transition.\n<style>. No text.\" (Template in `references/prompts.md`.)\n\nInsurance: Seedance lands *close* to the end-image but not always pixel-perfect, so the\nengine still applies a **short crossfade** (a few frames) at each seam. Frame-matched\nendpoints + a small crossfade = no visible cut. Never skip the actual-frame handoff and\nrely on the crossfade alone; a big content jump can't be hidden by a crossfade.\n\n---\n\n## Step 6 — Encode for smooth scrubbing\n\nScrubbing = setting `video.currentTime` from scroll. Two things matter, and they are\noften gotten wrong:\n\n1. **Seekability, not keyframe density, is what makes scrubbing work.** Many static\n   hosts (and `python -m http.server`) don't serve HTTP byte-range requests, which pins\n   `video.seekable` to `[0,0]` and clamps *every* seek to frame 0 — the video looks\n   frozen. The robust fix is to **fetch each clip as a `Blob` and play it from an\n   in-memory object URL** (blobs are always fully seekable). The engine does this.\n   Because of it, you do **not** need all-intra video.\n2. **Don't shrink quality to get smooth seeks.** Encode at the **native resolution**\n   (1080p from Seedance — don't downscale), `crf ~20`, a **small GOP** (`-g 8`) rather\n   than all-intra (all-intra bloats an 8s clip to ~25 MB; GOP 8 is ~8 MB and scrubs\n   fine via blob). Strip audio, add faststart, and a light `unsharp` counters video\n   softness:\n\n```bash\nffmpeg -i src.mp4 -an -vf \"unsharp=5:5:0.8:5:5:0.0\" \\\n  -c:v libx264 -preset slow -crf 20 -pix_fmt yuv420p \\\n  -g 8 -keyint_min 8 -sc_threshold 0 -movflags +faststart out.mp4\n```\n\nEncode all 2N-1 clips (dives + connectors) with the same settings for uniform quality.\n\n**Mobile encodes (only if the user opted in at Step 1.6).** The mobile version is\nthe **native 9:16 portrait chain** (pipeline.md §6b): portrait renders of every dive and\nconnector, encoded **720 wide (`scale=720:-2`), `-g 4`** (more keyframes = cheaper seeks —\nphone decoders' seek cost scales with GOP length), crf 23 — wired as `clipMobile` /\n`connectorsMobile`, with each portrait dive's first frame extracted as the section's\n`stillMobile` poster (Step 7). The engine serves them automatically on phones and falls\nback to the desktop clip when absent. The 16:9 centre-crop `encm()` encodes\n(pipeline.md §6) are a **fallback only** — for when credits can't cover the portrait\nchain — and shipping them must be called out to the user, never silent. If the user chose\ndesktop-only, skip this — the engine still hardens phone scrubbing regardless\n(seek-coalescing, iOS priming), so the page degrades gracefully rather than breaking.\n\n---\n\n## Step 7 — Assemble the page\n\nCopy `references/scrub-engine.js` (and, if you want a fully standalone page, the tiny\n`references/index-template.html`) into the user's project — or adapt into their\nframework. It's config-driven and self-contained:\n\n```js\nmountScrollWorld(document.getElementById('world'), {\n  brand: { name: 'Pearl & Co.' },\n  diveScroll: 1.3, connScroll: 0.9,          // viewport-heights of scroll per clip\n  sections: [\n    { id:'farm', label:'The Farms', still:'assets/farm.webp',\n      clip:'assets/vid/farm.mp4',\n      clipMobile:'assets/vid/farm-m.mp4',      // mobile opt-in only: native 9:16 render\n      stillMobile:'assets/farm-m.webp',        // its first frame as the portrait poster\n      scroll: 1.6, linger: 0.45,   // optional pacing: longer dwell + camera settles mid-scene\n      accent:'#8FB98A', eyebrow:'From leaf to last sip', title:'It starts in the hills.',\n      body:'…', tags:['Single-origin','Hand-picked'] },\n    // …one per section; last may carry a `cta`\n  ],\n  connectors:       ['assets/vid/conn1.mp4','assets/vid/conn2.mp4',   /* … length = sections-1 */],\n  connectorsMobile: ['assets/vid/conn1-m.mp4','assets/vid/conn2-m.mp4' /* … same length; mobile opt-in only */],\n});\n```\n\nThe engine handles: the ordered dive/connector chain, scroll→currentTime with rAF\nsmoothing, blob loading, lazy prefetch of nearby clips, frame-matched crossfades, pinned\nper-section copy (first section greets on landing, last holds its CTA), a route rail,\n`prefers-reduced-motion`, and mobile. **Pacing per section:** `scroll` overrides\n`diveScroll` for that scene (more scroll = longer dwell) and `linger` (0–1, keep ≤ 0.6)\nremaps time so the camera settles mid-scene — exactly while the copy peaks — then speeds\nup toward the seam; seam frames are untouched (f(0)=0, f(1)=1). Give the hero and finale\nscenes a higher `scroll` + some `linger`; keep transit scenes brisk. Theme it with CSS variables (`--accent`,\n`--sw-bg`, `--sw-ink`, …) — the visual identity comes from the generated clips, so the\nchrome stays quiet. See the header of `scrub-engine.js` for the full config + CSS vars.\n\n**On phones the engine adapts automatically** (coarse pointer or ≤860px): it serves\n`clipMobile` / `connectorsMobile` when present, **coalesces seeks** (never queues a new\n`currentTime` while the decoder is still seeking — this is what stops a fast flick from\nfreezing the clip), **keeps the still as a poster until the clip paints its first frame**\nand **primes each video on first touch** (fixes iOS's blank-until-played video), drops the\ndrifting particles, ignores URL-bar-only resizes (no scroll jump), and uses safe-area\ninsets so copy clears the notch/home indicator. All of this hardening is on by default —\nno config needed. The `clipMobile`/`connectorsMobile` encodes are the opt-in part\n(Step 1.6): only wire them when the user asked for the mobile version.\n\nFor non-JS backends (Python/Rails/etc.): serve the assets and drop the engine `<script>`\ninto the rendered HTML; nothing about it is framework-specific.\n\n---\n\n## Step 8 — QA the seams (don't skip)\n\nDrive the page in a headless browser and **verify frame continuity at the seams**, which\nis the thing most likely to be wrong:\n\n- Screenshot at scroll positions just before and just after each seam. The two frames\n  must be near-identical (the dive's last frame == the connector's first frame). If they\n  pop, you used the diorama still instead of the actual rendered frame (redo Step 5), or\n  the crossfade band is too short. Calibration: judge seams by *composition*, not raw\n  PSNR — at 720p/1080p a correctly frame-locked seam can read ~18–25 dB from detail\n  shimmer alone (observed on a verified-good build); a real mismatch shows as different\n  composition/props, not just softness.\n- Check the console for errors, confirm `video.seekable.end(0) > 0` (blob working), and\n  that `currentTime` tracks scroll across each clip's band.\n- **Mobile — full checklist only if the user opted into the mobile version (Step 1.6).**\n  For a desktop-only build, just sanity-check a phone viewport once: page loads, still\n  posters show, nothing overlaps — the engine's hardening covers graceful degradation.\n  For the mobile build (do this on a real phone or an emulated one, portrait + landscape):\n  - Emulate a phone viewport **with CPU throttled 4–6×** and scroll fast — the clip should\n    track without freezing (the seek-coalescing + `-m.mp4` encodes are what make this hold).\n  - Confirm the first scene shows immediately (its still is the poster) and the video takes\n    over the instant you scroll — no blank/black scene (the iOS priming fix). Test iOS Safari\n    specifically; it's the one that goes blank if this regresses.\n  - Verify the `-m.mp4` variant is actually served on mobile (Network panel), and the\n    heavy 1080p master on desktop. The mobile clips must be **natively portrait**\n    (`videoWidth < videoHeight` — not a downscaled 16:9 file), and the `stillMobile`\n    posters must be served and match each portrait clip's first frame (no\n    landscape→portrait flash when the video paints).\n  - Slowly scroll so the URL bar collapses — the page must **not jump** (height-only resizes\n    are ignored on touch). Rotate the device — layout should recompose cleanly.\n  - Only if the crop **fallback** shipped (no credits for the portrait chain): portrait\n    crops a 16:9 clip to its centre — confirm the focal subject still reads, and remind\n    the user this is the stopgap, not the mobile version.\n- Check reduced-motion (should fall back to the stills, no video, no particles).\n\n---\n\n## Gotchas (hard-won)\n\n- **Seam pop** → connector endpoints were the diorama stills, not the neighbouring\n  clips' actual frames. Always extract real frames (Step 5).\n- **Seam stutter / camera \"jumps backward\"** → even with frame-matched seams, if the\n  camera *velocity reverses* (forward dive, then a connector that pulls back out) it\n  reads as a rewind. This is inherent to architecture B. For any grounded walkthrough use\n  architecture A (one continuous forward take — legs chained from actual last frames, no\n  pull-back, no `--end-image`); see Step 4.\n- **Frozen video / stuck at frame 0** → `seekable=[0,0]`; the host isn't serving byte\n  ranges. Use blob URLs (engine does).\n- **Huge files** → you used all-intra. Use `-g 8` + blob instead.\n- **Soft / low quality** → you downscaled or over-compressed. Encode native 1080p,\n  crf ≤ 20, add `unsharp`. Video is inherently softer than the stills — keep the stills\n  as the lite fallback for max fidelity.\n- **Concurrent gens 503 / \"not_enough_credits\" race** → transient when many launch at\n  once; re-roll the individual failure, it's not really out of credits (verify with\n  `higgsfield workspace list`).\n- **NSFW false-positives (Seedance `status \"nsfw\"`)** → the video content filter flags\n  perfectly innocuous clips, especially **bedroom, pool, spa/wellness** contexts and\n  trigger words like \"bed\", \"pool\", \"waterfall\", \"wine\", \"swim\". It's partly the prompt\n  wording and partly the reference frames. Fixes, in order: (1) re-roll — it's often\n  non-deterministic and passes on the 2nd–3rd try; (2) strip trigger words and add\n  \"empty, unoccupied, no people, no figures, architectural, tasteful\"; (3) regenerate\n  just that clip on **`kling3_0`** with the same start/end frames — a different\n  provider's filter often passes what Seedance blocks. Expect a slight render-character\n  shift on that one clip (each model has its own grain/motion feel); for a 5s connector\n  behind a crossfade that usually beats option (4): set the connector slot to `null` —\n  the engine crossfades that seam directly (optional connectors), so the page still\n  completes. Budget extra credits/time for these re-rolls on interiors/real-estate content.\n- **Dark / custom theme** → the engine wraps its default tokens in `@layer sw`, so a\n  page-level `:root` / `.sw-root { --sw-bg; --sw-ink; --sw-accent; --sw-font-* }` block\n  wins cleanly (no specificity hacks). `--sw-ink` is your primary **text/heading** colour;\n  the **accent** fills the primary button and active nav. For a dark theme, set `--sw-bg`\n  dark and `--sw-ink` light — the copy scrim and title shadow follow `--sw-bg` automatically.\n- **Phone scrub stutters / freezes on a fast flick** → the 1080p master is too heavy for a\n  phone decoder and seeks pile up. Ship the `-m.mp4` mobile encodes (720p, `-g 4`) and wire\n  `clipMobile`/`connectorsMobile` (Step 6/7). The engine already coalesces seeks; the lighter\n  encode is the other half. Still choppy on a low-end device? Tighten GOP (`-g 2` / all-intra).\n- **Blank / black scene on iOS (desktop was fine)** → an iOS Safari quirk: a muted video that\n  was never played won't paint a seeked frame. The engine fixes this by keeping the still as a\n  poster until the clip paints and priming each video on first touch — so **don't** hide the\n  still on `loadedmetadata` or strip the `playsinline`/`muted` attributes if you adapt the\n  engine into a framework.\n- **Page jumps while scrolling on mobile** → something is re-running layout on the URL-bar\n  show/hide `resize`. The engine ignores height-only resizes on touch; if you ported it, gate\n  your resize handler on a width change (keep the `orientationchange` path for rotation).\n- **Copy hidden behind the URL bar / notch on mobile** → use the engine's safe-area-aware\n  bottom offset (`env(safe-area-inset-bottom)` + `dvh`); make sure the page's\n  `<meta viewport>` includes `viewport-fit=cover` (the template does).\n- **Portrait crops the scene** → a 16:9 clip on a tall phone shows only its centre — which\n  is why the mobile version is the native 9:16 chain (§6b), never the crop. If you're seeing\n  this on a mobile build, either the crop fallback shipped (call it out to the user) or the\n  9:16 encodes aren't actually being served (check `videoWidth < videoHeight`). Keeping each\n  scene's focal subject centred (prompts.md) still matters for the desktop film itself.\n- **`--generate-audio` errors on seedance** → omit it; mute in HTML and `-an` on encode.\n- **Kling rejects your flags** → `kling3_0` has **no `--resolution` param** (don't pass\n  one; encode at whatever native res ffprobe reports) and **sound defaults on** — pass\n  `--sound off`. Duration default is 5; legs/dives want 10.\n- **Seam pop only where you \"saved credits\"** → you swapped models mid-chain, or used a\n  start-image-only model where a connector needs an `--end-image`. One model for the whole\n  chain; the only cheap tier is `seedance_2_0_mini`, which keeps frame-locking so it stays\n  seamless. (Any model with reference-only inputs can't hold a seam at all — Step 4.)\n- **Monid seedance rejects inline images** → \"Must be a public https:// URL or an\n  asset://<id> reference\": frames go through the free `sfs` file system\n  (put → `curl -T` → cat → signed URL; pipeline.md → Monid backend), never base64.\n  Quirk: `/put` echoes back `home/<path>`, but `/cat` and `/ls` want the **original\n  relative path** you gave `/put` — using the echoed path 404s.\n- **Monid clip wrong aspect** → the `ratio` default is adaptive and follows the input\n  image (a 3:2 still → a 4:3-ish video). Pass `--ratio` — `16:9` desktop, `9:16`\n  mobile chain — explicitly on every chained clip.\n- **Monid CLI \"Polling timed out after 120s\"** → only the local wait died; the run\n  continues server-side. Re-poll with `monid runs get -r <runId> -w 120` (find the id\n  in `monid runs list`). Result URLs expire (~24–48 h) — download immediately.\n- **Monid minimax drops the image when a prompt is present** → `prompt` +\n  `first_frame_image` together returns an unrelated t2v clip AND bills the wrong matrix\n  cell ($0.56 vs $0.28 observed). Image-only frame-locks but has no camera control.\n  Until the wrapper is fixed, that endpoint can't chain — use Monid's seedance-2.0.\n  (The model itself is fine — the same prompt+image via Higgsfield `minimax_hailuo`\n  frame-locks.)\n- **Monid billing surprises** → matrix/token-priced endpoints bill by selector match:\n  pass every selector field explicitly (`model`, `resolution`, `duration`) and read\n  `cost.value` off the run result after each clip. A big base64 field in any body can\n  also bounce as an HTML error page (\"Unexpected token '<'\") — another reason frames\n  travel by sfs URL.\n- **Monid schema changed since last build** → it happens (seedance was t2v-only until\n  late July 2026, then gained first/last-frame support). `monid inspect` before each\n  build; re-run the Step 4 qualification probes when the Input schema differs from\n  what pipeline.md documents.\n- **Codex stills hang at \"Reading additional input from stdin...\"** → parallel\n  `codex exec` calls launched from one script share the parent's stdin; one wins it,\n  the rest block forever (observed: 1 of 3 completed, 2 hung, the second batch never\n  started). Always append `< /dev/null` to every backgrounded `codex exec` — the\n  pipeline's `gen_still_codex` has it; keep it if you adapt the command.\n- **White-box scenes** → `gpt_image_2` returns a solid bg; either match the page bg to it\n  or knock it out (Step 3).\n- **bash 3.2** on macOS → no associative arrays in scripts.\n- **Connector grabs the wrong scene's frames** (or errors on a frame that doesn't exist\n  yet) → the array loop ran in **zsh** (macOS default interactive shell), where arrays are\n  1-indexed, not bash's 0-indexed. Keep every array-driven chain step in a `#!/bin/bash`\n  script run via `bash script.sh` — never inline array loops in the interactive shell.\n\n## References\n\n- `references/prompts.md` — the intake checklist, style-preamble pattern, and every\n  prompt template (scene still, dive, connector) with fill-in slots.\n- `references/pipeline.md` — copy-paste batch scripts for the whole run (generate →\n  extract frames → connectors → encode → mobile encode), bash-3.2-safe.\n- `references/scrub-engine.js` — the portable, config-driven scrub engine (builds DOM +\n  injects CSS; blob-seek, lazy load, seam crossfade, copy, route rail, reduced-motion, and\n  phone hardening: mobile encodes, seek-coalescing, iOS priming, safe-area, no-jump resize).\n- `references/index-template.html` — a minimal standalone page that mounts the engine.\n- `references/knockout.py` — border-connected background knockout for floating scenes.\n","skills/scroll-world/references/pipeline.md":"# Pipeline: copy-paste scripts (bash 3.2 safe)\n\nSet these once. `NAMES` is the ordered section ids; the last is the hero/finale.\n\n```bash\nWORK=/tmp/scroll-world           # scratch dir for prompts, sources, frames\nASSETS=./assets                  # where the site reads stills (webp) + clips (mp4)\nmkdir -p \"$WORK\" \"$ASSETS/vid\"\nNAMES=\"farm kitchen shop delivery plaza finale\"   # <-- your section ids, in order\n\n# Chain video model — ONE for every chained clip (SKILL Step 4 roster).\n# Must accept --start-image AND --end-image (verify: higgsfield model get <model>):\n# seedance_2_0 | kling3_0 | seedance_2_0_mini (draft tier). Reference-only models can't\n# hold a seam; models without --mode (e.g. kling3_0_turbo) need their own flag branch below.\n# DEFAULT backend: Monid pay-per-clip (§7 — gen_dive_monid/gen_conn_monid with\n# VRES=1080p|720p|480p instead of VOPTS). The gen_dive/gen_conn functions in §2/§4\n# below are the Higgsfield-credits FALLBACK (and the only home of kling3_0/mini).\nVMODEL=seedance_2_0\ncase \"$VMODEL\" in                                  # per-model flags + durations (bash 3.2 safe)\n  kling3_0)          VOPTS=\"--mode std --sound off\";          DIVE_DUR=10; CONN_DUR=5 ;;  # no --resolution param on Kling\n  seedance_2_0_mini) VOPTS=\"--mode std --resolution 720p\";    DIVE_DUR=8;  CONN_DUR=5 ;;  # cheap frame-locked previz\n  *)                 VOPTS=\"--mode std --resolution 1080p\";   DIVE_DUR=8;  CONN_DUR=5 ;;  # seedance_2_0 default\nesac\n```\n\nHiggsfield generations take minutes — every `higgsfield ... --wait` call below is meant\nto run inside a **backgrounded** script. Launch the whole script with your tool's\nbackground/detached mode and poll the progress log; never block the foreground.\n\n## 1. Scene stills (Step 2)\n\nWrite one prompt file per section to `$WORK/still_<name>.txt` (see prompts.md), then:\n\n```bash\ngen_still() { # name\n  higgsfield generate create gpt_image_2 --prompt \"$(cat \"$WORK/still_$1.txt\")\" \\\n    --aspect_ratio 3:2 --resolution 2k --quality high --wait --wait-timeout 15m --json \\\n    > \"$WORK/still_$1.json\" 2> \"$WORK/still_$1.err\"\n  url=$(jq -r '.[0].result_url // empty' \"$WORK/still_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/still_$1.png\" && echo \"still $1 ok\" || echo \"still $1 FAIL\"\n}\nfor n in $NAMES; do gen_still \"$n\" & done ; wait\n```\n\nCodex variant (STILLS_SOURCE=codex, SKILL Step 1.7 — subscription-billed, zero\ncredits; ~1–3 min each, parallelize in small batches):\n\n```bash\ngen_still_codex() { # name   (< /dev/null is REQUIRED for parallel calls — see SKILL Gotchas)\n  codex exec -C \"$WORK\" -s workspace-write --skip-git-repo-check \\\n    'Use the image generation tool ($imagegen) to generate: '\"$(cat \"$WORK/still_$1.txt\")\"' Wide 3:2 landscape, high resolution. Save it as ./still_'\"$1\"'.png. Do not do anything else.' \\\n    > \"$WORK/still_$1.codex.log\" 2>&1 < /dev/null\n  [ -f \"$WORK/still_$1.png\" ] && echo \"still $1 ok (codex)\" || echo \"still $1 FAIL (see .codex.log)\"\n}\n```\n\nConvert to webp for the site (and optionally run knockout.py first for transparency):\n\n```bash\nfor n in $NAMES; do cwebp -quiet -q 84 -resize 1800 0 \"$WORK/still_$n.png\" -o \"$ASSETS/$n.webp\"; done\n```\n\nReview the stills for cohesion before continuing. Re-roll any off-style one (optionally\nadd `--image \"$WORK/still_<good>.png\"` to lock style).\n\n## 2. Dive-in clips (Step 4)\n\nPrompt files at `$WORK/dive_<name>.txt`. Start image = the solid-bg still PNG.\n\n```bash\ngen_dive() { # name                       ($VOPTS is unquoted on purpose — word-split flags)\n  higgsfield generate create \"$VMODEL\" --prompt \"$(cat \"$WORK/dive_$1.txt\")\" \\\n    --start-image \"$WORK/still_$1.png\" \\\n    $VOPTS --aspect_ratio 16:9 --duration \"$DIVE_DUR\" \\\n    --wait --wait-timeout 20m --json > \"$WORK/dive_$1.json\" 2> \"$WORK/dive_$1.err\"\n  url=$(jq -r '.[0].result_url // empty' \"$WORK/dive_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/dive_$1.mp4\" && echo \"dive $1 ok\" || echo \"dive $1 FAIL\"\n}\nfor n in $NAMES; do gen_dive \"$n\" & done ; wait\n```\n\nRe-roll individual failures (503 / credit race are transient):\n`gen_dive shop`  (just that one).\n\n## 3. Extract boundary frames — the seam handoff (Step 5)\n\nFor each adjacent pair, the connector's start = dive_i's LAST frame, end = dive_{i+1}'s\nFIRST frame — extracted from the **rendered videos**, never the stills.\n\n```bash\nset -- $NAMES\nprev=\"\"\nfor n in \"$@\"; do\n  ffmpeg -v error -ss 0 -i \"$WORK/dive_$n.mp4\" -frames:v 1 -q:v 2 \"$WORK/first_$n.png\"      # establishing\n  ffmpeg -v error -sseof -0.15 -i \"$WORK/dive_$n.mp4\" -frames:v 1 -q:v 2 \"$WORK/last_$n.png\" # interior\ndone\n```\n\n## 4. Connector clips (Step 5)\n\nPrompt files at `$WORK/conn_<i>.txt` (i = 1..N-1). Iterate adjacent pairs:\n\n```bash\ngen_conn() { # i startPng endPng          (end-image required → seedance/kling3_0 only)\n  higgsfield generate create \"$VMODEL\" --prompt \"$(cat \"$WORK/conn_$1.txt\")\" \\\n    --start-image \"$2\" --end-image \"$3\" \\\n    $VOPTS --aspect_ratio 16:9 --duration \"$CONN_DUR\" \\\n    --wait --wait-timeout 20m --json > \"$WORK/conn_$1.json\" 2> \"$WORK/conn_$1.err\"\n  url=$(jq -r '.[0].result_url // empty' \"$WORK/conn_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/conn_$1.mp4\" && echo \"conn $1 ok\" || echo \"conn $1 FAIL\"\n}\nset -- $NAMES ; i=0 ; prev=\"\"\nfor n in \"$@\"; do\n  if [ -n \"$prev\" ]; then i=$((i+1)); gen_conn \"$i\" \"$WORK/last_$prev.png\" \"$WORK/first_$n.png\" & fi\n  prev=\"$n\"\ndone ; wait\n```\n\n## 5. Encode everything for scrubbing (Step 6)\n\nNative resolution (1080p from seedance std; kling3_0 std returned **720p** in testing —\nnever upscale, encode what ffprobe reports), crf 20, GOP 8, light sharpen, no audio,\nfaststart. Same for dives + connectors.\n\n```bash\nenc() { ffmpeg -v error -y -i \"$1\" -an -vf \"unsharp=5:5:0.8:5:5:0.0\" \\\n  -c:v libx264 -preset slow -crf 20 -pix_fmt yuv420p \\\n  -g 8 -keyint_min 8 -sc_threshold 0 -movflags +faststart \"$2\"; echo \"enc $2 $(du -h \"$2\"|cut -f1)\"; }\n\nfor n in $NAMES; do enc \"$WORK/dive_$n.mp4\" \"$ASSETS/vid/$n.mp4\"; done\ni=0; for f in \"$WORK\"/conn_*.mp4; do i=$((i+1)); enc \"$f\" \"$ASSETS/vid/conn$i.mp4\"; done\n```\n\nNow the engine config's `sections[k].clip = assets/vid/<name>.mp4` and\n`connectors = [assets/vid/conn1.mp4, …]` (length N-1, in order).\n\n## 6. Centre-crop mobile encodes — FALLBACK ONLY, not the mobile version\n\n**The mobile version is the native 9:16 portrait chain (§6b).** This section's crop\nencodes exist for one case: the user opted into mobile but credits can't cover the\nportrait chain — and shipping them must be called out and approved, never silent\n(portrait phones will see the landscape film's centre ~26%). The encode mechanics\nmatter either way: scrubbing sets `currentTime` every frame, and a phone decoder's\n**seek cost scales with how many frames it must decode from the nearest keyframe** — so\na 1080p `-g 8` master that scrubs fine on a laptop stutters on a phone. A **smaller\nframe + tighter GOP** fixes that (and halves the bytes on cellular). The crop `-m.mp4`\nsibling per clip:\n\n```bash\n# 720p, GOP 4 (twice the keyframes = ~half the seek-decode work), crf 23, same sharpen/faststart.\nencm() { ffmpeg -v error -y -i \"$1\" -an -vf \"scale=-2:720,unsharp=5:5:0.6:5:5:0.0\" \\\n  -c:v libx264 -preset slow -crf 23 -pix_fmt yuv420p \\\n  -g 4 -keyint_min 4 -sc_threshold 0 -movflags +faststart \"$2\"; echo \"encm $2 $(du -h \"$2\"|cut -f1)\"; }\n\nfor n in $NAMES; do encm \"$WORK/dive_$n.mp4\" \"$ASSETS/vid/$n-m.mp4\"; done\ni=0; for f in \"$WORK\"/conn_*.mp4; do i=$((i+1)); encm \"$f\" \"$ASSETS/vid/conn$i-m.mp4\"; done\n```\n\nWire the variants in the engine config — the engine serves them automatically on phones,\nfalling back to the desktop `clip` when a mobile one is absent:\n\n```js\nsections[k].clipMobile = 'assets/vid/<name>-m.mp4';\nconnectorsMobile = ['assets/vid/conn1-m.mp4', …];   // length N-1, in order\n```\n\nIf phone scrubbing still stutters, tighten the GOP further (`-g 2`, or `-g 1` for all-intra\n= instant seeks at the cost of larger files); if cellular weight is the bigger worry, raise\n`crf` (24–26) or drop to `scale=-2:600`. If the master is already 720p (e.g. kling3_0 std),\nthe mobile encode still pays off — the tighter GOP is what makes phone seeks cheap. All-mobile encodes stay 16:9 — the engine\ncentre-crops them; see the portrait note in SKILL Step 8 / prompts.md.\n\n## 6b. Native 9:16 portrait chain — THE mobile version (Step 1.6 opt-in)\n\nWhen the user opts into mobile, this is what they get: a **parallel 9:16 chain** rendered\nnatively for phones and shipped as the mobile variants — never the §6 crops (those are the\nno-credits stopgap). Same seam laws as the main chain — the portrait chain frame-locks\nagainst its own rendered frames, never the landscape ones. Budget ~2N-1 video gens +\nre-rolls (interiors trip the NSFW filter in portrait too); state the credit cost at the\nStep 1.6 interview.\n\n1. **Portrait start canvases.** Don't hand the video model a 3:2 still and hope: composite\n   each scene onto a 1080×1920 canvas in the page bg colour (island at ~94% width, visual\n   centre at ~45% height). The render then opens exactly on what the portrait poster shows.\n   For knocked-out stills, composite the RGBA over the bg colour first.\n2. **Dives/legs**: same prompt templates with a portrait clause up front (\"Vertical\n   portrait composition, the diorama centered with generous [bg] space above and below\"),\n   `--aspect_ratio 9:16`, same model/params as the main chain. Review each last frame\n   before chaining, as ever.\n3. **Connectors**: extract first/last frames **from the 9:16 renders** and generate 9:16\n   connectors between them. A native 9:16 scene mixed into cropped-16:9 neighbours pops at\n   both seams — the portrait chain must be complete, not partial.\n4. **Encode** with the §6 settings but portrait-oriented scale: `scale=720:-2` (720 wide),\n   `-g 4`, crf 23 → these ARE the `-m.mp4` mobile files (and they replace any §6 crop\n   stopgaps that shipped earlier).\n5. **Posters**: extract each 9:16 dive's first frame → webp → wire as the section's\n   `stillMobile` so the poster matches the portrait video's frame 0 (no landscape→portrait\n   flash when the clip paints). Engine support: `sections[k].stillMobile`.\n\n## 7. Monid backend — Seedance 2.0 pay-per-clip (the DEFAULT; qualified 2026-07-25)\n\n`bytedance /v1/video/seedance-2.0` via Monid is the roster's `seedance_2_0` billed\nper clip in USD, and the **default** chain backend (SKILL Step 4 → Monid backend;\nboth probes passed) — use these functions in the §2/§4 loops unless the build fell\nback to Higgsfield credits. Same chain laws as everywhere else — only the I/O\ndiffers: **frames ride Monid's free `sfs` file\nsystem** (inline base64 is rejected), **`ratio` must be explicit** (the adaptive\ndefault follows the input image's aspect), and runs are fire-and-poll. Token-priced\n`w×h×24×sec/1024` at $7/1M (480p/720p), $7.7/1M (1080p) — measured: 1080p ≈ $2.99\ndive / $1.87 connector; 720p ≈ $1.21 / $0.76; 480p previz ≈ $0.28 / $0.35.\n\n```bash\n# helper: upload a local frame to sfs, print a signed public URL for it ($0).\n# NB: /cat and /ls take the SAME relative path you gave /put — not the\n# \"home/...\"-prefixed path /put echoes back (that one 404s).\nmonid_frame_url() { # localPng remoteName  (JPEG-compresses on the way up)\n  jpg=\"$WORK/sfs_$2.jpg\"\n  ffmpeg -v error -y -i \"$1\" -vf \"scale='min(1536,iw)':-2\" -q:v 2 \"$jpg\"\n  size=$(stat -f%z \"$jpg\")\n  up=$(NO_COLOR=1 monid run -p sfs -e /put \\\n    -i \"{\\\"path\\\":\\\"chain/$2.jpg\\\",\\\"sizeBytes\\\":$size,\\\"ttl\\\":\\\"1h\\\"}\" -w 60 -j \\\n    | jq -r '.output.uploadUrl')\n  curl -fsS -T \"$jpg\" \"$up\" > /dev/null\n  NO_COLOR=1 monid run -p sfs -e /cat -i \"{\\\"path\\\":\\\"chain/$2.jpg\\\",\\\"ttl\\\":\\\"1d\\\"}\" \\\n    -w 60 -j | jq -r '.output.url'\n}\n\n# fire-and-poll (the CLI's -w caps at 120s and seedance can exceed it)\nmonid_wait() { # runId outJson\n  while :; do\n    NO_COLOR=1 monid runs get -r \"$1\" -j > \"$2\" 2>/dev/null\n    case \"$(jq -r '.status // empty' \"$2\")\" in\n      COMPLETED|FAILED|BLOCKED|STOPPED|TIME_OUT) break ;;\n    esac\n    sleep 8\n  done\n}\n\ngen_dive_monid() { # name   (VRES=1080p|720p|480p; DIVE_DUR as usual)\n  furl=$(monid_frame_url \"$WORK/still_$1.png\" \"still_$1\")\n  jq -n --arg p \"$(cat \"$WORK/dive_$1.txt\")\" --arg u \"$furl\" --arg r \"$VRES\" \\\n    '{content:[{type:\"text\",text:$p},\n               {type:\"image_url\",image_url:{url:$u},role:\"first_frame\"}],\n      resolution:$r, duration:'\"$DIVE_DUR\"', ratio:\"16:9\", generate_audio:false}' \\\n    > \"$WORK/dive_$1.body.json\"\n  rid=$(NO_COLOR=1 monid run -p bytedance -e /v1/video/seedance-2.0 \\\n    -f \"$WORK/dive_$1.body.json\" -j | jq -r '.runId')\n  monid_wait \"$rid\" \"$WORK/dive_$1.json\"\n  url=$(jq -r '.output.content.video_url // empty' \"$WORK/dive_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/dive_$1.mp4\" \\\n    && echo \"dive $1 ok (\\$$(jq -r '.cost.value' \"$WORK/dive_$1.json\"))\" \\\n    || echo \"dive $1 FAIL ($(jq -r '.status' \"$WORK/dive_$1.json\"))\"\n}\n\ngen_conn_monid() { # i startPng endPng\n  su=$(monid_frame_url \"$2\" \"conn$1_start\"); eu=$(monid_frame_url \"$3\" \"conn$1_end\")\n  jq -n --arg p \"$(cat \"$WORK/conn_$1.txt\")\" --arg s \"$su\" --arg e \"$eu\" --arg r \"$VRES\" \\\n    '{content:[{type:\"text\",text:$p},\n               {type:\"image_url\",image_url:{url:$s},role:\"first_frame\"},\n               {type:\"image_url\",image_url:{url:$e},role:\"last_frame\"}],\n      resolution:$r, duration:'\"$CONN_DUR\"', ratio:\"16:9\", generate_audio:false}' \\\n    > \"$WORK/conn_$1.body.json\"\n  rid=$(NO_COLOR=1 monid run -p bytedance -e /v1/video/seedance-2.0 \\\n    -f \"$WORK/conn_$1.body.json\" -j | jq -r '.runId')\n  monid_wait \"$rid\" \"$WORK/conn_$1.json\"\n  url=$(jq -r '.output.content.video_url // empty' \"$WORK/conn_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/conn_$1.mp4\" \\\n    && echo \"conn $1 ok (\\$$(jq -r '.cost.value' \"$WORK/conn_$1.json\"))\" \\\n    || echo \"conn $1 FAIL ($(jq -r '.status' \"$WORK/conn_$1.json\"))\"\n}\n```\n\nDefault usage in §2/§4: same loops, `gen_dive_monid`/`gen_conn_monid` in place of\nthe Higgsfield `gen_dive`/`gen_conn` (mobile chain: `ratio:\"9:16\"` and the §6b\nportrait canvases). Previz: same functions with `VRES=480p`.\nResult URLs expire (~24–48 h) — the functions download immediately. Read the billed\n`cost.value` per clip (echoed in the ok-line) and `monid balance` between phases; a\n`BLOCKED` status is a workspace budget/run cap — terminal, surface it to the user.\nFrame extraction, encoding, QA: identical to the Higgsfield path.\n\n**Qualification harness for future/changed endpoints** (each probe = one cheap 480p\nclip): (1) prompt + first_frame from a real still → downloaded video's frame 0 must\nmatch the still (eyeball + PSNR ≳ 30 dB) and `cost.value` must match the advertised\ncell; (2) add a last_frame from a *different* still → the final frame must land on\nthat composition (Seedance-style near-miss ok — the crossfade covers it). Known\nfailure to watch for (it's why the harness exists): `minimax /v1/video_generation`\nstill silently drops the image when `prompt` is present — image-only output proves\nnothing about steerability.\n\n## Notes\n\n- `.[0].result_url` is the field on the `--wait --json` job object. `.min_result_url` is\n  a lower-res preview if you ever want it.\n- **NSFW fallback across models**: if one clip keeps getting flagged on seedance after\n  re-rolls + prompt scrubbing, regenerate just that clip on `kling3_0` with the SAME\n  start/end frames: `VMODEL=kling3_0; VOPTS=\"--mode std --sound off\"; gen_conn 3 …` —\n  then restore your chain model. See SKILL Gotchas for the trade-off.\n- **Previz on the cheap**: run the whole chain once with `VMODEL=seedance_2_0_mini`\n  (frame-locking intact, ~720p) to validate the journey and seams before spending\n  full-model credits — because it's still seamless, the previz translates directly to the\n  final render. Don't reach for reference-only models here: without `--start/--end-image`\n  they can't hold a seam, so their output can't be chained (Step 4 rule).\n- If a whole batch stalls, check `higgsfield workspace list` for credits and\n  `$WORK/*.err` for the reason.\n- Concurrency: launching ~5–6 gens at once is fine; much more can trigger transient\n  credit/race errors — stagger or re-roll.\n"},"items":[{"name":"pipeline.md","path":"skills/scroll-world/references/pipeline.md","rawUrl":"https://raw.githubusercontent.com/oso95/scroll-world/HEAD/skills/scroll-world/references/pipeline.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Pipeline: copy-paste scripts (bash 3.2 safe)\n\nSet these once. `NAMES` is the ordered section ids; the last is the hero/finale.\n\n```bash\nWORK=/tmp/scroll-world           # scratch dir for prompts, sources, frames\nASSETS=./assets                  # where the site reads stills (webp) + clips (mp4)\nmkdir -p \"$WORK\" \"$ASSETS/vid\"\nNAMES=\"farm kitchen shop delivery plaza finale\"   # <-- your section ids, in order\n\n# Chain video model — ONE for every chained clip (SKILL Step 4 roster).\n# Must accept --start-image AND --end-image (verify: higgsfield model get <model>):\n# seedance_2_0 | kling3_0 | seedance_2_0_mini (draft tier). Reference-only models can't\n# hold a seam; models without --mode (e.g. kling3_0_turbo) need their own flag branch below.\n# DEFAULT backend: Monid pay-per-clip (§7 — gen_dive_monid/gen_conn_monid with\n# VRES=1080p|720p|480p instead of VOPTS). The gen_dive/gen_conn functions in §2/§4\n# below are the Higgsfield-credits FALLBACK (and the only home of kling3_0/mini).\nVMODEL=seedance_2_0\ncase \"$VMODEL\" in                                  # per-model flags + durations (bash 3.2 safe)\n  kling3_0)          VOPTS=\"--mode std --sound off\";          DIVE_DUR=10; CONN_DUR=5 ;;  # no --resolution param on Kling\n  seedance_2_0_mini) VOPTS=\"--mode std --resolution 720p\";    DIVE_DUR=8;  CONN_DUR=5 ;;  # cheap frame-locked previz\n  *)                 VOPTS=\"--mode std --resolution 1080p\";   DIVE_DUR=8;  CONN_DUR=5 ;;  # seedance_2_0 default\nesac\n```\n\nHiggsfield generations take minutes — every `higgsfield ... --wait` call below is meant\nto run inside a **backgrounded** script. Launch the whole script with your tool's\nbackground/detached mode and poll the progress log; never block the foreground.\n\n## 1. Scene stills (Step 2)\n\nWrite one prompt file per section to `$WORK/still_<name>.txt` (see prompts.md), then:\n\n```bash\ngen_still() { # name\n  higgsfield generate create gpt_image_2 --prompt \"$(cat \"$WORK/still_$1.txt\")\" \\\n    --aspect_ratio 3:2 --resolution 2k --quality high --wait --wait-timeout 15m --json \\\n    > \"$WORK/still_$1.json\" 2> \"$WORK/still_$1.err\"\n  url=$(jq -r '.[0].result_url // empty' \"$WORK/still_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/still_$1.png\" && echo \"still $1 ok\" || echo \"still $1 FAIL\"\n}\nfor n in $NAMES; do gen_still \"$n\" & done ; wait\n```\n\nCodex variant (STILLS_SOURCE=codex, SKILL Step 1.7 — subscription-billed, zero\ncredits; ~1–3 min each, parallelize in small batches):\n\n```bash\ngen_still_codex() { # name   (< /dev/null is REQUIRED for parallel calls — see SKILL Gotchas)\n  codex exec -C \"$WORK\" -s workspace-write --skip-git-repo-check \\\n    'Use the image generation tool ($imagegen) to generate: '\"$(cat \"$WORK/still_$1.txt\")\"' Wide 3:2 landscape, high resolution. Save it as ./still_'\"$1\"'.png. Do not do anything else.' \\\n    > \"$WORK/still_$1.codex.log\" 2>&1 < /dev/null\n  [ -f \"$WORK/still_$1.png\" ] && echo \"still $1 ok (codex)\" || echo \"still $1 FAIL (see .codex.log)\"\n}\n```\n\nConvert to webp for the site (and optionally run knockout.py first for transparency):\n\n```bash\nfor n in $NAMES; do cwebp -quiet -q 84 -resize 1800 0 \"$WORK/still_$n.png\" -o \"$ASSETS/$n.webp\"; done\n```\n\nReview the stills for cohesion before continuing. Re-roll any off-style one (optionally\nadd `--image \"$WORK/still_<good>.png\"` to lock style).\n\n## 2. Dive-in clips (Step 4)\n\nPrompt files at `$WORK/dive_<name>.txt`. Start image = the solid-bg still PNG.\n\n```bash\ngen_dive() { # name                       ($VOPTS is unquoted on purpose — word-split flags)\n  higgsfield generate create \"$VMODEL\" --prompt \"$(cat \"$WORK/dive_$1.txt\")\" \\\n    --start-image \"$WORK/still_$1.png\" \\\n    $VOPTS --aspect_ratio 16:9 --duration \"$DIVE_DUR\" \\\n    --wait --wait-timeout 20m --json > \"$WORK/dive_$1.json\" 2> \"$WORK/dive_$1.err\"\n  url=$(jq -r '.[0].result_url // empty' \"$WORK/dive_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/dive_$1.mp4\" && echo \"dive $1 ok\" || echo \"dive $1 FAIL\"\n}\nfor n in $NAMES; do gen_dive \"$n\" & done ; wait\n```\n\nRe-roll individual failures (503 / credit race are transient):\n`gen_dive shop`  (just that one).\n\n## 3. Extract boundary frames — the seam handoff (Step 5)\n\nFor each adjacent pair, the connector's start = dive_i's LAST frame, end = dive_{i+1}'s\nFIRST frame — extracted from the **rendered videos**, never the stills.\n\n```bash\nset -- $NAMES\nprev=\"\"\nfor n in \"$@\"; do\n  ffmpeg -v error -ss 0 -i \"$WORK/dive_$n.mp4\" -frames:v 1 -q:v 2 \"$WORK/first_$n.png\"      # establishing\n  ffmpeg -v error -sseof -0.15 -i \"$WORK/dive_$n.mp4\" -frames:v 1 -q:v 2 \"$WORK/last_$n.png\" # interior\ndone\n```\n\n## 4. Connector clips (Step 5)\n\nPrompt files at `$WORK/conn_<i>.txt` (i = 1..N-1). Iterate adjacent pairs:\n\n```bash\ngen_conn() { # i startPng endPng          (end-image required → seedance/kling3_0 only)\n  higgsfield generate create \"$VMODEL\" --prompt \"$(cat \"$WORK/conn_$1.txt\")\" \\\n    --start-image \"$2\" --end-image \"$3\" \\\n    $VOPTS --aspect_ratio 16:9 --duration \"$CONN_DUR\" \\\n    --wait --wait-timeout 20m --json > \"$WORK/conn_$1.json\" 2> \"$WORK/conn_$1.err\"\n  url=$(jq -r '.[0].result_url // empty' \"$WORK/conn_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/conn_$1.mp4\" && echo \"conn $1 ok\" || echo \"conn $1 FAIL\"\n}\nset -- $NAMES ; i=0 ; prev=\"\"\nfor n in \"$@\"; do\n  if [ -n \"$prev\" ]; then i=$((i+1)); gen_conn \"$i\" \"$WORK/last_$prev.png\" \"$WORK/first_$n.png\" & fi\n  prev=\"$n\"\ndone ; wait\n```\n\n## 5. Encode everything for scrubbing (Step 6)\n\nNative resolution (1080p from seedance std; kling3_0 std returned **720p** in testing —\nnever upscale, encode what ffprobe reports), crf 20, GOP 8, light sharpen, no audio,\nfaststart. Same for dives + connectors.\n\n```bash\nenc() { ffmpeg -v error -y -i \"$1\" -an -vf \"unsharp=5:5:0.8:5:5:0.0\" \\\n  -c:v libx264 -preset slow -crf 20 -pix_fmt yuv420p \\\n  -g 8 -keyint_min 8 -sc_threshold 0 -movflags +faststart \"$2\"; echo \"enc $2 $(du -h \"$2\"|cut -f1)\"; }\n\nfor n in $NAMES; do enc \"$WORK/dive_$n.mp4\" \"$ASSETS/vid/$n.mp4\"; done\ni=0; for f in \"$WORK\"/conn_*.mp4; do i=$((i+1)); enc \"$f\" \"$ASSETS/vid/conn$i.mp4\"; done\n```\n\nNow the engine config's `sections[k].clip = assets/vid/<name>.mp4` and\n`connectors = [assets/vid/conn1.mp4, …]` (length N-1, in order).\n\n## 6. Centre-crop mobile encodes — FALLBACK ONLY, not the mobile version\n\n**The mobile version is the native 9:16 portrait chain (§6b).** This section's crop\nencodes exist for one case: the user opted into mobile but credits can't cover the\nportrait chain — and shipping them must be called out and approved, never silent\n(portrait phones will see the landscape film's centre ~26%). The encode mechanics\nmatter either way: scrubbing sets `currentTime` every frame, and a phone decoder's\n**seek cost scales with how many frames it must decode from the nearest keyframe** — so\na 1080p `-g 8` master that scrubs fine on a laptop stutters on a phone. A **smaller\nframe + tighter GOP** fixes that (and halves the bytes on cellular). The crop `-m.mp4`\nsibling per clip:\n\n```bash\n# 720p, GOP 4 (twice the keyframes = ~half the seek-decode work), crf 23, same sharpen/faststart.\nencm() { ffmpeg -v error -y -i \"$1\" -an -vf \"scale=-2:720,unsharp=5:5:0.6:5:5:0.0\" \\\n  -c:v libx264 -preset slow -crf 23 -pix_fmt yuv420p \\\n  -g 4 -keyint_min 4 -sc_threshold 0 -movflags +faststart \"$2\"; echo \"encm $2 $(du -h \"$2\"|cut -f1)\"; }\n\nfor n in $NAMES; do encm \"$WORK/dive_$n.mp4\" \"$ASSETS/vid/$n-m.mp4\"; done\ni=0; for f in \"$WORK\"/conn_*.mp4; do i=$((i+1)); encm \"$f\" \"$ASSETS/vid/conn$i-m.mp4\"; done\n```\n\nWire the variants in the engine config — the engine serves them automatically on phones,\nfalling back to the desktop `clip` when a mobile one is absent:\n\n```js\nsections[k].clipMobile = 'assets/vid/<name>-m.mp4';\nconnectorsMobile = ['assets/vid/conn1-m.mp4', …];   // length N-1, in order\n```\n\nIf phone scrubbing still stutters, tighten the GOP further (`-g 2`, or `-g 1` for all-intra\n= instant seeks at the cost of larger files); if cellular weight is the bigger worry, raise\n`crf` (24–26) or drop to `scale=-2:600`. If the master is already 720p (e.g. kling3_0 std),\nthe mobile encode still pays off — the tighter GOP is what makes phone seeks cheap. All-mobile encodes stay 16:9 — the engine\ncentre-crops them; see the portrait note in SKILL Step 8 / prompts.md.\n\n## 6b. Native 9:16 portrait chain — THE mobile version (Step 1.6 opt-in)\n\nWhen the user opts into mobile, this is what they get: a **parallel 9:16 chain** rendered\nnatively for phones and shipped as the mobile variants — never the §6 crops (those are the\nno-credits stopgap). Same seam laws as the main chain — the portrait chain frame-locks\nagainst its own rendered frames, never the landscape ones. Budget ~2N-1 video gens +\nre-rolls (interiors trip the NSFW filter in portrait too); state the credit cost at the\nStep 1.6 interview.\n\n1. **Portrait start canvases.** Don't hand the video model a 3:2 still and hope: composite\n   each scene onto a 1080×1920 canvas in the page bg colour (island at ~94% width, visual\n   centre at ~45% height). The render then opens exactly on what the portrait poster shows.\n   For knocked-out stills, composite the RGBA over the bg colour first.\n2. **Dives/legs**: same prompt templates with a portrait clause up front (\"Vertical\n   portrait composition, the diorama centered with generous [bg] space above and below\"),\n   `--aspect_ratio 9:16`, same model/params as the main chain. Review each last frame\n   before chaining, as ever.\n3. **Connectors**: extract first/last frames **from the 9:16 renders** and generate 9:16\n   connectors between them. A native 9:16 scene mixed into cropped-16:9 neighbours pops at\n   both seams — the portrait chain must be complete, not partial.\n4. **Encode** with the §6 settings but portrait-oriented scale: `scale=720:-2` (720 wide),\n   `-g 4`, crf 23 → these ARE the `-m.mp4` mobile files (and they replace any §6 crop\n   stopgaps that shipped earlier).\n5. **Posters**: extract each 9:16 dive's first frame → webp → wire as the section's\n   `stillMobile` so the poster matches the portrait video's frame 0 (no landscape→portrait\n   flash when the clip paints). Engine support: `sections[k].stillMobile`.\n\n## 7. Monid backend — Seedance 2.0 pay-per-clip (the DEFAULT; qualified 2026-07-25)\n\n`bytedance /v1/video/seedance-2.0` via Monid is the roster's `seedance_2_0` billed\nper clip in USD, and the **default** chain backend (SKILL Step 4 → Monid backend;\nboth probes passed) — use these functions in the §2/§4 loops unless the build fell\nback to Higgsfield credits. Same chain laws as everywhere else — only the I/O\ndiffers: **frames ride Monid's free `sfs` file\nsystem** (inline base64 is rejected), **`ratio` must be explicit** (the adaptive\ndefault follows the input image's aspect), and runs are fire-and-poll. Token-priced\n`w×h×24×sec/1024` at $7/1M (480p/720p), $7.7/1M (1080p) — measured: 1080p ≈ $2.99\ndive / $1.87 connector; 720p ≈ $1.21 / $0.76; 480p previz ≈ $0.28 / $0.35.\n\n```bash\n# helper: upload a local frame to sfs, print a signed public URL for it ($0).\n# NB: /cat and /ls take the SAME relative path you gave /put — not the\n# \"home/...\"-prefixed path /put echoes back (that one 404s).\nmonid_frame_url() { # localPng remoteName  (JPEG-compresses on the way up)\n  jpg=\"$WORK/sfs_$2.jpg\"\n  ffmpeg -v error -y -i \"$1\" -vf \"scale='min(1536,iw)':-2\" -q:v 2 \"$jpg\"\n  size=$(stat -f%z \"$jpg\")\n  up=$(NO_COLOR=1 monid run -p sfs -e /put \\\n    -i \"{\\\"path\\\":\\\"chain/$2.jpg\\\",\\\"sizeBytes\\\":$size,\\\"ttl\\\":\\\"1h\\\"}\" -w 60 -j \\\n    | jq -r '.output.uploadUrl')\n  curl -fsS -T \"$jpg\" \"$up\" > /dev/null\n  NO_COLOR=1 monid run -p sfs -e /cat -i \"{\\\"path\\\":\\\"chain/$2.jpg\\\",\\\"ttl\\\":\\\"1d\\\"}\" \\\n    -w 60 -j | jq -r '.output.url'\n}\n\n# fire-and-poll (the CLI's -w caps at 120s and seedance can exceed it)\nmonid_wait() { # runId outJson\n  while :; do\n    NO_COLOR=1 monid runs get -r \"$1\" -j > \"$2\" 2>/dev/null\n    case \"$(jq -r '.status // empty' \"$2\")\" in\n      COMPLETED|FAILED|BLOCKED|STOPPED|TIME_OUT) break ;;\n    esac\n    sleep 8\n  done\n}\n\ngen_dive_monid() { # name   (VRES=1080p|720p|480p; DIVE_DUR as usual)\n  furl=$(monid_frame_url \"$WORK/still_$1.png\" \"still_$1\")\n  jq -n --arg p \"$(cat \"$WORK/dive_$1.txt\")\" --arg u \"$furl\" --arg r \"$VRES\" \\\n    '{content:[{type:\"text\",text:$p},\n               {type:\"image_url\",image_url:{url:$u},role:\"first_frame\"}],\n      resolution:$r, duration:'\"$DIVE_DUR\"', ratio:\"16:9\", generate_audio:false}' \\\n    > \"$WORK/dive_$1.body.json\"\n  rid=$(NO_COLOR=1 monid run -p bytedance -e /v1/video/seedance-2.0 \\\n    -f \"$WORK/dive_$1.body.json\" -j | jq -r '.runId')\n  monid_wait \"$rid\" \"$WORK/dive_$1.json\"\n  url=$(jq -r '.output.content.video_url // empty' \"$WORK/dive_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/dive_$1.mp4\" \\\n    && echo \"dive $1 ok (\\$$(jq -r '.cost.value' \"$WORK/dive_$1.json\"))\" \\\n    || echo \"dive $1 FAIL ($(jq -r '.status' \"$WORK/dive_$1.json\"))\"\n}\n\ngen_conn_monid() { # i startPng endPng\n  su=$(monid_frame_url \"$2\" \"conn$1_start\"); eu=$(monid_frame_url \"$3\" \"conn$1_end\")\n  jq -n --arg p \"$(cat \"$WORK/conn_$1.txt\")\" --arg s \"$su\" --arg e \"$eu\" --arg r \"$VRES\" \\\n    '{content:[{type:\"text\",text:$p},\n               {type:\"image_url\",image_url:{url:$s},role:\"first_frame\"},\n               {type:\"image_url\",image_url:{url:$e},role:\"last_frame\"}],\n      resolution:$r, duration:'\"$CONN_DUR\"', ratio:\"16:9\", generate_audio:false}' \\\n    > \"$WORK/conn_$1.body.json\"\n  rid=$(NO_COLOR=1 monid run -p bytedance -e /v1/video/seedance-2.0 \\\n    -f \"$WORK/conn_$1.body.json\" -j | jq -r '.runId')\n  monid_wait \"$rid\" \"$WORK/conn_$1.json\"\n  url=$(jq -r '.output.content.video_url // empty' \"$WORK/conn_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/conn_$1.mp4\" \\\n    && echo \"conn $1 ok (\\$$(jq -r '.cost.value' \"$WORK/conn_$1.json\"))\" \\\n    || echo \"conn $1 FAIL ($(jq -r '.status' \"$WORK/conn_$1.json\"))\"\n}\n```\n\nDefault usage in §2/§4: same loops, `gen_dive_monid`/`gen_conn_monid` in place of\nthe Higgsfield `gen_dive`/`gen_conn` (mobile chain: `ratio:\"9:16\"` and the §6b\nportrait canvases). Previz: same functions with `VRES=480p`.\nResult URLs expire (~24–48 h) — the functions download immediately. Read the billed\n`cost.value` per clip (echoed in the ok-line) and `monid balance` between phases; a\n`BLOCKED` status is a workspace budget/run cap — terminal, surface it to the user.\nFrame extraction, encoding, QA: identical to the Higgsfield path.\n\n**Qualification harness for future/changed endpoints** (each probe = one cheap 480p\nclip): (1) prompt + first_frame from a real still → downloaded video's frame 0 must\nmatch the still (eyeball + PSNR ≳ 30 dB) and `cost.value` must match the advertised\ncell; (2) add a last_frame from a *different* still → the final frame must land on\nthat composition (Seedance-style near-miss ok — the crossfade covers it). Known\nfailure to watch for (it's why the harness exists): `minimax /v1/video_generation`\nstill silently drops the image when `prompt` is present — image-only output proves\nnothing about steerability.\n\n## Notes\n\n- `.[0].result_url` is the field on the `--wait --json` job object. `.min_result_url` is\n  a lower-res preview if you ever want it.\n- **NSFW fallback across models**: if one clip keeps getting flagged on seedance after\n  re-rolls + prompt scrubbing, regenerate just that clip on `kling3_0` with the SAME\n  start/end frames: `VMODEL=kling3_0; VOPTS=\"--mode std --sound off\"; gen_conn 3 …` —\n  then restore your chain model. See SKILL Gotchas for the trade-off.\n- **Previz on the cheap**: run the whole chain once with `VMODEL=seedance_2_0_mini`\n  (frame-locking intact, ~720p) to validate the journey and seams before spending\n  full-model credits — because it's still seamless, the previz translates directly to the\n  final render. Don't reach for reference-only models here: without `--start/--end-image`\n  they can't hold a seam, so their output can't be chained (Step 4 rule).\n- If a whole batch stalls, check `higgsfield workspace list` for credits and\n  `$WORK/*.err` for the reason.\n- Concurrency: launching ~5–6 gens at once is fine; much more can trigger transient\n  credit/race errors — stagger or re-roll.\n","isInternal":false,"tokens":5125,"sizeBytes":16220},{"name":"prompts.md","path":"skills/scroll-world/references/prompts.md","rawUrl":"https://raw.githubusercontent.com/oso95/scroll-world/HEAD/skills/scroll-world/references/prompts.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Prompt templates & intake\n\nEverything here is fill-in-the-slots. Keep the **style preamble** byte-for-byte identical\nacross all scene stills — that identical text is what makes the world feel like one place.\n\n## Intake checklist (Step 1)\n\nCollect and write down:\n\n- `SUBJECT` — the business + one-line pitch.\n- `BRAND_NAME` — display name.\n- `PALETTE` — 4–6 named hexes, e.g. `taro #9B7EBD, cream #F5EDE0, caramel #C88A5A, matcha #8FB98A, plum #3A2E48`. Pick ONE as the scene **background** colour (usually the lightest) and one as the primary **accent**.\n- `TONE` — a word or two (cozy/premium, playful, industrial…).\n- `STYLE` — the art direction (default below).\n- `SECTIONS[]` — ordered list; for each: `id`, `label`, `subject` (what's in the diorama), `eyebrow`, `title`, `body` (≤ 1 sentence), `tags[]` (0–3). Last section = hero product + CTA.\n- `CAMERA` — fly-through (arch B: dives + aerial hops — the flagship-demo look) |\n  walkthrough (arch A: one continuous forward flight) | locked-iso (arch A + the\n  locked-iso clause below: one fixed angle for the whole film). **Always asked**\n  (SKILL Step 1.4), presented by feel with a one-line trade-off each.\n- `MOBILE` — yes/no. **Always asked** (SKILL Step 1.6), presented to the user\n  with the ~2× credit cost stated. Yes = the **native 9:16 portrait chain**\n  (pipeline §6b): portrait renders of every dive/connector +\n  `clipMobile`/`connectorsMobile`/`stillMobile` wiring + the full mobile QA. The\n  §6 crop encodes are a no-credits stopgap only.\n- `VIDEO_TIER` — default is `seedance_2_0` **via Monid** (pay-per-clip; previz =\n  same endpoint at 480p, final at 1080p). Higgsfield-credit alternates: draft\n  (`seedance_2_0_mini`) | standard (`seedance_2_0`) | alternate (`kling3_0`).\n  Chosen by cost at SKILL Step 1.7, with the estimated total stated before\n  anything renders.\n- `STILLS_SOURCE` — higgsfield (`gpt_image_2`, spends credits) | codex\n  (`image_gen`, subscription-billed; only offer when the Codex CLI is present).\n\n## Style preamble (default: clay diorama)\n\nReuse verbatim in every scene prompt. Swap the bracketed bits for the brand's palette/bg.\n\n```\nIsometric low-poly 3D diorama floating as a small rounded island on a plain solid\n[BG_HEX] background with a soft contact shadow beneath it. Soft matte clay 3D render,\nrounded toy-model shapes, gentle warm studio lighting, soft long shadows, tilt-shift\nminiature look. Cohesive color palette of [PALETTE]. Highly detailed, centered\ncomposition, absolutely no text, no letters, no numbers, no logos.\n```\n\nAlternate directions (swap the first two sentences, keep the palette/no-text tail):\n- **Flat papercraft:** \"Isometric layered paper-craft diorama, matte cardstock, clean die-cut edges, subtle drop shadows between layers.\"\n- **Glossy toy:** \"Isometric glossy vinyl-toy diorama, smooth plastic shading, soft rim light, collectible figurine look.\"\n- **Claymation:** \"Isometric stop-motion clay set, visible thumbprints, handmade plasticine texture, soft studio softbox light.\"\n- **Neon night:** \"Isometric miniature at night, warm interior glow and neon signage, moody rim light, wet reflective ground.\"\n- **Photoreal architectural** (real estate, hospitality, premium/luxury): \"Ultra-photorealistic architectural photography of a single cohesive [subject], cinematic wide-angle, warm golden-hour light, natural materials, restrained designer furnishings, a breathtaking view, editorial magazine quality (Architectural Digest), shallow depth of field, no people.\" For photoreal, drop the floating-island framing and the knockout (Step 3) — the scenes are **full-bleed** (a dark page background reads premium), the \"dive\" glides *through doorways/glass* rather than opening a roof, and cohesion comes entirely from the identical preamble (do NOT pass an `--image` reference — it clones the same room). Interiors trip Seedance's NSFW filter often; see SKILL Gotchas.\n\n## Scene still prompt (Step 2)\n\n```\n[STYLE PREAMBLE]\nSubject: [SECTION.subject — describe the miniature scene: the building/space, a few\ncharacters doing the work, the props that signal this stage of the business].\n```\n\nTips:\n- Name concrete props (they anchor the scene): tanks, cauldrons, conveyor, crates, awning, string lights, benches, scooters, map pins.\n- For the final \"hero product\" section, drop the diorama-island framing and prompt a\n  single oversized product centerpiece floating on the same background with a few small\n  orbiting props.\n- **Compose for the centre.** The page renders every clip `object-fit:cover`. Keep the\n  focal subject horizontally centred with a little headroom, and don't park anything\n  essential at the far left/right edges. Mobile ships its own native 9:16 chain\n  (pipeline §6b), so this is not about surviving a crop — but a centred composition makes\n  the portrait renders open cleanly from the same still, and it keeps the dive's focal\n  point where the camera actually flies.\n- Aspect `3:2`, `--resolution 2k --quality high`.\n\n## Leg prompt — architecture A, continuous forward take (Step 4)\n\n`--start-image = previous leg's ACTUAL last frame` (leg 0: the first scene's still).\n**No `--end-image`.** The bolded clauses are the motion-handoff contract — keep them\nverbatim; the mid-leg move is where the expression goes.\n\n```\nSingle continuous cinematic camera move, no cuts. **Continue the same slow, steady\nforward glide.** [MID-LEG MOVE — optional, from the library below.] The camera moves\ninto [SCENE i] toward [FOCAL POINT]. **In the final second, settle back into a slow,\nsteady forward glide toward [the doorway / opening / direction of the next scene].**\n[STYLE tail + PALETTE]. Smooth, graceful, slow motion, subtle parallax. No text, no captions.\n```\n\n### Mid-leg move library (pick by concept; omit for a plain glide)\n\nReversals are safe *inside* a leg (it's one continuous render) — only a seam may never\nreverse. That's why \"ease back out\" is fine mid-leg.\n\n**Locked-iso clause** (`CAMERA` = locked isometric glide — SKILL Step 1.4/Step 4):\nskip this library entirely and put this clause, verbatim, in the mid-leg slot of\nEVERY leg:\n\n```\nThe camera keeps exactly the same high isometric angle throughout — no rotation, no\norbit, no tilt. It only travels straight and level, the world sliding past beneath\nthe same view.\n```\n\nKeep the handoff-contract clauses around it unchanged. When checking each leg's last\nframe, also check the ANGLE hasn't drifted (Seedance rotates slightly on long legs) —\nre-roll the leg if the view has turned.\n\n- **Half-orbit** (product, luxury): \"sweeping in a slow half-orbit around [the hero\n  object], keeping it centered, then continuing past it\"\n- **Crane-up reveal** (scale, atriums, campuses): \"rising smoothly as the full scale of\n  [the space] reveals below\"\n- **Low lateral track** (production lines, counters, shelves): \"tracking low and level\n  alongside [the line], foreground objects sliding past in parallax\"\n- **Push-in + ease back** (craft, detail): \"pushing in close to [the craft moment] until\n  it nearly fills the frame, then easing gently back out\"\n- **Rise-and-swoop** (travel, outdoors): \"climbing in a gentle arc over [the terrain],\n  then swooping down toward [the next focal point]\"\n\nAfter rendering each leg, **check its last frame** before generating the next: it should\nread as a frame from a calm forward glide (no motion blur sideways, no half-finished\norbit). If it doesn't, re-roll this leg — a bad handoff frame poisons every leg after it.\n\n## Dive-in clip prompt (Step 4)\n\n`--start-image = the scene still` (solid-bg version).\n\n```\nSingle continuous cinematic camera move, no cuts. Begin high and far, looking down at the\nwhole [SECTION.subject] from outside like a tiny model. The camera slowly glides forward\nand descends toward it, sweeping in toward [FOCAL POINT — the counter/the cauldrons/the\npeople], as if flying inside. As the camera pushes in, the roof and upper structure\ngently lift and open away to reveal the warm interior. [STYLE tail: soft matte clay\ndiorama, tilt-shift miniature, warm light, [PALETTE]]. Smooth, graceful, slow motion,\nsubtle parallax. No text, no captions.\n```\n\nFor scenes with no building to open (a field, a plaza, a road), replace the roof clause\nwith \"the camera flies low across [the scene] toward [focal point].\"\n\nParams by chain model (SKILL Step 4 table): seedance —\n`--mode std --resolution 1080p --aspect_ratio 16:9 --duration 8`, no audio flag;\nkling3_0 — `--mode std --sound off --aspect_ratio 16:9 --duration 10` (no `--resolution`\nparam). Same for architecture-A legs.\n\n## Connector clip prompt (Step 5)\n\n`--start-image = dive_i LAST frame` (extracted), `--end-image = dive_{i+1} FIRST frame`\n(extracted). Both from the RENDERED videos, not the stills.\n\n```\nSingle continuous cinematic camera move, no cuts. The camera smoothly pulls up and back\nout of [SCENE i], rising into the sky, then glides forward across the connected miniature\nworld and arrives above [SCENE i+1], beginning to descend toward it. One connected\nminiature clay world, seamless flowing aerial transition. [STYLE tail + PALETTE]. Smooth\ngraceful slow motion. No text, no captions.\n```\n\nFor the last connector into a hero-product finale: \"…glides forward and the world\ndissolves toward a single giant [PRODUCT] floating in soft [BG] space, arriving in front\nof it.\"\n\nseedance: `--mode std --resolution 1080p --aspect_ratio 16:9 --duration 5`; kling3_0:\n`--mode std --sound off --aspect_ratio 16:9 --duration 5`. Connectors need `--end-image`\n→ use a roster model that accepts it (Step 4).\n\n## Copy per section (for the engine config)\n\n- `eyebrow` — 2–4 words, uppercase feel (a value-prop label).\n- `title` — 3–6 words, the beat's headline. First section = the site's hero line; last =\n  the payoff + it carries the CTA.\n- `body` — one sentence, plain-spoken, from the visitor's side.\n- `tags` — 0–3 short proof chips (e.g. \"Fresh-cooked\", \"30-min delivery\").\n","isInternal":false,"tokens":2575,"sizeBytes":9986},{"name":"SKILL.md","path":"skills/scroll-world/SKILL.md","rawUrl":"https://raw.githubusercontent.com/oso95/scroll-world/HEAD/skills/scroll-world/SKILL.md","title":"scroll-world","category":"anthropic-skill","format":"markdown","content":"---\nname: scroll-world\ndescription: >\n  Build an immersive scroll-scrubbed \"fly through the world\" landing page for any\n  industry or brand using Higgsfield. As the visitor scrolls, a pre-rendered camera\n  flies from outside each scene into its interior, then flows on to the next scene\n  with NO cuts — one continuous connected flight (Emons-style isometric diorama world,\n  or any art direction you pick). The skill interviews the user for the topic, the\n  story beats/sections, and brand kit, then generates cohesive scenes + seamless camera\n  clips with Higgsfield and wires a portable, framework-agnostic scroll-scrub engine.\n  The video chain renders through Monid by default (Seedance 2.0, pay-per-clip\n  USD — capability re-checked each build, see Step 4) with Higgsfield credits as\n  the fallback biller. Use when the user wants a \"3D world\" /\n  \"browse-through-the-industry\" hero, a scroll cinematic, a diorama landing, or to\n  turn a business into a scrollable world.\nallowed-tools: Bash, Read, Write, Edit, AskUserQuestion, Skill\n---\n\n# scroll-world\n\nProduces a landing page where **scroll drives a camera**: it dives from outside a scene\ninto its interior, then flies out and into the next scene, continuously, with no visible\ncuts. The visuals are AI-generated — stills via Higgsfield (or Codex), the video chain\nvia **Monid by default** (pay-per-clip Seedance 2.0; Higgsfield credits as fallback) —\nand the page just scrubs pre-rendered video by scroll position. This is the same technique behind Apple's scroll-through product\npages — the camera genuinely moves, scroll only drives time.\n\n**What you generate:** N scene stills → N \"dive-in\" camera clips → N-1 \"connector\" clips\nthat join consecutive scenes seamlessly → a portable scrub engine that plays the whole\nchain as one flight.\n\n**The one rule that makes or breaks it:** seams must be *frame-identical*. Read\n[The seamless chain](#step-5--the-seamless-chain-the-critical-part) before generating any\nconnector. Getting this wrong is the single most common failure and produces a visible\n\"pop\" between scenes.\n\nDo not assume a frontend framework. The scrub engine in `references/scrub-engine.js` is\nself-contained vanilla JS (it builds its own DOM + injects its own CSS into a container\nyou give it), so it drops into plain HTML, Next.js, Vue, a Python-served page, anything.\nThe value of this skill is the Higgsfield pipeline, the prompts, and the seam method —\nnot the framework.\n\n---\n\n## Step 0 — Bootstrap\n\n1. **Monid CLI — the default video-chain backend.** Check `monid --version`,\n   `monid keys list` (active key) and `monid balance` — the chain is billed per\n   clip in USD (Step 1.7 has the numbers; a 1080p N=6 chain ≈ $27). If the CLI is\n   missing or the balance can't cover the chain, say so and fall back to\n   rendering the chain on Higgsfield credits instead — same model, same\n   pipeline, different biller (Step 4 → Monid backend).\n2. **Higgsfield CLI — still required even on the Monid path**: it renders the\n   scene stills (`gpt_image_2`) and is the home of the `kling3_0` NSFW fallback\n   and the fallback chain. If `higgsfield` is not on `$PATH`, install per the\n   `higgsfield-generate` skill. If `higgsfield workspace list` fails auth, ask the user\n   to run `higgsfield auth login` (interactive OAuth — you cannot run it) and, if needed,\n   `higgsfield workspace set <id>`. Confirm credits cover the stills (~N image\n   gens) — plus `(2N-1)` video gens if the chain falls back here.\n3. **ffmpeg / ffprobe** on `$PATH` (frame extraction + encoding).\n4. **An image tool** for background knockout if you want floating scenes: PIL\n   (`python3 -c \"import PIL\"`), or `cwebp`/`sips`. Optional — see Step 3.\n5. **(Optional) Codex CLI** — if `codex` is on `$PATH` (≥ 0.125) and\n   `codex login status` reports a ChatGPT login, the scene stills can be generated\n   through Codex's built-in `image_gen` (the same gpt-image-2 model) billed to the\n   user's ChatGPT subscription instead of Higgsfield credits — offer it at\n   Step 1.7, command in Step 2. Absence just removes the option.\n6. Caveats: macOS ships **bash 3.2** (no `declare -A`); don't use associative arrays in\n   scripts. Higgsfield generations take **3–8 min each** — always run them detached\n   (background) and poll, never a foreground blocking call. Reference-by-job-UUID is\n   rejected by media flags — pass **local file paths** to `--image/--start-image/--end-image`.\n   Video models differ in accepted params (e.g. Kling has no `--resolution`) and in whether\n   they support start/end-image conditioning at all — before batching, confirm the chosen\n   model's schema with `higgsfield model get <job_type>` and see the Step 4 model table.\n\n---\n\n## Step 1 — Interview the user\n\nThe **subject is the user's to state — ask it as an open question in plain prose**, never a\nfabricated multiple-choice. A made-up list of industries biases them and reads as you\ndeciding their business for them; let them answer in their own words (their real business,\na client's, or any idea). Reserve structured multiple-choice (`AskUserQuestion` in Claude\nCode; a plain either/or question elsewhere) for the genuinely\nenumerable, lower-stakes choices below — art direction, camera style, and brand-kit\napproach — and even\nthere, signal they can go their own way (\"Other\"). Ask only what you can't sensibly\ndefault. Cover:\n\n1. **Subject** (ask openly, not multiple-choice) — \"What should this world be about? Your\n   business, a client's, or any idea — a word or a sentence is fine.\" Capture the\n   industry/product + a one-line pitch (e.g. \"a bubble tea company, from leaf to last\n   sip\"), and a brand name if they have one; otherwise you'll propose one below.\n2. **Brand kit** — offer three paths, pick one:\n   - Import from a URL: `higgsfield marketing-studio brand-kits fetch --url <site> --wait`\n     (pulls name, colours, tone). Then read it back with `brand-kits list --json`.\n   - The user hands you palette + name + tone directly.\n   - You propose a palette + name and let them approve.\n   Capture **4–6 named hex values**, a display name, and a tone word or two.\n3. **Art direction** — default is \"soft matte low-poly **clay diorama**, isometric,\n   tilt-shift miniature, warm light.\" Offer alternatives (flat papercraft, glossy toy,\n   claymation, neon night). Whatever is chosen becomes the shared **style preamble**\n   reused verbatim in every scene prompt (this is what makes the world cohesive).\n4. **Camera style — ALWAYS ask; it's the film's personality, not a technical\n   detail.** Ask by feel (`AskUserQuestion` in Claude Code; a plain question\n   elsewhere) and record the answer as `CAMERA`. The options map to the Step 4\n   architectures — Step 4 then *implements* the choice, it never re-decides it:\n   - **\"Fly through the world\"** — the camera dives into each scene, pulls up and\n     out, and hops across the miniature world to the next; angles change\n     constantly, big expressive aerial moves (this is the flagship-demo look).\n     → Architecture B. Recommend as the default for diorama/miniature art\n     directions.\n   - **\"One continuous walkthrough\"** — a single forward flight that glides\n     through each scene straight into the next, never pulling back; expressive\n     but always-forward moves per scene (camera grammar table). → Architecture A.\n     Recommend as the default for grounded/photoreal art directions.\n   - **\"Locked isometric glide\"** — the camera keeps one fixed angle for the whole\n     film, Emons-style; the world slides past/toward it, no rotation, no reveals.\n     → Architecture A + the locked-iso clause in every leg prompt (prompts.md).\n   State the trade-off in one line each (B reverses direction at seams — charming\n   in miniature, jarring in realism; locked-iso is the calmest and cheapest to\n   re-roll; walkthrough sits between).\n5. **The journey (sections)** — the ordered scenes the camera flies through. Propose a\n   set derived from the subject's own value chain and let the user edit. 5–7 works well.\n   Boba example: farms → pearl kitchen → flagship shop → delivery → community plaza →\n   the hero product. Each section needs: a short subject description (what's IN the\n   diorama), an eyebrow, a headline, one line of body, and 0–3 tag pills. The last\n   section is usually the hero product + the CTA.\n6. **Mobile version — ALWAYS ask this; never silently generate both.** Ask as a\n   two-option choice (`AskUserQuestion` in Claude Code; a plain question elsewhere):\n   *\"Want a mobile-optimized version too? The mobile version is a second camera chain\n   rendered natively in **9:16 portrait** — composed for phones, not a crop of the\n   landscape film — which roughly doubles the Higgsfield credit spend (state the\n   estimated number).\"*\n   Options: \"Desktop only\" / \"Desktop + mobile (native 9:16 — ~2× credits)\". The\n   credit cost must be stated to the user, not just implied.\n   What the answer gates:\n   - **Yes** → render the parallel 9:16 portrait chain and ship it as the mobile variants\n     (Step 6 / pipeline.md §6b): portrait start canvases → 9:16 dives + connectors\n     frame-locked against their own renders → 720-wide `-m.mp4` encodes → `stillMobile`\n     portrait posters. Wire `clipMobile`/`connectorsMobile`/`stillMobile` (Step 7); run\n     the full mobile QA (Step 8). Budget ~2N-1 extra video gens + NSFW re-rolls.\n     **Never ship the centre-crop as the mobile version by default** — if credits can't\n     cover the portrait chain, say so and offer the crop encodes (pipeline.md §6) as an\n     explicitly-labelled stopgap the user must approve.\n   - **No** → skip the mobile encodes and wiring entirely. The engine's phone hardening\n     (seek-coalescing, iOS priming, safe-area CSS) is always on regardless — that's not\n     a \"mobile version,\" it's just the page not breaking when a phone visits — so a\n     desktop-only build still degrades gracefully.\n\n7. **Budget — engines shown by cost, decided before anything renders.** Present the\n   render tiers (`AskUserQuestion`), then compute and state the estimated total for\n   the user's N scenes — `N stills + (2N−1) videos [videos ×2 if mobile] + ~15%\n   re-roll headroom` — and get a go before generating.\n   - **Video tier** (roster only — every option frame-locks seams, Step 4):\n\n     | Tier | Model | Rough cost |\n     |---|---|---|\n     | Draft / previz | `seedance_2_0_mini` (720p) | ~¼ of Standard |\n     | Standard (default) | `seedance_2_0` (1080p) | baseline |\n     | Alternate | `kling3_0` (720p native) | ≈ Standard; different look + content filter |\n\n     Draft doubles as the previz path: run the whole chain cheap, approve the\n     journey, re-render final legs on Standard (pipeline.md Notes) — suggest it\n     unprompted when the balance reads tight.\n   - **Backend — Monid is the DEFAULT biller for the chain** (Step 0.1; wiring\n     in pipeline.md → Monid backend). Same Seedance 2.0, per-clip USD instead of\n     credits. Token-priced `width × height × 24 × seconds / 1024` at $7/1M\n     (480p/720p) or $7.7/1M (1080p) — measured: 1080p 8s dive ≈ $2.99, 5s\n     connector ≈ $1.87; 720p ≈ $1.21 / $0.76; 480p ≈ $0.28 / $0.35. An N=6\n     desktop chain ≈ $27 at 1080p / ~$11 at 720p vs Higgsfield Plus-monthly ≈\n     $32 / $16 — ~15% cheaper per clip, parity with Plus-annual; structurally\n     better for one-off builds (pay-per-use, no monthly expiry). On Monid the\n     Draft/previz tier is simply the **same endpoint at 480p** — no model swap,\n     so previz→final stays one-model by construction. State `monid balance`\n     against the estimate; **fall back to Higgsfield credits** (per-model tiers\n     above) when the user prefers their subscription, the balance is short, or\n     the model must be `kling3_0` (Higgsfield-only). It's the same underlying\n     model (`seedance_2_0` ≙ Monid's `seedance-2.0`), so finishing a stranded\n     chain on the other biller is a reasonable rescue — but the serving stacks\n     differ and cross-provider seam character is **untested**: eyeball the first\n     rescued seam before rendering the rest, same as any model swap.\n   - **Stills source** (only offer if the Codex CLI is present, Step 0.5):\n     Higgsfield `gpt_image_2` (spends credits) vs **Codex `image_gen`** — the same\n     gpt-image-2 model billed to the ChatGPT subscription (zero credits; counts\n     toward Codex usage limits; 1536×1024 output — exactly 3:2, slightly under\n     Higgsfield's 2k). Stills are plain PNGs handed to `--start-image`, so the\n     video chain is indifferent to their source. Command in Step 2. **One source\n     for all N stills of a build** — the two render with slightly different\n     character (verified: Codex runs warmer/lighter), and mixing sources across\n     scenes reads as style drift, same reason the video chain uses one model.\n   - **Calibrate costs, don't guess.** The CLI exposes no pricing and plans differ.\n     Run ONE still and ONE video first, diff `higgsfield workspace list` before/\n     after, extrapolate to the full run, and warn the user whenever the estimate\n     exceeds ~70% of the balance. (Observed on a plus plan, 2026-07: Standard\n     video ≈ 40–55 credits, still ≈ 15.) A real `not_enough_credits` mid-run is\n     recoverable (finished clips survive; resume after top-up) but ugly — the\n     whole point of this step is that the user decides *before* the spend.\n\nIf the user names a video model outside the roster, honor it **only if it can\nframe-lock seams** (Step 4). This skill only ships seamless output, so a model that\ncan't frame-lock is declined with a one-line why, not substituted in — use a roster\nmodel instead.\n\nKeep the scroll mechanic fixed (continuous fly-through) — that's the point of the skill.\nSee `references/prompts.md` for the intake checklist and copy structure.\n\n---\n\n## Step 2 — Generate the scene stills\n\nOne image per section, **all sharing the same style preamble** for cohesion. Default\nmodel **`gpt_image_2`** (crisp, great at isometric illustration; returns a solid/white\nbackground which is perfect for floating diorama \"islands\"). Use `nano_banana_2` only if\nthe brief is character/cartoon-heavy (note: `nano_banana_2` is a CLI alias — it resolves\nto `nano_banana_pro`; it won't appear under that name in `higgsfield model list`).\n\nPrompt shape (full templates in `references/prompts.md`):\n\n```\n<STYLE PREAMBLE, identical every time>. On a plain solid <bg> background with a soft\ncontact shadow. <PALETTE hexes>. No text, no letters, no logos, centered, 3:2.\nSubject: <what is in THIS diorama>.\n```\n\n- Run all N concurrently, detached. Command per scene:\n  `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`\n- Result URL is `.[]0.result_url` in the `--wait --json` output. `curl` it down.\n- **Codex stills variant** (if chosen at Step 1.7 — subscription-billed, zero\n  credits): same prompt files, same byte-identical preamble, generated by Codex's\n  built-in `image_gen`:\n\n  ```bash\n  codex exec -C \"$WORK\" -s workspace-write --skip-git-repo-check \\\n    '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.' \\\n    < /dev/null\n  ```\n\n  Single-quote the `$imagegen` segment (the shell must not expand it); if editing\n  with reference images, the prompt goes BEFORE any `-i` flag (it's variadic).\n  ~1–3 min per image; run a few in parallel, not all N at once — and keep the\n  `< /dev/null`: parallel `codex exec` calls sharing a script's stdin hang\n  waiting for input (Gotchas). Output lands at\n  1536×1024 (3:2) — fine for `--start-image` and posters. Everything downstream\n  (cohesion review, knockout, dives) is unchanged.\n- A generation may fail transiently (HTTP 503) — re-roll that one individually; don't\n  restart the batch.\n- **Review the stills before continuing.** They must read as one cohesive world (same\n  angle, palette, light). If one is off-style, regenerate it, optionally passing an\n  approved scene as `--image` to lock style.\n\nSee `references/pipeline.md` for the exact batch script.\n\n---\n\n## Step 3 — (Optional) Float the scenes\n\nIf you want the dioramas to float over an atmospheric background instead of sitting in a\nsolid box, knock out the flat background to transparency with\n`references/knockout.py` (border-connected flood fill — preserves interior colour that\nmatches the bg, e.g. cream walls). Then encode to webp. If you'd rather keep it simple,\njust make the page background the same colour as the scene background and skip this.\n\nThese stills double as **video posters and lazy-load fallbacks**, so keep them.\n\n---\n\n## Step 4 — Camera architecture (implements the Step 1.4 choice)\n\nHow the camera moves *between* scenes is the single biggest quality lever. The user\nalready chose the style at the interview (`CAMERA`, Step 1.4): fly-through → **B**,\nwalkthrough → **A**, locked-iso → **A + the locked-iso leg clause** (prompts.md). If\nthe interview somehow skipped it, ask now — never silently pick for them. The two\nshapes, and the grammar that colors them:\n\n### Video model — pick ONE for the whole chain\n\n**This skill only ships seamless output**, so the only usable models are ones that can\nframe-lock a seam: every chained clip must accept `--start-image`, and connectors also\nneed `--end-image`. That capability — not preference — is the selection rule. Check any\nmodel with `higgsfield model get <job_type>` and **skip anything whose media inputs are\nreference-only** (no start/end image): it can only *condition* a generation, not\n*continue* a shot, so it physically can't hold a seam. Schemas below were confirmed\nagainst the CLI:\n\n| Model | start/end image | Notes |\n|---|---|---|\n| `seedance_2_0` (default) | ✓ / ✓ | Full chain (legs + connectors). `--mode std --resolution 1080p`. Its NSFW filter is the touchy one (see Gotchas). |\n| `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. |\n| `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. |\n\nThose three are the roster — all do both architectures. (`kling3_0_turbo` also frame-locks\nvia `--start-image`, but has no `--end-image`, so it's architecture-A-only and can't make\nconnectors; it also takes a different flag set — no `--mode`, has `--resolution` — so it\ndoesn't drop into the pipeline as-is. It's not in the default roster; only reach for it, and\nwire it by hand, if architecture A's sequential render time is a proven bottleneck and you've\nbenchmarked it as actually faster.)\n\nOne more architecture-A-only candidate, worth knowing because it is by far the cheapest\nprobe: **`minimax_hailuo`** (Hailuo-2.3, ~6 credits per 768p/6s clip vs 22–72 for the\nroster). Verified 2026-07: `--start-image` + prompt frame-locks (output frame 0 ≡ input,\nPSNR 33 dB) and a forward-glide prompt was obeyed, gently. Constraints: the 2.3 variant\nrejects `end_image` (no connectors → arch A only), output aspect follows the input image\n(hand it a 16:9 canvas, not a bare 3:2 still), motion runs subtler than seedance, and\ndon't pass `--resolution` (the CLI mis-types the enum; the 768 default works — 1080\nsupports 6s only). One clip ≠ a chain: qualify a leg-to-leg handoff before betting a\nfull build on it.\n\nRules:\n- **One model for all chained clips.** Each renderer has its own motion/color/grain\n  character; mixing models mid-chain keeps *position* continuity (frames still hand off)\n  but the render-character shift reads as a subtle pop. The one sanctioned exception is\n  the NSFW fallback for a single stubborn clip (Gotchas) — a slight character shift on\n  one 5s connector beats a missing connector.\n- Default to `seedance_2_0`, rendered through **Monid by default** (per-clip USD —\n  next section) with Higgsfield credits as the fallback biller (Step 0.1/1.6); honor\n  a user's stated preference **only if the model qualifies** (frame-locking). If it\n  doesn't, say so and use a supported model — never ship a non-seamless build to\n  satisfy a model request. `kling3_0` and `seedance_2_0_mini` exist only on the\n  Higgsfield side.\n- The pipeline scripts take the model as `$VMODEL` with per-model flags already cased\n  out (`references/pipeline.md`).\n\n### Monid backend — the DEFAULT chain biller (qualified 2026-07-25)\n\nMonid's **`bytedance /v1/video/seedance-2.0`** passed both paid probes on\n2026-07-25 and is the **default** way this skill renders the chain, for **both\narchitectures** — it is the roster's `seedance_2_0` served pay-per-USD (wiring in\npipeline.md → \"Monid backend\"; Higgsfield renders the chain only as the fallback\nbiller or for Higgsfield-only models):\n\n- **Leg probe** (prompt + `first_frame` image): output frame 0 ≡ input still\n  (PSNR 31.6 dB), forward-glide prompt obeyed, billed the advertised cell\n  ($0.279 / 480p 4s).\n- **Connector probe** (prompt + `first_frame` + `last_frame`): start locked\n  (31.6 dB); the end **lands close but not pixel-perfect** (27.5 dB, same\n  composition, prop-level drift) — the exact end-image behavior Seedance shows\n  on Higgsfield, covered by the engine's seam crossfade and by using the next\n  dive's ACTUAL first frame as the end-image (Step 5 law, unchanged).\n\nThe I/O contract differs from the Higgsfield CLI — three rules:\n\n1. **Images go by URL, never inline.** `content` items are\n   `{\"type\":\"image_url\",\"image_url\":{\"url\":…},\"role\":\"first_frame\"|\"last_frame\"}`;\n   base64 data URLs are **rejected** (\"Must be a public https:// URL or an\n   asset://<id> reference\"). Local frames travel through Monid's free workspace\n   file system: `sfs /put` → `curl -T` the bytes → `sfs /cat` returns a signed\n   public URL to paste into the body ($0, explicitly built for this).\n2. **Pass `ratio` explicitly** (`16:9`, or `9:16` for the mobile chain) — the\n   adaptive default follows the input image's aspect instead.\n3. **Bill-check every clip**: cost is token-priced\n   (`w × h × 24 × sec / 1024` at $7–7.7/1M); read `cost.value` off each run.\n\nHistory that shaped these rules (still true as of 2026-07-25): the seedance\nendpoints were text-to-video-only until late July 2026 — **re-`inspect` before\neach build; the catalog moves in both directions.** `minimax\n/v1/video_generation` (Hailuo-2.3) remains disqualified: sending `prompt` +\n`first_frame_image` together silently drops the image (unrelated t2v output,\nwrong price cell); image-only frame-locks (33 dB) but has no camera control.\n\n**Qualification protocol for any new/changed Monid endpoint** (each probe is one\ncheap 480p clip): (1) prompt + first-frame from a real still — frame 0 must\nmatch the input to codec noise (PSNR ≳ 30 dB) and `cost.value` must match the\nadvertised cell; (2) for connector duty, add a `last_frame` from a different\nstill — the end must land on that composition (Seedance-style near-miss is fine,\nthe crossfade covers it). Pass → pay-per-clip tier (arch A if start-only; full\nroster if start+end).\n\n### A) Continuous forward take — RECOMMENDED for grounded / realistic / walkthrough\nOne camera that only ever glides **forward**, first scene through last, as a single take.\nGenerate the legs **sequentially**: leg 0 from scene-0's still (glide forward into it);\nthen each leg's `--start-image` = the **previous leg's ACTUAL last frame** (extract with\nffmpeg), prompt *\"continue gliding smoothly FORWARD into [scene i], never pulling back\"*\n(or an expressive mid-leg move under the motion-handoff contract — see **Camera grammar**\nbelow), and **no `--end-image`** — an end-image of a wide establishing shot forces the\ncamera to pull back, which is the #1 cause of stutter. Extract each leg's last frame to feed the\nnext. Result: every seam is frame-identical **and** the camera never reverses. There are\n**no connectors** (skip Step 5) — the legs ARE the journey. Wire each leg as a section\nclip with `connectors: []` and a small `crossfade` (~0.08). Even without an `--end-image`\nthe legs still arrive at distinct rooms (the prompt steers the content). Cost: strictly\n**sequential** (can't parallelize) and slower; interiors trip the NSFW filter, so build in\nre-rolls (3 attempts/leg).\n\n### B) Dive-in + aerial connector — only for diorama / miniature / god's-eye worlds\nA \"dive into each scene\" clip + a connector that pulls **up and out** and flies over to the\nnext scene (Step 5). The pull-out **reverses camera direction at every seam** (forward dive\n→ backward pull-out). In a miniature/diorama world that reads as an intentional \"zoom out\nto the map, fly to the next island\"; in a grounded first-person walkthrough it reads as a\njarring **rewind/stutter**. Use B only for the map-like aesthetic — which is exactly\nwhat the \"fly through the world\" interview answer opts into; the reversal reads as\nintentional there. If the user picked B against a grounded/photoreal direction, say\nwhy it will read as a stutter and confirm before rendering.\n\n### Camera grammar — the move should fit the concept (A is NOT \"forward only\")\n\n\"Forward only\" is the *seam* rule, not the *leg* rule. The physics of the chain:\n\n- **Position continuity** at a seam comes from the frame handoff (next leg starts from the\n  previous leg's actual last frame).\n- **Velocity continuity** at a seam means the camera must never *reverse across a seam* —\n  that's the rewind stutter.\n- **Inside a single leg the camera is free.** One leg is one continuous render — there is\n  no seam to break mid-leg, so orbits, crane-ups, lateral tracking, even a push-in that\n  eases back out are all safe *within* the clip. Reversals are only fatal *across* seams.\n\nSo give each leg an expressive move chosen from the scene's own logic, under a **motion\nhandoff contract**: every leg **ends by settling into a slow, steady forward drift** toward\nthe next destination (final ~1 s), and every leg **begins by continuing that same drift**.\nKeep both clauses in the prompts verbatim (templates in `references/prompts.md`).\n\nPick the grammar from the concept:\n\n| Concept / tone | Mid-leg move |\n|---|---|\n| Product / luxury retail | slow half-orbit around the hero object, then continue past it |\n| Real estate / hospitality | steadicam glide through doorways; gentle crane-up in atria |\n| Industrial / process / logistics | low lateral track alongside the line, foreground parallax |\n| Travel / outdoors / campus | drone-style rise-and-reveal, then a descending swoop |\n| Food / craft / detail-driven | push in close to the craft moment, ease back, carry on |\n| Playful miniature (arch. B) | dives + aerial hops — the connector IS the grammar |\n\nHonest costs: expressive mid-leg moves raise re-roll odds — the model can end a fancy move\nin a state that isn't a clean forward drift. Mitigations: keep the final-second settle\nclause verbatim; **eyeball each leg's last frame before chaining the next** (it should look\nlike a frame from a gentle forward glide — if not, re-roll before wasting the next leg);\nbudget ~1 extra re-roll per expressive leg. A plain forward glide stays the zero-risk\ndefault — use it for legs where the scene itself is the show.\n\n**Locked-iso variant** (`CAMERA` = locked isometric glide): architecture A where every\nleg pins the view instead of taking a mid-leg move — \"the camera keeps exactly the same\nhigh isometric angle throughout, no rotation, no orbit, no tilt; it only travels\nstraight and level, the world sliding past beneath the same view\" (verbatim clause in\nprompts.md). The handoff contract is unchanged. Seedance drifts the angle slightly on\nlong legs — the existing eyeball-each-last-frame rule is the catch; re-roll a leg whose\nview has rotated. Calmest look, cheapest re-rolls, and the closest to the Emons\nreference.\n\nTwo related pacing knobs live in the engine (Step 7): per-section `scroll` (more scroll\ndistance = longer dwell in that scene) and `linger` (the camera settles mid-scene exactly\nwhile the copy peaks, then picks up speed toward the seam). Prefer expressive motion in the\n*clip* and restraint in the *scrub mapping* — they compound.\n\nAnd remember scroll is a scrubber: visitors can scroll **up**, so every move also plays in\nreverse. That's free and expected — no extra work — but it's another reason seam velocity\nmust be consistent in both directions (a seam that reads fine forward reads as a stutter\nbackward too if velocity flips).\n\n**For B**, one camera flight per scene: starts high/outside, descends into the interior,\nstructure opens. Model: the chain model you picked above (default **`seedance_2_0`**),\n`--start-image = the scene still`.\n\n- Use the **solid-background still** (not the knocked-out transparent one) as the\n  start image, so the video has a full frame.\n- Prompt: \"Single continuous cinematic camera move, no cuts. Begin high and far looking\n  at the whole <scene> from outside … descend and fly inside toward <focal point> … the\n  roof/walls gently open to reveal the interior. <style>, smooth graceful slow motion.\n  No text.\" (Template in `references/prompts.md`.)\n- Params (seedance): `--mode std --resolution 1080p --aspect_ratio 16:9 --duration 8`.\n  For Kling: drop `--resolution` (no such param), add `--sound off`, `--duration 10`.\n  Do **not** pass `--generate-audio` (it errors on seedance; audio is wasted anyway —\n  you'll mute).\n- Run concurrently, detached, then download each `.result_url`. Re-roll individual\n  failures. Keep the raw 1080p sources — you need their frames next.\n\n---\n\n## Step 5 — Connectors (architecture B only)\n\nSkip this whole step for architecture **A** — the forward take has no connectors; its legs\nalready chain seamlessly. This step applies to **B** (diorama/miniature), and note the\nreversal caveat from Step 4.\n\nThe connector clips are what make the world feel *connected* instead of cut. A connector\nflies from the end of scene i out and into the start of scene i+1. **Both of its\nendpoints must be the ACTUAL RENDERED FRAMES of the neighbouring clips — never the\noriginal diorama still.**\n\nWhy: every Higgsfield generation renders slightly differently. If a connector *ends* on\na fresh render of \"the kitchen diorama,\" but the next dive clip *starts* on its own\ndifferent render of that same diorama, the two won't match and you get a pop at the seam.\nThe fix is to hand off the exact pixels:\n\n```\nFor each connector between dive_i and dive_{i+1}:\n  start-image = the LAST frame extracted from dive_i's rendered video\n  end-image   = the FIRST frame extracted from dive_{i+1}'s rendered video\n```\n\nNow every seam is frame-identical on *both* sides:\n`dive_i.end == connector.start` and `connector.end == dive_{i+1}.start`.\n\nExtract the boundary frames from the rendered dives (not the stills):\n\n```bash\nffmpeg -sseof -0.15 -i dive_i.mp4   -frames:v 1 -q:v 2 dive_i_last.png    # interior of i\nffmpeg -ss 0      -i dive_{i+1}.mp4 -frames:v 1 -q:v 2 dive_next_first.png # establishing of i+1\n```\n\nGenerate the connector (`--duration 5` is plenty). Connectors need `--end-image`, so the\nmodel must accept it — any roster model does (`seedance_2_0`, `seedance_2_0_mini`,\n`kling3_0`):\n\n```bash\nhiggsfield generate create \"$VMODEL\" \\\n  --prompt \"$(cat connector_i.txt)\" \\\n  --start-image dive_i_last.png --end-image dive_next_first.png \\\n  $VOPTS --aspect_ratio 16:9 --duration 5 --wait --json\n# seedance: VOPTS=\"--mode std --resolution 1080p\"; kling3_0: VOPTS=\"--mode std --sound off\"\n```\n\nConnector prompt: \"Single continuous camera move, no cuts. Pull up and back out of\n<scene i>, rise into the sky, glide across the connected miniature world, and arrive\nabove <scene i+1>, beginning to descend toward it. Seamless flowing aerial transition.\n<style>. No text.\" (Template in `references/prompts.md`.)\n\nInsurance: Seedance lands *close* to the end-image but not always pixel-perfect, so the\nengine still applies a **short crossfade** (a few frames) at each seam. Frame-matched\nendpoints + a small crossfade = no visible cut. Never skip the actual-frame handoff and\nrely on the crossfade alone; a big content jump can't be hidden by a crossfade.\n\n---\n\n## Step 6 — Encode for smooth scrubbing\n\nScrubbing = setting `video.currentTime` from scroll. Two things matter, and they are\noften gotten wrong:\n\n1. **Seekability, not keyframe density, is what makes scrubbing work.** Many static\n   hosts (and `python -m http.server`) don't serve HTTP byte-range requests, which pins\n   `video.seekable` to `[0,0]` and clamps *every* seek to frame 0 — the video looks\n   frozen. The robust fix is to **fetch each clip as a `Blob` and play it from an\n   in-memory object URL** (blobs are always fully seekable). The engine does this.\n   Because of it, you do **not** need all-intra video.\n2. **Don't shrink quality to get smooth seeks.** Encode at the **native resolution**\n   (1080p from Seedance — don't downscale), `crf ~20`, a **small GOP** (`-g 8`) rather\n   than all-intra (all-intra bloats an 8s clip to ~25 MB; GOP 8 is ~8 MB and scrubs\n   fine via blob). Strip audio, add faststart, and a light `unsharp` counters video\n   softness:\n\n```bash\nffmpeg -i src.mp4 -an -vf \"unsharp=5:5:0.8:5:5:0.0\" \\\n  -c:v libx264 -preset slow -crf 20 -pix_fmt yuv420p \\\n  -g 8 -keyint_min 8 -sc_threshold 0 -movflags +faststart out.mp4\n```\n\nEncode all 2N-1 clips (dives + connectors) with the same settings for uniform quality.\n\n**Mobile encodes (only if the user opted in at Step 1.6).** The mobile version is\nthe **native 9:16 portrait chain** (pipeline.md §6b): portrait renders of every dive and\nconnector, encoded **720 wide (`scale=720:-2`), `-g 4`** (more keyframes = cheaper seeks —\nphone decoders' seek cost scales with GOP length), crf 23 — wired as `clipMobile` /\n`connectorsMobile`, with each portrait dive's first frame extracted as the section's\n`stillMobile` poster (Step 7). The engine serves them automatically on phones and falls\nback to the desktop clip when absent. The 16:9 centre-crop `encm()` encodes\n(pipeline.md §6) are a **fallback only** — for when credits can't cover the portrait\nchain — and shipping them must be called out to the user, never silent. If the user chose\ndesktop-only, skip this — the engine still hardens phone scrubbing regardless\n(seek-coalescing, iOS priming), so the page degrades gracefully rather than breaking.\n\n---\n\n## Step 7 — Assemble the page\n\nCopy `references/scrub-engine.js` (and, if you want a fully standalone page, the tiny\n`references/index-template.html`) into the user's project — or adapt into their\nframework. It's config-driven and self-contained:\n\n```js\nmountScrollWorld(document.getElementById('world'), {\n  brand: { name: 'Pearl & Co.' },\n  diveScroll: 1.3, connScroll: 0.9,          // viewport-heights of scroll per clip\n  sections: [\n    { id:'farm', label:'The Farms', still:'assets/farm.webp',\n      clip:'assets/vid/farm.mp4',\n      clipMobile:'assets/vid/farm-m.mp4',      // mobile opt-in only: native 9:16 render\n      stillMobile:'assets/farm-m.webp',        // its first frame as the portrait poster\n      scroll: 1.6, linger: 0.45,   // optional pacing: longer dwell + camera settles mid-scene\n      accent:'#8FB98A', eyebrow:'From leaf to last sip', title:'It starts in the hills.',\n      body:'…', tags:['Single-origin','Hand-picked'] },\n    // …one per section; last may carry a `cta`\n  ],\n  connectors:       ['assets/vid/conn1.mp4','assets/vid/conn2.mp4',   /* … length = sections-1 */],\n  connectorsMobile: ['assets/vid/conn1-m.mp4','assets/vid/conn2-m.mp4' /* … same length; mobile opt-in only */],\n});\n```\n\nThe engine handles: the ordered dive/connector chain, scroll→currentTime with rAF\nsmoothing, blob loading, lazy prefetch of nearby clips, frame-matched crossfades, pinned\nper-section copy (first section greets on landing, last holds its CTA), a route rail,\n`prefers-reduced-motion`, and mobile. **Pacing per section:** `scroll` overrides\n`diveScroll` for that scene (more scroll = longer dwell) and `linger` (0–1, keep ≤ 0.6)\nremaps time so the camera settles mid-scene — exactly while the copy peaks — then speeds\nup toward the seam; seam frames are untouched (f(0)=0, f(1)=1). Give the hero and finale\nscenes a higher `scroll` + some `linger`; keep transit scenes brisk. Theme it with CSS variables (`--accent`,\n`--sw-bg`, `--sw-ink`, …) — the visual identity comes from the generated clips, so the\nchrome stays quiet. See the header of `scrub-engine.js` for the full config + CSS vars.\n\n**On phones the engine adapts automatically** (coarse pointer or ≤860px): it serves\n`clipMobile` / `connectorsMobile` when present, **coalesces seeks** (never queues a new\n`currentTime` while the decoder is still seeking — this is what stops a fast flick from\nfreezing the clip), **keeps the still as a poster until the clip paints its first frame**\nand **primes each video on first touch** (fixes iOS's blank-until-played video), drops the\ndrifting particles, ignores URL-bar-only resizes (no scroll jump), and uses safe-area\ninsets so copy clears the notch/home indicator. All of this hardening is on by default —\nno config needed. The `clipMobile`/`connectorsMobile` encodes are the opt-in part\n(Step 1.6): only wire them when the user asked for the mobile version.\n\nFor non-JS backends (Python/Rails/etc.): serve the assets and drop the engine `<script>`\ninto the rendered HTML; nothing about it is framework-specific.\n\n---\n\n## Step 8 — QA the seams (don't skip)\n\nDrive the page in a headless browser and **verify frame continuity at the seams**, which\nis the thing most likely to be wrong:\n\n- Screenshot at scroll positions just before and just after each seam. The two frames\n  must be near-identical (the dive's last frame == the connector's first frame). If they\n  pop, you used the diorama still instead of the actual rendered frame (redo Step 5), or\n  the crossfade band is too short. Calibration: judge seams by *composition*, not raw\n  PSNR — at 720p/1080p a correctly frame-locked seam can read ~18–25 dB from detail\n  shimmer alone (observed on a verified-good build); a real mismatch shows as different\n  composition/props, not just softness.\n- Check the console for errors, confirm `video.seekable.end(0) > 0` (blob working), and\n  that `currentTime` tracks scroll across each clip's band.\n- **Mobile — full checklist only if the user opted into the mobile version (Step 1.6).**\n  For a desktop-only build, just sanity-check a phone viewport once: page loads, still\n  posters show, nothing overlaps — the engine's hardening covers graceful degradation.\n  For the mobile build (do this on a real phone or an emulated one, portrait + landscape):\n  - Emulate a phone viewport **with CPU throttled 4–6×** and scroll fast — the clip should\n    track without freezing (the seek-coalescing + `-m.mp4` encodes are what make this hold).\n  - Confirm the first scene shows immediately (its still is the poster) and the video takes\n    over the instant you scroll — no blank/black scene (the iOS priming fix). Test iOS Safari\n    specifically; it's the one that goes blank if this regresses.\n  - Verify the `-m.mp4` variant is actually served on mobile (Network panel), and the\n    heavy 1080p master on desktop. The mobile clips must be **natively portrait**\n    (`videoWidth < videoHeight` — not a downscaled 16:9 file), and the `stillMobile`\n    posters must be served and match each portrait clip's first frame (no\n    landscape→portrait flash when the video paints).\n  - Slowly scroll so the URL bar collapses — the page must **not jump** (height-only resizes\n    are ignored on touch). Rotate the device — layout should recompose cleanly.\n  - Only if the crop **fallback** shipped (no credits for the portrait chain): portrait\n    crops a 16:9 clip to its centre — confirm the focal subject still reads, and remind\n    the user this is the stopgap, not the mobile version.\n- Check reduced-motion (should fall back to the stills, no video, no particles).\n\n---\n\n## Gotchas (hard-won)\n\n- **Seam pop** → connector endpoints were the diorama stills, not the neighbouring\n  clips' actual frames. Always extract real frames (Step 5).\n- **Seam stutter / camera \"jumps backward\"** → even with frame-matched seams, if the\n  camera *velocity reverses* (forward dive, then a connector that pulls back out) it\n  reads as a rewind. This is inherent to architecture B. For any grounded walkthrough use\n  architecture A (one continuous forward take — legs chained from actual last frames, no\n  pull-back, no `--end-image`); see Step 4.\n- **Frozen video / stuck at frame 0** → `seekable=[0,0]`; the host isn't serving byte\n  ranges. Use blob URLs (engine does).\n- **Huge files** → you used all-intra. Use `-g 8` + blob instead.\n- **Soft / low quality** → you downscaled or over-compressed. Encode native 1080p,\n  crf ≤ 20, add `unsharp`. Video is inherently softer than the stills — keep the stills\n  as the lite fallback for max fidelity.\n- **Concurrent gens 503 / \"not_enough_credits\" race** → transient when many launch at\n  once; re-roll the individual failure, it's not really out of credits (verify with\n  `higgsfield workspace list`).\n- **NSFW false-positives (Seedance `status \"nsfw\"`)** → the video content filter flags\n  perfectly innocuous clips, especially **bedroom, pool, spa/wellness** contexts and\n  trigger words like \"bed\", \"pool\", \"waterfall\", \"wine\", \"swim\". It's partly the prompt\n  wording and partly the reference frames. Fixes, in order: (1) re-roll — it's often\n  non-deterministic and passes on the 2nd–3rd try; (2) strip trigger words and add\n  \"empty, unoccupied, no people, no figures, architectural, tasteful\"; (3) regenerate\n  just that clip on **`kling3_0`** with the same start/end frames — a different\n  provider's filter often passes what Seedance blocks. Expect a slight render-character\n  shift on that one clip (each model has its own grain/motion feel); for a 5s connector\n  behind a crossfade that usually beats option (4): set the connector slot to `null` —\n  the engine crossfades that seam directly (optional connectors), so the page still\n  completes. Budget extra credits/time for these re-rolls on interiors/real-estate content.\n- **Dark / custom theme** → the engine wraps its default tokens in `@layer sw`, so a\n  page-level `:root` / `.sw-root { --sw-bg; --sw-ink; --sw-accent; --sw-font-* }` block\n  wins cleanly (no specificity hacks). `--sw-ink` is your primary **text/heading** colour;\n  the **accent** fills the primary button and active nav. For a dark theme, set `--sw-bg`\n  dark and `--sw-ink` light — the copy scrim and title shadow follow `--sw-bg` automatically.\n- **Phone scrub stutters / freezes on a fast flick** → the 1080p master is too heavy for a\n  phone decoder and seeks pile up. Ship the `-m.mp4` mobile encodes (720p, `-g 4`) and wire\n  `clipMobile`/`connectorsMobile` (Step 6/7). The engine already coalesces seeks; the lighter\n  encode is the other half. Still choppy on a low-end device? Tighten GOP (`-g 2` / all-intra).\n- **Blank / black scene on iOS (desktop was fine)** → an iOS Safari quirk: a muted video that\n  was never played won't paint a seeked frame. The engine fixes this by keeping the still as a\n  poster until the clip paints and priming each video on first touch — so **don't** hide the\n  still on `loadedmetadata` or strip the `playsinline`/`muted` attributes if you adapt the\n  engine into a framework.\n- **Page jumps while scrolling on mobile** → something is re-running layout on the URL-bar\n  show/hide `resize`. The engine ignores height-only resizes on touch; if you ported it, gate\n  your resize handler on a width change (keep the `orientationchange` path for rotation).\n- **Copy hidden behind the URL bar / notch on mobile** → use the engine's safe-area-aware\n  bottom offset (`env(safe-area-inset-bottom)` + `dvh`); make sure the page's\n  `<meta viewport>` includes `viewport-fit=cover` (the template does).\n- **Portrait crops the scene** → a 16:9 clip on a tall phone shows only its centre — which\n  is why the mobile version is the native 9:16 chain (§6b), never the crop. If you're seeing\n  this on a mobile build, either the crop fallback shipped (call it out to the user) or the\n  9:16 encodes aren't actually being served (check `videoWidth < videoHeight`). Keeping each\n  scene's focal subject centred (prompts.md) still matters for the desktop film itself.\n- **`--generate-audio` errors on seedance** → omit it; mute in HTML and `-an` on encode.\n- **Kling rejects your flags** → `kling3_0` has **no `--resolution` param** (don't pass\n  one; encode at whatever native res ffprobe reports) and **sound defaults on** — pass\n  `--sound off`. Duration default is 5; legs/dives want 10.\n- **Seam pop only where you \"saved credits\"** → you swapped models mid-chain, or used a\n  start-image-only model where a connector needs an `--end-image`. One model for the whole\n  chain; the only cheap tier is `seedance_2_0_mini`, which keeps frame-locking so it stays\n  seamless. (Any model with reference-only inputs can't hold a seam at all — Step 4.)\n- **Monid seedance rejects inline images** → \"Must be a public https:// URL or an\n  asset://<id> reference\": frames go through the free `sfs` file system\n  (put → `curl -T` → cat → signed URL; pipeline.md → Monid backend), never base64.\n  Quirk: `/put` echoes back `home/<path>`, but `/cat` and `/ls` want the **original\n  relative path** you gave `/put` — using the echoed path 404s.\n- **Monid clip wrong aspect** → the `ratio` default is adaptive and follows the input\n  image (a 3:2 still → a 4:3-ish video). Pass `--ratio` — `16:9` desktop, `9:16`\n  mobile chain — explicitly on every chained clip.\n- **Monid CLI \"Polling timed out after 120s\"** → only the local wait died; the run\n  continues server-side. Re-poll with `monid runs get -r <runId> -w 120` (find the id\n  in `monid runs list`). Result URLs expire (~24–48 h) — download immediately.\n- **Monid minimax drops the image when a prompt is present** → `prompt` +\n  `first_frame_image` together returns an unrelated t2v clip AND bills the wrong matrix\n  cell ($0.56 vs $0.28 observed). Image-only frame-locks but has no camera control.\n  Until the wrapper is fixed, that endpoint can't chain — use Monid's seedance-2.0.\n  (The model itself is fine — the same prompt+image via Higgsfield `minimax_hailuo`\n  frame-locks.)\n- **Monid billing surprises** → matrix/token-priced endpoints bill by selector match:\n  pass every selector field explicitly (`model`, `resolution`, `duration`) and read\n  `cost.value` off the run result after each clip. A big base64 field in any body can\n  also bounce as an HTML error page (\"Unexpected token '<'\") — another reason frames\n  travel by sfs URL.\n- **Monid schema changed since last build** → it happens (seedance was t2v-only until\n  late July 2026, then gained first/last-frame support). `monid inspect` before each\n  build; re-run the Step 4 qualification probes when the Input schema differs from\n  what pipeline.md documents.\n- **Codex stills hang at \"Reading additional input from stdin...\"** → parallel\n  `codex exec` calls launched from one script share the parent's stdin; one wins it,\n  the rest block forever (observed: 1 of 3 completed, 2 hung, the second batch never\n  started). Always append `< /dev/null` to every backgrounded `codex exec` — the\n  pipeline's `gen_still_codex` has it; keep it if you adapt the command.\n- **White-box scenes** → `gpt_image_2` returns a solid bg; either match the page bg to it\n  or knock it out (Step 3).\n- **bash 3.2** on macOS → no associative arrays in scripts.\n- **Connector grabs the wrong scene's frames** (or errors on a frame that doesn't exist\n  yet) → the array loop ran in **zsh** (macOS default interactive shell), where arrays are\n  1-indexed, not bash's 0-indexed. Keep every array-driven chain step in a `#!/bin/bash`\n  script run via `bash script.sh` — never inline array loops in the interactive shell.\n\n## References\n\n- `references/prompts.md` — the intake checklist, style-preamble pattern, and every\n  prompt template (scene still, dive, connector) with fill-in slots.\n- `references/pipeline.md` — copy-paste batch scripts for the whole run (generate →\n  extract frames → connectors → encode → mobile encode), bash-3.2-safe.\n- `references/scrub-engine.js` — the portable, config-driven scrub engine (builds DOM +\n  injects CSS; blob-seek, lazy load, seam crossfade, copy, route rail, reduced-motion, and\n  phone hardening: mobile encodes, seek-coalescing, iOS priming, safe-area, no-jump resize).\n- `references/index-template.html` — a minimal standalone page that mounts the engine.\n- `references/knockout.py` — border-connected background knockout for floating scenes.\n","frontmatter":{"name":"scroll-world","description":"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.\n","allowed-tools":"Bash, Read, Write, Edit, AskUserQuestion, Skill","allowedTools":["Bash,","Read,","Write,","Edit,","AskUserQuestion,","Skill"]},"allowedTools":["Bash,","Read,","Write,","Edit,","AskUserQuestion,","Skill"],"isInternal":false,"tokens":12861,"sizeBytes":49544},{"name":"marketplace.json","path":".claude-plugin/marketplace.json","rawUrl":"https://raw.githubusercontent.com/oso95/scroll-world/HEAD/.claude-plugin/marketplace.json","title":"scroll-world","category":"marketplace","format":"json","content":"{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"scroll-world\",\n  \"description\": \"Immersive scroll-scrubbed 'fly through the world' landing pages, generated with Higgsfield.\",\n  \"owner\": {\n    \"name\": \"cyw\",\n    \"email\": \"cyw@cywang.me\"\n  },\n  \"plugins\": [\n    {\n      \"name\": \"scroll-world\",\n      \"source\": \"./\",\n      \"skills\": [\"./skills/scroll-world\"],\n      \"description\": \"Build an immersive scroll-scrubbed landing page where a pre-rendered camera flies from outside each scene into its interior, then flows on to the next with no cuts. Interviews the user for topic, story beats, and brand kit; generates cohesive scenes + seamless camera clips with Higgsfield; wires a portable, framework-agnostic scrub engine.\",\n      \"category\": \"design\"\n    }\n  ]\n}\n","frontmatter":{"$schema":"https://anthropic.com/claude-code/marketplace.schema.json","name":"scroll-world","description":"Immersive scroll-scrubbed 'fly through the world' landing pages, generated with Higgsfield.","owner":{"name":"cyw","email":"cyw@cywang.me"},"plugins":[{"name":"scroll-world","source":"./","skills":["./skills/scroll-world"],"description":"Build an immersive scroll-scrubbed landing page where a pre-rendered camera flies from outside each scene into its interior, then flows on to the next with no cuts. Interviews the user for topic, story beats, and brand kit; generates cohesive scenes + seamless camera clips with Higgsfield; wires a portable, framework-agnostic scrub engine.","category":"design"}]},"isInternal":false,"tokens":203,"sizeBytes":801},{"name":"plugin.json","path":".claude-plugin/plugin.json","rawUrl":"https://raw.githubusercontent.com/oso95/scroll-world/HEAD/.claude-plugin/plugin.json","title":"scroll-world","category":"plugin-manifest","format":"json","content":"{\n  \"name\": \"scroll-world\",\n  \"description\": \"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. Interviews the user for the topic, story beats, and brand kit, then generates cohesive scenes + seamless camera clips and wires a portable, framework-agnostic scroll-scrub engine. Use for a '3D world' / 'browse-through-the-industry' hero, a scroll cinematic, or a diorama landing.\",\n  \"version\": \"0.8.0\",\n  \"author\": {\n    \"name\": \"cyw\",\n    \"email\": \"cyw@cywang.me\"\n  },\n  \"homepage\": \"https://github.com/oso95/scroll-world\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"higgsfield\",\n    \"monid\",\n    \"landing-page\",\n    \"scroll\",\n    \"video\",\n    \"seedance\",\n    \"diorama\",\n    \"webgl-alternative\",\n    \"cinematic\"\n  ]\n}\n","frontmatter":{"name":"scroll-world","description":"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. Interviews the user for the topic, story beats, and brand kit, then generates cohesive scenes + seamless camera clips and wires a portable, framework-agnostic scroll-scrub engine. Use for a '3D world' / 'browse-through-the-industry' hero, a scroll cinematic, or a diorama landing.","version":"0.8.0","author":{"name":"cyw","email":"cyw@cywang.me"},"homepage":"https://github.com/oso95/scroll-world","license":"MIT","keywords":["higgsfield","monid","landing-page","scroll","video","seedance","diorama","webgl-alternative","cinematic"]},"isInternal":false,"tokens":256,"sizeBytes":958}],"systemPromptSnippet":"<yakaai_skills repo=\"oso95/scroll-world\">\n<!-- File: skills/scroll-world/references/pipeline.md (Tokens: ~5125 | Category: anthropic-skill) -->\n# Pipeline: copy-paste scripts (bash 3.2 safe)\n\nSet these once. `NAMES` is the ordered section ids; the last is the hero/finale.\n\n```bash\nWORK=/tmp/scroll-world           # scratch dir for prompts, sources, frames\nASSETS=./assets                  # where the site reads stills (webp) + clips (mp4)\nmkdir -p \"$WORK\" \"$ASSETS/vid\"\nNAMES=\"farm kitchen shop delivery plaza finale\"   # <-- your section ids, in order\n\n# Chain video model — ONE for every chained clip (SKILL Step 4 roster).\n# Must accept --start-image AND --end-image (verify: higgsfield model get <model>):\n# seedance_2_0 | kling3_0 | seedance_2_0_mini (draft tier). Reference-only models can't\n# hold a seam; models without --mode (e.g. kling3_0_turbo) need their own flag branch below.\n# DEFAULT backend: Monid pay-per-clip (§7 — gen_dive_monid/gen_conn_monid with\n# VRES=1080p|720p|480p instead of VOPTS). The gen_dive/gen_conn functions in §2/§4\n# below are the Higgsfield-credits FALLBACK (and the only home of kling3_0/mini).\nVMODEL=seedance_2_0\ncase \"$VMODEL\" in                                  # per-model flags + durations (bash 3.2 safe)\n  kling3_0)          VOPTS=\"--mode std --sound off\";          DIVE_DUR=10; CONN_DUR=5 ;;  # no --resolution param on Kling\n  seedance_2_0_mini) VOPTS=\"--mode std --resolution 720p\";    DIVE_DUR=8;  CONN_DUR=5 ;;  # cheap frame-locked previz\n  *)                 VOPTS=\"--mode std --resolution 1080p\";   DIVE_DUR=8;  CONN_DUR=5 ;;  # seedance_2_0 default\nesac\n```\n\nHiggsfield generations take minutes — every `higgsfield ... --wait` call below is meant\nto run inside a **backgrounded** script. Launch the whole script with your tool's\nbackground/detached mode and poll the progress log; never block the foreground.\n\n## 1. Scene stills (Step 2)\n\nWrite one prompt file per section to `$WORK/still_<name>.txt` (see prompts.md), then:\n\n```bash\ngen_still() { # name\n  higgsfield generate create gpt_image_2 --prompt \"$(cat \"$WORK/still_$1.txt\")\" \\\n    --aspect_ratio 3:2 --resolution 2k --quality high --wait --wait-timeout 15m --json \\\n    > \"$WORK/still_$1.json\" 2> \"$WORK/still_$1.err\"\n  url=$(jq -r '.[0].result_url // empty' \"$WORK/still_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/still_$1.png\" && echo \"still $1 ok\" || echo \"still $1 FAIL\"\n}\nfor n in $NAMES; do gen_still \"$n\" & done ; wait\n```\n\nCodex variant (STILLS_SOURCE=codex, SKILL Step 1.7 — subscription-billed, zero\ncredits; ~1–3 min each, parallelize in small batches):\n\n```bash\ngen_still_codex() { # name   (< /dev/null is REQUIRED for parallel calls — see SKILL Gotchas)\n  codex exec -C \"$WORK\" -s workspace-write --skip-git-repo-check \\\n    'Use the image generation tool ($imagegen) to generate: '\"$(cat \"$WORK/still_$1.txt\")\"' Wide 3:2 landscape, high resolution. Save it as ./still_'\"$1\"'.png. Do not do anything else.' \\\n    > \"$WORK/still_$1.codex.log\" 2>&1 < /dev/null\n  [ -f \"$WORK/still_$1.png\" ] && echo \"still $1 ok (codex)\" || echo \"still $1 FAIL (see .codex.log)\"\n}\n```\n\nConvert to webp for the site (and optionally run knockout.py first for transparency):\n\n```bash\nfor n in $NAMES; do cwebp -quiet -q 84 -resize 1800 0 \"$WORK/still_$n.png\" -o \"$ASSETS/$n.webp\"; done\n```\n\nReview the stills for cohesion before continuing. Re-roll any off-style one (optionally\nadd `--image \"$WORK/still_<good>.png\"` to lock style).\n\n## 2. Dive-in clips (Step 4)\n\nPrompt files at `$WORK/dive_<name>.txt`. Start image = the solid-bg still PNG.\n\n```bash\ngen_dive() { # name                       ($VOPTS is unquoted on purpose — word-split flags)\n  higgsfield generate create \"$VMODEL\" --prompt \"$(cat \"$WORK/dive_$1.txt\")\" \\\n    --start-image \"$WORK/still_$1.png\" \\\n    $VOPTS --aspect_ratio 16:9 --duration \"$DIVE_DUR\" \\\n    --wait --wait-timeout 20m --json > \"$WORK/dive_$1.json\" 2> \"$WORK/dive_$1.err\"\n  url=$(jq -r '.[0].result_url // empty' \"$WORK/dive_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/dive_$1.mp4\" && echo \"dive $1 ok\" || echo \"dive $1 FAIL\"\n}\nfor n in $NAMES; do gen_dive \"$n\" & done ; wait\n```\n\nRe-roll individual failures (503 / credit race are transient):\n`gen_dive shop`  (just that one).\n\n## 3. Extract boundary frames — the seam handoff (Step 5)\n\nFor each adjacent pair, the connector's start = dive_i's LAST frame, end = dive_{i+1}'s\nFIRST frame — extracted from the **rendered videos**, never the stills.\n\n```bash\nset -- $NAMES\nprev=\"\"\nfor n in \"$@\"; do\n  ffmpeg -v error -ss 0 -i \"$WORK/dive_$n.mp4\" -frames:v 1 -q:v 2 \"$WORK/first_$n.png\"      # establishing\n  ffmpeg -v error -sseof -0.15 -i \"$WORK/dive_$n.mp4\" -frames:v 1 -q:v 2 \"$WORK/last_$n.png\" # interior\ndone\n```\n\n## 4. Connector clips (Step 5)\n\nPrompt files at `$WORK/conn_<i>.txt` (i = 1..N-1). Iterate adjacent pairs:\n\n```bash\ngen_conn() { # i startPng endPng          (end-image required → seedance/kling3_0 only)\n  higgsfield generate create \"$VMODEL\" --prompt \"$(cat \"$WORK/conn_$1.txt\")\" \\\n    --start-image \"$2\" --end-image \"$3\" \\\n    $VOPTS --aspect_ratio 16:9 --duration \"$CONN_DUR\" \\\n    --wait --wait-timeout 20m --json > \"$WORK/conn_$1.json\" 2> \"$WORK/conn_$1.err\"\n  url=$(jq -r '.[0].result_url // empty' \"$WORK/conn_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/conn_$1.mp4\" && echo \"conn $1 ok\" || echo \"conn $1 FAIL\"\n}\nset -- $NAMES ; i=0 ; prev=\"\"\nfor n in \"$@\"; do\n  if [ -n \"$prev\" ]; then i=$((i+1)); gen_conn \"$i\" \"$WORK/last_$prev.png\" \"$WORK/first_$n.png\" & fi\n  prev=\"$n\"\ndone ; wait\n```\n\n## 5. Encode everything for scrubbing (Step 6)\n\nNative resolution (1080p from seedance std; kling3_0 std returned **720p** in testing —\nnever upscale, encode what ffprobe reports), crf 20, GOP 8, light sharpen, no audio,\nfaststart. Same for dives + connectors.\n\n```bash\nenc() { ffmpeg -v error -y -i \"$1\" -an -vf \"unsharp=5:5:0.8:5:5:0.0\" \\\n  -c:v libx264 -preset slow -crf 20 -pix_fmt yuv420p \\\n  -g 8 -keyint_min 8 -sc_threshold 0 -movflags +faststart \"$2\"; echo \"enc $2 $(du -h \"$2\"|cut -f1)\"; }\n\nfor n in $NAMES; do enc \"$WORK/dive_$n.mp4\" \"$ASSETS/vid/$n.mp4\"; done\ni=0; for f in \"$WORK\"/conn_*.mp4; do i=$((i+1)); enc \"$f\" \"$ASSETS/vid/conn$i.mp4\"; done\n```\n\nNow the engine config's `sections[k].clip = assets/vid/<name>.mp4` and\n`connectors = [assets/vid/conn1.mp4, …]` (length N-1, in order).\n\n## 6. Centre-crop mobile encodes — FALLBACK ONLY, not the mobile version\n\n**The mobile version is the native 9:16 portrait chain (§6b).** This section's crop\nencodes exist for one case: the user opted into mobile but credits can't cover the\nportrait chain — and shipping them must be called out and approved, never silent\n(portrait phones will see the landscape film's centre ~26%). The encode mechanics\nmatter either way: scrubbing sets `currentTime` every frame, and a phone decoder's\n**seek cost scales with how many frames it must decode from the nearest keyframe** — so\na 1080p `-g 8` master that scrubs fine on a laptop stutters on a phone. A **smaller\nframe + tighter GOP** fixes that (and halves the bytes on cellular). The crop `-m.mp4`\nsibling per clip:\n\n```bash\n# 720p, GOP 4 (twice the keyframes = ~half the seek-decode work), crf 23, same sharpen/faststart.\nencm() { ffmpeg -v error -y -i \"$1\" -an -vf \"scale=-2:720,unsharp=5:5:0.6:5:5:0.0\" \\\n  -c:v libx264 -preset slow -crf 23 -pix_fmt yuv420p \\\n  -g 4 -keyint_min 4 -sc_threshold 0 -movflags +faststart \"$2\"; echo \"encm $2 $(du -h \"$2\"|cut -f1)\"; }\n\nfor n in $NAMES; do encm \"$WORK/dive_$n.mp4\" \"$ASSETS/vid/$n-m.mp4\"; done\ni=0; for f in \"$WORK\"/conn_*.mp4; do i=$((i+1)); encm \"$f\" \"$ASSETS/vid/conn$i-m.mp4\"; done\n```\n\nWire the variants in the engine config — the engine serves them automatically on phones,\nfalling back to the desktop `clip` when a mobile one is absent:\n\n```js\nsections[k].clipMobile = 'assets/vid/<name>-m.mp4';\nconnectorsMobile = ['assets/vid/conn1-m.mp4', …];   // length N-1, in order\n```\n\nIf phone scrubbing still stutters, tighten the GOP further (`-g 2`, or `-g 1` for all-intra\n= instant seeks at the cost of larger files); if cellular weight is the bigger worry, raise\n`crf` (24–26) or drop to `scale=-2:600`. If the master is already 720p (e.g. kling3_0 std),\nthe mobile encode still pays off — the tighter GOP is what makes phone seeks cheap. All-mobile encodes stay 16:9 — the engine\ncentre-crops them; see the portrait note in SKILL Step 8 / prompts.md.\n\n## 6b. Native 9:16 portrait chain — THE mobile version (Step 1.6 opt-in)\n\nWhen the user opts into mobile, this is what they get: a **parallel 9:16 chain** rendered\nnatively for phones and shipped as the mobile variants — never the §6 crops (those are the\nno-credits stopgap). Same seam laws as the main chain — the portrait chain frame-locks\nagainst its own rendered frames, never the landscape ones. Budget ~2N-1 video gens +\nre-rolls (interiors trip the NSFW filter in portrait too); state the credit cost at the\nStep 1.6 interview.\n\n1. **Portrait start canvases.** Don't hand the video model a 3:2 still and hope: composite\n   each scene onto a 1080×1920 canvas in the page bg colour (island at ~94% width, visual\n   centre at ~45% height). The render then opens exactly on what the portrait poster shows.\n   For knocked-out stills, composite the RGBA over the bg colour first.\n2. **Dives/legs**: same prompt templates with a portrait clause up front (\"Vertical\n   portrait composition, the diorama centered with generous [bg] space above and below\"),\n   `--aspect_ratio 9:16`, same model/params as the main chain. Review each last frame\n   before chaining, as ever.\n3. **Connectors**: extract first/last frames **from the 9:16 renders** and generate 9:16\n   connectors between them. A native 9:16 scene mixed into cropped-16:9 neighbours pops at\n   both seams — the portrait chain must be complete, not partial.\n4. **Encode** with the §6 settings but portrait-oriented scale: `scale=720:-2` (720 wide),\n   `-g 4`, crf 23 → these ARE the `-m.mp4` mobile files (and they replace any §6 crop\n   stopgaps that shipped earlier).\n5. **Posters**: extract each 9:16 dive's first frame → webp → wire as the section's\n   `stillMobile` so the poster matches the portrait video's frame 0 (no landscape→portrait\n   flash when the clip paints). Engine support: `sections[k].stillMobile`.\n\n## 7. Monid backend — Seedance 2.0 pay-per-clip (the DEFAULT; qualified 2026-07-25)\n\n`bytedance /v1/video/seedance-2.0` via Monid is the roster's `seedance_2_0` billed\nper clip in USD, and the **default** chain backend (SKILL Step 4 → Monid backend;\nboth probes passed) — use these functions in the §2/§4 loops unless the build fell\nback to Higgsfield credits. Same chain laws as everywhere else — only the I/O\ndiffers: **frames ride Monid's free `sfs` file\nsystem** (inline base64 is rejected), **`ratio` must be explicit** (the adaptive\ndefault follows the input image's aspect), and runs are fire-and-poll. Token-priced\n`w×h×24×sec/1024` at $7/1M (480p/720p), $7.7/1M (1080p) — measured: 1080p ≈ $2.99\ndive / $1.87 connector; 720p ≈ $1.21 / $0.76; 480p previz ≈ $0.28 / $0.35.\n\n```bash\n# helper: upload a local frame to sfs, print a signed public URL for it ($0).\n# NB: /cat and /ls take the SAME relative path you gave /put — not the\n# \"home/...\"-prefixed path /put echoes back (that one 404s).\nmonid_frame_url() { # localPng remoteName  (JPEG-compresses on the way up)\n  jpg=\"$WORK/sfs_$2.jpg\"\n  ffmpeg -v error -y -i \"$1\" -vf \"scale='min(1536,iw)':-2\" -q:v 2 \"$jpg\"\n  size=$(stat -f%z \"$jpg\")\n  up=$(NO_COLOR=1 monid run -p sfs -e /put \\\n    -i \"{\\\"path\\\":\\\"chain/$2.jpg\\\",\\\"sizeBytes\\\":$size,\\\"ttl\\\":\\\"1h\\\"}\" -w 60 -j \\\n    | jq -r '.output.uploadUrl')\n  curl -fsS -T \"$jpg\" \"$up\" > /dev/null\n  NO_COLOR=1 monid run -p sfs -e /cat -i \"{\\\"path\\\":\\\"chain/$2.jpg\\\",\\\"ttl\\\":\\\"1d\\\"}\" \\\n    -w 60 -j | jq -r '.output.url'\n}\n\n# fire-and-poll (the CLI's -w caps at 120s and seedance can exceed it)\nmonid_wait() { # runId outJson\n  while :; do\n    NO_COLOR=1 monid runs get -r \"$1\" -j > \"$2\" 2>/dev/null\n    case \"$(jq -r '.status // empty' \"$2\")\" in\n      COMPLETED|FAILED|BLOCKED|STOPPED|TIME_OUT) break ;;\n    esac\n    sleep 8\n  done\n}\n\ngen_dive_monid() { # name   (VRES=1080p|720p|480p; DIVE_DUR as usual)\n  furl=$(monid_frame_url \"$WORK/still_$1.png\" \"still_$1\")\n  jq -n --arg p \"$(cat \"$WORK/dive_$1.txt\")\" --arg u \"$furl\" --arg r \"$VRES\" \\\n    '{content:[{type:\"text\",text:$p},\n               {type:\"image_url\",image_url:{url:$u},role:\"first_frame\"}],\n      resolution:$r, duration:'\"$DIVE_DUR\"', ratio:\"16:9\", generate_audio:false}' \\\n    > \"$WORK/dive_$1.body.json\"\n  rid=$(NO_COLOR=1 monid run -p bytedance -e /v1/video/seedance-2.0 \\\n    -f \"$WORK/dive_$1.body.json\" -j | jq -r '.runId')\n  monid_wait \"$rid\" \"$WORK/dive_$1.json\"\n  url=$(jq -r '.output.content.video_url // empty' \"$WORK/dive_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/dive_$1.mp4\" \\\n    && echo \"dive $1 ok (\\$$(jq -r '.cost.value' \"$WORK/dive_$1.json\"))\" \\\n    || echo \"dive $1 FAIL ($(jq -r '.status' \"$WORK/dive_$1.json\"))\"\n}\n\ngen_conn_monid() { # i startPng endPng\n  su=$(monid_frame_url \"$2\" \"conn$1_start\"); eu=$(monid_frame_url \"$3\" \"conn$1_end\")\n  jq -n --arg p \"$(cat \"$WORK/conn_$1.txt\")\" --arg s \"$su\" --arg e \"$eu\" --arg r \"$VRES\" \\\n    '{content:[{type:\"text\",text:$p},\n               {type:\"image_url\",image_url:{url:$s},role:\"first_frame\"},\n               {type:\"image_url\",image_url:{url:$e},role:\"last_frame\"}],\n      resolution:$r, duration:'\"$CONN_DUR\"', ratio:\"16:9\", generate_audio:false}' \\\n    > \"$WORK/conn_$1.body.json\"\n  rid=$(NO_COLOR=1 monid run -p bytedance -e /v1/video/seedance-2.0 \\\n    -f \"$WORK/conn_$1.body.json\" -j | jq -r '.runId')\n  monid_wait \"$rid\" \"$WORK/conn_$1.json\"\n  url=$(jq -r '.output.content.video_url // empty' \"$WORK/conn_$1.json\")\n  [ -n \"$url\" ] && curl -fsSL \"$url\" -o \"$WORK/conn_$1.mp4\" \\\n    && echo \"conn $1 ok (\\$$(jq -r '.cost.value' \"$WORK/conn_$1.json\"))\" \\\n    || echo \"conn $1 FAIL ($(jq -r '.status' \"$WORK/conn_$1.json\"))\"\n}\n```\n\nDefault usage in §2/§4: same loops, `gen_dive_monid`/`gen_conn_monid` in place of\nthe Higgsfield `gen_dive`/`gen_conn` (mobile chain: `ratio:\"9:16\"` and the §6b\nportrait canvases). Previz: same functions with `VRES=480p`.\nResult URLs expire (~24–48 h) — the functions download immediately. Read the billed\n`cost.value` per clip (echoed in the ok-line) and `monid balance` between phases; a\n`BLOCKED` status is a workspace budget/run cap — terminal, surface it to the user.\nFrame extraction, encoding, QA: identical to the Higgsfield path.\n\n**Qualification harness for future/changed endpoints** (each probe = one cheap 480p\nclip): (1) prompt + first_frame from a real still → downloaded video's frame 0 must\nmatch the still (eyeball + PSNR ≳ 30 dB) and `cost.value` must match the advertised\ncell; (2) add a last_frame from a *different* still → the final frame must land on\nthat composition (Seedance-style near-miss ok — the crossfade covers it). Known\nfailure to watch for (it's why the harness exists): `minimax /v1/video_generation`\nstill silently drops the image when `prompt` is present — image-only output proves\nnothing about steerability.\n\n## Notes\n\n- `.[0].result_url` is the field on the `--wait --json` job object. `.min_result_url` is\n  a lower-res preview if you ever want it.\n- **NSFW fallback across models**: if one clip keeps getting flagged on seedance after\n  re-rolls + prompt scrubbing, regenerate just that clip on `kling3_0` with the SAME\n  start/end frames: `VMODEL=kling3_0; VOPTS=\"--mode std --sound off\"; gen_conn 3 …` —\n  then restore your chain model. See SKILL Gotchas for the trade-off.\n- **Previz on the cheap**: run the whole chain once with `VMODEL=seedance_2_0_mini`\n  (frame-locking intact, ~720p) to validate the journey and seams before spending\n  full-model credits — because it's still seamless, the previz translates directly to the\n  final render. Don't reach for reference-only models here: without `--start/--end-image`\n  they can't hold a seam, so their output can't be chained (Step 4 rule).\n- If a whole batch stalls, check `higgsfield workspace list` for credits and\n  `$WORK/*.err` for the reason.\n- Concurrency: launching ~5–6 gens at once is fine; much more can trigger transient\n  credit/race errors — stagger or re-roll.\n\n<!-- File: skills/scroll-world/references/prompts.md (Tokens: ~2575 | Category: anthropic-skill) -->\n# Prompt templates & intake\n\nEverything here is fill-in-the-slots. Keep the **style preamble** byte-for-byte identical\nacross all scene stills — that identical text is what makes the world feel like one place.\n\n## Intake checklist (Step 1)\n\nCollect and write down:\n\n- `SUBJECT` — the business + one-line pitch.\n- `BRAND_NAME` — display name.\n- `PALETTE` — 4–6 named hexes, e.g. `taro #9B7EBD, cream #F5EDE0, caramel #C88A5A, matcha #8FB98A, plum #3A2E48`. Pick ONE as the scene **background** colour (usually the lightest) and one as the primary **accent**.\n- `TONE` — a word or two (cozy/premium, playful, industrial…).\n- `STYLE` — the art direction (default below).\n- `SECTIONS[]` — ordered list; for each: `id`, `label`, `subject` (what's in the diorama), `eyebrow`, `title`, `body` (≤ 1 sentence), `tags[]` (0–3). Last section = hero product + CTA.\n- `CAMERA` — fly-through (arch B: dives + aerial hops — the flagship-demo look) |\n  walkthrough (arch A: one continuous forward flight) | locked-iso (arch A + the\n  locked-iso clause below: one fixed angle for the whole film). **Always asked**\n  (SKILL Step 1.4), presented by feel with a one-line trade-off each.\n- `MOBILE` — yes/no. **Always asked** (SKILL Step 1.6), presented to the user\n  with the ~2× credit cost stated. Yes = the **native 9:16 portrait chain**\n  (pipeline §6b): portrait renders of every dive/connector +\n  `clipMobile`/`connectorsMobile`/`stillMobile` wiring + the full mobile QA. The\n  §6 crop encodes are a no-credits stopgap only.\n- `VIDEO_TIER` — default is `seedance_2_0` **via Monid** (pay-per-clip; previz =\n  same endpoint at 480p, final at 1080p). Higgsfield-credit alternates: draft\n  (`seedance_2_0_mini`) | standard (`seedance_2_0`) | alternate (`kling3_0`).\n  Chosen by cost at SKILL Step 1.7, with the estimated total stated before\n  anything renders.\n- `STILLS_SOURCE` — higgsfield (`gpt_image_2`, spends credits) | codex\n  (`image_gen`, subscription-billed; only offer when the Codex CLI is present).\n\n## Style preamble (default: clay diorama)\n\nReuse verbatim in every scene prompt. Swap the bracketed bits for the brand's palette/bg.\n\n```\nIsometric low-poly 3D diorama floating as a small rounded island on a plain solid\n[BG_HEX] background with a soft contact shadow beneath it. Soft matte clay 3D render,\nrounded toy-model shapes, gentle warm studio lighting, soft long shadows, tilt-shift\nminiature look. Cohesive color palette of [PALETTE]. Highly detailed, centered\ncomposition, absolutely no text, no letters, no numbers, no logos.\n```\n\nAlternate directions (swap the first two sentences, keep the palette/no-text tail):\n- **Flat papercraft:** \"Isometric layered paper-craft diorama, matte cardstock, clean die-cut edges, subtle drop shadows between layers.\"\n- **Glossy toy:** \"Isometric glossy vinyl-toy diorama, smooth plastic shading, soft rim light, collectible figurine look.\"\n- **Claymation:** \"Isometric stop-motion clay set, visible thumbprints, handmade plasticine texture, soft studio softbox light.\"\n- **Neon night:** \"Isometric miniature at night, warm interior glow and neon signage, moody rim light, wet reflective ground.\"\n- **Photoreal architectural** (real estate, hospitality, premium/luxury): \"Ultra-photorealistic architectural photography of a single cohesive [subject], cinematic wide-angle, warm golden-hour light, natural materials, restrained designer furnishings, a breathtaking view, editorial magazine quality (Architectural Digest), shallow depth of field, no people.\" For photoreal, drop the floating-island framing and the knockout (Step 3) — the scenes are **full-bleed** (a dark page background reads premium), the \"dive\" glides *through doorways/glass* rather than opening a roof, and cohesion comes entirely from the identical preamble (do NOT pass an `--image` reference — it clones the same room). Interiors trip Seedance's NSFW filter often; see SKILL Gotchas.\n\n## Scene still prompt (Step 2)\n\n```\n[STYLE PREAMBLE]\nSubject: [SECTION.subject — describe the miniature scene: the building/space, a few\ncharacters doing the work, the props that signal this stage of the business].\n```\n\nTips:\n- Name concrete props (they anchor the scene): tanks, cauldrons, conveyor, crates, awning, string lights, benches, scooters, map pins.\n- For the final \"hero product\" section, drop the diorama-island framing and prompt a\n  single oversized product centerpiece floating on the same background with a few small\n  orbiting props.\n- **Compose for the centre.** The page renders every clip `object-fit:cover`. Keep the\n  focal subject horizontally centred with a little headroom, and don't park anything\n  essential at the far left/right edges. Mobile ships its own native 9:16 chain\n  (pipeline §6b), so this is not about surviving a crop — but a centred composition makes\n  the portrait renders open cleanly from the same still, and it keeps the dive's focal\n  point where the camera actually flies.\n- Aspect `3:2`, `--resolution 2k --quality high`.\n\n## Leg prompt — architecture A, continuous forward take (Step 4)\n\n`--start-image = previous leg's ACTUAL last frame` (leg 0: the first scene's still).\n**No `--end-image`.** The bolded clauses are the motion-handoff contract — keep them\nverbatim; the mid-leg move is where the expression goes.\n\n```\nSingle continuous cinematic camera move, no cuts. **Continue the same slow, steady\nforward glide.** [MID-LEG MOVE — optional, from the library below.] The camera moves\ninto [SCENE i] toward [FOCAL POINT]. **In the final second, settle back into a slow,\nsteady forward glide toward [the doorway / opening / direction of the next scene].**\n[STYLE tail + PALETTE]. Smooth, graceful, slow motion, subtle parallax. No text, no captions.\n```\n\n### Mid-leg move library (pick by concept; omit for a plain glide)\n\nReversals are safe *inside* a leg (it's one continuous render) — only a seam may never\nreverse. That's why \"ease back out\" is fine mid-leg.\n\n**Locked-iso clause** (`CAMERA` = locked isometric glide — SKILL Step 1.4/Step 4):\nskip this library entirely and put this clause, verbatim, in the mid-leg slot of\nEVERY leg:\n\n```\nThe camera keeps exactly the same high isometric angle throughout — no rotation, no\norbit, no tilt. It only travels straight and level, the world sliding past beneath\nthe same view.\n```\n\nKeep the handoff-contract clauses around it unchanged. When checking each leg's last\nframe, also check the ANGLE hasn't drifted (Seedance rotates slightly on long legs) —\nre-roll the leg if the view has turned.\n\n- **Half-orbit** (product, luxury): \"sweeping in a slow half-orbit around [the hero\n  object], keeping it centered, then continuing past it\"\n- **Crane-up reveal** (scale, atriums, campuses): \"rising smoothly as the full scale of\n  [the space] reveals below\"\n- **Low lateral track** (production lines, counters, shelves): \"tracking low and level\n  alongside [the line], foreground objects sliding past in parallax\"\n- **Push-in + ease back** (craft, detail): \"pushing in close to [the craft moment] until\n  it nearly fills the frame, then easing gently back out\"\n- **Rise-and-swoop** (travel, outdoors): \"climbing in a gentle arc over [the terrain],\n  then swooping down toward [the next focal point]\"\n\nAfter rendering each leg, **check its last frame** before generating the next: it should\nread as a frame from a calm forward glide (no motion blur sideways, no half-finished\norbit). If it doesn't, re-roll this leg — a bad handoff frame poisons every leg after it.\n\n## Dive-in clip prompt (Step 4)\n\n`--start-image = the scene still` (solid-bg version).\n\n```\nSingle continuous cinematic camera move, no cuts. Begin high and far, looking down at the\nwhole [SECTION.subject] from outside like a tiny model. The camera slowly glides forward\nand descends toward it, sweeping in toward [FOCAL POINT — the counter/the cauldrons/the\npeople], as if flying inside. As the camera pushes in, the roof and upper structure\ngently lift and open away to reveal the warm interior. [STYLE tail: soft matte clay\ndiorama, tilt-shift miniature, warm light, [PALETTE]]. Smooth, graceful, slow motion,\nsubtle parallax. No text, no captions.\n```\n\nFor scenes with no building to open (a field, a plaza, a road), replace the roof clause\nwith \"the camera flies low across [the scene] toward [focal point].\"\n\nParams by chain model (SKILL Step 4 table): seedance —\n`--mode std --resolution 1080p --aspect_ratio 16:9 --duration 8`, no audio flag;\nkling3_0 — `--mode std --sound off --aspect_ratio 16:9 --duration 10` (no `--resolution`\nparam). Same for architecture-A legs.\n\n## Connector clip prompt (Step 5)\n\n`--start-image = dive_i LAST frame` (extracted), `--end-image = dive_{i+1} FIRST frame`\n(extracted). Both from the RENDERED videos, not the stills.\n\n```\nSingle continuous cinematic camera move, no cuts. The camera smoothly pulls up and back\nout of [SCENE i], rising into the sky, then glides forward across the connected miniature\nworld and arrives above [SCENE i+1], beginning to descend toward it. One connected\nminiature clay world, seamless flowing aerial transition. [STYLE tail + PALETTE]. Smooth\ngraceful slow motion. No text, no captions.\n```\n\nFor the last connector into a hero-product finale: \"…glides forward and the world\ndissolves toward a single giant [PRODUCT] floating in soft [BG] space, arriving in front\nof it.\"\n\nseedance: `--mode std --resolution 1080p --aspect_ratio 16:9 --duration 5`; kling3_0:\n`--mode std --sound off --aspect_ratio 16:9 --duration 5`. Connectors need `--end-image`\n→ use a roster model that accepts it (Step 4).\n\n## Copy per section (for the engine config)\n\n- `eyebrow` — 2–4 words, uppercase feel (a value-prop label).\n- `title` — 3–6 words, the beat's headline. First section = the site's hero line; last =\n  the payoff + it carries the CTA.\n- `body` — one sentence, plain-spoken, from the visitor's side.\n- `tags` — 0–3 short proof chips (e.g. \"Fresh-cooked\", \"30-min delivery\").\n\n<!-- File: skills/scroll-world/SKILL.md (Tokens: ~12861 | Category: anthropic-skill) -->\n---\nname: scroll-world\ndescription: >\n  Build an immersive scroll-scrubbed \"fly through the world\" landing page for any\n  industry or brand using Higgsfield. As the visitor scrolls, a pre-rendered camera\n  flies from outside each scene into its interior, then flows on to the next scene\n  with NO cuts — one continuous connected flight (Emons-style isometric diorama world,\n  or any art direction you pick). The skill interviews the user for the topic, the\n  story beats/sections, and brand kit, then generates cohesive scenes + seamless camera\n  clips with Higgsfield and wires a portable, framework-agnostic scroll-scrub engine.\n  The video chain renders through Monid by default (Seedance 2.0, pay-per-clip\n  USD — capability re-checked each build, see Step 4) with Higgsfield credits as\n  the fallback biller. Use when the user wants a \"3D world\" /\n  \"browse-through-the-industry\" hero, a scroll cinematic, a diorama landing, or to\n  turn a business into a scrollable world.\nallowed-tools: Bash, Read, Write, Edit, AskUserQuestion, Skill\n---\n\n# scroll-world\n\nProduces a landing page where **scroll drives a camera**: it dives from outside a scene\ninto its interior, then flies out and into the next scene, continuously, with no visible\ncuts. The visuals are AI-generated — stills via Higgsfield (or Codex), the video chain\nvia **Monid by default** (pay-per-clip Seedance 2.0; Higgsfield credits as fallback) —\nand the page just scrubs pre-rendered video by scroll position. This is the same technique behind Apple's scroll-through product\npages — the camera genuinely moves, scroll only drives time.\n\n**What you generate:** N scene stills → N \"dive-in\" camera clips → N-1 \"connector\" clips\nthat join consecutive scenes seamlessly → a portable scrub engine that plays the whole\nchain as one flight.\n\n**The one rule that makes or breaks it:** seams must be *frame-identical*. Read\n[The seamless chain](#step-5--the-seamless-chain-the-critical-part) before generating any\nconnector. Getting this wrong is the single most common failure and produces a visible\n\"pop\" between scenes.\n\nDo not assume a frontend framework. The scrub engine in `references/scrub-engine.js` is\nself-contained vanilla JS (it builds its own DOM + injects its own CSS into a container\nyou give it), so it drops into plain HTML, Next.js, Vue, a Python-served page, anything.\nThe value of this skill is the Higgsfield pipeline, the prompts, and the seam method —\nnot the framework.\n\n---\n\n## Step 0 — Bootstrap\n\n1. **Monid CLI — the default video-chain backend.** Check `monid --version`,\n   `monid keys list` (active key) and `monid balance` — the chain is billed per\n   clip in USD (Step 1.7 has the numbers; a 1080p N=6 chain ≈ $27). If the CLI is\n   missing or the balance can't cover the chain, say so and fall back to\n   rendering the chain on Higgsfield credits instead — same model, same\n   pipeline, different biller (Step 4 → Monid backend).\n2. **Higgsfield CLI — still required even on the Monid path**: it renders the\n   scene stills (`gpt_image_2`) and is the home of the `kling3_0` NSFW fallback\n   and the fallback chain. If `higgsfield` is not on `$PATH`, install per the\n   `higgsfield-generate` skill. If `higgsfield workspace list` fails auth, ask the user\n   to run `higgsfield auth login` (interactive OAuth — you cannot run it) and, if needed,\n   `higgsfield workspace set <id>`. Confirm credits cover the stills (~N image\n   gens) — plus `(2N-1)` video gens if the chain falls back here.\n3. **ffmpeg / ffprobe** on `$PATH` (frame extraction + encoding).\n4. **An image tool** for background knockout if you want floating scenes: PIL\n   (`python3 -c \"import PIL\"`), or `cwebp`/`sips`. Optional — see Step 3.\n5. **(Optional) Codex CLI** — if `codex` is on `$PATH` (≥ 0.125) and\n   `codex login status` reports a ChatGPT login, the scene stills can be generated\n   through Codex's built-in `image_gen` (the same gpt-image-2 model) billed to the\n   user's ChatGPT subscription instead of Higgsfield credits — offer it at\n   Step 1.7, command in Step 2. Absence just removes the option.\n6. Caveats: macOS ships **bash 3.2** (no `declare -A`); don't use associative arrays in\n   scripts. Higgsfield generations take **3–8 min each** — always run them detached\n   (background) and poll, never a foreground blocking call. Reference-by-job-UUID is\n   rejected by media flags — pass **local file paths** to `--image/--start-image/--end-image`.\n   Video models differ in accepted params (e.g. Kling has no `--resolution`) and in whether\n   they support start/end-image conditioning at all — before batching, confirm the chosen\n   model's schema with `higgsfield model get <job_type>` and see the Step 4 model table.\n\n---\n\n## Step 1 — Interview the user\n\nThe **subject is the user's to state — ask it as an open question in plain prose**, never a\nfabricated multiple-choice. A made-up list of industries biases them and reads as you\ndeciding their business for them; let them answer in their own words (their real business,\na client's, or any idea). Reserve structured multiple-choice (`AskUserQuestion` in Claude\nCode; a plain either/or question elsewhere) for the genuinely\nenumerable, lower-stakes choices below — art direction, camera style, and brand-kit\napproach — and even\nthere, signal they can go their own way (\"Other\"). Ask only what you can't sensibly\ndefault. Cover:\n\n1. **Subject** (ask openly, not multiple-choice) — \"What should this world be about? Your\n   business, a client's, or any idea — a word or a sentence is fine.\" Capture the\n   industry/product + a one-line pitch (e.g. \"a bubble tea company, from leaf to last\n   sip\"), and a brand name if they have one; otherwise you'll propose one below.\n2. **Brand kit** — offer three paths, pick one:\n   - Import from a URL: `higgsfield marketing-studio brand-kits fetch --url <site> --wait`\n     (pulls name, colours, tone). Then read it back with `brand-kits list --json`.\n   - The user hands you palette + name + tone directly.\n   - You propose a palette + name and let them approve.\n   Capture **4–6 named hex values**, a display name, and a tone word or two.\n3. **Art direction** — default is \"soft matte low-poly **clay diorama**, isometric,\n   tilt-shift miniature, warm light.\" Offer alternatives (flat papercraft, glossy toy,\n   claymation, neon night). Whatever is chosen becomes the shared **style preamble**\n   reused verbatim in every scene prompt (this is what makes the world cohesive).\n4. **Camera style — ALWAYS ask; it's the film's personality, not a technical\n   detail.** Ask by feel (`AskUserQuestion` in Claude Code; a plain question\n   elsewhere) and record the answer as `CAMERA`. The options map to the Step 4\n   architectures — Step 4 then *implements* the choice, it never re-decides it:\n   - **\"Fly through the world\"** — the camera dives into each scene, pulls up and\n     out, and hops across the miniature world to the next; angles change\n     constantly, big expressive aerial moves (this is the flagship-demo look).\n     → Architecture B. Recommend as the default for diorama/miniature art\n     directions.\n   - **\"One continuous walkthrough\"** — a single forward flight that glides\n     through each scene straight into the next, never pulling back; expressive\n     but always-forward moves per scene (camera grammar table). → Architecture A.\n     Recommend as the default for grounded/photoreal art directions.\n   - **\"Locked isometric glide\"** — the camera keeps one fixed angle for the whole\n     film, Emons-style; the world slides past/toward it, no rotation, no reveals.\n     → Architecture A + the locked-iso clause in every leg prompt (prompts.md).\n   State the trade-off in one line each (B reverses direction at seams — charming\n   in miniature, jarring in realism; locked-iso is the calmest and cheapest to\n   re-roll; walkthrough sits between).\n5. **The journey (sections)** — the ordered scenes the camera flies through. Propose a\n   set derived from the subject's own value chain and let the user edit. 5–7 works well.\n   Boba example: farms → pearl kitchen → flagship shop → delivery → community plaza →\n   the hero product. Each section needs: a short subject description (what's IN the\n   diorama), an eyebrow, a headline, one line of body, and 0–3 tag pills. The last\n   section is usually the hero product + the CTA.\n6. **Mobile version — ALWAYS ask this; never silently generate both.** Ask as a\n   two-option choice (`AskUserQuestion` in Claude Code; a plain question elsewhere):\n   *\"Want a mobile-optimized version too? The mobile version is a second camera chain\n   rendered natively in **9:16 portrait** — composed for phones, not a crop of the\n   landscape film — which roughly doubles the Higgsfield credit spend (state the\n   estimated number).\"*\n   Options: \"Desktop only\" / \"Desktop + mobile (native 9:16 — ~2× credits)\". The\n   credit cost must be stated to the user, not just implied.\n   What the answer gates:\n   - **Yes** → render the parallel 9:16 portrait chain and ship it as the mobile variants\n     (Step 6 / pipeline.md §6b): portrait start canvases → 9:16 dives + connectors\n     frame-locked against their own renders → 720-wide `-m.mp4` encodes → `stillMobile`\n     portrait posters. Wire `clipMobile`/`connectorsMobile`/`stillMobile` (Step 7); run\n     the full mobile QA (Step 8). Budget ~2N-1 extra video gens + NSFW re-rolls.\n     **Never ship the centre-crop as the mobile version by default** — if credits can't\n     cover the portrait chain, say so and offer the crop encodes (pipeline.md §6) as an\n     explicitly-labelled stopgap the user must approve.\n   - **No** → skip the mobile encodes and wiring entirely. The engine's phone hardening\n     (seek-coalescing, iOS priming, safe-area CSS) is always on regardless — that's not\n     a \"mobile version,\" it's just the page not breaking when a phone visits — so a\n     desktop-only build still degrades gracefully.\n\n7. **Budget — engines shown by cost, decided before anything renders.** Present the\n   render tiers (`AskUserQuestion`), then compute and state the estimated total for\n   the user's N scenes — `N stills + (2N−1) videos [videos ×2 if mobile] + ~15%\n   re-roll headroom` — and get a go before generating.\n   - **Video tier** (roster only — every option frame-locks seams, Step 4):\n\n     | Tier | Model | Rough cost |\n     |---|---|---|\n     | Draft / previz | `seedance_2_0_mini` (720p) | ~¼ of Standard |\n     | Standard (default) | `seedance_2_0` (1080p) | baseline |\n     | Alternate | `kling3_0` (720p native) | ≈ Standard; different look + content filter |\n\n     Draft doubles as the previz path: run the whole chain cheap, approve the\n     journey, re-render final legs on Standard (pipeline.md Notes) — suggest it\n     unprompted when the balance reads tight.\n   - **Backend — Monid is the DEFAULT biller for the chain** (Step 0.1; wiring\n     in pipeline.md → Monid backend). Same Seedance 2.0, per-clip USD instead of\n     credits. Token-priced `width × height × 24 × seconds / 1024` at $7/1M\n     (480p/720p) or $7.7/1M (1080p) — measured: 1080p 8s dive ≈ $2.99, 5s\n     connector ≈ $1.87; 720p ≈ $1.21 / $0.76; 480p ≈ $0.28 / $0.35. An N=6\n     desktop chain ≈ $27 at 1080p / ~$11 at 720p vs Higgsfield Plus-monthly ≈\n     $32 / $16 — ~15% cheaper per clip, parity with Plus-annual; structurally\n     better for one-off builds (pay-per-use, no monthly expiry). On Monid the\n     Draft/previz tier is simply the **same endpoint at 480p** — no model swap,\n     so previz→final stays one-model by construction. State `monid balance`\n     against the estimate; **fall back to Higgsfield credits** (per-model tiers\n     above) when the user prefers their subscription, the balance is short, or\n     the model must be `kling3_0` (Higgsfield-only). It's the same underlying\n     model (`seedance_2_0` ≙ Monid's `seedance-2.0`), so finishing a stranded\n     chain on the other biller is a reasonable rescue — but the serving stacks\n     differ and cross-provider seam character is **untested**: eyeball the first\n     rescued seam before rendering the rest, same as any model swap.\n   - **Stills source** (only offer if the Codex CLI is present, Step 0.5):\n     Higgsfield `gpt_image_2` (spends credits) vs **Codex `image_gen`** — the same\n     gpt-image-2 model billed to the ChatGPT subscription (zero credits; counts\n     toward Codex usage limits; 1536×1024 output — exactly 3:2, slightly under\n     Higgsfield's 2k). Stills are plain PNGs handed to `--start-image`, so the\n     video chain is indifferent to their source. Command in Step 2. **One source\n     for all N stills of a build** — the two render with slightly different\n     character (verified: Codex runs warmer/lighter), and mixing sources across\n     scenes reads as style drift, same reason the video chain uses one model.\n   - **Calibrate costs, don't guess.** The CLI exposes no pricing and plans differ.\n     Run ONE still and ONE video first, diff `higgsfield workspace list` before/\n     after, extrapolate to the full run, and warn the user whenever the estimate\n     exceeds ~70% of the balance. (Observed on a plus plan, 2026-07: Standard\n     video ≈ 40–55 credits, still ≈ 15.) A real `not_enough_credits` mid-run is\n     recoverable (finished clips survive; resume after top-up) but ugly — the\n     whole point of this step is that the user decides *before* the spend.\n\nIf the user names a video model outside the roster, honor it **only if it can\nframe-lock seams** (Step 4). This skill only ships seamless output, so a model that\ncan't frame-lock is declined with a one-line why, not substituted in — use a roster\nmodel instead.\n\nKeep the scroll mechanic fixed (continuous fly-through) — that's the point of the skill.\nSee `references/prompts.md` for the intake checklist and copy structure.\n\n---\n\n## Step 2 — Generate the scene stills\n\nOne image per section, **all sharing the same style preamble** for cohesion. Default\nmodel **`gpt_image_2`** (crisp, great at isometric illustration; returns a solid/white\nbackground which is perfect for floating diorama \"islands\"). Use `nano_banana_2` only if\nthe brief is character/cartoon-heavy (note: `nano_banana_2` is a CLI alias — it resolves\nto `nano_banana_pro`; it won't appear under that name in `higgsfield model list`).\n\nPrompt shape (full templates in `references/prompts.md`):\n\n```\n<STYLE PREAMBLE, identical every time>. On a plain solid <bg> background with a soft\ncontact shadow. <PALETTE hexes>. No text, no letters, no logos, centered, 3:2.\nSubject: <what is in THIS diorama>.\n```\n\n- Run all N concurrently, detached. Command per scene:\n  `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`\n- Result URL is `.[]0.result_url` in the `--wait --json` output. `curl` it down.\n- **Codex stills variant** (if chosen at Step 1.7 — subscription-billed, zero\n  credits): same prompt files, same byte-identical preamble, generated by Codex's\n  built-in `image_gen`:\n\n  ```bash\n  codex exec -C \"$WORK\" -s workspace-write --skip-git-repo-check \\\n    '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.' \\\n    < /dev/null\n  ```\n\n  Single-quote the `$imagegen` segment (the shell must not expand it); if editing\n  with reference images, the prompt goes BEFORE any `-i` flag (it's variadic).\n  ~1–3 min per image; run a few in parallel, not all N at once — and keep the\n  `< /dev/null`: parallel `codex exec` calls sharing a script's stdin hang\n  waiting for input (Gotchas). Output lands at\n  1536×1024 (3:2) — fine for `--start-image` and posters. Everything downstream\n  (cohesion review, knockout, dives) is unchanged.\n- A generation may fail transiently (HTTP 503) — re-roll that one individually; don't\n  restart the batch.\n- **Review the stills before continuing.** They must read as one cohesive world (same\n  angle, palette, light). If one is off-style, regenerate it, optionally passing an\n  approved scene as `--image` to lock style.\n\nSee `references/pipeline.md` for the exact batch script.\n\n---\n\n## Step 3 — (Optional) Float the scenes\n\nIf you want the dioramas to float over an atmospheric background instead of sitting in a\nsolid box, knock out the flat background to transparency with\n`references/knockout.py` (border-connected flood fill — preserves interior colour that\nmatches the bg, e.g. cream walls). Then encode to webp. If you'd rather keep it simple,\njust make the page background the same colour as the scene background and skip this.\n\nThese stills double as **video posters and lazy-load fallbacks**, so keep them.\n\n---\n\n## Step 4 — Camera architecture (implements the Step 1.4 choice)\n\nHow the camera moves *between* scenes is the single biggest quality lever. The user\nalready chose the style at the interview (`CAMERA`, Step 1.4): fly-through → **B**,\nwalkthrough → **A**, locked-iso → **A + the locked-iso leg clause** (prompts.md). If\nthe interview somehow skipped it, ask now — never silently pick for them. The two\nshapes, and the grammar that colors them:\n\n### Video model — pick ONE for the whole chain\n\n**This skill only ships seamless output**, so the only usable models are ones that can\nframe-lock a seam: every chained clip must accept `--start-image`, and connectors also\nneed `--end-image`. That capability — not preference — is the selection rule. Check any\nmodel with `higgsfield model get <job_type>` and **skip anything whose media inputs are\nreference-only** (no start/end image): it can only *condition* a generation, not\n*continue* a shot, so it physically can't hold a seam. Schemas below were confirmed\nagainst the CLI:\n\n| Model | start/end image | Notes |\n|---|---|---|\n| `seedance_2_0` (default) | ✓ / ✓ | Full chain (legs + connectors). `--mode std --resolution 1080p`. Its NSFW filter is the touchy one (see Gotchas). |\n| `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. |\n| `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. |\n\nThose three are the roster — all do both architectures. (`kling3_0_turbo` also frame-locks\nvia `--start-image`, but has no `--end-image`, so it's architecture-A-only and can't make\nconnectors; it also takes a different flag set — no `--mode`, has `--resolution` — so it\ndoesn't drop into the pipeline as-is. It's not in the default roster; only reach for it, and\nwire it by hand, if architecture A's sequential render time is a proven bottleneck and you've\nbenchmarked it as actually faster.)\n\nOne more architecture-A-only candidate, worth knowing because it is by far the cheapest\nprobe: **`minimax_hailuo`** (Hailuo-2.3, ~6 credits per 768p/6s clip vs 22–72 for the\nroster). Verified 2026-07: `--start-image` + prompt frame-locks (output frame 0 ≡ input,\nPSNR 33 dB) and a forward-glide prompt was obeyed, gently. Constraints: the 2.3 variant\nrejects `end_image` (no connectors → arch A only), output aspect follows the input image\n(hand it a 16:9 canvas, not a bare 3:2 still), motion runs subtler than seedance, and\ndon't pass `--resolution` (the CLI mis-types the enum; the 768 default works — 1080\nsupports 6s only). One clip ≠ a chain: qualify a leg-to-leg handoff before betting a\nfull build on it.\n\nRules:\n- **One model for all chained clips.** Each renderer has its own motion/color/grain\n  character; mixing models mid-chain keeps *position* continuity (frames still hand off)\n  but the render-character shift reads as a subtle pop. The one sanctioned exception is\n  the NSFW fallback for a single stubborn clip (Gotchas) — a slight character shift on\n  one 5s connector beats a missing connector.\n- Default to `seedance_2_0`, rendered through **Monid by default** (per-clip USD —\n  next section) with Higgsfield credits as the fallback biller (Step 0.1/1.6); honor\n  a user's stated preference **only if the model qualifies** (frame-locking). If it\n  doesn't, say so and use a supported model — never ship a non-seamless build to\n  satisfy a model request. `kling3_0` and `seedance_2_0_mini` exist only on the\n  Higgsfield side.\n- The pipeline scripts take the model as `$VMODEL` with per-model flags already cased\n  out (`references/pipeline.md`).\n\n### Monid backend — the DEFAULT chain biller (qualified 2026-07-25)\n\nMonid's **`bytedance /v1/video/seedance-2.0`** passed both paid probes on\n2026-07-25 and is the **default** way this skill renders the chain, for **both\narchitectures** — it is the roster's `seedance_2_0` served pay-per-USD (wiring in\npipeline.md → \"Monid backend\"; Higgsfield renders the chain only as the fallback\nbiller or for Higgsfield-only models):\n\n- **Leg probe** (prompt + `first_frame` image): output frame 0 ≡ input still\n  (PSNR 31.6 dB), forward-glide prompt obeyed, billed the advertised cell\n  ($0.279 / 480p 4s).\n- **Connector probe** (prompt + `first_frame` + `last_frame`): start locked\n  (31.6 dB); the end **lands close but not pixel-perfect** (27.5 dB, same\n  composition, prop-level drift) — the exact end-image behavior Seedance shows\n  on Higgsfield, covered by the engine's seam crossfade and by using the next\n  dive's ACTUAL first frame as the end-image (Step 5 law, unchanged).\n\nThe I/O contract differs from the Higgsfield CLI — three rules:\n\n1. **Images go by URL, never inline.** `content` items are\n   `{\"type\":\"image_url\",\"image_url\":{\"url\":…},\"role\":\"first_frame\"|\"last_frame\"}`;\n   base64 data URLs are **rejected** (\"Must be a public https:// URL or an\n   asset://<id> reference\"). Local frames travel through Monid's free workspace\n   file system: `sfs /put` → `curl -T` the bytes → `sfs /cat` returns a signed\n   public URL to paste into the body ($0, explicitly built for this).\n2. **Pass `ratio` explicitly** (`16:9`, or `9:16` for the mobile chain) — the\n   adaptive default follows the input image's aspect instead.\n3. **Bill-check every clip**: cost is token-priced\n   (`w × h × 24 × sec / 1024` at $7–7.7/1M); read `cost.value` off each run.\n\nHistory that shaped these rules (still true as of 2026-07-25): the seedance\nendpoints were text-to-video-only until late July 2026 — **re-`inspect` before\neach build; the catalog moves in both directions.** `minimax\n/v1/video_generation` (Hailuo-2.3) remains disqualified: sending `prompt` +\n`first_frame_image` together silently drops the image (unrelated t2v output,\nwrong price cell); image-only frame-locks (33 dB) but has no camera control.\n\n**Qualification protocol for any new/changed Monid endpoint** (each probe is one\ncheap 480p clip): (1) prompt + first-frame from a real still — frame 0 must\nmatch the input to codec noise (PSNR ≳ 30 dB) and `cost.value` must match the\nadvertised cell; (2) for connector duty, add a `last_frame` from a different\nstill — the end must land on that composition (Seedance-style near-miss is fine,\nthe crossfade covers it). Pass → pay-per-clip tier (arch A if start-only; full\nroster if start+end).\n\n### A) Continuous forward take — RECOMMENDED for grounded / realistic / walkthrough\nOne camera that only ever glides **forward**, first scene through last, as a single take.\nGenerate the legs **sequentially**: leg 0 from scene-0's still (glide forward into it);\nthen each leg's `--start-image` = the **previous leg's ACTUAL last frame** (extract with\nffmpeg), prompt *\"continue gliding smoothly FORWARD into [scene i], never pulling back\"*\n(or an expressive mid-leg move under the motion-handoff contract — see **Camera grammar**\nbelow), and **no `--end-image`** — an end-image of a wide establishing shot forces the\ncamera to pull back, which is the #1 cause of stutter. Extract each leg's last frame to feed the\nnext. Result: every seam is frame-identical **and** the camera never reverses. There are\n**no connectors** (skip Step 5) — the legs ARE the journey. Wire each leg as a section\nclip with `connectors: []` and a small `crossfade` (~0.08). Even without an `--end-image`\nthe legs still arrive at distinct rooms (the prompt steers the content). Cost: strictly\n**sequential** (can't parallelize) and slower; interiors trip the NSFW filter, so build in\nre-rolls (3 attempts/leg).\n\n### B) Dive-in + aerial connector — only for diorama / miniature / god's-eye worlds\nA \"dive into each scene\" clip + a connector that pulls **up and out** and flies over to the\nnext scene (Step 5). The pull-out **reverses camera direction at every seam** (forward dive\n→ backward pull-out). In a miniature/diorama world that reads as an intentional \"zoom out\nto the map, fly to the next island\"; in a grounded first-person walkthrough it reads as a\njarring **rewind/stutter**. Use B only for the map-like aesthetic — which is exactly\nwhat the \"fly through the world\" interview answer opts into; the reversal reads as\nintentional there. If the user picked B against a grounded/photoreal direction, say\nwhy it will read as a stutter and confirm before rendering.\n\n### Camera grammar — the move should fit the concept (A is NOT \"forward only\")\n\n\"Forward only\" is the *seam* rule, not the *leg* rule. The physics of the chain:\n\n- **Position continuity** at a seam comes from the frame handoff (next leg starts from the\n  previous leg's actual last frame).\n- **Velocity continuity** at a seam means the camera must never *reverse across a seam* —\n  that's the rewind stutter.\n- **Inside a single leg the camera is free.** One leg is one continuous render — there is\n  no seam to break mid-leg, so orbits, crane-ups, lateral tracking, even a push-in that\n  eases back out are all safe *within* the clip. Reversals are only fatal *across* seams.\n\nSo give each leg an expressive move chosen from the scene's own logic, under a **motion\nhandoff contract**: every leg **ends by settling into a slow, steady forward drift** toward\nthe next destination (final ~1 s), and every leg **begins by continuing that same drift**.\nKeep both clauses in the prompts verbatim (templates in `references/prompts.md`).\n\nPick the grammar from the concept:\n\n| Concept / tone | Mid-leg move |\n|---|---|\n| Product / luxury retail | slow half-orbit around the hero object, then continue past it |\n| Real estate / hospitality | steadicam glide through doorways; gentle crane-up in atria |\n| Industrial / process / logistics | low lateral track alongside the line, foreground parallax |\n| Travel / outdoors / campus | drone-style rise-and-reveal, then a descending swoop |\n| Food / craft / detail-driven | push in close to the craft moment, ease back, carry on |\n| Playful miniature (arch. B) | dives + aerial hops — the connector IS the grammar |\n\nHonest costs: expressive mid-leg moves raise re-roll odds — the model can end a fancy move\nin a state that isn't a clean forward drift. Mitigations: keep the final-second settle\nclause verbatim; **eyeball each leg's last frame before chaining the next** (it should look\nlike a frame from a gentle forward glide — if not, re-roll before wasting the next leg);\nbudget ~1 extra re-roll per expressive leg. A plain forward glide stays the zero-risk\ndefault — use it for legs where the scene itself is the show.\n\n**Locked-iso variant** (`CAMERA` = locked isometric glide): architecture A where every\nleg pins the view instead of taking a mid-leg move — \"the camera keeps exactly the same\nhigh isometric angle throughout, no rotation, no orbit, no tilt; it only travels\nstraight and level, the world sliding past beneath the same view\" (verbatim clause in\nprompts.md). The handoff contract is unchanged. Seedance drifts the angle slightly on\nlong legs — the existing eyeball-each-last-frame rule is the catch; re-roll a leg whose\nview has rotated. Calmest look, cheapest re-rolls, and the closest to the Emons\nreference.\n\nTwo related pacing knobs live in the engine (Step 7): per-section `scroll` (more scroll\ndistance = longer dwell in that scene) and `linger` (the camera settles mid-scene exactly\nwhile the copy peaks, then picks up speed toward the seam). Prefer expressive motion in the\n*clip* and restraint in the *scrub mapping* — they compound.\n\nAnd remember scroll is a scrubber: visitors can scroll **up**, so every move also plays in\nreverse. That's free and expected — no extra work — but it's another reason seam velocity\nmust be consistent in both directions (a seam that reads fine forward reads as a stutter\nbackward too if velocity flips).\n\n**For B**, one camera flight per scene: starts high/outside, descends into the interior,\nstructure opens. Model: the chain model you picked above (default **`seedance_2_0`**),\n`--start-image = the scene still`.\n\n- Use the **solid-background still** (not the knocked-out transparent one) as the\n  start image, so the video has a full frame.\n- Prompt: \"Single continuous cinematic camera move, no cuts. Begin high and far looking\n  at the whole <scene> from outside … descend and fly inside toward <focal point> … the\n  roof/walls gently open to reveal the interior. <style>, smooth graceful slow motion.\n  No text.\" (Template in `references/prompts.md`.)\n- Params (seedance): `--mode std --resolution 1080p --aspect_ratio 16:9 --duration 8`.\n  For Kling: drop `--resolution` (no such param), add `--sound off`, `--duration 10`.\n  Do **not** pass `--generate-audio` (it errors on seedance; audio is wasted anyway —\n  you'll mute).\n- Run concurrently, detached, then download each `.result_url`. Re-roll individual\n  failures. Keep the raw 1080p sources — you need their frames next.\n\n---\n\n## Step 5 — Connectors (architecture B only)\n\nSkip this whole step for architecture **A** — the forward take has no connectors; its legs\nalready chain seamlessly. This step applies to **B** (diorama/miniature), and note the\nreversal caveat from Step 4.\n\nThe connector clips are what make the world feel *connected* instead of cut. A connector\nflies from the end of scene i out and into the start of scene i+1. **Both of its\nendpoints must be the ACTUAL RENDERED FRAMES of the neighbouring clips — never the\noriginal diorama still.**\n\nWhy: every Higgsfield generation renders slightly differently. If a connector *ends* on\na fresh render of \"the kitchen diorama,\" but the next dive clip *starts* on its own\ndifferent render of that same diorama, the two won't match and you get a pop at the seam.\nThe fix is to hand off the exact pixels:\n\n```\nFor each connector between dive_i and dive_{i+1}:\n  start-image = the LAST frame extracted from dive_i's rendered video\n  end-image   = the FIRST frame extracted from dive_{i+1}'s rendered video\n```\n\nNow every seam is frame-identical on *both* sides:\n`dive_i.end == connector.start` and `connector.end == dive_{i+1}.start`.\n\nExtract the boundary frames from the rendered dives (not the stills):\n\n```bash\nffmpeg -sseof -0.15 -i dive_i.mp4   -frames:v 1 -q:v 2 dive_i_last.png    # interior of i\nffmpeg -ss 0      -i dive_{i+1}.mp4 -frames:v 1 -q:v 2 dive_next_first.png # establishing of i+1\n```\n\nGenerate the connector (`--duration 5` is plenty). Connectors need `--end-image`, so the\nmodel must accept it — any roster model does (`seedance_2_0`, `seedance_2_0_mini`,\n`kling3_0`):\n\n```bash\nhiggsfield generate create \"$VMODEL\" \\\n  --prompt \"$(cat connector_i.txt)\" \\\n  --start-image dive_i_last.png --end-image dive_next_first.png \\\n  $VOPTS --aspect_ratio 16:9 --duration 5 --wait --json\n# seedance: VOPTS=\"--mode std --resolution 1080p\"; kling3_0: VOPTS=\"--mode std --sound off\"\n```\n\nConnector prompt: \"Single continuous camera move, no cuts. Pull up and back out of\n<scene i>, rise into the sky, glide across the connected miniature world, and arrive\nabove <scene i+1>, beginning to descend toward it. Seamless flowing aerial transition.\n<style>. No text.\" (Template in `references/prompts.md`.)\n\nInsurance: Seedance lands *close* to the end-image but not always pixel-perfect, so the\nengine still applies a **short crossfade** (a few frames) at each seam. Frame-matched\nendpoints + a small crossfade = no visible cut. Never skip the actual-frame handoff and\nrely on the crossfade alone; a big content jump can't be hidden by a crossfade.\n\n---\n\n## Step 6 — Encode for smooth scrubbing\n\nScrubbing = setting `video.currentTime` from scroll. Two things matter, and they are\noften gotten wrong:\n\n1. **Seekability, not keyframe density, is what makes scrubbing work.** Many static\n   hosts (and `python -m http.server`) don't serve HTTP byte-range requests, which pins\n   `video.seekable` to `[0,0]` and clamps *every* seek to frame 0 — the video looks\n   frozen. The robust fix is to **fetch each clip as a `Blob` and play it from an\n   in-memory object URL** (blobs are always fully seekable). The engine does this.\n   Because of it, you do **not** need all-intra video.\n2. **Don't shrink quality to get smooth seeks.** Encode at the **native resolution**\n   (1080p from Seedance — don't downscale), `crf ~20`, a **small GOP** (`-g 8`) rather\n   than all-intra (all-intra bloats an 8s clip to ~25 MB; GOP 8 is ~8 MB and scrubs\n   fine via blob). Strip audio, add faststart, and a light `unsharp` counters video\n   softness:\n\n```bash\nffmpeg -i src.mp4 -an -vf \"unsharp=5:5:0.8:5:5:0.0\" \\\n  -c:v libx264 -preset slow -crf 20 -pix_fmt yuv420p \\\n  -g 8 -keyint_min 8 -sc_threshold 0 -movflags +faststart out.mp4\n```\n\nEncode all 2N-1 clips (dives + connectors) with the same settings for uniform quality.\n\n**Mobile encodes (only if the user opted in at Step 1.6).** The mobile version is\nthe **native 9:16 portrait chain** (pipeline.md §6b): portrait renders of every dive and\nconnector, encoded **720 wide (`scale=720:-2`), `-g 4`** (more keyframes = cheaper seeks —\nphone decoders' seek cost scales with GOP length), crf 23 — wired as `clipMobile` /\n`connectorsMobile`, with each portrait dive's first frame extracted as the section's\n`stillMobile` poster (Step 7). The engine serves them automatically on phones and falls\nback to the desktop clip when absent. The 16:9 centre-crop `encm()` encodes\n(pipeline.md §6) are a **fallback only** — for when credits can't cover the portrait\nchain — and shipping them must be called out to the user, never silent. If the user chose\ndesktop-only, skip this — the engine still hardens phone scrubbing regardless\n(seek-coalescing, iOS priming), so the page degrades gracefully rather than breaking.\n\n---\n\n## Step 7 — Assemble the page\n\nCopy `references/scrub-engine.js` (and, if you want a fully standalone page, the tiny\n`references/index-template.html`) into the user's project — or adapt into their\nframework. It's config-driven and self-contained:\n\n```js\nmountScrollWorld(document.getElementById('world'), {\n  brand: { name: 'Pearl & Co.' },\n  diveScroll: 1.3, connScroll: 0.9,          // viewport-heights of scroll per clip\n  sections: [\n    { id:'farm', label:'The Farms', still:'assets/farm.webp',\n      clip:'assets/vid/farm.mp4',\n      clipMobile:'assets/vid/farm-m.mp4',      // mobile opt-in only: native 9:16 render\n      stillMobile:'assets/farm-m.webp',        // its first frame as the portrait poster\n      scroll: 1.6, linger: 0.45,   // optional pacing: longer dwell + camera settles mid-scene\n      accent:'#8FB98A', eyebrow:'From leaf to last sip', title:'It starts in the hills.',\n      body:'…', tags:['Single-origin','Hand-picked'] },\n    // …one per section; last may carry a `cta`\n  ],\n  connectors:       ['assets/vid/conn1.mp4','assets/vid/conn2.mp4',   /* … length = sections-1 */],\n  connectorsMobile: ['assets/vid/conn1-m.mp4','assets/vid/conn2-m.mp4' /* … same length; mobile opt-in only */],\n});\n```\n\nThe engine handles: the ordered dive/connector chain, scroll→currentTime with rAF\nsmoothing, blob loading, lazy prefetch of nearby clips, frame-matched crossfades, pinned\nper-section copy (first section greets on landing, last holds its CTA), a route rail,\n`prefers-reduced-motion`, and mobile. **Pacing per section:** `scroll` overrides\n`diveScroll` for that scene (more scroll = longer dwell) and `linger` (0–1, keep ≤ 0.6)\nremaps time so the camera settles mid-scene — exactly while the copy peaks — then speeds\nup toward the seam; seam frames are untouched (f(0)=0, f(1)=1). Give the hero and finale\nscenes a higher `scroll` + some `linger`; keep transit scenes brisk. Theme it with CSS variables (`--accent`,\n`--sw-bg`, `--sw-ink`, …) — the visual identity comes from the generated clips, so the\nchrome stays quiet. See the header of `scrub-engine.js` for the full config + CSS vars.\n\n**On phones the engine adapts automatically** (coarse pointer or ≤860px): it serves\n`clipMobile` / `connectorsMobile` when present, **coalesces seeks** (never queues a new\n`currentTime` while the decoder is still seeking — this is what stops a fast flick from\nfreezing the clip), **keeps the still as a poster until the clip paints its first frame**\nand **primes each video on first touch** (fixes iOS's blank-until-played video), drops the\ndrifting particles, ignores URL-bar-only resizes (no scroll jump), and uses safe-area\ninsets so copy clears the notch/home indicator. All of this hardening is on by default —\nno config needed. The `clipMobile`/`connectorsMobile` encodes are the opt-in part\n(Step 1.6): only wire them when the user asked for the mobile version.\n\nFor non-JS backends (Python/Rails/etc.): serve the assets and drop the engine `<script>`\ninto the rendered HTML; nothing about it is framework-specific.\n\n---\n\n## Step 8 — QA the seams (don't skip)\n\nDrive the page in a headless browser and **verify frame continuity at the seams**, which\nis the thing most likely to be wrong:\n\n- Screenshot at scroll positions just before and just after each seam. The two frames\n  must be near-identical (the dive's last frame == the connector's first frame). If they\n  pop, you used the diorama still instead of the actual rendered frame (redo Step 5), or\n  the crossfade band is too short. Calibration: judge seams by *composition*, not raw\n  PSNR — at 720p/1080p a correctly frame-locked seam can read ~18–25 dB from detail\n  shimmer alone (observed on a verified-good build); a real mismatch shows as different\n  composition/props, not just softness.\n- Check the console for errors, confirm `video.seekable.end(0) > 0` (blob working), and\n  that `currentTime` tracks scroll across each clip's band.\n- **Mobile — full checklist only if the user opted into the mobile version (Step 1.6).**\n  For a desktop-only build, just sanity-check a phone viewport once: page loads, still\n  posters show, nothing overlaps — the engine's hardening covers graceful degradation.\n  For the mobile build (do this on a real phone or an emulated one, portrait + landscape):\n  - Emulate a phone viewport **with CPU throttled 4–6×** and scroll fast — the clip should\n    track without freezing (the seek-coalescing + `-m.mp4` encodes are what make this hold).\n  - Confirm the first scene shows immediately (its still is the poster) and the video takes\n    over the instant you scroll — no blank/black scene (the iOS priming fix). Test iOS Safari\n    specifically; it's the one that goes blank if this regresses.\n  - Verify the `-m.mp4` variant is actually served on mobile (Network panel), and the\n    heavy 1080p master on desktop. The mobile clips must be **natively portrait**\n    (`videoWidth < videoHeight` — not a downscaled 16:9 file), and the `stillMobile`\n    posters must be served and match each portrait clip's first frame (no\n    landscape→portrait flash when the video paints).\n  - Slowly scroll so the URL bar collapses — the page must **not jump** (height-only resizes\n    are ignored on touch). Rotate the device — layout should recompose cleanly.\n  - Only if the crop **fallback** shipped (no credits for the portrait chain): portrait\n    crops a 16:9 clip to its centre — confirm the focal subject still reads, and remind\n    the user this is the stopgap, not the mobile version.\n- Check reduced-motion (should fall back to the stills, no video, no particles).\n\n---\n\n## Gotchas (hard-won)\n\n- **Seam pop** → connector endpoints were the diorama stills, not the neighbouring\n  clips' actual frames. Always extract real frames (Step 5).\n- **Seam stutter / camera \"jumps backward\"** → even with frame-matched seams, if the\n  camera *velocity reverses* (forward dive, then a connector that pulls back out) it\n  reads as a rewind. This is inherent to architecture B. For any grounded walkthrough use\n  architecture A (one continuous forward take — legs chained from actual last frames, no\n  pull-back, no `--end-image`); see Step 4.\n- **Frozen video / stuck at frame 0** → `seekable=[0,0]`; the host isn't serving byte\n  ranges. Use blob URLs (engine does).\n- **Huge files** → you used all-intra. Use `-g 8` + blob instead.\n- **Soft / low quality** → you downscaled or over-compressed. Encode native 1080p,\n  crf ≤ 20, add `unsharp`. Video is inherently softer than the stills — keep the stills\n  as the lite fallback for max fidelity.\n- **Concurrent gens 503 / \"not_enough_credits\" race** → transient when many launch at\n  once; re-roll the individual failure, it's not really out of credits (verify with\n  `higgsfield workspace list`).\n- **NSFW false-positives (Seedance `status \"nsfw\"`)** → the video content filter flags\n  perfectly innocuous clips, especially **bedroom, pool, spa/wellness** contexts and\n  trigger words like \"bed\", \"pool\", \"waterfall\", \"wine\", \"swim\". It's partly the prompt\n  wording and partly the reference frames. Fixes, in order: (1) re-roll — it's often\n  non-deterministic and passes on the 2nd–3rd try; (2) strip trigger words and add\n  \"empty, unoccupied, no people, no figures, architectural, tasteful\"; (3) regenerate\n  just that clip on **`kling3_0`** with the same start/end frames — a different\n  provider's filter often passes what Seedance blocks. Expect a slight render-character\n  shift on that one clip (each model has its own grain/motion feel); for a 5s connector\n  behind a crossfade that usually beats option (4): set the connector slot to `null` —\n  the engine crossfades that seam directly (optional connectors), so the page still\n  completes. Budget extra credits/time for these re-rolls on interiors/real-estate content.\n- **Dark / custom theme** → the engine wraps its default tokens in `@layer sw`, so a\n  page-level `:root` / `.sw-root { --sw-bg; --sw-ink; --sw-accent; --sw-font-* }` block\n  wins cleanly (no specificity hacks). `--sw-ink` is your primary **text/heading** colour;\n  the **accent** fills the primary button and active nav. For a dark theme, set `--sw-bg`\n  dark and `--sw-ink` light — the copy scrim and title shadow follow `--sw-bg` automatically.\n- **Phone scrub stutters / freezes on a fast flick** → the 1080p master is too heavy for a\n  phone decoder and seeks pile up. Ship the `-m.mp4` mobile encodes (720p, `-g 4`) and wire\n  `clipMobile`/`connectorsMobile` (Step 6/7). The engine already coalesces seeks; the lighter\n  encode is the other half. Still choppy on a low-end device? Tighten GOP (`-g 2` / all-intra).\n- **Blank / black scene on iOS (desktop was fine)** → an iOS Safari quirk: a muted video that\n  was never played won't paint a seeked frame. The engine fixes this by keeping the still as a\n  poster until the clip paints and priming each video on first touch — so **don't** hide the\n  still on `loadedmetadata` or strip the `playsinline`/`muted` attributes if you adapt the\n  engine into a framework.\n- **Page jumps while scrolling on mobile** → something is re-running layout on the URL-bar\n  show/hide `resize`. The engine ignores height-only resizes on touch; if you ported it, gate\n  your resize handler on a width change (keep the `orientationchange` path for rotation).\n- **Copy hidden behind the URL bar / notch on mobile** → use the engine's safe-area-aware\n  bottom offset (`env(safe-area-inset-bottom)` + `dvh`); make sure the page's\n  `<meta viewport>` includes `viewport-fit=cover` (the template does).\n- **Portrait crops the scene** → a 16:9 clip on a tall phone shows only its centre — which\n  is why the mobile version is the native 9:16 chain (§6b), never the crop. If you're seeing\n  this on a mobile build, either the crop fallback shipped (call it out to the user) or the\n  9:16 encodes aren't actually being served (check `videoWidth < videoHeight`). Keeping each\n  scene's focal subject centred (prompts.md) still matters for the desktop film itself.\n- **`--generate-audio` errors on seedance** → omit it; mute in HTML and `-an` on encode.\n- **Kling rejects your flags** → `kling3_0` has **no `--resolution` param** (don't pass\n  one; encode at whatever native res ffprobe reports) and **sound defaults on** — pass\n  `--sound off`. Duration default is 5; legs/dives want 10.\n- **Seam pop only where you \"saved credits\"** → you swapped models mid-chain, or used a\n  start-image-only model where a connector needs an `--end-image`. One model for the whole\n  chain; the only cheap tier is `seedance_2_0_mini`, which keeps frame-locking so it stays\n  seamless. (Any model with reference-only inputs can't hold a seam at all — Step 4.)\n- **Monid seedance rejects inline images** → \"Must be a public https:// URL or an\n  asset://<id> reference\": frames go through the free `sfs` file system\n  (put → `curl -T` → cat → signed URL; pipeline.md → Monid backend), never base64.\n  Quirk: `/put` echoes back `home/<path>`, but `/cat` and `/ls` want the **original\n  relative path** you gave `/put` — using the echoed path 404s.\n- **Monid clip wrong aspect** → the `ratio` default is adaptive and follows the input\n  image (a 3:2 still → a 4:3-ish video). Pass `--ratio` — `16:9` desktop, `9:16`\n  mobile chain — explicitly on every chained clip.\n- **Monid CLI \"Polling timed out after 120s\"** → only the local wait died; the run\n  continues server-side. Re-poll with `monid runs get -r <runId> -w 120` (find the id\n  in `monid runs list`). Result URLs expire (~24–48 h) — download immediately.\n- **Monid minimax drops the image when a prompt is present** → `prompt` +\n  `first_frame_image` together returns an unrelated t2v clip AND bills the wrong matrix\n  cell ($0.56 vs $0.28 observed). Image-only frame-locks but has no camera control.\n  Until the wrapper is fixed, that endpoint can't chain — use Monid's seedance-2.0.\n  (The model itself is fine — the same prompt+image via Higgsfield `minimax_hailuo`\n  frame-locks.)\n- **Monid billing surprises** → matrix/token-priced endpoints bill by selector match:\n  pass every selector field explicitly (`model`, `resolution`, `duration`) and read\n  `cost.value` off the run result after each clip. A big base64 field in any body can\n  also bounce as an HTML error page (\"Unexpected token '<'\") — another reason frames\n  travel by sfs URL.\n- **Monid schema changed since last build** → it happens (seedance was t2v-only until\n  late July 2026, then gained first/last-frame support). `monid inspect` before each\n  build; re-run the Step 4 qualification probes when the Input schema differs from\n  what pipeline.md documents.\n- **Codex stills hang at \"Reading additional input from stdin...\"** → parallel\n  `codex exec` calls launched from one script share the parent's stdin; one wins it,\n  the rest block forever (observed: 1 of 3 completed, 2 hung, the second batch never\n  started). Always append `< /dev/null` to every backgrounded `codex exec` — the\n  pipeline's `gen_still_codex` has it; keep it if you adapt the command.\n- **White-box scenes** → `gpt_image_2` returns a solid bg; either match the page bg to it\n  or knock it out (Step 3).\n- **bash 3.2** on macOS → no associative arrays in scripts.\n- **Connector grabs the wrong scene's frames** (or errors on a frame that doesn't exist\n  yet) → the array loop ran in **zsh** (macOS default interactive shell), where arrays are\n  1-indexed, not bash's 0-indexed. Keep every array-driven chain step in a `#!/bin/bash`\n  script run via `bash script.sh` — never inline array loops in the interactive shell.\n\n## References\n\n- `references/prompts.md` — the intake checklist, style-preamble pattern, and every\n  prompt template (scene still, dive, connector) with fill-in slots.\n- `references/pipeline.md` — copy-paste batch scripts for the whole run (generate →\n  extract frames → connectors → encode → mobile encode), bash-3.2-safe.\n- `references/scrub-engine.js` — the portable, config-driven scrub engine (builds DOM +\n  injects CSS; blob-seek, lazy load, seam crossfade, copy, route rail, reduced-motion, and\n  phone hardening: mobile encodes, seek-coalescing, iOS priming, safe-area, no-jump resize).\n- `references/index-template.html` — a minimal standalone page that mounts the engine.\n- `references/knockout.py` — border-connected background knockout for floating scenes.\n\n<!-- File: .claude-plugin/marketplace.json (Tokens: ~203 | Category: marketplace) -->\n{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"scroll-world\",\n  \"description\": \"Immersive scroll-scrubbed 'fly through the world' landing pages, generated with Higgsfield.\",\n  \"owner\": {\n    \"name\": \"cyw\",\n    \"email\": \"cyw@cywang.me\"\n  },\n  \"plugins\": [\n    {\n      \"name\": \"scroll-world\",\n      \"source\": \"./\",\n      \"skills\": [\"./skills/scroll-world\"],\n      \"description\": \"Build an immersive scroll-scrubbed landing page where a pre-rendered camera flies from outside each scene into its interior, then flows on to the next with no cuts. Interviews the user for topic, story beats, and brand kit; generates cohesive scenes + seamless camera clips with Higgsfield; wires a portable, framework-agnostic scrub engine.\",\n      \"category\": \"design\"\n    }\n  ]\n}\n\n<!-- File: .claude-plugin/plugin.json (Tokens: ~256 | Category: plugin-manifest) -->\n{\n  \"name\": \"scroll-world\",\n  \"description\": \"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. Interviews the user for the topic, story beats, and brand kit, then generates cohesive scenes + seamless camera clips and wires a portable, framework-agnostic scroll-scrub engine. Use for a '3D world' / 'browse-through-the-industry' hero, a scroll cinematic, or a diorama landing.\",\n  \"version\": \"0.8.0\",\n  \"author\": {\n    \"name\": \"cyw\",\n    \"email\": \"cyw@cywang.me\"\n  },\n  \"homepage\": \"https://github.com/oso95/scroll-world\",\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"higgsfield\",\n    \"monid\",\n    \"landing-page\",\n    \"scroll\",\n    \"video\",\n    \"seedance\",\n    \"diorama\",\n    \"webgl-alternative\",\n    \"cinematic\"\n  ]\n}\n</yakaai_skills>"}