---
name: stamp
description: Make a narrated vertical reel (1080x1920, 45-60 s) in the "Stamp" format - a fast motion-graphics escalation story ("You want X. So you do A, and it works, but... So you do B, but...") where every spoken "but" gets a red rubber BUT stamp slammed onto the screen. Glossy magenta app windows on a near-black grid with a magenta stage beam, hard flips to an off-white grid, and one window per beat that keeps evolving on the words: counters roll, cards whip in and float, a cursor clicks and drags, wires draw and carry pulses, big numbers snap into a selection box with a burst and confetti. Word-by-word captions at the top with magenta selection boxes and emoji. A quick male ElevenLabs voice, a bass-heavy 105 bpm bed and ~300 sound cues (ticks, pops, whooshes, stamp slams). Drawn frame by frame in headless Chrome, encoded with ffmpeg. Use it when someone asks for a stamp reel, "stamp.md", a "but" reel, an escalation / "so you... but" explainer, a reel in the style of fast magenta UI motion graphics, or wants to turn a script into this kind of video.
---

# Stamp reel: how to make one

You will make a **45-60 second vertical video (1080x1920, 30 fps, H.264 + AAC, at most 60 s and 60 MB)** that tells **one escalation chain**. It opens on a dream with a number ("You want to get a million views a month with your Claude reels"). Then come three or four "**So you** … **and** it works, **but** …" steps; **every "but" slams a red rubber BUT stamp** onto the card. A turn follows, then the payoff that mirrors the hook, then the ask. The picture is **fast, glossy app UI in one magenta**, drawn from HTML/CSS/SVG (no images, no footage, no real brands). It never sits still: **something reacts to almost every spoken word.**

**`reference.jpg`** (next to this file, if you have it) is a contact sheet of 12 frames from a finished Stamp reel. **This is what it should look like.** Left to right, top row first:
1. **The hook:** a giant glowing magenta "1,000,000" in a selection box with round handles and orbiting rings, a chip "👁 views · this month", and a dark "reels.studio" app window with four pink reel tiles and a Claude prompt bar. A tilted sticker "made with Claude".
2. A dark prompt bar "make tonight's reel", a reply "✓ Reel 12 posted", and a calendar "October · reels posted" with every day checked in magenta. A "🔥 30-night streak" chip and a big "30 nights" in a selection box.
3. **Light:** the reels window with a **red BUT stamp** over the tiles, and a second card "Your last 6 reels" (six identical tiles, a red "all look the same", "= same hook" chips).
4. A "trending" card with a STOP SCROLLING reel and "2.2M views", little avatars floating around it, a copy on the right with "✓ hook / cuts / captions / music" chips flying in, a "🔥 going viral" sticker and a cursor.
5. **Light:** "Insights · views" (48,910 / +1,204 / 71 %, a climbing line, "📈 +3,700 %") stamped BUT, with an "Editing · reel 14" timer card at 4:12:29 in red.
6. A "skills.market" page: a VIRAL REELS SKILL hero, stars, feature checks, a red "$30" price tag that swings, a BUT stamp with a red glow, "Buy now · $30.00" and a cursor.
7. **Light:** a Downloads row "viral-reels-skill.zip · $30.00 paid" and one small "SKILL.md · 4 KB" text file dropped below it in a selection box, with a sticker "1 file · 4 KB".
8. A "Casper 👋" pill over a "Morphy" window whose canvas nodes (Chat, Agent ● running, Card, Note) are joined by glowing wires with pulses, a sticker "agentic super app", chips "🤖 agents · 💬 chats".
9. **Light:** a "my-format" card ($0, "✓ Published · 1,284") stamped BUT, avatars around it, and a "skills.market" card "ULTIMATE VIRAL PACK · repackaged · $30 · Sold: 87" with two "New sale · $30.00" notifications.
10. A "Content formats" library window with 16 `.md` files (stamp.md glowing) inside a magenta "Morphy" frame, with a sticker "inside Morphy".
11. Three cards ("Unlock this skill $30", "Pro · monthly $9.99/mo", "Follow + share · strings"), each banned by a glowing red circle-slash and cracked.
12. **The ask:** a big glowing comment box with "STAMP" in a selection box, a "✓ stamp.md" chip, and a paper plane flying along a dashed line to "Messages".

The caption at the top of every frame shows the current phrase, its key word boxed. If you only have this file, section 3 describes everything with numbers.

Everything is drawn **one frame at a time** as HTML + SVG in headless Chrome (`window.setT(t)`), then muxed with a mix made in ffmpeg. This file gives you the look (exact colours, sizes, motion), the grammar of the script and of the edit, the complete tested engine (Appendix A: write it once) and a complete example video (Appendix B). With the engine written, **a video is two files**: `script.mjs` (what the voice says and the captions show) and `scenes.js` (what each scene shows, keyed to the words).

Work in this order and don't skip ahead: **setup → script → fake timings → scenes → stills → voice → real timings → bed → fix the scenes → 30-40 stills on the key words + scan → full render → mix → encode → check → deliver.**

---

## 0. Rules of engagement (read first)

1. **Be gentle with the user's computer.** One headless browser at a time, at the lowest priority (`cdp.mjs` does this; on Windows it also pins Chrome to one CPU). Never run two renders at once.
2. **Never print secrets.** The key is `ELEVENLABS_API_KEY` (environment, or `.env` in the project folder). Scripts read it; you never echo it, log it or put it in a file you show.
3. **Spend credits deliberately.** `gen-vo.mjs`, `gen-sfx.mjs` and `bed.mjs --eleven` are dry runs unless you pass `--yes`. The voice is one take per section (~1 credit per character, ~930 for a 55 s reel); takes are cached. The sounds and the bed work without any credits (synthesized).
4. **Stills before film.** Direct on fake timings (`build-timeline.mjs --fake`) and check stills before you pay for the voice; never start the full render until a dense stills sheet (30-40 frames on the key words) is right.
5. **Background jobs die when your turn ends.** Render in foreground chunks under your tool timeout (`--full --to 600`, then `--full --from 600`); the render resumes where it stopped.
6. **No real people, brands or numbers you can't stand behind.** Everything is drawn: neutral person silhouettes, made-up app names (reels.studio, skills.market), no screenshots, logos, real handles or real view counts. A number on screen is one the script says, or a plain counter the scene implies.
7. **Don't publish anything.** You deliver files. Posting is the user's call.
8. If the user's instructions conflict with this file, the user wins. If something is missing (no key, no ffmpeg), say exactly what and offer the closest thing. Never fake success.

## 1. Requirements

- **Node 22+** (global `fetch` and `WebSocket`), **ffmpeg + ffprobe** on PATH, **Chrome or Edge** (or set `CHROME=<path>`). Debug ports 9671 and 9698 on 127.0.0.1. No npm packages.
- **An ElevenLabs API key** for the voice. The voice is **Liam**, a premade voice every account has. The free plan is enough to try the format but has no commercial licence; videos that promote anything need a paid plan. No key yet: do everything up to the stills with `--fake` timings and ask for the key.
- **Fonts:** Inter Tight and Archivo Black (SIL OFL) are free downloads (`setup.mjs` fetches them). Arial Black (Windows, macOS) is preferred for the stamp. Emoji come from the system: Segoe UI Emoji on Windows (which has **no Emoji 13+** such as 🪢 or 🫠, so use older ones), Apple Color Emoji on a Mac, Noto Color Emoji on Linux.
- **Sound:** the 22 sound effects (`sounds.mjs`) and the music bed (`bed.mjs`, made per video to its length) are synthesized, so there are no licences and no credits. Optional: `gen-sfx.mjs --yes` and `bed.mjs --eleven --yes` make them with ElevenLabs instead, closer to the reference.
- Optional: Python + `pip install faster-whisper` lets `check.mjs --whisper` compare the finished audio with the script.

## 2. Project layout and setup

```
stamp-reel/                      one project folder, many videos
  .env                           ELEVENLABS_API_KEY=...   (never print it)
  film.html                      the page: timeline.js -> engine -> scenes.js -> window.setT(t)
  engine/core.js parts.js stamp.js fx.js film.css markup.mjs   clock + looks + scenes/camera + captions + safe zone · the UI kit · Stamp's parts · motion + effects · the look · script markup + voice
  setup.mjs sounds.mjs gen-sfx.mjs bed.mjs cdp.mjs gen-vo.mjs build-timeline.mjs render.mjs check.mjs whisper.py
  fonts/ sfx/                    made by setup.mjs
  my-video/                      one folder per video (Appendix B is a complete one)
    script.mjs scenes.js         what the voice says · what each scene shows
    vo/ timeline.js(on) vo.wav bed.wav cues.json stills/ frames/ mix.wav video.mp4 cover.jpg   (made by the tools)
```

```bash
mkdir stamp-reel && cd stamp-reel
# write every file of Appendix A, then:
node setup.mjs                   # checks Node/ffmpeg/Chrome/key, downloads the fonts, makes sfx/
```

A video (every tool takes `--video <folder>`, a folder inside the project):

```bash
mkdir my-video                   # write my-video/script.mjs (section 4) and my-video/scenes.js (Appendix B shows both)
node build-timeline.mjs --video my-video --fake                     # guessed timings + a silent vo.wav: direct before you pay
node render.mjs --video my-video --stills --at beats --safe --sheet sheet   # look at my-video/stills/sheet.jpg
node gen-vo.mjs --video my-video                                    # dry run: the text of every take + the character count
node gen-vo.mjs --video my-video --yes                              # one take per section -> my-video/vo/
node build-timeline.mjs --video my-video                            # real timings: prints every word with its time
node bed.mjs --video my-video                                       # the 105 bpm beat, cut to this length
node render.mjs --video my-video --scan                             # every visible text, every 0.25 s, against the safe zone
node render.mjs --video my-video --stills --at 1.6,4.4,5.6,7.5,… --sheet words   # 30-40 stills on the key words
node render.mjs --video my-video --full --to 600 && node render.mjs --video my-video --full --from 600 --to 1200 && node render.mjs --video my-video --full --from 1200
node render.mjs --video my-video --mix                              # -14 LUFS; prints how far the bed sits under the voice
node render.mjs --video my-video --encode --out video.mp4 --cover 1.75   # my-video/video.mp4 + my-video/cover.jpg
node check.mjs --video my-video --whisper                           # specs, frame 0, 5 random frames with the safe box, words heard
```

## 3. The look, pinned down

### 3.1 The frame (1080x1920 px)

| Layer | Where | What |
|---|---|---|
| Background, dark | everywhere | `#0b0c0a`, a 72 px grid of 1.5 px lines at 5 % white, a soft vignette. On top: **the stage beam**, a trapezoid from y 380 (36 % wide) to the bottom (64 % wide), magenta at its top (`rgba(214,66,200,0.24)`) fading to 2.5 % white, blurred 10 px. Also a magenta hot spot behind the captions, three low haze pools, eight twinkling 4-point sparkles and drifting dust |
| Background, light | everywhere | `#f2f2ee` with the same grid in 6.5 % black. No beam |
| Captions | from y 270, centred on x 510, ≤ 820 px wide | word by word (3.4) |
| The scene | design space y 520-1480, x 40-1040 | the engine scales each scene by 0.92 around (540, 1000) and shifts it -30, -10, so it lands under the captions inside the safe zone |

**Safe zone (every platform covers the edges):** all text inside **x 60-960, y 250-1480**; captions inside x 100-920; nothing important in the top 250 px, the bottom 440 px or the right 120 px. `render.mjs --stills --safe` draws the zone and outlines any visible text outside it in red; `--scan` tests every text every 0.25 s. Whip and zoom transitions may leave a blurred, moving text outside for one frame; anything static outside is a bug (usually a camera pan with scale > 1.03).

### 3.2 Colours

| Token | Hex | Used for |
|---|---|---|
| magenta | `#c42fb8`, light `#e24fd6`, mid `#ec7fe3`, pale `#fbe6f9`, pink `#f070e4` | every accent: buttons and chips (gradient `#e24fd6 → #b0219f`), checks, charts, the big numbers (`#ff8af2 → #e24fd6 → #c42fb8` with a magenta glow), selection boxes, wires |
| stamp red | `#e5283a` (`#ff4b5c → #c81e30` on tags) | the BUT stamp, ban circles, prices, failures, cracks |
| ink | `#f3f3f1` on dark, `#141518` on light | text |
| dark cards | `linear-gradient(#202126, #15161a)`, 1 px white 9 % border, radius 30, a deep shadow | windows on dark |
| light cards | white → `#f6f6f8`, a soft magenta-grey shadow, radius 30 | windows on light |
| Claude orange | `#d97757` | only for Claude's own spark |

### 3.3 Type

**Inter Tight** everywhere (600-900). **Cascadia Mono** / Consolas / monospace for file names, code lines, times and the footer lines. **Arial Black** (or Archivo Black) for the stamp. System emoji in captions and chips.

### 3.4 The captions

The Motion look, at the top. Each phrase (split at `/` in the script) shows **word by word**: a word fades in 0.1 s before it is spoken, rising 16 px and un-blurring. Earlier words are grey (`#8b8c8f` on dark, `#9a9b9e` on light); the current word is ink. **Key words** (`*word*`) sit in a selection box (`rgba(120,16,110,0.55)` fill on dark, `rgba(226,79,214,0.22)` on light, a 2.5 px magenta border, radius 3), with **round magenta handles** at the top-left and bottom-right corners; the box springs in. `@icon:word` puts an emoji before the word. 56 px, weight 620 (keys 700), line-height 1.24, two lines at most.

### 3.5 The stamp

`P.but(el, cx, cy, t0, { size: 150, rot: -7, text: 'BUT', light })`: **"BUT" in Arial Black, red `#e5283a`**, letter-spacing 0.02em, a 9 px border with radius 22 plus a 3.5 px outline 7 px outside it (the double rubber-stamp frame), and a **grunge mask** (420 tiny holes, 10 scratches, 6 worn patches). It enters 0.08 s before t0 at **2.1x with 10 px blur**, rotated 10° further, then lands in 0.12 s with a squash (8 %). On landing it throws **a red shockwave ring** (0.45 s) and **ten ink flecks**, shakes the camera (11 px, 0.3 s) and plays `stampslam` + `boom` (and a `whoosh` 0.16 s before). On light it multiplies onto the card; on dark it gets a red glow and a dark wash. Put it **over the thing that just failed**; it stays until the scene ends, and the scene keeps moving around it.

### 3.6 The vocabulary (section 7.3 lists the options)

- **App windows** (`P.mac`): three dots on the left (red, pink and violet), the app's icon (a magenta rounded square with ✦) and its name centred in a 74 px header, and an optional status chip on the right. Inside: a sidebar, reel tiles, a composer, stats, charts and lists.
- **Big numbers** (`P.bigNum`): 150-180 px, weight 850, gradient + glow, tabular digits. They count up, and at the hit a selection box springs around them with orbiting rings, a radial burst and an ellipse shockwave.
- **Stickers** (`P.sticker`): a 76 px magenta gradient pill with a white 3 px border, 38 px bold text, slammed in tilted (-6° … 6°).
- **Chips** (`P.chip`): 40 px pills, pale (light), translucent magenta (`dk`), red (`rd`, `rdk`) and solid gradient (`on`).
- **Reel tiles** (`P.reel`): vertical pink-magenta gradient tiles with a two-line caps hook (the second line in a black box) and a neutral person circle.
- **Others:** calendars (`P.cal`, days tick in magenta), line charts that draw left to right with a dot (`P.chart`), stat boxes, list rows, phone notifications, a plain text file (`P.file`), price tags that swing, a receipt that prints, chat bubbles, a comment box.
- **Motion and effects:** whip-slides with motion blur, zoom-ins and blur dissolves between scenes. Also a **camera rig per scene** (push-ins, punches on hits, pans to the card that just arrived, a constant tiny drift), **idle float** on every card, and connector **wires** that draw and carry glowing pulses. Then bursts, shockwaves, confetti, gloss sweeps, red cracks, floating avatars, a loading shimmer, and a cursor that clicks or drags with someone else's labelled cursor.

### 3.7 Motion

| Move | How |
|---|---|
| between scenes | `in: 'up'` (a whip from below, 1500 px, 26 px blur, a vertical stretch) for most dark → light flips, `'zoom'` (from 50 %, blurred) for "look closer" beats, `'blur'` (a soft dissolve) otherwise; always with a `whoosh` or `swoosh` |
| inside a scene | elements enter on their word with a spring (`pop`), a whip (`left/right/up/down`), a `slam` (from 1.7-2.2x), a `drop` (falls and bounces) or a `zoom`, then **float** (a 5 px bob and a 0.4° sway); the camera pushes 3-7 % and punches on hits |
| a number | counts on a curve while the voice says it; ticks under it; at the hit: box, burst, shockwave, sometimes confetti, a sub hit |
| a "but" | the stamp (3.5) |
| a list ("No X, no Y, no Z") | one card per item whips in on its "no", a ban circle slams on its word, the card cracks and greys out |
| the ask | a small comment field grows into a big glowing box on "comment", the keyword types in, a selection box snaps on, then the deliverable chip rises and a paper plane flies to Messages on "send it to you" |

## 4. Writing for this format

**The chain** (the reference's and the example's), ~170 words ≈ 55 s:
- **s0, the hook:** a dream with a number: "You want to get / @money:*a_million_views* a month / with your Claude reels."
- **3-4 steps**, each one sentence: "**So you** <do the obvious fix>, **and** <it works a bit>, **but** <a new problem>." For example: "So you prompt Claude for a new reel *every_night,* / and it actually works, / but they all look the *same.*"
- **The turn** (who is talking, and why this matters), **the answer**, then **the payoff that mirrors the hook**: "And now you're getting a million views, and you never pay for a Claude content skill again."
- **The ask:** "This format is called *stamp.md.* / So comment *STAMP* and I'll send it to you. / Completely *free.*"

**Markup:** `/` starts a new caption phrase (2-7 words) · `*word*` boxes a key word (`*two_words*` makes one box, one per phrase) · `@icon:word` puts an emoji before a word (define `F.ICONS.money = () => '💸'` in scenes.js) · `{shown|said}` when the caption and the voice differ (`{$30,|thirty dollars,}`, `{.md|dot M D}`, `{STAMP|stamp}`). Say numbers as words in the voice.

## 5. The voice (ElevenLabs)

**Liam** (`TX3LPaxmHKxFdv7VOQHJ`, premade), `eleven_multilingual_v2`, stability 0.4 · similarity 0.8 · style 0.5 · speaker boost · **speed 1.1**: a young American male, quick and bright. The reference is a male narrator at ~127 Hz and ~200 wpm; Liam measures ~128 Hz and ~198 wpm here. These are the `engine/markup.mjs` DEFAULTS; a `script.mjs` may export `VOICE`, `MODEL`, `SETTINGS` or `SPEED` to override them. One take per section, word times from ElevenLabs' character alignment. `GAP` (silence before a section) 0.22-0.3 s, `HOLD` 1.8 s. Whisper will hear "thirty dollars" as "30", ".md" as "md", "Claude" as "clawed" and names loosely: judge those by ear.

## 6. Timings

`build-timeline.mjs` lays the takes end to end (LEAD before the first, GAP before each section, HOLD after the last) and writes `timeline.js` (every caption token with its `t0`/`t1` on the film clock) and `vo.wav`. Markup-only edits (where `/` and `*` fall) need no new voice: it re-applies them to the takes. `--fake` guesses times at Liam's pace (~200 wpm) so you can direct before the voice exists. Every scene time comes from a word, so the real timeline moves every scene automatically. Run `bed.mjs` again after any timing change.

## 7. Directing

### 7.1 Times come from words

`T0('s1', 'every night')` is the start of the first token in section s1 whose text starts with "every night" (case and punctuation blind), `T1(…)` its end, `T0('s5', 'no', 2)` the third "No". `S.s3.start` / `S.s3.until` are a section's bounds; `END` is the film's length. A scene starts 0.03-0.16 s before its word: `const tB2 = T0('s1', 'and') - 0.03`. **Check that a beat's payoff lands before its scene cuts** (a payoff on the last word of a sentence can fall after the next scene's start).

### 7.2 scenes.js

```js
const { S, T0, T1, END, scene, cue, looks, div, html, set, clamp, lerp, eio, eo, back, typeIn, centerX } = F;
const P = F.parts, M = P.M, E = F.enter, tw = F.tw;
Object.assign(F.ICONS, { money: () => '💸' });
const tB = S.s1.start - 0.12, tB2 = T0('s1', 'and') - 0.03;
looks([[0, 'dark'], [tB2, 'light']]);                        // [time, 'dark' | 'light', fade s]: one flip per beat or two
{
  const sc = scene(tB2, S.s2.start - 0.12, { in: 'up' }), el = sc.el;   // el is the scene's camera rig
  const tW = T0('s1', 'works'), tBut = T0('s1', 'but'), tThey = T0('s1', 'they');
  F.cam(sc, [[tB2, 1, 0, 0], [tBut, 1.02, 0, 0], [tThey + 0.1, 1.04, 0, -70]]);   // [t, scale, x, y, rot], eased
  const win = P.mac(el, 80, 560, 840, 520, { light: true, title: 'reels.studio' });
  const live = P.chip(win, 640, 17, '● Posted ✓', 'on', { height: '40px' });
  const card = div(el, 'lcard', 360, 950, 620, 450, '<b>Your last 6 reels</b>');
  const but = P.but(el, 520, 760, tBut, { size: 150, rot: -8, light: true });   // plays its own sounds + shake
  sc.on(t => {
    E(live, t, tW - 0.06, { from: 'pop', float: 0 });
    but.update(t);
    E(card, t, tThey - 0.08, { from: 'up', dist: 520, spin: 4, seed: 8 });      // whips in, then floats
  });
  cue('uipop', tW - 0.05, 0.08); cue('swoosh', tThey - 0.1, 0.06);
}
```

- A scene is shown from `tin` to `tout` (plus its exit). Make each end where the next begins. `scene(tin, tout, { in, out, push })`: in `'blur' | 'up' | 'down' | 'left' | 'right' | 'zoom'`, out `'blur' | 'zoom' | …`, push (default 0.03: a slow zoom over the scene).
- Draw in **design coordinates** (1080x1920, content inside x 40-1040, y 520-1480) on `sc.el`; the engine fits the scene into the safe zone. Animate per frame in `sc.on(t => …)`. `F.enter` sets an element's transform every frame; to add a bounce or swing, append to `el.style.transform` after it.
- **Keep the camera's x near 0 when its scale is above 1.03**, or the window's left text leaves the safe zone.

### 7.3 What the kit gives you

| Part | Options and use |
|---|---|
| `F.enter(el, t, t0, { from, d, dist, s0, rot, spin, blur, float, seed, t1, d1, to, x, y })` | every element's entry, idle float and exit. from `'pop' 'up' 'down' 'left' 'right' 'slam' 'drop' 'zoom' 'fade'`; to `'fade' 'pop' 'up' …` at t1 |
| `F.cam(sc, keys)` · `F.shake(t, amp, dur)` · `F.tw(t, t0, t1, ease)` · `F.swing(t, t0, amp, freq, damp)` | the scene camera · a camera shake · a 0-1 progress · a hit tag's damped swing (degrees) |
| `P.mac(el, x, y, w, h, { light, title, glyph, hh })` | an app window |
| `P.but(el, cx, cy, t0, { size, rot, text, light })` → `{ update(t) }` · `P.ban(el, cx, cy, r, t0)` → `{ update(t) }` | the stamp · a red circle-slash with a flare (both play their sounds) |
| `P.bigNum(el, cx, y, { size, light })` → `{ el, set(s), update(t, tHit) }` · `F.num(v)` · `F.money(v)` | the giant number and its hit box |
| `P.chip(el, x, y, html, '' \| 'dk' \| 'rd' \| 'rdk' \| 'on', style)` · `P.sticker(el, x, y, html)` · `P.ICON(size, glyph)` | chips · stickers · the app icon |
| `P.reel(el, x, y, w, h, { title: [line1, line2], hue: 0-5 })` | a reel tile |
| `P.cal(el, x, y, { cell, gap, title, light })` → `{ el, update(n) }` · `P.chart(el, x, y, w, h, { pts, light })` → `{ update(u), at(u) }` · `P.stat(el, x, y, w, label, value, { light, mag, red })` → `{ v }` · `P.row` · `P.notif(el, x, y, w, { app, text, time, light, warn })` · `P.file(el, x, y, w, h, { name, size, lines, light })` → `{ el, lines }` | the UI pieces |
| `P.composer(el, x, y, w, h, { light, card, lead: 'claude' \| null, placeholder })` → `{ el, type(t, text, t0, t1), press(t, at), hint(s) }` · `P.bubble(el, y, text, { right, size })` · `P.pill(el, x, y, html, 'bl' \| 'dk' \| 'lt' \| 'rd', style)` | the prompt bar, a chat bubble, a button (from the Motion kit, recoloured) |
| `P.wire(el, d, { t0, dur, width, dash, pulses, speed, w, hh, color })` → `{ el, path, update(t) }` | an SVG connector that draws itself, then carries pulses. A rounded-rect path draws a window's frame |
| `P.burst(el, cx, cy, t0, { n, r0, r1, rx, ry, len })` · `P.shock(el, cx, cy, t0, { r, rx, ry, color })` · `P.confetti(el, cx, cy, t0, { n, spread, up })` · `P.shine(host)` → `{ update(t, t0, dur) }` · `P.crack(el, x, y, w, h, t0, { seed, n })` | hit lines · ring · paper bits · gloss sweep · red cracks (each has `update(t)`) |
| `P.orbiters(el, cx, cy, n, { rx, ry, t0, s })` · `P.skel(host, x, y, w, h)` → `{ update(t, tDone) }` · `P.vib(el, cx, cy)` · `F.cursor(el)` / `P.cursor2(el, 'someone')` + `F.drive(cur, t, [[t, x, y]…], [clickTimes])` | floating avatars · loading shimmer · phone buzz · cursors |
| `div(parent, 'abs' \| 'gcard' \| 'lcard', x, y, w, h, html, style)` · `html(el, s)` · `typeIn(el, text, t, t0, t1, caret)` · `centerX(el, cx)` | your own elements |

### 7.4 The edit follows the words

Every spoken claim gets its literal picture at that moment. In the example:
- "a million views" → the counter hits 1,000,000.
- "every night" → the calendar fills to 30.
- "actually works" → gloss sweeps and "Posted ✓".
- "but they all look the same" → BUT, then six identical tiles drop in.
- "copy a format" → a cursor clicks "Copy format" and the clone flies across.
- "views start climbing" → the chart draws up.
- "takes you hours" → a timer races to 4:12:37 and turns red.
- "it costs thirty dollars" → the tag slams and swings, and the cursor pays.
- "just one text file" → the zip drops a single 4 KB file.
- "I built Morphy" → a frame draws itself and nodes pop with wires.
- "people repackaged it" → someone's cursor drags the file into a "pack".
- "an entire library" → 16 files fly in.
- "no paywalls" → a ban circle.
- "a million views" → the hit with confetti.
- "you never pay" → "$30.00" rolls down to "$0.00".
- "comment STAMP" → the box grows and types it.

### 7.5 Motion rules (break these and it looks lazy or buggy)

- **Something reacts to almost every word.** A scene that only pops its cards in and holds them reads as lazy next to the reference: keep counters, typing, cursors, wires, chips and stickers coming on the words.
- Nothing appears before its word, nor later than its word.
- Vary the transitions (whip, zoom, blur) and give every scene a camera move. Punch in on hits.
- Elements float after they land; nothing is ever perfectly still. Inside a window, set `float: 0` so the content stays aligned.
- One BUT per "but". The stamp lands on the thing that failed and stays.

## 8. Stills and the safe zone

