Docs/Skills and code/skill video-visual-production
.claude/skills/video-visual-production/references/scene-catalogue.md
Scene catalogue: Twelve scene types exist today (render/scenes.js). A scene in the beat sheet looks like this; every field is explained below.
Scene catalogue
Twelve scene types exist today (render/scenes.js). A scene in the beat sheet looks like this; every field is explained below.
{
"id": "B1.06", "span": [95, 130], "type": "doc", "class": "E", "enter": { "type": "cut" },
"props": { ... type-specific ... },
"cues": { "enter": 95, "strikes": 105 },
"text": [ { "value": "Frequent strikes", "cue": "enter", "role": "evidence" } ],
"ledger": ["calcutta-strikes"],
"sfx": [ { "sound": "swipe", "cue": "strikes", "contact": "sweep", "role": "doc", "event": "B1.06 highlight: frequent strikes" } ]
}
| Field | Meaning |
|---|---|
id | <beat>.<two digits>, in order |
span | [first word index, last word index] of the words this scene covers. Spans tile the beat with no gap |
type | one of the twelve below |
class | E evidence, D data from the ledger, M diagram, T typography (A atmosphere is not used) |
enter | { "type": "cut" } or { "type": "fade", "frames": 8 } (a cross-fade from the previous scene) |
start_shift_frames | optional: start this many frames after the first word (lets the previous scene hold longer) |
props | what to draw (per type below) |
cues | build name -> word index. The build starts when that word starts. The name enter is the scene start and does not need defining |
text | every string the scene puts on screen, for the lint: value, cue, role (number, label, name, keyword, evidence, source, chapter). Add "number": true for figures, "spoken_as": "bayalis" when the voice says a number as a word or a label by another word, "recall_from": <word index> when it repeats something said earlier in the beat |
ledger | ids of rows in ledger-<beat>.json the scene relies on (required for class D) |
sfx | sound events: sound, cue, contact (offset from the cue, from render/contacts.js), role, event (a short description of the visual event). Optional step (tick pitch steps) and ticks (clock ticks) |
Sounds: tick (a number or step appears; contact: pop or check), stamp (a date lands; stamp), thud (two things meet; merge), swipe (a highlight starts; sweep), paper (a document slides in; sweep), whoosh (movement; arc), pivot (a named idea lands; label), clink (glass; glass), clock (role: texture, ticks: 4). Contacts: stamp 0.14 s, pop 0.10, sweep 0, arc -0.05, label 0.12, merge 0.45, check 0.20, glass 0.20. Each is the time after the cue when the picture lands; the sound is placed there so picture and sound agree.
Reusable as they are: date, slam, doc, timeline, crowd, route (two places and an arc), ladder, ceiling. Shaped by beat B1 (some text or layout is fixed in code; reuse only if your story fits, otherwise adapt the function): cast (draws a factory icon and a "running" chip), photo (Delhi label, clock icon and the photographer's credit are in code, so a different photo needs the credit changed in scenes.js), merge (the Swiss tile text is in code), cocktail.
date (T)
A big year or date stamped on the word. Use for the opening or a turning date. props: year ("1983"), chapter ("THE CITY HE LEFT", small label top left). cues: year (the word that says it). text: the year (number, number: true) and the chapter label (chapter, cue enter). sfx: stamp at year with contact stamp.
cast (M)
A person card on the left (name, role, an optional age chip) and a place with a factory on the right. First time a person appears, use this so the viewer knows who they are. props: name, role, age { "text": "about 42", "status": "derived" } (derived or single-source status draws a dotted outline), place, placeSub, state. cues: card, age, calcutta, kolkata, factory, running. sfx: tick at age, contact pop.
doc (E)
A captured article passage on a card; the phrases being discussed are highlighted as they are spoken, and the current phrase is repeated large below. The strongest evidence scene. One passage, a few lines. props: asset (a crop id from assets), tag ("Source: Business Today, 17 Apr 2011"), highlights [ { "phrase": "...", "cue": "name" } ] (each phrase is also listed in the crop's highlights in assets), focus (the phrase shown last). cues: enter plus one per highlight. text: each highlighted phrase (evidence, cue enter, because it is visible from the start) and the source line (source). sfx: paper at enter for the first doc of a run, swipe at each important highlight (not more than one per 6 s). If the capture fails the scene shows a typeset card and the tag says so. Asset request: { "id": "bt2011", "kind": "capture", "fallback": true, "url": "...", "outlet": "Business Today", "published": "17 Apr 2011", "crops": [ { "id": "bt2011-odd", "anchor": ["first words of the passage", "last words of the passage"], "highlights": ["phrase one", "phrase two"], "fallback_text": "the whole passage, verbatim" } ] }. The anchor and phrases must occur in the page text. Fallback text is used only if capture fails.
crowd (M)
A row of identical faceless figures; one becomes "you" and steps out. For lines that speak to the viewer ("if you have ever been the one who..."). props: count (9), you ("you"), line (a 6-word line shown when the figure steps out). cues: row, you, out, ring.
timeline (D)
A staircase of up to three cards in order (not to scale), each with an optional year, a title, a sub line. For a short career or event sequence. props: steps [ { "year": "1962" | null, "title", "sub", "cueYear", "cueTitle" } ], making { "text", "cue" } (a small "then..." line under the last card). cues: the names used. text: each year (number), title and sub (label). sfx: tick at each year with contact: pop, step 0, 2, 4 for rising pitch.
slam (T)
Plain large text, one or two lines. Use for a statement, or when no honest picture exists. It is the fallback for everything. props: lines [ { "text", "cue", "size" (px, e.g. 64 to 150), "tone": "ink" | "muted" | "accent" | "good" } ] (at most two). cues: the line cues. Keep a keyword line to 7 words or fewer. The text must be held at least max(1.2 s, 0.35 s per word).
route (M)
Two places and an arc between them, no map outline. For a move from one place to another with a reason. Left place dims ("stops working"), arc draws with a label, right place lights and grows. props: from, to, label (the date on the arc), stops, grows. cues: places, stops, leave, restart, grow. sfx: whoosh at leave, contact arc.
ladder (M)
The pivot ladder: a ladder with rung 1 filled, rung 2 dashed ("next"). First use defines it; reuse for every later pivot with the same look and the same pivot sound. props: mode "label" (big "PIVOT 1" on the left; cues rung, tick, label) or "worked" (a factory squeezed by walls that open, rung turns green with a check, "it worked"; cues city, tight, worked), rungText, label, worked. sfx: pivot at label (contact label); tick at worked (contact check).
photo (E)
A licensed photograph with a caption, a credit and a small icon column. props: asset (a Commons asset id), caption, caption2, credit, clockLabel, city. cues: delhi, photo, clocks, swiss. The caption must say what the photo is and, if it is a different organisation with the same name, say so. Needs a Commons asset: { "id": "swiss-building", "kind": "commons", "title": "File:...jpg", "width": 1500, "use": "..." }; refused unless the licence is free.
merge (M)
Two name tiles; one slides into the other and the name stays. props: a, b, result, status ("single-source" draws a dotted outline). cues: tiles, buy, name. sfx: thud at buy, contact merge. Draw no percentages unless the voice says them.
ceiling (M)
A clock rises to a dashed ceiling line and stops; a dashed circle "something new" appears above. props: clockLabel, newLabel. cues: clock, ceiling, stop, new. sfx: clock with role: texture, ticks: 4, at stop, contact sweep.
cocktail (M)
A bulb ("next idea") becomes a glass; a pin and a two-line place name. props: idea, place (two words per line work best: the first word is line 1, the rest line 2). cues: idea, place, glass. sfx: clink at glass, contact glass.
Scene types added for beat B0 (the hook), in render/scenes-b0.js
All of these are cue-driven like the others. Use the B0 beat sheet (research/the-fall-of-moser-baer/video/beat-sheet-B0.json) as the example for each.
| Type | Use | Props | Cues | |||
|---|---|---|---|---|---|---|
disc | A turning silver disc with label chips and a name settling on it (mode: "turn"), or a disc multiplying into a grid with a gold tint (mode: "grid") | turn: name, chips [ { text, cue } ]; grid: none | turn: one per chip, name; grid: grid, tint | |||
black | A declared black frame (a pause with a picture). Set "black": true on the scene so the black-frame check expects it | none | none | |||
plant | A factory silhouette at night with windows (`windows: "dark" | "lit"), a gate (gate: "open" | "closing" | "closed"), optional date, place, a notice (maint, maintSource), three day squares (days: true), an escape arrow ending at the gate, a faceless figure` outside | those flags | date, place, maint, days, third, close, arrow, figure, lit |
workers | A faceless worker in a plant, tally marks in groups of five, a label, a pay slip with a slice offered. Generic on purpose: draws no name | tally, union, pay | worker, tally, union, slip, pay, slice | |||
person | A name card (name, role) beside a thing the person turns away from (land), with an arrow that turns away | name, role, land | land, away | |||
chips3 | Three cards with an icon (glass, clock with half filled, medal), a label and an optional sub line | items [ { icon, label, sub, cue } ] | the item cues | |||
dots | Three big dots and a label for open questions | count, label | dots | |||
loop | An exit arrow that bends into a loop (the way out becomes a trap) | exit, trap | exit, loop, trap | |||
pivotno | A hollow first rung of the ladder and a running factory stamped NO | label, no, running | rung, factory, no |
Waiving a numeral on purpose. If the voice says a number that must not be drawn (for example a fact still marked [V-search]), put "waive_numerals": { "17": "reason" } on the scene. The lint then warns instead of failing, and the reason is printed. Use it only with the user's knowledge and tell them.
A typeset quote on purpose. Asset kind quote: { "id": "care2017", "kind": "quote", "url": "...", "outlet": "CARE Ratings", "published": "2017", "text": "... fragment ...", "highlights": ["fragment"] }. The doc scene shows it typeset with the tag "(typeset quote)". Take the wording only from a source you have read or from a ledger row marked [V], and say which in the ledger.
Generic data and diagram scenes (render/scenes-data.js)
Added 2026-11-01 for the middle beats. Every string comes from props and every build from a cue, so no code is needed to use them. node render/selftest-scenes.js draws one working example of each (the examples live in EXAMPLES in that file; copy a type's example and change it). Number text (role: number) must be at least 70 px and is set that way by the scene; every numeral on screen must be spoken in the scene (lint H8), so show a number only on the word that says it.
| Type | Use | Props | Cues | Sounds | |
|---|---|---|---|---|---|
stat | One big figure with a label, an optional count-up and a source line. status: solid (verified), dotted (single source or derived), ghost (searched for, not found: red dashed) | value or count {from,to,prefix,suffix,decimals}, label, sub, source, status | stat, label | tick at stat, contact pop | |
bars | Up to five columns. A negative value hangs below the zero line in red. ghost: true draws a dashed red empty bar for "no record found". gap {from,to,text,cue} brackets a difference between two bars | bars [ { label, text, unit, value, cue, tone, ghost } ], gap, source | one per bar and for the gap | tick per bar, contact pop; never more than 3 in 10 s | |
line | One or two series, points drawn one to the next, each with a value text and unit (x positions are by order, not to scale) | series [ { label, tone, points [ { x, y, text, sub, cue } ] } ], yMax, source | one per point | tick at each point, contact pop | |
photo2 | A licensed photo (Commons asset) with a caption, a two-line credit and notes on the right. Generic version of photo | asset, caption, caption2, credit1, credit2, notes [ { text, cue, tone, number } ] | photo plus the note cues | none | |
chips | A set of reasons or items as pills; ghost: true draws a dashed red pill (an item searched for and not found); `tone: "muted" | "accent"` to compare | title, items [ { text, cue, tone, ghost } ] (up to 6) | one per item (title optional) | tick per item (mind the density limit) |
race | Lanes with a marker each: one already ahead, one at the start line. For "late to the race" | track, lanes [ { label, pos 0..1, cue, tone } ] | one per lane | none | |
stalls | Two stalls with price boards that change in turn, and a lane that fills with faceless figures ("nobody is richer") | stalls [ { label, boards [ { text, cue } ] } ] (two stalls), fill { count, cue, text } | every board cue, fill | tick when a board changes | |
treadmill | A runner on a belt; the belt speeds up at the speed cues; an outline appears and flickers dashed when the speed is high | speeds [ { cue, v } ], outline { cue }, label, labelCue | the speed cues, the outline cue | none today (a belt hum is not built) | |
unitgrid | Icons that stand for a big count (icon: disc, factory, person), filling one by one, with a legend and a headline value | icon, count, cols, legend, value, span, tone | grid, legend, value | tick at grid | |
stamps | A row of identical stamps landing one after another | count, text, title, labels, every | stamps, title | stamp at stamps, contact stamp (one only) | |
paths | A fork: a start box and two options (tone, glow, dashed) | from, options [ { text, cue, tone, glow, dashed } ] (two) | from, one per option | whoosh at an option, contact arc | |
rungs | The generic pivot ladder: N rungs, each filled (tick), glow (the next one) or hollow; an optional big title | count, rungs [ { state, cue, label } ], title, titleCue, x | each rung cue, titleCue | pivot at the title, contact label; tick at a rung, contact check |
Examples (props and cues that draw correctly):
stat: props{"value":"1,100","label":"crore, planned in 2000","sub":"when the country chased services","source":"Source: Business Today, 22 Jun 2003","status":"solid"}cues{"stat":0.3,"label":1}bars: props{"bars":[{"label":"FY07","text":"110","unit":"crore","value":110,"cue":"a"},{"label":"FY08","text":"79","unit":"crore loss","value":-79,"cue":"b"},{"label":"next","text":"no record","value":0,"ghost":true,"cue":"c"}],"source":"Source: Storage Newsletter"}cues{"a":0.3,"b":1.1,"c":1.9}line: props{"series":[{"label":"price","points":[{"x":"Oct 2002","y":28,"text":"28","sub":"cents","cue":"p1"},{"x":"later","y":14,"text":"14","sub":"cents","cue":"p2"}]}],"source":"Source: Taipei Times, 12 Oct 2002"}cues{"p1":0.3,"p2":1.5}chips: props{"title":"four reasons given","items":[{"text":"cheaper imports","cue":"a"},{"text":"weak demand","cue":"b"},{"text":"pen drives","cue":"c","tone":"muted"},{"text":"price","cue":"d","tone":"accent"},{"text":"a missing customer: not mentioned","cue":"e","ghost":true}]}cues{"a":0.3,"b":0.7,"c":1.1,"d":1.5,"e":2}race: props{"track":"CD-R","lanes":[{"label":"Taiwan","pos":0.7,"cue":"a","tone":"muted"},{"label":"India","pos":0,"cue":"b","tone":"accent"}]}cues{"a":0.2,"b":0.8}stalls: props{"stalls":[{"label":"stall A","boards":[{"text":"10","cue":"a1"},{"text":"8","cue":"a2"},{"text":"6","cue":"a3"}]},{"label":"stall B","boards":[{"text":"10","cue":"b1"},{"text":"9","cue":"b2"},{"text":"7","cue":"b3"}]}],"fill":{"count":10,"cue":"fill","text":"nobody is richer"}}cues{"a1":0.2,"b1":0.2,"a2":1,"b2":1.5,"a3":2,"b3":2.5,"fill":3}treadmill: props{"speeds":[{"cue":"s1","v":1},{"cue":"s2","v":2.2}],"outline":{"cue":"o"},"label":"running faster to stay in place","labelCue":"s1"}cues{"s1":0.2,"s2":2,"o":2.6}unitgrid: props{"icon":"disc","count":25,"cols":5,"legend":"1 icon = 100 million discs","value":"2.5 billion","span":2}cues{"grid":0.3,"legend":0.5,"value":2.2}stamps: props{"count":5,"text":"WON","title":"five cases in Europe","labels":["1","2","3","4","5"],"every":0.4}cues{"stamps":0.6,"title":0.1}paths: props{"from":"now","options":[{"text":"stay small","cue":"a","tone":"muted"},{"text":"pivot again","cue":"b","tone":"accent","glow":true}]}cues{"from":0.1,"a":0.7,"b":1.4}rungs: props{"count":4,"rungs":[{"state":"filled","cue":"r1","label":"floppy"},{"state":"filled","cue":"r2","label":"CD-R"},{"state":"glow","cue":"r3","label":"next"},{"state":"hollow"}],"title":"PIVOT 3","titleCue":"t"}cues{"r1":0.2,"r2":0.8,"r3":1.4,"t":1.4}
Charts and honesty. Do not chart two figures the voice does not compare, or figures in different units. A bar or line gets a source line. A single-source or derived figure uses status: "dotted" on stat (or the dotted outline in a card); a figure that was searched for and not found uses the ghost form, never an invented number. Colour red is for loss, "alleged" and "not found" only.
Palette and type (do not add colours)
One accent (gold), one "good" (teal), red only for loss, "alleged" or "not found" (unused so far), warm neutrals. Typeface Poppins. Minimum sizes as a share of 1080 px: numbers 6.5%, labels 3.5%, source lines 3%. Text stays inside the 5% / 7% safe area and out of the bottom 12%. The check script measures all of this on the rendered scenes, so a violation shows up as a FAIL row.
Show the raw text
# Scene catalogue
Twelve scene types exist today (`render/scenes.js`). A scene in the beat sheet looks like this; every field is explained below.
```json
{
"id": "B1.06", "span": [95, 130], "type": "doc", "class": "E", "enter": { "type": "cut" },
"props": { ... type-specific ... },
"cues": { "enter": 95, "strikes": 105 },
"text": [ { "value": "Frequent strikes", "cue": "enter", "role": "evidence" } ],
"ledger": ["calcutta-strikes"],
"sfx": [ { "sound": "swipe", "cue": "strikes", "contact": "sweep", "role": "doc", "event": "B1.06 highlight: frequent strikes" } ]
}
```
| Field | Meaning |
|---|---|
| `id` | `<beat>.<two digits>`, in order |
| `span` | `[first word index, last word index]` of the words this scene covers. Spans tile the beat with no gap |
| `type` | one of the twelve below |
| `class` | `E` evidence, `D` data from the ledger, `M` diagram, `T` typography (`A` atmosphere is not used) |
| `enter` | `{ "type": "cut" }` or `{ "type": "fade", "frames": 8 }` (a cross-fade from the previous scene) |
| `start_shift_frames` | optional: start this many frames after the first word (lets the previous scene hold longer) |
| `props` | what to draw (per type below) |
| `cues` | build name -> word index. The build starts when that word starts. The name `enter` is the scene start and does not need defining |
| `text` | every string the scene puts on screen, for the lint: `value`, `cue`, `role` (`number`, `label`, `name`, `keyword`, `evidence`, `source`, `chapter`). Add `"number": true` for figures, `"spoken_as": "bayalis"` when the voice says a number as a word or a label by another word, `"recall_from": <word index>` when it repeats something said earlier in the beat |
| `ledger` | ids of rows in `ledger-<beat>.json` the scene relies on (required for class `D`) |
| `sfx` | sound events: `sound`, `cue`, `contact` (offset from the cue, from `render/contacts.js`), `role`, `event` (a short description of the visual event). Optional `step` (tick pitch steps) and `ticks` (clock ticks) |
Sounds: `tick` (a number or step appears; `contact: pop` or `check`), `stamp` (a date lands; `stamp`), `thud` (two things meet; `merge`), `swipe` (a highlight starts; `sweep`), `paper` (a document slides in; `sweep`), `whoosh` (movement; `arc`), `pivot` (a named idea lands; `label`), `clink` (glass; `glass`), `clock` (`role: texture`, `ticks: 4`). Contacts: `stamp` 0.14 s, `pop` 0.10, `sweep` 0, `arc` -0.05, `label` 0.12, `merge` 0.45, `check` 0.20, `glass` 0.20. Each is the time after the cue when the picture lands; the sound is placed there so picture and sound agree.
**Reusable as they are:** `date`, `slam`, `doc`, `timeline`, `crowd`, `route` (two places and an arc), `ladder`, `ceiling`. **Shaped by beat B1** (some text or layout is fixed in code; reuse only if your story fits, otherwise adapt the function): `cast` (draws a factory icon and a "running" chip), `photo` (Delhi label, clock icon and the photographer's credit are in code, so a different photo needs the credit changed in `scenes.js`), `merge` (the Swiss tile text is in code), `cocktail`.
---
## date (T)
A big year or date stamped on the word. Use for the opening or a turning date.
`props`: `year` ("1983"), `chapter` ("THE CITY HE LEFT", small label top left). `cues`: `year` (the word that says it). `text`: the year (`number`, `number: true`) and the chapter label (`chapter`, cue `enter`). `sfx`: `stamp` at `year` with contact `stamp`.
## cast (M)
A person card on the left (name, role, an optional age chip) and a place with a factory on the right. First time a person appears, use this so the viewer knows who they are.
`props`: `name`, `role`, `age` `{ "text": "about 42", "status": "derived" }` (derived or single-source status draws a dotted outline), `place`, `placeSub`, `state`. `cues`: `card`, `age`, `calcutta`, `kolkata`, `factory`, `running`. `sfx`: `tick` at `age`, contact `pop`.
## doc (E)
A captured article passage on a card; the phrases being discussed are highlighted as they are spoken, and the current phrase is repeated large below. The strongest evidence scene. One passage, a few lines.
`props`: `asset` (a crop id from `assets`), `tag` ("Source: Business Today, 17 Apr 2011"), `highlights` `[ { "phrase": "...", "cue": "name" } ]` (each phrase is also listed in the crop's `highlights` in `assets`), `focus` (the phrase shown last). `cues`: `enter` plus one per highlight. `text`: each highlighted phrase (`evidence`, cue `enter`, because it is visible from the start) and the source line (`source`). `sfx`: `paper` at `enter` for the first doc of a run, `swipe` at each important highlight (not more than one per 6 s). If the capture fails the scene shows a typeset card and the tag says so.
Asset request: `{ "id": "bt2011", "kind": "capture", "fallback": true, "url": "...", "outlet": "Business Today", "published": "17 Apr 2011", "crops": [ { "id": "bt2011-odd", "anchor": ["first words of the passage", "last words of the passage"], "highlights": ["phrase one", "phrase two"], "fallback_text": "the whole passage, verbatim" } ] }`. The anchor and phrases must occur in the page text. Fallback text is used only if capture fails.
## crowd (M)
A row of identical faceless figures; one becomes "you" and steps out. For lines that speak to the viewer ("if you have ever been the one who...").
`props`: `count` (9), `you` ("you"), `line` (a 6-word line shown when the figure steps out). `cues`: `row`, `you`, `out`, `ring`.
## timeline (D)
A staircase of up to three cards in order (not to scale), each with an optional year, a title, a sub line. For a short career or event sequence.
`props`: `steps` `[ { "year": "1962" | null, "title", "sub", "cueYear", "cueTitle" } ]`, `making` `{ "text", "cue" }` (a small "then..." line under the last card). `cues`: the names used. `text`: each year (`number`), title and sub (`label`). `sfx`: `tick` at each year with `contact: pop`, `step` 0, 2, 4 for rising pitch.
## slam (T)
Plain large text, one or two lines. Use for a statement, or when no honest picture exists. It is the fallback for everything.
`props`: `lines` `[ { "text", "cue", "size" (px, e.g. 64 to 150), "tone": "ink" | "muted" | "accent" | "good" } ]` (at most two). `cues`: the line cues. Keep a keyword line to 7 words or fewer. The text must be held at least `max(1.2 s, 0.35 s per word)`.
## route (M)
Two places and an arc between them, no map outline. For a move from one place to another with a reason. Left place dims ("stops working"), arc draws with a label, right place lights and grows.
`props`: `from`, `to`, `label` (the date on the arc), `stops`, `grows`. `cues`: `places`, `stops`, `leave`, `restart`, `grow`. `sfx`: `whoosh` at `leave`, contact `arc`.
## ladder (M)
The pivot ladder: a ladder with rung 1 filled, rung 2 dashed ("next"). First use defines it; reuse for every later pivot with the same look and the same `pivot` sound.
`props`: `mode` `"label"` (big "PIVOT 1" on the left; cues `rung`, `tick`, `label`) or `"worked"` (a factory squeezed by walls that open, rung turns green with a check, "it worked"; cues `city`, `tight`, `worked`), `rungText`, `label`, `worked`. `sfx`: `pivot` at `label` (contact `label`); `tick` at `worked` (contact `check`).
## photo (E)
A licensed photograph with a caption, a credit and a small icon column. `props`: `asset` (a Commons asset id), `caption`, `caption2`, `credit`, `clockLabel`, `city`. `cues`: `delhi`, `photo`, `clocks`, `swiss`. The caption must say what the photo is and, if it is a different organisation with the same name, say so. Needs a Commons asset: `{ "id": "swiss-building", "kind": "commons", "title": "File:...jpg", "width": 1500, "use": "..." }`; refused unless the licence is free.
## merge (M)
Two name tiles; one slides into the other and the name stays. `props`: `a`, `b`, `result`, `status` ("single-source" draws a dotted outline). `cues`: `tiles`, `buy`, `name`. `sfx`: `thud` at `buy`, contact `merge`. Draw no percentages unless the voice says them.
## ceiling (M)
A clock rises to a dashed ceiling line and stops; a dashed circle "something new" appears above. `props`: `clockLabel`, `newLabel`. `cues`: `clock`, `ceiling`, `stop`, `new`. `sfx`: `clock` with `role: texture`, `ticks: 4`, at `stop`, contact `sweep`.
## cocktail (M)
A bulb ("next idea") becomes a glass; a pin and a two-line place name. `props`: `idea`, `place` (two words per line work best: the first word is line 1, the rest line 2). `cues`: `idea`, `place`, `glass`. `sfx`: `clink` at `glass`, contact `glass`.
---
## Scene types added for beat B0 (the hook), in `render/scenes-b0.js`
All of these are cue-driven like the others. Use the B0 beat sheet (`research/the-fall-of-moser-baer/video/beat-sheet-B0.json`) as the example for each.
| Type | Use | Props | Cues |
|---|---|---|---|
| `disc` | A turning silver disc with label chips and a name settling on it (`mode: "turn"`), or a disc multiplying into a grid with a gold tint (`mode: "grid"`) | turn: `name`, `chips` `[ { text, cue } ]`; grid: none | turn: one per chip, `name`; grid: `grid`, `tint` |
| `black` | A declared black frame (a pause with a picture). Set `"black": true` on the scene so the black-frame check expects it | none | none |
| `plant` | A factory silhouette at night with windows (`windows: "dark"|"lit"`), a gate (`gate: "open"|"closing"|"closed"`), optional `date`, `place`, a notice (`maint`, `maintSource`), three day squares (`days: true`), an escape `arrow` ending at the gate, a faceless `figure` outside | those flags | `date`, `place`, `maint`, `days`, `third`, `close`, `arrow`, `figure`, `lit` |
| `workers` | A faceless worker in a plant, `tally` marks in groups of five, a label, a pay slip with a slice offered. Generic on purpose: draws no name | `tally`, `union`, `pay` | `worker`, `tally`, `union`, `slip`, `pay`, `slice` |
| `person` | A name card (name, role) beside a thing the person turns away from (`land`), with an arrow that turns away | `name`, `role`, `land` | `land`, `away` |
| `chips3` | Three cards with an icon (`glass`, `clock` with half filled, `medal`), a label and an optional sub line | `items` `[ { icon, label, sub, cue } ]` | the item cues |
| `dots` | Three big dots and a label for open questions | `count`, `label` | `dots` |
| `loop` | An exit arrow that bends into a loop (the way out becomes a trap) | `exit`, `trap` | `exit`, `loop`, `trap` |
| `pivotno` | A hollow first rung of the ladder and a running factory stamped NO | `label`, `no`, `running` | `rung`, `factory`, `no` |
**Waiving a numeral on purpose.** If the voice says a number that must not be drawn (for example a fact still marked `[V-search]`), put `"waive_numerals": { "17": "reason" }` on the scene. The lint then warns instead of failing, and the reason is printed. Use it only with the user's knowledge and tell them.
**A typeset quote on purpose.** Asset kind `quote`: `{ "id": "care2017", "kind": "quote", "url": "...", "outlet": "CARE Ratings", "published": "2017", "text": "... fragment ...", "highlights": ["fragment"] }`. The doc scene shows it typeset with the tag "(typeset quote)". Take the wording only from a source you have read or from a ledger row marked `[V]`, and say which in the ledger.
---
## Generic data and diagram scenes (`render/scenes-data.js`)
Added 2026-11-01 for the middle beats. Every string comes from `props` and every build from a `cue`, so no code is needed to use them. `node render/selftest-scenes.js` draws one working example of each (the examples live in `EXAMPLES` in that file; copy a type's example and change it). Number text (`role: number`) must be at least 70 px and is set that way by the scene; every numeral on screen must be spoken in the scene (lint H8), so show a number only on the word that says it.
| Type | Use | Props | Cues | Sounds |
|---|---|---|---|---|
| `stat` | One big figure with a label, an optional count-up and a source line. `status`: `solid` (verified), `dotted` (single source or derived), `ghost` (searched for, not found: red dashed) | `value` or `count {from,to,prefix,suffix,decimals}`, `label`, `sub`, `source`, `status` | `stat`, `label` | `tick` at `stat`, contact `pop` |
| `bars` | Up to five columns. A negative value hangs below the zero line in red. `ghost: true` draws a dashed red empty bar for "no record found". `gap {from,to,text,cue}` brackets a difference between two bars | `bars [ { label, text, unit, value, cue, tone, ghost } ]`, `gap`, `source` | one per bar and for the gap | `tick` per bar, contact `pop`; never more than 3 in 10 s |
| `line` | One or two series, points drawn one to the next, each with a value text and unit (x positions are by order, not to scale) | `series [ { label, tone, points [ { x, y, text, sub, cue } ] } ]`, `yMax`, `source` | one per point | `tick` at each point, contact `pop` |
| `photo2` | A licensed photo (Commons asset) with a caption, a two-line credit and notes on the right. Generic version of `photo` | `asset`, `caption`, `caption2`, `credit1`, `credit2`, `notes [ { text, cue, tone, number } ]` | `photo` plus the note cues | none |
| `chips` | A set of reasons or items as pills; `ghost: true` draws a dashed red pill (an item searched for and not found); `tone: "muted"|"accent"` to compare | `title`, `items [ { text, cue, tone, ghost } ]` (up to 6) | one per item (`title` optional) | `tick` per item (mind the density limit) |
| `race` | Lanes with a marker each: one already ahead, one at the start line. For "late to the race" | `track`, `lanes [ { label, pos 0..1, cue, tone } ]` | one per lane | none |
| `stalls` | Two stalls with price boards that change in turn, and a lane that fills with faceless figures ("nobody is richer") | `stalls [ { label, boards [ { text, cue } ] } ]` (two stalls), `fill { count, cue, text }` | every board cue, `fill` | `tick` when a board changes |
| `treadmill` | A runner on a belt; the belt speeds up at the speed cues; an outline appears and flickers dashed when the speed is high | `speeds [ { cue, v } ]`, `outline { cue }`, `label`, `labelCue` | the speed cues, the outline cue | none today (a belt hum is not built) |
| `unitgrid` | Icons that stand for a big count (`icon`: `disc`, `factory`, `person`), filling one by one, with a legend and a headline value | `icon`, `count`, `cols`, `legend`, `value`, `span`, `tone` | `grid`, `legend`, `value` | `tick` at `grid` |
| `stamps` | A row of identical stamps landing one after another | `count`, `text`, `title`, `labels`, `every` | `stamps`, `title` | `stamp` at `stamps`, contact `stamp` (one only) |
| `paths` | A fork: a start box and two options (`tone`, `glow`, `dashed`) | `from`, `options [ { text, cue, tone, glow, dashed } ]` (two) | `from`, one per option | `whoosh` at an option, contact `arc` |
| `rungs` | The generic pivot ladder: N rungs, each `filled` (tick), `glow` (the next one) or `hollow`; an optional big title | `count`, `rungs [ { state, cue, label } ]`, `title`, `titleCue`, `x` | each rung cue, `titleCue` | `pivot` at the title, contact `label`; `tick` at a rung, contact `check` |
**Examples (props and cues that draw correctly):**
- `stat`: props `{"value":"1,100","label":"crore, planned in 2000","sub":"when the country chased services","source":"Source: Business Today, 22 Jun 2003","status":"solid"}` cues `{"stat":0.3,"label":1}`
- `bars`: props `{"bars":[{"label":"FY07","text":"110","unit":"crore","value":110,"cue":"a"},{"label":"FY08","text":"79","unit":"crore loss","value":-79,"cue":"b"},{"label":"next","text":"no record","value":0,"ghost":true,"cue":"c"}],"source":"Source: Storage Newsletter"}` cues `{"a":0.3,"b":1.1,"c":1.9}`
- `line`: props `{"series":[{"label":"price","points":[{"x":"Oct 2002","y":28,"text":"28","sub":"cents","cue":"p1"},{"x":"later","y":14,"text":"14","sub":"cents","cue":"p2"}]}],"source":"Source: Taipei Times, 12 Oct 2002"}` cues `{"p1":0.3,"p2":1.5}`
- `chips`: props `{"title":"four reasons given","items":[{"text":"cheaper imports","cue":"a"},{"text":"weak demand","cue":"b"},{"text":"pen drives","cue":"c","tone":"muted"},{"text":"price","cue":"d","tone":"accent"},{"text":"a missing customer: not mentioned","cue":"e","ghost":true}]}` cues `{"a":0.3,"b":0.7,"c":1.1,"d":1.5,"e":2}`
- `race`: props `{"track":"CD-R","lanes":[{"label":"Taiwan","pos":0.7,"cue":"a","tone":"muted"},{"label":"India","pos":0,"cue":"b","tone":"accent"}]}` cues `{"a":0.2,"b":0.8}`
- `stalls`: props `{"stalls":[{"label":"stall A","boards":[{"text":"10","cue":"a1"},{"text":"8","cue":"a2"},{"text":"6","cue":"a3"}]},{"label":"stall B","boards":[{"text":"10","cue":"b1"},{"text":"9","cue":"b2"},{"text":"7","cue":"b3"}]}],"fill":{"count":10,"cue":"fill","text":"nobody is richer"}}` cues `{"a1":0.2,"b1":0.2,"a2":1,"b2":1.5,"a3":2,"b3":2.5,"fill":3}`
- `treadmill`: props `{"speeds":[{"cue":"s1","v":1},{"cue":"s2","v":2.2}],"outline":{"cue":"o"},"label":"running faster to stay in place","labelCue":"s1"}` cues `{"s1":0.2,"s2":2,"o":2.6}`
- `unitgrid`: props `{"icon":"disc","count":25,"cols":5,"legend":"1 icon = 100 million discs","value":"2.5 billion","span":2}` cues `{"grid":0.3,"legend":0.5,"value":2.2}`
- `stamps`: props `{"count":5,"text":"WON","title":"five cases in Europe","labels":["1","2","3","4","5"],"every":0.4}` cues `{"stamps":0.6,"title":0.1}`
- `paths`: props `{"from":"now","options":[{"text":"stay small","cue":"a","tone":"muted"},{"text":"pivot again","cue":"b","tone":"accent","glow":true}]}` cues `{"from":0.1,"a":0.7,"b":1.4}`
- `rungs`: props `{"count":4,"rungs":[{"state":"filled","cue":"r1","label":"floppy"},{"state":"filled","cue":"r2","label":"CD-R"},{"state":"glow","cue":"r3","label":"next"},{"state":"hollow"}],"title":"PIVOT 3","titleCue":"t"}` cues `{"r1":0.2,"r2":0.8,"r3":1.4,"t":1.4}`
**Charts and honesty.** Do not chart two figures the voice does not compare, or figures in different units. A bar or line gets a `source` line. A single-source or derived figure uses `status: "dotted"` on `stat` (or the dotted outline in a card); a figure that was searched for and not found uses the ghost form, never an invented number. Colour red is for loss, "alleged" and "not found" only.
---
## Palette and type (do not add colours)
One accent (gold), one "good" (teal), red only for loss, "alleged" or "not found" (unused so far), warm neutrals. Typeface Poppins. Minimum sizes as a share of 1080 px: numbers 6.5%, labels 3.5%, source lines 3%. Text stays inside the 5% / 7% safe area and out of the bottom 12%. The check script measures all of this on the rendered scenes, so a violation shows up as a `FAIL` row.
Shown as it is in the repository at build time (2026-11-03); the home directory appears as <home>. If this page and the file differ, the file is right. Back to the catalogue.