AVERSE STICKMAN KIT (FREE) — HOW TO WRITE AN EPISODE'S SCENES
Read all of this before writing scenes.js. episodes/example is a complete, working episode to copy from.

An episode is two files in episodes/<name>/:
  script.json  the words (tools/audio.mjs reads them aloud for free, times them and makes the sound)
  scenes.js    the pictures: one call to EPISODE({...}) with a list of shots
Every shot starts on a line of the script and lasts until the next shot starts (or the next day card).

==================================================================
1. script.json
==================================================================
{ "title": "Zombie Outbreak: 30 Days Trapped on the 30th Floor",
  "voice": "en-US-AndrewMultilingualNeural",      (node tools/audio.mjs --voices en-US lists voices; Arabic: ar-SA-HamedNeural)
  "rate": "+0%",  "music": "tense" | "calm" | "none",  "outro": 4  (seconds for the end card; leave out for none),
  "lines": [
    { "id": "d00_1", "day": 0, "chapter": "The Alert", "time": "9:40 PM", "text": "...", "sfx": "alarm" },
    { "id": "d00_2", "text": "..." },
    { "id": "d01_1", "day": 1, "chapter": "Counting Food", "time": "7:10 AM", "text": "..." },
    { "id": "d01_4", "time": "10:30 PM", "text": "...", "sfx": [["hit", 1.6, 1]] } ] }
- A line with "day" starts a new day: a day card plays before it and the day display changes. "time" sets the clock.
- Optional per line: "sfx" = a name, or [[name, seconds into the line, volume], ...]; "pause" (seconds after it, default 0.32);
  "wait" (seconds before it); "voice" / "rate" / "pitch" (another speaker); "fx": "radio" (a radio or phone voice).
- Sounds: hit boom whoosh knock bang glass heartbeat alarm beep static groan footsteps rumble drip door thunder
- One or two short sentences per line, second person, present tense, about 140 words a minute.
- After any change: node tools/audio.mjs <name>  (unchanged lines are reused), then Reload the preview.

==================================================================
2. scenes.js
==================================================================
EPISODE({
  band: "ZOMBIE OUTBREAK · 30TH FLOOR",           the label top right (the series and the place)
  days: 30,                                         shows a "Day N of 30" bar (leave out for none)
  hero: { hat: "beanie", color: "#e2582c" },        the hero's signature look: keep it the same in every episode
  end: { title: "TO BE CONTINUED", next: "DAY 30 → DAY 60" },   the end card (needs "outro" in script.json)
  hud: true,                                        false hides the day and clock everywhere
  shots: [ ... ],
  thumb: { left: {a shot}, right: {a shot}, labels: ["DAY 1", "DAY 30"], title: "" },   the thumbnail (or full: {a shot})
});

A shot:
{ line: "d03_2",                 starts with this line (cuts in 0.12 s before it), or
  at: ["d03_2", 1.8],            starts 1.8 s into that line (a second cut inside one line)
  place: "office"  or  { name: "office", chaos: true, ... }        (section 4)
  light: "day" | "dusk" | "night" | "dark" | "red"  or  ["day", "night"] (it changes during the shot)   (section 5)
  cam: "slow" (default) | "still" | "push" | "pull" | "close" | "face" | "left" | "right" | "shake" | "wide"
       or { zoom: 2 or [from, to], at: [x, y] or [[x0, y0], [x1, y1]] or "hero" or "all", shake: 0.5, ease: "linear" }
  focus: "auto" (default: the hero, if the shot draws one) | "all" (everyone drawn) | [x, y]
  draw: (k, t, len) => { ... }      people and props. k = 0..1 through the shot, t = seconds into it, len = its length
  overlay: (k, t, len) => { ... }   on-screen cards over the picture (section 8)
  caption: "TWO WEEKS"  or  [["DON'T MAKE", 1.2, 2.2], ["A SOUND", 2.2, 3.6]]   (text, from s, to s; never overlapping)
  capAt: [960, 930], capColor: "#ffd45c", capSize: 112, capTilt: -0.025
  hud: false, flash: true (a white flash in), fade: true (in from black), shake: 0.5, grade: false }

The camera finds the hero by itself: "slow" frames them head to toe, "close" head to waist, "face" a close-up of the face,
"push"/"pull" move in/out, "left"/"right" drift, "shake" for impacts, "wide" shows the whole place. Use focus: "all" to frame a
group, or focus: [x, y] to aim at a thing (a door, a window). Shots without people slowly push in on the middle.

