# SPEC.md: 3Blue1Brown-style explainer videos made entirely by Claude. The explaining rules (term budget), look, voice, music recipe, mix, export, and a paste-in prompt to build your own.

> Source: <https://gist.github.com/Lurrobert/c3928a46f892f8a1e2fc18058a7cfff1>
> Published: 2026-11-01 22:04:56+00:00

I wanted to finally understand materials science, so I had Claude make me 3Blue1Brown-style explainer videos about it. Dark canvas, formulas that write themselves on in color, a calm narrator, a soft piano score. Four videos so far (6 to 15 minutes each), a few evenings with Claude Code + Opus 5.5. I never opened a video editor.

The first version was unwatchable. It introduced 50 new terms in 7 minutes and I did not understand half of it myself. So I had Claude study how Grant Sanderson actually explains things, measure it, and turn it into rules. The rules are the part worth sharing. The rest of this file is every setting you need to rebuild the whole thing: look, voice, music, mix, export, and a prompt to paste into Claude Code.

|  | 3Blue1Brown, ch. 1 | My v1 | My v2 (after these rules) | 
|---|---|---|---|
| Spoken words | ~3,800 | 1,111 | 979 | 
| New terms and names | ~25 | 50 | 6 | 
| Words per new term | ~150 | 22 | 163 | 
| Running examples | 1 | ~10 | 1 | 

**Contents:** 1 How to explain · 2 The look · 3 The voice · 4 The music · 5 The mix · 6 QA · 7 Export · 8 The pipeline · 9 Build your own (paste-in prompt) · 10 Credits

Source studied: 3Blue1Brown's own text versions of two of his most-watched lessons, "But what is a neural network?" (deep learning ch. 1) and "Gradient descent, how neural networks learn" (ch. 2), at 3blue1brown.com/lessons. This is an analysis with a few short quotes. It is not a copy of his text; his work is copyrighted.

|  | 3Blue1Brown ch. 1 | 3Blue1Brown ch. 2 | My superconductor video v1 | 
|---|---|---|---|
| Spoken words | ~3,800 (~19 min) | ~2,500 | 1,111 (7.3 min) | 
| New terms and names | ~25 | ~13 | **50** | 
| Words per new term | **~150** | ~190 | **22** (7× too dense) | 
| Words before the first jargon | ~400 | (recap of ch. 1) | ~30 | 
| Running examples | 1 (handwritten digits) | the same one | ~10 | 

1. **Term budget: one new term per ~150 spoken words.** A 6–7 minute video (~900 words) gets**at most 6 new terms** .
2. **Name only what you will use three more times.** Say everything else in plain words: "a very sensitive magnet sensor", not "magnetometer"; "swap in a few different atoms", not "doping"; "a fusion company", not the company's name.
3. **Picture first, then the name.** Show it, let it sink in, then say "this is what physicists call X." He does it the same way:*"We call this additional number a bias."* Never say a name for something the viewer has not already seen.
4. **One running example carries the whole video.** He uses one digit recognizer for 19 minutes. Pick one concrete thing (a magnet floating over a cold disk) and return to it in every chapter.
5. **Open on something the viewer already knows,** with no jargon for the first ~60 seconds. He opens with a working demo and how effortless reading digits is for a person.
6. **Give a simple mental model by decree,** and say it is a simplification:*"all I want you to think is 'a thing that holds a number.'"*
7. **Ask the viewer a question, then answer it.** "Why not just…?", "How sure does this feel?" One per chapter.
8. **Give big numbers a physical anchor.** 13,002 parameters become "knobs and dials"; 10,000 km of tape becomes "a quarter of the way around the Earth".
9. **Intuition first, then the formula, said in words first.** At most one formula per idea.
10. **Admit what is hard or skipped.***"It's kind of reassuring that this looks complicated."*
11. **Three proper nouns per video, max.** No lists of companies, cities, materials or years. One year per era.
12. **Callbacks and one recap.** Start each chapter by reminding the viewer where we are. End on the one idea, not a list.
13. **Slow enough to think.** He spends ~150 words on each idea. If a chapter has more than one new idea, split it or cut one.

