---
name: cd-case
description: Build an interactive 3D CD jewel case with a hinged lid, four disc designs and a spinning disc, with a slot-filled meta prompt, a runnable sample, headless checks and one HTML file.
version: 0.1.0
---

# CD case

## Example

Open `results/four-discs/index.html` in a desktop browser. A clear plastic jewel case floats on a white studio backdrop, tilted so its thickness shows, with a black disc covered in holographic stickers behind the lid. Click the case: the lid swings open on its left hinge and lies flat beside the tray, and the pair stays centred. Drag to turn the case; past a quarter turn the silver data side shows rainbow streaks through the clear back, and on release the case coasts and springs back to its resting tilt. The four round thumbnails in the toolbar (or keys 1 to 4) slide in the other discs on a vertical carousel: gold with fine engraved line diagrams, a deep red rose, a glossy liquid swirl. The disc button (or S) spins the disc inside the case, the case button (or O, Space) opens and closes it, and the pause button (or P) stops the autoplay, which otherwise shows each disc for 7 seconds and opens the case once in between. Sound is synthesized after your first click; the button at the top right mutes it.

- The file is one self-contained HTML page with no network access and no storage, built from `assets/four-discs/`.
- `results/four-discs/report.en.html` and `report.zh-CN.html` hold its headless verification record (console errors, CSP violations, interaction proofs, design distinctness, the phone embed layout, reduced motion, audio state, frame rate).

## When to use

- Someone wants a product-object showcase: one physical object built in code, a few variants to switch between, a hinge or another moving part, and a small toolbar.
- You need one self-contained HTML file that runs inside a strict sandbox (no network, no storage) and also works as a small square embed on phones.

When not to use: scenes that need model files or photo textures, physics simulation, many objects at once or server-side rendering. This Skill calls no model; an agent does the work locally.

## Inputs (slots)

Required: `case`, `designs`. Optional, with defaults: `materials`, `background`, `interaction`, `ui`, `extraPrompt`. Every value is a string of at most 2000 characters. `references/prompt-slots.md` explains each slot; `assets/prompts/examples/four-discs.json` is the slots file of the example.

## Workflow

```
node <skill>/scripts/fill-prompt.mjs --slots slots.json --lang en --out prompt.md
node <skill>/scripts/new-project.mjs --out my-case
cd my-case && npm ci
npm run dev                      # live preview; stop it with Ctrl+C
npm run build:single             # → dist-single/index.html
npm run shoot -- --file dist-single/index.html   # needs local Chrome
```

1. Fill the slots and render the meta prompt (`fill-prompt`). Give the prompt and any reference images to the coding agent.
2. Start from the reference app (`new-project`). It already has the case geometry (tray, rails, hub, hinged lid), the clear plastic and disc materials, the four procedural labels, the carousel, the pose and lid simulation, the framing that keeps the case above the toolbar, the HUD, synthesized sound, the `window.__scene` capture API and the single-file build. Change `src/case/geometry.ts` for another object and `src/case/labels.ts` for other designs.
3. Between `dev` and `build:single`, iterate: read `references/lessons.md` before changing the materials or the HUD, and run `shoot` after each round to compare its screenshots (closed, open, turned, each design, the 345×345 embed) with the reference.
4. Deliver `dist-single/index.html` once `shoot` passes. `references/verification.md` describes every check; `references/hosting.md` the sandbox it must pass.

## Language

`fill-prompt --lang en|zh-CN` picks the prompt language (`zh` is accepted; anything else falls back to English). The scene UI follows `?lang=en|zh-CN` (default English). The CLIs print English by default; `new-project` and `fill-prompt` take `--lang`, and the app scripts (`notices`, `inline-build`, `shoot`, `record`) take `--lang` or the `SCENE_LANG` environment variable.

## Keys

None. Nothing in this Skill calls a model or a network service. `npm ci` downloads the pinned packages from the public npm registry.

## Failure boundaries

- The case is a single rigid object with one hinge; there is no collision, the disc cannot be taken out and the lid holds no booklet.
- A tap opens the lid when it lands inside the case's screen box, so a tap just beside a turned case can still count.
- The low tier (phones) draws the plastic as a transparent sheet instead of physical transmission, has no MSAA and smaller label textures; phones are verified under headless touch emulation only, not on devices.
- With `prefers-reduced-motion: reduce` the scene starts paused, swaps designs with a cut and opens the lid in a short ease; capture mode ignores the setting.
- `shoot` and `record` need Node 22.4 or newer and a local Chrome; `record` also needs ffmpeg. Headless capture has no audio, so listening is a manual check.

## FAQ

- **Does it need an API key?** No.
- **Why one HTML file?** It can be opened from disk, hosted anywhere and embedded in a sandboxed iframe without a server.
- **Are the disc designs images?** No. Each label is drawn on a canvas in `src/case/labels.ts` (seeded shapes, gradients and line work), and the toolbar thumbnails are made from the same canvases.
- **Can I show another object?** Yes: replace the parts in `src/case/geometry.ts`, keep the subject group named `case` (the layout check and the tap test read its screen box) and update the framing sizes in `fit()` in `src/components/Stage.tsx`.
- **How do I add a design?** Add a drawing function in `src/case/labels.ts`, its finish in `src/case/materials.ts`, its id in `src/config.ts` and its name in `src/i18n/strings.json`; `shoot` then expects it to differ from every other design.
- **Why is there no drei `Environment`?** It ships a CDN preset table; the sandbox blocks network access. The studio light is a few panels in a private scene, prefiltered in code.
- **Where are the third-party licences?** `npm run build:single` embeds `THIRD_PARTY_NOTICES.txt` as an HTML comment in the file's `<head>`; never strip it.
