Making a video
Don't screen-record. A recording stutters whenever the box is busy, and nobody can re-run it after one fix. Write the film as a script instead. The kit runs the page on a virtual clock and takes one frame per 1/30 s of page time, so every animation, timer, scroll and typed key lands on exact frames. Camera moves, the cursor and click rings are drawn over the frames afterwards. The same script always makes the same film.
The loop
video new DIRwrites a starterDIR/film.mjs. Point it at a throwaway server holding made-up data, never at a real account. Write three to five beats with one idea each.heavy timeout 1800 video render DIR/film.mjs OUT/films, encodes each beat and stitches the beats into the MP4. The first render runsvideo checkfirst. A render fails if any word on the deny list appears on screen.video sheet OUT/film.mp4 OUT/sheet.png 16, then look at the sheet. Most bugs only show up here: a list that never scrolled, an empty panel, the camera on the wrong thing.video stutters OUT/film.mp4must report 0 stutters. Jerks are fast or popping motion. A jerk is fine where content is meant to cut (a page switch, a message arriving);--allow A-Bskips those spans.video mp4 IN OUT.mp4makes the web MP4 (H.264, faststart).video gif IN OUT.gif --max 3steps the width down from 960 px until the GIF fits under 3 MB.- Iterate on timings in the script, never by trimming the video.
The pace and the full director API are in kit/KIT.md; read it before your first film. Briefly: about 20 s in
total; the thing working within 2 s; one camera move a beat (1.5-2x, 0.4-0.7 s); hold a result at least 0.8 s
(d.show); type at about 30 keys a second.
What makes it smooth (learned the hard way)
- Put film-only polish in an init script, not in the app. Make new items ease in
(
animation: in .4s cubic-bezier(.16,1,.3,1)from opacity 0 and translateY 12px) instead of popping. For a list that jumps to its end, wrapElement.prototype.scrollTop's setter so it glides there over about 500 ms. The hive-stars demo film is a worked example with both. - Drive your own motion with
setTimeout. The film's clock runs timers exactly. requestAnimationFrame callbacks may not fire while the clock is paused. - Look up the page's timeline. If the app replies on timers (canned replies at 0.7 s, 1.8 s, and so on), size
each beat's
holdto them, so that a beat ends just after the thing it shows. zoomand viewport units. The director'szoom: 1.5films a 1280x720 layout at 1920x1080. Chrome doesn't dividevh/dvh/vwby root zoom, so the kit rewrites those units in same-origin stylesheets. Without that, acalc(100dvh - x)layout is 1.5x too tall and its scroller never scrolls. Cross-origin CSS isn't rewritten, so serve the page yourself.- Apps that patch
Date. The kit reads the clock through its own savedDate, so a page that wrapsDate(a time skew, a fake "now") can't makepauseAtgo backwards. - The deny list: host name, user, git name and email, real project names. Fake data only.
3D shots (Blender)
video blender SHOT.py OUT/ [--quality preview|final] renders a shot script on the CPU. Run it under your job limiter (heavy on H2),
and run --quality preview first, because a final render takes minutes per second of film. The helpers (easing,
camera with depth of field, light rig, a PCB from KiCad, GDS extrusion, a UI plane, node graphs) are in
kit/blender/README.md. Encode the frames with the kit's encodeFrames or ffmpeg, then cut them in with
stitch.
Terminals
kit/term.mjs records a real tmux/bash session in a sandbox with a made-up home and host, then replays it with
xterm.js on the film clock. See kit/KIT.md.