Give the model the script alone, in a fresh context:

You are a curious non-expert hearing this script once, at normal speed. List every word or name you would not use at dinner, in order of first appearance, with its word position. List the proper nouns separately. Then report: total spoken words, new terms, words per new term, proper nouns. PASS only if words per term ≥ 150 and proper nouns ≤ 3. For each term over budget, propose the plain-words replacement.

Over budget means rewrite the script. Do not narrate it and hope.

- Term audit PASS: ≥ 150 words per new term, ≤ 3 proper nouns
- One running example named in chapter 1 and seen again in every chapter
- Every term appears on screen as a picture before it is said
- Each chapter answers one question the viewer can repeat in one sentence

| Piece | Setting | 
|---|---|
| Canvas | 1920×1080 HTML `<canvas>` , background`#101114` , text`#ECECEC` , dim text`#6b6f78` , grey lines`#3a3d45` | 
| Colors | blue `#58C4DD` , yellow`#FFFF00` , green`#83C167` , red`#FC6255` , teal`#5CD0B3` , purple`#C59BFF` , orange`#F9A65A` . One color per concept, kept for the whole video: in the picture, the label, the formula | 
| Font | Latin Modern Roman / CMU Serif / Computer Modern (the LaTeX look), Georgia as fallback. A clean sans for small labels | 
| A new term | appears big, in its color, right after the picture it names (rule 3 on screen: see the hero frame above) | 
| Chapter chip | small, dim, top-left: `4 · The catch: it has to be cold` | 
| Title card | 0.7 s before a scene's first line (2.6 s when the scene has a subtitle) | 
| Subtitles | bottom center, white on a dark rounded box, the sentence being spoken | 
| Motion | everything eased with smoothstep `x²(3 − 2x)` ; things grow, fade or draw on, nothing pops | 
| Clock | one function `renderAt(t)` draws any frame from the time alone. Preview, scrubbing and export all call it, so there is no drift | 
| Pictures | **OpenAI text-to-image** (`gpt-image` , 1536×1024 landscape): realistic photos of the running example, one per idea. In the video: slow push-in and pan, rounded corners, dimmed when text sits on top | 

1. Write each formula as colored segments, e.g. `[("$\hat{H}\,$", blue), ("$\Psi$", yellow), ("$\;=\;E\,$", white), ("$\Psi$", yellow)]` .
2. Render each segment with matplotlib's mathtext on one baseline and save as SVG with glyphs kept as paths. One `<g id="seg-k">` per segment.
3. In the video, draw each glyph's outline on by animating `stroke-dashoffset` from the path length to 0; fade the fill in over the second half; then fade the outline out. Segment by segment, in the color of its concept.

| Piece | Setting | 
|---|---|
| Takes | **one continuous TTS take per scene** , not per sentence. Per-sentence clips sound choppy; a whole paragraph keeps natural rhythm | 
| Style prompt | "warm, curious YouTube science explainer talking to a friend who is not an expert; natural conversational pace, real pauses after each idea, gentle emphasis, never rushed and never reading" | 
| Sentence timing | recover each sentence's start inside the take from word-level timestamps of a speech-to-text model. Fallback: detect pauses ( `ffmpeg silencedetect=noise=-38dB:d=0.16` ) and snap each expected boundary (by text length) to the nearest pause | 
| Timeline | per scene: lead (0.7 s, or 2.6 s with a title card), the take, then 1.1 s tail. Writes `timeline.json` : every sentence's`start` and`dur` . Animation cues and subtitles key to these numbers | 
| Cache | each take cached by hash of (engine, model, voice, style, text). A re-run never pays twice; editing one scene re-voices one scene | 
| Format | 48 kHz WAV | 

Any TTS that accepts a speaking-style instruction works. I used Gemini's TTS (voice "Achird").

Pick one:

