Algorithm explainer reels drawn entirely in code — 9:16, safe-zone verified, sound synced to the algorithm's own steps.
| Bubble sort | Binary search | Breadth-first search |
|---|---|---|
![]() |
![]() |
![]() |
Every frame is React, every sound is synthesised.
The sound is synthesised from raw PCM and every number in the hook is measured from a real run of the algorithm. No stock footage, no samples, no timeline editor — and two gates that fail the build if a label drifts under the platform UI or a sound misses its step.
This is the engine behind the @bytepatterns reels, trimmed to three example scenes and opened up as a template. Built on Remotion.
git clone https://github.com/bytepatterns/code-motion.git
cd code-motion
npm install # also synthesises the sound banks (npm run sfx)
npm run render:bubble-sort # -> out/bubble-sort.mp4
npm run check # safe-zone + sound gates over everything in out/
npm run studio # live preview in the browserRequirements: Node 22.18+ (the scripts import the TypeScript sources directly through Node's type stripping — no build step), and ffmpeg + ffprobe on your PATH for the gates. Remotion downloads a headless Chromium on the first render.
| Command | What it does |
|---|---|
npm run render:all |
renders the three example scenes to out/ |
npm run check |
runs verify-layout + verify-sound on every rendered scene, exits non-zero on failure |
npm run stills |
writes the middle frame of every scene to docs/stills/ |
npm run sfx |
rebuilds the five synthesised sound banks into public/sfx/ |
npm run render:preview:<style> |
the same bubble sort in each sound style, for A/B listening |
npm run typecheck |
tsc --noEmit |
| Scene | Composition | Hook | What it shows | Full lesson |
|---|---|---|---|---|
| Bubble sort | BubbleSortBars |
"Can you sort this in 12 swaps?" | 8 bars, pitch-mapped compares and swaps, each pass locking the right end | bytepatterns.com |
| Binary search | BinarySearch |
"1,000,000 items. Found in 20 steps." | a range bar that halves and zooms, a 20-step ladder | bytepatterns.com |
| Breadth-first search | BreadthFirstSearch |
"BFS doesn't search. It spreads." | a 12-node graph laid out by computed distance, the live queue | bytepatterns.com |
The numbers in those hooks are not typed in: the scene runs the algorithm at module load and the hook, the chips, the counters and the code panel's closing comment all read the result. Change the input and every number on screen changes with it.
Reels, TikTok and Shorts all draw their UI on top of your video. The intersection of the three is the only area you can trust:
1080 x 1920
┌──────────────────────────────┐
│ top 230: camera, title │
│ ┌───────────────────────┐ │
│ │ hook band 232-502 │ │
│ │ stage card 514-1092 │ ← │ right 180: like / comment / share rail
│ │ code panel 1102-1340 │ │
│ │ watermark 1350-1394 │ │
│ └───────────────────────┘ │
│ bottom 520: caption, audio │
└──────────────────────────────┘
left 60 safe = x 60..900, y 230..1400
Every text, badge, bar, label, counter, code line and the watermark is placed from the tokens in src/theme.ts (SAFE, ZONE, STAGE). The grid texture and vignettes bleed to the edges on purpose, so the frame never looks letterboxed behind the platform chrome.
scripts/verify-layout.mjs checks every frame, not a screenshot. It streams the whole MP4 through ffmpeg as raw RGB, treats any run of 3+ pixels brighter than luma 70 as content, tracks the worst-case bounding box across the clip and exits 1 if a single content pixel ever leaves x 60..900, y 230..1400. A hook that is one character too long fails the build.
Sound is scheduled from the same step list that drives the animation, so it cannot drift from the picture:
| Event | Meaning | Example |
|---|---|---|
compare |
a read where nothing moves | two bars compared, a mid checked, a node dequeued |
swap |
something physically moves | bars exchange, half the range leaves, a ripple crosses an edge |
lock |
something is now settled | a slot is final, a BFS layer is done, a digit drops off "items left" |
final |
the closing chime | once, after the last step |
The bar sort is pitch-mapped: each value's rank picks a note on a pentatonic scale, so a pass literally plays the array. A shared scheduler (finalizeSfx) then rate-limits compares, drops a compare that is answered by the swap right after it, ducks anything that lands on an accent and applies the per-scene trim.
Five synthesised styles ship: musical, tactile, arcade (dense, −20..−14 LUFS) and calm, calm-pad (sparse, dark, −23..−20 LUFS, −8 dBFS true-peak ceiling). The examples render with calm; switch every scene at once with DEFAULT_STYLE in src/audio/sfxEvents.ts. There is no background music by design — trending audio is attached inside the platform at upload time.
scripts/verify-sound.mjs is the second gate. Per rendered MP4 it checks: an audio stream exists and the duration matches the timeline; integrated loudness (EBU R128) is inside the style's band and the true peak under its ceiling; the render is in sync with an offline premix (±3 frames); every event that is meant to be audible produces an onset within ±2 frames of its scheduled frame; no sample clips; the middle of the track carries level, not just the finale.
src/theme.ts is the only file to edit to re-skin every scene:
COLOR— canvas, surfaces, text ramp,primary(what to look at now),secondary(done / locked / progress),warning,error.WATERMARK— your handle (text), a short glyph (mark), opacity. Default:</> code-motion.TYPE— the type scale. Keep the hook at 72 px and value labels at 44 px or more; that is the watch-distance legibility floor.- Fonts are loaded in
src/fonts.ts(Manrope + IBM Plex Mono via@remotion/google-fonts).
One rule the gate enforces: the canvas and every decorative colour must stay at or under luma 70, and anything a viewer must read must sit above it. Scenes are dark by design.
- Install —
git clone https://github.com/bytepatterns/code-motion.git && cd code-motion && npm install - Open the live preview —
npm run studio, then pickBubbleSortBarsand press play. - Change the input, watch every number follow — edit
VALUESinsrc/algorithms/spec.tsand save. The hook, the swap counter, the chips and the length of the clip update, because they are read from the run.
When you like it, npm run render:bfs && npm run check -- bfs renders the shortest scene to out/bfs.mp4 and runs both gates on it — exactly what CI runs on every push (a few minutes on a laptop).
A scene is two files: a React-free scene module that runs the algorithm and lays out the clock, the code, the hook and the sound events, and a composition that draws one frame inside <ReelFrame>. Register it in two places, render, run the gates.
The whole contract — steps → events → code panel → hook rules → gates — is one file: SCENE-SPEC.md.
The contract is a single file and the definition of done is a set of commands that exit non-zero, which is exactly the shape any AI coding agent works well with:
- Give the agent
prompts/new-scene.mdwith the algorithm, the hook and the code you want filled in. - It reads
SCENE-SPEC.md(AGENTS.mdpoints there too), writes the scene module and the composition, registers them, renders, and runsnpm run checkuntil both gates pass. - It renders the middle frame with
npm run stillsand looks at it before reporting back.
You review a video that has already passed the layout and sound gates. The gates are the same whether a person or an agent wrote the scene, and they are not negotiable: if one fails, the scene is fixed — never the gate.
src/
theme.ts colours, type scale, watermark, safe-zone geometry
fonts.ts font loading
Root.tsx every composition
template/ ReelFrame (hook band, stage card, scrubber), CodePanel, Watermark
timeline/ the scene contract (spec.ts) and the registry every tool uses
audio/ event scheduling, the <SfxTrack>, the generated bank manifest
algorithms/ AlgoSpec + bubble sort (any sort renders through BarSort)
scenes/ React-free scene modules: binary search, BFS
compositions/ BarSort, BinarySearch, BreadthFirstSearch
scripts/
make-sfx.mjs the synthesiser (styles in scripts/lib/styles/)
premix-sfx.mjs offline mix used as the sync reference
verify-layout.mjs gate 1: safe zone, every frame
verify-sound.mjs gate 2: loudness, peak, sync, onsets, clipping
check.mjs, stills.mjs
SCENE-SPEC.md how to write a scene
prompts/new-scene.md a ready-to-fill brief for an AI coding agent
25 more reels rendered by the engine this template is trimmed from: the same frame, the same safe zone, the same hook band and code panel. Their scenes are not in this repository; each links to the interactive lesson it comes from.
![]() Kadane's Algorithm: Maximum Subarray in One Pass 45 subarrays? Kadane reads just 9. Full lesson |
![]() Monotonic Stack: Next Greater Element in O(n) Pop until smaller. Nobody waits twice. Full lesson |
![]() Tree Traversals: Preorder, Inorder, Postorder, Level Order One tree, 4 orders. Which one is sorted? Full lesson |
![]() Merge Intervals: Sort Once, Sweep Once Merge 6 intervals into 3 blocks. Full lesson |
![]() Dijkstra's Algorithm: Why the Nearest Node Goes First One hop: 9 ms. Two hops: 5 ms. Full lesson |
![]() Two Sum in One Pass: The Complement Lookup 14 pair checks? Try 6 lookups. Full lesson |
![]() LRU Cache: Hash Map + Doubly Linked List 4 slots. A 5th key. Who gets evicted? Full lesson |
![]() 4 Rate Limiting Algorithms on the Same Burst Limit: 4 per second. One lets 8 through. Full lesson |
![]() Top K Elements with a Min-Heap Top 4 of 10? The root decides. Full lesson |
![]() Attention, Intuitively: One Word, Two Sentences Which words does "bank" listen to? Full lesson |
![]() IAM Policy Evaluation: Why Deny Beats Allow Allow s3:* says yes. Deny beats allow. Full lesson |
![]() One AWS Availability Zone Dies. The App Stays Up. One zone dies. The app stays up. Full lesson |
![]() SQS vs SNS: One Message, Five Workers (Fan-Out + DLQ) One message, five workers. Full lesson |
![]() DynamoDB Hot Partitions: Why One Key Melts the Table Why one key melts the table. Full lesson |
![]() Design an Image Upload Pipeline on AWS in 40 Seconds Image upload pipeline in 40 seconds. Full lesson |
![]() Validate a BST: Why Checking the Parent Is Not Enough 6 beats its parent 3. Still not a BST. Full lesson |
![]() Lowest Common Ancestor in a BST: Stop Where the Paths Split LCA of 0 and 4? Just 2 nodes visited. Full lesson |
![]() Edit Distance: Turn flaw into lawn in 2 Edits, Not 4 Turn flaw into lawn. Not 4 edits. Just 2. Full lesson |
![]() Coin Change: Where Greedy Goes Wrong and DP Gets It Right Coins 1, 3, 4. Make 6. Greedy: 3. DP: 2. Full lesson |
![]() Longest Increasing Subsequence in O(n log n) with Patience Piles 8 cards, 4 piles. Piles = LIS length. Full lesson |
![]() KMP Failure Table: Fall Back, Never Restart Mismatch at "c"? Jump to table[k-1]. Full lesson |
![]() Union-Find Path Compression: 4 Hops Once, Then 1 find(0): 4 hops. Next time: 1 hop. Full lesson |
![]() Word Break with DP: Why the "cam" Cut Is a Dead End Split "codecamp". "cam" fits. Dead end. Full lesson |
![]() Sliding Window Median: Two Heaps and Lazy Deletion 1 leaves the window. It stays in the heap. Full lesson |
![]() Kruskal vs Prim: Same Minimum Spanning Tree, Different Order Kruskal: 1, 2, 3. Prim: 1, 3, 2. Both 6. Full lesson |
New scenes ship as short videos and interactive lessons.
- Website: bytepatterns.com
- YouTube: @bytepatterns
- Instagram: @bytepatterns
- TikTok: @bytepatterns
The code in this repository is MIT licensed. The sound banks are synthesised by the code, so they carry no third-party licence.
Two dependencies have their own terms: Remotion is source-available under the Remotion License — free for individuals and small teams, a company licence is required above that threshold — and the fonts (Manrope, IBM Plex Mono) are under the SIL Open Font License and are fetched from Google Fonts at render time. See CONTRIBUTING.md to add a scene.
The animations under docs/reels/ are excerpts of BytePatterns reels shown for reference; the scenes that draw them are not part of this repository.



























