motion-lab

Motion graphics drawn entirely in code — one HTML file per piece, every frame deterministic, sound synthesized in the same file.

No timeline editor, no stock footage, no samples, no fonts to download. Each piece is a single index.html: open it and it plays in the browser; hand it to the renderer and it becomes an MP4, frame for frame and sample for sample the same every time.

Click a preview to open the piece live in your browser (click once more there for sound).

piecevertical
Motion Showreel — animated preview
01 · Motion Showreel 18 s
Eighteen seconds of kinetic type, shape morphs, a dive through a letter and a tunnel warp, cut to a synthesized 120 BPM beat.
open in browser · still · source
Motion Showreel — vertical cut, animated preview
9:16 · 17 s
open · still
BytePatterns Launch Film — animated preview
02 · BytePatterns Launch Film 24 s
A 24-second launch film: a paragraph turns into sorted bars, product cards play their own lessons, and 300 tiles light up.
open in browser · still · source
BytePatterns Launch Film — vertical cut, animated preview
9:16 · 18 s
open · still
A Short History of Sorting — animated preview
03 · A Short History of Sorting 30 s
Merge sort, Shellsort, quicksort, heapsort and Timsort, 1945 to 2002, each one running for real on a row of bars.
open in browser · still · source
A Short History of Sorting — vertical cut, animated preview
9:16 · 20 s
open · still
Fourier Epicycles — animated preview
04 · Fourier Epicycles 20 s
Spinning circles add up to a square wave as the series grows from 1 to 31 terms, and you hear the same sum.
open in browser · still · source
Fourier Epicycles — vertical cut, animated preview
9:16 · 15 s
open · still
Same Start, Different Fate — animated preview
05 · Same Start, Different Fate 24 s
Three double pendulums released 0.001 rad apart move as one, then split apart around the twelve-second mark.
open in browser · still · source
Same Start, Different Fate — vertical cut, animated preview
9:16 · 18 s
open · still
Murmuration — animated preview
06 · Murmuration 24 s
2,400 starlings follow three local rules at dusk, dodge a hawk and draw a letter in the sky.
open in browser · still · source
Murmuration — vertical cut, animated preview
9:16 · 17 s
open · still
Raymarch City — animated preview
07 · Raymarch City 20 s
A camera flight through a neon city that exists only as a distance function in one fragment shader.
open in browser · still · source
Raymarch City — vertical cut, animated preview
9:16 · 15 s
open · still
Neon Breakout — animated preview
08 · Neon Breakout 20 s
A neon Breakout level that plays itself: deterministic physics, multi-ball, a fire-ball and every brick cleared.
open in browser · still · source
Neon Breakout — vertical cut, animated preview
9:16 · 18 s
open · still
Sorting Race — animated preview
09 · Sorting Race 28 s
Eight sorting algorithms sort the same 64 numbers side by side, paced by their real comparison counts.
open in browser · still · source
Sorting Race — vertical cut, animated preview
9:16 · 18 s
open · still
Signal — animated preview
10 · Signal 20 s
A documentary title sequence: one trace, a pulse that keeps quickening, then silence and the title.
open in browser · still · source
Signal — vertical cut, animated preview
9:16 · 15 s
open · still

Quick start

git clone https://github.com/bytepatterns/motion-lab.git
cd motion-lab
npm i                               # Playwright + its headless Chromium
node tools/render.mjs 01-showreel   # -> out/01-showreel.mp4 (1920x1080, 30 fps, AAC)

Or skip the install and just open projects/01-showreel/index.html in a browser.

Requirements: Node 20+ and ffmpeg + ffprobe on your PATH.

Command What it does
node tools/render.mjs NN-slug draws every frame headless, pipes the PNGs into ffmpeg (h264, yuv420p, crf 18), muxes the synthesized soundtrack → out/NN-slug.mp4
node tools/still.mjs NN-slug [frame ...] poster frame → out/NN-slug.png; with frame numbers → out/stills/
node tools/gif.mjs NN-slug a 6-8 s, 960 px, ≤ 3 MB loop with a two-pass palette → out/NN-slug.gif
node tools/verify.mjs NN-slug checks the piece and its MP4 against the contract, exits 1 on failure
node tools/all.mjs [NN-slug ...] render + still + gif + verify for every piece, then rebuilds this gallery
node tools/build-readme.mjs rebuilds the gallery above from projects/*/index.html

render.mjs takes --workers N (parallel pages), --range a:b (a quick preview of a few frames) and --keep-frames (also write the PNGs to out/frames/). Every tool takes --variant vertical for a piece’s 9:16 cut.

Vertical cuts for Reels/Shorts/TikTok

Every piece also has a 1080×1920, 12–20 s cut, recomposed from the same scene code with all type inside the area no platform covers, a hook on the first frame and a closing card — it is the right-hand column of the gallery, and ?variant=vertical plays it in the browser. node tools/all.mjs --variant vertical renders them to out/NN-slug.vertical.mp4, and verify checks the safe area on top of the usual contract (see CONTRACT.md).

How a piece works

Every piece exposes one object, and the tools use nothing else:

window.__motion = {
  title, description,           // shown in the gallery
  fps: 30, width: 1920, height: 1080,
  frames: 540,                  // 15-30 s
  seek(i) {},                   // draws frame i: synchronous, a pure function of i
  async audio() {},             // the whole soundtrack as one AudioBuffer (OfflineAudioContext, 48 kHz stereo)
};

The full rules — canvas, fonts, determinism, audio format, what verify checks — are in CONTRACT.md.

What verify checks

Group Checks
file one self-contained file: no external src / href, no CSS imports, has a <title>
page loads without errors and without a single network request; __motion has 30 fps, 1920×1080, 450-900 frames
frames random frames are not empty, are pixel-identical when re-drawn out of order, and seek never calls Math.random, Date.now or performance.now
audio 2 channels, 48 kHz, exactly frames / fps seconds, not silent, no clipping
mp4 h264 1920×1080 yuv420p, duration within 0.1 s, audio stream present and audible
luma every frame of the MP4 decoded: none near-black, none a flat colour
safe vertical cuts only: on 7 frames nothing brighter than luma 70 outside x 60..900, y 230..1400

Add your own piece

  1. cp -r template projects/11-my-piece (the folder name is NN-slug, and the file must stay index.html).
  2. Replace draw(f) and renderAudio() with your own; set title, description, frames.
  3. Open it in a browser while you work. ?frame=240 freezes on a frame, Space pauses.
  4. node tools/all.mjs 11-my-piece — then look at a few frames with node tools/still.mjs 11-my-piece 30 240 480.

If verify fails, the piece is fixed — never the tool.

Project layout

projects/NN-slug/index.html   one piece = one file
template/index.html           starter piece with the player block and helpers
tools/                        render, still, gif, verify, all, build-readme (+ lib.mjs)
out/                          GIFs and posters (also *.vertical.*) are committed; MP4s and frames are not
CONTRACT.md                   the rules every piece follows

More from BytePatterns

Animated algorithm lessons, free and without an account: bytepatterns.com · the reel engine: code-motion

License

MIT. The soundtracks are synthesized by the code in each file, so they carry no third-party licence.