Files
wehub-resource-sync 85453da49f
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:58:35 +08:00
..

THEME mode — composed visual constitutions

Theme mode is the third compiler (scripts/make-theme.cjs), beside Standard and Cinematic. It exists because "mode" was a bundle of orthogonal axes pretending to be one switch. A theme DNA composes its identity from registries implemented ONCE in the compiler — paradigms are the unit of code; DNAs are the unit of identity. A new look is a JSON file; only a genuinely new paradigm/setpiece (rare) touches the compiler.

theme DNA = body PARADIGM   how the transcript surface lives
          × body LAYER      fg-alpha (rail.html channel) | bg-embed
          × hero SETPIECE   the climax choreography
          × front FX        flash / rings / sparks / scanband / crowdflash / paflash (fg, over subject)
          × PLATE budget    charge-dim (in-page) + punch/shake/grain (_postfx.sh)
          × LINKAGES        declarative theme interactions

Standard and Cinematic were, in retrospect, two fixed points of this space. Standard is now RETIRED (2026-06-12): its rail×embed-climax point is served by the anchor theme (rail paradigm × settle setpiece — the quiet default). Cinematic remains a separate compiler; do not re-implement it as a theme yet.

Unification roadmap (strangler fig — interface first, engines later)

The user-facing model is already unified (SKILL.md Step 0): one catalog of LOOKS; classic looks pick a DELIVERY (rail | column), themed looks bind their own. "Standard/Cinematic" are delivery/compiler names, not modes. Remaining phases, each gated on need — never rewrite for tidiness alone:

  • Phase 2 — one authoring schema. lines/minors/hero are already ~90% shared between standard.json and theme.json; a router that translates a single caption.json into the engine-specific file removes the last user-visible seam. cinematic.json's blocks/planes are the odd one — map the common fields, pass engine-specific ones through.
  • Phase 3 — engine convergence. Port a classic delivery into make-theme ONLY when something forces it (e.g. a classic DNA wants a plate budget or a setpiece). Acceptance bar: blind A/B on the cap_multi regression scenes vs the old compiler — swap engines only when indistinguishable or better. Until then the old compilers are the reference implementation of 8 rounds of validated typography (lockup/orbit, multi-climax, ratio-lock, per-plane legibility, occlusion adjudication) — that machinery is the moat, not debt.

Body paradigms (registry)

