popup-storybook-v2

Verified@siosan/popup-storybook-v2
Forked from neta-search/pop-up-storybook
Use it in Cohub

Paste this into your agent's chat — it fetches the skill and follows it.

Use the skill: popup-storybook-v2. Run `npx -y @neta-art/neta-search-cli@latest skills use @siosan/popup-storybook-v2 --via site` and follow what it prints. Read its complete output, redirecting it to a temporary file first if necessary, and resolve relative paths from the skill directory it prints.
Use in Neta Studio Open it in Neta Studio and see what people made with it.
Install from a terminal
npx -y @neta-art/neta-search-cli@latest skills use @siosan/popup-storybook-v2
Summary

Turn a story, scene or your world into a 3D pop-up book whose figures stand up off the page.

  • You get an opening book on a sunlit desk: text and a lift-the-flap card on the left, three standing paper figures on the right.
  • Backdrop, main figure and foreground prop rise off the paper in any art style you pick, such as ink, watercolour or pixel art.
  • Readers drag to look around, tap figures for their names, lift the flap, switch the lamp and day or night, and click the text to read it typed out.
  • Start with one page, then say "continue" to add the next page to the same book. Finished pages are never redrawn.
  • Works best with a clear scene, a main character with a distinctive look, and a short story beat. Choose a theme, or let it read your world.
SKILL.md
# Pop-Up Storybook (page by page)

A clothbound book on an oak desk beside an architect's lamp. It opens onto one spread: the left page
is typeset (title, lede, passage, a lift-the-flap card); on the right, a backdrop, a figure and a prop
are drawn in by pen and stand up off the paper. Readers drag to look around, tap the figures, lift the
flap, switch the lamp, switch day and night, hover the text for a loupe, and click it to read a typed-out copy. A book
starts as **one spread** and grows **one page (spread) per run** when the author asks to continue (§5);
readers turn the pages. The standees may be in **any art style**. Only the standees and the words
change per book — the book, desk, lamp and chrome are a shared template.

**Cost contract:** one page = **1 composition master** (the selected model — gpt-image-2.5 — at
`medium` quality: the whole scene page in one picture, so style, light and scale agree) + **1
qwen-image-2.1 edit** (~$0.01) that keeps only the backdrop. Everything that edit removed is found by
comparing the two pictures: **however many pieces the master holds** (up to `maxPieces`, default 6)
become standees, each where it stood. A piece on bare paper is cut straight from the master for free;
a piece standing in front of the backdrop gets **one more qwen edit** by its position (its edge
against the scenery needs it), accepted only if it matches the piece's silhouette, else the free cut
is kept. Typical page: 1 master + 2–4 cheap edits; pages already built cost nothing again. Plus **4 agent
steps**: read → write spec → run → hand over (and ask whether to continue).
If the selected model does not answer (it can stall ~5 min and then time out), the master goes once to
the fallback model (`gemini-3.1-flash-lite-image`) instead of being sent again; the build never repeats
a request that already timed out. Other modes, only when the user asks: `"mode": "master"` (always exactly bg / mid / fg lifts),
`"mode": "separate"` (three independent images, no shared scene) and `"mode": "sheet"` (one image split
three ways — cheapest, softest). If the backdrop edit moves the scene so the pieces cannot be found,
the run falls back to `master` by itself.
No questionnaire. Never regenerate for taste on your own — only when the user asks.

## 1. Read the theme (at most 2 CLI calls)

- **Theme named** in the request → that is the subject. Ask nothing.
- **No theme named** → this world: `node /mods/neta/neta atom list` once; `atom get` for at most one
  item if a body matters. Never edit world files.
- **World empty and no theme** → ask in one sentence what the page should show, and stop.

## 2. Write `book.spec.json` (one write, anywhere, e.g. `/tmp/<slug>.json`)

```json
{
  "slug": "kebab-case-name",
  "title": "Book title", "subtitle": "One line", "chapter": "Chapter name",
  "spreadTitle": "Heading on the text page", "lede": "One-line standfirst.",
  "language": "zh-CN",
  "style": "One art-style phrase for all three pieces: ink line, watercolour, cel shading, pixel art…",
  "passages": [
    { "speaker": "Narrator", "text": "≤ 70 CJK characters or ~45 words each; 3 passages." },
    { "speaker": "Name", "text": "A line of dialogue." },
    { "speaker": "Narrator", "text": "A closing beat." }
  ],
  "flap": { "label": "Lift the card", "text": "One hidden line." },
  "figure": { "caption": "Fig. 1  Caption", "hint": "Tap the …" },
  "layers": {
    "bg":  { "prompt": "Backdrop subject only, one English sentence.", "label": "Tap name", "identity": "location:Name" },
    "mid": { "prompt": "Main figure subject only.", "label": "Tap name", "identity": "character:Name", "side": "right" },
    "fg":  { "prompt": "Low foreground prop subject only.", "label": "Tap name", "identity": "item:Name" }
  },
  "extras": [ { "prompt": "Optional extra prop or creature, subject only.", "label": "Tap name" } ],
  "colophon": "Small print on the desk",
  "ui": {}
}
```

