Chalkboard — Style Prompt

A real classroom blackboard on which chalk writes itself, stroke by stroke in true stroke order, with no hand, no chalk stick and no eraser in sight. One idea is taught the way a teacher does it: write what people think, wipe it with one pass, write what is true in the same place. References (grammar only): a primary-school literacy lesson on a sliding double blackboard; the way a teacher underlines the parts of a character, circles one component and wipes a wrong line. Take the order of a lesson, not any teacher's handwriting.

Suits short lessons on one idea: where a word or a character comes from, a misconception set right, a worked problem. The length follows how many steps the lesson has.

1. Essence, and what it is not

Not Whiteboard Explainer: that style is a bright glossy board, coloured markers, floating marker props and magnets, ghost marks of old lessons, and a camera travelling across a knowledge map of many regions to explain how something works. Chalkboard is a dark matte slate with nothing floating in front of it, kept clean, one question per board, the camera facing it flat; it advances by writing a claim, wiping it and rewriting in place, and it lives in a classroom (panels that slide, grids painted on, a tray, a wall). Not a slide either: nothing is typeset, nothing fades in.

2. Materials & rendering

3. Colour logic

4. Type & subtitles

Voice and subtitles

5. Motion quality

6. Camera grammar

The camera always faces the board flat on (no perspective tilt).

Move What it expresses Can serve
Close shot on the writing attention on how a character or line is built a component; a step of a proof; a spelling
Slow lateral pan along a line time or sequence a timeline; a derivation; a list of cases
Pull back to a fixed medium frame the whole question at once a comparison; a claim and its counter-claim
Same frame later judgement a correction; a before / after
Locked frame while a panel slides a page turned in the room's own way a new chapter; a hidden board
Whip with motion blur (≤ 0.5 s) a jump to another part of the wall a change of board
Cut on a line crossing a seam a connection cause and effect; the same part in two places
Wide shot of the whole wall summary the map of what was learnt

