holo-pack-opening

Officialneta-search/holo-pack-opening
Use it in Cohub

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

Use the skill: holo-pack-opening. Run `npx -y @neta-art/neta-search-cli@latest skills use neta-search/holo-pack-opening --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 neta-search/holo-pack-opening
Summary

Turn your characters into holographic trading cards you can tilt, open, and collect.

  • Make a single foil card, a card that flips into a second picture when tilted, a video card, or a full booster pack of cards.
  • Rare cards get a tear-open pack moment with light, burst effects, and a gallery of everything you pulled.
  • Works best with characters that have a strong, clear look, since the foil and tilt effects show off a clean silhouette.
  • A booster pack can mix common, rare, and legendary cards, with at least one card that reveals a second hidden form.
  • Delivered as a page you can open and share, viewable right in the browser with no extra apps.
SKILL.md
# Holo Pack Opening (Holo Card V5)

One card = one Three.js face shader. Layer A is the RuiC composite (background + subject + optional effects + line glints + text, four finishes: pearl / silver / gold / plain). Layer B (optional) is a full registered illustration shown by lenticular phase: tilt, or forced during a reveal. Effects are separate additive planes placed behind / on the card, driven by a rarity preset. **V5 adds form 4**: a multi-card booster pack — the same holo card faces, but a whole pack of them with a tear -> pick -> reveal -> gallery opening loop.

Status: **V5** (2026-10-08) — the upgrade of the original single-card Holo Card skill. Forms 1–3 (static / lenticular / video card, each in showcase or reference-pack presentation) are v1-tested. **Form 4 (multi-card booster, new in V5)**: one pack, N mixed cards — tear -> deal face-down -> pick -> burn/holo reveal per rarity -> gallery, with lenticular hits, god packs, pity and gilded cards; extracted from the live **Booster 09A V5** (`cohub.live/syd/syd-cards/w/booster-09a`). See `VERSION` for the source hashes and `references/booster.md` for the engine. Keep the whole skill generic — no Space, account, credential or artwork is required, and all per-project look lives in a config, not code.

Resolve every relative path from this skill directory. Nothing here depends on a particular Space, account, credential or artwork.

## Contract

### When to pick it
The user wants a holo / foil trading card as a browser-only Three.js page — a single layered foil card, an optional lenticular A→B "awakening", a video card, or a multi-card booster pack with a tear → deal → pick → reveal → gallery loop. No Blender, no GLB.

### Stop when the pick is wrong
This is a card / pack, not a generic web page or a poster; artwork generation belongs to `generate`, and world covers are identity references, not finished layers. A build needing a real 3D asset pipeline (Blender / GLB) is out of scope.

### Input
Required: card copy — title, subtitle, collection, edition, **English (Latin script) only** (no CJK on the card under any circumstance, even if the user asks). You write it from the roster (form 4) or the subject (forms 1–3); it is never a reason to ask. What decides whether to ask is the Step 0 list (form, art source, roster & size, style, awakening).
Optional: `text`, `effects`, `lineart`, a `stateB` registered illustration, `back`, finish (pearl / silver / gold / plain), rarity; for form 4 a roster of N freshly generated 2:3 faces (+ `_b` for lenticular), a generated ~4:5 pack front and a generated card back.
Defaults per `card-config.json` / `booster-config.json` (8-card pack, exactly 1 guaranteed lenticular, rarities common → legendary).

### Transform
brief gate — one questionnaire round only if any of form / art source / roster & size / style / awakening is open (all answered → skip) → build (form 1 `build_card.py` / form 3 `build_video_card.py` / form 4 `build_booster.py`) → serve over http and look → `verify.mjs` → **Step N approval round** → publish only the site folder.

### Deliver
A publishable static site folder (the card / pack page) plus the job and config files and the verification result. Form 4 also emits `build-report.json` with per-asset `source`. Deliver the URL, folder, verification result, and any `reference` fallback taken.

The deliverable is **one web Work** (every form, including the video card): `neta work init web works/web/<slug> --title "<title>"`, copy the built site folder in (without `verification/` and `build-report.json`), then `neta work publish web works/web/<slug>`. A loose file, a markdown link, or a folder under the scratch dir is not a delivery. Hand it over through the `interaction-components` skill as a link card on its own line whose target is exactly `{ "type": "work", "workId": "<preview.workId>" }`, with `preview.workId` read from `works/web/<slug>/meta.json` after publish (the same value as `cohubWorkId`) — never an id you put together yourself. A `work` target needs no `worldId`: do not build a `studio-route`, and never take a world id from `.neta/runtime.json` (its `forkSource.worldId` is the world this one was copied from, not this world). The card is the hand-over — not only a link in the prose.

**Reply language:** every chat reply — questions, approval round, hand-over — is in the language of the user's opening message, even though this SKILL is in English. Only the card copy stays English (see Input).

### Explicitly not doing
Any CJK text on the card or generated UI; publishing before the Step N approval round; cropping / rescaling a world cover straight in as a finished asset when the user did not choose reuse (form 4 faces, `artB`, pack art and back are `artSource: "generated"` by default; a Step 0 "reuse" / "cover" answer flips them to `reference` + `--allow-reference`, declared); changing look in code instead of config; a `reference` build that is not declared.

### Stop when input is missing
Any Step 0 item open (form, art source, roster & size, style, awakening) — ask the one round, never pick for the user. No subject / background you can produce (or, for form 4, no fresh faces / pack front / card back). A build that would have to ship a `reference` asset without the explicit `--allow-reference` escape. A `verify.mjs` that is not green — and even a green report only proves the controls, not the look, so the approval round is still required.

## Cases

### should
"Make an 8-card holo pack for this world, with a gacha reveal." (form and size named; roster, art source, style, awakening open)
Expect: this skill (form 4 booster); one brief round for the open items, freshly generated faces / pack / back, the approval round before publish, English copy only.

### should (brief complete)
"An 8-card pack of these eight named characters, generate the whole set fresh, in the world's own style; the hero awakens into their signature transformed form."
Expect: no brief round — all five answered; build straight away (pack & back default: generate), then the approval round before publish.

### shouldNot
"Turn this fan card into a GLB, I want to import it into Unity."
Expect: not this skill — a real 3D asset pipeline (Blender / GLB) is out of scope.

### missing
"Make one holo card." (form named; who is on it, art source, style and awakening open)
Expect: stop before any generation; one round for the open items. Do not pick a subject yourself.

### theme
"Make one awakening card of a character from this world, A→B lenticular."
Expect: same skill, form 2 (lenticular A/B); registered `stateB`, English copy only.

## Output forms (all verified)

| Card type | Script | Needs | Look |
|---|---|---|---|
| 1. Foil layered card | `build_card.py` | subject + background (+text, effects) | RuiC foil laminate, subject/background parallax on tilt |
| 2. Lenticular awakening card | `build_card.py` + `stateB` | form 1 + a registered B illustration | form 1, plus tilt/reveal flips A -> B (lens stripes, ridge sheen) |
| 3. Video card | `build_video_card.py` | one portrait loop video (+text, back) | muted loop in an 84% framed window, low foil, no parallax |
| 4. Multi-card booster | `build_booster.py` | N **freshly generated** flat 2:3 card images (+`_b` for lenticular), a **generated** 4:5 pack front, a **generated** card back | 8-card pack: tear -> deal face-down -> pick -> burn/holo reveal -> hold FX -> gallery; lenticular hits, god packs, pity, gilded |

Presentation for forms 1–3: **showcase** (`pack.enabled: false`, reveal flash on load) or **pack opening** (`pack.enabled: true`, optional `pack.art` RGBA 4:5 front). Form 4 is its own presentation (multi-card pack, gallery). Finish (pearl / silver / gold / plain) and rarity (common -> legendary) are parameters, not forms.

## Inputs

Required:
1. `subject` — transparent RGBA character layer (real alpha, not a painted checkerboard).
2. `background` — opaque plate with the subject removed/completed, same canvas as the subject.
3. Card copy: title, subtitle, collection, edition — **English (Latin script) only**.
   Hard rule: no Chinese / CJK text may appear on the card under any circumstance, even if the user explicitly asks for it. If asked for Chinese, say it is unsupported and use the English equivalent instead.

Optional: `text` (RGBA typography + frame, printed without parallax), `effects` (RGBA VFX that should float between subject and text, e.g. fire, light beams), `lineart` (dark contours on white; derived automatically when absent), `stateB` (full 2:3 illustration of the second state, registered to A: same camera, crop, head position), `back` (card back art), finish, rarity.

All card layers must be 2:3. Never stretch; crop or pad with the user's approval. Keep the highest resolution available (the script caps at 2560 px tall).

### Art provenance (hard rule)

Every visible asset a form ships — card faces (`cards[].art`, `artB`), the card back and the pack
front — must be **made fresh in this run, for this world**. Existing world material (a character
cover, an existing work, a previous pack) may only be used as a **reference** to generate from; it
must never be cropped, re-scaled or copied straight in as a finished asset.

- The job records each asset as `artSource: "generated"` (or `"reference"`). `build_booster.py`
  refuses a form-4 build whose faces, `artB`, pack art or card back are `"reference"`, and writes the
  per-asset `source` into `build-report.json` so the delivery gate can read it. The rare escape is
  `--allow-reference`; it stamps `source: reference` and must then be declared in the hand-over.
- World covers are the **identity anchor**: pass them to `generate` as reference images so the card
  reads as this world's character, then ship the generated result — not the cover.
- If generation is blocked (content policy) and you must fall back to a procedural wrapper or an
  existing asset, keep it visible: record it and say so in the hand-over. Never silently.
- **The user's Step 0 answer outranks this default.** Art source = *reuse* → the faces are made from
  the world covers (crop / restyle them as the chosen style needs) with `artSource: "reference"`.
  Pack & back = *reuse a world cover* → `pack.art` and `pack.back` are built from a world cover
  (`artSource` / `backSource: "reference"`), with the pack's English copy laid over it. Pack & back =
  *plain* → the procedural wrapper. Build with `--allow-reference` whenever an answer chose reuse, and
  declare it in the hand-over. Never swap a reuse answer for freshly drawn or procedural art (or the
  reverse) on your own. If the cover you would use carries CJK lettering, crop a region without
  lettering, or use another of the world's covers that has none. It is still the cover, not new art.

### Producing missing layers (ask before paid generation)
- Subject cutout: background-removal model on the approved master.
- Background plate: inpaint the subject region from the SAME master (not a regenerated scene).
- stateB: image-edit A into B with identity lock (same face, pose, camera, crop). A frame from an approved video of the same shot also works but is usually softer (compression, lower resolution).
- Never let generated layers drift in pose/scale; they must register pixel-for-pixel with the subject.

## Execute

Every run has **three phases**: (0) settle the brief with **one questionnaire round**, (1) build,
(2) **approve the art before publishing**. Both gates are required; `references/gates.md` holds the exact
questions and how to handle the answers.

### Step 0 — brief gate (one round, before any generation)

**Ask or not — decide on these five, nothing else:** form, art source, roster & size, style, awakening.

- **All five answered by the brief → no round. Go straight to the build** and name the defaults you took
  (pack & back, finish, rarities) in one clause.
- **Any of the five open → one round** through the `questionnaire` skill. Put the open ones in it;
  you may fold in pack & back (it has a default) — never re-ask what the user just said.
- **What counts as answered:**
  - form — booster / pack, single (awakening) card, or video card is named;
  - art source — the brief says generate fresh or reuse the covers. **Never infer it**: silence is open;
  - roster & size — characters / places **by name**, or a named group of this world ("the trio", a named faction)
    *and* a count; naming only the world ("make a pack for this world") or "some characters" is **not** a roster;
  - style — a named style, or "the world's own style";
  - awakening — which card awakens into what, or "no awakening".
- **Not reasons to ask:** pack & back (default: generate both), card copy (you write it from the
  roster, English only), finish, rarity, odds.
- If `questionnaire ask` returns `unavailable`, do what the `questionnaire` skill says — fix and re-ask
  at most twice, then put the same questions inline in chat **once**. Never send a test questionnaire.

The questions, most important first:

1. **Form** — multi-card booster / lenticular awakening card / video card (ground it in the world).
2. **Art source** — generate the whole set fresh (world covers as reference only) / reuse existing world
   covers. Name the cost either way; the default is generate.
3. **Roster & size** — the world's real characters / places by name, and how many cards.
4. **Style** — tiles: the world's own visual style first, then a few card-friendly styles.
5. **Pack & back** — generate a themed pack front + card back / reuse a world cover / a clean procedural
   wrapper. (Default: generate both.)
6. **Awakening** — which card carries the A→B lenticular, and what it awakens into.

No image generation before the round returns. Carry the answers into the job **and** into the art gates:
*art source* and *pack & back* become the `artSource` flags the builder checks (see Art provenance).
Write every answer into the job exactly as given before the first build: roster names, style, which
card awakens into what, and reuse vs. generate for faces and for pack & back. Then check the job
against the answers once. If an answer truly cannot be built as given, do the nearest thing that
still honours it and name the gap in the hand-over. Do not pause to ask.

### Step N — approval gate (after verify, before publish, never skip)

The moment the build verifies, lay the art out for the user and ask the approval round
(`references/gates.md`): list every piece that was made — each card face, each `artB`, `pack.art`,
`pack.back` — and ask **which pieces to keep vs. redo**, plus what is wrong with the ones being redone.
Then:

- **Nothing is published until the user approves.** A green `verify.mjs` proves the controls, not the look.
- One questionnaire per turn. If Step 0's questionnaire already ran this turn, ask this round in chat
  (pieces by name, "which to redo, or reply 'publish'") and end the turn. Do not retry the questionnaire
  (`references/gates.md` "Same turn as Step 0").
- Regenerate **only the rejected pieces**, overwriting them in place in the same build folder (`build_booster.py … --in-place`); the rest stay untouched. Show them again and re-ask.
- Publish only once the round comes back clean (as a web Work — see Deliver). Then hand over the Work
  card via `interaction-components` (target `{ "type": "work", "workId": "<preview.workId>" }` — see Deliver), plus verification result and any `reference` fallback you had to
  take — in the user's language.

### A. Layered static card (RuiC foil + optional lenticular A/B)
1. Write a job file (see `references/job.example.json`). Paths are relative to the job file.
2. `python <skill>/scripts/build_card.py job.json <new-folder>` (Pillow + NumPy). Validates ratio, alpha and sizes, derives line art, writes WebP/JPEG, copies `templates/site/`, writes `card-config.json` and `build-report.json`. Refuses a non-empty output folder.
3. Serve the folder over http:// (any static server) and look at it.
4. `node <skill>/scripts/verify.mjs <folder>` (needs Playwright; set `CHROME_PATH` to use an installed Chrome). Checks: load, pack keyframes when `pack.enabled`, tilt parallax, A/B switch, reveal flash, flip, 390 px overflow, console errors / failed requests. Screenshots + `report.json` go to `<folder>/verification/`. A green report proves the controls, not the look: open the screenshots.
5. Run the Step N approval gate, then publish only the site folder (exclude `verification/`, `build-report.json`). Deliver URL, folder, verification result, and known limitations.

### B. Video card (looping video in a framed window)
1. Job file: `{ title, subtitle, collection, edition, finish, rarity, video: <portrait mp4/webm>, poster?, text?, back?, loop?{start,end}, pack? }`.
2. `python <skill>/scripts/build_video_card.py job.json <new-folder> [--max-height 1280] [--max-mb 2.5]`. Pre-checks before any paid work: >= 4s, portrait only (landscape refused — crop, never stretch), scene-cut score, loop-seam distance, watermark corner scan; warns and asks to continue. Then two-pass H.264 transcode (yuv420p, +faststart, no audio), poster from the mid-loop frame, optional text/back layers, scaffold from `templates/site/` with `app-video.js`.
3. Verify with the same `scripts/verify.mjs` — video checks run automatically: clock advances after reveal, canvas changes over time, payload < 4MB, pack reveal starts the video.
4. Video card rules (enforced in `video-card-core.js`): muted loop; `cover` fit into an 84% window (crop, never stretch); poster plane hides the video's black pre-roll and returns on video error; video paused when the tab is hidden or the card scrolls out of view; foil capped low (default .35 — the video is the star); full card back (from `back` art) at z=-CARD_T.

### C. Multi-card booster (`build_booster.py`)
1. Job file: `{ preset, brand?, pack:{size, guaranteedLenticular, art, artSource, back, backSource}, cards:[{id,no,name,sub,rarity,b?,art,artSource,artB?,artBSource?,palette?,holdFx?,godRays?}], odds?, copy? }` (see `references/job.booster.example.json`). Start from a preset in `references/booster.presets.json` (`default`, or `09a` for the Veil/Brok look). Paths are relative to the job file.
   **Pool sizing — settle it before generating any art.** Keep `pack.size` 8 (pick grid and gallery are a fixed 4×2). A normal pack deals `size − guaranteedLenticular` non-lenticular cards plus the hits, so the pool needs **≥ 7 non-lenticular cards + the lenticular ones** (default 1 guaranteed); a god pack deals every lenticular card. Say this in the Step 0 roster question ("8 cards per pack; name fewer and I complete it from this world"). If the roster the user named is smaller than that, keep every named card and fill the rest with other real entries of this world (lower rarity than the named ones). Fill-ins are plain cards: only the awakening the user named gets a `b`. Generate the whole pool in the first batch (plan one or two spares for generation failures, but ship only the pool the pack needs), and list the fill-ins in the Step N approval round and the hand-over. Never shrink `pack.size` or patch the engine to fit a short roster. `build_booster.py` refuses a pool that is too small.
2. `python <skill>/scripts/build_booster.py job.json <new-folder> [--max-mb 2.5] [--max-width 1600]`. Validates every card is 2:3 (+`_b` for lenticular), the pack art ~4:5, >= 1 lenticular card, >= `guaranteedLenticular`; **refuses any face / `artB` / `pack.art` / `pack.back` marked `artSource: "reference"` unless `--allow-reference` is passed** (every asset must be generated this run); rejects CJK text; copies art verbatim when it already fits (never stretches), otherwise transcodes to WebP; scaffolds `templates/booster/`; writes `booster-config.json` + `build-report.json` (with per-asset `source`). Refuses a non-empty output folder (or rebuild in place with `--in-place`).
3. Serve the folder over http:// and look at it (invite -> tear -> pick -> reveal -> gallery).
4. `node <skill>/scripts/verify.mjs <folder>` — auto-detects `window.__booster` and runs the booster suite (tear/pick/reveal/gallery, god pack, gilded, reveal-all, a real-time auto run, 390 px overflow, console errors, payload < 2.5 MB).
5. Run the Step N approval gate, then publish only the site folder (exclude `verification/`, `build-report.json`).

The engine + its data contract are documented in `references/booster.md`. Re-extracting a newer app engine
is `scripts/sync_from_app.py`; packaging/hashing is `scripts/manifest.py`; the whole update chain is
`references/sync.md`.

## Defaults and tuning (`card-config.json`)
- `parameters`: subjectScale 1.0, subjectDepth 0.28, backgroundDepth −0.2, effectsDepth 0.14, foil 0.55. Keep layer depths clearly different or the card reads flat.
- `lenticular`: flipAngle 18°, softness 0.3, pitch 120, stripe 0.22, sweep 4, refract 0.3, depth 0.03, ridge 0.5. No sparkle particles on the lens (removed by design).
- `reveal`: `settle` "a" (card returns to A at rest, B on tilt) or "b" (stays awakened); `holdMs` 1600; `autoplay` true.
- `rarity`: common / rare / epic / legendary (see `fx.js` RARITY: which modules fire, palette, flash strength).

### Booster defaults (`booster-config.json`)
- Rarity tiers: `common` (plain, no burn) / `rare` (silver, short burn) / `epic` (gold) / `legendary` (pearl, tear + god rays). Foil/holo/glow/dim, halo aura and `beat` are per tier.
- Pack: 8 cards, exactly 1 guaranteed lenticular, ~7% god pack (pity within 10), every card ~7% gilded; progress in localStorage (QA runs never persist).
- Backdrops: `neutral` + per-rarity + per-legendary palettes; `hot` drives the burn edge / rim / god ray colour.
- All of the above is editable in `references/booster.presets.json` or overridden in the job — never in `booster.js`.

## Quality gates
- **Card text is English (Latin script) only.** Never render Chinese or any CJK characters on the card, the text layer, or generated UI — not even when the user asks. Applies to title, subtitle, collection, edition, flavor text and every label. Latin punctuation and diacritics are fine.
- Face must stay readable: foil ≤ 0.65 on portraits; no glitter over eyes.
- A and B must register (face and silhouette in the same place); otherwise the flip reads as a cross-fade of two different pictures.
- Total page payload target < 2.5 MB (static / booster; form 4's vendored three+postprocessing is ~1.05 MB of that); video card < 4 MB. No PNG over 400 KB except line art.
- No console errors, no 404s, no overflow at 390 px, loading text visible immediately, readable error when WebGL is missing.
- **Art provenance**: form 4's card faces, `artB`, `pack.art` and `pack.back` are all `artSource: "generated"` this run unless the user's Step 0 answer chose reuse for them (then `reference` + `--allow-reference`, declared); `build_booster.py` refuses `reference` otherwise. A world cover standing in as a card face without that answer is a failed build; in forms 1–3 a world cover may seed a generation, never be the finished layer.
- **Approval**: the Step N approval round is answered before publish; only the rejected pieces are regenerated, in place; any `reference` fallback or policy-blocked generation is declared in the hand-over.
- Booster: tear → deal → pick → reveal → gallery must complete with no errors; `?seed=` must be deterministic (same seed → same pack); the god pack must carry every lenticular card and must not be spoiled by the pick glow; gilded cards fire their own beat; the pack must not overflow at 390 px.
- Pack opening (v3, ~6.1s total, asserted by `verify.mjs` via `pack.info()`/`pack.beats`): **invite** — sealed pack floats/breathes, a dashed tear line pulses (`mode: 'interactive'`, the default) or a timer fires it (`mode: 'auto'`, for hands-free/video-recorded showcases). **tear** — dragging the top seal peels it along a jagged edge with light leaking from the gap and a rip sound that follows drag speed; releasing past halfway or a quick tap finishes it, releasing early springs it back shut. **tease** (~1s) — pack trembles, seam light grows; epic/legendary can fake out silver-then-gold. **draw** (~1.2s) — card back slides out slowly as the pack drops away. **hold** (~0.8s, or tap to skip) — card back fills the frame, trembling, edge glow rising. **flip** (~0.8s) — brief slow motion at edge-on, then burst + reveal. **settle** — eases into the normal showcase idle. Tap anywhere during tease/draw to fast-forward, or during hold to flip immediately.
- Pack art (`pack.art`): RGBA ~4:5 pouch front with crimped top/bottom seals; the procedural wrapper is only a fallback. `pack.mode` (`interactive` default | `auto`), `pack.tearLine`, `pack.hintText`, `pack.autoTearAfterMs` (interactive fallback auto-tear if the visitor never touches it) are optional job fields.
- Audio is muted for automatic playback; user click unlocks tick/whoosh/impact audio.

See `references/gates.md` (Step 0 brief round + Step N approval round), `references/workflow.md` (forms 1–3 architecture), `references/booster.md` (form 4 engine + data contract),
`references/sync.md` (app → skill → neta_skills update chain), and `templates/site/THIRD_PARTY_NOTICES.txt` for credits.