- All reader-facing text in the language of the user's opening message, your own words — never a
  licensed text verbatim. `language` is its BCP-47 tag.
- `prompt` = subject only (the script adds layout, role, style, background wording). `extras`
  (optional, 0–3) ask the master for more separate pieces; leave it out for a simple scene. The tallest
  piece found is labelled with `mid`, the lowest-standing other one with `fg`, the rest with `extras`
  in order (unlabelled pieces still stand, just show no name when tapped).
- `style`: the user's wish, else a phrase echoing `world/WORLD.md`'s `visualStyle`, else watercolour.
- `refs` (optional, on any layer): a character material path (`world/characters/<name>.md`) or an
  image path; its cover is sent as the reference so the figure matches the world.
- `heights` (optional, rarely): `{"figure": 0.9, "figurePage": 0.62, "props": 0.6, "propsPage": 0.42}` —
  the tallest a standee may stand, against the backdrop's height and the page's depth. Only when the user
  asks for bigger or smaller figures.
- `ui`: `{}` for Chinese, English, Japanese, Korean, French, Spanish (built in, each with its own reading
  face for the typed-text panel). Other languages, and optional `theme` overrides (desk,
  light, lamp colour, palette): `references/spec.md` — read it only then.

## 3. Run (one command, in the background)

```bash
python3 -u <skill-dir>/scripts/make.py --spec /tmp/<slug>.json > /tmp/<slug>.log 2>&1 &
```

then poll, at most once a minute, until the last line is JSON:

```bash
sleep 60; tail -n 3 /tmp/<slug>.log
```

`<skill-dir>` is this file's folder (the directory `skills use` printed). It builds every page not built
yet (page 1 on a first run) in
`/tmp/pop-up-<slug>` (kept, so reruns reuse every finished image for free), draws the master,
lifts and cuts the standees, uses the master as the cover, runs the browser QA, and publishes the web Work **only if QA
passes**. Usually **4–6 minutes** (the master alone takes 1–4); up to ~12 when the image service is slow.
Never run it in the foreground: a tool timeout kills it mid-generation and the paid images are lost.
While it runs the log shows `[ 95s] [step] …` / `[gen ]` / `[ok  ]` lines; a quiet log during the
master is normal — do not start a second run. The last line is one JSON line — read only that:

| `stage` / field | What to do |
|---|---|
| `done`, `ok: true` | Open `preview` (one small image) to confirm the page reads, then hand over. |
| `build` + `failed` mentioning credits/permission | Stop: report the book as blocked. Never ship placeholder art. |
| `build` + `failed.master` otherwise | No model could draw the master (nothing else was generated). Tell the user the image service is not answering; rerun once only if they want to wait. |
| `build` + `failed` otherwise | Rerun the same command once (free for finished parts). |
| `build` + `cutWarnings.<layer>` | Rerun with `--force <layer>` (re-lifts that piece from the master, ~$0.01). |
| `notes` lists the pieces found | Nothing to do; open `preview` to see them. |
| `notes` says a piece "stood at N% of its size" | Expected: a figure drawn taller than its backdrop is stood up shorter (see Gotchas). Nothing to do. |
| `notes` says the master was drawn by another model | Fine; tell the user in one line which model drew it. |
| `notes` says a lift did not match | The free cut from the master was kept; fine unless `preview` shows a ragged edge — then `--force <id>` once. |
| The scene itself is wrong (user asks) | `--force master` redraws the master and re-lifts all three. |
| `qa` + `problems` | Text too long → shorten passages in the spec and rerun (free). Otherwise fix what it says and rerun. |
| `publish` + `problems` | Fix what it says and rerun (free). |

Check without publishing (rebuilds the book data and reruns the browser QA, free):

```bash
python3 <skill-dir>/scripts/make.py --spec /tmp/<slug>.json --qa-only
```

`--mode separate` / `--mode sheet` switch strategy for one run (see the cost contract).

## 4. Hand over — the run is not delivered until both are done

Reply in the user's language — every message of the run, above all this final handover. Translate
what the scripts print (notes, problems); don't paste it.

1. Through the `interaction-components` skill: a link card on its own line with target exactly
   `{ "type": "work", "workId": "<workId from the JSON>" }` — never an id you put together, never a
   `studio-route`. Say in one line how many pages the book now has.