paradigm surface layer entrance verbs exit verbs
rail lower-third lines, replace fg stamp (1f hot appear→crush→recoil→cool), flick (tube double-flick), bootflick (corrupted-cyan boot 12f → settles to UI color; minors keep a cyan/red registration shadow). stamp minors cool to palette.minorCool (default amber) drop (mass falls), powercut (bright→ghost→off)
panel docked glass console, accumulate + typed fg type (width steps per word, $ prompt, caret blinks) hold
poem open-space stanzas, accumulate; letters CONDENSE from seeded dust fg condense drift (stanza hand-off) + optional dispersal linkage
takeover full-frame hard-cut cards, plate dimmed throughout bg cut (+ tension creep before silences, auto-detected) hard cut / blur dissolve
lastpage manuscript rail (typed serif) + a seeded field of BLURRED apex-word instances haunting the room from t=0 bg typed rail; field breathes imperceptibly rail fades; field resolves at apex (see rackfocus)
flaprail split-flap word tiles on a chyron bar (tag + ROW flip-counter; optional odometer chip docks above the bar while the first emphasis line reads) fg splitflap (3 flips: wrong word → wrong word → real word clacks on hot, squash → elastic settle → cools to bone; emphasis tiles keep amber text + underline) flip-to-blank cascade left→right (completes before the next line)
ledboard amber LED departure strip docked lower-third (body.board geometry); lines page by vertical scroll-snap (steps(6)) inside the board window; furniture: green platform tag, ticking seconds clock (stacked digit reel), ⚠ DELAYED blinks then pages to ● ON TIME ~0.7s after the apex, idle LED shimmer in dead air ≥1.2s fg ledwipe-mini (per-word L→R LED column wipe, clipPath steps(len), + 1-frame arrival flicker; emphasis words get the LED-refresh double-blink; last word gets one late refresh) scroll-snap page-up (next line pages in at the same instant)
vhsrail camcorder-playback lower third: every word carries a PERMANENT 1px red+cyan misregistration fringe (split clip-path halves); furniture: ▶ PLAY / ◀◀ REW top-left (REW gag in dead air ≥0.7s + the final rewind), ● REC top-right through the apex window, green timestamp bottom-right with 1 Hz blinking colon, idle tape-drift line in long silences, plus apex-coupled artifacts (1-frame white tear band + head-switching noise bar at REC FREEZE, scrub lines at the apex band on the hero rewind) fg trackglitch (instant on + 2-frame y-shake + 1-frame band-slice shift of the word's bottom half; emphasis = PAUSE jitter ±1px y oscillation + white noise-band flicker) REWIND streak left + flat-white echo copy + 2 scrub-line flickers
hudrail arcade-cabinet HUD: whole frame plays through CRT overlays (scanlines body.scan + corner vignette); docked HUD bar (body.bar geometry, 2px accent border, glow) with body.tags 1UP / CREDIT furniture — the 1UP tag blinks on the classic 0.45s attract cadence all clip; HUD yields (body.yield) while the boss lands fg coinpop (instant-on + steps(2) scale pop from the baseline + 1-frame white coin-blip tick over the word; emphasis = 3-frame palette swap body→accent→magenta→body) checkerboard dissolve (words vanish in 2 alternating 2-frame passes, line off ≤ DUR0.02)
carbonstrip cold-war case-file transcript strip docked on the clearer side (manila gradient, punch holes, faint red body.watermark, typed body.header that types on by width steps); lines live on up to body.rows fixed slots; PAGES (broken at full rows, dead-air gaps > 0.7s, and after the hero hand-off line — it is filed away while the stamp owns the frame); the strip top-shadow breathes during the stamp's dead-still hold and the strip yields (body.yield) while it lands; the final page is pulled with the whole strip at clip end fg hammer (per-char sets 30ms apart, scale 1.35→1 in one frame = the stamp motif at low amplitude; seeded ±1.2px baselines + seeded ribbon-ink opacity; every strike kicks the strip 1px — y-channel locked while the strip itself tweens; emphasis words get a red ink ellipse drawn around them, 0.25s) X-row over-strike (3 chunks per line, 0.05s apart, lines 0.16s apart) → carriage yank-down feed → return; roomy cadence in dead air, compressed when the next page is close — always completes before the next page's first strike
laserrail concert lower third: lines centered on a bottom rail (body.bottomPx), uppercase laser-green words with a neon text-glow; emphasis words render at body.minorScale× with a hotter glow; word centers are BAKED at compile time from the measured char-width table (assets/fonts/char-widths.json, ≤3px vs browser ground truth) so the beam aim can't be skewed by font-load timing; per-word furniture = two thin beams (accent from top-left, magenta from top-right) that sweep to CONVERGE on the word 2 frames early + a convergence flare; beams dim ×yield.beamDim inside the apex window and the visible line dims to yield.dim fg igniteconverge (beams converge at st0.083 → flare pop → word snaps on at opacity 1 with brightness 1.85, crush 0.09s power3.in → 2-frame contact squash → elastic settle → brightness cools; emphasis = single 1-frame strobe off/on + lingering beams) sweeper beam crosses L→R in 0.13s and each word streaks away (x+44, blur 5) as it passes; line off ≤ DUR0.02

| stormrail | rain-washed lower third: ambient rain = body.rain.count seeded thin streaks falling on repeating linear cycle tweens (pure f(t), one slanted wind vector, mid-fall at t=0); a second seeded set DOUBLES the rain for ~1s after the strike; lines sit on a bottom gradient scrim (stable reading surface); the visible line dims to yield.dim while the bolt lands and its text-shadow flips toward the strike (hero.params.side) for the flash frames | fg | lightningpop (the apex motif at low amplitude: instant on at brightness 2.2, y+4, scale 1.06 → 0.12s micro settle; emphasis = sheet-lightning double flash, 2-frame brightness 1.9/1.0/1.8 chain — the bg sky double-kisses at the same beats) | wash downward with the rain (y+22, blur 3, 0.18s power1.in); a line facing dead air washes early at lastWord+1.08s; line off ≤ DUR0.02 | | holorail | volumetric projection plate: a scanlined glass slab (body.plateW×plateH, centered at H bottomPx) with corner brackets + feed tag (body.tag / theme.plateTag override) boots like a projector turning ON (scaleY 0.03→1 expo + 1-frame flicker) and breathes a finite yoyo luminance cycle sized to the clip length; lines sit centered IN the plate (lineDy below plate center) with a constant chromatic-fringe text-shadow (1.5px accent / +1.5px magenta) and drift slowly in rotationY (it IS a projection); the whole railwrap — plate included — dims to yield.dim while the apex lands | fg | sweepboot (the apex motif at low amplitude: instant opacity + rising clip-wipe reveal 0.15s + 1-frame x interference glitch at st+0.05/0.09/0.13; emphasis = 2-frame projection DROPOUT at body.emphDelay, restores brighter in palette.em with a hotter fringe, then settles back — the delay auto-shrinks near the line's exit so dropout+restore always complete) | projector cut — brightness 2.2 pop → collapse to a horizontal slit (scaleY 0.045, 0.07s power3.in) → to a dot (scaleX 0.015, 0.06s) → off at exit+0.15; the plate itself gets the same cut at DUR0.14 | | planktonrail | deep-sea lower third on TWO coexisting row slots: even lines ride the upper row (H bottomPx rowDy), odd lines the lower (H bottomPx) — couplets share the water like drifting strata. A line clears when its row is next needed (line i+2 in 0.15) or ~0.3s after its couplet finishes speaking (staggered pair exits, +0.12s on odd rows); the pre-apex leftover clears at heroIn0.21 while the FEEDING line (last to start before the apex) yields to yield.dim instead — no restore unless ≥1.2s of hold remains (it exits dimmed, by design); a line entering during the hold arrives at yield.enter and restores at I+yield.post; every line floats on a gentle y keyframe drift sized to its window. Furniture: body.motes = {count, near, seed} plankton motes on seeded drift walks f(t); the first near seed a ring around the apex heart (ring radius scales with hero fontPx/120) and get ATTRACTED to the bloom at I0.19 (pulled 0.72× toward it, brightening to 0.42 at scale 1.7), wiggle through the hold, and sink at clip end | fg | glowon (the apex bloom at low amplitude: 2-frame opacity ramp + radial bloom from word center — scale 0.92→1, blur 5→0, brightness 0.45→1, 0.4s sine.out — while a cyan glow text-shadow grows in; emphasis = slow DOUBLE-BEAT propulsion pulse at st+0.52, 5-key brightness/scale 0.55s, guarded ≤ DUR0.09) | light sinks — y+15 while dimming, 0.3s power1.in; the last line floats to the end of the clip; line off ≤ DUR0.02 | | sheenrail | iridescent silk lower third on TWO coexisting row slots: even lines ride the upper row (H bottomPx rowDy), odd lines the lower (H bottomPx) — couplets read together like a woven pair. Words are aurora-gradient ink (background-clip:text over a rose→teal→violet 300% wash whose backgroundPosition drifts f(t) 0%→300% across the whole clip — the body's hold life) above a blurred dark understroke (top+3px, blur 3); a soft radial band scrim sits under the rows (stable reading surface). Line scheduling mirrors planktonrail: a row clears when next needed (line i+2 in 0.15) or shortly after its couplet finishes speaking (pairEnd + 0.23 + 0.17 on odd rows); the pre-apex leftover clears at heroIn0.32 while the FEEDING line stays and dims with the railwrap yield (yield.dim at Ipre, restore at I+post — band included); the last line holds to the end | fg | flowon (the ribbon's flow at low amplitude: 2-frame opacity + y8 settle 0.38s sine.out; emphasis = ONE bright sheen sweep — a white highlight marches through the word once at st+0.16 (backgroundPosition 80%→180%, 0.42s power2.inOut) + a brightness 1.55 pulse) | dissolve UPWARD — words to y12, blur 3, 0.32s power1.in staggered 0.035; each line releases body.streaks seeded light streaks (rose/teal/violet verticals) that float up from its row (y 14→−78, 0.62s, start clamped ≤ DUR0.66) | | scoperail | oscilloscope lower third: a dark phosphor scope band (body.bandH, top border + glass shadow) hosts HUD readouts (body.hudL left, sample-rate · TRIG + amber trig LED right), a graticule, and an ALWAYS-ALIVE waveform — an SVG polyline rebuilt from baked point-set states (seeded pseudo-RMS envelope at ~12 states/s + 3-frame triplets on word arrivals: speech grows the amplitude, silence flattens it, every word kicks a local gaussian burst at its slot center — burst x BAKED at compile time from the measured char-width table; the hero words kick WIDE bursts spread from HG.x toward HG.x + 0.6·halfW, where the trace surges to write). Body lines sit centered on the band above the trace with reserved-width slots (sizer + clip — no re-center drift as words arrive); the whole band (lines + wave + HUD) dims to yield.dim while the apex writes. At the FINAL transcript word the scope DIES: band x-shake, trig LED panics red then dies, the trace explodes to max-amp chaos for ~0.3s, flatlines, collapses to a single dot (scaleX 0.015) and body.siglost stamps with a 2-frame flicker (all clamped ≤ DUR0.02) | fg | tracewipe (the apex pen-write at low amplitude: clip wipes the word in L→R over 0.10s + a 2-frame just-traced flash — flash-white instantly, settle to phosphor green; the death word settles to palette.em amber; the trig LED blinks on every arrival; emphasis = a ticking frequency-readout chip above the word, 3 seeded ascending values stepping every 2 frames) | the line collapses INTO the waveform (y+30, 0.13s power2.in) — early into dead air (lastWordStart+0.67), else just before the next line needs the slot; the last line collapses as the trace flatlines (min(NZ+0.33, DUR0.2)) | | paperrail | stop-motion craft table: a kraft strip (torn top edge via seeded polygon clip-path, washi-taped corners, handwritten body.tag in the tag font) docked lower-third (body.strip geometry, centered by default); body words are cream paper chips (palette.chip on palette.chipEdge border, hard dy3 shadow) on centered replace-lines; the whole strip jitters ±0.7px every 4 frames (handmade stop-motion weave); the hero slot keeps a red paper star (palette.accent, rail↔climax hand-off) that pulses 1.18→1.06→1 as the apex chips land; the FINAL transcript word is an accent-red sign-off chip; emphasis words get a washi-tape slap (steps(2) scale 1.7→1 at word.start + body.tapeDelay, chip pressed 1 frame); strip + lines yield (body.yield) while the apex lands | fg | place (the papermat drop at low amplitude: opacity full in 0 frames + seeded ±2.5° rotation, y 16 + scale 1.06→1 steps(2) 0.12s; hold = rotation-only ~5fps wobble) | peel (rotation 8°, y 40, fade, steps(3), staggered 0.03s; last line clamped to finish on frame) | | popuprail | pop-up book page: a cream page strip (body.strip geometry, center foldline, top border + paper glow inset) unfolds up from the bottom edge (scaleY 0.02→1 back.out at 0.14 — the stage is built) and hosts TWO reading rows (body.rowA/rowB px above H) that alternate per line; the whole page breathes a gentle y drift sized to the clip; the hero slot keeps a blue paper star chip (palette.accent clip-path star, rail↔climax hand-off — spliced into the line whose window holds heroIn); emphasis words land at body.emPx weight 800 with a contact squash (1.06/0.93 set → elastic settle) the strip FEELS (1px dip), then flick-wobble (5-key rotationX, squeezed to fit before the fold) with a blue paper strut leaning in behind (scaleY back.out(2)); the FINAL transcript word is a blue sign-off with a strut; page yields (body.yield) while the centerfold lands; on the final word the whole page TILTS 2° +x6 (paper physics, body.tilt:false opts out) and the strip folds shut on the last frame (scaleY power3.in ≤ DUR0.017) | fg | popup (the centerfold motif at low amplitude: opacity 1 in 0 frames + rotationX 85→0 baseline hinge, 0.25s back.out(1.4), back-shadow grows as it rises) | fold (the page closing: rotationX 88 power2.in 0.12s, shadow off, L→R wave staggered 0.045s, slot hidden at +0.13; fold at next line in / lastWord+body.maxHold in silence, wave clamped ≤ DUR0.017) | | chalkrail | night-lecture chalk band: a deep-green board strip (body.strip geometry, worn top hairline, eraser smudges, handwritten body.tag header in the tag font, two chalk sticks bottom-right) docked at the bottom edge hosts body.rows reading rows that ACCUMULATE — lines fill the rows top→bottom in phases of rows.length; when the next phase needs the rows an ERASER SWIPE clears the board (blurred light band sweeps across 0.42s at prevEnd + body.eraseDelay clamped clear of the next phase, the old words SMEAR out scaleX 1.3 + x+24 + blur(8) staggered 0.06s, faint ghost smudges fade in and STAY — chalk never fully erases); the FINAL transcript word lands in yellow chalk (palette.em sign-off); the band yields (body.yield) while the apex writes | fg | scribble (the chalkwrite verb at low amplitude: 2-frame opacity pop + scribble clip-reveal steps(4) 0.12s + y4 drop-settle + 6 seeded dust specks puffing at the word base, seeded ±2° tilt; emphasis = double underline dashes drawn beneath at st+0.22/+0.38) | eraser-swipe smear between phases (scaleX 1.3, x+24, blur 8, power2.in 0.18s); the final phase holds to clip end | | markerrail | street handstyle rail: centered marker lines on a bottom rail (body.bottomPx), palette.body cream paint with a heavy drop text-shadow; emphasis words take ALTERNATING paint colors (palette.em/em2, yellow/magenta) + a spray-underline SWOOSH (dash-revealed squiggle viewBox-stretched to the word) with 8 seeded overspray dots that pop and fade; the FINAL transcript word lands BIG (1.5×) in palette.em with mini apex-impact physics (crush 0.09s power3.in → 2-frame contact squash → elastic settle — it rides the plate punch); the line visible at the tag's onset yields (body.yield, restore only with runway before its buff — else it exits dimmed) | fg | flick (the spray-write verb at low amplitude: opacity full in 1 frame at scale 1.18, seeded ±6° rotation settling to ±2°, 1-frame squeak skewX 7, back.out(2.2) settle 0.18s) | ROLLER BUFF (gray paint roller scaleX-sweeps the line 0.15s power2.in, words vanish under it, the wet smear lingers at 0.15 + blur(2) before fading); the last line is never buffed — the sign-off stays painted | | brushrail | washi paper band: a deckle-edged band (body.strip geometry, two faintly rotated paperHipaperLo under-sheets at 0.42 + a translucent sheetHisheetLo main sheet with warm inset shadow) swipes in L→R behind a traveling brush-head shadow (clipPath wipe 0.2s power2.out + blurred palette.bh gradient head — the ink gesture at low amplitude), then the under-sheet breathes faintly (0.32 Hz baked f(t)); body lines are centered replace-lines on the band (fonts.body serif at body.fontPx/body.weight, seeded ±1° tilt); emphasis words get a DOUBLE curved ink underline (palette.ulInk, fat 7/0.3 wash + tight 2.6/0.95 core, dash-drawn at st+0.12/+0.15); band + the line under the gesture yield (body.yield) while the apex lands; the band exits with the last line on one final R→L wipe | fg | brush-wipe (the gesture at low amplitude: clipPath inset reveal L→R 0.16s power2.out with a blurred per-word brush-head shadow riding the reveal edge, then vanishing) | wet-cloth WIPE (the reveal in reverse: the line clipPath wipes R→L over 0.100.14s power2.in with an erase brush head riding it, at min(next line 0.136, lastWord + body.maxHold); the LAST line and the band wipe together at DUR0.142) | | inkrail | sumi ink on still water: up to THREE fixed reading rows at the bottom center (body.bottomPx = last-row center, body.rowGap apart; rows cycle in pairs, the closing line takes the third row — the final thought ACCUMULATES instead of replacing); fonts.body Mincho serif at body.fontPx/body.weight/body.letterSpacing, palette.body ink over a paper-white text-shadow halo (palette.paper/paper2 glow stack — readable on bright skies) with a duplicate blurred dark halo per word; an ink-settling RULE (86%-width gradient hairline, scaleX 0→1 over 0.9s) draws beneath each line at firstWord+0.12; emphasis words shed a faint ink wisp curling off the word top (st+0.55, ±dir by line parity, skipped near clip end); the rows visible under the bloom yield (body.yield — dim at heroIn+delay, restore at heroIn+post only with runway before the exit) | fg | bleed (the apex bloom at low amplitude: opacity snaps in 2 frames; blur 12→0 + scale 1.25→1 power2.out carry the bleed — late words bleed faster so they resolve before clip end; the dark halo overshoots scale 1.5→1.04 while its opacity decays 0.5→0.25) | RE-DISSOLVE (blur 9 + scale 1.13 + fade + y8 power1.in — ink dispersing back into the water) as the line-after-next approaches (= the next occupant of its row, outDur 0.50.05·i, never mid-read); the closing TWO lines persist to clip end | | ransomrail | kidnapper's collage rail: every transcript word is a cutout chip in one of 4 cycled recipes (cream/fonts.body Anton, black/fonts.dark Inter-800, newsprint/fonts.news Special Elite, accent/fonts.accent Permanent Marker — emphasis words snap to the accent chip, never two identical chips in a row) PASTED along the bottom rail line (body.bottomPx) with seeded crookedness (rot sign alternates with line+word parity, baseline dy leans the other way), seeded ±px sizes (body.fontPx2..+1, emphasis +3) and torn edges (seeded 12-vertex tear polygon, same paper stock as the apex); chips carry a hard dy5 offset drop-shadow; lines breathe (5-key scale keyframes) through their hold; the whole railwrap yields (body.yield, container opacity — never contests per-chip channels) while the apex letters slam | fg | paste (the apex slam motif at low amplitude: opacity full in 1 frame at scale 1.12 / rot×1.5 / y8 → power3.in crush 0.08s → 2-frame contact squash 1.05/0.95 → elastic settle; emphasis = 1-frame RE-PASTE lift at body.emphDelay — shadow grows to a flight blur, rotation flips sign for good, then crush + squash + elastic again, rip-runway-guarded) | RIPPED off one by one (rotation ±16/17° throw + y30 + fade, power2.in, staggered 0.045s) with a 1-frame paper-tear flash under each chip — timed so the rip tail overlaps the next line's pastes a beat (collage chaos, by design); the last line tears off clamped to finish on frame |

fg body uses the rail.html alpha-webm channel (true alpha — dark panels and scrims work; never the screen-blend index_fg path, which can only add light).

Hero setpieces (registry)

setpiece what happens notes
detonation charge-dim → sheared stencil slices crush in hot → SNAP into register → squash → elastic settle → paint cools to bone → bars/ticks/designation-tag deploy → shear-apart exit pairs with fx flash/rings/sparks + plate punch/shake
decode slot-machine glyph reels (steps() ease, seek-safe) lock left→right with RGB jitter → lock snap → CRT power-off exit pairs with redact-until-hero
drawon the word is WRITTEN stroke-by-stroke from a single-line font (Hershey) — per-stroke paths revealed sequentially at constant pen speed, nib rides getPointAtLength, hops at pen lifts; then hum + buzz dip any word, zero tuning: gen-stroke-path.py lays out glyph pen-paths at compile time
cpslam acid-yellow stencil word slams in BEHIND the subject with REAL diagonal notch cuts (SVG mask = true transparency over footage), chromatic split that settles to a PERMANENT cyan/red registration error, seeded glitch ticks, katakana tag, glitch-out exit bounded hold (~2.6s default, params.hold / hero.exitAt override) — a climax is an event, not wallpaper
coverword the spoken apex word SET IN the replica typeface of the CP2077 cover mark (assets/brand/CyberpunkReplica.ttf — lowercase glyphs carry the logo's real brush chops, blade terminals and spikes; logo case = First-upper rest-lower). The setpiece adds only what the font lacks: solid cyan duplicate offset (-7,8), baseline streak merged with the glyph feet + cyan debris, circuit trace off the tail, tear-in slices / living print / tear-out metric-exact layout from cyberpunk-widths.json (advance widths + ink bounds; fontPx = target INK height). No digits in the replica → compile ERROR with guidance. Replica font: personal-use license — samurai is already fan-kit non-commercial
assembly (inline, poem) apex word condenses BIG while star particles fly into it
colorflip (inline, takeover) accent-color crush card + dim kick + squash/settle + loom pairs with fx flash + plate punch/shake
flapboard split-flap departures board takeover: floodlight dim/scrim swing on, the housing clacks down (rotationX, squash, elastic) behind the subject, one giant flap tile per char flip-cycles 35 seeded wrong glyphs then LOCKS left→right across params.lockWindow (hot flash → cools to bone) under baked clack y-jitter; lock-complete = big clack + amber glow flicker + rule wipe + steps() status-ticker; hold = loom + one tile re-flutter; exit = tiles flip to blank cascade, housing flips away pairs with fx.crowdflash (pops at lock start/end) and plate.punchOffset = lockWindow so the plate punch lands on the lock-complete clack, not the hero onset
ledwipe transit LED board takeover: cool scrim + dim charge, an amber dot-matrix panel EXPANDS upward (scaleY steps(6)) behind the subject, header tags + unlit LED ghost boot-flicker alive, then the word ILLUMINATES column-by-column (clipPath L→R, steps ≈ 1.4×chars over params.wipeDur) hot → cools to amber; contact = cell squash + elastic settle + PA "ding" double brightness pulse; an 8-phase chasing LED-marquee border runs to the exit; hold = glow breathe + seeded-look LED dropouts; exit = word pages up out of the window, panel collapses back down pairs with fx.paflash (amber PA flash at wipe-complete contact). Panel geometry derives from fontPx (panelH = 44 + 1.42×fontPx + 18); params.panelW fixed, width-fit shrinks the type only. Plate: grain-only (no punch) is the demo register
vhsosd REC FREEZE camcorder slam: permanent lens vignette (params.vignette) + backlight-comp scrim (peak = plate.charge, cools to 0.69×) kill the bright sky; the word crushes in as a huge OSD readout — 3 horizontal clip-path tracking bands (params.bandClips) sheared (params.bandShear) + blurred (power4.in over params.crush) SNAP into register on the contact frame, squash → elastic settle; red/cyan misregistration copies cool from ±misregIn to ±misreg px and keep BREATHING through the hold under tape-weave y jitter (params.weave) + slow loom; auto-iris pump (white wash 0.20, dark dip = plate.dim); exit at I + params.hold = REWIND streak left + flat-white echo copy pairs with the vhsrail paradigm, which emits the fg REC-FREEZE artifacts (tear band, head-switching bar, apex scrub lines, ● REC) at the same I/X times. Plate: grain-only (no punch) is the demo register; hold-life keyframes auto-clamp inside [contact, exit]
bossintro arcade BOSS INTRO: the room dims (gradient plate.dim) behind a CRT edge glow (cyan base wash that pulses on the last emphasis word before the boss and spills magenta on the strobe + the final word); params.tag (WARNING) blinks 2× above the word rect; ~COLS×ROWS (params.rows, cells 0.7×/0.5× fontPx) seeded pixel blocks fall steps(3) from above into a tight matrix grid covering the word rect, each landing with a 1-frame white tick; at I + params.flashDelay the word flashes ON (Press Start 2P, instant at opacity 1, scale 1.15 → steps(3) crush → 2-frame squash → elastic settle) with ±4px chromatic ghost layers + a 3-frame palette strobe (white→cyan→magenta→white); hold = quantized 1.6% breathe + CRT glow cycle + baked block shimmer blinks; exit at I + params.hold = per-letter checkerboard dissolve, blocks drop away steps(3) pairs with the hudrail paradigm; emits fg furniture via setpiece fg parts: letterbox bars (params.letterbox px) slam in steps(3) with a 1-frame overshoot and retract after the exit, HI-SCORE odometer (accent label, yellow digits) rolls every 0.066s through the hold, 1-frame white tick out. fx.coinflash adds the 1-frame radial impact flash at the strike; plate punch anchors at punchOffset = flashDelay
rubberstamp the footage becomes an archive page (vignette charge = plate.charge, manila sepia wash params.sepia, dim kick = plate.dim that cools to 0.385×) and the verdict slams in BEHIND the subject as a bordered rubber stamp at params.rotation: a manila paper wash lights an instant before contact, the stamp crushes 2.2→1 power4.in over 0.115s, 2-frame contact squash, double-strike ghost print (opacity 0.35, offset +5/4 rot 1.3), two thin-ink patches (params.patches clip-path circles in paper color), wet-ink saturate/brightness settle, paper indent shadow bloom; stamps don't move — the hold is DEAD STILL, sold by the breathing indent + a slow paper loom; exit at C + params.hold = mechanical 3-frame yank-down cut (the page is filed away), the archive plate releases 0.25s before contact C = heroIn + params.crush; pairs with fx.inkflecks (seeded red spatter at C, ballistic with gravity) and plate.punchOffset = params.crush so the plate punch lands on the stamp contact; pairs with the carbonstrip paradigm (strip yield + shade breathe anchor at the same C); face chrome (border, radius, padding, ghost offset) scales with fontPx from the 120px demo cut
lasercage concert-blackout laser show: the plate dims to plate.dim at t=0 and deepens to plate.charge before the drop while haze cones breathe in the top corners; a 6-beam array (alternating accent/magenta, emitters along the top edge) snaps on at I0.26 and SWEEPS to converge on the word center (staggered 0.022s), a hot core pops at convergence; the word IGNITES behind the subject as an additive glow stack (wide accent blur 0.22×fontPx + magenta fringe offset + tight soft blur + near-white core): scale 1.16 crush power4.in → 2-frame contact squash → elastic settle + 2 controlled strobe pops (brightness 2.05, 1s apart, runway-guarded); a criss-cross beam CAGE (6 lines derived from the word rect) draws itself dash-by-dash around the word and flickers; through the hold the array FANS OUT slowly to floor targets (the iconic concert fan) while the word looms; exit at heroOut 0.38 = cage un-draws, beams sweep away L→R (+0.74W) carrying the letters (per-letter streak + blur, stagger auto-clamped ≤ DUR), the room lifts back to 0.12 word geometry is metric-exact via assets/fonts/char-widths.json (cage/spill/flash all derive from halfW + fontPx); pairs with fx.laserflash (accent contact flash at I+0.03, sized 2·halfW+80 × 4.8·fontPx) and the laserrail paradigm (same converge-ignite motif at low amplitude); plate punch anchors at punchOffset ≈ 0.07 (the ignite contact)

| boltstrike | THE STRIKE: a slate storm mood (params.mood) sits on the room from t=0 and the sky KISSES (1-frame 6% gradient brighten) on every body word — double-kiss on emphasis words, re-flashes at I+0.84/I+1.44 during the hold; the room charge-dims to plate.charge under a radial word scrim from I0.39; a hand-authored branched bolt (5-seg zigzag + 2 branches, haze/glow/core SVG stack in palette.accent/glow/core) flashes 2 frames BEFORE the word — leader 1 → 0.35 → return stroke 1 at contact — with a sky flash; the word slams in (Anton scale 3.1 crush power4.in 0.105s, blur 14→0, brightness 3.2→2.5) → 2-frame contact squash → elastic settle, lit HARD for 2 frames then cools to 1; SIGNATURE: the word's text-shadow flips toward the bolt side for the flash frames; ozone under-glow (accent, blur 26×fontPx/145) blooms at contact and breathes (6-key opacity cycle) through the hold + slow loom + 2-frame lit blips on the sheet re-flashes; the dim releases at the flash, settles to a plate.dim storm hold and lifts before the exit; exit at min(heroOut0.2, I+params.hold) = washes downward with the rain (y+38, blur 6) | bolt geometry is baked at compile time as offsets from the word center (halfW/360 × fontPx/145 units, params.side flips it); word rect is metric-exact via assets/fonts/char-widths.json; word scrim = 2·halfW+380 × 2.28·fontPx; pairs with fx.strikeflash (full-frame fg flash with the same leader/return flicker grammar) and the stormrail paradigm (rain doubles + shadow flip at the same I); plate punch anchors at punchOffset ≈ 0.02 (the return stroke) | | holoboot | full volumetric boot behind the subject: a desk emitter dot pops (back.out scale), the projection cone SNAPS wide (scaleX 0.07→1 expo, 0.11s, then dips 0.55→0.95), a seed beam fires up from the emitter (scaleY power3.in, fades after contact), and the word boots with a RISING clip sweep (opacity instant at I0.055, the reveal is the wipe) into a 7-layer projection stack (dark backing shadow / wide palette.glow blur / magenta + cyan chromatic fringes / palette.core word / scanline mask / climbing interference bands, band pitch 0.39×fontPx); contact at I+0.07 = brightness 2.3 flash + scaleX 1.07 / scaleY 0.91 squash held 2 frames + elastic settle + 1-frame x interference jumps; fringes blow ±8px then settle to the permanent ±2px registration offset; hold = bands climb 6 pitches, rotationY oscillates [0,6,7,5,0], glow/cone breathe, emitter dot pulses, slow 1.028 loom; at I + params.passAt (≥ I+1.25, runway-guarded) a hand "passes through" the projection (skew 6/4/0 + blur + fringe blowout + scaleY ripple — pure projection logic); exit at min(heroOut0.22, DUR0.3) = projector cut: brightness 2.4 → collapse to a horizontal line (scaleY 0.018) → to the base dot (scaleX 0.015), cone fades, dot pops 2.2× and dies. A second smaller emitter cone (params.coneW) rises from the desk under the holorail plate ALL video with baked flicker cycles + dot pulses, and gets its own projector cut at DUR0.2 | word rect is metric-exact via assets/fonts/char-widths.json (cone width 2·halfW+80, emitter at cy+2.4·fontPx, all rig geometry derives from halfW + fontPx; scene scrim is a radial centered on the hero's frame fractions); pairs with fx.holoflash (cyan fg lock flash at I+0.075, radial at the same hero fractions) and the holorail paradigm (same sweep-boot + interference-glitch motif at low amplitude; the body cone is aimed at ITS plate); plate: grain-only (no punch) is the demo register, scene reaction = plate.scrim/charge/dim | | biobloom | bioluminescent BLOOM behind the subject: the abyss grade (params.mood) + a breathing vignette (6-key opacity cycle sized to the clip) sit on the room from t=0 — the world is underwater before anything speaks; a creature pre-glow (1.083×/0.717× fontPx ellipse) gathers with a double-beat anticipation at I0.31 and crushes to a point at I0.115; a dark pocket scrim (plate.scrim, 2·halfW+280 × 2.5·fontPx) opens behind the word zone at I0.03; the word CONDENSES in (scale 2.35 crush power4.in over params.crush, blur 15→2, brightness 2.6→1.7) → 2-frame contact squash (1.09/0.93) → elastic settle while cyan light SPILLS onto the scene (radial at the hero's frame fractions: snap 0.55 at contact → 0.2 → shimmer keyframes); 6 luminous tendrils (paired blurred SVG strokes — glow w9/blur5 + core w3/blur1.1, accent/violet/ice alternating) draw outward from the word rect staggered 0.05s (stagger auto-clamps so the last filament completes before the sink); hold = glow breathes 814% (4-key scale/opacity), glow-layer flicker, two counter-drifting caustic gradients shimmer over the text zone, the bloom drifts slowly with the current (params.drift on the x channel); exit at min(heroOut0.2, DUR0.3) = the light SINKS — tendrils (via their HTML wrapper, never gsap-y on inner SVG), caustics, pocket, spill and the word all drift down (+14/+26) while dimming, word blurs out | tendril geometry is hand-authored from the demo and baked at compile time as offsets from the word center (x scaled by halfW/demoHalf, y by fontPx/120, endpoints frame-clamped); word rect is metric-exact via assets/fonts/char-widths.json (0.01em tracking); pairs with fx.sporeburst (seeded spore puffs burst PAST the bloom at contact in the fg layer, ballistic with slight sink) and the planktonrail paradigm (same glow-on motif at low amplitude + mote attraction anchored at the same I); plate: pushIn+grain (no punch) is the demo register — the demo's postfx profile was push | | silkribbon | aurora silk WRITTEN into the night sky behind the subject: the hero word is a sequential Hershey-stroke reveal (one <path> per pen stroke at constant pen speed, gen-stroke-path.py baked at compile time — any word, zero tuning) in a blurred halo (params.haloWidth, feGaussianBlur 7) + tight core (params.coreWidth, white drop-shadow), BOTH stroked by ONE userSpaceOnUse rose→teal→violet linearGradient with reflect spread; SIGNATURE: the gradient's x1/x2 translate f(t) from I0.07 to the clip end (shift 1.5× the gradient span) so the aurora colors FLOW THROUGH the drawn ribbon during the hold; a soft light mote (blurred halo + core dot) rides the pen, hops at lifts, and blooms out 2.4× at the final stroke; scene reaction = two huge aurora blobs (teal top-left / rose top-right, frame-fraction geometry) drift the sky f(t) from t=0 at params.wash and DEEPEN to params.deepen as the ribbon writes, a top scrim (plate.scrim, 0.653H) charges in at I0.34 (sky washout guard), a light spill (params.spill) tints the sky at I+0.1 once the ribbon completes and flickers gently, and the halo breathes (5-key opacity cycle) through the hold; gentle — no shake, no contact squash: the apex verb is the writing itself; writing window = params.window clamped so the ribbon finishes + holds before ENDT = DUR0.04 | canvas/gradient/spill all derive from params.targetWidth (demo: 880 ink on a 1010×280 canvas, baseline 200; svgW = tw+130, spill = (tw+60)×0.51, gradient span = 0.6364·tw), centered at the scene-aware HG (pref top 18, the sky band — same as drawon); pairs with fx.silkmotes (seeded aurora motes rise near the ribbon during the hold, fg layer, demo spread fractions keep the face gap) and the sheenrail paradigm (same flow motif at low amplitude); plate: pushIn+grain (no punch) is the demo register — the demo's postfx profile was push | | scopetrace | the scope's own trace LEAPS off the band and WRITES the hero word behind the subject: a surge column (3px, bottom-anchored scaleY pop, landing at the pen-start point parsed from the baked path) jumps from the scope band at I0.12; the word is a sequential Hershey-stroke reveal (one <path> per pen lift at constant pen speed, gen-stroke-path.py baked at compile time — any word, zero tuning; punctuation stripped for the pen) drawn CORE-ONLY in a thin phosphor stroke (params.coreWidth, bright palette.bright + accent drop-shadows — it is a trace, not a tube), nib rides getPointAtLength and pops 2.4× at DRAWN; the phosphor FILL (blurred halo strokes, params.haloWidth, blur 7) flicker-arrives AFTER the write at FLICK = DRAWN+params.flickGap (pre-arm 2 frames early, 5-step 1/0.25/1/0.55/1 blink, core dips 0.55 for a frame); hold = glow hum (7 Hz + 11.3 Hz sine keyframes, f(t)) + 1.6% breathe on an HTML wrapper with transformOrigin at the hero's frame fractions, ending exactly at the collapse; exit = the word COLLAPSES to a flatline (wrapper scaleY 0.02, 0.14s power3.in, same origin) + a horizontal flash bar (2·halfW wide at the word center) that shrinks out — scheduled at nextSpokenWord params.clearGap (clamped ≥ FLICK+0.45, ≤ heroOut) so the trace clears BEFORE the next line speaks; scene reaction = phosphor radial scrim (plate.scrim at the hero fractions, I0.18 → EXIT+0.05) + green spill sized from targetWidth (params.spill); second beat: a 1-frame phosphor pulse answers the FINAL transcript word — the same death beat the scoperail finale owns | trace geometry derives from params.targetWidth (svgW = tw+140, local baseline 215, ink center ≈ baseline28 — the demo registration; surge x parsed from the first M of the path); centered at the scene-aware HG (pref top 25, ink height 115); pairs with the scoperail paradigm (tracewipe = the same pen-write at low amplitude; the paradigm's hero waveform kicks fire at the same word onsets) and needs no front fx; plate: grain-only (no punch) is the demo register | | papermat | STOP-MOTION PAPERCUT behind the subject: a torn-edge kraft mat (two seeded torn polygon layers) DROPS from above with steps(2) gravity and slaps down (squash → rest, soft blob shadow); then one torn cream chip per char (palette.paper/paper2, hero-font ink palette.ink) drops with steps(3) gravity + 1-frame overshoot at I + i·params.step, each announced by an anticipation shadow 3 frames early, landing crooked (seeded ±3° ±4px), settling through three 12fps poses, then holding an 8fps wobble (baked set chain — the stop-motion heartbeat) while the whole tableau breathes 1.6% (camera-push loom); scene reaction = a warm vignette (params.vig) charging with the landing; on the FINAL word the table BUMPS (y4, 1 frame) and the chips SCATTER off down-right steps(4) staggered 0.045s, the mat swept off last (disperse-on-last-word, hero.exitAt overrides) | chip geometry derives from hero.fontPx (demo ratios: tile 0.91×/1.31× fontPx, pitch tile+14, mat N·pitch+80 × 1.846× fontPx), width-fit shrinks type and chips together; pairs with fx.paperscraps (paper scraps puff at each chip contact, fg) and the paperrail paradigm (same place verb at low amplitude); plate: grain only is the demo register | | centerfold | POP-UP BOOK CENTERFOLD behind the subject: blue + cream 12-point paper starbursts (deterministic starPts polygon, palette.accent/cream fills with kraft-edge strokes) unfold on a baseline hinge (scaleY 0.03→1 back.out(1.6), cream staggered +0.06) starting I0.272 while a blob shadow grows beneath; the hero word pops up at I in params.segs hinged clip-path segments (rotationX 85→0 back.out(1.4) staggered params.segStagger — the body's popup verb at full amplitude); two kraft struts lean in at I+0.33; backdrop + word wobble-settle TOGETHER (one #cfWob wrapper owns rotationX, 6-key) then breathe through the hold (word 5-key rotationX + shadow scaleX, both auto-squeezed into the window); scene reaction = a dusk radial scrim at the hero's frame fractions (params.scrim, bright-sky insurance) charging with the unfold; exit = the whole spread folds FLAT (segments rotationX 88 staggered, struts/bursts scaleY collapse cream-then-blue, scrim out) at max(I+segStagger·(segs1)+0.45, min(heroOut0.33, DUR0.39)) — the book closes before the next sentence | burst geometry derives from the word rect (metric-exact via char-widths: burstR = clamp(1.83·fontPx, 0.59·wordW, 0.42·H), box 2R+180 × 2R+fontPx, demo props scale at burstR/220), width-fit shrinks the type first; pairs with fx.confetti (seeded ballistic paper confetti launched from the hinge line at I+0.128, fg) and the popuprail paradigm (same baseline-hinge verb at low amplitude); plate: grain only is the demo register | | chalkwrite | NIGHT LECTURE behind the subject: the lower chalk band grows into a FULL deep-green board (scaleY 0→1 power3.out from the bottom edge at I0.3, the room dims params.dim + vignette params.vig with it); the hero word is HAND-WRITTEN in chalk — sequential Hershey stroke reveal (drawon pattern, gen-stroke-path.py baked at compile time), dry/rough via seeded ±1px jitter on every pen point (params.jitterSeed), a blurred halo (params.haloWidth, feGaussianBlur 3.2, group opacity params.halo) under a tight core (params.coreWidth), a chalk-stick nib (rotated rect) riding the pen — while params.dustN dust specks fall from the nib along the path; at write-complete a hand-slap CLAP shakes the board 1 frame (set-chain y 3/2/1/0; the dust burst lives in the rail, in FRONT: fx.chalkclap), then TWO violent accelerating underlines slash beneath (seeded waviness, power3.in, the nib rides them, dust kicks at the ends), the chalk flicks away, and the halo breathes (baked 1.6/3.7 Hz f(t)) through the hold with two slow motes drifting off the letters | geometry registers on the MEASURED ink box (the stroke path is parsed at compile time; ink CENTER lands on scene-aware HG.y, pref top 44): the board rect (params.boardGap×inkH above the ink, bottom flush with the frame, width = the body strip), the underlines (inkL+14→inkR4 at inkBot+30/+50) and the fg clap point (the pen's LAST contact point → HG.clapX/Y/T) all derive from it; x defaults to the board's LEFT margin + tw/2 — a hand writes from the left, never centered on the subject (theme.hero.x overrides); pairs with fx.chalkclap (12 seeded radial dust specks + a blurred puff at DRAWN, fg) and the chalkrail paradigm (same scribble verb at low amplitude); plate: grain only is the demo register; needs python3 | | spraytag | STREET TAG behind the subject: the dusk wall dims (params.dim) + a radial scrim charges (params.scrim) at I0.42 as the writer steps up; the can RATTLES at the pen-start point (6 seeded set-chain nib jitters, I0.30→I0.02), then the hero word is spray-written stroke by stroke — sequential Hershey dash reveal at constant pen speed (gen-stroke-path.py baked at compile time) — as a TRIPLE stack per stroke: fat dark under-stroke palette.under (params.underWidth, sky-contrast armor) + blurred overspray halo (params.haloWidth, feGaussianBlur 7, group opacity params.haloOpacity) + wet paint core (params.coreWidth, drop-shadow), the glowing nib (r9, palette.nib) riding each stroke; params.osprayN seeded overspray dots pop near the nib as it passes; nib throws off 2.6× at DRAWN; through the hold params.drips seeded DRIPS (paint→dripMiddripLo gradient bars) grow from painted low points (x-separated >110px) and KEEP growing, the halo hums (7/11 Hz baked f(t)) and the whole tag looms 1.014; writing window = params.window (demo 0.58s) clamped so write + splat + drip fit before ENDT = DUR0.04 | geometry registers on the MEASURED ink box (ink CENTER lands on scene-aware HG.y, pref top 26 — the sky band, crossing the subject silhouette is the point; tag centers on the hero anchor, targetWidth capped at W140); the pen-finish point is baked onto HG.splatX/Y/T for the fg splat; pairs with fx.paintsplat (hot em2 ring + paint ring + seeded ballistic droplets at DRAWN, fg, in FRONT of the subject) and the markerrail paradigm (same flick verb at low amplitude); plate: punch with punchOffset ≈ window+0.05 so the punch lands on the splat, not the hero onset; needs python3 | | brushwrite | SUMI-E GESTURE behind the subject: a warm paper wash (palette.wash/wash2 radial, targetWidth+380 × params.washH) charges at I0.18 to params.wash, settling to params.washSettle through the hold; the brush SLAPS down at the first pen point — a start-SPLAT (irregular ink ellipse rotated 24° + 5 seeded satellite dots, scale 0.35→1 power3.out 0.12s; the fg fleck pass rides fx.brushflecks) — then the hero word is written in ONE sequential gesture (Hershey dash reveal at constant pen speed, gen-stroke-path.py baked at compile time) as a FOUR-layer stack per stroke: blurred bleed (params.bleedWidth, feGaussianBlur 6, group hidden until the hold) under fat/mid/core widths (fatWidth 0.25 / midWidth 0.6 / coreWidth 1.0 stroke-opacity = pressure-tapered wet brush), the round ink nib (r7.5, palette.nib) riding the pen and flicking off (+42,30, scale 1.9) at DRAWN; ≤params.spatterN spatter droplets eject PERPENDICULAR at the sharpest-curvature points of the gesture (angle-delta > 0.55 rad, te-separated > 0.045s); through the hold the ink BLEEDS into the paper (bleed group fades to params.bleed over ≤1.7s) and the whole work breathes 0.6% (params.breatheHz baked f(t), svgOrigin = ink center); then the vermilion SEAL chop (params.sealGlyph, palette.seal/sealMid/sealLo radial face, fonts.seal glyph) stamps at DRAWN+params.sealDelay — power4.in crush 0.09s → 2-frame squash → elastic settle + 3 seeded red flecks — skipped entirely if the clip ends too soon; writing window = params.window (demo 0.45s) clamped so write + seal + bleed fit before ENDT = DUR0.04 | geometry registers on the MEASURED ink box (ink CENTER lands on scene-aware HG.y, pref top 19 — the sky band, crossing the subject silhouette is the point; gesture centers on the hero anchor, targetWidth capped at W240 — on scenes with a dark object near the pen start, set theme.hero.x so the slap-down lands clear, like the demo's 864); the pen-START point is baked onto HG.fleckX/Y/T for the fg flecks and the seal sits at (inkR103, inkBot+72) clamped on frame (params.sealDx/sealDy override); pairs with fx.brushflecks (seeded upward ballistic ink flecks at the slap-down, fg, in FRONT of the subject) and the brushrail paradigm (same brush-wipe verb at low amplitude); plate: punch with punchOffset ≈ window+sealDelay so the punch lands on the seal CONTACT, not the hero onset; needs python3 | | ransomnote | RANSOM NOTE slam behind the subject: one cutout chip per letter (4 recipes cycled, seeded ±13% sizes around hero.fontPx, alternating-sign rotations ±25°, misaligned baselines ±26px, seeded tear-polygon edges) slams in oversized (scale 1.5, rot×1.8, flight drop-shadow) at S_k = I + 0.01 + params.step·k, crushes power3.in over 0.085s, and on contact the shadow POPS tight (1-frame filter swap) → 2-frame squash 1.07/0.93 → elastic settle; when the LAST letter lands the whole word takes a collective 1-frame JOLT (y6 → power3.out) + a scene pulse (params.pulse full-frame dark blink); hold = slow 5-key loom (the crooked grid never straightens); exit = letters RIPPED off one by one (rot+±1522° throw, y34, fade, 1-frame tear flash under each) at ripT = min(lastWord params.ripLead, DUR-clamped), ≥ jolt+0.45; scene reaction = collage scrim (params.scrim top-dark gradient) charges at I0.07, out with the rip | width-fit measures each glyph IN ITS OWN recipe font (assets/fonts/char-widths.json) + 0.20em chip padding + 6px margins so the collage never overflows; chip chrome (shadows, space width) scales with fontPx from the 130px demo cut; the setpiece RECOMPUTES plate.punchOffset = 0.01 + step·(N1) + crush from the real letter count so the plate punch always lands on the collective jolt, whatever the hero word length; pairs with the ransomrail paradigm (same paste verb + tear flashes at low amplitude, same recipe classes/seeded tear polygon emitted into both layers); no front fx — the flashes live inside the chips | | inkbloom | INK DROP IN STILL WATER behind the subject: paper-white radial washes charge the two text zones (palette.wash rail wash from t≈0.15, PERSISTS to clip end so the closing body lines keep their ground — it registers on the body row geometry body.bottomPx/rowGap; palette.washApex apex wash at I0.077, out with the bloom) and a gentle vignette (params.vig) STILLS the world while the ink blooms; the drop (ink teardrop, params.fall 0.15s) appears above the word point at I0.08, swells, falls power2.in with a scaleY stretch, squashes 1.7/0.35 on contact (2 frames) and merges into the BLOOM — 3 stacked blurred irregular ink ellipses (palette.ink, opacity snaps 0.82/0.62/0.5 then decays) scaling at different rates with counter-rotations (expo.out 1.15/1.35/1.5s) + 2 thin concentric ripple rings expanding 7.2×/9.6× on the water surface; the hero words RESOLVE out of the blob (opacity snaps, blur 16→0 + scale 1.12→1 power2.out) — the FIRST at contact+0.02, the following at their own spoken times (multi-word hero, per-word hand-off); through the hold the glyph edges BREATHE (baked 5-key blur keyframes per word, alternating 0.66/0.5s), the group looms 1.018 and two faint ink wisps curl off the word top (C+0.573/C+0.753, fit-guarded); exit: RE-DISSOLVE (blur 11 + scale 1.15 + fade, 0.52s power1.in) at XOUT = min(I+params.maxHold, next body line params.clearGap, DUR0.6) — the ink clears before the next thought needs the reader | geometry derives from the measured word rect (assets/fonts/char-widths.json; kx = halfW/demoHalf, ky = fontPx/132 scale the demo's blob/ring/wisp anchors; pref top 25 — the sky band, crossing the subject silhouette is the point); dark palette.ink + its paper washes carry the contrast on bright skies; pairs with the inkrail paradigm (same bleed verb at low amplitude); plate: quiet pushIn, no punch — the bloom is liquid, not percussive |

Inline setpieces (hero.inline: true) live inside the body paradigm — the hero word stays in lines. Embed setpieces own the word: leave it OUT of lines (rail↔climax hand-off), except panel + redact-until-hero, where the words appear in the log as █████ until the decode locks.

A setpiece may additionally return fgCss/fgHtml/fgJs — those parts are merged into the fg alpha page (rail.html), for furniture that must ride ABOVE the matte (e.g. a verbatim chip that may never be occluded) while the setpiece scenery stays embedded behind the subject.

Linkages

  • redact-until-hero — panel log shows hero words as blocks; un-redacts at lock
  • corrupt-on-last-word — panel jitters + hue-shifts on the final word
  • disperse-on-last-word — every visible poem letter scatters back to dust
  • field-is-the-apex — (lastpage) the blurred future-field rack-focuses at the apex: every faint line was the apex word all along; the main instance holds, the rest re-blur and die

Hard rules inherited (do not relax)

  • determinism: paused GSAP on window.__timelines["main"], seeded PRNG only, set-chains / keyframes, no Math.random/Date.now/CSS-keyframes
  • 80 ms word sync (timings come from transcript.json via the strict matcher; any uncovered transcript word is a compile ERROR — verbatim completeness)
  • apex owns the frame: fg furniture must avoid the setpiece rect or yield (body.yield) during the hero window
  • plate punch/shake ALWAYS post-composite (_postfx.sh) so subject + text + plate move as one photographed frame (matte never desyncs)

SVG gotchas (paid for, do not rediscover)

  • dash patterns restart on EVERY subpath → sequential per-stroke paths, never one dashoffset across a multi-stroke word
  • round linecap renders a DOT at dashoffset=len → hide each stroke until its turn
  • inline-block trailing spaces collapse → word gaps via margin, not text spaces
  • SVG-font glyph y is UP → flip when converting (gen-stroke-path.py does)
  • NEVER gsap x/y/scale on raw SVG children: it runs _parseTransform/getBBox, and zero-area shapes (fan-kit slivers) trip GSAP's bbox hack → appendChild on a null docElement in injected-gsap contexts (preview-frames, renderer). Jitter SVG pieces via attr:{transform:"translate(...)"} only; scale/loom belongs on an HTML wrapper div. Nuance: a ROOT <svg> inside a div has null ownerSVGElement → GSAP classifies it as HTML (CSS transforms, no getBBox), so scale/skew on the svg root is safe — but copying that onto an inner <g> becomes the fatal class. attr transforms can't TWEEN — use frame-quantized step chains (identical on screen at 24fps)

Timeline ownership rules (audit-derived, do not relax)

  • ONE owner per (target, property) at any instant. A duration tween re-renders every tick until it ends — any set/tween on the same property inside that window gets visually overwritten a frame later (1-frame ghosts, flash-backs). Known shapes: yield dim vs line exit; yield restore vs powercut set-chain; per-word line-y bounces with word gaps < 0.22s. The rail emitter guards these (bounce ownership tracker, dim clamped ≥ line-in, restore only with ≥0.3s runway before exit — else the line exits dimmed, by design).
  • Inverted clamps: Math.max(lo, Math.min(hi, x)) with lo > hi silently pins to lo. When a lockup is wider than the frame, CENTER it (x = W/2) instead of clamping — decorative bleed must be symmetric.
  • The overflow gate only measures elements with direct text nodes — SVG-painted setpieces (mask text + rects) are invisible to it. Do not read its "ok" as proof for svg lockups; judge those on the preview frames.

theme.json — full schema

{ "dna": "<themes/*.json name>",            // required
  "lines": [["word","word",...], ...],       // required — verbatim transcript tokens, in order,
                                             //   INCLUDING punctuation ("size.", "noise.")
  "minors": ["word", ...],                   // optional — theme's inline emphasis treatment
  "hero": { "match": "word or phrase",       // required — exact transcript tokens (case-insensitive,
                                             //   punctuation-stripped match); first occurrence wins
            "text": "DISPLAY FORM",          // optional display override
            "out": 5.9, "exitAt": 3.3 },     // optional window overrides (seconds)
  "width": 1280, "height": 720,              // optional, default 1280x720 (theme DNAs are tuned
                                             //   for 16:9; vertical needs a DNA variant)
  "fps": 24,                                 // optional — matte.fps is AUTHORITATIVE when present
  "panelHeader": "..." }                     // optional (panel paradigm)

Scene awareness (inherited from the existing typography system): when safe-zones.json is present the compiler re-anchors the hero setpiece to the measured occlusion bands (heroBands.profile, 1855% occlusion, closest to the setpiece's preferred height) and heroAnchor.centerXPct, width-fits the hero font to the actual word, and docks panel/poem furniture on subject.clearerSide. Explicit theme.json values > scene auto > DNA fallback.

Expected (benign) log noise: [Browser:REQUESTFAILED] GET source.mp4 net::ERR_ABORTED during the fg-layer page load, reel-glyph overflow lint lines, and media_missing_data_start — all cosmetic; the render is good if render-theme.sh exits 0 and prints the gates summary.

Authoring a project (this is ALL a user writes)

<project>/theme.json:

{
  "dna": "ordnance",
  "lines": [
    ["The", "stars", "were", "only", "limited"],
    ["by", "the", "pixel", "size."],
    ["It's"],
    ["what", "you", "can", "actually"],
    ["recover", "from", "the", "noise."]
  ],
  "minors": ["stars"],
  "hero": { "match": "remarkable" }
}
  • lines follow the transcript verbatim, in order (compile error otherwise); every transcript word must be covered by lines hero.match — the example above omits "remarkable" from lines precisely because hero.match covers it (it slots between ["It's"] and ["what",...])
  • leave the hero word(s) out of lines for embed setpieces; keep them for inline setpieces and for panel+redact
  • takeover: each line is one CARD — group by beat (12 words), not subtitle length; panel scrolls automatically past ~5 lines (terminal scroll)
  • the ordnance "charge" is an edge vignette + rail yield (the center stays clean by design) — judge it at the frame corners, not the center
  • minors: words that get the theme's inline emphasis treatment
  • preview first: node scripts/make-theme.cjs <project> then node scripts/preview-frames.cjs <project> <t1> <t2> ... (Visual QA)
  • then: bash scripts/render-theme.sh <project>final_fx.mp4 (the deliverable; final.mp4 is pre-plate-reaction)

Prereqs identical to other modes — prepare.sh outputs: transcript.json, frames_fg/, matte.fps, safe-zones.json (scene awareness), source.mp4. frames_bg/ is only needed by preview-frames.cjs, not by the render.

Adding a theme DNA

Copy a themes/*.json, change identity fields. You may ONLY combine existing paradigm/setpiece/linkage names — if the look you want needs a new mechanism, that is a registry contribution (this file + make-theme.cjs), reviewed like an engine change. A DNA that needs per-word visual tuning to ship is rejected: any input, zero hand-fixing is the acceptance bar (the draw-on lesson).