- **Google Gemini TTS** (what I used): model`gemini-3.8-flash-tts` , voice "Achird", with the style prompt above.
- **OpenAI text-to-speech** : a model that takes voice instructions (e.g.`gpt-4o-mini-tts` ), with the same style prompt.

The full list of access is in Part 9.

The real 3Blue1Brown music is by Vincent Rubinetti (see credits). For anything public I use an original score, synthesized in ~100 lines of numpy. The recipe:

| Piece | Setting | 
|---|---|
| Harmony | D major, slow: **Dmaj9 – Bm9 – Gmaj7♯11 – Asus2** , 8 s per chord, looping. MIDI notes:`[50,57,62,64,66,69]` ,`[47,54,62,61,66,69]` ,`[43,50,59,61,66,71]` ,`[45,52,59,61,64,69]` | 
| Piano | additive sines, harmonics 1–4 at amplitude 1, 0.42, 0.18, 0.08; each higher harmonic decays faster ( `exp(−t·(k−1)·0.9)` ); envelope`exp(−1.6t)` with a 2–3 ms attack; 3.2 s notes | 
| Arpeggio | eighth notes at 60 bpm (0.5 s), an up-down figure over chord tones `[0,2,3,4,5,4,3,2]` twice per chord. 12% of notes skipped at random (fixed seed) so it breathes. In bars 5–8 of every 8, the top notes jump an octave. Downbeats a little louder (0.11 vs 0.085). Each note panned on a slow sine | 
| Pad | the chord's lowest 3 notes, an octave down, each as 3 sines detuned −0.12 / 0 / +0.12 Hz; 2.5 s fade in and out; slow swell (0.07 Hz LFO, ±20%); quiet (0.06) | 
| Room | three cross-channel echoes: 83 ms (0.28), 191 ms (0.20), 370 ms (0.13) | 
| Ends | 3 s fade in, 5 s fade out, peak-normalized to 0.8, 48 kHz stereo | 

Music sits under the voice and ducks whenever the narrator speaks:

```
ffmpeg -i narration.wav -i score.wav -filter_complex \
 "[1:a]aformat=channel_layouts=stereo,volume=0.42,afade=t=out:st=<total-4>:d=4[m];
  [0:a]aformat=channel_layouts=stereo,asplit=2[v][sc];
  [m][sc]sidechaincompress=threshold=0.03:ratio=6:attack=40:release=600[md];
  [v][md]amix=inputs=2:normalize=0,alimiter=limit=0.95[out]" \
 -map "[out]" -t <total> -ar 48000 mix.wav
```

- **Words:** transcribe each take with a speech-to-text model and diff it against the script. TTS can skip or repeat a phrase.
- **Timing:** compare word timestamps with`timeline.json` . A subtitle that lands 0.5 s early feels wrong.
- **Levels:** ffmpeg loudness of the voice alone, the music alone and the mix; check for clipping.
- **Stills:** export frames at the scene boundaries and look at them before rendering the full video.

- Headless Chromium (Playwright) opens the page in export mode (no player UI) and waits for fonts and formula SVGs.
- For every frame `i` : call`renderAt(i / 30)` , screenshot as JPEG, pipe it into ffmpeg (`-f image2pipe -c:v mjpeg` ), muxed with`mix.wav` .
- 1080p30 MP4, libx264, CRF 18, yuv420p.
- For speed, cut the film into N slices and render them at once (one browser and one encoder each), then stitch and mux. About N× faster on a many-core machine.

```
script.json ─► term audit ─► narrate ─► timeline.json ─► video.html (renderAt) ─► export ─► video.mp4
 scenes →        PASS?        one take     sentence        canvas + formula          frames      + mix.wav
 sentences      else rewrite  per scene    starts          SVGs + subtitles          → ffmpeg
                                                    music.py ─► score.wav ─► mix (duck under voice) ─┘
```