`render.mjs --stills --at <times | beats | spread:8> --safe --sheet sheet` writes `my-video/stills/<t>.jpg` and a contact sheet. `--safe` draws the zone and prints any visible text outside it. `--scan` tests every visible text every 0.25 s without screenshots. **Take 30-40 stills on the key words** (every "but", every hit, every scene's middle) before the full render. Then, after encoding, review the film as a 6 fps contact sheet (`ffmpeg -i my-video/video.mp4 -vf "fps=6,scale=216:384,tile=10x3" -frames:v 1 motion.jpg` for the first 5 s, with `-ss` for the rest): that is where missing motion shows.

## 9. The full render

`render.mjs --full` writes `my-video/frames/f00000.jpg …` (~150-450 ms a frame; a 55 s film takes ~5-12 minutes). It skips frames that exist, so split it (`--to 600`, `--from 600`) to stay under your tool timeout. After changing one scene, re-render just its frames: `--full --from <first> --to <last> --force`.

## 10. Sound

- **`sounds.mjs`** makes the 22 sounds, every one starting on its first sample and peaking at -1 dB: `stampslam boom bassdrop whoosh swoosh uipop uitick tick tick1 tick2 tick3 mouse send notif kaching error counter typing stopwatch cards riser sparkle`. **`gen-sfx.mjs --yes`** replaces them with ElevenLabs sound effects (its prompts are the reference set's), trimmed the same way. `sounds.mjs --force` brings the synthesized ones back.
- **`bed.mjs`** makes `my-video/bed.wav`: a 105 bpm dark trap beat. The 808 follows A-F-C-G, with a kick on 1 and 2.5 (plus 3.5 every other bar), snaps on 2 and 4, eighth hats with a triplet roll every other bar, and a quiet low pad. **`bed.mjs --eleven --yes`** asks ElevenLabs Music for the reference's kind of bed once per project, then loops 11 bars of its steady middle to the video's length.
- **`render.mjs --mix`** mixes the voice, the bed (ducked under the voice) and every `cue()`, fades the bed over the last 1.8 s, then brings it all to **-14 LUFS integrated, true peak ≤ -1 dBTP**. It prints how far the bed sits under the voice: aim for **~11-12 LU**. That is a little louder than usual, because the reference's bass bed is part of its feel. Set it with `--music` (0.23 is the default, right for the synthesized beat; ~0.1 for an ElevenLabs bed).
- **Cue a sound for nearly every UI event.** That is ~300 cues in 55 s; the reference's soundtrack is dense. Use `cue(name, t, gain, length)`:
  - every scene start: a `whoosh` or `swoosh` (0.05-0.07);
  - `uipop` on pops (0.06-0.09) and `uitick` on small changes (0.04-0.06);
  - `F.ticks(t0, t1, n, g)` for counters and fills; `counter` (0.04-0.06) under a long count;
  - `F.typeCue(t0, t1)` under typing; `mouse` on clicks (0.1-0.12); `send` on a send;
  - `cards` on slams and stickers; `kaching` on a price; `notif` on a notification;
  - `stopwatch` under a timer; `error` on a failure (≤ 0.05);
  - a `riser` 0.7 s into a big hit, then `bassdrop` / `boom` on it, plus a `sparkle` on highlights.
- `F.SFX_GAIN` (in `engine/fx.js`) trims the dense sounds for every cue (bassdrop 0.45, boom 0.65, uipop 0.6 …). The sound should never cover a word: if a spot gets busy, lower those cues.

## 11. Encode, check, deliver

`render.mjs --encode --out video.mp4 --cover 1.75` makes **H.264 High, yuv420p, 1080x1920, 30 fps, `-crf 20 -maxrate 10M -bufsize 20M`, AAC 192k, `+faststart`** (~15 MB for 55 s) and `cover.jpg` (the hook frame). `check.mjs --video my-video --whisper` checks:
- the specs, and that frame 0 isn't black;
- 5 random frames with the safe box (`my-video/check/frames.jpg`): look at it;
- the words heard against the script.

Deliver `video.mp4` and `cover.jpg`; posting is the user's call.

## 12. Checklist

- [ ] `setup.mjs` all ✓ · the script is one chain (section 4) · one "but" per step · ≤ 60 s
- [ ] fake-timing stills right before any credit is spent · `gen-vo.mjs` dry run read, then `--yes`
- [ ] `bed.mjs` run after the real timings
- [ ] something moves on almost every word · a BUT stamp on every "but" · every number counts · every scene has a camera move and a whoosh
- [ ] `--scan` clean (except one-frame whips) · 30-40 stills on the key words right · full render
- [ ] mix at -14 LUFS, bed ~11-12 LU under the voice, no sound covering a word
- [ ] encode ≤ 60 s and ≤ 60 MB · `check.mjs` all ok · frames.jpg and a 6 fps motion sheet looked at · every word checked (Whisper or by ear)

---

## Appendix A: the engine (write these files exactly)

Write every file below into the project folder at the path in its heading. They are the same for every video: never edit them per video.

### `film.html`

```html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>stamp</title>
<style>
@font-face { font-family: 'Inter Tight'; src: url(fonts/InterTight.ttf) format('truetype'); font-weight: 100 900; }
@font-face { font-family: 'Archivo Black'; src: url(fonts/ArchivoBlack-Regular.ttf) format('truetype'); }
</style>
<link rel="stylesheet" href="engine/film.css">
</head>
<body>
<div id="film"><div id="stage"></div></div>
<script>
  // film.html?v=<video folder> (default: video). The engine is the same for every video; the folder brings
  // timeline.js (from build-timeline.mjs) and scenes.js (the scene list). Flags: &nocap (no captions), &safe (safe-zone overlay).
  window.VIDEO_DIR = (new URLSearchParams(location.search).get('v') || 'video').replace(/\\/g, '/').replace(/\/$/, '');
  for (const s of [VIDEO_DIR + '/timeline.js', 'engine/core.js', 'engine/parts.js', 'engine/stamp.js', 'engine/fx.js', VIDEO_DIR + '/scenes.js'])
    document.write(`<script src="${s}"><\/script>`);
</script>
</body>
</html>
```

### `engine/core.js`

```js
// Stamp template · ENGINE CORE (grown from the Motion template: + the dark look's stage beam, the stamp grunge, a camera
// rig per scene and whip / zoom transitions). Same for every video: don't edit it while making one (a video's own scenes are
// <video>\scenes.js). One page, one clock: window.setT(t) draws film time t (s). Every beat is keyed to a spoken
// word from <video>\timeline.js via T0(section, word[, n]) / T1(...).
// Load order (film.html): <video>/timeline.js → core.js → art.js → parts.js → <video>/scenes.js → F.start() on DOMContentLoaded.
(() => {
  const F = window.F = {};
  F.errors = [];
  addEventListener('error', e => F.errors.push(String(e.message || e.error || e)));
  const Q = new URLSearchParams(location.search);
  F.DIR = window.VIDEO_DIR || 'video';
  F.NOCAP = Q.has('nocap'); F.SAFE = Q.has('safe');
  const film = document.getElementById('film'), stage = document.getElementById('stage');
  F.film = film; F.stage = stage;

  // ---------- maths ----------
  const TAU = Math.PI * 2;
  const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
  const lerp = (a, b, u) => a + (b - a) * u;
  const ease = x => { x = clamp(x); return x * x * (3 - 2 * x); };
  const eio = x => { x = clamp(x); return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; };
  const eo = x => 1 - Math.pow(1 - clamp(x), 3);
  const back = (x, c = 1.7) => { x = clamp(x); return 1 + (c + 1) * Math.pow(x - 1, 3) + c * Math.pow(x - 1, 2); };
  const px = v => v + 'px';
  const rng = seed => { let s = seed >>> 0; return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; };
  const esc = s => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;');
  Object.assign(F, { TAU, clamp, lerp, ease, eio, eo, back, px, rng, esc });

  // ---------- DOM ----------
  const h = (tag, attrs = {}, html) => {
    const e = document.createElement(tag);
    for (const [k, v] of Object.entries(attrs)) {
      if (k === 'style' && typeof v === 'object') Object.assign(e.style, v);
      else if (k === 'class') e.className = v;
      else e.setAttribute(k, v);
    }
    if (html !== undefined) e.innerHTML = html;
    return e;
  };
  const add = (p, ...k) => { k.forEach(x => p.appendChild(x)); return k[0]; };
  const box = (e, x, y, w, hh) => { if (x != null) e.style.left = px(x); if (y != null) e.style.top = px(y); if (w != null) e.style.width = px(w); if (hh != null) e.style.height = px(hh); return e; };
  // div(parent, classes, x, y, w, h, innerHTML, style) — absolutely placed when classes include abs/gcard/lcard/pill/…
  const div = (p, cls, x, y, w, hh, html, style) => { const e = add(p, h('div', { class: cls }, html)); box(e, x, y, w, hh); if (style) Object.assign(e.style, style); return e; };
  // set(el, opacity, transform, blur) — the one way to draw an element's state for a frame
  const set = (el, op, tr, bl = 0) => {
    el.style.opacity = op.toFixed(3);
    el.style.visibility = op < 0.004 ? 'hidden' : 'visible';
    if (tr != null) el.style.transform = tr;
    el.style.filter = bl > 0.3 ? `blur(${bl.toFixed(1)}px)` : 'none';
    return op;
  };
  // pop: spring in from scale s0 with overshoot and blur; optional exit at t1
  const pop = (el, t, t0, o = {}) => {
    const { d = 0.42, s0 = 0.55, blur = 12, t1 = null, d1 = 0.22, x = 0, y = 0, rot = 0, dy = 0 } = o;
    const a = clamp((t - t0) / d);
    let op = clamp(a * 2.4), sc = s0 + (1 - s0) * back(a), bl = (1 - eo(a)) * blur, yy = y + (1 - eo(a)) * dy;
    if (t1 != null) { const z = eo((t - t1) / d1); op *= 1 - z; sc *= 1 - 0.15 * z; bl += z * blur; }
    return set(el, op, `translate(${x}px, ${yy.toFixed(1)}px) rotate(${rot}deg) scale(${sc.toFixed(4)})`, bl);
  };
  // rise: fade + slide up in, blur in; optional exit at t1
  const rise = (el, t, t0, o = {}) => {
    const { d = 0.34, dy = 40, blur = 10, t1 = null, d1 = 0.24, dy1 = -30, x = 0 } = o;
    const a = eo((t - t0) / d);
    let op = a, y = (1 - a) * dy, b = (1 - a) * blur;
    if (t1 != null) { const z = ease((t - t1) / d1); op *= 1 - z; y += z * dy1; b += z * blur; }
    return set(el, op, `translate(${x}px, ${y.toFixed(1)}px)`, b);
  };
  // typeIn: types `text` into el between t0 and t1 (with a caret while typing). Returns true when done.
  const typeIn = (el, text, t, t0, t1, caret = true) => {
    const ch = [...text], n = Math.round(clamp((t - t0) / Math.max(0.05, t1 - t0)) * ch.length);
    const html = esc(ch.slice(0, n).join('')) + (caret && t > t0 - 0.4 && n < ch.length ? '<span class="caret"></span>' : '');
    if (el.__h !== html) { el.innerHTML = html; el.__h = html; }
    return n >= ch.length;
  };
  const html = (el, s) => { if (el.__h !== s) { el.innerHTML = s; el.__h = s; } };
  const centerX = (el, cx) => { el.style.left = px(cx - el.offsetWidth / 2); };
  // <img> that hides itself if the file is missing (prep shots before the first --prep)
  const img = (src, style = '') => `<img src="${src}" style="${style}" onerror="this.style.visibility='hidden'">`;
  Object.assign(F, { h, add, box, div, set, pop, rise, typeIn, html, centerX, img });

  // ---------- glyphs ----------
  const spark = (s = 60, c = '#d97757') => {
    const L = [1, 0.8, 0.96, 0.78, 1, 0.84, 0.92, 0.8, 0.98, 0.83, 0.9];
    let d = ''; L.forEach((l, i) => { const a = (i / L.length) * TAU - Math.PI / 2; d += `<line x1="${(Math.cos(a) * 0.12).toFixed(3)}" y1="${(Math.sin(a) * 0.12).toFixed(3)}" x2="${(Math.cos(a) * l).toFixed(3)}" y2="${(Math.sin(a) * l).toFixed(3)}"/>`; });
    return `<svg viewBox="-1.12 -1.12 2.24 2.24" width="${s}" height="${s}" style="display:block"><g stroke="${c}" stroke-width="0.21" stroke-linecap="round">${d}</g></svg>`;
  };
  const claudeBadge = (s = 56) => `<div style="width:${s}px;height:${s}px;border-radius:${s * 0.3}px;background:#f6eee8;display:flex;align-items:center;justify-content:center;box-shadow:0 6px 16px rgba(0,0,0,0.25)">${spark(s * 0.66)}</div>`;
  const svg = (s, body, o = '') => `<svg width="${s}" height="${s}" viewBox="0 0 24 24" ${o}>${body}</svg>`;
  const G = {
    CURSOR: '<svg viewBox="0 0 24 24" width="56" height="56"><path d="M4 2.5l15 9.2-6.6 1.3 3.9 7.4-2.9 1.5-3.9-7.4L4.8 19z" fill="#fff" stroke="#111" stroke-width="1.2" stroke-linejoin="round"/></svg>',
    XMARK: (s = 54) => svg(s, '<path d="M6 6l12 12M18 6L6 18"/>', 'fill="none" stroke="#fff" stroke-width="3.4" stroke-linecap="round"'),
    CHECK: (s = 54, c = '#fff') => svg(s, '<path d="M5 12.5l4.5 4.5L19 7.5"/>', `fill="none" stroke="${c}" stroke-width="3.2" stroke-linecap="round" stroke-linejoin="round"`),
    SEND: (s = 44) => svg(s, '<path d="M12 19V5M5.5 11.5L12 5l6.5 6.5"/>', 'fill="none" stroke="#fff" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"'),
    MIC: (s = 44) => svg(s, '<rect x="9" y="3" width="6" height="11" rx="3"/><path d="M5.5 11a6.5 6.5 0 0 0 13 0M12 17.5V21"/>', 'fill="none" stroke="#fff" stroke-width="2.2" stroke-linecap="round"'),
    LOCK: (s = 28, c = '#a4a6aa') => svg(s, '<rect x="5" y="11" width="14" height="10" rx="2"/><path d="M8 11V8a4 4 0 0 1 8 0v3"/>', `fill="none" stroke="${c}" stroke-width="2.2"`),
    HEART: (s = 40, c = '#fff') => svg(s, '<path d="M12 21s-7.5-4.6-9.3-9.2C1.4 8.5 3.5 5 7 5c2 0 3.6 1.2 5 3 1.4-1.8 3-3 5-3 3.5 0 5.6 3.5 4.3 6.8C19.5 16.4 12 21 12 21z"/>', `fill="${c}"`),
    PLAY: (s = 56, c = '#fff') => svg(s, '<path d="M7 4.5v15l12-7.5z"/>', `fill="${c}" style="margin-left:${Math.round(s * 0.14)}px"`),
    UPLOAD: (s = 54) => svg(s, '<path d="M12 16V5M7 9.5L12 5l5 4.5M5 19h14"/>', 'fill="none" stroke="#fff" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"'),
    COMMENT: (s = 38) => svg(s, '<path d="M4 18l1.4-4A8 8 0 1 1 9 19.2z"/>', 'fill="none" stroke="#fff" stroke-width="2"'),
    SHARE: (s = 36) => svg(s, '<path d="M21 4L3 11l7 3 3 7z"/>', 'fill="none" stroke="#fff" stroke-width="2" stroke-linejoin="round"'),
    PERSON: c => `<svg viewBox="0 0 100 100" width="100%" height="100%"><circle cx="50" cy="40" r="17" fill="${c}"/><path d="M18 92c4-20 18-30 32-30s28 10 32 30z" fill="${c}"/></svg>`,
  };
  // app glyphs for P.appTile (white line icons, 24x24 viewBox)
  const GLYPH = {
    capcut: '<svg width="110" height="110" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round"><circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M20 4L8.1 15.9M14.5 14.5L20 20M8.1 8.1L12 12"/></svg>',
    premiere: '<svg width="110" height="110" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M7 4v16M17 4v16M3 9h4M3 15h4M17 9h4M17 15h4"/></svg>',
    finalcut: '<svg width="110" height="110" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linejoin="round"><path d="M3 10h18v10H3z"/><path d="M3 10l2-5 16-1-1 5"/><path d="M8 5.5l3 4M13.5 5l3 4.2"/></svg>',
    chat: '<svg width="110" height="110" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linejoin="round"><path d="M4 5h16v11H9l-5 4z"/></svg>',
    mail: '<svg width="110" height="110" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linejoin="round"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="M3.5 6l8.5 7 8.5-7"/></svg>',
    doc: '<svg width="110" height="110" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linejoin="round"><path d="M6 3h8l4 4v14H6z"/><path d="M14 3v4h4M9 12h6M9 16h6"/></svg>',
    sheet: '<svg width="110" height="110" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M3 10h18M3 15h18M9 4v16"/></svg>',
    camera: '<svg width="110" height="110" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linejoin="round"><rect x="3" y="7" width="13" height="11" rx="2"/><path d="M16 11l5-3v9l-5-3z"/></svg>',
    code: '<svg width="110" height="110" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8 7l-5 5 5 5M16 7l5 5-5 5M13.5 4l-3 16"/></svg>',
  };
  Object.assign(F, { spark, claudeBadge, GLYPH }, G);
  // caption icon chips: TAKES markup @name:*word* → F.ICONS[name](). Add your own in scenes.js.
  F.ICONS = { claude: () => spark(44) };

  // ---------- timeline lookups ----------
  const TL = F.TL = window.TL || { total: 1, sections: [] };
  if (!window.TL) F.errors.push(`no ${F.DIR}/timeline.js: run node build-timeline.mjs --video ${F.DIR}`);
  const S = F.S = Object.fromEntries(TL.sections.map(s => [s.id, s]));
  const norm = s => s.toLowerCase().replace(/[^a-z0-9 ]/g, '');
  // K(section, word, n): the n-th token in that section whose shown text starts with `word` (case/punctuation-blind)
  F.K = (id, w, n = 0) => {
    if (!S[id]) throw new Error(`no section "${id}" in the timeline`);
    let c = 0;
    for (const t of S[id].tokens) if (norm(t.d).startsWith(norm(w))) { if (c === n) return t; c++; }
    throw new Error(`no token "${w}"${n ? ' #' + n : ''} in ${id}`);
  };
  F.T0 = (id, w, n) => F.K(id, w, n).t0;
  F.T1 = (id, w, n) => F.K(id, w, n).t1;
  F.END = TL.total;
  // sound cues → <video>/cues.json → render.mjs --mix.  cue(sfx name, time, gain, length?, fade-in?)
  F.CUES = [];
  F.cue = (f, t, g = 0.1, len, fin) => F.CUES.push([f, +t.toFixed(3), g, len ?? null, fin ?? null]);
  const updaters = [];
  F.on = f => updaters.push(f);

  // ---------- layout: where scenes and captions sit in the safe zone ----------
  // Scenes are authored on the 1080 canvas (content within x 40–1040, y 520–1480, centred on x 540);
  // the engine scales them by k around (540, 1000) and shifts them so they land inside x 60–960.
  const LAYOUTS = {
    top: { cap: 'top', k: 0.92, dx: -30, dy: -10 },      // captions from y 270 (this format's look), scenes in y ≈ 540–1445
    band: { cap: 'band', k: 0.86, dx: -30, dy: -300 },   // captions in the RULES caption band (y 1170+), scenes in y ≈ 270–1125
  };
  F.LAYOUT = LAYOUTS.top;
  F.layout = (mode, over = {}) => { F.LAYOUT = { ...(LAYOUTS[mode] || LAYOUTS.top), ...over }; };

  // ---------- looks: dark / light / warm / moody, crossfaded by keys [t, look, fade] ----------
  const MODES = ['dark', 'light', 'warm', 'moody'];
  let BG = [[-9, 'dark', 0.2]];
  F.looks = keys => {
    BG = keys.map(([t, m, d = 0.2]) => { if (!MODES.includes(m)) throw new Error('unknown look ' + m); return [t, m, d]; }).sort((a, b) => a[0] - b[0]);
    if (!BG.length || BG[0][0] > -1) BG.unshift([-9, BG.length ? BG[0][1] : 'dark', 0.2]);
  };
  const lookAt = F.lookAt = t => { let m = BG[0][1]; for (const [k, mm] of BG) if (t >= k) m = mm; return m; };
  const weights = F.lookWeights = t => {
    const w = { dark: 0, light: 0, warm: 0, moody: 0 };
    let i = 0; for (let j = 0; j < BG.length; j++) if (t >= BG[j][0]) i = j;
    const u = i === 0 ? 1 : ease((t - BG[i][0]) / BG[i][2]);
    w[BG[i][1]] += u; if (i > 0) w[BG[i - 1][1]] += 1 - u;
    return w;
  };
  const BGL = {};
  for (const m of MODES) { BGL[m] = add(stage, h('div', { class: 'layer bg-' + m })); BGL[m].style.inset = '-40px'; }
  // the dark look's stage light (Stamp): a magenta-topped beam, a hot spot behind the captions, haze, twinkling sparkles, dust
  const beam = add(stage, h('div', { class: 'beam' }));
  add(beam, h('div', { class: 'hot' })); add(beam, h('div', { class: 'cone' }));
  [[120, 1500, 520], [700, 1620, 560], [380, 1780, 640]].forEach(([x, y, s]) => div(beam, 'haze', x - s / 2, y - s / 2, s, s * 0.7));
  const stars = (() => { const r = rng(31); return [[140, 560], [930, 760], [210, 1290], [880, 1400], [470, 1690], [990, 1150], [80, 980], [640, 1830]].map(([x, y]) => ({ e: div(beam, 'star', x, y, 26, 26, '<svg viewBox="-1 -1 2 2" width="26" height="26"><path d="M0 -1 Q0.12 -0.12 1 0 Q0.12 0.12 0 1 Q-0.12 0.12 -1 0 Q-0.12 -0.12 0 -1z" fill="#fff"/></svg>'), ph: r() * 6, sp: 0.6 + r() * 0.8, s: 0.55 + r() * 0.6 })); })();
  const dust = (() => { const r = rng(9); return Array.from({ length: 26 }, () => ({ e: div(beam, 'dust', 0, 0), x: 330 + r() * 420, y: 500 + r() * 1350, v: 6 + r() * 14, ph: r() * 6, a: 0.15 + r() * 0.4 })); })();
  const drawBeam = (t, w) => {
    beam.style.opacity = w.toFixed(3); beam.style.display = w < 0.01 ? 'none' : 'block';
    if (w < 0.01) return;
    for (const s of stars) { const k = 0.35 + 0.65 * Math.pow(0.5 + 0.5 * Math.sin(t * s.sp * 2.4 + s.ph), 3); s.e.style.opacity = k.toFixed(3); s.e.style.transform = `scale(${(s.s * (0.7 + 0.3 * k)).toFixed(3)}) rotate(${(t * 12 * s.sp).toFixed(1)}deg)`; }
    for (const d of dust) { const y = d.y - ((t * d.v) % 1400); d.e.style.transform = `translate(${(d.x + Math.sin(t * 0.7 + d.ph) * 14).toFixed(1)}px, ${(y < 480 ? y + 1400 : y).toFixed(1)}px)`; d.e.style.opacity = (d.a * (0.6 + 0.4 * Math.sin(t * 1.3 + d.ph))).toFixed(3); }
  };
  // rubber-stamp grunge: a speckle + scratch mask for P.but (made once, as a data URL)
  F.GRUNGE = (() => {
    const c = document.createElement('canvas'); c.width = 600; c.height = 300; const g = c.getContext('2d'), r = rng(404);
    g.fillStyle = '#fff'; g.fillRect(0, 0, 600, 300);
    g.globalCompositeOperation = 'destination-out';
    for (let i = 0; i < 420; i++) { g.globalAlpha = 0.18 + r() * 0.5; g.beginPath(); g.arc(r() * 600, r() * 300, 0.5 + Math.pow(r(), 4) * 4, 0, TAU); g.fill(); }
    g.lineCap = 'round';
    for (let i = 0; i < 10; i++) { g.globalAlpha = 0.2 + r() * 0.4; g.lineWidth = 0.6 + r() * 1.6; g.beginPath(); const x = r() * 600, y = r() * 300, a = r() * TAU, l = 20 + r() * 90; g.moveTo(x, y); g.lineTo(x + Math.cos(a) * l, y + Math.sin(a) * l); g.stroke(); }
    g.globalAlpha = 0.16; for (let i = 0; i < 6; i++) { g.beginPath(); g.ellipse(r() * 600, r() * 300, 30 + r() * 60, 8 + r() * 22, r() * 3, 0, TAU); g.fill(); }
    return c.toDataURL();
  })();
  const leak = add(stage, h('div', { class: 'leak' }));
  const grain = add(stage, h('div', { class: 'grain' }));
  {
    const c = document.createElement('canvas'); c.width = c.height = 256; const g = c.getContext('2d'), d = g.createImageData(256, 256), r = rng(5);
    for (let i = 0; i < d.data.length; i += 4) { const v = 128 + (r() - 0.5) * 220; d.data[i] = d.data[i + 1] = d.data[i + 2] = v; d.data[i + 3] = 255; }
    g.putImageData(d, 0, 0); grain.style.backgroundImage = `url(${c.toDataURL()})`;
  }
  const rain = add(stage, h('canvas', { class: 'layer', width: 1080, height: 1920 }));
  const rctx = rain.getContext('2d');
  const drops = (() => { const r = rng(77); return Array.from({ length: 170 }, () => ({ x: r() * 1300 - 110, y: r() * 2200, l: 50 + r() * 110, v: 1900 + r() * 900, a: 0.08 + r() * 0.2, w: r() < 0.2 ? 2.2 : 1.2 })); })();
  const drawLook = t => {
    const w = weights(t);
    for (const m in BGL) { BGL[m].style.opacity = w[m].toFixed(3); BGL[m].style.display = w[m] < 0.002 ? 'none' : 'block'; }
    drawBeam(t, w.dark);
    const k = Math.floor(t * 24), r = rng(k * 7 + 3);
    grain.style.backgroundPosition = `${Math.floor(r() * 64)}px ${Math.floor(r() * 64)}px`;
    grain.style.opacity = (0.035 + 0.1 * w.warm + 0.16 * w.moody).toFixed(3);
    leak.style.display = w.warm < 0.01 ? 'none' : 'block';
    if (w.warm >= 0.01) {
      let tw = 0; for (const [k2, m] of BG) if (m === 'warm' && k2 <= t) tw = k2;
      leak.style.opacity = (w.warm * (0.75 + 0.25 * Math.sin(t * 1.3))).toFixed(3);
      leak.style.transform = `translate(${(-560 + Math.sin(t * 0.5) * 140 + (t - tw) * 30).toFixed(0)}px, ${(-620 + Math.cos(t * 0.4) * 90).toFixed(0)}px)`;
    }
    rain.style.display = w.moody < 0.01 ? 'none' : 'block';
    if (w.moody >= 0.01) {
      rctx.clearRect(0, 0, 1080, 1920); rctx.lineCap = 'round';
      for (const d of drops) {
        const y = ((d.y + t * d.v) % 2300) - 200, x = d.x + y * 0.16;
        rctx.strokeStyle = `rgba(170,200,222,${(d.a * w.moody).toFixed(3)})`; rctx.lineWidth = d.w;
        rctx.beginPath(); rctx.moveTo(x, y); rctx.lineTo(x + d.l * 0.16, y + d.l); rctx.stroke();
      }
    }
  };

  // ---------- scenes: one container each; blur-dissolve in and out, slow push, fitted into the safe zone ----------
  const scenesRoot = add(stage, h('div', { class: 'layer' }));
  const scenes = [];
  // scene(tin, tout, { in, out, inD, push, full })
  //   in:  'blur' (default: dissolve from a soft blur) · 'up' / 'down' / 'left' / 'right' (whip-slide with motion blur) · 'zoom' (flies in from small)
  //   out: 'blur' (default) · 'zoom' (flies into the camera) · 'up' / 'down' / 'left' / 'right' (whip-slide away)
  //   Each scene has a camera rig (sc.el is the rig): F.cam(sc, [[t, scale, x, y, rot], …]) moves it with eased keys, and it
  //   always drifts a little so nothing is ever perfectly still.
  F.scene = (tin, tout, o = {}) => {
    const box = add(scenesRoot, h('div', { class: 'scene' }));
    const el = add(box, h('div', { class: 'rig' }));
    const sc = { box, el, tin, tout, o, upd: [], cam: null, seed: scenes.length * 1.7 };
    sc.on = f => sc.upd.push(f);
    scenes.push(sc); return sc;
  };
  F.cam = (sc, keys) => { sc.cam = keys; };
  const camAt = (keys, t) => {
    if (!keys || !keys.length) return [1, 0, 0, 0];
    if (t <= keys[0][0]) return keys[0].slice(1);
    for (let i = 1; i < keys.length; i++) if (t < keys[i][0]) { const u = eio((t - keys[i - 1][0]) / (keys[i][0] - keys[i - 1][0])); return [1, 2, 3, 4].map(j => lerp(keys[i - 1][j] ?? (j === 1 ? 1 : 0), keys[i][j] ?? (j === 1 ? 1 : 0), u)); }
    return keys.at(-1).slice(1);
  };
  const SLIDE = { up: [0, 1], down: [0, -1], left: [-1, 0], right: [1, 0] };
  const drawScene = (sc, t) => {
    const { box, el, tin, tout, o } = sc;
    const kin = o.in || 'blur', kout = o.zoom ? 'zoom' : (o.out || 'blur');
    const outLen = kout === 'zoom' ? 0.4 : SLIDE[kout] ? 0.3 : 0.24, inLen = o.inD || (SLIDE[kin] ? 0.34 : kin === 'zoom' ? 0.36 : 0.28);
    if (t < tin - 0.02 || t > tout + outLen + 0.04) { if (box.style.display !== 'none') box.style.display = 'none'; return false; }
    box.style.display = 'block';
    const ai = tin < 0 ? 1 : clamp((t - tin) / inLen), ao = clamp((t - tout) / outLen);
    const push = 1 + (o.push ?? 0.03) * clamp((t - tin) / Math.max(1, tout - tin));
    let s = push, x = 0, y = 0, op = 1, bl = 0, sy = 1;
    if (kin === 'blur') { const a = eo(ai); s *= 0.94 + 0.06 * a; op *= a; bl += (1 - a) * 14; }
    else if (kin === 'zoom') { const a = eo(ai); s *= 0.5 + 0.5 * a; op *= clamp(ai * 2.6); bl += (1 - a) * 20; }
    else if (SLIDE[kin]) { const a = eo(ai), [dx, dy] = SLIDE[kin]; x += dx * (1 - a) * 1150; y += dy * (1 - a) * 1500; bl += (1 - a) * 26; op *= clamp(ai * 3); if (dy) sy = 1 + 0.12 * (1 - a); }
    if (kout === 'blur') { const z = eo(ao); s *= 1 + 0.06 * z; op *= 1 - z; bl += z * 14; }
    else if (kout === 'zoom') { const zz = eio(ao); s *= 1 + 5 * zz; op *= 1 - clamp((ao - 0.35) / 0.65); bl += zz * 12; }
    else if (SLIDE[kout]) { const z = eio(ao), [dx, dy] = SLIDE[kout]; x -= dx * z * 1150; y -= dy * z * 1500; bl += z * 26; op *= 1 - clamp((ao - 0.55) / 0.45); if (dy) sy = 1 + 0.12 * z; }
    const L = o.full ? { k: 1, dx: 0, dy: 0 } : F.LAYOUT;
    set(box, op, `translate(${(L.dx + x).toFixed(1)}px, ${(L.dy + y).toFixed(1)}px) scale(${(L.k * s).toFixed(4)}, ${(L.k * s * sy).toFixed(4)})`, bl);
    const [cs, cx, cy, cr] = camAt(sc.cam, t), ph = sc.seed;
    const dx = Math.sin(t * 0.55 + ph) * 5, dy = Math.cos(t * 0.43 + ph * 1.3) * 4, dr = Math.sin(t * 0.37 + ph) * 0.18;
    el.style.transform = `translate(${(cx + dx).toFixed(1)}px, ${(cy + dy).toFixed(1)}px) rotate(${(cr + dr).toFixed(3)}deg) scale(${cs.toFixed(4)})`;
    return true;
  };

  // ---------- cursor: keys [[t, x, y], …], clicks [t, …] ----------
  F.cursor = p => ({ c: add(p, h('div', { class: 'cursor' }, G.CURSOR)), r: add(p, h('div', { class: 'ripple' })) });
  F.drive = (cur, t, keys, clicks = []) => {
    const t0 = keys[0][0] - 0.25, t1 = keys.at(-1)[0] + 0.5;
    if (t < t0 || t > t1) { cur.c.style.opacity = 0; cur.r.style.opacity = 0; return; }
    let x = keys[0][1], y = keys[0][2];
    for (let i = 1; i < keys.length; i++) if (t >= keys[i - 1][0]) { const u = eio((t - keys[i - 1][0]) / (keys[i][0] - keys[i - 1][0])); x = lerp(keys[i - 1][1], keys[i][1], u); y = lerp(keys[i - 1][2], keys[i][2], u); }
    const op = clamp((t - t0) / 0.2) * (1 - clamp((t - (t1 - 0.25)) / 0.25));
    let sc = 1; for (const c of clicks) { const d = t - c; if (d > -0.08 && d < 0.14) sc = 0.84; }
    cur.c.style.opacity = op.toFixed(3); cur.c.style.transform = `translate(${(x - 10).toFixed(1)}px, ${(y - 6).toFixed(1)}px) scale(${sc})`;
    let rop = 0, rs = 1;
    for (const c of clicks) { const u = (t - c) / 0.5; if (u >= 0 && u <= 1) { rop = 1 - u; rs = 0.3 + 1.3 * eo(u); } }
    cur.r.style.opacity = rop.toFixed(3); cur.r.style.transform = `translate(${x.toFixed(1)}px, ${y.toFixed(1)}px) scale(${rs.toFixed(3)})`;
  };

  // ---------- whole-frame effects ----------
  const flashEl = add(stage, h('div', { class: 'flash' }));
  const flashes = [], shakes = [];
  F.flash = (t, peak = 0.75) => flashes.push([t, peak]);              // camera-flash white-out at t
  F.shake = (t, amp = 16, dur = 0.35) => shakes.push([t, amp, dur]);  // camera shake starting at t
  const drawFx = t => {
    let fl = 0; for (const [t0, pk] of flashes) fl = Math.max(fl, pk * clamp((t - t0) / 0.05) * (1 - clamp((t - t0 - 0.05) / 0.3)));
    flashEl.style.opacity = fl.toFixed(3);
    let tr = '';
    for (const [t0, amp, dur] of shakes) { const u = (t - t0) / dur; if (u >= 0 && u <= 1) { const a = amp * (1 - u); tr = `translate(${(Math.sin(u * 47) * a).toFixed(1)}px, ${(Math.cos(u * 61) * a * 0.7).toFixed(1)}px)`; } }
    stage.style.transform = tr;
  };

  // ---------- captions: word by word, key words boxed, styled by the look at the phrase's start ----------
  // Options (set them in scenes.js): F.cap.hide = (token, section) => true to drop a word from the captions
  // (e.g. when a giant word on screen says it);  F.cap.big = { from: t, until: t, scale: 1.3 } makes the captions bigger.
  F.cap = { hide: null, big: null };
  const capRoot = add(stage, h('div', { class: 'over' }));
  const phrases = [];
  const buildCaptions = () => {
    if (F.NOCAP) return;
    for (const s of TL.sections) { let cur = null; s.tokens.forEach(tk => { if (tk.br || !cur) { cur = { s, toks: [] }; phrases.push(cur); } cur.toks.push(tk); }); }
    const B = F.cap.big;
    phrases.forEach((p, i) => {
      const nx = phrases[i + 1]; p.t0 = p.toks[0].t0;
      p.t1 = nx && nx.s === p.s ? nx.toks[0].t0 - 0.1 : Math.min(p.s.until - 0.04, p.toks.at(-1).t1 + 0.6);
      p.mode = lookAt(p.t0 - 0.02);
      p.scale = B && p.t0 >= B.from - 0.01 && p.t0 < (B.until ?? Infinity) ? (B.scale || 1.3) : 1;
      p.grow = p.scale !== 1 && Math.abs(p.t0 - B.from) < 0.011;
      p.el = add(capRoot, h('div', { class: 'cap m-' + p.mode }));
      if (p.scale !== 1) { p.el.style.width = px(820 / p.scale); p.el.style.left = px(510 - 410 / p.scale); } // scaled, still ≤ 820 wide
      p.w = p.toks.filter(tk => !(F.cap.hide && F.cap.hide(tk, p.s))).map(tk => {
        let ic = null;
        if (tk.icon) ic = add(p.el, h('span', { class: 'ic' }, (F.ICONS[tk.icon] || F.ICONS.claude)()));
        const sp = add(p.el, h('span', { class: 'w' + (tk.key ? ' k' : '') }));
        sp.appendChild(document.createTextNode(tk.d));
        let bx = null, hd = [];
        if (tk.key) { bx = add(sp, h('span', { class: 'bx' })); hd = [add(sp, h('span', { class: 'hd', style: { left: '-7px', top: '-8px' } })), add(sp, h('span', { class: 'hd', style: { right: '-7px', bottom: '-8px' } }))]; }
        p.el.appendChild(document.createTextNode(' '));
        return { tk, sp, ic, bx, hd };
      });
    });
  };
  const drawCaptions = t => {
    for (const p of phrases) {
      if (t < p.t0 - 0.12 || t >= p.t1) { if (p.el.style.display !== 'none') p.el.style.display = 'none'; continue; }
      p.el.style.display = 'block';
      const sc = p.scale === 1 ? 1 : p.grow ? 1 + (p.scale - 1) * eo((t - p.t0 - 0.05) / 0.35) : p.scale;
      p.el.style.transform = sc !== 1 ? `scale(${sc.toFixed(4)})` : '';
      let cur = -1; p.w.forEach((w, j) => { if (t >= w.tk.t0 - 0.05) cur = j; });
      p.w.forEach((w, j) => {
        const ap = w.tk.t0 < 0.4 ? -1 : w.tk.t0 - 0.1;
        const a = w.tk.t0 < 0.4 ? 1 : eo((t - ap) / 0.14);
        for (const e of [w.sp, w.ic]) if (e) { e.style.opacity = a.toFixed(3); e.style.transform = `translateY(${((1 - a) * 16).toFixed(1)}px)`; e.style.filter = a < 0.97 ? `blur(${((1 - a) * 8).toFixed(1)}px)` : 'none'; }
        if (w.ic) { const b = back((t - ap) / 0.3); w.ic.style.transform = `translateY(${((1 - a) * 16).toFixed(1)}px) scale(${(0.5 + 0.5 * b).toFixed(3)})`; }
        if (!w.tk.key) w.sp.classList.toggle('cur', j === cur);
        else {
          const b = w.tk.t0 < 0.4 ? 1 : back((t - w.tk.t0 + 0.04) / 0.24);
          w.bx.style.transform = `scale(${(0.86 + 0.14 * b).toFixed(4)})`; w.bx.style.opacity = clamp(b * 1.6).toFixed(3);
          w.hd.forEach(d => { d.style.opacity = clamp((b - 0.5) * 2).toFixed(3); });
        }
      });
    }
  };

  // ---------- safe zone (RULES.md rule 7): overlay + a report of every visible text outside it ----------
  const ZONE = { x0: 60, x1: 960, y0: 250, y1: 1480, cx0: 100, cx1: 920, band0: 1150 };
  F.ZONE = ZONE;
  let safeEl = null;
  const buildSafe = () => {
    safeEl = add(film, h('div', { id: 'safe' }));
    const r = (cls, x, y, w, hh, txt) => div(safeEl, cls, x, y, w, hh, txt);
    r('no', 0, 0, 1080, 250); r('no', 0, 1480, 1080, 440); r('no', 960, 250, 120, 1230); r('no', 0, 250, 60, 1230);
    r('zone', 60, 250, 900, 1230); r('band', 60, 1150, 900, 330); r('mid', 509, 250, 2, 1230);
    r('lbl', 60, 214, null, null, 'text zone x 60–960 · y 250–1480'); r('lbl', 60, 1486, null, null, 'caption band 1150–1480 (yellow)');
  };
  const visibleOp = el => {
    let op = 1;
    for (let e = el; e && e !== document.body; e = e.parentElement) {
      const cs = getComputedStyle(e);
      if (cs.display === 'none' || cs.visibility === 'hidden') return 0;
      op *= +cs.opacity;
    }
    return op;
  };
  // F.safeReport(mark): [{ text, box:[x0,y0,x1,y1], cap }] for visible text outside the zone (captions: outside x 100–920).
  F.safeReport = (mark = false) => {
    const bad = [], tw = document.createTreeWalker(stage, NodeFilter.SHOW_TEXT);
    for (let n; (n = tw.nextNode());) {
      if (!n.nodeValue.trim()) continue;
      const el = n.parentElement, op = visibleOp(el);
      if (op < 0.2) continue;
      const cap = !!el.closest('.cap');
      const rg = document.createRange(); rg.selectNodeContents(n);
      let x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9;
      for (const b of rg.getClientRects()) { if (b.width < 1 || b.height < 1) continue; x0 = Math.min(x0, b.left); y0 = Math.min(y0, b.top); x1 = Math.max(x1, b.right); y1 = Math.max(y1, b.bottom); }
      if (x0 > x1) continue;
      if (x1 < 0 || y1 < 0 || x0 > 1080 || y0 > 1920) continue; // fully off-screen
      const L = cap ? ZONE.cx0 : ZONE.x0, R = cap ? ZONE.cx1 : ZONE.x1, tol = 2;
      if (x0 < L - tol || x1 > R + tol || y0 < ZONE.y0 - tol || y1 > ZONE.y1 + tol) bad.push({ text: n.nodeValue.trim().slice(0, 40), box: [x0, y0, x1, y1].map(v => Math.round(v)), cap });
    }
    if (mark && safeEl) {
      safeEl.querySelectorAll('.bad').forEach(e => e.remove());
      for (const b of bad) div(safeEl, 'bad', b.box[0] - 4, b.box[1] - 4, b.box[2] - b.box[0] + 8, b.box[3] - b.box[1] + 8);
    }
    return bad;
  };

  // ---------- prep shots: frames of this very film that the film shows (a phone showing it, storyboard panels) ----------
  // F.prep([{ name: 'self', at: 1.75 }, { name: 'sb', at: [1.9, 5.1, …], nocap: true, width: 432 }]) → render.mjs --prep
  // writes <video>/prep/self.jpg and <video>/prep/sb/01.jpg …; use them with F.prepSrc('self') / F.prepSrc('sb', 1).
  F.PREP = [];
  F.prep = list => { F.PREP = list; };
  F.prepSrc = (name, i) => `${F.DIR}/prep/${name}${i != null ? '/' + String(i).padStart(2, '0') : ''}.jpg`;

  // ---------- start ----------
  F.start = async () => {
    if (Q.get('layout')) F.layout(Q.get('layout'));   // preview another layout without editing scenes.js (render.mjs --layout band)
    buildCaptions();
    if (F.SAFE) buildSafe();
    film.classList.toggle('cap-band', F.LAYOUT.cap === 'band');
    window.TOTAL = TL.total;
    window.BEATS = scenes.map(s => [Math.max(0, s.tin), Math.min(TL.total, s.tout)]);
    window.CUES = F.CUES;
    window.LOOKS = BG;
    window.PREP = F.PREP;
    window.setT = async t => {
      drawFx(t);
      drawLook(t);
      for (const s of scenes) if (drawScene(s, t)) for (const u of s.upd) u(t);
      for (const u of updaters) await u(t);
      drawCaptions(t);
      await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)));
      return true;
    };
    await document.fonts.ready;
    await Promise.all([...document.images].map(i => i.decode().catch(() => { })));
    await window.setT(0);
    return true;
  };
  window.ready = new Promise(res => addEventListener('DOMContentLoaded', () => F.start().then(res, e => { F.errors.push('start: ' + (e && e.stack || e)); res(false); })));
})();
```

### `engine/parts.js`

```js
// Stamp · UI PARTS from the Motion kit (recoloured magenta): pill, check, the prompt bar, chat bubbles, typing dots, avatar,
// a phone. Stamp's own parts are in stamp.js + fx.js. A scene composes them and animates
// what they return (pop / rise / set / typeIn in its sc.on(t => …) update). All coordinates are design space: the
// 1080x1920 canvas, centre x 540, content between y ≈ 520 and 1480 (the engine fits it into the safe zone).
// Every part: P.name(parent, …) → the element, or an object of elements (+ an update(t) helper where it has motion).
(() => {
  const { h, add, div, px, rng, set, pop, rise, clamp, lerp, eo, eio, back, typeIn, html, spark, claudeBadge, GLYPH, img, TAU } = F;
  const P = F.parts = {};
  const IMG = 'width:100%;height:100%;object-fit:cover;display:block';

  // P.pill(p, x, y, html, kind 'dk'|'lt'|'bl'|'rd', style) — re-centre each frame with F.centerX(el, cx) if its text changes
  P.pill = (p, x, y, inner, kind = 'dk', style) => div(p, 'pill ' + kind, x, y, null, null, inner, style);
  P.stamp = (p, x, y, s = 110) => div(p, 'stamp', x, y, s, s, F.XMARK(s * 0.5));            // red ✕ stamp
  P.check = (p, x, y, s = 44) => div(p, 'check', x, y, s, s, F.CHECK(s * 0.58));             // blue ✓ badge
  // P.composer(p, x, y, w, h, { light, card: 'gcard'|'lcard', lead: 'claude'|null, btn: 'send'|'mic', placeholder, font, bs, tx }) →
  //   { el, txt, btn, bx, by, bs, type(t, text, t0, t1, caret), press(t, at), hint(text) }
  //   light: white pill · dark (light:false): a translucent field inside a dark card · card: a standalone glossy card
  P.composer = (p, x, y, w, hh, o = {}) => {
    const { light = true, lead = 'claude', btn = 'send', font = Math.round(hh * 0.26), card } = o;
    const el = div(p, card || (light ? 'lcard' : 'abs'), x, y, w, hh, '', light || card ? { borderRadius: px(hh / 2) } : { borderRadius: px(hh / 2), background: 'rgba(0,0,0,0.3)', border: '1px solid rgba(255,255,255,0.1)' });
    const bs = o.bs || Math.round(hh * (light ? 0.64 : 0.8)), pad = Math.round((hh - bs) / 2);
    let tx = o.tx || Math.round(hh * 0.36);
    if (lead === 'claude') { const ls = Math.round(hh * 0.5); div(el, 'abs', Math.round(hh * 0.2), Math.round((hh - ls) / 2), ls, ls, claudeBadge(ls)); tx = Math.round(hh * 0.2 + ls + hh * 0.17); }
    const txt = div(el, 'abs', tx, Math.round((hh - font * 1.45) / 2), w - tx - bs - pad - 20, Math.round(font * 1.45), o.placeholder ? `<span style="color:${light ? '#a3a5aa' : '#6f727a'}">${o.placeholder}</span>` : '', { fontSize: px(font), fontWeight: 600, color: light ? '#141518' : '#f3f3f1', whiteSpace: 'nowrap', overflow: 'hidden' });
    const b = div(el, 'abs', w - bs - pad, pad, bs, bs, btn === 'mic' ? F.MIC(Math.round(bs * 0.46)) : F.SEND(Math.round(bs * 0.46)), { borderRadius: '50%', background: 'linear-gradient(180deg,#e24fd6,#b0219f)', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 10px 26px rgba(196,47,184,0.45)' });
    return {
      el, txt, btn: b, bx: w - bs - pad, by: pad, bs,
      type: (t, text, t0, t1, caret = true) => typeIn(txt, text, t, t0, t1, caret),
      hint: s => html(txt, `<span style="color:${light ? '#a3a5aa' : '#6f727a'}">${s}</span>`),
      press: (t, at) => { const pr = clamp((t - at) / 0.1) - clamp((t - at - 0.1) / 0.12); b.style.transform = `scale(${(1 - 0.13 * pr).toFixed(3)})`; },
    };
  };
  // P.bubble(p, y, text, { right, kind: 'me'|'dark'|'warm', size }) — a chat bubble anchored to the right edge
  P.bubble = (p, y, text, o = {}) => {
    const { right = 90, kind = 'me', size } = o;
    const st = kind === 'me' ? {} : kind === 'warm' ? { background: '#2e2119', color: '#f6eadb', borderBottomRightRadius: '10px', boxShadow: '0 18px 40px rgba(60,30,10,0.35)' } : { background: 'rgba(255,255,255,0.07)', color: '#f3f3f1', borderBottomRightRadius: '10px' };
    const e = div(p, 'bubble' + (kind === 'me' ? ' me' : ''), null, y, null, null, text, { ...st, ...(size ? { fontSize: px(size) } : {}) });
    e.style.right = px(right); return e;
  };
  // P.msg(p, x, y, html, { light }) — an assistant message (grey, tail bottom-left)
  P.msg = (p, x, y, inner, o = {}) => div(p, 'abs', x, y, null, null, inner, { fontSize: px(o.size || 31), fontWeight: 560, padding: '24px 30px', borderRadius: '30px', borderBottomLeftRadius: '10px', background: o.light ? '#eef0f4' : 'rgba(255,255,255,0.07)', color: o.light ? '#141518' : '#f3f3f1', whiteSpace: 'nowrap' });
  // P.typing(p, x, y) → { el, update(t, on) } — three bouncing dots
  P.typing = (p, x, y, o = {}) => {
    const el = div(p, 'abs', x, y, 150, 70, '<i></i><i></i><i></i>', { borderRadius: '30px', background: o.light ? '#eef0f4' : 'rgba(255,255,255,0.07)', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '12px' });
    [...el.children].forEach(d => Object.assign(d.style, { width: '14px', height: '14px', borderRadius: '50%', background: o.light ? '#8a8d94' : '#c9cacd', display: 'block' }));
    return { el, update: (t, on) => { el.style.opacity = on.toFixed(2); [...el.children].forEach((d, i) => { d.style.transform = `translateY(${(Math.sin(t * 10 - i * 0.9) * 6).toFixed(1)}px)`; }); } };
  };
  // P.phone(p, cx, y, w, h, { src, reel }) → { el, screen, progress } — a phone; reel: true adds neutral Reels UI
  //   (like / comment / share icons without numbers, a neutral avatar, a progress bar). No real handle: RULES.
  P.phone = (p, cx, y, w, hh, o = {}) => {
    const el = div(p, 'abs', cx - w / 2, y, w, hh, '', { borderRadius: '70px', background: 'linear-gradient(160deg,#2a2b30,#0b0b0d)', boxShadow: '0 60px 120px rgba(0,0,0,0.7), 0 0 120px rgba(196,47,184,0.25), inset 0 2px 0 rgba(255,255,255,0.18)', border: '1px solid rgba(255,255,255,0.14)' });
    const screen = div(el, 'abs', 17, 17, w - 34, hh - 34, o.src ? img(o.src, IMG) : '', { borderRadius: '56px', overflow: 'hidden', background: '#000' });
    let progress = null;
    if (o.reel) {
      const ui = div(screen, 'abs', 0, 0, w - 34, hh - 34, '');
      const bar = (bw) => `<i style="display:block;width:${bw}px;height:8px;border-radius:4px;background:rgba(255,255,255,0.75);margin:6px auto 0"></i>`;
      ui.innerHTML = `<div style="position:absolute;right:20px;bottom:150px;display:flex;flex-direction:column;gap:30px;align-items:center;filter:drop-shadow(0 1px 6px rgba(0,0,0,0.6))">
        <div>${F.HEART(40, '#ff3b5c')}${bar(34)}</div><div>${F.COMMENT(38)}${bar(26)}</div><div>${F.SHARE(36)}</div></div>
        <div style="position:absolute;left:20px;bottom:56px;display:flex;align-items:center;gap:10px;filter:drop-shadow(0 1px 6px rgba(0,0,0,0.6))"><span style="width:38px;height:38px;border-radius:50%;border:2px solid #fff;background:radial-gradient(circle at 35% 30%,#ec7fe3,#c42fb8);display:block"></span><i style="display:block;width:120px;height:12px;border-radius:6px;background:rgba(255,255,255,0.85)"></i><i style="display:block;width:64px;height:26px;border-radius:8px;border:1.5px solid #fff"></i></div>
        <div style="position:absolute;left:0;right:0;bottom:30px;height:3px;background:rgba(255,255,255,0.3)"><i class="pb" style="position:absolute;left:0;top:0;bottom:0;background:#fff;width:0"></i></div>`;
      progress = ui.querySelector('.pb');
    }
    return { el, screen, progress };
  };
  // P.avatar(p, cx, cy, s, colour, inner) → el — a round glossy avatar (inner: svg/html; default a neutral person)
  P.avatar = (p, cx, cy, s, c, inner) => div(p, 'abs', cx - s / 2, cy - s / 2, s, s, inner || F.PERSON('rgba(255,255,255,0.55)'), { borderRadius: '50%', background: `radial-gradient(circle at 35% 30%, ${c}, #15161a 120%)`, boxShadow: '0 16px 36px rgba(0,0,0,0.5), inset 0 2px 0 rgba(255,255,255,0.2)', overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center' });
})();
```

### `engine/stamp.js`

```js
// Stamp · THE FORMAT'S OWN PARTS (on top of parts.js). The look of @getripping's escalation reels: glossy macOS-style app
// windows in magenta, big counting numbers in a selection box, tilted stickers, chips, reel tiles, a calendar, charts,
// notifications, a text-file card, the red rubber BUT stamp that slams onto the card on every spoken "but", a red ban
// circle, and the CTA comment box. Same coordinates as parts.js: the 1080x1920 design canvas, centre x 540.
(() => {
  const { h, add, div, px, rng, set, pop, rise, clamp, lerp, eo, eio, back, typeIn, html, spark, TAU } = F;
  const P = F.parts;
  const M = { mag: '#c42fb8', mag2: '#e24fd6', mid: '#ec7fe3', pale: '#fbe6f9', red: '#e5283a' };
  P.M = M;
  const ICON = (s = 34, glyph = '✦') => `<span style="width:${s}px;height:${s}px;border-radius:${Math.round(s * 0.28)}px;background:linear-gradient(160deg,#f070e4,#b0219f);display:inline-flex;align-items:center;justify-content:center;color:#fff;font-size:${Math.round(s * 0.56)}px;box-shadow:0 4px 12px rgba(196,47,184,0.45),inset 0 1px 0 rgba(255,255,255,0.4);flex:none">${glyph}</span>`;
  P.ICON = ICON;

  // P.mac(p, x, y, w, h, { light, title, glyph, right, hh }) → card — an app window: three dots on the left, the app's
  //   icon + name centred in the header, an optional chip on the right. Content goes below hh (default 74).
  P.mac = (p, x, y, w, hh, o = {}) => {
    const { light = false, title = '', glyph = '✦', right = '', head = o.hh || 74 } = o;
    const card = div(p, light ? 'lcard' : 'gcard', x, y, w, hh);
    const line = light ? 'rgba(20,22,40,0.07)' : 'rgba(255,255,255,0.07)';
    const dots = light ? ['#f0565a', '#f4a3c8', '#d9a6f0'] : ['#e5484d', '#c9739f', '#9b6fc4'];
    card.innerHTML = `<div style="position:absolute;left:0;right:0;top:0;height:${head}px;border-bottom:1px solid ${line};display:flex;align-items:center;padding:0 28px;gap:12px">${dots.map(c => `<span class="dot" style="background:${c}"></span>`).join('')}</div>
      <div style="position:absolute;left:0;right:0;top:0;height:${head}px;display:flex;align-items:center;justify-content:center;gap:12px;font-size:26px;font-weight:650;color:${light ? '#3a3b40' : '#c9cacd'}">${title ? ICON(34, glyph) + `<span>${title}</span>` : ''}</div>
      ${right ? `<div style="position:absolute;right:24px;top:0;height:${head}px;display:flex;align-items:center">${right}</div>` : ''}`;
    return card;
  };
  // P.chip(p, x, y, html, kind '' | 'dk' | 'rd' | 'rdk' | 'on', style) — a small status chip ("• Live ✓", "Free", "+38 this hour")
  P.chip = (p, x, y, inner, kind = '', style) => div(p, 'chip ' + kind, x, y, null, null, inner, style);
  // P.sticker(p, x, y, html, rot) — the tilted magenta sticker pill ("✦ made with Claude"); animate with pop(…, { rot })
  P.sticker = (p, x, y, inner, style) => div(p, 'sticker', x, y, null, null, inner, style);

  // P.but(p, cx, cy, t0, { size, rot, text, light }) → { el, update(t) } — the red rubber stamp. It drops in at t0 (scale 2 → 1 with
  //   blur), squashes on impact, throws a red shockwave ring and ink flecks, shakes the camera and plays its own stamp + sub hit.
  //   Put it where the card's problem is; it stays until the scene ends.
  P.but = (p, cx, cy, t0, o = {}) => {
    const { size = 150, rot = -7, text = 'BUT', light = true, shake = 11 } = o;
    const wrap = div(p, 'abs', cx, cy, 0, 0, '', { zIndex: 40 });
    const R = Math.round(size * 1.9), shock = div(p, 'abs', cx - R, cy - R, 2 * R, 2 * R, '', { zIndex: 39, borderRadius: '50%', border: `5px solid ${M.red}`, boxShadow: `0 0 24px rgba(229,40,58,0.5)`, opacity: 0 });
    const r = rng(Math.round(cx * 7 + cy));
    const flecks = Array.from({ length: 10 }, () => ({ e: div(p, 'abs', cx, cy, 6 + r() * 8, 6 + r() * 8, '', { zIndex: 39, borderRadius: '50%', background: M.red, opacity: 0 }), a: r() * TAU, d: 160 + r() * 200 }));
    const el = add(wrap, h('div', { class: 'but' }, text));
    Object.assign(el.style, { fontSize: px(size), padding: `${Math.round(size * 0.12)}px ${Math.round(size * 0.26)}px ${Math.round(size * 0.1)}px`, left: '0px', top: '0px', transform: 'translate(-50%,-50%)',
      WebkitMaskImage: `url(${F.GRUNGE})`, WebkitMaskSize: '100% 100%', maskImage: `url(${F.GRUNGE})`, maskSize: '100% 100%',
      mixBlendMode: light ? 'multiply' : 'normal', textShadow: light ? 'none' : '0 0 24px rgba(229,40,58,0.55)', boxShadow: light ? 'none' : '0 0 40px rgba(229,40,58,0.4), inset 0 0 30px rgba(229,40,58,0.25)', background: light ? 'rgba(255,255,255,0.0)' : 'rgba(30,6,10,0.4)' });
    F.cue('stampslam', t0 - 0.02, 0.22); F.cue('boom', t0 - 0.01, 0.1, 0.9); F.cue('whoosh', t0 - 0.16, 0.035);
    if (shake) F.shake(t0 + 0.02, shake, 0.3);
    return {
      el: wrap, update: t => {
        if (t < t0 - 0.08) { set(wrap, 0); set(shock, 0); flecks.forEach(f => set(f.e, 0)); return; }
        const u = clamp((t - t0 + 0.08) / 0.12), land = clamp((t - t0 - 0.04) / 0.28);
        const sq = 1 - 0.08 * Math.sin(land * Math.PI) * (1 - land);
        const sc = lerp(2.1, 1, eo(u)) * sq;
        set(wrap, clamp(u * 2.2) * 0.95, `rotate(${(rot - 10 * (1 - eo(u))).toFixed(2)}deg) scale(${sc.toFixed(4)})`, (1 - u) * 10);
        const k = clamp((t - t0 - 0.02) / 0.45);
        set(shock, k > 0 && k < 1 ? (1 - k) * 0.75 : 0, `scale(${(0.15 + 0.85 * eo(k)).toFixed(3)})`);
        flecks.forEach(f => { const q = clamp((t - t0 - 0.02) / 0.4); set(f.e, q > 0 && q < 1 ? (1 - q) * 0.9 : 0, `translate(${(Math.cos(f.a) * f.d * eo(q)).toFixed(1)}px, ${(Math.sin(f.a) * f.d * eo(q) + 80 * q * q).toFixed(1)}px) scale(${(1 - 0.6 * q).toFixed(2)})`); });
      },
    };
  };
  // P.ban(p, cx, cy, r, t0) → { el, update(t) } — a red circle-slash that slams over a thing, with a shockwave and a red flare
  P.ban = (p, cx, cy, r, t0, o = {}) => {
    const flare = div(p, 'abs', cx - r * 1.8, cy - r * 1.8, r * 3.6, r * 3.6, '', { zIndex: 38, borderRadius: '50%', background: 'radial-gradient(closest-side, rgba(229,40,58,0.45), transparent)', opacity: 0 });
    const shock = div(p, 'abs', cx - r, cy - r, 2 * r, 2 * r, '', { zIndex: 39, borderRadius: '50%', border: `4px solid ${M.red}`, opacity: 0 });
    const el = div(p, 'ban', cx - r, cy - r, 2 * r, 2 * r, '', { zIndex: 40, borderWidth: px(o.bw || Math.round(r * 0.14)) });
    F.cue('stampslam', t0 - 0.02, 0.17); F.cue('bassdrop', t0, 0.07, 0.6); F.cue('error', t0 + 0.02, 0.035, 0.3);
    if (o.shake !== false) F.shake(t0 + 0.02, 8, 0.24);
    return { el, update: t => {
      const u = clamp((t - t0 + 0.06) / 0.14);
      set(el, t < t0 - 0.06 ? 0 : clamp(u * 2), `scale(${lerp(1.8, 1, eo(u)).toFixed(4)}) rotate(${(-25 * (1 - eo(u))).toFixed(1)}deg)`, (1 - u) * 8);
      const k = clamp((t - t0) / 0.5); set(shock, k > 0 && k < 1 ? (1 - k) * 0.8 : 0, `scale(${(1 + eo(k) * 0.8).toFixed(3)})`);
      set(flare, t < t0 ? 0 : 0.35 + 0.65 * Math.exp(-(t - t0) * 5) + 0.08 * Math.sin(t * 6));
    } };
  };

  // P.bigNum(p, cx, y, { size, light }) → { el, txt, box, set(str), update(t, tBox) } — a giant glowing magenta number; at tBox a
  //   selection box with handles and orbiting markers snaps around it (the "$1,000,000" moment)
  P.bigNum = (p, cx, y, o = {}) => {
    const { size = 168, light = false } = o;
    const el = div(p, 'abs', 0, y, 1080, Math.round(size * 1.18), '', { textAlign: 'center' });
    const inner = add(el, h('span', { style: { position: 'relative', display: 'inline-block', padding: `0 ${Math.round(size * 0.12)}px`, isolation: 'isolate' } }));
    const txt = add(inner, h('span', { style: { fontSize: px(size), fontWeight: 850, letterSpacing: '-0.045em', lineHeight: px(Math.round(size * 1.18)), backgroundImage: 'linear-gradient(180deg,#ff8af2 0%,#e24fd6 45%,#c42fb8 100%)', WebkitBackgroundClip: 'text', backgroundClip: 'text', color: 'transparent', filter: `drop-shadow(0 0 ${Math.round(size * 0.16)}px rgba(226,79,214,${light ? 0.25 : 0.55}))`, fontVariantNumeric: 'tabular-nums' } }));
    const box = add(inner, h('span', { style: { position: 'absolute', inset: `${Math.round(size * 0.14)}px 0 ${Math.round(size * 0.1)}px 0`, border: `3px solid ${M.mag2}`, background: light ? 'rgba(226,79,214,0.12)' : 'rgba(120,16,110,0.35)', borderRadius: '4px', zIndex: -1 } }));
    const hd = [['-9px', null, '-9px', null], [null, '-9px', null, '-9px']].map(([l, r, t2, b]) => add(box, h('i', { style: { position: 'absolute', left: l, right: r, top: t2, bottom: b, width: '16px', height: '16px', borderRadius: '50%', background: M.mag2, boxShadow: '0 0 12px rgba(226,79,214,0.9)' } })));
    const marks = [0, 1, 2, 3, 4, 5].map(i => add(el, h('i', { style: { position: 'absolute', width: '22px', height: '22px', borderRadius: '50%', border: `3px solid ${i % 2 ? 'rgba(226,79,214,0.6)' : 'rgba(240,112,228,0.85)'}`, left: '0px', top: '0px' } })));
    return {
      el, txt, box, inner,
      set: s => { if (txt.textContent !== s) txt.textContent = s; },
      update: (t, tBox) => {
        const b = back((t - tBox) / 0.3);
        box.style.opacity = clamp((t - tBox) / 0.08).toFixed(3); box.style.transform = `scale(${(0.9 + 0.1 * b).toFixed(4)})`;
        const W = inner.offsetWidth, H = inner.offsetHeight, L = (1080 - W) / 2;
        marks.forEach((m, i) => {
          const a = i / 6 * TAU + t * 0.5 * (i % 2 ? -1 : 1), rx = W / 2 + 40 + (i % 3) * 14, ry = H / 2 + 20 + (i % 2) * 18;
          m.style.transform = `translate(${(L + W / 2 + Math.cos(a) * rx - 11).toFixed(1)}px, ${(H / 2 + Math.sin(a) * ry - 11).toFixed(1)}px)`;
          m.style.opacity = (clamp((t - tBox) / 0.3) * 0.9).toFixed(3);
        });
      },
    };
  };
  F.money = v => '$' + Math.round(v).toLocaleString('en-US');
  F.num = v => Math.round(v).toLocaleString('en-US');

  // P.reel(p, x, y, w, h, { title, hue, seed, light }) → el — a vertical reel tile: a pink-magenta gradient, a big
  //   two-line hook in white caps with a black word box, a round person silhouette (neutral, no real creator)
  const HUES = [['#f06ad8', '#b42aa8'], ['#ff7a8a', '#e04a9a'], ['#d65cf0', '#8e2fc4'], ['#ff9ad8', '#e2569d'], ['#f2589f', '#a82a7c'], ['#c86af2', '#6e2ec4']];
  P.reel = (p, x, y, w, hh, o = {}) => {
    const { title = ['DAY 30', ''], hue = 0, seed = 1 } = o, [c1, c2] = HUES[hue % HUES.length], r = rng(seed);
    const el = div(p, 'tile', x, y, w, hh, '', { background: `linear-gradient(160deg, ${c1}, ${c2})`, boxShadow: '0 16px 36px rgba(120,20,110,0.35), inset 0 1px 0 rgba(255,255,255,0.3)' });
    const s = Math.round(w * 0.42);
    div(el, 'abs', w - s * 0.95, hh - s * 1.02, s * 1.25, s * 1.25, '', { borderRadius: '50%', background: 'rgba(255,255,255,0.22)' });
    div(el, 'abs', w - s * 0.72, hh - s * 0.95, s * 0.8, s * 0.8, F.PERSON('rgba(255,255,255,0.92)'), { borderRadius: '50%' });
    const fs = Math.round(w * 0.15);
    div(el, 'abs', Math.round(w * 0.08), Math.round(hh * 0.08), w * 0.9, null, `<div style="font-size:${fs}px;font-weight:900;color:#fff;letter-spacing:-0.02em;line-height:1.05;text-shadow:0 2px 0 rgba(0,0,0,0.12)">${title[0]}</div>${title[1] ? `<div style="display:inline-block;margin-top:6px;font-size:${Math.round(fs * 0.9)}px;font-weight:900;color:#fff;background:#141316;padding:2px 8px;letter-spacing:-0.02em;line-height:1.05">${title[1]}</div>` : ''}`);
    return el;
  };
  // P.cal(p, x, y, { cols, rows, cell, gap, light, title }) → { el, cells, update(n) } — a month grid; update(n) ticks the first n days
  P.cal = (p, x, y, o = {}) => {
    const { cols = 7, rows = 5, cell = 74, gap = 12, light = false, title = 'October' } = o;
    const W = cols * cell + (cols - 1) * gap + 56, H = rows * cell + (rows - 1) * gap + 112;
    const el = div(p, light ? 'lcard' : 'gcard', x, y, W, H, `<div style="position:absolute;left:28px;top:24px;display:flex;align-items:center;gap:12px;font-size:28px;font-weight:720">${ICON(34, '▦')}<span>${title}</span></div>`);
    const cells = [];
    for (let i = 0; i < cols * rows; i++) {
      const c = div(el, 'abs', 28 + (i % cols) * (cell + gap), 88 + Math.floor(i / cols) * (cell + gap), cell, cell, '', { borderRadius: '16px', background: light ? '#efe9ee' : 'rgba(255,255,255,0.06)', border: light ? '1px solid rgba(0,0,0,0.05)' : '1px solid rgba(255,255,255,0.07)', display: 'flex', alignItems: 'center', justifyContent: 'center' });
      const ck = add(c, h('div', { style: { position: 'absolute', inset: '0', borderRadius: '16px', background: 'linear-gradient(180deg,#f070e4,#c42fb8)', boxShadow: '0 6px 16px rgba(196,47,184,0.45), inset 0 1px 0 rgba(255,255,255,0.35)', display: 'flex', alignItems: 'center', justifyContent: 'center', opacity: '0' } }, F.CHECK(Math.round(cell * 0.6))));
      cells.push({ c, ck });
    }
    return { el, cells, W, H, update: (n, t) => cells.forEach((c, i) => { const a = clamp(n - i); set(c.ck, a, `scale(${(0.6 + 0.4 * back(a)).toFixed(3)})`); }) };
  };
  // P.chart(p, x, y, w, h, { pts: [[0..1, 0..1]…], light, area }) → { el, update(u), at(u) } — a line chart drawn left to right
  P.chart = (p, x, y, w, hh, o = {}) => {
    const { pts, light = false, color = M.mag2, area = true, sw = 6 } = o;
    const el = div(p, 'abs', x, y, w, hh);
    const X = q => (q * w).toFixed(1), Y = q => ((1 - q) * hh).toFixed(1);
    const d = pts.map((q, i) => `${i ? 'L' : 'M'}${X(q[0])} ${Y(q[1])}`).join(' ');
    el.innerHTML = `<svg width="${w}" height="${hh}" style="overflow:visible"><defs><linearGradient id="ga${x}${y}" x1="0" y1="0" x2="0" y2="1"><stop offset="0" stop-color="${color}" stop-opacity="${light ? 0.28 : 0.38}"/><stop offset="1" stop-color="${color}" stop-opacity="0"/></linearGradient><clipPath id="gc${x}${y}"><rect class="cr" x="0" y="-40" width="0" height="${hh + 80}"/></clipPath></defs>
      <g clip-path="url(#gc${x}${y})">${area ? `<path d="${d} L${X(pts.at(-1)[0])} ${hh} L${X(pts[0][0])} ${hh} Z" fill="url(#ga${x}${y})"/>` : ''}<path d="${d}" fill="none" stroke="${color}" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round" style="filter:drop-shadow(0 0 8px ${color}88)"/></g>
      <circle class="hd" r="11" fill="#fff" stroke="${color}" stroke-width="5"/></svg>`;
    const cr = el.querySelector('.cr'), hd = el.querySelector('.hd');
    const at = u => { u = clamp(u); for (let i = 1; i < pts.length; i++) if (u <= pts[i][0] || i === pts.length - 1) { const k = clamp((u - pts[i - 1][0]) / Math.max(1e-6, pts[i][0] - pts[i - 1][0])); return [lerp(pts[i - 1][0], pts[i][0], k) * w, (1 - lerp(pts[i - 1][1], pts[i][1], k)) * hh]; } return [0, hh]; };
    return { el, at, update: u => { const [a, b] = at(u); cr.setAttribute('width', (a + 2).toFixed(1)); hd.setAttribute('cx', a.toFixed(1)); hd.setAttribute('cy', b.toFixed(1)); hd.style.opacity = u > 0.005 ? 1 : 0; } };
  };
  // P.notif(p, x, y, w, { app, text, time, light, warn }) → el — a phone notification row
  P.notif = (p, x, y, w, o = {}) => {
    const { app = 'REELS', text = '', time = 'now', light = false, warn = false } = o;
    return div(p, 'abs', x, y, w, 104, `<div style="position:absolute;left:20px;top:22px">${warn ? `<span style="width:56px;height:56px;border-radius:15px;background:linear-gradient(160deg,#ff5a68,#c81e30);display:inline-flex;align-items:center;justify-content:center;color:#fff;font-size:30px;font-weight:900">!</span>` : ICON(56, '✦')}</div>
      <div style="position:absolute;left:94px;top:18px;font-size:18px;font-weight:700;letter-spacing:0.12em;color:${light ? '#8a8d94' : '#9a9ca2'}">${app}</div>
      <div style="position:absolute;left:94px;top:44px;font-size:29px;font-weight:720;color:${warn ? '#ff5d6c' : light ? '#141518' : '#f3f3f1'};white-space:nowrap">${text}</div>
      <div style="position:absolute;right:22px;top:18px;font-size:19px;color:#8d9096">${time}</div>`,
      { borderRadius: '26px', background: light ? 'rgba(255,255,255,0.92)' : 'rgba(28,24,30,0.92)', border: warn ? '2px solid rgba(229,40,58,0.7)' : light ? '1px solid rgba(0,0,0,0.06)' : '1px solid rgba(255,255,255,0.1)', boxShadow: '0 18px 40px rgba(0,0,0,0.3)' });
  };
  // P.file(p, x, y, w, h, { name, size, lines, light }) → { el, lines } — a plain text file (SKILL.md): a mono header + numbered lines
  P.file = (p, x, y, w, hh, o = {}) => {
    const { name = 'SKILL.md', size = '4 KB', lines = [], light = true } = o;
    const el = div(p, light ? 'lcard' : 'gcard', x, y, w, hh, `<div style="position:absolute;left:28px;right:28px;top:0;height:78px;display:flex;align-items:center;gap:14px;border-bottom:1px solid ${light ? 'rgba(0,0,0,0.07)' : 'rgba(255,255,255,0.07)'}"><span style="width:40px;height:48px;border-radius:7px;background:${light ? '#f1eef2' : 'rgba(255,255,255,0.08)'};border:2px solid ${light ? '#d9d2db' : 'rgba(255,255,255,0.18)'};display:inline-flex;align-items:flex-end;justify-content:center;padding-bottom:4px;font:800 11px/1 var(--mono);color:${M.mag}">MD</span><span class="mono" style="font-size:27px;font-weight:700">${name}</span><span class="mono" style="margin-left:auto;font-size:21px;color:#8d9096">${size}</span></div>`);
    const ls = lines.map((l, i) => div(el, 'abs mono', 28, 100 + i * 42, w - 56, 40, `<span style="display:inline-block;width:40px;color:${light ? '#b9b3bd' : '#55575d'}">${i + 1}</span>${l}`, { fontSize: '22px', color: light ? '#4a4c52' : '#b7b9be', whiteSpace: 'nowrap', overflow: 'hidden' }));
    return { el, lines: ls };
  };
  // P.stat(p, x, y, w, label, value, { light, red }) → { el, v } — a small labelled number box ("VIEWS TODAY 214")
  P.stat = (p, x, y, w, label, value, o = {}) => {
    const el = div(p, 'abs', x, y, w, 104, `<div style="position:absolute;left:18px;top:14px;font-size:16px;font-weight:750;letter-spacing:0.1em;color:#8d9096">${label}</div><div class="v" style="position:absolute;left:18px;top:40px;font-size:42px;font-weight:820;letter-spacing:-0.03em;color:${o.red ? M.red : o.mag ? M.mag : o.light ? '#141518' : '#f3f3f1'};font-variant-numeric:tabular-nums">${value}</div>`,
      { borderRadius: '18px', background: o.light ? (o.mag ? '#fdeefb' : '#f7f6f8') : 'rgba(255,255,255,0.05)', border: o.light ? `1.5px solid ${o.mag ? 'rgba(196,47,184,0.35)' : 'rgba(0,0,0,0.06)'}` : '1px solid rgba(255,255,255,0.08)' });
    return { el, v: el.querySelector('.v') };
  };
  // P.row(p, x, y, w, { name, sub, right, light, hl, avatar }) → el — a list row: avatar, name, a small sub line, a right chip/label
  P.row = (p, x, y, w, o = {}) => {
    const { name = '', sub = '', right = '', light = true, hl = false, hue = 0 } = o, [c1] = HUES[hue % HUES.length];
    return div(p, 'abs', x, y, w, 76, `<span style="position:absolute;left:14px;top:13px;width:50px;height:50px;border-radius:50%;background:radial-gradient(circle at 35% 30%, ${c1}, #2a1a28 140%);overflow:hidden">${F.PERSON('rgba(255,255,255,0.8)')}</span>
      <div style="position:absolute;left:80px;top:${sub ? 10 : 22}px;font-size:26px;font-weight:700;color:${light ? '#141518' : '#f3f3f1'};white-space:nowrap">${name}</div>${sub ? `<div style="position:absolute;left:80px;top:42px;font-size:19px;font-weight:600;color:#8d9096;white-space:nowrap">${sub}</div>` : ''}
      <div style="position:absolute;right:14px;top:0;height:76px;display:flex;align-items:center">${right}</div>`,
      { borderRadius: '16px', background: hl ? (light ? '#fdeefb' : 'rgba(196,47,184,0.16)') : 'transparent', border: hl ? `2px solid ${M.mag2}` : '2px solid transparent' });
  };

})();
```

### `engine/fx.js`

```js
// Stamp · MOTION + EFFECTS. What gives the format its life: every element enters with a spring or a whip and then keeps
// floating; counters tick, wires draw and carry pulses, big moments burst. Loaded after stamp.js (film.html).
(() => {
  const { h, add, div, px, rng, set, clamp, lerp, eo, eio, back, TAU } = F;
  const P = F.parts, M = P.M;
  // the mix: per-sound trims so hits punch without covering a word (the bass sounds are long and dense). Every cue() goes
  // through this; change a level for one video with F.SFX_GAIN.name = x at the top of its scenes.js.
  F.SFX_GAIN = { bassdrop: 0.45, boom: 0.65, uipop: 0.6, riser: 0.8, stampslam: 0.85, cards: 0.8, sparkle: 0.8 };
  { const raw = F.cue; F.cue = (f, t, g = 0.1, len, fin) => raw(f, t, g * (F.SFX_GAIN[f] ?? 1), len, fin); }

  // F.enter(el, t, t0, { from, d, dist, s0, rot, blur, float, seed, t1, d1, to, x, y }) → opacity
  //   from: 'pop' (spring scale) · 'up' / 'down' / 'left' / 'right' (whip-slide with blur) · 'slam' (big → 1) · 'drop' (falls and
  //   bounces) · 'zoom' (small → 1) · 'fade'. After it lands it floats (a slow bob and sway) unless float: 0.
  //   to (exit at t1): 'fade' · 'up' / 'down' / 'left' / 'right' · 'pop' (shrinks away)
  F.enter = (el, t, t0, o = {}) => {
    const { from = 'pop', dist = 140, rot = 0, blur = 12, float = 1, seed = 0, t1 = null, d1 = 0.24, to = 'fade', x = 0, y = 0, s = 1 } = o;
    const d = o.d ?? (from === 'pop' ? 0.42 : from === 'drop' ? 0.55 : from === 'slam' ? 0.22 : 0.36);
    if (t < t0 - 0.01 || (t1 != null && t > t1 + d1 + 0.02)) { if (el.style.visibility !== 'hidden') { el.style.opacity = '0'; el.style.visibility = 'hidden'; } return 0; }
    const a = clamp((t - t0) / d);
    let tx = x, ty = y, sc = s, r = rot, op = 1, bl = 0;
    switch (from) {
      case 'pop': { const s0 = o.s0 ?? 0.55; sc *= s0 + (1 - s0) * back(a); op = clamp(a * 2.4); bl = (1 - eo(a)) * blur; r += (o.spin ?? 0) * (1 - eo(a)); break; }
      case 'up': case 'down': case 'left': case 'right': {
        const k = 1 - eo(a), dir = { up: [0, 1], down: [0, -1], left: [-1, 0], right: [1, 0] }[from];
        tx += dir[0] * k * dist; ty += dir[1] * k * dist; op = clamp(a * 2.5); bl = k * blur * 1.6; r += (o.spin ?? 0) * k; break;
      }
      case 'slam': { sc *= lerp(o.s0 ?? 1.9, 1, eo(a)); op = clamp(a * 3); bl = (1 - a) * blur; const q = clamp((t - t0 - d) / 0.25); sc *= 1 - 0.05 * Math.sin(q * Math.PI) * (1 - q); break; }
      case 'drop': { const k = clamp(a / 0.6); ty -= (1 - k * k) * dist; if (a > 0.6) ty -= Math.abs(Math.sin((a - 0.6) / 0.4 * Math.PI)) * dist * 0.08 * (1 - a); op = clamp(a * 4); r += (o.spin ?? 0) * (1 - eo(a)); break; }
      case 'zoom': { sc *= lerp(o.s0 ?? 0.2, 1, back(a, 1.2)); op = clamp(a * 2.5); bl = (1 - eo(a)) * blur * 1.5; break; }
      default: { op = eo(a); bl = (1 - eo(a)) * blur; }
    }
    if (float) { const k = clamp((t - t0 - d) / 0.6), ph = seed * 1.913 + 0.7; ty += Math.sin(t * 1.25 + ph) * 5 * float * k; tx += Math.cos(t * 0.8 + ph * 1.4) * 2.5 * float * k; r += Math.sin(t * 0.9 + ph * 1.3) * 0.4 * float * k; }
    if (t1 != null && t > t1) {
      const z = eo((t - t1) / d1);
      if (to === 'pop') { sc *= 1 - 0.7 * z; op *= 1 - z; }
      else if (to === 'fade') { op *= 1 - z; bl += z * blur; sc *= 1 + 0.06 * z; }
      else { const dir = { up: [0, -1], down: [0, 1], left: [-1, 0], right: [1, 0] }[to]; tx += dir[0] * z * dist * 1.5; ty += dir[1] * z * dist * 1.5; op *= 1 - z; bl += z * blur * 1.6; }
    }
    el.style.visibility = op < 0.004 ? 'hidden' : 'visible';
    el.style.opacity = op.toFixed(3);
    el.style.transform = `translate(${tx.toFixed(1)}px, ${ty.toFixed(1)}px) rotate(${r.toFixed(2)}deg) scale(${sc.toFixed(4)})`;
    el.style.filter = bl > 0.3 ? `blur(${bl.toFixed(1)}px)` : 'none';
    return op;
  };
  F.tw = (t, t0, t1, e = eio) => e(clamp((t - t0) / Math.max(1e-3, t1 - t0)));
  // sound helpers: ticks spread over a span (alternating three tick samples), and a typing bed for a typing span
  let tk = 0;
  F.ticks = (t0, t1, n, g = 0.05) => { for (let i = 0; i < n; i++) F.cue(['tick1', 'tick2', 'tick3'][tk++ % 3], lerp(t0, t1, n > 1 ? i / (n - 1) : 0), g); };
  F.typeCue = (t0, t1, g = 0.07) => F.cue('typing', t0, g, Math.max(0.25, t1 - t0 + 0.08));
  // F.swing(t, t0, amp, freq, damp) → degrees: a hanging tag that was just hit swings and settles
  F.swing = (t, t0, amp = 16, freq = 7.5, damp = 3.2) => t < t0 ? 0 : amp * Math.exp(-(t - t0) * damp) * Math.sin((t - t0) * freq);

  // P.wire(p, d, { w, h, color, width, dash, t0, dur, pulses, glow }) → { el, path, update(t) } — an SVG connector that draws
  //   itself from t0 over dur, then carries glowing pulses along it (pulses: how many travel at once)
  P.wire = (p, d, o = {}) => {
    const { color = M.mag2, width = 4, dash = '', t0 = 0, dur = 0.4, pulses = 0, glow = true, x = 0, y = 0, w = 1080, hh = 1920, speed = 0.9 } = o;
    const el = div(p, 'abs', x, y, w, hh, '', { pointerEvents: 'none' });
    el.innerHTML = `<svg width="${w}" height="${hh}" style="overflow:visible"><path d="${d}" fill="none" stroke="${color}" stroke-width="${width}" stroke-linecap="round" ${dash ? `stroke-dasharray="${dash}"` : ''} style="${glow ? `filter:drop-shadow(0 0 6px ${color})` : ''}"/>${Array.from({ length: pulses }, () => `<circle class="pl" r="${width + 3}" fill="#fff" style="filter:drop-shadow(0 0 8px ${color})"/>`).join('')}</svg>`;
    const svg = el.firstChild, line = svg.querySelector('path'), pls = [...svg.querySelectorAll('.pl')];
    let len = null;
    return {
      el, path: line, update: t => {
        if (len == null) { try { len = line.getTotalLength(); } catch { len = 1000; } }
        const u = clamp((t - t0) / dur);
        if (dash) { line.style.opacity = eo(u).toFixed(2); line.setAttribute('stroke-dashoffset', (-(t - t0) * 50).toFixed(1)); }
        else { line.setAttribute('stroke-dasharray', `${(len * eo(u)).toFixed(1)} ${(len + 20).toFixed(0)}`); line.style.opacity = u > 0 ? 1 : 0; }
        pls.forEach((c, i) => { const q = (((t - t0 - dur) * speed + i / pls.length) % 1 + 1) % 1; if (t < t0 + dur) { c.style.opacity = 0; return; } const pt = line.getPointAtLength(q * len); c.setAttribute('cx', pt.x.toFixed(1)); c.setAttribute('cy', pt.y.toFixed(1)); c.style.opacity = Math.sin(q * Math.PI).toFixed(2); });
      },
    };
  };
  // P.burst(p, cx, cy, t0, { n, r0, r1, len, color, width }) → { update(t) } — radial dashes flying out (a "hit" moment)
  P.burst = (p, cx, cy, t0, o = {}) => {
    const { n = 16, r0 = 80, r1 = 300, len = 34, color = M.mag2, width = 6, rx = 1, ry = 1, dur = 0.5 } = o;
    const el = div(p, 'abs', cx - r1 - 60, cy - r1 - 60, 2 * r1 + 120, 2 * r1 + 120, '', { pointerEvents: 'none', zIndex: 45 });
    const c = r1 + 60, rr = rng(Math.round(cx + cy * 3));
    el.innerHTML = `<svg width="${2 * c}" height="${2 * c}">${Array.from({ length: n }, (_, i) => { const a = (i / n) * TAU + rr() * 0.2; return `<line data-a="${a}" data-k="${(0.8 + rr() * 0.4).toFixed(2)}" stroke="${color}" stroke-width="${width}" stroke-linecap="round" style="filter:drop-shadow(0 0 6px ${color})"/>`; }).join('')}</svg>`;
    const ls = [...el.querySelectorAll('line')];
    return {
      el, update: t => {
        const u = clamp((t - t0) / dur);
        el.style.display = u <= 0 || u >= 1 ? 'none' : 'block'; if (u <= 0 || u >= 1) return;
        ls.forEach(l => { const a = +l.dataset.a, k = +l.dataset.k, r = lerp(r0, r1 * k, eo(u)), L2 = len * (1 - u); l.setAttribute('x1', (c + Math.cos(a) * r * rx).toFixed(1)); l.setAttribute('y1', (c + Math.sin(a) * r * ry).toFixed(1)); l.setAttribute('x2', (c + Math.cos(a) * (r + L2) * rx).toFixed(1)); l.setAttribute('y2', (c + Math.sin(a) * (r + L2) * ry).toFixed(1)); l.style.opacity = (1 - u).toFixed(2); });
      },
    };
  };
  // P.shock(p, cx, cy, t0, { r, color, width, rx, ry }) → { update(t) } — an expanding ring
  P.shock = (p, cx, cy, t0, o = {}) => {
    const { r = 300, color = M.mag2, width = 5, rx = 1, ry = 1, dur = 0.55 } = o;
    const el = div(p, 'abs', cx - r * rx, cy - r * ry, 2 * r * rx, 2 * r * ry, '', { borderRadius: '50%', border: `${width}px solid ${color}`, boxShadow: `0 0 30px ${color}`, pointerEvents: 'none', zIndex: 44, opacity: 0 });
    return { el, update: t => { const k = clamp((t - t0) / dur); set(el, k > 0 && k < 1 ? (1 - k) * 0.85 : 0, `scale(${(0.2 + 0.8 * eo(k)).toFixed(3)})`); } };
  };
  // P.confetti(p, cx, cy, t0, { n, spread, colors }) → { update(t) } — little paper bits thrown up that fall with gravity
  P.confetti = (p, cx, cy, t0, o = {}) => {
    const { n = 36, spread = 520, colors = ['#f070e4', '#c42fb8', '#ffd1f8', '#ffffff', '#ff8ab0'], up = 900, dur = 1.6 } = o;
    const r = rng(Math.round(cx * 3 + cy));
    const bits = Array.from({ length: n }, () => ({ e: div(p, 'abs', cx, cy, 10 + r() * 10, 14 + r() * 12, '', { background: colors[Math.floor(r() * colors.length)], borderRadius: '3px', zIndex: 46, opacity: 0 }), vx: (r() - 0.5) * spread * 2, vy: -up * (0.5 + r() * 0.6), vr: (r() - 0.5) * 900, d: r() * 0.12 }));
    return { update: t => bits.forEach(b => { const q = t - t0 - b.d; if (q <= 0 || q > dur) { b.e.style.opacity = 0; return; } b.e.style.opacity = (1 - clamp((q - dur * 0.6) / (dur * 0.4))).toFixed(2); b.e.style.transform = `translate(${(b.vx * q).toFixed(1)}px, ${(b.vy * q + 1500 * q * q).toFixed(1)}px) rotate(${(b.vr * q).toFixed(0)}deg) rotateX(${(q * 720).toFixed(0)}deg)`; }) };
  };
  // P.shine(el, { color }) → { update(t, t0, dur) } — a glossy light band sweeping across an element (put on cards and buttons)
  P.shine = (host, o = {}) => {
    host.style.overflow = 'hidden';
    const s = add(host, h('div', { style: { position: 'absolute', top: '-20%', bottom: '-20%', width: '38%', left: '0', background: `linear-gradient(100deg, transparent, ${o.color || 'rgba(255,255,255,0.55)'}, transparent)`, transform: 'translateX(-200%) skewX(-18deg)', pointerEvents: 'none', zIndex: 20 } }));
    return { update: (t, t0, dur = 0.55) => { const u = clamp((t - t0) / dur); s.style.transform = `translateX(${lerp(-200, 340, eio(u)).toFixed(1)}%) skewX(-18deg)`; s.style.opacity = u > 0 && u < 1 ? 1 : 0; } };
  };
  // P.crack(p, x, y, w, h, t0, { seed }) → { update(t) } — jagged red cracks that draw across a failing card
  P.crack = (p, x, y, w, hh, t0, o = {}) => {
    const r = rng(o.seed || 7), paths = [];
    for (let k = 0; k < (o.n || 3); k++) { let px0 = r() * w, py0 = r() < 0.5 ? 0 : hh, d = `M${px0.toFixed(0)} ${py0.toFixed(0)}`; const steps = 6; for (let i = 1; i <= steps; i++) { px0 += (r() - 0.5) * w * 0.35; py0 = py0 === 0 || py0 < hh / 2 ? py0 + hh / steps * (0.6 + r() * 0.6) : py0 - hh / steps * (0.6 + r() * 0.6); d += ` L${clamp(px0, 0, w).toFixed(0)} ${clamp(py0, 0, hh).toFixed(0)}`; } paths.push(d); }
    const el = div(p, 'abs', x, y, w, hh, `<svg width="${w}" height="${hh}" style="overflow:visible">${paths.map(d => `<path d="${d}" fill="none" stroke="#ff4d5e" stroke-width="3.5" stroke-linejoin="round" pathLength="100" stroke-dasharray="100" stroke-dashoffset="100" style="filter:drop-shadow(0 0 6px #e5283a)"/>`).join('')}</svg>`, { pointerEvents: 'none', zIndex: 30 });
    const ps = [...el.querySelectorAll('path')];
    return { el, update: t => ps.forEach((pp, i) => pp.setAttribute('stroke-dashoffset', (100 * (1 - eo((t - t0 - i * 0.06) / 0.3))).toFixed(1))) };
  };
  // P.orbiters(p, cx, cy, n, { rx, ry, t0, s, hues }) → { els, update(t) } — little avatar bubbles drifting around a card ("people watching")
  P.orbiters = (p, cx, cy, n, o = {}) => {
    const { rx = 420, ry = 300, t0 = 0, s = 56, stagger = 0.12 } = o, r = rng(n * 13 + Math.round(cx));
    const cols = ['#f070e4', '#d65cf0', '#ff7a9a', '#c86af2', '#ff9ad8'];
    const els = Array.from({ length: n }, (_, i) => ({ e: div(p, 'abs', cx - s / 2, cy - s / 2, s, s, F.PERSON('rgba(255,255,255,0.92)'), { borderRadius: '50%', background: `radial-gradient(circle at 35% 30%, ${cols[i % cols.length]}, #3a1036 140%)`, border: '3px solid rgba(255,255,255,0.75)', boxShadow: '0 10px 24px rgba(0,0,0,0.35)', overflow: 'hidden', zIndex: 35 }), a: (i / n) * TAU + r() * 0.5, sp: 0.18 + r() * 0.2, k: 0.85 + r() * 0.3, t: t0 + i * stagger }));
    return { els, update: t => els.forEach((b, i) => { const a = b.a + t * b.sp * (i % 2 ? -1 : 1); F.enter(b.e, t, b.t, { from: 'pop', float: 0, x: Math.cos(a) * rx * b.k, y: Math.sin(a) * ry * b.k + Math.sin(t * 2 + i) * 8 }); }) };
  };
  // P.cursor2(p, label, color) → cursor with a name tag (someone else's cursor); drive it with F.drive like F.cursor
  P.cursor2 = (p, label = 'someone', color = M.red) => {
    const c = F.cursor(p);
    c.c.innerHTML = `<svg viewBox="0 0 24 24" width="56" height="56"><path d="M4 2.5l15 9.2-6.6 1.3 3.9 7.4-2.9 1.5-3.9-7.4L4.8 19z" fill="${color}" stroke="#fff" stroke-width="1.4" stroke-linejoin="round"/></svg><span style="position:absolute;left:44px;top:44px;background:${color};color:#fff;font-size:22px;font-weight:750;padding:4px 12px;border-radius:10px;white-space:nowrap">${label}</span>`;
    return c;
  };
  // P.skel(host, x, y, w, h, { light, r }) → { el, update(t, tDone) } — a shimmering loading placeholder (fades out at tDone)
  P.skel = (host, x, y, w, hh, o = {}) => {
    const el = div(host, 'abs', x, y, w, hh, '', { borderRadius: px(o.r ?? 18), overflow: 'hidden', background: o.light ? '#ece7ec' : 'rgba(255,255,255,0.06)', border: o.light ? '1px solid rgba(0,0,0,0.04)' : '1px solid rgba(255,255,255,0.08)' });
    const band = add(el, h('div', { style: { position: 'absolute', top: 0, bottom: 0, width: '60%', background: `linear-gradient(90deg, transparent, ${o.light ? 'rgba(255,255,255,0.9)' : 'rgba(226,79,214,0.25)'}, transparent)` } }));
    return { el, update: (t, tDone) => { band.style.transform = `translateX(${(((t * 1.6) % 1.4) * 100 * 2 - 120).toFixed(0)}%)`; el.style.opacity = (1 - clamp((t - tDone) / 0.15)).toFixed(2); } };
  };
  // P.vib(p, cx, cy, { w, h, color }) → { update(t, on) } — "((( )))" buzz lines either side of a phone
  P.vib = (p, cx, cy, o = {}) => {
    const { w = 420, color = 'rgba(255,255,255,0.75)' } = o;
    const mk = (side) => div(p, 'abs', cx + side * w / 2 - 40, cy - 60, 80, 120, `<svg width="80" height="120"><g fill="none" stroke="${color}" stroke-width="5" stroke-linecap="round">${[0, 1, 2].map(i => `<path d="M${40 + side * (8 + i * 14)} ${30 - i * 10} Q ${40 + side * (22 + i * 14)} 60 ${40 + side * (8 + i * 14)} ${90 + i * 10}"/>`).join('')}</g></svg>`, { zIndex: 30 });
    const L = mk(-1), R = mk(1);
    return { update: (t, on) => [L, R].forEach((e, i) => { e.style.opacity = (on * (0.5 + 0.5 * Math.abs(Math.sin(t * 14)))).toFixed(2); e.style.transform = `translateX(${((i ? 1 : -1) * Math.sin(t * 40) * 3).toFixed(1)}px)`; }) };
  };
})();
```

### `engine/film.css`

```css
/* Stamp · STYLE. 1080x1920, glossy magenta UI illustrations on a dark grid with a spotlight beam / an off-white grid,
   magenta selection boxes on key words, a red rubber BUT stamp on every "but" (after @getripping's escalation reels).
   Tokens: magenta #c42fb8 · light magenta #e24fd6 · mid #ec7fe3 · pale pink #f7c2f2 · stamp red #e5283a · ink #f3f3f1 ·
   dark base #0b0c0a · light base #f2f2ee · Claude orange #d97757 (Claude only).
   Fonts: Inter Tight (../../shared/fonts, loaded in film.html) · Cascadia Mono · Arial Black (the stamp) · Segoe UI Emoji (caption emoji) ·
   Sitka Banner (warm look) · Bahnschrift condensed (moody look). */
:root {
  --blue: #c42fb8; --mag: #c42fb8; --mag2: #e24fd6; --mid: #ec7fe3; --lav: #f7c2f2; --pale: #fbe6f9; --red: #e5283a; --ink: #f3f3f1;
  --stamp: 'Arial Black', 'Archivo Black', 'Segoe UI Black', sans-serif; --emoji: 'Segoe UI Emoji', 'Apple Color Emoji', 'Noto Color Emoji', sans-serif;
  --claude: #d97757; --mono: 'Cascadia Mono', 'Cascadia Code', Consolas, monospace;
  --serif: 'Sitka Banner', 'Sitka Display', Georgia, serif;
  --cond: Bahnschrift, 'Inter Tight', sans-serif;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { width: 1080px; height: 1920px; overflow: hidden; background: #0b0c0a; }
body { font-family: 'Inter Tight', system-ui, sans-serif; color: var(--ink); -webkit-font-smoothing: antialiased; }
#film { position: relative; width: 1080px; height: 1920px; overflow: hidden; }
#stage { position: absolute; inset: 0; }
.layer, .scene, .over { position: absolute; inset: 0; }
/* a scene is authored on the 1080x1920 canvas around (540, 1000); the engine scales it into the safe zone */
.scene { transform-origin: 540px 1000px; will-change: transform, opacity, filter; }
.rig { position: absolute; inset: 0; transform-origin: 540px 1000px; }
.abs { position: absolute; }

/* looks (backgrounds), crossfaded by F.looks() */
.bg-dark { background-color: #0b0c0a; background-image: linear-gradient(rgba(255,255,255,0.05) 1.5px, transparent 1.5px), linear-gradient(90deg, rgba(255,255,255,0.05) 1.5px, transparent 1.5px); background-size: 72px 72px; background-position: -8px 12px; }
.bg-dark::after { content: ''; position: absolute; inset: 0; background: radial-gradient(1100px 900px at 50% 45%, transparent 40%, rgba(0,0,0,0.55) 100%); }
.bg-light { background-color: #f2f2ee; background-image: linear-gradient(rgba(20,20,10,0.065) 1.5px, transparent 1.5px), linear-gradient(90deg, rgba(20,20,10,0.065) 1.5px, transparent 1.5px), radial-gradient(900px 800px at 50% 50%, #f6f6f2 0%, #f2f2ee 70%); background-size: 72px 72px, 72px 72px, 100% 100%; background-position: -8px 12px, -8px 12px, 0 0; }
.bg-warm { background: radial-gradient(900px 700px at 22% 12%, rgba(255,196,140,0.55), transparent 65%), radial-gradient(1000px 900px at 80% 95%, rgba(214,140,96,0.28), transparent 70%), linear-gradient(170deg, #f6eadb 0%, #efdcc6 55%, #e6cdb2 100%); }
.bg-moody { background: radial-gradient(900px 700px at 50% 30%, rgba(46,86,104,0.32), transparent 70%), linear-gradient(180deg, #070a0d 0%, #0b1116 60%, #05070a 100%); }
/* the dark look's stage light: a magenta-topped beam from behind the captions down to the floor, sparkles, haze */
.beam { position: absolute; left: 0; top: 0; width: 1080px; height: 1920px; pointer-events: none; }
.beam .cone { position: absolute; left: 0; top: 380px; width: 1080px; height: 1540px; clip-path: polygon(41% 0, 59% 0, 82% 100%, 18% 100%); background: linear-gradient(180deg, rgba(214,66,200,0.24) 0%, rgba(160,60,150,0.10) 22%, rgba(255,255,255,0.045) 55%, rgba(255,255,255,0.025) 100%); filter: blur(10px); }
.beam .hot { position: absolute; left: 290px; top: 300px; width: 500px; height: 320px; border-radius: 50%; background: radial-gradient(closest-side, rgba(220,70,205,0.22), transparent); filter: blur(24px); }
.beam .haze { position: absolute; border-radius: 50%; background: radial-gradient(closest-side, rgba(200,60,185,0.16), transparent); filter: blur(30px); }
.beam .star { position: absolute; filter: drop-shadow(0 0 6px rgba(255,255,255,0.9)); }
.beam .dust { position: absolute; width: 4px; height: 4px; border-radius: 50%; background: rgba(255,255,255,0.45); }
.grain { position: absolute; inset: -64px; pointer-events: none; mix-blend-mode: overlay; }
.leak { position: absolute; width: 1400px; height: 1400px; border-radius: 50%; background: radial-gradient(closest-side, rgba(255,170,90,0.55), rgba(255,120,60,0.18) 50%, transparent 72%); mix-blend-mode: screen; pointer-events: none; }
.flash { position: absolute; inset: 0; background: #fff; opacity: 0; pointer-events: none; }

/* captions: word by word, centred on x 510, at most 820 wide (RULES safe zone). Top: from y 270. Band: from y 1170. */
.cap { position: absolute; left: 100px; width: 820px; top: 270px; text-align: center; font-size: 56px; font-weight: 620; line-height: 1.24; letter-spacing: -0.022em; text-wrap: balance; transform-origin: 50% 0; }
#film.cap-band .cap { top: 1170px; }
.cap .w { display: inline-block; position: relative; white-space: pre; }
.cap .k { padding: 0 10px; margin: 0 3px; font-weight: 700; isolation: isolate; }
.cap .k .bx { position: absolute; inset: -1px 0 -2px 0; border-radius: 6px; z-index: -1; }
.cap .k .hd { position: absolute; width: 13px; height: 13px; border-radius: 50%; background: #e24fd6; box-shadow: 0 0 8px rgba(226,79,214,0.7); }
.cap .ic { display: inline-block; margin-right: 10px; font-family: var(--emoji); font-size: 50px; line-height: 1; vertical-align: -4px; }
.cap.m-dark, .cap.m-moody { color: #8b8c8f; }
.cap.m-dark .cur, .cap.m-moody .cur, .cap.m-dark .k, .cap.m-moody .k { color: #f3f3f1; }
.cap.m-dark .k .bx, .cap.m-moody .k .bx { background: rgba(120,16,110,0.55); border: 2.5px solid #d958cd; border-radius: 3px; }
.cap.m-light { color: #9a9b9e; }
.cap.m-light .cur, .cap.m-light .k { color: #121316; }
.cap.m-light .k .bx { background: rgba(226,79,214,0.22); border: 2.5px solid #c42fb8; border-radius: 3px; }
.cap.m-warm { font-family: var(--serif); font-weight: 600; font-size: 64px; letter-spacing: -0.01em; color: #b39c86; }
.cap.m-warm .cur, .cap.m-warm .k { color: #3a281c; }
.cap.m-warm .k { font-style: italic; font-weight: 700; }
.cap.m-warm .k .bx { background: rgba(196,104,62,0.14); border: 2.5px solid #c4683e; }
.cap.m-warm .k .hd { border: 3px solid #c4683e; }
.cap.m-moody { font-family: var(--cond); font-stretch: 75%; font-weight: 700; font-size: 96px; letter-spacing: 0.01em; text-transform: uppercase; line-height: 1.02; }

/* cards & chips */
.gcard { position: absolute; background: linear-gradient(180deg, #202126 0%, #15161a 100%); border: 1px solid rgba(255,255,255,0.09); border-radius: 30px; box-shadow: 0 50px 110px rgba(0,0,0,0.62), inset 0 1px 0 rgba(255,255,255,0.09); overflow: hidden; }
.lcard { position: absolute; background: linear-gradient(180deg, #ffffff 0%, #f6f6f8 100%); border: 1px solid rgba(20,22,40,0.07); border-radius: 30px; box-shadow: 0 46px 90px rgba(90,30,80,0.13), 0 10px 24px rgba(0,0,0,0.06), inset 0 1px 0 #fff; overflow: hidden; color: #141518; }
.pill { position: absolute; display: inline-flex; align-items: center; gap: 12px; height: 62px; padding: 0 26px; border-radius: 999px; font-size: 26px; font-weight: 650; white-space: nowrap; }
.pill.dk { background: rgba(30,31,36,0.96); border: 1px solid rgba(255,255,255,0.12); box-shadow: 0 18px 44px rgba(0,0,0,0.5); color: #e7e8ea; }
.pill.lt { background: #fff; border: 1px solid rgba(0,0,0,0.07); box-shadow: 0 16px 40px rgba(20,30,80,0.14); color: #141518; }
.pill.bl { background: linear-gradient(180deg, #e24fd6, #b0219f); color: #fff; box-shadow: 0 16px 44px rgba(196,47,184,0.5), inset 0 1px 0 rgba(255,255,255,0.3); }
.pill.rd { background: linear-gradient(180deg, #ff4b5c, #e02a3d); color: #fff; box-shadow: 0 14px 36px rgba(240,56,75,0.45), inset 0 1px 0 rgba(255,255,255,0.3); }
.dot { display: inline-block; width: 14px; height: 14px; border-radius: 50%; }
.mono { font-family: var(--mono); }
.cursor { position: absolute; width: 56px; height: 56px; z-index: 60; filter: drop-shadow(0 6px 12px rgba(0,0,0,0.5)); }
.ripple { position: absolute; width: 90px; height: 90px; margin: -45px 0 0 -45px; border-radius: 50%; border: 4px solid #ec7fe3; opacity: 0; z-index: 59; }
.stamp { position: absolute; width: 110px; height: 110px; border-radius: 50%; background: linear-gradient(180deg, #ff4b5c, #d9263a); box-shadow: 0 14px 34px rgba(240,56,75,0.55), inset 0 2px 0 rgba(255,255,255,0.3); display: flex; align-items: center; justify-content: center; }
.check { position: absolute; border-radius: 50%; background: linear-gradient(180deg, #e24fd6, #b0219f); box-shadow: 0 12px 30px rgba(196,47,184,0.55), inset 0 2px 0 rgba(255,255,255,0.35); display: flex; align-items: center; justify-content: center; }
.photo { position: absolute; background: #fbf7f1; padding: 14px 14px 46px; border-radius: 6px; box-shadow: 0 30px 60px rgba(80,44,20,0.28), 0 6px 14px rgba(80,44,20,0.12); }
.photo img { display: block; width: 100%; height: 100%; object-fit: cover; border-radius: 2px; }
.bubble { position: absolute; padding: 26px 34px; border-radius: 34px; font-size: 36px; font-weight: 600; line-height: 1.25; white-space: nowrap; }
.bubble.me { background: linear-gradient(180deg, #e24fd6, #c42fb8); color: #fff; border-bottom-right-radius: 10px; box-shadow: 0 18px 40px rgba(196,47,184,0.4); }
.caret { display: inline-block; width: 3px; height: 1em; background: currentColor; vertical-align: -0.12em; margin-left: 3px; }

/* safe-zone overlay (film.html?safe, render.mjs --safe): RULES.md rule 7 */
#safe { position: absolute; inset: 0; pointer-events: none; z-index: 1000; }
#safe .no { position: absolute; background: rgba(255,40,80,0.16); }
#safe .zone { position: absolute; left: 60px; top: 250px; width: 900px; height: 1230px; border: 3px solid #19e3ff; }
#safe .band { position: absolute; left: 60px; top: 1150px; width: 900px; height: 330px; border: 3px dashed #ffd400; background: rgba(255,212,0,0.07); }
#safe .mid { position: absolute; left: 509px; top: 250px; width: 2px; height: 1230px; background: rgba(25,227,255,0.45); }
#safe .capbox { position: absolute; left: 100px; width: 820px; border: 2px dotted rgba(25,227,255,0.8); }
#safe .lbl { position: absolute; font: 700 22px/1 Consolas, monospace; color: #19e3ff; background: rgba(0,0,0,0.6); padding: 4px 8px; }
#safe .bad { position: absolute; border: 4px solid #ff2050; background: rgba(255,32,80,0.18); }

/* Stamp parts */
.but { position: absolute; display: flex; align-items: center; justify-content: center; color: #e5283a; font-family: var(--stamp); font-weight: 900; letter-spacing: 0.02em; line-height: 1; border: 9px solid #e5283a; border-radius: 22px; outline: 3.5px solid #e5283a; outline-offset: 7px; white-space: nowrap; }
.ban { position: absolute; border-radius: 50%; border: 26px solid #e5283a; box-shadow: 0 0 60px rgba(229,40,58,0.55), inset 0 0 40px rgba(229,40,58,0.35); }
.ban::after { content: ''; position: absolute; left: 50%; top: 50%; width: 120%; height: 26px; margin: -13px 0 0 -60%; background: #e5283a; transform: rotate(-45deg); border-radius: 4px; box-shadow: 0 0 30px rgba(229,40,58,0.5); }
.sticker { position: absolute; display: inline-flex; align-items: center; gap: 10px; padding: 0 30px; height: 76px; border-radius: 999px; background: linear-gradient(180deg, #f070e4, #c42fb8); color: #fff; font-size: 38px; font-weight: 760; letter-spacing: -0.01em; white-space: nowrap; box-shadow: 0 18px 40px rgba(196,47,184,0.5), inset 0 2px 0 rgba(255,255,255,0.35); border: 3px solid rgba(255,255,255,0.85); }
.chip { position: absolute; display: inline-flex; align-items: center; gap: 8px; height: 40px; padding: 0 16px; border-radius: 999px; font-size: 21px; font-weight: 700; white-space: nowrap; background: #fbe6f9; color: #b0219f; border: 1.5px solid rgba(196,47,184,0.35); }
.chip.dk { background: rgba(196,47,184,0.22); color: #f7a6f0; border-color: rgba(226,79,214,0.5); }
.chip.rd { background: rgba(229,40,58,0.12); color: #e5283a; border-color: rgba(229,40,58,0.45); }
.chip.rdk { background: rgba(229,40,58,0.2); color: #ff6b78; border-color: rgba(229,40,58,0.55); }
.chip.on { background: linear-gradient(180deg, #e24fd6, #b0219f); color: #fff; border-color: transparent; box-shadow: 0 8px 20px rgba(196,47,184,0.45); }
.tile { position: absolute; overflow: hidden; border-radius: 18px; }
.mag { background: linear-gradient(180deg, #e24fd6, #b0219f); color: #fff; }
.emo { font-family: var(--emoji); }
```

### `engine/markup.mjs`

```js
// Stamp · SCRIPT MARKUP (Node side: gen-vo, build-timeline, check). Same for every video.
//   /  a new caption phrase   *word*  key word, boxed (*two_words* = one box)   @icon:*word*  icon chip   {shown|said}  spelling
// The voice + its settings live here as the template's defaults; a video's script.mjs may override VOICE / MODEL / SETTINGS / SPEED.
import { pathToFileURL } from 'node:url';
import path from 'node:path';
import fs from 'node:fs';

export const DEFAULTS = {
  VOICE: 'TX3LPaxmHKxFdv7VOQHJ',   // ElevenLabs "Liam" (premade): the closest match to the reference (~127 Hz, ~200 wpm)
  MODEL: 'eleven_multilingual_v2',
  SETTINGS: { stability: 0.4, similarity_boost: 0.8, style: 0.5, use_speaker_boost: true },
  SPEED: 1.1,
  GAP: 0.24, LEAD: 0.1, HOLD: 1.8,
};

// tokens: [{ d: shown text, said: spoken text, key, icon, br (a phrase starts here) }]
export function parse(src) {
  const out = []; let br = true;
  const parts = src.match(/[^\s{]*\{[^}]*\}\S*|\S+/g) || [];
  for (let raw of parts) {
    if (raw === '/') { br = true; continue; }
    let icon = null, key = false;
    if (raw.startsWith('@')) { const i = raw.indexOf(':'); icon = raw.slice(1, i); raw = raw.slice(i + 1); }
    let core = raw, tail = '';
    if (raw.startsWith('*')) { const j = raw.lastIndexOf('*'); key = true; core = raw.slice(1, j); tail = raw.slice(j + 1); }
    let d = core, said = core;
    const m = core.match(/^(.*)\{([^|]*)\|([^}]*)\}(.*)$/);
    if (m) { d = m[1] + m[2] + m[4]; said = m[1] + m[3] + m[4]; }
    d = d.replace(/_/g, ' ') + tail; said = said.replace(/_/g, ' ') + tail;
    out.push({ d, said, key, icon, br }); br = false;
  }
  return out;
}
export const spoken = src => parse(src).map(t => t.said).join(' ');

// Re-apply new markup (boxes, phrase breaks, icons) to an existing take without new voice: the spoken words must be the
// same. Word times come from the take's tokens (a multi-word token's span is shared out by characters).
export function retime(toks, old) {
  const words = [];
  for (const k of old) {
    const ws = k.said.split(' '), total = ws.reduce((a, w) => a + w.length, 0) || 1; let acc = 0;
    for (const w of ws) { const a = k.t0 + (k.t1 - k.t0) * acc / total; acc += w.length; words.push({ w, t0: a, t1: k.t0 + (k.t1 - k.t0) * acc / total }); }
  }
  let wi = 0;
  return toks.map(t => { const n = t.said.split(' ').length, span = words.slice(wi, wi + n); wi += n; return { ...t, t0: +span[0].t0.toFixed(3), t1: +span.at(-1).t1.toFixed(3) }; });
}

// load <video>/script.mjs with the template defaults filled in
export async function loadScript(vdir) {
  const f = path.join(vdir, 'script.mjs');
  if (!fs.existsSync(f)) throw new Error(`no ${f}: write script.mjs and scenes.js in your video folder first (Appendix B is a complete example)`);
  const m = await import(pathToFileURL(f).href + '?t=' + Date.now());
  const s = { ...DEFAULTS, ...m };
  for (const id of s.ORDER) if (!s.TAKES[id]) throw new Error(`script.mjs: ORDER has "${id}" but TAKES has no such take`);
  return s;
}
// --video <dir> (default: video) → absolute path, must sit inside the template/work folder (film.html loads it relatively)
export function videoDir(here, argv = process.argv) {
  const i = argv.indexOf('--video'), rel = i > 0 ? argv[i + 1] : 'video';
  const abs = path.resolve(here, rel), r = path.relative(here, abs);
  if (!r || r.startsWith('..') || path.isAbsolute(r)) throw new Error(`--video must be a folder inside ${here} (got ${rel})`);
  return { abs, rel: r.split(path.sep).join('/') };
}
```

### `setup.mjs`

```js
// Stamp · requirements check + the free fonts + the synthesized sounds. Run once per project:  node setup.mjs
// Node 22+, ffmpeg/ffprobe on PATH, Chrome or Edge, the ElevenLabs key (never printed); downloads Inter Tight + Archivo Black
// (SIL OFL, google/fonts) to fonts/, makes sfx/*.wav (sounds.mjs; the bed is made per video by bed.mjs), and starts the
// browser once to prove it works.
import fs from 'node:fs'; import path from 'node:path'; import { spawnSync } from 'node:child_process';
import { CHROME, launch } from './cdp.mjs';
const HERE = import.meta.dirname;
const ok = (good, msg) => console.log(good ? '✓' : '✗', msg);
ok(+process.versions.node.split('.')[0] >= 22, 'Node ' + process.versions.node + ' (22+ needed: global fetch and WebSocket)');
for (const c of ['ffmpeg', 'ffprobe']) ok(spawnSync(c, ['-version']).status === 0, c + ' on PATH');
ok(!!CHROME, 'Chrome or Edge: ' + (CHROME || 'not found, set CHROME=<path to the browser>'));
ok(!!(process.env.ELEVENLABS_API_KEY || (fs.existsSync(path.join(HERE, '.env')) && /^ELEVENLABS_API_KEY=./m.test(fs.readFileSync(path.join(HERE, '.env'), 'utf8')))), 'ELEVENLABS_API_KEY (environment or .env) — not needed for --fake timings and stills');
const FONTS = {
  'InterTight.ttf': 'https://raw.githubusercontent.com/google/fonts/main/ofl/intertight/InterTight%5Bwght%5D.ttf',
  'ArchivoBlack-Regular.ttf': 'https://raw.githubusercontent.com/google/fonts/main/ofl/archivoblack/ArchivoBlack-Regular.ttf',
};
for (const [name, url] of Object.entries(FONTS)) {
  const font = path.join(HERE, 'fonts', name);
  if (fs.existsSync(font)) { ok(true, 'fonts/' + name); continue; }
  fs.mkdirSync(path.dirname(font), { recursive: true });
  try { const r = await fetch(url, { headers: { 'user-agent': 'Mozilla/5.0' } }); if (!r.ok) throw new Error(r.status); fs.writeFileSync(font, Buffer.from(await r.arrayBuffer())); ok(true, 'fonts/' + name + ' downloaded'); }
  catch (e) { ok(false, `fonts/${name}: download failed (${e.message}) from ${url}`); }
}
spawnSync(process.execPath, [path.join(HERE, 'sounds.mjs')], { stdio: 'inherit' });
if (CHROME) try { const api = await launch({ port: 9698 }); ok(true, 'Chrome starts headless · renderer: ' + api.gpu); await api.close(); } catch (e) { ok(false, e.message); }
setTimeout(() => process.exit(0), 300);
```

### `sounds.mjs`

```js
// Stamp · the sounds, synthesized (no downloads, no licences, no credits):  node sounds.mjs [--force]
// The 22 sounds the scenes cue, in sfx/<name>.wav: stampslam boom bassdrop whoosh swoosh uipop uitick tick tick1 tick2 tick3
// mouse send notif kaching error counter typing stopwatch cards riser sparkle. Seeded, so every project gets the same set.
// Every sound starts on its first sample (a cue lands on its frame) and peaks at -1 dB. gen-sfx.mjs can replace them with
// ElevenLabs sound effects (the reference reel's kind of sound); the music bed is made per video by bed.mjs.
import fs from 'node:fs'; import path from 'node:path';
const HERE = import.meta.dirname, SR = 48000, FORCE = process.argv.includes('--force'), TAU = Math.PI * 2;
let seed = 11; const rnd = () => (seed = (seed * 1664525 + 1013904223) >>> 0) / 4294967296;
const wav = (file, L, R = L) => {
  const n = L.length, b = Buffer.alloc(44 + n * 4);
  b.write('RIFF', 0); b.writeUInt32LE(36 + n * 4, 4); b.write('WAVEfmt ', 8); b.writeUInt32LE(16, 16); b.writeUInt16LE(1, 20); b.writeUInt16LE(2, 22);
  b.writeUInt32LE(SR, 24); b.writeUInt32LE(SR * 4, 28); b.writeUInt16LE(4, 32); b.writeUInt16LE(16, 34); b.write('data', 36); b.writeUInt32LE(n * 4, 40);
  for (let i = 0; i < n; i++) { b.writeInt16LE(Math.round(Math.max(-1, Math.min(1, L[i])) * 32767), 44 + i * 4); b.writeInt16LE(Math.round(Math.max(-1, Math.min(1, R[i])) * 32767), 46 + i * 4); }
  fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, b);
};
const norm = (a, peak = 0.89) => { let m = 0; for (const v of a) m = Math.max(m, Math.abs(v)); if (m) for (let i = 0; i < a.length; i++) a[i] *= peak / m; return a; };
const buf = s => new Float32Array(Math.round(s * SR));
const add = (a, b, at = 0, g = 1) => { const o = Math.round(at * SR); for (let i = 0; i < b.length && o + i < a.length; i++) a[o + i] += b[i] * g; return a; };
// noise through a one-pole low-pass whose cutoff follows f(u) (hp: keep only the highs), shaped by env(u)
const filtered = (dur, f, env, hp = 0) => { const a = buf(dur); let lp = 0, prev = 0; for (let i = 0; i < a.length; i++) { const u = i / a.length, k = 1 - Math.exp(-TAU * f(u) / SR); lp += k * (rnd() * 2 - 1 - lp); const v = hp ? lp - prev : lp; prev = lp; a[i] = v * env(u); } return a; };
// a sine whose pitch glides from f0 to f1 (rate r), with an exponential decay d
const sweep = (dur, f0, f1, r, d, sat = 0) => { const a = buf(dur); let ph = 0; for (let i = 0; i < a.length; i++) { const t = i / SR; ph += TAU * (f1 + (f0 - f1) * Math.exp(-t * r)) / SR; let v = Math.sin(ph) * Math.exp(-t * d) * Math.min(1, t * 3000); a[i] = sat ? Math.tanh(v * sat) / Math.tanh(sat) : v; } return a; };
const bell = (dur, f, d, parts = [[1, 1], [2.76, 0.3], [5.4, 0.12]]) => { const a = buf(dur); for (const [m, g] of parts) for (let i = 0; i < a.length; i++) { const t = i / SR; a[i] += g * Math.sin(TAU * f * m * t) * Math.exp(-t * d * m ** 0.5) * Math.min(1, t * 1500); } return a; };
const click = (dur = 0.03, f = 6000, d = 9) => filtered(dur, () => f, u => Math.exp(-u * d), 1);
const tick = f => { const a = buf(0.05); for (let i = 0; i < a.length; i++) { const t = i / SR; a[i] = (0.65 * Math.sin(TAU * f * t) + 0.35 * (rnd() * 2 - 1)) * Math.exp(-t * 170); } return a; };

const S = {
  // the stamp: a hard knock, a paper slap and a sub thump under it
  stampslam: () => { const a = buf(0.8); add(a, sweep(0.75, 140, 42, 22, 7.5, 1.6), 0, 1); add(a, filtered(0.12, () => 2600, u => Math.exp(-u * 10)), 0, 0.9); add(a, sweep(0.2, 260, 170, 40, 26), 0, 0.5); return a; },
  boom: () => { const a = sweep(1.1, 75, 34, 6, 4.8, 1.3); add(a, filtered(0.8, u => 400 * Math.exp(-u * 3) + 60, u => Math.exp(-u * 6)), 0, 0.35); return a; },
  bassdrop: () => sweep(1.2, 110, 49, 9, 1.6, 2.2),
  whoosh: () => filtered(0.6, u => 300 + 3200 * Math.sin(Math.PI * u) ** 2, u => Math.sin(Math.PI * Math.min(1, u * 1.3)) ** 2 * (1 - u * 0.3)),
  swoosh: () => filtered(0.65, u => 500 + 5200 * u, u => Math.sin(Math.PI * Math.min(1, u * 1.6)) ** 2 * (1 - u)),
  uipop: () => { const a = buf(0.18); let ph = 0; for (let i = 0; i < a.length; i++) { const t = i / SR, f = 340 + 760 * Math.exp(-t * 50); ph += TAU * f / SR; a[i] = Math.sin(ph) * Math.exp(-t * 30) * Math.min(1, t * 2500); } return a; },
  uitick: () => { const a = click(0.025, 7000, 12); add(a, tick(3100), 0, 0.6); return a; },
  tick: () => tick(2400), tick1: () => tick(2200), tick2: () => tick(2700), tick3: () => tick(3200),
  mouse: () => { const a = buf(0.12); add(a, click(0.02, 5000, 14)); add(a, sweep(0.03, 1800, 1200, 80, 120), 0, 0.4); add(a, click(0.02, 6500, 14), 0.055, 0.6); return a; },
  send: () => { const a = filtered(0.45, u => 900 + 7000 * u * u, u => Math.sin(Math.PI * u) ** 1.5); add(a, sweep(0.15, 900, 1700, 12, 18), 0.18, 0.35); return a; },
  notif: () => { const a = bell(0.8, 1318.5, 5); add(a, bell(0.7, 1975.5, 6), 0.11, 0.8); return a; },
  kaching: () => { const a = buf(1.0); add(a, filtered(0.06, () => 3000, u => Math.exp(-u * 6), 1), 0, 0.8); for (let k = 0; k < 14; k++) add(a, bell(0.5, 2600 + rnd() * 4200, 14, [[1, 1]]), 0.02 + rnd() * 0.12, 0.25); add(a, bell(1.0, 2093, 4), 0.1, 0.9); add(a, bell(0.9, 3136, 5), 0.1, 0.6); return a; },
  error: () => { const a = buf(0.6); for (let i = 0; i < a.length; i++) { const t = i / SR; a[i] = (0.6 * Math.sign(Math.sin(TAU * 150 * t)) + 0.4 * Math.sign(Math.sin(TAU * 226 * t))) * (0.6 + 0.4 * Math.sign(Math.sin(TAU * 18 * t))) * Math.min(1, t * 200) * Math.min(1, (0.6 - t) * 30) * 0.5; } return a; },
  counter: () => { const a = buf(1.5); for (let k = 0; k < 27; k++) add(a, tick(2600 + (k % 3) * 300), k / 18, 0.7 + 0.3 * (k % 2)); return a; },
  typing: () => { const a = buf(2.0); let t = 0; while (t < 1.95) { add(a, filtered(0.035, () => 2400 + rnd() * 2600, u => Math.exp(-u * 7), 1), t, 0.6 + rnd() * 0.4); add(a, sweep(0.03, 420 + rnd() * 140, 300, 60, 90), t, 0.25); t += 0.06 + rnd() * 0.09; } return a; },
  stopwatch: () => { const a = buf(2.0); for (let k = 0; k < 16; k++) add(a, sweep(0.03, 3600, 3000, 90, 160), k * 0.125, k % 2 ? 0.7 : 1); return a; },
  cards: () => { const a = buf(0.8); [0, 0.07, 0.15, 0.26].forEach((at, k) => add(a, filtered(0.16, () => 1500 + 900 * k, u => Math.exp(-u * 9) * Math.min(1, u * 60)), at, 1 - k * 0.15)); return a; },
  riser: () => { const a = filtered(1.5, u => 300 + 6500 * u * u, u => u ** 1.8); let ph = 0; for (let i = 0; i < a.length; i++) { const u = i / a.length; ph += TAU * (110 + 360 * u * u) / SR; a[i] += 0.35 * Math.sin(ph) * u ** 2; } return a; },
  sparkle: () => { const a = buf(1.0); for (let k = 0; k < 9; k++) add(a, bell(0.5, 3000 + rnd() * 3500, 10, [[1, 1], [2.01, 0.25]]), k * 0.06 + rnd() * 0.03, 0.7 - k * 0.05); return a; },
};
for (const [name, make] of Object.entries(S)) {
  const f = path.join(HERE, 'sfx', name + '.wav'); if (fs.existsSync(f) && !FORCE) { console.log('kept sfx/' + name + '.wav'); continue; }
  const a = norm(make()); wav(f, a); console.log('made sfx/' + name + '.wav');
}
```

### `gen-sfx.mjs`

```js
// Stamp · OPTIONAL: the sound set from ElevenLabs sound-generation, the kind of sound the reference reel has (punchier and
// more "produced" than the synthesized set). Replaces sfx/<name>.wav; each sound is trimmed to its first sample and
// normalised to -1 dB peak, so a cue lands on its frame.
//   node gen-sfx.mjs [name …]          dry run: the prompts, nothing sent
//   node gen-sfx.mjs --yes [name …]    make them (no names: all 22). Uses ElevenLabs credits; the key as for gen-vo.mjs.
// Undo: node sounds.mjs --force (back to the synthesized set).
import fs from 'node:fs'; import path from 'node:path'; import { execFileSync } from 'node:child_process';
const HERE = import.meta.dirname, SR = 44100;
const SFX = {
  stampslam: ['a rubber stamp slammed hard onto a desk, punchy thump with a deep cinematic sub bass hit, dry, no reverb tail', 1.0],
  boom: ['deep cinematic sub bass boom hit with a short tail, trailer impact', 1.5],
  bassdrop: ['short punchy 808 bass drop hit, modern beat', 1.2],
  whoosh: ['fast short airy whoosh for a motion graphics transition, clean', 0.6],
  swoosh: ['quick swoosh of a card sliding up into place, motion graphics, short', 0.7],
  uipop: ['a single loud bubbly pop sound, like a cartoon bubble popping, short and punchy', 0.5],
  uitick: ['a single crisp short UI click sound, one tap on a glass phone screen, loud and clear', 0.5],
  tick: ['one tiny crisp digital tick, very short', 0.5], tick1: ['one tiny soft digital tick, very short, lower', 0.5],
  tick2: ['one tiny crisp digital tick, very short, brighter', 0.5], tick3: ['one tiny glassy digital tick, very short, high', 0.5],
  mouse: ['one computer mouse click, close and crisp', 0.5],
  send: ['message sent swoosh, short bright upward whoosh like sending a text', 0.6],
  notif: ['a bright short phone notification ding, modern app', 0.7],
  kaching: ['short cash register ka-ching with coins, bright', 1.0],
  error: ['short harsh digital error glitch buzz, app failing', 0.7],
  counter: ['rapid digital counter ticking as numbers roll up quickly, soft clicks', 1.5],
  typing: ['fast typing on a laptop keyboard, crisp keys, close mic', 2.0],
  stopwatch: ['a stopwatch ticking fast, clean mechanical ticks', 2.0],
  cards: ['several paper cards and envelopes landing on a desk in a pile, quick rustle and slaps', 1.0],
  riser: ['short tension riser whoosh building up into a hit', 1.5],
  sparkle: ['short magical sparkle shimmer twinkle', 1.0],
};
const names = process.argv.slice(2).filter(a => !a.startsWith('--'));
const want = names.length ? names : Object.keys(SFX);
for (const n of want) if (!SFX[n]) { console.error('unknown sound ' + n + ' (one of: ' + Object.keys(SFX).join(' ') + ')'); process.exit(1); }
if (!process.argv.includes('--yes')) { for (const n of want) console.log(` ${n} (${SFX[n][1]} s): ${SFX[n][0]}`); console.log(`dry run: nothing sent. Add --yes to make these ${want.length} sounds with ElevenLabs (uses credits).`); process.exit(0); }
const envFile = path.join(HERE, '.env');
const env = Object.fromEntries((fs.existsSync(envFile) ? fs.readFileSync(envFile, 'utf8') : '').split(/\r?\n/).filter(l => /^[A-Z_]+=/.test(l)).map(l => { const i = l.indexOf('='); return [l.slice(0, i), l.slice(i + 1).trim().replace(/^["']|["']$/g, '')]; }));
const KEY = env.ELEVENLABS_API_KEY || process.env.ELEVENLABS_API_KEY;
if (!KEY) { console.error('no ELEVENLABS_API_KEY (environment, or .env in the project folder)'); process.exit(1); }
const wav = (file, x, ch) => {
  const n = x.length / ch, b = Buffer.alloc(44 + x.length * 2);
  b.write('RIFF', 0); b.writeUInt32LE(36 + x.length * 2, 4); b.write('WAVEfmt ', 8); b.writeUInt32LE(16, 16); b.writeUInt16LE(1, 20); b.writeUInt16LE(ch, 22);
  b.writeUInt32LE(SR, 24); b.writeUInt32LE(SR * 2 * ch, 28); b.writeUInt16LE(2 * ch, 32); b.writeUInt16LE(16, 34); b.write('data', 36); b.writeUInt32LE(x.length * 2, 40);
  for (let i = 0; i < x.length; i++) b.writeInt16LE(Math.round(Math.max(-1, Math.min(1, x[i])) * 32767), 44 + i * 2);
  fs.writeFileSync(file, b);
};
fs.mkdirSync(path.join(HERE, 'sfx'), { recursive: true });
for (const n of want) {
  const [text, dur] = SFX[n];
  const r = await fetch('https://api.elevenlabs.io/v1/sound-generation?output_format=mp3_44100_128', { method: 'POST', headers: { 'xi-api-key': KEY, 'content-type': 'application/json' }, body: JSON.stringify({ text, duration_seconds: dur, prompt_influence: 0.6 }) });
  if (!r.ok) { console.log(n, r.status, (await r.text()).slice(0, 200)); continue; }
  const mp3 = path.join(HERE, 'sfx', n + '.tmp.mp3');
  fs.writeFileSync(mp3, Buffer.from(await r.arrayBuffer()));
  const raw = execFileSync('ffmpeg', ['-v', 'error', '-i', mp3, '-ac', '2', '-ar', String(SR), '-f', 's16le', '-'], { maxBuffer: 1 << 28 }); fs.rmSync(mp3);
  const x = new Float32Array(raw.length / 2); for (let i = 0; i < x.length; i++) x[i] = raw.readInt16LE(i * 2) / 32768;
  let pk = 0; for (const v of x) pk = Math.max(pk, Math.abs(v));
  let on = 0; while (on < x.length && Math.abs(x[on]) < pk * 0.08) on++;
  on = Math.max(0, on - Math.round(0.004 * SR) * 2) & ~1;
  const y = x.slice(on).map(v => v / pk * 0.89), fi = Math.round(0.004 * SR) * 2, fo = Math.round(0.03 * SR) * 2;
  for (let i = 0; i < fi && i < y.length; i++) y[i] *= i / fi;
  for (let i = 0; i < fo && i < y.length; i++) y[y.length - 1 - i] *= i / fo;
  wav(path.join(HERE, 'sfx', n + '.wav'), y, 2);
  console.log(n, 'ok', (y.length / 2 / SR).toFixed(2) + ' s', on ? `(trimmed ${(on / 2 / SR).toFixed(3)} s of lead-in)` : '');
}
```

### `bed.mjs`

```js
// Stamp · THE BED → <video>/bed.wav, cut to this video's length (render.mjs --mix uses it). Run after build-timeline.mjs,
// and again whenever the timing changes.
//   node bed.mjs --video <dir>                     a synthesized dark minimal trap beat at 105 bpm, no credits: a deep 808 that
//                                                  follows A - F - C - G (one bar each), a punchy kick, ticking hats with
//                                                  triplet rolls, snaps on 2 and 4, a quiet low pad. Starts on the downbeat,
//                                                  no intro and no drop, so the voice rides on it from the first word.
//   node bed.mjs --video <dir> --eleven [--yes]    the reference's kind of bed from ElevenLabs Music (a dry run without --yes):
//                                                  57 s are made once into bed-eleven.mp3 in the project, then 11 bars of its
//                                                  steady middle (from 10 s) are looped with crossfades to the video's length.
//   --bpm 105   (the synthesized beat's tempo)
import fs from 'node:fs'; import path from 'node:path'; import os from 'node:os'; import { execFileSync } from 'node:child_process';
import { videoDir } from './engine/markup.mjs';
try { os.setPriority(os.constants.priority.PRIORITY_LOW); } catch { }
const HERE = import.meta.dirname, { abs: VD } = videoDir(HERE), SR = 48000, TAU = Math.PI * 2;
const TLf = path.join(VD, 'timeline.json');
if (!fs.existsSync(TLf)) { console.error('no timeline.json: run build-timeline.mjs first'); process.exit(1); }
const TL = JSON.parse(fs.readFileSync(TLf, 'utf8'));
const arg = (k, d) => { const i = process.argv.indexOf(k); return i > 0 ? process.argv[i + 1] : d; };
const dur = TL.total + 0.5, n = Math.round(dur * SR);
const writeWav = (file, L, R) => {
  const b = Buffer.alloc(44 + L.length * 4);
  b.write('RIFF', 0); b.writeUInt32LE(36 + L.length * 4, 4); b.write('WAVEfmt ', 8); b.writeUInt32LE(16, 16); b.writeUInt16LE(1, 20); b.writeUInt16LE(2, 22);
  b.writeUInt32LE(SR, 24); b.writeUInt32LE(SR * 4, 28); b.writeUInt16LE(4, 32); b.writeUInt16LE(16, 34); b.write('data', 36); b.writeUInt32LE(L.length * 4, 40);
  for (let i = 0; i < L.length; i++) { b.writeInt16LE(Math.round(Math.max(-1, Math.min(1, L[i])) * 32767), 44 + i * 4); b.writeInt16LE(Math.round(Math.max(-1, Math.min(1, R[i])) * 32767), 46 + i * 4); }
  fs.writeFileSync(file, b);
};
const out = path.join(VD, 'bed.wav');

if (process.argv.includes('--eleven')) {
  const src = path.join(HERE, 'bed-eleven.mp3');
  if (!fs.existsSync(src)) {
    const prompt = 'Instrumental background beat for a fast tech motion-graphics explainer reel with a voiceover on top. Dark minimal modern trap beat at 105 BPM: deep warm 808 sub bass, tight punchy kick, crisp ticking hi-hats, soft snaps, a subtle airy synth pad. Steady and driving from the first second to the end, no intro build, no drops, no vocals, no lead melody, leaves room for a voice.';
    if (!process.argv.includes('--yes')) { console.log('prompt: ' + prompt + '\ndry run: nothing sent. Add --yes to make a 57 s bed with ElevenLabs Music (uses credits; made once per project).'); process.exit(0); }
    const envFile = path.join(HERE, '.env');
    const env = Object.fromEntries((fs.existsSync(envFile) ? fs.readFileSync(envFile, 'utf8') : '').split(/\r?\n/).filter(l => /^[A-Z_]+=/.test(l)).map(l => { const i = l.indexOf('='); return [l.slice(0, i), l.slice(i + 1).trim().replace(/^["']|["']$/g, '')]; }));
    const KEY = env.ELEVENLABS_API_KEY || process.env.ELEVENLABS_API_KEY;
    if (!KEY) { console.error('no ELEVENLABS_API_KEY (environment, or .env in the project folder)'); process.exit(1); }
    const r = await fetch('https://api.elevenlabs.io/v1/music?output_format=mp3_44100_128', { method: 'POST', headers: { 'xi-api-key': KEY, 'content-type': 'application/json' }, body: JSON.stringify({ prompt, music_length_ms: 57000, force_instrumental: true }) });
    if (!r.ok) { console.error('ElevenLabs Music:', r.status, (await r.text()).slice(0, 300), '\nUse the synthesized bed instead: node bed.mjs --video ' + path.relative(HERE, VD)); process.exit(1); }
    fs.writeFileSync(src, Buffer.from(await r.arrayBuffer()));
    console.log('made bed-eleven.mp3 (57 s)');
  }
  const raw = execFileSync('ffmpeg', ['-v', 'error', '-i', src, '-ac', '2', '-ar', String(SR), '-f', 's16le', '-'], { maxBuffer: 1 << 29 });
  const x = new Float32Array(raw.length / 2); for (let i = 0; i < x.length; i++) x[i] = raw.readInt16LE(i * 2) / 32768;
  const BAR = 4 * 60 / 105, a0 = Math.round(10 * SR), len = Math.round(11 * BAR * SR), xf = Math.round(0.05 * SR);
  const L = new Float32Array(n), R = new Float32Array(n);
  for (let o = 0; o < n; o += len) for (let i = 0; i < len + xf && o + i < n; i++) {
    const s = a0 + i; if (s * 2 + 1 >= x.length) break;
    const g = o > 0 && i < xf ? i / xf : i >= len ? 1 - (i - len) / xf : 1;
    L[o + i] += x[s * 2] * g; R[o + i] += x[s * 2 + 1] * g;
  }
  writeWav(out, L, R); console.log(`bed.wav ${dur.toFixed(1)} s from ElevenLabs Music (11 bars from 10 s, looped)`); process.exit(0);
}

// ---------- the synthesized beat ----------
const BPM = +arg('--bpm', 105), B = 60 / BPM, BAR = 4 * B;
const L = new Float32Array(n), R = new Float32Array(n);
let seed = 5; const rnd = () => (seed = (seed * 1664525 + 1013904223) >>> 0) / 4294967296;
const put = (at, len, fn, gl = 1, gr = gl) => { const i0 = Math.round(at * SR), m = Math.round(len * SR); for (let i = 0; i < m && i0 + i < n; i++) { const v = fn(i / SR); L[i0 + i] += v * gl; R[i0 + i] += v * gr; } };
const kick = at => { let ph = 0; put(at, 0.32, t => { ph += TAU * (46 + 110 * Math.exp(-t * 32)) / SR; return Math.sin(ph) * Math.exp(-t * 9) * 0.9; }); };
const hat = (at, g, open = false) => { let lp = 0, prev = 0; put(at, open ? 0.14 : 0.04, t => { lp += 0.5 * (rnd() * 2 - 1 - lp); const v = lp - prev; prev = lp; return v * Math.exp(-t * (open ? 30 : 110)) * g; }, 0.8, 1); };
const snap = at => { let lp = 0; put(at, 0.16, t => { lp += 0.35 * (rnd() * 2 - 1 - lp); return lp * Math.exp(-t * 26) * 0.6; }, 1, 0.85); };
const sub = (at, len, f) => { let ph = 0; put(at, len, t => { ph += TAU * f * (1 + 0.02 * Math.exp(-t * 20)) / SR; const env = Math.min(1, t * 60) * Math.min(1, (len - t) * 8) * Math.exp(-t * 0.6); return Math.tanh(Math.sin(ph) * 1.8) * 0.55 * env; }); };
const pad = (at, len, fs) => { const ph = fs.map(() => rnd() * TAU); let lp = 0; put(at, len, t => { let v = 0; fs.forEach((f, k) => { v += Math.sin(TAU * f * t + ph[k]) + 0.3 * Math.sin(TAU * f * 2.003 * t + ph[k]); }); lp += 0.06 * (v - lp); const env = Math.min(1, t / 0.8) * Math.min(1, (len - t) / 0.8); return lp * env * 0.035; }); };
const ROOT = [55, 43.65, 65.41, 49.0], CHORD = [[220, 261.6, 329.6], [174.6, 220, 261.6], [261.6, 329.6, 392], [196, 246.9, 293.7]];
for (let b = 0; b * BAR < dur; b++) {
  const t0 = b * BAR, k = b % 4;
  kick(t0); kick(t0 + 2.5 * B); if (b % 2) kick(t0 + 3.5 * B);
  snap(t0 + B); snap(t0 + 3 * B);
  for (let i = 0; i < 8; i++) hat(t0 + i * B / 2, i % 2 ? 0.22 : 0.32);
  if (b % 2 === 1) for (let i = 0; i < 6; i++) hat(t0 + 3 * B + i * B / 6, 0.16 + 0.03 * i);
  hat(t0 + 1.5 * B, 0.18, true);
  sub(t0, 1.6 * B, ROOT[k]); sub(t0 + 2.5 * B, 1.3 * B, ROOT[k] * (b % 2 ? 1.5 : 1));
  pad(t0, BAR + 0.4, CHORD[k]);
}
let pk = 0; for (let i = 0; i < n; i++) pk = Math.max(pk, Math.abs(L[i]), Math.abs(R[i]));
for (let i = 0; i < n; i++) { L[i] *= 0.5 / pk; R[i] *= 0.5 / pk; }
writeWav(out, L, R);
console.log(`bed.wav ${dur.toFixed(1)} s · ${BPM} bpm · ${Math.ceil(dur / BAR)} bars (synthesized)`);
```

### `cdp.mjs`

```js
// Headless Chrome/Edge over the DevTools protocol, no dependencies. ONE browser at a time, at the lowest priority.
// The film is HTML + a 2D canvas: Chrome draws it on the GPU when it can, and a CPU fallback is light enough to be fine.
import { spawn, execFile } from 'node:child_process'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path';
export const CHROME = [process.env.CHROME, 'C:/Program Files/Google/Chrome/Application/chrome.exe', 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe',
  'C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe', 'C:/Program Files/Microsoft/Edge/Application/msedge.exe',
  '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
  '/usr/bin/google-chrome', '/usr/bin/google-chrome-stable', '/usr/bin/chromium', '/usr/bin/chromium-browser', '/usr/bin/microsoft-edge'].find(p => p && fs.existsSync(p));
const ANGLE = process.env.STAMP_ANGLE || (process.platform === 'win32' ? 'd3d11' : process.platform === 'darwin' ? 'metal' : 'gl');
const sleep = ms => new Promise(r => setTimeout(r, ms));

export async function launch({ width = 1080, height = 1920, port = 9650 } = {}) {
  if (!CHROME) throw new Error('Chrome or Edge not found (set CHROME=<path>)');
  try { os.setPriority(os.constants.priority.PRIORITY_LOW); } catch { }
  const udd = fs.mkdtempSync(path.join(os.tmpdir(), 'stamp-chrome-'));
  const proc = spawn(CHROME, [`--remote-debugging-port=${port}`, `--user-data-dir=${udd}`, '--headless=new', '--no-first-run', '--no-default-browser-check', '--disable-extensions',
    '--disable-background-timer-throttling', '--disable-renderer-backgrounding', '--mute-audio', `--window-size=${width},${height}`,
    '--use-gl=angle', `--use-angle=${ANGLE}`, '--ignore-gpu-blocklist', '--enable-gpu', 'about:blank'], { stdio: 'ignore' });
  let list = null; for (let i = 0; i < 100 && !list?.length; i++) { try { list = await (await fetch(`http://127.0.0.1:${port}/json/list`)).json(); } catch { await sleep(150); } }
  if (!list?.length) { proc.kill(); throw new Error('Chrome devtools not reachable on port ' + port); }
  // polite: lower the browser and every child process, now and every 5 s; on Windows pin them to one logical CPU
  const pinned = new Set();
  const polite = async () => { try {
    const v = await (await fetch(`http://127.0.0.1:${port}/json/version`)).json(), bws = new WebSocket(v.webSocketDebuggerUrl);
    await new Promise((res, rej) => { bws.onopen = res; bws.onerror = rej; });
    const info = await new Promise(res => { bws.onmessage = ev => { const m = JSON.parse(ev.data); if (m.id === 1) res(m.result); }; bws.send(JSON.stringify({ id: 1, method: 'SystemInfo.getProcessInfo' })); setTimeout(() => res(null), 10000); });
    bws.close(); const pids = [proc.pid, ...(info?.processInfo || []).map(p => p.id)];
    for (const id of pids) { try { os.setPriority(id, os.constants.priority.PRIORITY_LOW); } catch { } }
    const fresh = pids.filter(id => !pinned.has(id)); fresh.forEach(id => pinned.add(id));
    if (process.platform === 'win32' && fresh.length) execFile('powershell', ['-NoProfile', '-NonInteractive', '-Command', `$ErrorActionPreference='SilentlyContinue'; foreach ($id in @(${fresh.join(',')})) { (Get-Process -Id $id).ProcessorAffinity = [IntPtr]${2 ** (Math.min(os.cpus().length, 52) - 1)} }`], { windowsHide: true }, () => { });
  } catch { } };
  await polite(); setTimeout(polite, 1200).unref(); const timer = setInterval(polite, 5000); timer.unref();
  const ws = new WebSocket(list.find(t => t.type === 'page').webSocketDebuggerUrl); await new Promise((res, rej) => { ws.onopen = res; ws.onerror = rej; });
  let id = 0; const pending = new Map(), logs = [];
  ws.onmessage = ev => { const m = JSON.parse(ev.data);
    if (m.id && pending.has(m.id)) { const { res, rej } = pending.get(m.id); pending.delete(m.id); m.error ? rej(new Error(m.error.message)) : res(m.result); }
    else if (m.method === 'Runtime.exceptionThrown') logs.push({ type: 'exception', text: m.params.exceptionDetails.exception?.description || m.params.exceptionDetails.text });
    else if (m.method === 'Runtime.consoleAPICalled') logs.push({ type: m.params.type, text: m.params.args.map(a => a.value ?? a.description).join(' ') }); };
  const send = (method, params = {}) => new Promise((res, rej) => { const i = ++id; pending.set(i, { res, rej }); ws.send(JSON.stringify({ id: i, method, params })); });
  await send('Runtime.enable'); await send('Page.enable');
  await send('Emulation.setDeviceMetricsOverride', { width, height, deviceScaleFactor: 1, mobile: false });
  const api = { send, logs, console: logs,
    async shot(file, quality = 90) { const r = await send('Page.captureScreenshot', { format: 'jpeg', quality }); fs.writeFileSync(file, Buffer.from(r.data, 'base64')); },
    async eval(expr) { const r = await send('Runtime.evaluate', { expression: expr, returnByValue: true, awaitPromise: true }); if (r.exceptionDetails) throw new Error('eval: ' + (r.exceptionDetails.exception?.description || r.exceptionDetails.text)); return r.result.value; },
    async waitFor(expr, ms = 10000) { const t0 = Date.now(); while (Date.now() - t0 < ms) { try { if (await api.eval(expr)) return; } catch { } await sleep(100); } throw new Error('timeout: ' + expr); },
    async goto(url) { await send('Page.navigate', { url }); await sleep(300); await api.waitFor('document.readyState === "complete"', 15000); },
    async close() { clearInterval(timer); try { ws.close(); } catch { } try { proc.kill(); } catch { }
      // wait for Chrome to let go of its profile folder, then delete it (a timer would never fire if the script exits first)
      for (let k = 0; k < 20; k++) { await sleep(150); try { fs.rmSync(udd, { recursive: true, force: true }); break; } catch { } } } };
  api.gpu = await api.eval(`(() => { const c = document.createElement('canvas'), g = c.getContext('webgl2') || c.getContext('webgl'); if (!g) return 'none'; const e = g.getExtension('WEBGL_debug_renderer_info'); return String(e ? g.getParameter(e.UNMASKED_RENDERER_WEBGL) : g.getParameter(g.RENDERER)); })()`);
  return api;
}
```

### `gen-vo.mjs`

```js
// Stamp · VOICE. One ElevenLabs take per section, with word timings → <video>\vo\<id>.mp3 + <id>.json.
//   node gen-vo.mjs --video <dir> [--yes] [--only s1,s3] [--fresh]   (a dry run unless --yes)
// --dry prints what would be sent and the character count, and sends nothing. A take is cached: it is only made again
// when its spoken words changed (or with --fresh). Markup-only edits (boxes, phrase breaks) need no new take:
// build-timeline.mjs re-applies them. Voice + settings: engine/markup.mjs DEFAULTS (Liam, multilingual v2, speed 1.1)
// unless script.mjs exports VOICE / MODEL / SETTINGS / SPEED. Never prints the key.
import fs from 'node:fs';
import path from 'node:path';
import { parse, loadScript, videoDir } from './engine/markup.mjs';
const HERE = import.meta.dirname;
const { abs: VD } = videoDir(HERE);
const S = await loadScript(VD);
const arg = k => { const i = process.argv.indexOf(k); return i > 0 ? process.argv[i + 1] : null; };
const has = k => process.argv.includes(k);
const only = arg('--only') ? arg('--only').split(',') : S.ORDER;
const speed = +(arg('--speed') || S.SPEED);
fs.mkdirSync(path.join(VD, 'vo'), { recursive: true });
const todo = [];
for (const id of only) {
  if (!S.TAKES[id]) { console.log(id, 'is not in TAKES'); continue; }
  const toks = parse(S.TAKES[id]), text = toks.map(t => t.said).join(' '), js = path.join(VD, 'vo', id + '.json');
  const old = fs.existsSync(js) ? JSON.parse(fs.readFileSync(js, 'utf8')) : null;
  if (old && old.text === text && !has('--fresh')) { console.log(id, 'cached'); continue; }
  todo.push({ id, toks, text, why: old ? (old.text === text ? 'fresh' : 'changed') : 'new' });
}
const chars = todo.reduce((a, t) => a + t.text.length, 0);
console.log(`${todo.length} take(s) to make, ${chars} characters (${todo.map(t => `${t.id}:${t.why}`).join(' ') || '-'})`);
if (!has('--yes') && todo.length) console.log('dry run: nothing sent. Add --yes to make these takes (~1 credit per character).');
if (has('--dry') || !has('--yes') || !todo.length) { for (const t of todo) console.log(` ${t.id}: ${t.text}`); process.exit(0); }
const envFile = path.join(HERE, '.env');
const env = Object.fromEntries((fs.existsSync(envFile) ? fs.readFileSync(envFile, 'utf8') : '').split(/\r?\n/).filter(l => /^[A-Z_]+=/.test(l)).map(l => { const i = l.indexOf('='); return [l.slice(0, i), l.slice(i + 1).trim().replace(/^["']|["']$/g, '')]; }));
env.ELEVENLABS_API_KEY ||= process.env.ELEVENLABS_API_KEY;
if (!env.ELEVENLABS_API_KEY) { console.error('no ELEVENLABS_API_KEY (environment, or .env in the project folder)'); process.exit(1); }
await Promise.all([0, 1, 2].map(async w => {
  for (let i = w; i < todo.length; i += 3) {
    const { id, toks, text } = todo[i];
    const body = { text, model_id: S.MODEL, voice_settings: { ...S.SETTINGS, speed } };
    const r = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${S.VOICE}/with-timestamps?output_format=mp3_44100_128`, { method: 'POST', headers: { 'xi-api-key': env.ELEVENLABS_API_KEY, 'content-type': 'application/json' }, body: JSON.stringify(body) });
    if (!r.ok) { console.log(id, r.status, (await r.text()).slice(0, 300)); continue; }
    const j = await r.json();
    fs.writeFileSync(path.join(VD, 'vo', id + '.mp3'), Buffer.from(j.audio_base64, 'base64'));
    const A = j.alignment, words = []; let cur = null;
    A.characters.forEach((c, k) => {
      if (/\s/.test(c)) { if (cur) { words.push(cur); cur = null; } return; }
      if (!cur) cur = { w: '', t0: A.character_start_times_seconds[k] };
      cur.w += c; cur.t1 = A.character_end_times_seconds[k];
    });
    if (cur) words.push(cur);
    let wi = 0;
    for (const t of toks) { const n = t.said.split(' ').length, span = words.slice(wi, wi + n); wi += n; t.t0 = +span[0].t0.toFixed(3); t.t1 = +span.at(-1).t1.toFixed(3); }
    if (wi !== words.length) console.log(id, 'WARNING word count', wi, 'vs', words.length);
    const dur = A.character_end_times_seconds.at(-1);
    fs.writeFileSync(path.join(VD, 'vo', id + '.json'), JSON.stringify({ id, text, voice: S.VOICE, speed, dur, tokens: toks }, null, 1));
    console.log(id, 'ok', dur.toFixed(2) + 's', (text.split(' ').length / dur * 60).toFixed(0), 'wpm');
  }
}));
console.log('characters sent', chars);
```

### `build-timeline.mjs`

```js
// Stamp · TIMELINE. Lays the takes end to end → <video>\timeline.json + timeline.js (every caption word on
// the film clock) and <video>\vo.wav (the voice track).   node build-timeline.mjs --video <dir> [--fake]
// It re-applies the current markup of script.mjs to the takes, so box / phrase-break edits need no new voice; if a
// take's spoken words changed, it stops and tells you to run gen-vo.mjs. Gaps: GAP (per section or one number), LEAD, HOLD.
import fs from 'node:fs';
import path from 'node:path';
import { execFileSync } from 'node:child_process';
import { parse, retime, loadScript, videoDir } from './engine/markup.mjs';
const FF = 'ffmpeg';
const HERE = import.meta.dirname;
const { abs: VD } = videoDir(HERE);
const S = await loadScript(VD);
const gap = id => typeof S.GAP === 'number' ? S.GAP : (S.GAP[id] ?? 0.3);
let t = S.LEAD; const sections = [], inputs = [], fc = [], bad = [];
const FAKE = process.argv.includes('--fake');
// --fake: guessed word times (~200 wpm like Liam at 1.1, a breath after . , ? !) and a silent vo.wav, to direct before paying for a voice
const fakeTake = toks => { let x = 0; return toks.map(k => { const n = k.said.split(' ').length, d = 0.08 + 0.04 * k.said.length + 0.04 * (n - 1); const o = { ...k, t0: +x.toFixed(3), t1: +(x + d).toFixed(3) }; x += d + (/[.?!]$/.test(k.said) ? 0.27 : /[,;:]$/.test(k.said) ? 0.12 : 0.02); return o; }); };
S.ORDER.forEach((id, i) => {
  const jf = path.join(VD, 'vo', id + '.json');
  if (FAKE) {
    const tk = fakeTake(parse(S.TAKES[id])); if (i) t += gap(id);
    const start = +t.toFixed(3), speechEnd = tk.at(-1).t1;
    sections.push({ id, start, end: +(start + speechEnd).toFixed(3), tokens: tk.map(k => ({ ...k, t0: +(start + k.t0).toFixed(3), t1: +(start + k.t1).toFixed(3) })) });
    t = start + speechEnd; return;
  }
  if (!fs.existsSync(jf)) { bad.push(`${id}: no take yet`); return; }
  const j = JSON.parse(fs.readFileSync(jf, 'utf8'));
  const toks = parse(S.TAKES[id]);
  if (toks.map(k => k.said).join(' ') !== j.text) { bad.push(`${id}: the spoken words changed`); return; }
  const tk = retime(toks, j.tokens);
  if (i) t += gap(id);
  const start = +t.toFixed(3), speechEnd = tk.at(-1).t1;
  sections.push({ id, start, end: +(start + speechEnd).toFixed(3), tokens: tk.map(k => ({ ...k, t0: +(start + k.t0).toFixed(3), t1: +(start + k.t1).toFixed(3) })) });
  inputs.push('-i', path.join(VD, 'vo', id + '.mp3'));
  const ms = Math.round(start * 1000), n = inputs.length / 2 - 1;
  fc.push(`[${n}:a]atrim=0:${(speechEnd + 0.12).toFixed(3)},afade=t=out:st=${(speechEnd + 0.02).toFixed(3)}:d=0.1,adelay=${ms}|${ms}[a${n}]`);
  t = start + speechEnd;
});
if (bad.length) { console.error('Make the voice first (node gen-vo.mjs --video ' + path.relative(HERE, VD) + '):\n  ' + bad.join('\n  ')); process.exit(1); }
sections.forEach((s, i) => { s.until = i + 1 < sections.length ? sections[i + 1].start : +(t + S.HOLD).toFixed(3); });
const total = +(t + S.HOLD).toFixed(3);
fs.writeFileSync(path.join(VD, 'timeline.json'), JSON.stringify({ total, sections }, null, 1));
fs.writeFileSync(path.join(VD, 'timeline.js'), `window.TL = ${JSON.stringify({ total, sections })};`);
if (FAKE) {
  execFileSync(FF, ['-y', '-v', 'error', '-f', 'lavfi', '-i', 'anullsrc=r=48000:cl=mono', '-t', String(total), path.join(VD, 'vo.wav')]);
  console.log('FAKE timings (no voice yet): direct the scenes and take stills, then gen-vo.mjs + build-timeline.mjs for the real ones');
} else {
  fc.push(`${sections.map((_, i) => `[a${i}]`).join('')}amix=inputs=${sections.length}:normalize=0,apad=whole_dur=${total},atrim=0:${total}[vo]`);
  execFileSync(FF, ['-y', '-v', 'error', ...inputs, '-filter_complex', fc.join(';'), '-map', '[vo]', '-ar', '48000', '-ac', '1', path.join(VD, 'vo.wav')]);
}
const words = sections.reduce((a, s) => a + s.tokens.reduce((b, k) => b + k.said.split(' ').length, 0), 0);
console.log(`total ${total} s · ${words} words · ${(words / (t - S.LEAD) * 60).toFixed(0)} wpm · ` + sections.map(s => `${s.id}@${s.start}`).join(' '));
if (total > 60) console.log('WARNING: over 60 s (RULES 10). Tighten GAP/HOLD or the script.');
for (const s of sections) console.log(s.id, s.tokens.map(k => `${k.d}@${k.t0.toFixed(2)}`).join(' '));
```

### `render.mjs`

```js
// Stamp · RENDER + MIX + ENCODE. Run from the project folder, one render at a time:
//   node render.mjs --video <dir> <steps…>
// Steps (any of them, done in this order):
//   --prep                    the frames this film shows of itself (F.prep in scenes.js) → <dir>\prep\
//   --scan [--step 0.25]      safe-zone check of every visible text through the whole film, no screenshots (~1 min)
//   --stills [--at 0,3.5,8 | beats | spread:8] [--safe] [--nocap] [--sheet sheet]
//                             stills → <dir>\stills\<t>.jpg (+ a contact sheet); --safe draws the RULES safe zone and
//                             reports every visible text outside it (<dir>\stills\safe-report.txt)
//   --full [--from f] [--to f] [--force]   every frame → <dir>\frames\f00000.jpg … (resumable, ~150 ms/frame)
//   --cover <t> [--cover-out cover.jpg]   the 1080x1920 hook frame at film time t (deliverable)
//   --mix [--bed <dir>\bed.wav] [--music 0.23] [--dur s]   voice + bed (bed.mjs makes it) + sfx → <dir>\mix.wav at −14 LUFS
//   --encode [--out video.mp4]      frames + mix → H.264 with the RULES settings, then probes it (size, length, loudness)
// Any step: --layout band previews the captions in the RULES caption band (set it for real with F.layout('band') in scenes.js).
// Chrome only through cdp.mjs: one browser per call, idle priority, one thread.
import { launch } from './cdp.mjs';
import { execFileSync, spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { videoDir } from './engine/markup.mjs';
try { os.setPriority(os.constants.priority.PRIORITY_LOW); } catch { }
const HERE = import.meta.dirname, FF = 'ffmpeg', FP = 'ffprobe';
process.chdir(HERE);
const arg = (k, d) => { const i = process.argv.indexOf(k); return i > 0 ? process.argv[i + 1] : d; };
const has = k => process.argv.includes(k);
const FPS = 30, port = +arg('--port', 9671);
const { abs: VD, rel: VREL } = videoDir(HERE);
const V = (...p) => path.join(VD, ...p);
for (const f of ['scenes.js', 'timeline.js']) if (!fs.existsSync(V(f))) { console.error(`no ${V(f)}${f === 'timeline.js' ? ' — run build-timeline.mjs first' : ''}`); process.exit(1); }
const url = q => 'file:///' + path.join(HERE, 'film.html').split(path.sep).join('/') + '?v=' + encodeURIComponent(VREL) + (arg('--layout') ? '&layout=' + arg('--layout') : '') + (q ? '&' + q : '');
const ff = (args, o = {}) => execFileSync(FF, ['-y', '-v', 'error', ...args], { stdio: 'inherit', ...o });
const probe = f => JSON.parse(execFileSync(FP, ['-v', 'error', '-show_entries', 'format=duration,size,bit_rate:stream=codec_type,codec_name,profile,pix_fmt,width,height,r_frame_rate,sample_rate,channels', '-of', 'json', f], { encoding: 'utf8' }));
const loud = (f, extra = []) => { const p = spawnSync(FF, ['-hide_banner', '-nostats', ...extra, '-i', f, '-af', 'ebur128=peak=true', '-f', 'null', '-'], { encoding: 'utf8' }); const s = p.stderr.slice(p.stderr.lastIndexOf('Summary:')); return { I: +(s.match(/I:\s+(-?[\d.]+) LUFS/) || [])[1], TP: +(s.match(/Peak:\s+(-?[\d.]+) dBFS/) || [])[1] }; };

// ---------- the page ----------
let page = null;
async function load(q) {
  if (!page) page = await launch({ width: 1080, height: 1920, port });
  page.console.length = 0;
  await page.goto(url(q));
  const ok = await page.eval('window.ready');
  const errs = await page.eval('window.F ? F.errors : ["engine did not load"]');
  if (!ok || errs.length || !(await page.eval('typeof window.setT === "function"'))) { console.error('The film did not build:\n  ' + (errs.length ? errs.join('\n  ') : 'unknown error')); await page.close(); process.exit(1); }
  // what the mix needs from the film: its sound cues and its looks (warm/moody windows restyle the bed)
  fs.writeFileSync(V('cues.json'), JSON.stringify({ total: await page.eval('window.TOTAL'), cues: await page.eval('window.CUES'), looks: await page.eval('window.LOOKS') }));
}
const pageErrors = () => { const e = page.console.filter(c => /error|exception/i.test(c.type)); if (e.length) console.log('PAGE ERRORS:', JSON.stringify(e.slice(-6), null, 1)); };
const sheet = (files, out) => {
  const tmp = V('stills', '_seq'); fs.rmSync(tmp, { recursive: true, force: true }); fs.mkdirSync(tmp, { recursive: true });
  files.forEach((f, i) => fs.copyFileSync(f, path.join(tmp, `${String(i).padStart(3, '0')}.jpg`)));
  const n = files.length, cols = n <= 4 ? n : n <= 8 ? 4 : n <= 12 ? 6 : 8, rows = Math.ceil(n / cols);
  ff(['-i', path.join(tmp, '%03d.jpg'), '-vf', `scale=270:480,tile=${cols}x${rows}:padding=6:color=0x444444`, '-frames:v', '1', out]);
  fs.rmSync(tmp, { recursive: true, force: true });
};

try {
  // ---------- prep ----------
  if (has('--prep')) {
    await load('');
    const list = await page.eval('window.PREP');
    if (!list.length) console.log('scenes.js asks for no prep shots');
    fs.mkdirSync(V('prep'), { recursive: true });
    for (const nocap of [false, true]) {
      const items = list.filter(p => !!p.nocap === nocap);
      if (!items.length) continue;
      if (nocap) await load('nocap');
      for (const p of items) {
        const ats = Array.isArray(p.at) ? p.at : [p.at], multi = Array.isArray(p.at);
        if (multi) fs.mkdirSync(V('prep', p.name), { recursive: true });
        for (let i = 0; i < ats.length; i++) {
          const f = multi ? V('prep', p.name, `${String(i + 1).padStart(2, '0')}.jpg`) : V('prep', p.name + '.jpg');
          await page.eval(`setT(${ats[i]})`); await page.shot(f, 88);
          if (p.width) ff(['-i', f, '-vf', `scale=${p.width}:-1`, '-q:v', '3', f + '.tmp.jpg']), fs.renameSync(f + '.tmp.jpg', f);
        }
      }
    }
    console.log('prep done:', list.map(p => p.name).join(', '));
  }

  // ---------- safe-zone scan: every text on screen, every 0.25 s, no screenshots (fast) ----------
  if (has('--scan')) {
    await load('safe');
    const total = await page.eval('window.TOTAL'), step = +arg('--step', 0.25), hits = new Map();
    for (let t = 0; t <= total; t += step) for (const b of await page.eval(`setT(${t.toFixed(3)}).then(() => F.safeReport())`)) {
      const k = `${b.cap ? 'caption' : 'text'} "${b.text}"`, e = hits.get(k) || { t0: t, t1: t, box: b.box }; e.t1 = t; hits.set(k, e);
    }
    const lines = [...hits].map(([k, e]) => `${e.t0.toFixed(2)}–${e.t1.toFixed(2)}s  ${k}  box ${e.box.join(',')}`);
    console.log(lines.length ? `SAFE ZONE: ${lines.length} text(s) outside at some point:\n  ` + lines.join('\n  ') : `safe zone scan (every ${step} s): all visible text inside`);
    pageErrors();
  }

  // ---------- stills ----------
  if (has('--stills')) {
    const safe = has('--safe');
    await load([has('--nocap') ? 'nocap' : '', safe ? 'safe' : ''].filter(Boolean).join('&'));
    const total = await page.eval('window.TOTAL'), A = String(arg('--at', 'spread:8'));
    const ats = A === 'beats' ? (await page.eval('window.BEATS')).map(([a, b]) => +((a + b) / 2).toFixed(2))
      : A.startsWith('spread:') ? Array.from({ length: +A.slice(7) }, (_, i) => +(total * (i + 0.5) / +A.slice(7)).toFixed(2))
      : A.split(',').map(Number);
    fs.mkdirSync(V('stills'), { recursive: true });
    const files = [], report = [];
    for (const at of ats) {
      await page.eval(`setT(${at})`);
      if (safe) { const bad = await page.eval('F.safeReport(true)'); for (const b of bad) report.push(`${at}s  ${b.cap ? 'caption' : 'text'} "${b.text}"  box ${b.box.join(',')}`); }
      const f = V('stills', `${at}.jpg`); await page.shot(f, 88); files.push(f);
    }
    if (arg('--sheet')) sheet(files, V('stills', arg('--sheet') + '.jpg'));
    if (safe) {
      fs.writeFileSync(V('stills', 'safe-report.txt'), report.length ? report.join('\n') + '\n' : 'all visible text inside the safe zone\n');
      console.log(report.length ? `SAFE ZONE: ${report.length} text box(es) outside (red in the stills):\n  ` + report.join('\n  ') : 'safe zone: all visible text inside');
    }
    pageErrors();
    console.log('stills', ats.join(','), arg('--sheet') ? '+ sheet ' + V('stills', arg('--sheet') + '.jpg') : '');
  }

  // ---------- full render ----------
  if (has('--full')) {
    await load('');
    const total = await page.eval('window.TOTAL'), n = Math.round(total * FPS);
    fs.mkdirSync(V('frames'), { recursive: true });
    const from = +arg('--from', 0), to = Math.min(n, +arg('--to', n));
    const t0 = Date.now(); let done = 0;
    for (let i = from; i < to; i++) {
      const f = V('frames', `f${String(i).padStart(5, '0')}.jpg`);
      if (fs.existsSync(f) && !has('--force')) continue;
      await page.eval(`setT(${(i / FPS).toFixed(5)})`); await page.shot(f, 92); done++;
      if (done % 150 === 0) console.log(`${i + 1}/${n} frames, ${((Date.now() - t0) / done).toFixed(0)} ms/frame`);
    }
    pageErrors();
    console.log(`frames ${from}–${to - 1} of ${n} done (${done} new) in ${((Date.now() - t0) / 1000).toFixed(0)} s`);
  }

  // ---------- cover ----------
  if (has('--cover')) {
    await load('');
    const at = +arg('--cover'), out = path.resolve(VD, arg('--cover-out', 'cover.jpg'));
    if (!Number.isFinite(at)) { console.error('--cover needs a time: --cover 1.9'); process.exit(1); }
    await page.eval(`setT(${at})`); await page.shot(out, 95);
    console.log('cover', at + 's →', out);
  }
} finally { if (page) await page.close(); }

// ---------- mix ----------
if (has('--mix')) {
  if (!fs.existsSync(V('vo.wav'))) { console.error('no vo.wav: run build-timeline.mjs'); process.exit(1); }
  if (!fs.existsSync(V('cues.json'))) { console.error('no cues.json: run any page step first (--stills or --full)'); process.exit(1); }
  const { total, cues, looks } = JSON.parse(fs.readFileSync(V('cues.json'), 'utf8'));
  const DUR = Math.min(total, +arg('--dur', total));
  const bed = arg('--bed') ? path.resolve(HERE, arg('--bed')) : V('bed.wav'), MV = +arg('--music', 0.23);
  if (!fs.existsSync(bed)) { console.error('no ' + bed + ': run node bed.mjs --video <dir> first'); process.exit(1); }
  // sfx: <video>\sfx, then the project's sfx\ (sounds.mjs / gen-sfx.mjs)
  const sfx = name => [V('sfx', name + '.wav'), path.join(HERE, 'sfx', name + '.wav')].find(f => fs.existsSync(f));
  // the score obeys the film: low-passed + vinyl crackle in a warm window, low-passed + echo in a moody one
  const win = (a, b, fi, fo) => `min(1\\,max(0\\,(t-${a.toFixed(3)})/${fi}))*min(1\\,max(0\\,(${b.toFixed(3)}-t)/${fo}))`;
  const segs = { warm: [], moody: [] };
  looks.forEach(([t, m, d], i) => { if (segs[m]) { const nx = looks[i + 1]; segs[m].push({ a: Math.max(0, t), b: nx ? nx[0] : DUR + 1, fi: d, fo: nx ? nx[2] : 0.3 }); } });
  const W = s => s.length ? s.map(x => win(x.a, x.b, x.fi, x.fo)).join('+') : '0';
  const args = ['-i', V('vo.wav'), '-stream_loop', '-1', '-i', bed];
  const fc = [
    `[0:a]aformat=channel_layouts=stereo,atrim=0:${DUR.toFixed(3)},asplit=2[vo][sc]`,
    `[1:a]aformat=sample_rates=48000:channel_layouts=stereo,atrim=0:${DUR.toFixed(3)},asplit=3[mn][mw][md]`,
    `[mn]volume='max(0\\,1-(${W(segs.warm)})-(${W(segs.moody)}))':eval=frame[mn2]`,
    `[mw]lowpass=f=1500,highpass=f=70,acompressor=threshold=0.2:ratio=2,volume='0.9*(${W(segs.warm)})':eval=frame[mw2]`,
    `[md]lowpass=f=380,aecho=0.8:0.6:140|300:0.35|0.22,volume='1.5*(${W(segs.moody)})':eval=frame[md2]`,
    `[mn2][mw2][md2]amix=inputs=3:normalize=0,afade=t=out:st=${Math.max(0, DUR - 1.8).toFixed(3)}:d=1.8,volume=${MV}[m0]`,
    `[m0][sc]sidechaincompress=threshold=0.035:ratio=5:attack=15:release=300,asplit=2[m][mst]`,
  ];
  const all = cues.filter(c => c[1] < DUR);
  for (const s of segs.warm) if (s.a < DUR) all.push(['crackle', s.a, 0.22, Math.min(DUR, s.b) - s.a + 0.3, 0.4]);
  const ins = [];
  all.forEach(([f, t, g, len, fin]) => {
    const file = sfx(f); if (!file) { console.log('missing sfx', f); return; }
    const k = ins.length + 2; ins.push(k);
    if (f === 'crackle') args.push('-stream_loop', '-1');
    args.push('-i', file);
    const ms = Math.max(0, Math.round(t * 1000));
    const trim = len ? `atrim=0:${len.toFixed(3)},afade=t=out:st=${Math.max(0, len - (fin ? 0.35 : 0.12)).toFixed(3)}:d=${fin ? 0.35 : 0.12},` : '';
    fc.push(`[${k}:a]aformat=sample_rates=48000:channel_layouts=stereo,${trim}${fin ? `afade=t=in:d=${fin},` : ''}volume=${g},adelay=${ms}|${ms},apad=whole_dur=${DUR}[s${k}]`);
  });
  fc.push(`[vo][m]${ins.map(k => `[s${k}]`).join('')}amix=inputs=${ins.length + 2}:normalize=0:duration=first,atrim=0:${DUR}[pre]`);
  fs.writeFileSync(V('mix.filter'), fc.join(';\n'));
  ff([...args, '-filter_complex_script', V('mix.filter'), '-map', '[pre]', '-ar', '48000', '-ac', '2', V('pre.wav'), '-map', '[mst]', '-ar', '48000', '-ac', '2', V('bedstem.wav')]);
  // −14 LUFS integrated, true peak ≤ −1 dBTP (RULES 9): measured gain, then a 4x-oversampled limiter at −2 dBFS
  // (a plain linear loudnorm stops short when sfx peaks hit the ceiling); repeat until within 0.15 LU
  const target = -14; let g = target - loud(V('pre.wav')).I, L;
  for (let k = 0; k < 4; k++) {
    ff(['-i', V('pre.wav'), '-af', `volume=${g.toFixed(2)}dB,aresample=192000,alimiter=limit=0.794:attack=1:release=60:level=0,aresample=48000`, '-c:a', 'pcm_s16le', V('mix.wav')]);
    L = loud(V('mix.wav')); if (Math.abs(L.I - target) <= 0.15) break; g += target - L.I;
  }
  // how far the bed sits under the voice after ducking (RULES 9: about 13 LU; change it with --music)
  const lv = loud(V('vo.wav'), ['-t', String(DUR)]).I, lm = loud(V('bedstem.wav')).I;
  fs.rmSync(V('pre.wav'), { force: true }); fs.rmSync(V('bedstem.wav'), { force: true });
  console.log(`mix.wav ${DUR.toFixed(2)} s · ${L.I} LUFS · true peak ${L.TP} dBFS · bed ${(lv - lm).toFixed(1)} LU under the voice (--music ${MV}) · ${ins.length} sfx`);
}

// ---------- encode ----------
if (has('--encode')) {
  const frames = fs.existsSync(V('frames')) ? fs.readdirSync(V('frames')).filter(f => /^f\d{5}\.jpg$/.test(f)).sort() : [];
  let n = 0; while (n < frames.length && frames[n] === `f${String(n).padStart(5, '0')}.jpg`) n++;
  if (!n) { console.error('no frames: run --full'); process.exit(1); }
  if (!fs.existsSync(V('mix.wav'))) { console.error('no mix.wav: run --mix'); process.exit(1); }
  const out = path.resolve(VD, arg('--out', 'video.mp4'));
  const mixDur = +probe(V('mix.wav')).format.duration;
  if (Math.abs(mixDur - n / FPS) > 0.2) console.log(`note: ${n} frames = ${(n / FPS).toFixed(2)} s but mix.wav is ${mixDur.toFixed(2)} s; the shorter one wins (use --mix --dur for a test clip)`);
  ff(['-framerate', String(FPS), '-i', V('frames', 'f%05d.jpg'), '-i', V('mix.wav'), '-map', '0:v', '-map', '1:a', '-frames:v', String(n),
    // Chrome's JPEG frames are full-range BT.601: convert to limited-range BT.709 yuv420p, tagged, so players don't wash it out
    '-c:v', 'libx264', '-preset', 'slow', '-profile:v', 'high', '-vf', 'scale=1080:1920:flags=lanczos:in_range=full:out_range=limited:in_color_matrix=bt601:out_color_matrix=bt709,format=yuv420p,setparams=range=tv:color_primaries=bt709:color_trc=bt709:colorspace=bt709,setsar=1',
    '-pix_fmt', 'yuv420p', '-color_range', 'tv', '-colorspace', 'bt709', '-color_primaries', 'bt709', '-color_trc', 'bt709', '-r', String(FPS),
    '-crf', '20', '-maxrate', '10M', '-bufsize', '20M', '-c:a', 'aac', '-b:a', '192k', '-ar', '48000', '-shortest', '-movflags', '+faststart', out]);
  const P = probe(out), v = P.streams.find(s => s.codec_type === 'video'), a = P.streams.find(s => s.codec_type === 'audio');
  const mb = +P.format.size / 1048576, dur = +P.format.duration, L = loud(out);
  console.log(`${out}\n  ${v.codec_name} ${v.profile} ${v.pix_fmt} ${v.width}x${v.height} ${v.r_frame_rate} fps · ${a ? `${a.codec_name} ${a.sample_rate} Hz ${a.channels} ch` : 'NO AUDIO'} · ${dur.toFixed(2)} s · ${mb.toFixed(1)} MB · ${(+P.format.bit_rate / 1e6).toFixed(1)} Mb/s · ${L.I} LUFS · TP ${L.TP} dBFS`);
  if (mb > 60) console.log('  WARNING: over 60 MB (RULES 10)');
  if (dur > 60.05) console.log('  WARNING: over 60 s (RULES 10)');
  if (!(L.I > -15 && L.I < -13)) console.log('  WARNING: loudness is not −14 LUFS ±1');
}
```

### `check.mjs`

```js
// Stamp · FINAL CHECKS on a finished file:
//   node check.mjs --video <dir> [--file video.mp4] [--whisper]   (--whisper needs Python + `pip install faster-whisper`)
// Prints the encode facts against RULES (H.264 High, yuv420p, 1080x1920, 30 fps, AAC, ≤ 60 MB, ≤ 60 s, −14 LUFS,
// TP ≤ −1), checks that frame 0 isn't black or empty, pulls 5 random frames with the safe zone drawn on them
// (<dir>\check\frames.jpg, look at it), and with --whisper transcribes the audio and diffs it against the script.
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import { execFileSync, spawnSync } from 'node:child_process';
import { loadScript, spoken, videoDir } from './engine/markup.mjs';
try { os.setPriority(os.constants.priority.PRIORITY_LOW); } catch { }
const HERE = import.meta.dirname, FF = 'ffmpeg', FP = 'ffprobe';
const PY = process.env.PYTHON || (process.platform === 'win32' ? 'python' : 'python3');
const arg = (k, d) => { const i = process.argv.indexOf(k); return i > 0 ? process.argv[i + 1] : d; };
const { abs: VD } = videoDir(HERE);
const file = path.resolve(VD, arg('--file', 'video.mp4'));
if (!fs.existsSync(file)) { console.error('no ' + file); process.exit(1); }
const out = path.join(VD, 'check'); fs.mkdirSync(out, { recursive: true });
const ok = (c, s) => console.log(`${c ? ' ok ' : 'FAIL'}  ${s}`);

const P = JSON.parse(execFileSync(FP, ['-v', 'error', '-show_entries', 'format=duration,size:stream=codec_type,codec_name,profile,pix_fmt,width,height,r_frame_rate,sample_rate', '-of', 'json', file], { encoding: 'utf8' }));
const v = P.streams.find(s => s.codec_type === 'video'), a = P.streams.find(s => s.codec_type === 'audio');
const dur = +P.format.duration, mb = +P.format.size / 1048576;
ok(v.codec_name === 'h264' && v.profile === 'High' && v.pix_fmt === 'yuv420p', `video ${v.codec_name} ${v.profile} ${v.pix_fmt}`);
ok(v.width === 1080 && v.height === 1920 && v.r_frame_rate === '30/1', `${v.width}x${v.height} @ ${v.r_frame_rate}`);
ok(a && a.codec_name === 'aac', `audio ${a ? a.codec_name + ' ' + a.sample_rate + ' Hz' : 'missing'}`);
ok(dur <= 60.05, `length ${dur.toFixed(2)} s (≤ 60)`);
ok(mb <= 60, `size ${mb.toFixed(1)} MB (≤ 60)`);
const e = spawnSync(FF, ['-hide_banner', '-nostats', '-i', file, '-af', 'ebur128=peak=true', '-f', 'null', '-'], { encoding: 'utf8' }).stderr;
const sum = e.slice(e.lastIndexOf('Summary:')), I = +(sum.match(/I:\s+(-?[\d.]+) LUFS/) || [])[1], TP = +(sum.match(/Peak:\s+(-?[\d.]+) dBFS/) || [])[1];
ok(I > -15 && I < -13, `loudness ${I} LUFS (−14 ±1)`);
ok(TP <= -1, `true peak ${TP} dBFS (≤ −1)`);
// frame 0: not black (mean luma) and not flat (darkest vs brightest pixels); saved as <dir>/check/frame0.jpg to look at
execFileSync(FF, ['-y', '-v', 'error', '-i', file, '-frames:v', '1', path.join(out, 'frame0.jpg')]);
const st = spawnSync(FF, ['-hide_banner', '-i', file, '-vf', 'select=eq(n\\,0),signalstats,metadata=print', '-frames:v', '1', '-f', 'null', '-'], { encoding: 'utf8' }).stderr;
const yavg = +(st.match(/YAVG=([\d.]+)/) || [])[1], ymin = +(st.match(/YMIN=([\d.]+)/) || [])[1], ymax = +(st.match(/YMAX=([\d.]+)/) || [])[1];
ok(yavg > 10 && ymax - ymin > 100, `frame 0 not black/empty (mean luma ${yavg.toFixed(0)}, range ${ymin}–${ymax}; look at ${path.join(out, 'frame0.jpg')})`);
// 5 random frames with the safe zone drawn (cyan: all text x 60–960 · y 250–1480; yellow: the caption band)
const r = (() => { let s = Math.round(mb * 1000) >>> 0; return () => (s = (s * 1664525 + 1013904223) >>> 0) / 4294967296; })();
const ts = Array.from({ length: 5 }, () => +(0.5 + r() * (dur - 1)).toFixed(2)).sort((x, y) => x - y);
ts.forEach((t, i) => execFileSync(FF, ['-y', '-v', 'error', '-ss', String(t), '-i', file, '-frames:v', '1', '-vf', 'drawbox=x=60:y=250:w=900:h=1230:color=cyan@0.9:t=4,drawbox=x=60:y=1150:w=900:h=330:color=yellow@0.8:t=3', path.join(out, `r${i}.jpg`)]));
execFileSync(FF, ['-y', '-v', 'error', '-i', path.join(out, 'r%d.jpg'), '-vf', 'scale=270:480,tile=5x1:padding=6:color=0x444444', '-frames:v', '1', path.join(out, 'frames.jpg')]);
console.log(`look at ${path.join(out, 'frames.jpg')} (frames at ${ts.join(', ')} s): captions and labels must sit inside the cyan box`);

if (process.argv.includes('--whisper')) {
  const S = await loadScript(VD);
  const wav = path.join(out, 'audio16k.wav'), js = path.join(out, 'whisper.json');
  execFileSync(FF, ['-y', '-v', 'error', '-i', file, '-ac', '1', '-ar', '16000', '-c:a', 'pcm_s16le', wav]);
  const p = spawnSync(PY, [path.join(HERE, 'whisper.py'), wav, js], { encoding: 'utf8', env: { ...process.env, PYTHONIOENCODING: 'utf-8' } });
  if (p.status) { console.log('whisper could not run (needs Python + `pip install faster-whisper`), so listen to the video instead:', (p.stderr || '').slice(-300)); process.exit(0); }
  const heard = JSON.parse(fs.readFileSync(js, 'utf8')).segments.map(s => s.text).join(' ');
  const W = s => s.toLowerCase().replace(/[’']/g, '').replace(/[^a-z0-9]+/g, ' ').trim().split(' ').filter(Boolean);
  // the script's words spoken within this file's length (a short test clip is compared with its part of the script)
  const TLf = path.join(VD, 'timeline.json');
  const said = fs.existsSync(TLf) ? JSON.parse(fs.readFileSync(TLf, 'utf8')).sections.flatMap(s => s.tokens).filter(k => k.t0 < dur - 0.15).map(k => k.said).join(' ') : S.ORDER.map(id => spoken(S.TAKES[id])).join(' ');
  const A = W(said), B = W(heard);
  // word diff (LCS)
  const L = Array.from({ length: A.length + 1 }, () => new Int16Array(B.length + 1));
  for (let i = A.length - 1; i >= 0; i--) for (let j = B.length - 1; j >= 0; j--) L[i][j] = A[i] === B[j] ? L[i + 1][j + 1] + 1 : Math.max(L[i + 1][j], L[i][j + 1]);
  const diff = []; let i = 0, j = 0, miss = [], extra = [], joined = 0;
  const flush = () => {   // "capcut" vs "cap cut" is the same word: count it, don't report it
    if (miss.length && miss.join('') === extra.join('')) joined += miss.length;
    else if (miss.length || extra.length) diff.push(`script "${miss.join(' ')}" → heard "${extra.join(' ')}"`);
    miss = []; extra = [];
  };
  while (i < A.length || j < B.length) {
    if (i < A.length && j < B.length && A[i] === B[j]) { flush(); i++; j++; }
    else if (j < B.length && (i >= A.length || L[i][j + 1] >= L[i + 1][j])) extra.push(B[j++]);
    else miss.push(A[i++]);
  }
  flush();
  fs.writeFileSync(path.join(out, 'whisper.txt'), heard.trim() + '\n');
  const got = L[0][0] + joined;
  ok(got / A.length > 0.95, `whisper: ${got}/${A.length} script words heard (${(100 * got / A.length).toFixed(1)} %)`);
  for (const d of diff) console.log('       ' + d);
  console.log('       (numbers and spellings like "1" vs "one" show up here too: judge them by ear)');
}
```

### `whisper.py`

```python
# Transcribe one audio file with faster-whisper on the GPU (CPU if that fails).
#   python whisper.py <audio> <out.json>
# WHISPER_MODEL picks the model (default: large-v3-turbo, downloaded once; 'small' is lighter).
import ctypes, json, os, site, sys

# Idle priority (Windows), so the machine stays usable.
try:
    ctypes.windll.kernel32.SetPriorityClass(ctypes.windll.kernel32.GetCurrentProcess(), 0x40)
except Exception:
    pass

# cuDNN/cuBLAS from the nvidia-* pip wheels.
for base in site.getsitepackages() + [site.getusersitepackages()]:
    for sub in ("nvidia/cudnn/bin", "nvidia/cublas/bin"):
        p = os.path.join(base, sub)
        if os.path.isdir(p):
            os.add_dll_directory(p)
            os.environ["PATH"] = p + os.pathsep + os.environ["PATH"]

import wave

import numpy as np
from faster_whisper import WhisperModel

audio, out = sys.argv[1], sys.argv[2]

# The 16 kHz mono WAV from ffmpeg, as samples: faster-whisper's own decoder (PyAV)
# breaks on av 19 (open() lost `metadata_errors`).
with wave.open(audio, "rb") as w:
    pcm = np.frombuffer(w.readframes(w.getnframes()), dtype=np.int16).astype(np.float32) / 32768.0
name = os.environ.get("WHISPER_MODEL", "large-v3-turbo")


def run(device, compute):
    model = WhisperModel(name, device=device, compute_type=compute, cpu_threads=2)
    # Not conditioning on earlier text stops loops like "Go! Go! Go!" over music.
    segments, info = model.transcribe(pcm, beam_size=5, vad_filter=True, condition_on_previous_text=False)
    end = len(pcm) / 16000
    segs = [{"start": round(s.start, 2), "end": round(min(s.end, end), 2), "text": s.text} for s in segments if s.start < end - 0.2]
    return segs, info


try:
    # int8 weights, float32 maths: GTX 16-series cards are unreliable in float16.
    segs, info = run("cuda", "int8_float32")
    device = "GPU"
except Exception as e:
    print(f"GPU failed ({e}); using the CPU", file=sys.stderr)
    segs, info = run("cpu", "int8")
    device = "CPU"

with open(out, "w", encoding="utf-8") as f:
    json.dump({"model": name, "device": device, "language": info.language, "probability": info.language_probability, "segments": segs}, f, ensure_ascii=False, indent=1)
```


## Appendix B: an example video (complete)

"Never pay for a content skill" (54.9 s, 8 sections, 16 scenes, Liam). Copy it to start a new video, then rewrite the takes and every scene for your script.

### `my-video/script.mjs`

```js
// STAMP · "Never pay for a content skill" (after @getripping's "every 'but' between your app and a million").
// The script is one escalation chain: "So you … and … but …". Every spoken "but" gets a red BUT stamp on screen.
// Markup (engine/markup.mjs):  /  a new caption phrase   *word*  key word in a magenta selection box (*two_words* = one box)
//   @icon:word  an emoji before the word (F.ICONS in scenes.js)   {shown|said}  caption shows one thing, voice says another
export const VOICE = 'TX3LPaxmHKxFdv7VOQHJ';    // ElevenLabs "Liam": the closest to the reference (median ~127 Hz, ~200 wpm)
export const MODEL = 'eleven_multilingual_v2';
export const SETTINGS = { stability: 0.4, similarity_boost: 0.8, style: 0.5, use_speaker_boost: true };
export const SPEED = 1.1;
export const ORDER = ['s0', 's1', 's2', 's3', 's4', 's5', 's6', 'cta'];
export const GAP = { s1: 0.22, s2: 0.22, s3: 0.22, s4: 0.3, s5: 0.26, s6: 0.3, cta: 0.3 };
export const LEAD = 0.1, HOLD = 1.8;
export const TAKES = {
  s0: "You want to get / @money:*a_million_views* a month / with your Claude reels.",
  s1: "So you prompt Claude / for a new reel *every_night,* / and it @sparkles:actually works, / but they all / look the *same.*",
  s2: "So you copy a format / that's going *viral,* / and your views / @chart:start *climbing,* / but every video / takes you *hours.*",
  s3: "So you buy a *content_skill* / that does it for you, / but it costs / *{$30,|thirty dollars,}* / and it's just / *one_text_file.*",
  s4: "I'm *Casper,* / and I built *Morphy,* / an agentic super app. / I gave my own format away / *for_free,* / but people *repackaged* it / and started selling it / for *{$30.|thirty dollars.}*",
  s5: "*{$30,|Thirty dollars,}* / for a text file / that was *free* / to begin with. / So here's my *response.* / I'm putting / an *entire_library* / of content-format {.md|dot M D} files / inside *Morphy.* / @ban:No *paywalls,* / no *subscriptions,* / no *strings_attached.*",
  s6: "And now you're getting / @bag:*a_million_views,* / and you never pay / for a Claude content skill / *again.*",
  cta: "This format is called / *{stamp.md.|stamp dot M D.}* / So comment *{STAMP|stamp}* / and I'll send it to you. / Completely *free.*",
};
```

### `my-video/scenes.js`

```js
// EXAMPLE VIDEO · "Never pay for a content skill" (54.9 s, voice Liam) — the scene list: every word moves something.
// One escalation chain like @getripping's reel: dark grid + stage beam / off-white grid flips, one magenta app per beat that
// keeps evolving (counters tick, cards whip in, cursors click and drag, wires draw), and a red BUT stamp on every "but".
// Every time comes from a spoken word: T0(section, word). F.enter = entry + idle float; F.cam = the scene's camera moves.
const { S, T0, T1, END, scene, cue, looks, div, add, h, px, set, pop, rise, typeIn, html, centerX, clamp, lerp, ease, eio, eo, back, rng, TAU, spark } = F;
const P = F.parts, M = P.M, E = F.enter, tw = F.tw;

Object.assign(F.ICONS, { money: () => '💸', sparkles: () => '✨', chart: () => '📈', ban: () => '🚫', bag: () => '💰' });

// ---------- scene times (each scene starts on a word) ----------
const tB = S.s1.start - 0.12, tB2 = T0('s1', 'and') - 0.03, tC = S.s2.start - 0.12, tC2 = T0('s2', 'and') - 0.14;
const tD = S.s3.start - 0.12, tD2 = T0('s3', 'and') - 0.14, tE = S.s4.start - 0.12, tE2 = T0('s4', 'gave') - 0.3;
const tF = S.s5.start - 0.12, tF2 = T0('s5', 'so') - 0.16, tG = T0('s5', "i'm") - 0.14, tH = T0('s5', 'no') - 0.16;
const tI = S.s6.start - 0.12, tJ = S.cta.start - 0.12, tK = T0('cta', 'so') - 0.14;

looks([[0, 'dark'], [tB2, 'light'], [tC, 'dark'], [tC2, 'light'], [tD, 'dark'], [tD2, 'light'], [tE, 'dark'], [tE2, 'light'],
  [tF, 'dark'], [tF2, 'light'], [tG, 'dark'], [tI, 'light'], [tJ, 'dark']]);
// a whoosh on every cut (slides get a swoosh)
[[tB, 'whoosh'], [tB2, 'swoosh'], [tC, 'whoosh'], [tC2, 'whoosh'], [tD, 'swoosh'], [tD2, 'whoosh'], [tE, 'whoosh'], [tE2, 'swoosh'], [tF, 'whoosh'], [tF2, 'whoosh'], [tG, 'whoosh'], [tH, 'whoosh'], [tI, 'swoosh'], [tJ, 'whoosh'], [tK, 'whoosh']].forEach(([t, n]) => cue(n, t - 0.06, n === 'swoosh' ? 0.07 : 0.055));

const side = (win, items, light, y0 = 100) => items.map((s, i) => div(win, 'abs', 22, y0 + i * 58, 168, 48, s, { fontSize: '23px', fontWeight: 650, color: i ? (light ? '#8a8d94' : '#8d9096') : (light ? '#141518' : '#f3f3f1'), padding: '10px 14px', borderRadius: '12px', background: i ? 'none' : (light ? '#fbe6f9' : 'rgba(196,47,184,0.22)') }));
const box = (p, x, y, w, hh, light) => { const b = div(p, 'abs', x, y, w, hh, '', { border: `3px solid ${M.mag2}`, borderRadius: '10px', background: light ? 'rgba(226,79,214,0.08)' : 'rgba(160,30,150,0.18)', zIndex: 41, opacity: 0 }); [[-10, -10], [w - 8, hh - 8]].forEach(([a, b2]) => div(b, 'abs', a, b2, 18, 18, '', { borderRadius: '50%', background: M.mag2, boxShadow: '0 0 12px rgba(226,79,214,0.9)' })); return b; };
const snapBox = (b, t, t0) => { const k = back((t - t0) / 0.3); b.style.opacity = clamp((t - t0) / 0.07).toFixed(3); b.style.transform = `scale(${(1.08 - 0.08 * k).toFixed(4)})`; };

// ===================== A · you want a million views a month with your Claude reels =====================
{
  const sc = scene(-1, tB, { push: 0 }), el = sc.el;
  const tHit = T1('s0', 'a million') - 0.04, tMo = T0('s0', 'month'), tCl = T0('s0', 'claude'), tRe = T0('s0', 'reels');
  F.cam(sc, [[0, 1, 0, 0], [tHit - 0.05, 1.02, 0, 10], [tHit + 0.08, 1.07, 0, 40], [tHit + 0.5, 1.045, 0, 25], [tB, 1.07, 0, 10]]);
  const num = P.bigNum(el, 540, 516, { size: 176 });
  const burst = P.burst(el, 540, 620, tHit, { n: 20, r0: 330, r1: 520, ry: 0.42, len: 40 });
  const shock = P.shock(el, 540, 620, tHit, { r: 470, ry: 0.36 });
  const per = P.chip(el, 380, 744, '<span class="emo">👁</span><span>views · this month</span>', 'dk', { height: '58px', fontSize: '27px', padding: '0 24px' });
  const win = P.mac(el, 100, 850, 880, 590, { title: 'reels.studio' });
  side(win, ['✦ Create', 'Library', 'Posted', 'Settings'], false);
  const tT = [0.18, 0.5, 0.82, 1.14];
  const skel = tT.map((_, i) => P.skel(win, 214 + i * 160, 96, 146, 258));
  const tiles = [['NIGHT 1', 'IT WORKED'], ['DAY 30', ''], ['I QUIT', 'MY JOB'], ['WATCH', 'THIS']].map((tt, i) => P.reel(win, 214 + i * 160, 96, 146, 258, { title: tt, hue: i, seed: i + 3 }));
  const shines = tiles.map(e => P.shine(e));
  const comp = P.composer(win, 214, 384, 636, 86, { light: false, lead: 'claude' });
  const gen = P.chip(win, 660, 17, '', 'on', { height: '40px' });
  const foot = div(win, 'abs mono', 28, 506, 760, 30, '', { fontSize: '19px', color: '#8d9096' });
  const bar = div(win, 'abs', 28, 546, 822, 8, '<i style="position:absolute;left:0;top:0;bottom:0;width:0;border-radius:4px;background:linear-gradient(90deg,#f070e4,#c42fb8);box-shadow:0 0 10px #e24fd6"></i>', { borderRadius: '4px', background: 'rgba(255,255,255,0.07)', overflow: 'hidden' });
  const stk = P.sticker(el, 560, 806, `${spark(34)}<span>made with Claude</span>`);
  sc.on(t => {
    num.set(F.num(lerp(612408, 1000000, eio(clamp((t + 0.1) / (tHit + 0.1))))));
    num.update(t, tHit); burst.update(t); shock.update(t);
    num.el.style.transform = `scale(${(1 + (t > tHit ? 0.06 * Math.exp(-(t - tHit) * 7) : 0)).toFixed(4)})`;
    E(per, t, tMo - 0.06, { from: 'pop', seed: 1 }); centerX(per, 540);
    tiles.forEach((e, i) => { E(e, t, tT[i], { from: 'zoom', s0: 0.6, float: 0, d: 0.32 }); skel[i].update(t, tT[i] + 0.05); shines[i].update(t, tT[i] + 0.18); });
    tiles.forEach((e, i) => { const k = clamp((t - tRe - i * 0.06) / 0.28); if (k > 0 && k < 1) e.style.transform += ` translateY(${(-16 * Math.sin(k * Math.PI)).toFixed(1)}px)`; });
    comp.type(t, "make tonight's reel about AI tools", 0.02, 1.25);
    const n = tT.filter(x => t > x + 0.1).length;
    html(gen, n < 4 ? `✦ Generating ${n}/4` : '✓ 4 reels ready');
    html(foot, n < 4 ? `<span style="color:${M.mid}">●</span> Rendering · ${n} of 4 reels` : `<span style="color:${M.mid}">●</span> Ready · posting nightly`);
    bar.firstChild.style.width = (100 * clamp(t / 1.3)).toFixed(1) + '%';
    E(stk, t, tCl - 0.05, { from: 'slam', s0: 1.7, rot: -6, seed: 2 });
  });
  cue('counter', 0, 0.06, tHit + 0.02); F.typeCue(0.02, 1.25, 0.045);
  tT.forEach(x => { cue('uipop', x, 0.07); cue('sparkle', x + 0.15, 0.025); });
  cue('bassdrop', tHit - 0.01, 0.11, 0.8); cue('sparkle', tHit, 0.07); cue('uitick', tHit, 0.06);
  cue('uipop', tMo - 0.05, 0.09); cue('whoosh', tCl - 0.18, 0.04); cue('cards', tCl - 0.04, 0.05); cue('uipop', tCl - 0.03, 0.08);
  F.ticks(tRe, tRe + 0.2, 4, 0.05);
}

// ===================== B1 · so you prompt Claude for a new reel every night =====================
{
  const sc = scene(tB, tB2), el = sc.el;
  const tP = T0('s1', 'prompt'), tSend = T0('s1', 'for') - 0.05, tNew = T0('s1', 'new'), tReel = T0('s1', 'reel'), tEv = T0('s1', 'every night'), tEv1 = T1('s1', 'every night');
  F.cam(sc, [[tB, 1.04, 0, 30], [tSend, 1.0, 0, 0], [tEv, 1.02, 0, -20], [tB2, 1.06, 0, -60]]);
  const comp = P.composer(el, 110, 548, 860, 120, { light: false, card: 'gcard', lead: 'claude' });
  const rep = div(el, 'gcard', 110, 690, 860, 96, '', { borderRadius: '26px' });
  rep.innerHTML = `<div style="position:absolute;left:24px;top:22px">${F.claudeBadge(52)}</div><div class="tx" style="position:absolute;left:96px;top:16px;font-size:26px;font-weight:700"></div><div style="position:absolute;left:96px;top:60px;width:560px;height:8px;border-radius:4px;background:rgba(255,255,255,0.08);overflow:hidden"><i style="position:absolute;left:0;top:0;bottom:0;width:0;background:linear-gradient(90deg,#f070e4,#c42fb8);box-shadow:0 0 10px #e24fd6"></i></div>`;
  const repTx = rep.querySelector('.tx'), repBar = rep.querySelector('i');
  const cal = P.cal(el, 175, 812, { cell: 78, gap: 11, title: 'October · reels posted' });
  const big = div(el, 'abs', 0, 1316, 1080, 160, '', { textAlign: 'center' });
  big.innerHTML = `<span style="position:relative;display:inline-flex;align-items:baseline;gap:18px;padding:0 22px"><span class="n" style="font-size:132px;font-weight:900;letter-spacing:-0.05em;background:linear-gradient(180deg,#ff8af2,#c42fb8);-webkit-background-clip:text;background-clip:text;color:transparent;filter:drop-shadow(0 0 22px rgba(226,79,214,0.6))">30</span><span style="font-size:64px;font-weight:800;color:#fff;letter-spacing:-0.03em">nights</span></span>`;
  const bigN = big.querySelector('.n');
  const bx = box(big.firstChild, 0, 0, 10, 10, false); Object.assign(bx.style, { left: '-6px', right: '-6px', top: '22px', bottom: '8px', width: 'auto', height: 'auto' });
  bx.lastChild.style.left = 'auto'; bx.lastChild.style.top = 'auto'; bx.lastChild.style.right = '-10px'; bx.lastChild.style.bottom = '-10px';
  const streak = P.chip(el, 700, 826, '', 'dk', { height: '54px', fontSize: '24px', padding: '0 20px' });
  sc.on(t => {
    E(comp.el, t, tB + 0.02, { from: 'down', dist: 120, seed: 3 });
    comp.type(t, "make tonight's reel", tB + 0.1, tP + 0.42); comp.press(t, tSend);
    E(rep, t, tSend + 0.06, { from: 'up', dist: 60, seed: 4 });
    html(repTx, t < tReel + 0.1 ? `Rendering reel 12 ${'·'.repeat(1 + Math.floor(t * 6) % 3)}` : `<span style="color:${M.mid}">✓</span> Reel 12 posted`);
    repBar.style.width = (100 * tw(t, tSend + 0.1, tReel + 0.1)).toFixed(1) + '%';
    E(cal.el, t, T0('s1', 'claude') - 0.1, { from: 'up', dist: 140, seed: 5 });
    const n = 11 + clamp((t - tReel - 0.1) / 0.12) + 18 * tw(t, tEv - 0.04, tEv + 0.3, x => x);
    cal.update(n);
    E(streak, t, tReel + 0.05, { from: 'pop', seed: 6 });
    html(streak, `<span class="emo">🔥</span><span>${Math.floor(n)}-night streak</span>`);
    E(big, t, tEv + 0.12, { from: 'slam', s0: 1.6, seed: 7, float: 0.6 });
    bigN.textContent = String(Math.max(12, Math.floor(n)));
    snapBox(bx, t, tEv + 0.24);
  });
  F.typeCue(tB + 0.1, tP + 0.42, 0.05); cue('mouse', tSend, 0.12); cue('send', tSend + 0.02, 0.07);
  F.ticks(tSend + 0.15, tReel + 0.1, 4, 0.03); cue('uipop', tReel + 0.1, 0.07); cue('notif', tReel + 0.12, 0.05);
  F.ticks(tEv - 0.03, tEv + 0.3, 18, 0.045); cue('bassdrop', tEv + 0.12, 0.08, 0.6); cue('sparkle', tEv + 0.2, 0.05);
}

// ===================== B2 · and it actually works, BUT they all look the same =====================
{
  const sc = scene(tB2, tC, { in: 'up' }), el = sc.el;
  const tAc = T0('s1', 'actually'), tW = T0('s1', 'works'), tBut = T0('s1', 'but'), tThey = T0('s1', 'they'), tLook = T0('s1', 'look'), tSame = T0('s1', 'same');
  F.cam(sc, [[tB2, 1, 0, 0], [tBut, 1.02, 0, 0], [tThey + 0.1, 1.04, 0, -70], [tC, 1.05, 0, -90]]);
  const win = P.mac(el, 80, 560, 840, 520, { light: true, title: 'reels.studio' });
  const lv = P.chip(win, 640, 17, '● Posted ✓', 'on', { height: '40px' });
  side(win, ['✦ Create', 'Library', 'Posted', 'Settings'], true);
  const tiles = [['NIGHT 12', 'IT WORKED'], ['DAY 30', ''], ['I QUIT', 'MY JOB'], ['WATCH', 'THIS']].map((tt, i) => P.reel(win, 214 + i * 154, 96, 140, 250, { title: tt, hue: i, seed: i + 3 }));
  const shines = tiles.map(e => P.shine(e));
  const views = div(win, 'abs mono', 28, 372, 760, 40, '', { fontSize: '22px', color: '#55585f' });
  const spk = P.burst(el, 805, 600, tW, { n: 12, r0: 30, r1: 110, len: 20, width: 5 });
  const card = div(el, 'lcard', 360, 950, 620, 450);
  card.innerHTML = `<div style="position:absolute;left:28px;top:24px;display:flex;align-items:center;gap:12px;font-size:27px;font-weight:720">${P.ICON(34, '▦')}<span>Your last 6 reels</span></div>`;
  const same = [0, 1, 2, 3, 4, 5].map(i => P.reel(card, 28 + i * 96, 86, 84, 150, { title: ['NIGHT', 'AI TIPS'], hue: 0, seed: 1 }));
  const eqs = [0, 1, 2, 3, 4].map(i => div(card, 'abs', 28 + i * 96 + 78, 140, 24, 40, '=', { fontSize: '34px', fontWeight: 900, color: M.red, textAlign: 'center', zIndex: 5 }));
  const allSame = div(card, 'abs', 28, 262, 580, 70, '', { fontSize: '50px', fontWeight: 840, letterSpacing: '-0.03em', color: '#141518', whiteSpace: 'nowrap' });
  const chips = ['same hook', 'same cuts', 'same look'].map((s, i) => P.chip(card, 28 + i * 186, 356, `= ${s}`, 'rd'));
  const but = P.but(el, 520, 760, tBut, { size: 150, rot: -8, light: true });
  sc.on(t => {
    E(lv, t, tW - 0.06, { from: 'pop', float: 0 });
    tiles.forEach((e, i) => shines[i].update(t, tAc + i * 0.08));
    html(views, `<span style="color:${M.mag}">✓</span> Night 12 posted · <b style="color:#141518">${F.num(lerp(312, 1240, tw(t, tB2 + 0.2, tW + 0.4)))}</b> views`);
    spk.update(t); but.update(t);
    E(card, t, tThey - 0.08, { from: 'up', dist: 520, spin: 4, seed: 8 });
    same.forEach((e, i) => E(e, t, tThey + 0.1 + i * 0.07, { from: 'drop', dist: 120, float: 0 }));
    same.forEach(e => { if (t > tSame - 0.04) { const k = (t - tSame) * 2.2; e.style.transform += ` scale(${(1 + 0.06 * Math.max(0, Math.sin(k * Math.PI * 2)) * Math.exp(-k)).toFixed(4)})`; } });
    eqs.forEach((e, i) => E(e, t, tSame - 0.02 + i * 0.05, { from: 'pop', s0: 0.2, float: 0 }));
    typeIn(allSame, 'all look the same', t, tLook - 0.05, tSame + 0.25, false);
    allSame.style.color = t > tSame - 0.04 ? M.red : '#141518';
    chips.forEach((c, i) => E(c, t, tSame + 0.12 + i * 0.1, { from: 'pop', seed: 9 + i }));
  });
  F.ticks(tB2 + 0.2, tW + 0.3, 8, 0.03); cue('sparkle', tW - 0.08, 0.07); cue('uipop', tW - 0.05, 0.08);
  [0, 1, 2, 3, 4, 5].forEach(i => cue('uitick', tThey + 0.18 + i * 0.07, 0.045));
  F.typeCue(tLook - 0.05, tSame + 0.25, 0.04); F.ticks(tSame, tSame + 0.22, 5, 0.05); [0, 1, 2].forEach(i => cue('uipop', tSame + 0.12 + i * 0.1, 0.06));
}

// ===================== C1 · so you copy a format that's going viral =====================
{
  const sc = scene(tC, tC2), el = sc.el;
  const tCopy = T0('s2', 'copy'), tFmt = T0('s2', 'format'), tGo = T0('s2', 'going'), tV = T0('s2', 'viral');
  const tClk = tCopy - 0.02, tMenu = tCopy + 0.24, tFly = tMenu + 0.08;
  F.cam(sc, [[tC, 1.05, 30, 0], [tClk, 1.02, 20, 0], [tFly + 0.5, 1.0, -20, 0], [tV, 1.03, -10, -20], [tC2, 1.06, -10, -30]]);
  const L = P.mac(el, 90, 580, 400, 760, { title: 'trending' });
  P.reel(L, 26, 96, 348, 520, { title: ['STOP', 'SCROLLING'], hue: 2, seed: 9 });
  const vw = P.chip(L, 26, 640, '', 'dk', { height: '58px', fontSize: '27px', padding: '0 22px' });
  const orb = P.orbiters(el, 290, 960, 7, { rx: 250, ry: 420, t0: tC + 0.1, s: 58, stagger: 0.09 });
  const R = div(el, 'abs', 590, 580, 400, 760, '', { borderRadius: '30px', border: '3px dashed rgba(255,255,255,0.18)', background: 'rgba(255,255,255,0.025)' });
  const rl = div(R, 'abs', 0, 330, 400, 40, 'your version', { textAlign: 'center', fontSize: '28px', fontWeight: 650, color: '#6f727a' });
  const clone = P.reel(el, 616, 676, 348, 520, { title: ['STOP', 'SCROLLING'], hue: 2, seed: 9 });
  const menu = div(el, 'gcard', 330, 830, 280, 120, '', { borderRadius: '20px', zIndex: 50 });
  menu.innerHTML = `<div style="position:absolute;left:12px;right:12px;top:12px;height:46px;border-radius:12px;background:rgba(226,79,214,0.3);display:flex;align-items:center;gap:10px;padding:0 14px;font-size:23px;font-weight:700"><span class="emo">📋</span>Copy format</div><div style="position:absolute;left:26px;top:72px;font-size:22px;color:#8d9096;font-weight:600">Share · Save</div>`;
  const layers = ['hook', 'cuts', 'captions', 'music'].map(s => P.chip(el, 0, 0, `✓ ${s}`, 'on', { height: '46px', fontSize: '22px' }));
  const fire = P.sticker(el, 150, 1268, '<span class="emo">🔥</span><span>going viral</span>');
  const cur = F.cursor(el);
  sc.on(t => {
    E(L, t, tC + 0.02, { from: 'left', dist: 160, seed: 10 });
    orb.update(t);
    const m = 1.2 + 1.2 * tw(t, tC, tV + 0.3, x => x * x);
    html(vw, `<span class="emo">👁</span><span>${m.toFixed(1)}M views</span>`);
    vw.style.boxShadow = t > tV - 0.05 ? `0 0 ${(20 + 14 * Math.sin(t * 9)).toFixed(0)}px rgba(226,79,214,0.85)` : 'none';
    E(R, t, tC + 0.12, { from: 'right', dist: 160, seed: 11 });
    E(menu, t, tMenu - 0.12, { from: 'pop', s0: 0.6, t1: tFly, d1: 0.15, to: 'pop', float: 0 });
    const u = clamp((t - tFly) / 0.6);
    set(clone, t < tFly ? 0 : clamp(u * 3), `translate(${lerp(-500, 0, eio(u)).toFixed(1)}px, ${(-80 * Math.sin(u * Math.PI)).toFixed(1)}px) rotate(${(-8 * Math.sin(u * Math.PI)).toFixed(2)}deg) scale(${(0.8 + 0.2 * eio(u)).toFixed(3)})`, Math.sin(u * Math.PI) * 6);
    rl.style.opacity = u > 0.4 ? 0 : 1;
    layers.forEach((c, i) => { const t0 = tFmt + 0.02 + i * 0.1, q = clamp((t - t0) / 0.4); const x0 = 120, y0 = 820 + i * 60, x1 = 630, y1 = 1080 + i * 54; set(c, t < t0 ? 0 : 1, `translate(${lerp(x0, x1, eio(q)).toFixed(1)}px, ${(lerp(y0, y1, eio(q)) - 90 * Math.sin(q * Math.PI)).toFixed(1)}px) scale(${(0.7 + 0.3 * eo(q)).toFixed(3)})`, Math.sin(q * Math.PI) * 4); });
    E(fire, t, tV - 0.05, { from: 'slam', s0: 1.7, rot: -6, seed: 12 });
    F.drive(cur, t, [[tC + 0.2, 560, 1520], [tClk - 0.05, 300, 900], [tMenu, 400, 860], [tFly + 0.6, 790, 960], [tGo + 0.2, 860, 1180]], [tClk, tMenu + 0.02]);
  });
  cue('mouse', tClk, 0.12); cue('uipop', tMenu - 0.1, 0.07); cue('mouse', tMenu + 0.02, 0.1); cue('swoosh', tFly, 0.07); cue('cards', tFly + 0.55, 0.05);
  [0, 1, 2, 3].forEach(i => cue('uitick', tFmt + 0.4 + i * 0.1, 0.05));
  cue('counter', tC + 0.1, 0.035, tV - tC); cue('riser', tV - 0.75, 0.05, 0.8); cue('bassdrop', tV - 0.02, 0.07, 0.5); cue('cards', tV - 0.03, 0.04);
  F.ticks(tC + 0.1, tC + 0.75, 7, 0.03);
}

// ===================== C2 · and your views start climbing, BUT every video takes you hours =====================
{
  const sc = scene(tC2, tD, { in: 'zoom' }), el = sc.el;
  const tVw = T0('s2', 'views'), tCl = T0('s2', 'climbing'), tBut = T0('s2', 'but'), tEv = T0('s2', 'every'), tHr = T0('s2', 'hours');
  F.cam(sc, [[tC2, 1, 0, 0], [tBut, 1.03, 0, 0], [tEv + 0.15, 1.04, 0, -90], [tHr, 1.05, 0, -110], [tHr + 0.06, 1.07, 0, -120], [tD, 1.06, 0, -120]]);
  const card = P.mac(el, 70, 560, 860, 580, { light: true, title: 'Insights · views' });
  const st = [['VIEWS TODAY', 'mag'], ['FOLLOWERS', ''], ['AVG WATCH', '']].map(([l, k], i) => P.stat(card, 28 + i * 272, 96, 256, l, '0', { light: true, mag: !!k }));
  const ch = P.chart(card, 50, 260, 760, 240, { light: true, pts: [[0, 0.06], [0.14, 0.08], [0.28, 0.12], [0.42, 0.18], [0.56, 0.3], [0.7, 0.48], [0.84, 0.7], [1, 0.95]] });
  div(card, 'abs mono', 50, 520, 760, 30, '<span>Mon</span><span>Tue</span><span>Wed</span><span>Thu</span><span>Fri</span><span>Sat</span><span>Sun</span>', { fontSize: '18px', color: '#9a9b9e', display: 'flex', justifyContent: 'space-between' });
  const pct = P.chip(el, 690, 690, '<span class="emo">📈</span><span>+3,700%</span>', 'on', { height: '50px', fontSize: '25px' });
  const tm = div(el, 'lcard', 420, 990, 540, 410);
  tm.innerHTML = `<div style="position:absolute;left:28px;top:24px;display:flex;align-items:center;gap:12px;font-size:27px;font-weight:720"><span class="sw" style="display:inline-block;width:36px;height:36px;border-radius:50%;border:4px solid #141518;position:relative"><i style="position:absolute;left:12px;top:3px;width:4px;height:13px;background:${M.mag};border-radius:2px;transform-origin:2px 12px"></i></span><span>Editing · reel 14</span></div>`;
  const hand = tm.querySelector('.sw i');
  const clock = div(tm, 'abs mono', 28, 92, 490, 120, '0:00:00', { fontSize: '96px', fontWeight: 800, letterSpacing: '-0.05em', color: '#141518', fontVariantNumeric: 'tabular-nums' });
  const prog = div(tm, 'abs', 28, 232, 484, 12, '<i style="position:absolute;left:0;top:0;bottom:0;width:0;border-radius:6px;background:linear-gradient(90deg,#f070e4,#c42fb8)"></i>', { borderRadius: '6px', background: '#efe9ee', overflow: 'hidden' });
  const steps = div(tm, 'abs mono', 28, 266, 490, 40, '', { fontSize: '21px', color: '#8a8d94' });
  const per = P.chip(tm, 28, 330, '<span>every. single. video.</span>', 'rd', { height: '50px', fontSize: '25px' });
  const but = P.but(el, 520, 880, tBut, { size: 150, rot: -6, light: true });
  const S5 = ['hook', 'cuts', 'captions', 'music', 'export'];
  sc.on(t => {
    const u = tw(t, tVw - 0.1, tBut - 0.02);
    ch.update(u);
    html(st[0].v, F.num(lerp(1284, 48910, u))); html(st[1].v, '+' + F.num(lerp(12, 1204, u))); html(st[2].v, Math.round(lerp(38, 71, u)) + '%');
    E(pct, t, tCl + 0.05, { from: 'pop', seed: 13 });
    but.update(t);
    E(tm, t, tEv - 0.1, { from: 'right', dist: 620, spin: 3, seed: 14 });
    const k = tw(t, tEv, tHr + 0.2, ease), secs = Math.round(lerp(0, 4 * 3600 + 12 * 60 + 37, k));
    html(clock, `${Math.floor(secs / 3600)}:${String(Math.floor(secs / 60) % 60).padStart(2, '0')}:${String(secs % 60).padStart(2, '0')}`);
    clock.style.color = t > tHr - 0.04 ? M.red : '#141518';
    clock.style.transform = t > tHr - 0.04 && t < tHr + 0.4 ? `translateX(${(Math.sin(t * 90) * 6 * (1 - (t - tHr) / 0.4)).toFixed(1)}px)` : '';
    hand.style.transform = `rotate(${(k * 360 * 6).toFixed(0)}deg)`;
    prog.firstChild.style.width = (100 * clamp(k * 0.55)).toFixed(1) + '%';
    html(steps, S5.map((s, i) => `<span style="color:${k * 5 > i + 0.5 ? M.mag : '#c4c5c9'}">${k * 5 > i + 0.5 ? '✓' : '○'} ${s}</span>`).join('  '));
    E(per, t, tHr + 0.06, { from: 'slam', s0: 1.6, seed: 15, rot: -2 });
  });
  cue('counter', tVw - 0.1, 0.045, tBut - tVw); cue('uipop', tCl + 0.05, 0.08); cue('sparkle', tCl + 0.05, 0.04);
  cue('stopwatch', tEv, 0.07, tHr - tEv + 0.25); [0, 1, 2, 3, 4].forEach(i => cue('uitick', lerp(tEv, tHr + 0.2, (i + 0.5) / 5), 0.04));
  cue('error', tHr - 0.02, 0.045, 0.35); cue('cards', tHr + 0.06, 0.05); F.shake(tHr, 6, 0.25);
}

// ===================== D1 · so you buy a content skill that does it for you, BUT it costs $30 =====================
{
  const sc = scene(tD, tD2, { in: 'up' }), el = sc.el;
  const tBuy = T0('s3', 'buy'), tCS = T0('s3', 'content skill'), tDo = T0('s3', 'does'), tFor = T0('s3', 'for'), tYou = T0('s3', 'you', 1), tBut = T0('s3', 'but'), tCo = T0('s3', 'costs'), t30 = T0('s3', '30');
  const tClick = T1('s3', '30') + 0.1;
  F.cam(sc, [[tD, 1, 0, 0], [tBut, 1.03, 0, -10], [t30, 1.05, 30, -30], [tD2, 1.07, 40, -50]]);
  const card = P.mac(el, 110, 560, 860, 820, { title: 'skills.market' });
  const hero = div(card, 'abs', 28, 96, 804, 250, '', { borderRadius: '22px', background: 'linear-gradient(135deg,#f070e4 0%,#c42fb8 55%,#6e1a8e 100%)', overflow: 'hidden', boxShadow: 'inset 0 1px 0 rgba(255,255,255,0.35)' });
  hero.innerHTML = `<div style="position:absolute;right:-60px;top:-60px;width:320px;height:320px;border-radius:50%;background:rgba(255,255,255,0.14)"></div><div style="position:absolute;left:40px;top:56px;font-size:84px;font-weight:900;letter-spacing:-0.04em;color:#fff;line-height:0.95">VIRAL REELS<br><span style="background:#141316;padding:0 12px">SKILL</span></div><div class="st" style="position:absolute;right:46px;bottom:40px;font-size:110px;color:#fff;opacity:0.9">✦</div>`;
  const heroStar = hero.querySelector('.st'), hs = P.shine(hero);
  P.chip(hero, 40, 18, '★ BESTSELLER', '', { height: '36px', fontSize: '18px', background: 'rgba(255,255,255,0.92)' });
  div(card, 'abs', 32, 372, 520, 50, 'Viral Reels Skill', { fontSize: '42px', fontWeight: 800, letterSpacing: '-0.02em' });
  const stars = div(card, 'abs', 32, 426, 560, 34, '', { fontSize: '23px', color: '#8d9096', fontWeight: 600 });
  const feats = ['Writes the hook', 'Cuts the video', 'Adds the captions'].map((s, i) => div(card, 'abs', 32, 484 + i * 58, 520, 48, `<span class="ck" style="display:inline-flex;width:38px;height:38px;border-radius:50%;background:linear-gradient(180deg,#f070e4,#c42fb8);align-items:center;justify-content:center;vertical-align:middle;margin-right:16px">${F.CHECK(24)}</span>${s}`, { fontSize: '29px', fontWeight: 650, color: '#e7e8ea' }));
  const tag = div(card, 'abs', 560, 440, 250, 150, '', { transformOrigin: '30px 74px', zIndex: 42 });
  tag.innerHTML = `<div style="position:absolute;left:0;top:10px;width:250px;height:130px;background:linear-gradient(180deg,#ff4b5c,#c81e30);box-shadow:0 20px 40px rgba(229,40,58,0.45);clip-path:polygon(18% 0,100% 0,100% 100%,18% 100%,0 50%);border-radius:22px"></div><div style="position:absolute;left:22px;top:66px;width:18px;height:18px;border-radius:50%;background:#1b1b1f"></div><div style="position:absolute;left:58px;top:16px;font-size:100px;font-weight:900;letter-spacing:-0.05em;color:#fff;line-height:120px">$30</div>`;
  const btn = P.pill(card, 32, 690, '', 'bl', { width: '796px', height: '96px', justifyContent: 'center', fontSize: '34px', fontWeight: 760 });
  const bsh = P.shine(btn);
  const but = P.but(el, 400, 1010, tBut, { size: 150, rot: -7, light: false });
  const cur = F.cursor(el);
  sc.on(t => {
    E(hero, t, tBuy - 0.25, { from: 'pop', s0: 0.85, seed: 16, float: 0.4 });
    hs.update(t, tCS + 0.05, 0.6); heroStar.style.transform = `rotate(${(t * 40).toFixed(0)}deg) scale(${(1 + 0.08 * Math.sin(t * 5)).toFixed(3)})`;
    const ns = Math.floor(tw(t, tCS + 0.1, tCS + 0.55, x => x) * 5 + 0.001);
    html(stars, `<span style="color:#f4b740">${'★'.repeat(ns)}</span><span style="color:#3a3b40">${'★'.repeat(5 - ns)}</span> 4.9 · ${F.num(lerp(1800, 2314, tw(t, tCS, tDo)))} sold`);
    feats.forEach((f, i) => { const ti = [tDo, tFor, tYou][i]; E(f, t, ti - 0.08, { from: 'left', dist: 60, float: 0 }); const q = clamp((t - ti + 0.02) / 0.3); f.firstChild.style.transform = `scale(${back(q).toFixed(3)}) rotate(${((1 - q) * -90).toFixed(0)}deg)`; });
    but.update(t);
    E(tag, t, t30 - 0.05, { from: 'slam', s0: 1.8, float: 0 }); if (t > t30) tag.style.transform += ` rotate(${F.swing(t, t30 + 0.12, 14, 8, 2.6).toFixed(2)}deg)`;
    const proc = t > tClick && t < tClick + 0.3;
    html(btn, t > tClick + 0.3 ? `${F.CHECK(34)}<span>Paid · $30.00</span>` : proc ? `<span style="display:inline-block;width:30px;height:30px;border-radius:50%;border:4px solid rgba(255,255,255,0.35);border-top-color:#fff;transform:rotate(${(t * 900).toFixed(0)}deg)"></span><span>Processing…</span>` : t > t30 - 0.05 ? '<span>Buy now · $30.00</span>' : '<span>Buy now</span>');
    bsh.update(t, tBuy + 0.1, 0.5);
    const pr = clamp((t - tClick) / 0.08) - clamp((t - tClick - 0.08) / 0.12); btn.style.transform = `scale(${(1 - 0.05 * pr).toFixed(3)})`;
    F.drive(cur, t, [[tCo - 0.1, 820, 1520], [tClick - 0.04, 560, 1310], [tClick + 0.6, 600, 1350]], [tClick]);
  });
  cue('sparkle', tCS + 0.05, 0.05); F.ticks(tCS + 0.1, tCS + 0.55, 5, 0.05);
  [tDo, tFor, tYou].forEach(x => { cue('uipop', x - 0.06, 0.06); cue('uitick', x, 0.04); });
  cue('kaching', t30 - 0.02, 0.09); cue('mouse', tClick, 0.12); F.ticks(tClick + 0.05, tClick + 0.28, 3, 0.03); cue('notif', tClick + 0.3, 0.06);
}

// ===================== D2 · and it's just one text file =====================
{
  const sc = scene(tD2, tE), el = sc.el;
  const tJ = T0('s3', 'just'), tOne = T0('s3', 'one text file'), tDrop = tJ - 0.02;
  F.cam(sc, [[tD2, 1, 0, 0], [tDrop, 1.0, 0, 0], [tOne + 0.1, 1.1, 10, -110], [tE, 1.13, 10, -130]]);
  const dl = P.mac(el, 90, 560, 860, 210, { light: true, title: 'Downloads' });
  const zip = div(dl, 'abs', 28, 96, 70, 84, '<span style="font:800 18px/1 var(--mono);color:#fff">ZIP</span>', { borderRadius: '14px', background: 'linear-gradient(160deg,#f070e4,#b0219f)', display: 'flex', alignItems: 'flex-end', justifyContent: 'center', paddingBottom: '12px' });
  div(dl, 'abs mono', 118, 100, 520, 40, 'viral-reels-skill.zip', { fontSize: '27px', fontWeight: 700, color: '#141518' });
  const paid = P.chip(dl, 640, 98, '$30.00 paid', 'rd');
  const pb = div(dl, 'abs', 118, 154, 700, 10, '<i style="position:absolute;left:0;top:0;bottom:0;width:0;border-radius:5px;background:linear-gradient(90deg,#f070e4,#c42fb8)"></i>', { borderRadius: '5px', background: '#efe9ee', overflow: 'hidden' });
  const file = P.file(el, 200, 830, 640, 470, { name: 'SKILL.md', size: '4 KB', lines: ['# Viral reels', 'Hook in the first 3 seconds.', 'Cut every 1.5 seconds.', 'Big captions, word by word.', 'End on a question.', "(that's the whole file)"] });
  const sel = box(el, 186, 816, 668, 498, true);
  const bu = P.burst(el, 520, 1065, tOne + 0.04, { n: 14, r0: 340, r1: 470, ry: 0.7, len: 26, width: 5 });
  const one = P.sticker(el, 560, 1250, '<span>1 file · 4 KB</span>');
  sc.on(t => {
    E(dl, t, tD2, { from: 'down', dist: 160, seed: 17 });
    pb.firstChild.style.width = (100 * tw(t, tD2 + 0.05, tJ - 0.1)).toFixed(1) + '%';
    zip.style.transform = t > tJ - 0.12 && t < tJ + 0.1 ? `rotate(${(Math.sin(t * 80) * 8).toFixed(1)}deg)` : '';
    E(paid, t, tD2 + 0.1, { from: 'pop', float: 0 }); paid.style.boxShadow = `0 0 ${(12 + 10 * Math.sin(t * 7)).toFixed(0)}px rgba(229,40,58,0.4)`;
    E(file.el, t, tDrop, { from: 'drop', dist: 420, spin: -6, seed: 18 });
    file.lines.forEach((l, i) => { l.style.opacity = clamp((t - tJ - 0.15 - i * 0.07) / 0.1).toFixed(2); });
    snapBox(sel, t, tOne); bu.update(t);
    E(one, t, tOne + 0.28, { from: 'slam', s0: 1.7, rot: -5, seed: 19 });
  });
  F.ticks(tD2 + 0.05, tJ - 0.1, 6, 0.035); cue('error', tJ - 0.12, 0.025, 0.2); cue('swoosh', tDrop, 0.05); cue('cards', tDrop + 0.32, 0.07);
  F.typeCue(tJ + 0.15, tJ + 0.6, 0.04); cue('uipop', tOne, 0.08); cue('sparkle', tOne + 0.04, 0.04); cue('cards', tOne + 0.28, 0.05); cue('uipop', tOne + 0.28, 0.07);
}

// ===================== E1 · I'm Casper, and I built Morphy, an agentic super app =====================
{
  const sc = scene(tE, tE2), el = sc.el;
  const tCa = T0('s4', 'Casper'), tBu = T0('s4', 'built'), tMo = T0('s4', 'morphy'), tAg = T0('s4', 'agentic'), tSu = T0('s4', 'super'), tAp = T0('s4', 'app');
  F.cam(sc, [[tE, 1.4, 0, 560], [tCa + 0.3, 1.36, 0, 540], [tBu - 0.05, 1.3, 0, 500], [tMo + 0.3, 1.0, 0, 0], [tAg, 1.0, 0, 0], [tE2, 0.97, 0, 10]]);
  const me = div(el, 'gcard', 290, 548, 500, 120, '', { borderRadius: '60px' });
  const tags = [['🛠️ builder', 240, 470, -5], ['📍 Belgium', 640, 456, 4], ['✦ makes apps with AI', 300, 700, -2]].map(([s2, x, y, r]) => ({ e: P.chip(el, x, y, s2, 'dk', { height: '50px', fontSize: '23px' }), r }));
  me.innerHTML = `<div style="position:absolute;left:18px;top:16px;width:88px;height:88px;border-radius:50%;background:radial-gradient(circle at 35% 30%,#f070e4,#5a1052);overflow:hidden;border:3px solid rgba(255,255,255,0.4)">${F.PERSON('rgba(255,255,255,0.9)')}</div><div style="position:absolute;left:128px;top:20px;font-size:40px;font-weight:800">Casper <span class="emo wv" style="display:inline-block;transform-origin:70% 80%">👋</span></div><div style="position:absolute;left:128px;top:70px;font-size:22px;color:#8d9096;font-weight:600">builder · solo</div>`;
  const wave = me.querySelector('.wv');
  const W = { x: 110, y: 720, w: 860, h: 560 };
  const frame = P.wire(el, `M${W.x + 30} ${W.y} H${W.x + W.w - 30} Q${W.x + W.w} ${W.y} ${W.x + W.w} ${W.y + 30} V${W.y + W.h - 30} Q${W.x + W.w} ${W.y + W.h} ${W.x + W.w - 30} ${W.y + W.h} H${W.x + 30} Q${W.x} ${W.y + W.h} ${W.x} ${W.y + W.h - 30} V${W.y + 30} Q${W.x} ${W.y} ${W.x + 30} ${W.y}`, { t0: tBu - 0.05, dur: 0.35, width: 4 });
  const win = P.mac(el, W.x, W.y, W.w, W.h, { title: 'Morphy', glyph: '✦' });
  const cv = div(win, 'abs', 20, 92, 820, 448, '', { borderRadius: '18px', background: 'radial-gradient(rgba(255,255,255,0.09) 1.5px, transparent 1.5px) 0 0 / 28px 28px, rgba(255,255,255,0.02)' });
  const NODES = [[40, 40, 'Chat', 'writing the hook…'], [300, 160, 'Agent', 'cutting the reel'], [560, 40, 'Card', 'views · 48.9K'], [60, 290, 'Note', 'ideas for tonight'], [560, 290, 'Chat', 'captions done ✓']];
  const nodes = NODES.map(([x, y, a, b], i) => div(cv, 'abs', x, y, 220, 120, `<div style="display:flex;align-items:center;gap:10px;font-size:22px;font-weight:720;color:#f3f3f1">${P.ICON(28, ['✦', '◆', '▦', '✎', '✦'][i])}${a}<span class="st" style="margin-left:auto;width:12px;height:12px;border-radius:50%;background:${M.mid};box-shadow:0 0 10px ${M.mag2}"></span></div><div style="margin-top:12px;font-size:18px;color:#a4a6aa;font-weight:600">${b}</div>`, { borderRadius: '18px', padding: '18px', background: 'linear-gradient(180deg,#26232a,#1b191e)', border: '1px solid rgba(255,255,255,0.1)', boxShadow: '0 14px 30px rgba(0,0,0,0.4)' }));
  const wires = [['M260 100 C 300 100, 280 220, 300 220', 0], ['M520 220 C 560 220, 540 100, 560 100', 1], ['M280 350 C 300 350, 280 250, 300 250', 2], ['M520 250 C 545 250, 535 350, 560 350', 3]].map(([d, i]) => P.wire(cv, d, { t0: tMo + 0.25 + i * 0.1, dur: 0.25, width: 3, pulses: 2, w: 820, hh: 448, speed: 1.2 }));
  const run = P.chip(cv, 300, 120, '● running', 'on', { height: '34px', fontSize: '17px' });
  const CH = [['🤖', 'agents', 40, 1330], ['💬', 'chats', 290, 1330], ['🗂️', 'cards', 520, 1330], ['⚡', 'flows', 760, 1330]];
  const chips = CH.map(([e, s]) => P.chip(el, 0, 0, `<span class="emo">${e}</span><span>${s}</span>`, 'dk', { height: '62px', fontSize: '28px', padding: '0 24px' }));
  const stk = P.sticker(el, 590, 676, '<span>agentic super app</span>');
  sc.on(t => {
    E(me, t, tE + 0.04, { from: 'down', dist: 120, seed: 20 });
    tags.forEach((g, i) => E(g.e, t, tCa + 0.05 + i * 0.12, { from: 'pop', rot: g.r, seed: 90 + i, t1: tBu - 0.05, d1: 0.2 }));
    wave.style.transform = `rotate(${(t > tCa ? 18 * Math.sin((t - tCa) * 16) * Math.exp(-(t - tCa) * 2.5) : 0).toFixed(1)}deg)`;
    frame.update(t); frame.el.style.opacity = (1 - clamp((t - tBu - 0.4) / 0.25)).toFixed(2);
    E(win, t, tBu + 0.2, { from: 'fade', blur: 10, float: 0.5, seed: 21 });
    nodes.forEach((n, i) => E(n, t, tMo + i * 0.09, { from: 'pop', s0: 0.4, seed: 22 + i }));
    wires.forEach(w => w.update(t));
    E(run, t, tAg - 0.04, { from: 'pop', float: 0 }); nodes[1].style.boxShadow = t > tAg ? `0 0 ${(24 + 12 * Math.sin(t * 6)).toFixed(0)}px rgba(226,79,214,0.6)` : '';
    nodes.forEach((n, i) => { n.querySelector('.st').style.opacity = (0.5 + 0.5 * Math.sin(t * 7 + i * 1.3)).toFixed(2); });
    chips.forEach((c, i) => { const t0 = lerp(tSu - 0.05, tAp + 0.15, i / 3), q = clamp((t - t0) / 0.45); const [, , x1, y1] = CH[i]; set(c, t < t0 ? 0 : clamp(q * 3), `translate(${lerp(470, x1, eo(q)).toFixed(1)}px, ${(lerp(980, y1, eo(q)) + (q >= 1 ? Math.sin(t * 1.5 + i) * 5 : 0)).toFixed(1)}px) scale(${(0.4 + 0.6 * back(q)).toFixed(3)})`, (1 - eo(q)) * 6); });
    E(stk, t, tAg - 0.05, { from: 'slam', s0: 1.7, rot: 5, seed: 26 });
  });
  cue('uipop', tE + 0.04, 0.08); [0, 1, 2].forEach(i => cue('uipop', tCa + 0.05 + i * 0.12, 0.05)); cue('swoosh', tBu - 0.05, 0.06); F.ticks(tBu - 0.02, tBu + 0.3, 4, 0.03); cue('sparkle', tBu + 0.2, 0.05);
  [0, 1, 2, 3, 4].forEach(i => cue('uipop', tMo + i * 0.09, 0.045)); [0, 1, 2, 3].forEach(i => cue('uitick', tMo + 0.3 + i * 0.1, 0.035));
  cue('cards', tAg - 0.04, 0.05); cue('uipop', tAg - 0.02, 0.08); [0, 1, 2, 3].forEach(i => cue('uitick', lerp(tSu - 0.05, tAp + 0.15, i / 3) + 0.3, 0.05));
}

// ===================== E2 · I gave my own format away for free, BUT people repackaged it and sell it for $30 =====================
{
  const sc = scene(tE2, tF, { in: 'up' }), el = sc.el;
  const tAw = T0('s4', 'away'), tFr = T0('s4', 'for free'), tBut = T0('s4', 'but'), tPe = T0('s4', 'people'), tRe = T0('s4', 'repackaged'), tSt = T0('s4', 'started'), tSe = T0('s4', 'selling'), t30 = T0('s4', '30');
  const tPub = tAw - 0.05;
  F.cam(sc, [[tE2, 1, 0, 0], [tBut, 1.02, 10, 10], [tRe, 1.04, 0, -120], [tF, 1.05, 0, -150]]);
  const a = P.mac(el, 70, 540, 660, 450, { light: true, title: 'my-format' });
  const ff = P.file(a, 28, 92, 604, 200, { name: 'SKILL.md', size: '4 KB', lines: ['# My content format', 'Hook, beats, captions, cuts.'] });
  ff.el.style.boxShadow = 'none'; ff.el.style.border = '1.5px solid rgba(0,0,0,0.07)';
  div(a, 'abs', 28, 314, 250, 110, '$0', { fontSize: '96px', fontWeight: 900, letterSpacing: '-0.05em', color: M.mag });
  const pub = P.pill(a, 300, 332, '', 'bl', { width: '332px', height: '76px', justifyContent: 'center', fontSize: '28px' });
  const orb = P.orbiters(el, 400, 765, 8, { rx: 400, ry: 290, t0: tPub + 0.15, s: 54, stagger: 0.06 });
  const arrows = Array.from({ length: 8 }, () => div(el, 'abs', 0, 0, 40, 40, '<svg width="40" height="40" viewBox="0 0 24 24"><path d="M12 4v13M6 11.5L12 17.5l6-6M5 20h14" fill="none" stroke="#c42fb8" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round"/></svg>', { zIndex: 36 }));
  const fs = P.sticker(el, 440, 948, '<span>FREE · for everyone</span>');
  const but = P.but(el, 400, 740, tBut, { size: 140, rot: -9, light: true });
  const b = P.mac(el, 390, 1000, 600, 420, { light: true, title: 'skills.market' });
  const bx = div(b, 'abs', 26, 92, 548, 180, '', { borderRadius: '20px', background: 'linear-gradient(135deg,#f070e4,#c42fb8 60%,#6e1a8e)', overflow: 'hidden' });
  bx.innerHTML = `<div class="lbl" style="position:absolute;left:28px;top:30px;font-size:58px;font-weight:900;letter-spacing:-0.04em;color:#fff;line-height:0.95;transform-origin:0 50%">ULTIMATE<br>VIRAL PACK</div>`;
  const lbl = bx.querySelector('.lbl'), bsh = P.shine(bx);
  const fic = div(el, 'abs', 0, 0, 96, 120, '<span style="font:900 20px/1 var(--mono);color:#c42fb8">MD</span>', { borderRadius: '12px', background: '#fff', border: '2px solid #e8d7e8', boxShadow: '0 14px 30px rgba(120,20,110,0.3)', display: 'flex', alignItems: 'flex-end', justifyContent: 'center', paddingBottom: '12px', zIndex: 48 });
  const rp = P.chip(b, 26, 292, 'repackaged', 'rd', { height: '48px', fontSize: '24px' });
  const tag = div(b, 'abs', 330, 270, 240, 130, '', { transformOrigin: '30px 64px', zIndex: 42 });
  tag.innerHTML = `<div style="position:absolute;left:0;top:6px;width:240px;height:118px;background:linear-gradient(180deg,#ff4b5c,#c81e30);box-shadow:0 18px 36px rgba(229,40,58,0.45);clip-path:polygon(18% 0,100% 0,100% 100%,18% 100%,0 50%);border-radius:20px"></div><div style="position:absolute;left:22px;top:56px;width:16px;height:16px;border-radius:50%;background:#fff"></div><div style="position:absolute;left:56px;top:6px;font-size:92px;font-weight:900;letter-spacing:-0.05em;color:#fff;line-height:118px">$30</div>`;
  const sold = div(b, 'abs mono', 26, 356, 300, 40, '', { fontSize: '24px', color: '#55585f', fontWeight: 700 });
  const notes = [0, 1].map(i => P.notif(el, 560, i ? 1438 : 1320, 440, { app: 'SKILLS.MARKET', text: 'New sale · $30.00', time: 'now', light: true }));
  const cur = F.cursor(el), c2 = P.cursor2(el, 'someone');
  sc.on(t => {
    E(a, t, tE2, { from: 'fade', seed: 27 });
    html(pub, t > tPub + 0.08 ? `${F.CHECK(30)}<span>Published · ${F.num(lerp(0, 1284, tw(t, tPub + 0.1, tBut, x => x)))}</span>` : '<span>Publish free</span>');
    const pr = clamp((t - tPub) / 0.08) - clamp((t - tPub - 0.08) / 0.12); pub.style.transform = `scale(${(1 - 0.06 * pr).toFixed(3)})`;
    F.drive(cur, t, [[tE2 + 0.1, 760, 1500], [tPub - 0.04, 500, 900], [tPub + 0.4, 560, 1000]], [tPub]);
    orb.update(t);
    arrows.forEach((e, i) => { const t0 = tPub + 0.15 + i * 0.07, q = clamp((t - t0) / 0.4); if (q <= 0 || q >= 1) { e.style.opacity = 0; return; } const ang = (i / 8) * TAU; e.style.opacity = Math.sin(q * Math.PI).toFixed(2); e.style.transform = `translate(${(380 + Math.cos(ang) * 330 * eo(q)).toFixed(1)}px, ${(745 + Math.sin(ang) * 240 * eo(q)).toFixed(1)}px)`; });
    E(fs, t, tFr - 0.05, { from: 'slam', s0: 1.7, rot: -5, t1: tBut - 0.06, d1: 0.12, to: 'pop', seed: 28 });
    but.update(t);
    E(b, t, tPe - 0.12, { from: 'right', dist: 700, spin: 5, seed: 29 });
    const g0 = tPe + 0.05, g1 = tRe + 0.12, q = clamp((t - g0) / (g1 - g0));
    set(fic, t < g0 - 0.1 || t > g1 + 0.12 ? 0 : 1, `translate(${lerp(160, 700, eio(q)).toFixed(1)}px, ${(lerp(650, 1110, eio(q)) - 120 * Math.sin(q * Math.PI)).toFixed(1)}px) rotate(${(10 * Math.sin(q * Math.PI)).toFixed(1)}deg) scale(${(1 - 0.35 * clamp((t - g1) / 0.12)).toFixed(3)})`);
    F.drive(c2, t, [[tPe - 0.2, 800, 560], [g0, 200, 690], [g1, 740, 1150], [tSt + 0.3, 780, 1300]], [g0]);
    lbl.style.transform = `scale(${(t > tRe + 0.15 ? 1 + 0.1 * Math.exp(-(t - tRe - 0.15) * 8) : 1).toFixed(3)})`; bsh.update(t, tRe + 0.15, 0.5);
    E(rp, t, tRe + 0.15, { from: 'pop', float: 0 });
    E(tag, t, t30 - 0.05, { from: 'slam', s0: 1.8, float: 0 }); if (t > t30) tag.style.transform += ` rotate(${F.swing(t, t30 + 0.1, 14, 8, 2.6).toFixed(2)}deg)`;
    html(sold, `Sold: <span style="color:${M.red}">${F.num(Math.floor(lerp(0, 214, tw(t, tSe - 0.05, tF, x => x))))}</span>`);
    notes.forEach((n, i) => E(n, t, tSe + 0.1 + i * 0.32, { from: 'down', dist: 100, seed: 30 + i }));
  });
  cue('mouse', tPub, 0.12); cue('send', tPub + 0.05, 0.06); [0, 1, 2, 3, 4, 5, 6, 7].forEach(i => cue('uitick', tPub + 0.25 + i * 0.07, 0.035));
  cue('uipop', tFr - 0.05, 0.09); cue('sparkle', tFr, 0.06);
  cue('swoosh', tPe - 0.12, 0.07); cue('mouse', tPe + 0.05, 0.1); cue('whoosh', tPe + 0.2, 0.04); cue('cards', tRe + 0.12, 0.07); cue('uipop', tRe + 0.15, 0.07);
  cue('kaching', t30 - 0.02, 0.09); cue('notif', tSe + 0.1, 0.06); cue('notif', tSe + 0.42, 0.05); F.ticks(tSe, tF - 0.1, 10, 0.03);
}

// ===================== F1 · $30, for a text file that was free to begin with =====================
{
  const sc = scene(tF, tF2), el = sc.el;
  const t30 = S.s5.start, tTx = T0('s5', 'text'), tFi = T0('s5', 'file'), tFree = T0('s5', 'free'), tBeg = T0('s5', 'begin');
  F.cam(sc, [[tF, 1.06, 0, 80], [t30 + 0.1, 1.0, 0, 0], [tTx, 1.0, 0, 0], [tFree, 1.05, 0, -150], [tF2, 1.07, 0, -170]]);
  const big = div(el, 'abs', 0, 510, 1080, 190, '$30', { textAlign: 'center', fontSize: '180px', fontWeight: 900, letterSpacing: '-0.05em', color: M.red, textShadow: '0 0 60px rgba(229,40,58,0.55)' });
  const shock = P.shock(el, 540, 605, t30, { r: 380, color: M.red, ry: 0.5 });
  const printer = div(el, 'gcard', 210, 720, 660, 70, '', { borderRadius: '22px', zIndex: 5 });
  printer.innerHTML = `<div style="position:absolute;left:30px;right:30px;top:40px;height:10px;border-radius:5px;background:#050506"></div><div class="led" style="position:absolute;right:30px;top:14px;width:14px;height:14px;border-radius:50%;background:${M.mid}"></div>`;
  const led = printer.querySelector('.led');
  const clipBox = div(el, 'abs', 250, 765, 580, 700, '', { overflow: 'hidden', zIndex: 4 });
  const rc = div(clipBox, 'abs', 0, 0, 580, 640, '', { background: '#f7f5f0', color: '#1b1b1d', fontFamily: 'var(--mono)', clipPath: 'polygon(0 0,100% 0,100% 96%,95% 100%,90% 96%,85% 100%,80% 96%,75% 100%,70% 96%,65% 100%,60% 96%,55% 100%,50% 96%,45% 100%,40% 96%,35% 100%,30% 96%,25% 100%,20% 96%,15% 100%,10% 96%,5% 100%,0 96%)' });
  rc.innerHTML = `<div style="position:absolute;left:36px;top:34px;font:900 38px/1 'Inter Tight';letter-spacing:-0.02em">receipt</div><div style="position:absolute;left:36px;top:82px;font-size:19px;color:#77777b">skills.market · order #2314</div><div style="position:absolute;left:36px;right:36px;top:124px;border-top:2px dashed #c9c6bf"></div><div style="position:absolute;left:36px;right:36px;top:418px;border-top:2px dashed #c9c6bf"></div>`;
  [['1 × SKILL.md', '$30.00', ''], ['  (one text file)', '', 'sub'], ['original price', '$0.00', 'free'], ['you paid', '$30.00', 'tot']].forEach(([l, r, k], i) => div(rc, 'abs', 36, 150 + i * 70 + (k === 'tot' ? 60 : 0), 508, 54, `<span>${l}</span><span>${r}</span>`, { display: 'flex', justifyContent: 'space-between', alignItems: 'center', fontSize: k === 'tot' ? '34px' : '27px', fontWeight: k === 'tot' ? 800 : 600, color: k === 'tot' ? M.red : k === 'sub' ? '#8a8a8e' : '#1b1b1d' }));
  const ul = div(rc, 'abs', 36, 200, 0, 4, '', { background: M.red, borderRadius: '2px' });
  const sel = box(rc, 24, 282, 532, 70, true);
  const fr = P.sticker(el, 610, 1080, '<span>was free</span>');
  const pc = P.chip(el, 640, 690, '+$30 for nothing', 'rdk', { height: '48px', fontSize: '23px' });
  sc.on(t => {
    E(big, t, t30 - 0.05, { from: 'slam', s0: 2.2, seed: 31, float: 0.6 }); shock.update(t);
    E(printer, t, tF + 0.05, { from: 'down', dist: 80, float: 0 }); led.style.opacity = (t > t30 + 0.3 && t < tBeg ? 0.4 + 0.6 * Math.abs(Math.sin(t * 12)) : 0.3).toFixed(2);
    const pr = tw(t, t30 + 0.35, tBeg + 0.1, x => x);
    rc.style.transform = `translateY(${lerp(-640, 0, pr).toFixed(1)}px) rotate(${(pr * -1.5).toFixed(2)}deg)`; rc.style.visibility = pr > 0.02 ? 'visible' : 'hidden';
    ul.style.width = px(330 * tw(t, tTx, tFi + 0.2));
    snapBox(sel, t, tFree);
    E(fr, t, tFree + 0.12, { from: 'slam', s0: 1.7, rot: 6, seed: 32 });
    E(pc, t, tBeg - 0.05, { from: 'pop', seed: 33 });
  });
  cue('boom', t30 - 0.02, 0.1, 1); cue('stampslam', t30 - 0.02, 0.1); F.shake(t30 + 0.02, 9, 0.28);
  cue('counter', t30 + 0.35, 0.05, tBeg - t30 - 0.25); cue('uitick', tTx, 0.05);
  cue('uipop', tFree, 0.08); cue('cards', tFree + 0.12, 0.05); cue('sparkle', tFree + 0.12, 0.05); cue('uipop', tBeg - 0.05, 0.06);
}

// ===================== F2 · so here's my response =====================
{
  const sc = scene(tF2, tG), el = sc.el;
  const tSo = T0('s5', 'so'), tRes = T0('s5', 'response'), tSend = T1('s5', 'response') + 0.02;
  F.cam(sc, [[tF2, 1.05, 0, 40], [tSend, 1.0, 0, 0], [tG, 1.03, 0, -30]]);
  const q = div(el, 'lcard', 130, 640, 820, 170);
  q.innerHTML = `<div style="position:absolute;left:26px;top:24px;width:6px;height:122px;border-radius:3px;background:${M.mag}"></div><div style="position:absolute;left:52px;top:26px;font-size:20px;font-weight:700;letter-spacing:0.1em;color:#8a8d94">REPLYING TO</div><div style="position:absolute;left:52px;top:62px;font-size:34px;font-weight:800">Ultimate Viral Pack</div><div style="position:absolute;left:52px;top:110px;font-size:24px;color:#8a8d94;font-weight:600">one text file · <s style="color:${M.red}">$30</s></div>`;
  const comp = P.composer(el, 130, 1150, 820, 124, { light: true, lead: null, placeholder: 'Write a reply…' });
  const bub = P.bubble(el, 900, 'Every format. Free. ✦', { right: 130, size: 46 });
  const con = P.confetti(el, 760, 940, tSend + 0.25, { n: 30, spread: 420, up: 700 });
  const sub = P.chip(el, 650, 1040, '<span>no catch</span>', 'on', { height: '52px', fontSize: '26px' });
  sc.on(t => {
    E(q, t, tF2 + 0.02, { from: 'down', dist: 100, seed: 34 });
    E(comp.el, t, tF2 + 0.08, { from: 'up', dist: 120, seed: 35 });
    if (t < tSo) comp.hint('Write a reply…'); else comp.type(t, 'Every format. Free.', tSo + 0.05, tRes + 0.15, t < tSend);
    comp.press(t, tSend);
    const u = clamp((t - tSend - 0.05) / 0.35);
    set(bub, t < tSend + 0.05 ? 0 : clamp(u * 3), `translateY(${(lerp(260, 0, eo(u)) + (u >= 1 ? Math.sin(t * 1.3) * 4 : 0)).toFixed(1)}px) scale(${(0.7 + 0.3 * back(u)).toFixed(3)})`, (1 - eo(u)) * 8);
    con.update(t);
    E(sub, t, tSend + 0.4, { from: 'pop', seed: 36 });
  });
  F.typeCue(tSo + 0.05, tRes + 0.15, 0.05); cue('mouse', tSend, 0.12); cue('send', tSend + 0.04, 0.08); cue('sparkle', tSend + 0.25, 0.06); cue('uipop', tSend + 0.4, 0.07);
}

// ===================== G1 · an entire library of content-format .md files, inside Morphy =====================
{
  const sc = scene(tG, tH, { in: 'zoom' }), el = sc.el;
  const tPut = T0('s5', 'putting'), tLib = T0('s5', 'entire library'), tCf = T0('s5', 'content-format'), tMd = T0('s5', '.md'), tIn = T0('s5', 'inside'), tMo = T0('s5', 'morphy');
  F.cam(sc, [[tG, 1.08, 0, 140], [tLib, 1.07, 0, 120], [tCf, 1.0, 0, 0], [tIn - 0.05, 1.0, 0, 0], [tIn + 0.3, 0.9, 0, 0], [tH, 0.9, 0, -10]]);
  const win = P.mac(el, 80, 560, 920, 800, { title: 'Content formats' });
  const cnt = P.chip(win, 640, 17, '', 'on', { height: '40px' });
  const names = ['motion', 'kinetic', 'voxel', 'light', 'terracotta', 'sandbox', 'paper', 'pixel', 'glow', 'halftone', 'lyric', 'toybox', 'comic', 'wild', 'lantern', 'stamp'];
  const t0s = names.map((_, i) => lerp(tPut, tMd - 0.1, Math.pow(i / names.length, 0.85)));
  const files = names.map((n, i) => {
    const x = 28 + (i % 4) * 218, y = 96 + Math.floor(i / 4) * 170, me = n === 'stamp';
    return div(win, 'abs', x, y, 200, 152, `<div style="width:52px;height:64px;border-radius:9px;background:${me ? 'linear-gradient(160deg,#f070e4,#b0219f)' : 'rgba(255,255,255,0.08)'};border:2px solid ${me ? 'transparent' : 'rgba(255,255,255,0.16)'};display:flex;align-items:flex-end;justify-content:center;padding-bottom:7px;font:800 13px/1 var(--mono);color:${me ? '#fff' : M.mid}">MD</div><div class="mono" style="margin-top:14px;font-size:22px;font-weight:700;color:#f3f3f1;white-space:nowrap">${n}.md</div><div style="margin-top:4px;font-size:16px;color:${M.mid};font-weight:700">free</div>`,
      { borderRadius: '18px', padding: '16px 18px', background: me ? 'rgba(196,47,184,0.22)' : 'rgba(255,255,255,0.04)', border: me ? `2px solid ${M.mag2}` : '1px solid rgba(255,255,255,0.08)' });
  });
  const sh = files.map(f => P.shine(f, { color: 'rgba(240,112,228,0.35)' }));
  const fw = { x: 40, y: 500, w: 1000, h: 920 };
  const outer = P.wire(el, `M${fw.x + 40} ${fw.y} H${fw.x + fw.w - 40} Q${fw.x + fw.w} ${fw.y} ${fw.x + fw.w} ${fw.y + 40} V${fw.y + fw.h - 40} Q${fw.x + fw.w} ${fw.y + fw.h} ${fw.x + fw.w - 40} ${fw.y + fw.h} H${fw.x + 40} Q${fw.x} ${fw.y + fw.h} ${fw.x} ${fw.y + fw.h - 40} V${fw.y + 40} Q${fw.x} ${fw.y} ${fw.x + 40} ${fw.y}`, { t0: tIn - 0.02, dur: 0.4, width: 5, pulses: 3, speed: 0.5 });
  const tab = div(el, 'abs', 80, 436, 290, 64, `${P.ICON(40, '✦')}<span style="font-size:32px;font-weight:800">Morphy</span>`, { display: 'flex', alignItems: 'center', gap: '14px', padding: '0 22px', borderRadius: '20px 20px 0 0', background: 'linear-gradient(180deg,#2a2230,#1c1720)', border: `2px solid ${M.mag2}`, borderBottom: 'none' });
  const inM = P.sticker(el, 600, 1384, '<span style="font-size:34px">✦</span><span>inside Morphy</span>');
  sc.on(t => {
    files.forEach((f, i) => { E(f, t, t0s[i], { from: 'up', dist: 90, blur: 8, seed: 40 + i, float: 0.4 }); sh[i].update(t, tMd + 0.02 + (i % 4) * 0.05 + Math.floor(i / 4) * 0.08, 0.45); });
    html(cnt, `● ${files.filter((_, i) => t > t0s[i]).length} formats · all free`);
    outer.update(t);
    E(tab, t, tMo - 0.1, { from: 'up', dist: 60, float: 0 });
    E(inM, t, tMo - 0.04, { from: 'slam', s0: 1.7, rot: -4, seed: 60 });
  });
  t0s.forEach(x => cue('uitick', x + 0.05, 0.04)); cue('sparkle', tMd, 0.05);
  cue('riser', tIn - 0.7, 0.04, 0.75); cue('swoosh', tIn, 0.05); cue('bassdrop', tMo - 0.04, 0.08, 0.6); cue('uipop', tMo - 0.03, 0.08); cue('sparkle', tMo, 0.05);
}

// ===================== G2 · no paywalls, no subscriptions, no strings attached =====================
{
  const sc = scene(tH, tI), el = sc.el;
  const tN = [T0('s5', 'no', 0), T0('s5', 'no', 1), T0('s5', 'no', 2)], tX = [T0('s5', 'paywalls'), T0('s5', 'subscriptions'), T0('s5', 'strings attached')];
  F.cam(sc, [[tH, 1.04, 0, 30], [tX[0], 1.0, 0, 0], [tX[2], 1.02, 0, -20], [tI, 1.05, 0, -30]]);
  const grid = Array.from({ length: 30 }, (_, i) => { const x = 60 + (i % 6) * 160, y = 520 + Math.floor(i / 6) * 190; return div(el, 'abs', x, y, 140, 150, `<div class="emo" style="font-size:40px;text-align:center;margin-top:24px">${['🔒', '💳', '🔁'][i % 3]}</div><div class="mono" style="text-align:center;font-size:18px;color:#6f727a;margin-top:10px">${['$30', '$9.99', '$49'][i % 3]}</div>`, { borderRadius: '18px', background: 'rgba(255,255,255,0.035)', border: '1px solid rgba(255,255,255,0.06)', opacity: 0 }); });
  const C = [
    ['🔒', 'Unlock this skill', 'pay to see the file', '$30'],
    ['🔁', 'Pro · monthly', 'billed every month', '$9.99/mo'],
    ['🔗', 'Follow + share', 'to unlock · email required', 'strings'],
  ].map(([e, a, b2, rr], i) => {
    const y = 570 + i * 300;
    const c = div(el, 'gcard', 110, y, 860, 250);
    c.innerHTML = `<div class="emo" style="position:absolute;left:34px;top:52px;width:120px;height:120px;border-radius:28px;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.1);display:flex;align-items:center;justify-content:center;font-size:64px">${e}</div><div style="position:absolute;left:186px;top:62px;font-size:40px;font-weight:800;letter-spacing:-0.02em;white-space:nowrap">${a}</div><div style="position:absolute;left:186px;top:118px;font-size:24px;color:#8d9096;font-weight:600">${b2}</div>`;
    P.chip(c, 186, 166, rr, 'dk', { height: '48px', fontSize: '24px' });
    const ban = P.ban(el, 830, y + 125, 112, tX[i] - 0.02);
    const cr = P.crack(c, 0, 0, 860, 250, tX[i] + 0.08, { seed: 11 + i, n: 3 });
    return { c, ban, cr, y };
  });
  const SX = [180, 430, 680];
  const strings = div(el, 'abs', 110, C[2].y - 160, 860, 200, `<svg width="860" height="200">${SX.map(x => `<path class="sl" d="M${x} 0 L${x} 160" fill="none" stroke="rgba(255,255,255,0.6)" stroke-width="3" stroke-linecap="round"/>`).join('')}</svg>`, { opacity: 0 });
  const sls = [...strings.querySelectorAll('.sl')];
  sc.on(t => {
    grid.forEach((g, i) => { const q = clamp((t - tH - (i % 6) * 0.02 - Math.floor(i / 6) * 0.03) / 0.3); g.style.opacity = (q * (t > tX[2] + 0.2 ? 0.25 : 0.55)).toFixed(2); g.style.transform = `scale(${(0.8 + 0.2 * eo(q)).toFixed(3)})`; });
    C.forEach((o, i) => {
      E(o.c, t, tN[i] - 0.12, { from: i % 2 ? 'left' : 'right', dist: 500, seed: 61 + i, float: 0.6 });
      o.ban.update(t); o.cr.update(t);
      const k = clamp((t - tX[i]) / 0.25);
      if (k > 0) { o.c.style.filter = `grayscale(${(0.85 * k).toFixed(2)}) brightness(${(1 - 0.4 * k).toFixed(2)})`; o.c.style.borderColor = `rgba(229,40,58,${(0.7 * k).toFixed(2)})`; }
      if (i === 2 && t > tX[2] + 0.05) o.c.style.transform += ` translateY(${(40 * eo((t - tX[2] - 0.05) / 0.35)).toFixed(1)}px) rotate(${(2 * eo((t - tX[2] - 0.05) / 0.35)).toFixed(2)}deg)`;
    });
    // puppet strings hold the third card, then snap on "attached"
    const s0 = tN[2] - 0.05, snap = tX[2] + 0.05;
    strings.style.opacity = (t < s0 ? 0 : t < snap ? clamp((t - s0) / 0.2) : 1 - clamp((t - snap) / 0.35)).toFixed(2);
    sls.forEach((p2, j) => { const x = SX[j], z = clamp((t - snap) / 0.3), sw = Math.sin(t * 3 + j) * 6; p2.setAttribute('d', t < snap ? `M${x} 0 Q ${x + sw} 80 ${x} 160` : `M${x} 0 Q ${x + (j - 1) * 50 * z} ${80 - 40 * z} ${x + (j - 1) * 70 * z + 20 * z} ${160 - 110 * eo(z)}`); });
  });
  F.ticks(tH, tH + 0.35, 8, 0.025);
  tN.forEach(x => cue('swoosh', x - 0.12, 0.05)); cue('cards', tX[2] + 0.05, 0.05); cue('whoosh', tX[2] + 0.05, 0.04);
}

// ===================== H · and now you're getting a million views, and you never pay for a content skill again =====================
{
  const sc = scene(tI, tJ, { in: 'up' }), el = sc.el;
  const tHit = T1('s6', 'a million') - 0.04, tNv = T0('s6', 'never'), tPay = T0('s6', 'pay'), tCs = T0('s6', 'content'), tAg = T0('s6', 'again');
  F.cam(sc, [[tI, 1, 0, 0], [tHit - 0.05, 1.03, 0, 20], [tHit + 0.08, 1.08, 0, 40], [tHit + 0.45, 1.04, 0, 0], [tNv + 0.2, 1.05, 0, -90], [tJ, 1.07, 0, -110]]);
  const card = P.mac(el, 80, 560, 920, 640, { light: true, title: 'Insights · this month' });
  P.chip(card, 770, 17, '● Live', 'on', { height: '40px' });
  const num = P.bigNum(el, 540, 656, { size: 150, light: true });
  const burst = P.burst(el, 540, 745, tHit, { n: 20, r0: 300, r1: 470, ry: 0.4, len: 40 });
  const shock = P.shock(el, 540, 745, tHit, { r: 450, ry: 0.32 });
  const con = P.confetti(el, 540, 760, tHit + 0.02, { n: 44, spread: 600, up: 1000 });
  const ch = P.chart(card, 60, 330, 800, 240, { light: true, pts: [[0, 0.04], [0.15, 0.07], [0.3, 0.12], [0.45, 0.2], [0.6, 0.33], [0.75, 0.52], [0.9, 0.78], [1, 0.96]] });
  div(card, 'abs', 60, 330, 800, 2, '', { borderTop: '3px dashed rgba(229,40,58,0.5)' });
  const goal = P.chip(card, 60, 290, 'goal · 1M', 'rd', { height: '36px', fontSize: '18px' });
  const notes = [['first format', 0.3], ['library', 0.6], ['every night', 0.88]].map(([s, u]) => { const [x, y] = ch.at(u); return { u, e: P.chip(card, 60 + x - 60, 330 + y - 58, s, '', { height: '36px', fontSize: '18px', background: '#fff' }) }; });
  const spent = div(el, 'lcard', 230, 1236, 620, 170);
  spent.innerHTML = `<div style="position:absolute;left:30px;top:26px;font-size:20px;font-weight:750;letter-spacing:0.1em;color:#8a8d94">SPENT ON CONTENT SKILLS</div><div class="v" style="position:absolute;left:30px;top:60px;font-size:80px;font-weight:900;letter-spacing:-0.04em;color:${M.mag};font-variant-numeric:tabular-nums"></div>`;
  const sv = spent.querySelector('.v');
  const ok = div(spent, 'abs', 500, 50, 84, 84, F.CHECK(48), { borderRadius: '50%', background: 'linear-gradient(180deg,#f070e4,#c42fb8)', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 10px 26px rgba(196,47,184,0.5)' });
  const ever = P.sticker(el, 600, 1180, '<span>ever again</span>');
  sc.on(t => {
    const u = tw(t, tI, tHit);
    num.set(F.num(lerp(690438, 1000000, u))); num.update(t, tHit); burst.update(t); shock.update(t); con.update(t);
    ch.update(lerp(0.25, 1, u));
    notes.forEach(n => E(n.e, t, lerp(tI, tHit, clamp((n.u - 0.25) / 0.75)) - 0.05, { from: 'pop', float: 0.4 }));
    goal.style.background = t > tHit ? M.red : ''; goal.style.color = t > tHit ? '#fff' : '';
    E(spent, t, tNv - 0.1, { from: 'up', dist: 200, seed: 70 });
    const sp = 30 * (1 - tw(t, tPay, tCs + 0.1));
    html(sv, '$' + sp.toFixed(2));
    sv.style.color = sp > 0.01 ? M.red : M.mag;
    E(ok, t, tCs + 0.12, { from: 'pop', s0: 0.2, float: 0 });
    E(ever, t, tAg - 0.04, { from: 'slam', s0: 1.7, rot: -5, seed: 71 });
  });
  cue('counter', tI, 0.05, tHit - tI); cue('riser', tHit - 0.8, 0.045, 0.85);
  cue('boom', tHit - 0.01, 0.1, 1.2); cue('bassdrop', tHit - 0.01, 0.07, 0.6); cue('sparkle', tHit, 0.08); cue('cards', tHit + 0.05, 0.03);
  [0.3, 0.6, 0.88].forEach(x => cue('uipop', lerp(tI, tHit, clamp((x - 0.25) / 0.75)) - 0.05, 0.05));
  cue('swoosh', tNv - 0.1, 0.06); cue('counter', tPay, 0.045, tCs + 0.1 - tPay); cue('uipop', tCs + 0.12, 0.08); cue('sparkle', tCs + 0.15, 0.05);
  cue('cards', tAg - 0.04, 0.05); cue('uipop', tAg - 0.03, 0.08);
}

// ===================== I1 · this format is called stamp.md =====================
{
  const sc = scene(tJ, tK, { out: 'zoom' }), el = sc.el;
  const tSt = T0('cta', 'stamp');
  F.cam(sc, [[tJ, 1, 0, 0], [tSt, 1.04, 0, -20], [tSt + 0.08, 1.09, 0, -40], [tK, 1.06, 0, -40]]);
  const f = P.file(el, 170, 600, 740, 640, { light: false, name: 'stamp.md', size: 'content format', lines: ['# Stamp', 'One "So you…, and…, but…" chain.', 'Every "but" gets a red stamp.', 'One magenta app window per beat.', 'Captions word by word, key words boxed.', 'Dark grid, light grid, hard flips.', 'End: comment the keyword.'] });
  const st = P.but(el, 540, 1080, tSt + 0.05, { text: 'stamp.md', size: 104, rot: -8, light: false });
  const lib = P.chip(el, 330, 1300, '<span>✓ free in the library</span>', 'on', { height: '56px', fontSize: '27px' });
  sc.on(t => {
    E(f.el, t, tJ + 0.02, { from: 'zoom', s0: 0.5, seed: 72 });
    f.lines.forEach((l, i) => { const k = clamp((t - tJ - 0.15 - i * 0.12) / 0.12); l.style.opacity = k.toFixed(2); l.style.transform = `translateX(${((1 - eo(k)) * -20).toFixed(1)}px)`; });
    st.update(t);
    E(lib, t, tSt + 0.45, { from: 'pop', seed: 73 });
  });
  F.typeCue(tJ + 0.15, tJ + 0.15 + 7 * 0.12, 0.045); cue('uipop', tSt + 0.45, 0.07);
}

// ===================== I2 · so comment STAMP and I'll send it to you. Completely free. =====================
{
  const sc = scene(tK, END + 1, { in: 'zoom' }), el = sc.el;
  const tCo = T0('cta', 'comment'), tw0 = T0('cta', 'stamp', 1) - 0.18, tw1 = T1('cta', 'stamp', 1), tAnd = T0('cta', 'and'), tSend = T0('cta', 'send'), tYou = T0('cta', 'you'), tFree = T0('cta', 'free');
  const tGrow = tCo - 0.05, tPress = tw1 + 0.12, tMsg = tYou + 0.1;
  F.cam(sc, [[tK, 1.04, 0, 30], [tGrow, 1.0, 0, 0], [tPress + 0.06, 1.05, 0, 10], [tPress + 0.4, 1.02, 0, 0], [END, 1.05, 0, -20]]);
  const X = 110, Y = 600, W = 860, H = 330;
  const glow = div(el, 'abs', X - 80, Y - 80, W + 160, H + 160, '', { borderRadius: '90px', background: 'radial-gradient(closest-side, rgba(214,66,200,0.5), transparent)', filter: 'blur(30px)' });
  const bx = div(el, 'abs', X, Y, W, H, '', { borderRadius: '44px', background: 'linear-gradient(180deg,#1d1420,#120d14)', border: `3px solid ${M.mag2}`, boxShadow: '0 0 60px rgba(226,79,214,0.55), inset 0 0 40px rgba(226,79,214,0.18), 0 40px 90px rgba(0,0,0,0.6)', transformOrigin: '50% 50%' });
  const ph = div(bx, 'abs', 104, 0, 500, H, 'Add a comment…', { fontSize: '32px', fontWeight: 600, color: '#6f6a72', lineHeight: H + 'px', whiteSpace: 'nowrap' });
  const inner = div(bx, 'abs', 26, 26, W - 58, 206, '', { borderRadius: '22px', border: `3px solid ${M.mag2}`, background: 'rgba(160,30,150,0.25)', opacity: 0 });
  const hd = [[16, 16], [W - 46, 220]].map(([l, t2]) => div(bx, 'abs', l, t2, 18, 18, '', { borderRadius: '50%', background: M.mag2, boxShadow: '0 0 12px rgba(226,79,214,0.9)', opacity: 0 }));
  const wd = div(bx, 'abs', 0, 22, W, 214, '', { textAlign: 'center', fontSize: '190px', fontWeight: 900, letterSpacing: '-0.035em', lineHeight: '214px', color: '#fff', textShadow: '0 0 30px rgba(255,140,240,0.55)' });
  const av = div(bx, 'abs', 26, H - 82, 58, 58, F.PERSON('rgba(255,255,255,0.9)'), { borderRadius: '50%', background: 'radial-gradient(circle at 35% 30%,#f070e4,#7a1470)', overflow: 'hidden', border: '2px solid rgba(255,255,255,0.4)' });
  const send = div(bx, 'abs', W - 90, H - 86, 64, 64, F.SEND(34), { borderRadius: '50%', background: 'linear-gradient(180deg,#f070e4,#c42fb8)', display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 0 24px rgba(226,79,214,0.8)' });
  const burst = P.burst(el, 540, 732, tw1 + 0.04, { n: 18, r0: 440, r1: 560, ry: 0.42, len: 30 });
  const res = div(el, 'abs', 230, Y + H + 56, 620, 112, `<span style="position:absolute;left:22px;top:22px;width:66px;height:66px;border-radius:16px;background:rgba(196,47,184,0.25);border:2px solid rgba(226,79,214,0.6);display:flex;align-items:center;justify-content:center"><span class="ok" style="display:flex">${F.CHECK(40, '#ffb0f6')}</span></span><div class="mono" style="position:absolute;left:110px;top:20px;font-size:36px;font-weight:800;color:#fff">stamp.md</div><div style="position:absolute;left:110px;top:66px;font-size:22px;font-weight:600;color:#a9a3ab">the content format · its SKILL.md</div>`,
    { borderRadius: '24px', background: 'linear-gradient(180deg,#1b171d,#141116)', border: '1px solid rgba(255,255,255,0.1)', boxShadow: '0 24px 60px rgba(0,0,0,0.55)' });
  const ok = res.querySelector('.ok');
  const msg = div(el, 'abs', 150, Y + H + 230, 780, 112, `<div class="t1" style="position:absolute;left:34px;top:22px;font-size:30px;font-weight:760;color:#fff">Messages</div><div class="t2" style="position:absolute;left:34px;top:62px;font-size:22px;font-weight:600;color:#8d9096">nothing yet</div>`,
    { borderRadius: '24px', background: 'linear-gradient(180deg,#19161b,#121014)', border: '1px solid rgba(255,255,255,0.09)' });
  const mi = div(msg, 'abs', 780 - 98, 22, 68, 68, F.COMMENT(36), { borderRadius: '18px', background: 'rgba(196,47,184,0.25)', border: '2px solid rgba(226,79,214,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center' });
  const red = div(mi, 'abs', 52, -10, 24, 24, '', { borderRadius: '50%', background: M.red, border: '3px solid #121014', opacity: 0 });
  const t1e = msg.querySelector('.t1'), t2e = msg.querySelector('.t2');
  const path = P.wire(el, `M300 ${Y + H + 112} C 360 ${Y + H + 190}, 700 ${Y + H + 150}, 865 ${Y + H + 252}`, { dash: '10 12', width: 3, t0: tSend - 0.05, dur: 0.2, color: M.mid, glow: false });
  const plane = div(el, 'abs', 0, 0, 56, 56, '<svg width="56" height="56" viewBox="0 0 24 24"><path d="M21 3L3 10.5l7 2.5 2.5 7z" fill="#f070e4" stroke="#fff" stroke-width="1.2" stroke-linejoin="round"/></svg>', { zIndex: 50, filter: 'drop-shadow(0 0 10px #e24fd6)', opacity: 0 });
  const fr = P.sticker(el, 640, 1092, '<span>$0 · free</span>');
  sc.on(t => {
    // a small comment field grows into the big glowing box when "comment" is said
    const g = clamp((t - tGrow) / 0.42), a = clamp((t - tK) / 0.3);
    const cw = t < tGrow ? 640 : lerp(640, W, back(g, 1.2)), chh = t < tGrow ? 120 : lerp(120, H, back(g, 1.2));
    Object.assign(bx.style, { left: px(540 - cw / 2), top: px(Y + H / 2 - chh / 2), width: px(cw), height: px(chh), borderRadius: px(Math.min(44, chh / 2)) });
    set(bx, clamp(a * 2.5), `scale(${(0.85 + 0.15 * eo(a)).toFixed(4)})`, (1 - eo(a)) * 10);
    av.style.top = px(chh - (t < tGrow ? 89 : 82)); send.style.left = px(cw - 90); send.style.top = px(chh - (t < tGrow ? 92 : 86));
    ph.style.lineHeight = px(chh); ph.style.left = '104px';
    set(glow, clamp(a * 2) * (t < tGrow ? 0.4 : 0.75 + 0.25 * Math.sin(t * 3)), null);
    ph.style.opacity = t < tw0 ? 1 : 0;
    const n = Math.round(clamp((t - tw0) / Math.max(0.05, tw1 - tw0)) * 5), s = 'STAMP'.slice(0, n);
    const caret = t > tw0 - 0.3 && t < tPress && Math.floor(t * 3) % 2 === 0;
    const key = s + (caret ? '|' : ''); if (wd.__s !== key) { wd.innerHTML = s + (caret ? `<span style="color:${M.mag2};font-weight:300">|</span>` : ''); wd.__s = key; }
    const b = back((t - tw1 - 0.02) / 0.3);
    inner.style.opacity = clamp((t - tw1 - 0.02) / 0.07).toFixed(3); inner.style.transform = `scale(${(0.92 + 0.08 * b).toFixed(4)})`;
    hd.forEach(d => { d.style.opacity = clamp((t - tw1 - 0.1) / 0.1).toFixed(3); });
    burst.update(t);
    const pr = clamp((t - tPress) / 0.08) - clamp((t - tPress - 0.08) / 0.12); send.style.transform = `scale(${(1 - 0.16 * pr).toFixed(3)})`;
    E(res, t, tAnd - 0.05, { from: 'up', dist: 70, seed: 80 });
    ok.style.opacity = clamp((t - tMsg + 0.3) / 0.2).toFixed(3);
    E(msg, t, tAnd + 0.1, { from: 'up', dist: 70, seed: 81 });
    path.update(t); path.el.style.opacity = (t > tMsg + 0.2 ? 1 - clamp((t - tMsg - 0.2) / 0.3) : 1).toFixed(2);
    const q = clamp((t - tSend) / (tMsg - tSend));
    if (t < tSend || t > tMsg + 0.05) plane.style.opacity = 0; else { const pt = path.path.getPointAtLength(q * path.path.getTotalLength()); plane.style.opacity = 1; plane.style.transform = `translate(${(pt.x - 28).toFixed(1)}px, ${(pt.y - 28).toFixed(1)}px) rotate(${(20 + 30 * q).toFixed(0)}deg) scale(${(1 - 0.3 * q).toFixed(3)})`; }
    const got = t > tMsg;
    html(t1e, got ? 'stamp.md' : 'Messages'); html(t2e, got ? `<span style="color:${M.mid}">just now · from Casper</span>` : 'nothing yet');
    red.style.opacity = got ? 1 : 0; red.style.transform = `scale(${(got ? back((t - tMsg) / 0.3) : 0).toFixed(3)})`;
    mi.style.background = got ? 'linear-gradient(180deg,#f070e4,#c42fb8)' : 'rgba(196,47,184,0.25)';
    msg.style.boxShadow = got ? `0 0 ${(20 + 10 * Math.sin(t * 4)).toFixed(0)}px rgba(226,79,214,0.45)` : 'none';
    E(fr, t, tFree - 0.04, { from: 'slam', s0: 1.7, rot: -6, seed: 82 });
  });
  cue('swoosh', tGrow, 0.05); for (let i = 0; i < 5; i++) cue(['tick1', 'tick2', 'tick3'][i % 3], lerp(tw0, tw1, (i + 0.5) / 5), 0.07);
  cue('uipop', tw1 + 0.02, 0.09); cue('bassdrop', tw1 + 0.02, 0.08, 0.6); cue('sparkle', tw1 + 0.05, 0.06);
  cue('mouse', tPress, 0.1); cue('send', tPress + 0.03, 0.06); cue('uipop', tAnd - 0.05, 0.06);
  cue('whoosh', tSend, 0.05); cue('notif', tMsg, 0.08); cue('cards', tFree - 0.04, 0.05); cue('uipop', tFree - 0.03, 0.08); cue('sparkle', tFree, 0.05);
}
```