2. Close by asking the author whether to continue with the next page: name in one short phrase what
   could happen on it (the next beat of this story — the user's own wish if they hinted at one) and
   that it adds one page to this same book (about one more master; the pages already made are kept
   as they are). When the turn closes with tappable options, the first one is that next page. The
   turn ends on that question; the next page is made in a later turn, once the author says yes.

## 5. Add a page (only when the author says to continue)

1. Append one object to `pages` in the **same** spec file (same `slug`; if the file is gone, the copy
   at `/tmp/pop-up-<slug>/book.spec.json` or the published Work's `data/source/book.spec.json`):

   ```json
   "pages": [
     { "spreadTitle": "…", "lede": "…", "passages": [ … 3 items … ], "flap": { … }, "figure": { … },
       "layers": { "bg": { … }, "mid": { … }, "fg": { … } }, "extras": [ … ] }
   ]
   ```

   Same fields and rules as page 1 (step 2). Title, style, language, theme, `heights` come from the top
   level, so every page shares one look; keep the same characters' `refs` and descriptions so they stay
   recognisable. Add `"chapter": "…"` only to start a new chapter. If `subtitle` or `colophon` says
   the book is one page, update them.
2. Run the same command as step 3. It skips every finished page (`page N: already built … no cost`),
   draws only the new one, and republishes the **same** Work. In a new chat, after a sandbox restart,
   it brings the finished pages back from the published Work first. `--page N --force …` redraws one
   earlier page, only when the author asks.
3. Hand over as in step 4: the same Work's card, the page count, and the question about the next page.

## Out of scope

- Several new pages in one run, or a page the author did not ask for; branching (choose-your-path) books;
  a composition board, generated page grounds or a generated cover.
- Editing world materials or world settings, or deleting existing files and Works (`make.py` picks a
  fresh `works/webs/<slug>-N` when the slug is taken by another Work).
- Restyling the book, desk, lamp or chrome beyond `theme` overrides — the runtime
  (`templates/runtime/`, MIT, from the popup-storybook runtime by ChenzuoL; see its `LICENSE`) is shared.

## Gotchas

- Never put "transparent background" in a prompt: some models paint a grey-white checkerboard instead.
  The scripts ask for plain white in words and request transparency through the model parameter; the
  local cut also removes painted checkerboards.
- Sheet pieces are cropped tight, so they fill their frames; the cut check for them is the share of
  transparent pixels, not the frame cover.
- QA renders the low tier (no shadow maps) in a software GL: it proves mechanics, not lighting. Readers
  get shadows and the lamp light on real GPUs.
- Every generation is also saved as an image Work in the world (`<title> · bg` etc.); that is expected.
- One sheet split three ways gave a ~300 px-wide figure and a narrow backdrop; three separate images
  gave sharp pieces that did not share a scene. The master + lift default gives both.
- Finding the pieces: the master and its backdrop-only edit are blurred and differenced; thin lines
  (qwen redrawing edges a pixel off) are dropped, small bits next to a piece are merged into it (a stamp's
  knob above a thin neck), small holes are filled, large enclosed gaps stay open (between stool legs).
  A colour-matching edge against the scenery (a brown bag on a brown shelf) is why pieces over the
  backdrop are lifted by qwen instead of cut from the diff.
- Lifts name the kept piece by its place in the picture ("the character standing on the right"), never
  by the spec's words: the master may draw the subject differently (a boy for "a paper messenger"), and a
  description that does not match makes qwen redraw something new instead of keeping what is there.
- qwen sometimes keeps a neighbour (the figure lifted with the prop). The figure stands on `mid.side`
  and the prop on the other half, so blobs on the wrong half are dropped locally for free; enclosed
  white gaps (between stool legs) are cleared too — warm white for props, only pure white in figures,
  whose paper and shirts are white details.
- `work init` files the Work under `works/webs/`; `make.py` reads the folder it reports.
- Pages: page 1 is the spec's top level, page N lives in `/tmp/pop-up-<slug>/pages/N/` with its own
  generated.json (a `_done` marker once built) and ships as `pages/N/assets/` in the Work, which also
  keeps `data/source/` (the spec and every page's state) so a later chat can add a page without
  redrawing. The browser QA opens the book straight onto the new page and checks that page: every
  page turn costs ~15 s in the software GL, and earlier pages were checked when they were made.
- Speed: in the 2026-11-03 review three of nine runs sat 15 minutes with nothing to show. The master call
  stalled until the service answered "timed out" (~5 min), then the same request went out again, then
  again from the master fallback, while `make.py` ran in the foreground and was killed by the agent's
  900 s tool timeout. Now a timed-out request goes to the fallback model instead of being repeated, a
  master no model can draw stops the build at once, playwright installs while the images are drawn, and
  the run goes in the background with a progress log.
- Standee height: the master draws the figure in front of the scenery, so it is drawn far taller than
  the backdrop (perspective). Stood up in 3D, four of nine figures towered over their backdrops, up to
  the page's full depth. `fit_heights` (build.py) caps the figure against the backdrop and the page and
  shrinks every other piece by the same factor — shrinking only the figure left a prop as big as the
  person. The master prompt also asks for the figure's head below the backdrop's top edge.