| File | Role | 
|---|---|
| `script.json` | `{title, subtitle, voice, style, mode, scenes:[{id, title, subtitle, sentences:[…]}]}` | 
| `narrate.py` | one take per scene, sentence alignment, writes `timeline.json` and`narration.wav` | 
| `render_formulas.py` | colored LaTeX segments → SVG glyph paths | 
| `engine.js` | shared canvas helpers: palette, text, lines, arrows, axes, formula write-on, subtitles, chapter chip, preview player | 
| `video.html` | the scenes: one draw function per scene, keyed to `timeline.json` cues | 
| `img/` | pictures from OpenAI text-to-image ( `gpt-image` , 1536×1024) | 
| `music.py` | the synthesized score | 
| `audio_check.py` | the QA step | 
| `export.py` | mix, stills, MP4 | 

| API | For | Needed? | 
|---|---|---|
| **Google Gemini TTS** or**OpenAI text-to-speech** | the voice | yes, one of the two | 
| **OpenAI text-to-image** (`gpt-image` ) | the pictures | yes | 
| **OpenAI speech-to-text** (`whisper-1` ) | checking the voice and subtitle timing | optional | 

Tools (Claude installs what is missing): Python + numpy + matplotlib, ffmpeg, Playwright + Chromium.

The prompt below makes Claude ask you for each of these before it starts.

Put this file next to the prompt, fill in the two lines in angle brackets, and paste.

```
Make me a 3Blue1Brown-style explainer video.

Topic: <the thing you want to understand, e.g. "why the sky is blue">
Viewer: <who it is for, e.g. "me: smart, curious, no physics past high school">
Length: 6–8 minutes, 1080p MP4.

SPEC.md is the law. Read all of it first. Part 1 (the term budget) is the hard part,
and it is what makes the video watchable. Parts 2–7 give every setting for look, voice,
music, mix, QA and export.

Work in this order, and show me each result before you move on:
0. ASK ME for the API keys in "Access you need" (Part 9): my voice API (Google Gemini TTS
   or OpenAI text-to-speech) and its key, and my OpenAI key (text-to-image, and speech-to-text
   if I have it). Keep the keys local; never commit or publish them. Install missing tools.
1. Pick ONE running example the whole video returns to, and the one question each chapter answers.
2. Write script.json: scenes → sentences.
3. Run the term audit from Part 1 in a fresh context. Rewrite until it passes. Show me the table.
4. Narrate one take per scene with the style prompt from Part 3. Align sentences. Write timeline.json.
5. Make the pictures with OpenAI text-to-image (gpt-image, 1536×1024): the running example, one per idea.
   Build ONE HTML canvas page with renderAt(t), keyed to the timeline. Palette, font, easing,
   chapter chip, subtitles and formula write-on from Part 2. Add a preview player I can scrub.
6. Synthesize the score from Part 4 and mix it under the voice with the filter in Part 5.
7. QA from Part 6. Fix what it finds before I listen.
8. Export from Part 7.

Then send me 6 stills from scene boundaries and the MP4 path. I will watch it and tell you
where I got lost. That feedback goes back into the script, not into a longer video.
```

Everything you need is in "Access you need" above. Takes are cached, so you pay for each scene's voice once.

Bonus: I watch mine on a little local YouTube-style page. The thumbnail is a real frame from the middle of each video, the chapters come from `timeline.json`, and it resumes where I stopped. Ask Claude for one; it is one HTML file and a ~65-line Python server with HTTP Range support, so seeking works.

- **3Blue1Brown (Grant Sanderson):** the explaining style studied here. The rules are an analysis of his public lessons; the quotes are short and attributed. Watch the originals: youtube.com/@3blue1brown.
- **Music:** the real 3Blue1Brown music is by Vincent Rubinetti ("The Music of 3Blue1Brown"). He offers the album as a free download on his site, but ask him for a licence before you put it in a public video. The synthesized score in Part 4 avoids the question.
- **Colors:** blue, yellow, green, red and teal are manim's defaults (manim is 3Blue1Brown's open-source animation library).
- Made with Claude Code + Opus 5.5. Use this spec however you like.