==================================================================
3. THE WORLD
==================================================================
1920 x 1080. People stand with their feet at y 975 (the front, size 1) down to y 815 against the back wall (size 0.68);
depthScale(y) gives the size for any y (FRONT = 975, BACK = 815). A standing person of size 1 is about 280 px tall: the head's
centre is about y - 235. Draw people further back first, the closest last.
SCENE.floor (where people stand by default), SCENE.seat (the place's seat, for pose "sit"), SCENE.desks (desk tops in the office).

==================================================================
4. PLACES   place: "name"  or  { name, options }
==================================================================
apartment  door 200 (doorOpen 0..1, barricade 0..1, knock: 1 = it shakes), wall clock 610 (shows the clock), lamp 880 (lamp: true/
           false), window 1000-1380 (chaos, boards 0..1, outside(x, y, w, h, ground)), fridge 1520 (fridge: 0..1 open, fridgeEmpty),
           kitchen 1600-1920, couch at the front x 760 (sit on SCENE.seat; couch: false removes it), tv: true or "BREAKING NEWS"
office     elevator 110, stairwell door with EXIT 310 (doorOpen, barricade, knock), glass wall 420-1920 (chaos, boards), cooler
           560, desks 860 and 1460 (desks: false), a chair at 990 facing left (SCENE.seat), plant 1840, lights: false
store      shelves 210 / 610 / 1010 (stock 0..1), aisle: "AISLE 4", checkout 1390, glass front 1600-1900 (outside(...) draws who's
           out there, cracked, boards, closedSign), cart: x (a trolley)
subway     tunnel 0-340, signal 380 (signal: "green"), bench 720-1000 (SCENE.seat), stairs up from 1520, station: "NAME",
           train: 0..1 (pulls in); people stand at y 950, the tracks are below the yellow line
school     lockers 80-880 (lockerColor), noticeboard 960-1160, clock, classroom door 1300 (doorOpen, knock, barricade), windows
           1500-1860
bunker     supply shelf 300 (supplies 0..1), bunk bed 705-1095 (the lower bunk is SCENE.seat), radio table 1400, ladder to the
           hatch 1760, hanging bulb (flicker: true, lights: false), generator: true
rooftop    the city all round (chaos, heli: false), the ledge y 700-770, stair hut door 270 (doorOpen, barricade, knock), water
           tank 1570, AC unit 920, antenna 1230, fire: true (a barrel fire at the front)
street     buildings, the shop (shop: "MARKET") and its door 1170, the sidewalk y 780-860, the road (people at y 1000), abandoned
           cars 560 and 1560 (cars: false), street lamps 330 and 1480 (lampsOn)
city       the whole city, no people: smoke, fires, helicopters, a jammed highway (chaos: false = a calm city)
black      a dark backdrop (color) for phone screens, cards and inserts
wall       a plain wall and floor (color, floorColor) for anything else
Every window takes outside: (x, y, w, h, ground) => horde({...}) to show zombies outside (draw them inside that box).

==================================================================
5. LIGHT
==================================================================
day, dusk, night (lamps glow; people get a soft moonlight rim), dark (the power is out: you see only what lamps, windows,
flashlights and fires light), red (an emergency: dark with a slow red pulse). ["day", "night"] changes during the shot (a time-lapse).
A flashlight in a hand (hold: "flashlight") throws a beam along the arm; beamAng sets its angle (radians: 0 = right, Math.PI = left).
light(x, y, radius, "255,210,150", strength) and beam(x, y, angle, length, spread) add your own lights; hangBulb, lamp, tv, fire,
candle and lantern light themselves.

==================================================================
6. PEOPLE
==================================================================
hero({...})   the episode's hero (EPISODE hero look);  person({...}) anyone else;  zombie({...}) the infected.
  x, y (feet; default the place's floor), s (size; use depthScale(y) further back), face: 1 right | -1 left | 0 toward the camera
  pose   stand walk run sneak sit sit-scared floor crouch cower surrender point reach push carry look think wave cheer fall drink
         type knock listen hold-up swing lie sleep dead  |  zombies: idle (default) shamble bang lunge lie dead
         facing the camera (face 0): stand surrender cheer scared shrug wave point hold-up sit
  arms   read phone point reach up hold carry cross down wave face hips push torch  (on top of any pose: pose "sit" + arms "read")
  mood   calm happy sad scared shock angry determined tired worried sleep talk   (zombies keep their own face)
  hat    beanie cap cap-back headband headlamp helmet hair hair-long pony hood bandana none   (color: its colour; pom: false)
  pack: "#3e5873" (a backpack), scarf: "#c0392b", glasses: true, skin: "#e8c9a8"
  hold / hold2 (near / far hand)  flashlight phone radio bat pipe crowbar plank axe hammer bottle can bag map note keys lantern
         candle medkit binoculars battery     on: false (a light switched off), itemAng (the angle of a bat, pipe...)
  lookAt: [dx, dy] (-1..1, where the eyes look), seat (the seat's y for sit), lean, tilt, alpha (0..1), seed (varies zombies)
Walking without sliding feet:
  walking(o, fromX, toX, k0 = 0, k1 = 1)  walks between those x during that part of the shot, stands before and after
                                          (o.then = the pose after; o.pose: "run" | "sneak" | "shamble" for other gaits)
      e.g. hero(walking({ mood: "tired" }, 400, 900, 0, 0.8))
  strolling(o, x0, speed)                 keeps moving at speed px/s (someone crossing the frame)
  horde({ n, x0, x1, y0, y1, speed, face, pose, seed, s })   a crowd of zombies with depth (speed 0 = swaying in place)
Every call returns { x, y, head: [x, y], hand: [x, y], hand2, top, r }: use hand to put a prop exactly in a hand.

==================================================================
7. PROPS   (x = centre, y = the floor under them, s = size; against the back wall use s ≈ 0.68)
==================================================================
door(x, y, { s, open 0..1, barricade 0..1, shake: 1, sign: "TEXT", color, beyond(x, y, w, h) })   barricade(x, y, w, h, s, 0..1)
windowFrame(x, y, w, h, { boards, cracked, curtain, chaos, street, outside(...) })   (x, y = the top left corner)
table(x, y, { w, h }) → top   chair(x, y, { face }) → seat   couch(x, y) → seat   bed(x, y) → top   desk(x, y) → top
shelf(x, y, { w, h, rows, fill })   crates(x, y, { n })   pile(x, y, { cans, bottles, bars, batteries, med })   (a supply stash)
fridge(x, y, { open, empty })   lamp(x, y, { on })   hangBulb(x, y, { flicker })   tv(x, y, { text, on })   vending(x, y, { fill })
car(x, y, { face, color, lights, s })   fire(x, y)   bathtub(x, y, { water 0..1 })   ladder(x, y, height)   locker(x, y, { n })
generator(x, y, { on })   sandbags(x, y, { n })   sign(x, y, "TEXT")   blood(x, y, s)
label("TEXT", x, y, size, { fill, font }) writes text in the world (a sign, a name on a door).

==================================================================
8. ON-SCREEN CARDS (overlay)
==================================================================
tally([["food", 12], ["water", 9, "L"], ["battery", 4]], k)   counts up; icons: food can water bottle battery med days zombie people
alertCard("EMERGENCY ALERT", "Stay indoors. Lock your doors.", k)     a phone alert
bigText("BUT THEN...", k)     note("DON'T OPEN", x, y)     wrapText(text, x, y, maxWidth, size, fill)

==================================================================
9. HELPERS
==================================================================
lerp(a, b, k), ease(k), easeOut(k), easeIn(k), span(k, a, b) (0..1 between a and b), wave(t, hz), clamp(x, a, b), rng(seed),
SCENE.t (seconds into the shot), SCENE.k, Math.PI. Errors show as a red box on the frame with the shot's number; fix and Reload.

==================================================================
10. WHAT MAKES IT LOOK HUMAN-MADE (check every contact sheet against this)
==================================================================
- It makes sense: people carry only what the moment needs; something kept between shots lives somewhere (on a table, in a pack)
  and doesn't appear or vanish between two shots of the same moment.
- People stand on floors and sit on seats (SCENE.seat, a chair's or couch's seat); never inside furniture, never overlapping a
  door, a table or each other. Further back = smaller (depthScale) and drawn first.
- Feet never slide: anyone moving uses walking(), strolling() or horde's speed — never a changing x with a fixed pose.
- Hands touch what they hold, push or open: put the person at arm's length (about 2.2 x 40 x s px from the shoulder).
- Time only moves forward. A new day = a line with "day" in script.json; nobody is outside after a curfew the story set.
- Vary the shots with intent: a wide to set the place, push-ins on decisions, close/face on fear, a cutaway to the threat, a held
  wide after a shock. Cut every 1-4 seconds, but let the big lines breathe.
- Captions: one to three words, only the punch of a line (a number, a rule, a shock). Not every shot.
- Keep the look: don't restyle the people or places; change the story, the places, the props and the light.