The room is part of the style: at least once the frame opens wide enough to read board, frame, tray and wall as a classroom (where, and what else the room holds, is the film's choice). Close shots may show only slate and frame.

Rules: the subject is never smaller than a third of the frame in close shots; text blocks are never cut by the frame edge while they are read; no empty board for more than half a second. Transitions are hard cuts, a sliding panel or a line across a seam; no dissolves or page flips.

7. Sound palette

8. Native moves

9. Pitfalls of the medium

10. Engine

The demo's code is a working engine; copy it into the project folder and write the film yourself.

Copy as is: js/chalk.js (Stroke, bake, drawStroke: the chalk), js/hand.js (writeChar(ch, cx, cy, size, clock, o), writeLine for mixed Chinese and Latin, writeLatin, latinWidth, loadSvgFont; each returns Stroke[] with t0/t1 set and .end = the time the last stroke ends), js/r2d.js (grain tile, deposit), js/stage.js (slates, frame, tray, clock, wall, paintGrid; world units: slate x 0…6400, y 0…1700, panel width PW 3200), js/post.js, js/lab.js, tools/foley_lib.py, tools/subset.py (subtitle font cut from the strings in SUB in film.js), tools/srt.py. js/props.js too, minus the glow (§9).

Adapt: js/hz.js (keep its characters, add yours), index.html (title), js/app.js (the compositor: it draws the layers, wet streak, wipe, light, subtitle plate and exposes render, TEXTS, DUR, EV; edit it to the film's board instead of feeding it placeholder data).

Write yourself: js/film.js (every stroke, time, camera key, voice start and subtitle, as data), the voice lines, the score and mix (audio.py is the demo's own score; its chalk-grain part is reusable), build.sh. js/ancient.js is the demo's old forms of one character: not an engine file.

What app.js reads from build(W, H) in film.js:

Field What it is
S every Stroke, each with layer: 'FL' (the sliding front panel, in panel-local units, moved by slideX), 'FR' (the right panel, world units), 'BK' (the back board, drawn only once the panel has moved), 'WORLD' (on top, world units, drawn while t < T.back0); FL strokes also carry phase ('pre' = can be wiped, wiped: true = removed by the wipe; 'post' = written after it). o.R sets bake resolution (default 2)
DUR length in seconds
camAt(t) {cx, cy, s, shot}: centre in world units, scale for a 1920-wide frame, shot index (motion blur is only taken within one shot)
slideX(t) the front panel's offset; () => 0 if it never moves
TX board text for readcheck: {id, text, layer, box:[x0,y0,x1,y1] in that layer's units, t0, t1}
SUBS subtitle cues {id, t0, t1, zh, en}
EV sound events for core/render/events.mjs: vo (voice starts), sub (for the .srt), chalk (t, d, len, color, pan, big, one per stroke) and any of your own
G1, G2, SMALL, T.back0, wet, wipe painted grids on the front panel and back board; when the closing slide starts; the wet streak {x0, y0, y1, xEnd, bx0, t0, t1}; the wipe {t0, t1, w, path}

A film without a wet streak, a wipe, grids or a sliding panel removes those parts from its copy of app.js (or guards them, e.g. if (!F.wet) return at the top of drawWet, if (!F.wipe) return null in drawWipeHaze), and draws WORLD strokes on a condition of its own if there is no closing slide. Don't pass dummy fields to keep the compositor quiet.

New characters. hz.js maps a character to its strokes in standard stroke order (GF 0023-2020); each stroke is "type x,y x,y …": the centre line through its anchor points in a 100 × 100 box, y down. Types: h 橫, s 豎, p 撇, n 捺, d 點, t 提, x 懸針豎; z… for turns (corners pressed); a last letter g ends in a hook (its last point is the hook's tip), p / t in a point, d in a dot. Write only the centre line: the slight bow, the rise of horizontals and the pressed entry tick are added by writeChar. Symbols (arrows, plus, a tick) are written the same way. A way that works:

  1. Take the stroke order and count from a dictionary that shows the standard order, and write down the type sequence first.
  2. Set the character large from the reference kai (LXGW WenKai, fonts/) on a 100-unit grid, placed as the check page places it so the numbers line up: e.g. with PIL, ImageFont.truetype(font, 1000) drawn into a 1000 × 1000 image at (0, 880) with anchor='ls' (left edge, baseline at 0.88 of the box), with 100-px grid lines; read each stroke's start, corners and end off it and divide by 10. Keep the anchors on the middle of the font's stroke, not its edge.
  3. Open index.html?lab=hz&chars=<new characters>&size=400: the chalk is drawn over the font in red. Check stroke count and order, which strokes touch or cross and which stop short, the proportion of each component, hook directions and the 撇/捺 feet; then look again at the size the film uses for it. Fix the numbers, not the font.
  4. Read it at film size the way a native reader would, before shooting (§9): a structure that looks right at 400 px over the font can still read as another character on its own.

Sound. Three CC0 / CC BY recordings of chalk on a board from Wikimedia Commons (URLs in the demo's build.sh, credits in CREDITS) are cut into tagged grains with python -I tools/foley_lib.py <out_dir> <name>=<file> …: tap, stroke and squeak grains plus index.json with each grain's length, attack and brightness. The demo's audio.py places one grain chain per chalk event (length from d, level from len, the accent chalk low-passed); other recordings can be cut the same way.

Render. node core/render/events.mjs <film> (events from the page), your mix, node core/render/video.mjs <film> --fps 30, sh core/render/mux.sh <video> <mix> <out> 30 1, node core/render/readcheck.mjs <film>. The page bakes every stroke on load (about 10 s for 500 strokes), then 20–50 ms per 1080p frame. readcheck counts from the first frame a block's box is on screen, which is when its writing starts: leave the reading time after the last stroke yourself (§4).

11. Variation space

The agent decides the topic, the board layout, the number of panels, the tracked component, the hands, the camera path and the pacing. - Structures: a proof built on one board line by line; three wrong answers wiped one after another before the right one; two boards in dialogue, question on one, answer on the other. - Openings: a duster's last pass revealing a formula; a close-up of the tray as one chalk starts writing; the bell ringing over an empty, freshly wiped board. - Endings: the whole wall with the lesson complete; a final underline under one word; the board half-wiped for the next class with one line kept.


How our demo was made (story, shots, score, end card, build): DEMO.md. Read it after your treatment exists.