{"owner":"browser-use","repo":"video-use","hasSkills":true,"totalSkillsCount":17,"totalTokensCount":33126,"categories":["anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":["SKILL.md","skills/manim-video/README.md","skills/manim-video/SKILL.md","skills/manim-video/references/animation-design-thinking.md","skills/manim-video/references/animations.md","skills/manim-video/references/camera-and-3d.md","skills/manim-video/references/decorations.md","skills/manim-video/references/equations.md","skills/manim-video/references/graphs-and-data.md","skills/manim-video/references/mobjects.md","skills/manim-video/references/paper-explainer.md","skills/manim-video/references/production-quality.md","skills/manim-video/references/rendering.md","skills/manim-video/references/scene-planning.md","skills/manim-video/references/troubleshooting.md","skills/manim-video/references/updaters-and-trackers.md","skills/manim-video/references/visual-design.md"],"skills":{"SKILL.md":"---\nname: video-use\ndescription: Edit any video by conversation. Transcribe, cut, color grade, generate overlay animations, burn subtitles — for talking heads, montages, tutorials, travel, interviews. No presets, no menus. Ask questions, confirm the plan, execute, iterate, persist. Production-correctness rules are hard; everything else is artistic freedom.\n---\n\n# Video Use\n\n## Principle\n\n1. **LLM reasons from raw transcript + on-demand visuals.** The only derived artifact that earns its keep is a packed phrase-level transcript (`takes_packed.md`). Everything else — filler tagging, retake detection, shot classification, emphasis scoring — you derive at decision time.\n2. **Audio is primary, visuals follow.** Cut candidates come from speech boundaries and silence gaps. Drill into visuals only at decision points.\n3. **Ask → confirm → execute → iterate → persist.** Never touch the cut until the user has confirmed the strategy in plain English.\n4. **Generalize.** Do not assume what kind of video this is. Look at the material, ask the user, then edit.\n5. **Artistic freedom is the default.** Every specific value, preset, font, color, duration, pitch structure, and technique in this document is a *worked example* from one proven video — not a mandate. Read them to understand what's possible and why each worked. Then make your own taste calls based on what the material actually is and what the user actually wants. **The only things you MUST do are in the Hard Rules section below.** Everything else is yours.\n6. **Invent freely.** If the material calls for a technique not described here — split-screen, picture-in-picture, lower-third identity cards, reaction cuts, speed ramps, freeze frames, crossfades, match cuts, L-cuts, J-cuts, speed ramps over breath, whatever — build it. The helpers are ffmpeg and PIL. They can do anything the format supports. Do not wait for permission.\n7. **Verify your own output before showing it to the user.** If you wouldn't ship it, don't present it.\n\n## Hard Rules (production correctness — non-negotiable)\n\nThese are the things where deviation produces silent failures or broken output. They are not taste, they are correctness. Memorize them.\n\n1. **Subtitles are applied LAST in the filter chain**, after every overlay. Otherwise overlays hide captions. Silent failure.\n2. **Per-segment extract → lossless `-c copy` concat**, not single-pass filtergraph. Otherwise you double-encode every segment when overlays are added.\n3. **30ms audio fades at every segment boundary** (`afade=t=in:st=0:d=0.03,afade=t=out:st={dur-0.03}:d=0.03`). Otherwise audible pops at every cut.\n4. **Overlays use `setpts=PTS-STARTPTS+T/TB`** to shift the overlay's frame 0 to its window start. Otherwise you see the middle of the animation during the overlay window.\n5. **Master SRT uses output-timeline offsets**: `output_time = word.start - segment_start + segment_offset`. Otherwise captions misalign after segment concat.\n6. **Never cut inside a word.** Snap every cut edge to a word boundary from the Scribe transcript.\n7. **Pad every cut edge.** Working window: 30–200ms. Scribe timestamps drift 50–100ms — padding absorbs the drift. Tighter for fast-paced, looser for cinematic.\n8. **Word-level verbatim ASR only.** Never SRT/phrase mode (loses sub-second gap data). Never normalized fillers (loses editorial signal).\n9. **Cache transcripts per source.** Never re-transcribe unless the source file itself changed.\n10. **Parallel sub-agents for multiple animations.** Never sequential. Spawn N at once via the `Agent` tool; total wall time ≈ slowest one.\n11. **Strategy confirmation before execution.** Never touch the cut until the user has approved the plain-English plan.\n12. **All session outputs in `<videos_dir>/edit/`.** Never write inside the `video-use/` project directory.\n\nEverything else in this document is a worked example. Deviate whenever the material calls for it.\n\n## Directory layout\n\nThe skill lives in `video-use/`. User footage lives wherever they put it. All session outputs go into `<videos_dir>/edit/`.\n\n```\n<videos_dir>/\n├── <source files, untouched>\n└── edit/\n    ├── project.md               ← memory; appended every session\n    ├── takes_packed.md          ← phrase-level transcripts, the LLM's primary reading view\n    ├── edl.json                 ← cut decisions\n    ├── transcripts/<name>.json  ← cached raw Scribe JSON\n    ├── animations/slot_<id>/    ← per-animation source + render + reasoning\n    ├── clips_graded/            ← per-segment extracts with grade + fades\n    ├── master.srt               ← output-timeline subtitles\n    ├── downloads/               ← yt-dlp outputs\n    ├── verify/                  ← debug frames / timeline PNGs\n    ├── preview.mp4\n    └── final.mp4\n```\n\n## Setup\n\nFirst-time install lives in `install.md` (clone, deps, ffmpeg, skill registration, API key). Don't re-run it every session; on cold start just verify:\n\n- `ELEVENLABS_API_KEY` resolves — either in the environment or in `.env` at the video-use repo root. If missing, ask the user to paste one and write it to `.env` (never to the user's `<videos_dir>`).\n- `ffmpeg` + `ffprobe` on PATH.\n- Python deps installed (`uv sync` or `pip install -e .` inside the repo).\n- Node.js + npm available if the session needs HyperFrames or Remotion slots. HyperFrames currently requires Node.js 22+.\n- `yt-dlp`, HyperFrames, Remotion, Manim installed only on first use.\n- First-use animation setup happens inside the slot directory, never at the video-use repo root. HyperFrames can be invoked with `npx --yes hyperframes ...`; Remotion can be scaffolded with `npx create-video@latest` or installed as a project-local dependency before using its `remotion render` command.\n- This skill vendors `skills/manim-video/`. Read its SKILL.md when building a Manim slot.\n\nHelpers (`helpers/transcribe.py`, `helpers/render.py`, etc.) live alongside this SKILL.md. Resolve their paths relative to the directory containing this file — the skill is typically symlinked at `~/.claude/skills/video-use/` or `~/.codex/skills/video-use/`.\n\n## Helpers\n\n- **`transcribe.py <video>`** — single-file Scribe call. `--num-speakers N` optional. Cached.\n- **`transcribe_batch.py <videos_dir>`** — 4-worker parallel transcription. Use for multi-take.\n- **`pack_transcripts.py --edit-dir <dir>`** — `transcripts/*.json` → `takes_packed.md` (phrase-level, break on silence ≥ 0.5s).\n- **`timeline_view.py <video> <start> <end>`** — filmstrip + waveform PNG. On-demand visual drill-down. **Not a scan tool** — use it at decision points, not constantly.\n- **`render.py <edl.json> -o <out>`** — per-segment extract → concat → overlays (PTS-shifted) → subtitles LAST. `--preview` for 720p fast. `--build-subtitles` to generate master.srt inline.\n- **`grade.py <in> -o <out>`** — ffmpeg filter chain grade. Presets + `--filter '<raw>'` for custom.\n\nFor animations, create `<edit>/animations/slot_<id>/` with `Bash` and spawn a sub-agent via the `Agent` tool.\n\n## The process\n\n1. **Inventory.** `ffprobe` every source. `transcribe_batch.py` on the directory. `pack_transcripts.py` to produce `takes_packed.md`. Sample one or two `timeline_view`s for a visual first impression.\n2. **Pre-scan for problems.** One pass over `takes_packed.md` to note verbal slips, obvious mis-speaks, or phrasings to avoid. Plain list, feed into the editor brief.\n3. **Converse.** Describe what you see in plain English. Ask questions *shaped by the material*. Collect: content type, target length/aspect, aesthetic/brand direction, pacing feel, must-preserve moments, must-cut moments, animation and grade preferences, subtitle needs. Do not use a fixed checklist — the right questions are different every time.\n4. **Propose strategy.** 4–8 sentences: shape, take choices, cut direction, animation plan, grade direction, subtitle style, length estimate. **Wait for confirmation.**\n5. **Execute.** Produce `edl.json` via the editor sub-agent brief. Drill into `timeline_view` at ambiguous moments. Build animations in parallel sub-agents. Apply grade per-segment. Compose via `render.py`.\n6. **Preview.** `render.py --preview`.\n7. **Self-eval (before showing the user).** Run `timeline_view` on the **rendered output** (not the sources) at every cut boundary (±1.5s window). Check each image for:\n   - Visual discontinuity / flash / jump at the cut\n   - Waveform spike at the boundary (audio pop that slipped past the 30ms fade)\n   - Subtitle hidden behind an overlay (Rule 1 violation)\n   - Overlay misaligned or showing wrong frames (Rule 4 violation)\n\n   Also sample: first 2s, last 2s, and 2–3 mid-points — check grade consistency, subtitle readability, overall coherence. Run `ffprobe` on the output to verify duration matches the EDL expectation.\n\n   Measure the audio, don't assume it: `ffmpeg -i out.mp4 -af ebur128=peak=true -f null -` for integrated loudness and true peak, plus RMS per section (dialogue, music-only, end card). An end card 15 dB under the dialogue, or effects louder than speech, is a bug. You cannot listen: say so, and report the numbers.\n\n   For anything the user will publish (launch, promo, ad), also spawn one **critic sub-agent** with the rendered file, the EDL, and any reference videos the user gave. Brief it to roast, not to praise: a verdict, ranked problems with timecodes and evidence (frames, levels), and the 5 fixes to do first. Fresh eyes catch what the author stopped seeing — cut-off payoff lines, 0.5s memes, unreadable 28px text at phone size.\n\n   If anything fails: fix → re-render → re-eval. **Cap at 3 self-eval passes** — if issues remain after 3, flag them to the user rather than looping forever. Only present the preview once the self-eval passes.\n8. **Iterate + persist.** Natural-language feedback, re-plan, re-render. Never re-transcribe. Final render on confirmation. Append to `project.md`.\n\n## Cut craft (techniques)\n\n- **Audio-first.** Candidate cuts from word boundaries and silence gaps.\n- **Preserve peaks.** Laughs, punchlines, emphasis beats. Extend past punchlines to include reactions — the laugh IS the beat.\n- **Speaker handoffs** benefit from air between utterances. Common values: 400–600ms. Less for fast-paced, more for cinematic. Taste call.\n- **Audio events as signals.** `(laughs)`, `(sighs)`, `(applause)` mark beats. Extend past them.\n- **Silence gaps are cut candidates.** Silences ≥400ms are usually the cleanest. 150–400ms phrase boundaries are usable with a visual check. <150ms is unsafe (mid-phrase).\n- **Example cut padding** (the launch video shipped with this): 50ms before the first kept word, 80ms after the last. Tighter for montage energy, looser for documentary. Stay in the 30–200ms working window (Hard Rule 7).\n- **Never reason audio and video independently.** Every cut must work on both tracks.\n\n## The packed transcript (primary reading view)\n\n`pack_transcripts.py` reads all `transcripts/*.json` and produces one markdown file where each take is a list of phrase-level lines, each prefixed with its `[start-end]` time range. Phrases break on any silence ≥ 0.5s OR speaker change. This is the artifact the editor sub-agent reads to pick cuts — it gives word-boundary precision from text alone at 1/10 the tokens of raw JSON.\n\nExample line:\n```\n## C0103  (duration: 43.0s, 8 phrases)\n  [002.52-005.36] S0 Ninety percent of what a web agent does is completely wasted.\n  [006.08-006.74] S0 We fixed this.\n```\n\n## Editor sub-agent brief (for multi-take selection)\n\nWhen the task is \"pick the best take of each beat across many clips,\" spawn a dedicated sub-agent with a brief shaped like this. The structure is load-bearing; the pitch-shape example is not.\n\n```\nYou are editing a <type> video. Pick the best take of each beat and \nassemble them chronologically by beat, not by source clip order.\n\nINPUTS:\n  - takes_packed.md (time-annotated phrase-level transcripts of all takes)\n  - Product/narrative context: <2 sentences from the user>\n  - Speaker(s): <name, role, delivery style note>\n  - Expected structure: <pick an archetype or invent one>\n  - Verbal slips to avoid: <list from the pre-scan pass>\n  - Target runtime: <seconds>\n\nCommon structural archetypes (pick, adapt, or invent):\n  - Tech launch / demo:   HOOK → PROBLEM → SOLUTION → BENEFIT → EXAMPLE → CTA\n  - Tutorial:             INTRO → SETUP → STEPS → GOTCHAS → RECAP\n  - Interview:            (QUESTION → ANSWER → FOLLOWUP) repeat\n  - Travel / event:       ARRIVAL → HIGHLIGHTS → QUIET MOMENTS → DEPARTURE\n  - Documentary:          THESIS → EVIDENCE → COUNTERPOINT → CONCLUSION\n  - Music / performance:  INTRO → VERSE → CHORUS → BRIDGE → OUTRO\n  - Or invent your own.\n\nRULES:\n  - Start/end times must fall on word boundaries from the transcript.\n  - Pad cut boundaries (working window 30–200ms).\n  - Prefer silences ≥ 400ms as cut targets.\n  - Unavoidable slips are kept if no better take exists. Note them in \"reason\".\n  - If over budget, revise: drop a beat or trim tails. Report total and self-correct.\n\nOUTPUT (JSON array, no prose):\n  [{\"source\": \"C0103\", \"start\": 2.42, \"end\": 6.85, \"beat\": \"HOOK\",\n    \"quote\": \"...\", \"reason\": \"...\"}, ...]\n\nReturn the final EDL and a one-line total runtime check.\n```\n\n## Color grade (when requested)\n\nYour job is to **reason about the image**, not apply a preset. Look at a frame (via `timeline_view`), decide what's wrong, adjust one thing, look again.\n\nMental model is ASC CDL. Per channel: `out = (in * slope + offset) ** power`, then global saturation. `slope` → highlights, `offset` → shadows, `power` → midtones.\n\n**Example filter chains** (`grade.py` has `--list-presets`; use them as starting points or mix your own):\n\n- **`warm_cinematic`** — retro/technical, subtle teal/orange split, desaturated. Shipped in a real launch video. Safe for talking heads.\n- **`neutral_punch`** — minimal corrective: contrast bump + gentle S-curve. No hue shifts.\n- **`none`** — straight copy. Default when the user hasn't asked.\n\nFor anything else — portraiture, nature, product, music video, documentary — invent your own chain. `grade.py --filter '<raw ffmpeg>'` accepts any filter string.\n\nHard rules: apply **per-segment during extraction** (not post-concat, which re-encodes twice). Never go aggressive without testing skin tones.\n\n## Subtitles (when requested)\n\nSubtitles have three dimensions worth reasoning about: **chunking** (1/2/3/sentence per line), **case** (UPPER/Title/Natural), and **placement** (margin from bottom). The right combo depends on content.\n\n**Worked styles** — pick, adapt, or invent:\n\n**`bold-overlay`** — short-form tech launch, fast-paced social. ~2-word chunks, UPPERCASE, break on punctuation and pauses ≥ 0.3s, grow to 3 words rather than flash a cue < 0.35s (`chunk_words` in `render.py`), Helvetica 18 Bold, white-on-outline, `MarginV=35`. `render.py` ships with this as `SUB_FORCE_STYLE`.\n\n```\nFontName=Helvetica,FontSize=18,Bold=1,\nPrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BackColour=&H00000000,\nBorderStyle=1,Outline=2,Shadow=0,\nAlignment=2,MarginV=35\n```\n\n**`natural-sentence`** (if you invent this mode) — narrative, documentary, education. 4–7 word chunks, sentence case, break on natural pauses, `MarginV=60–80`, larger font for readability, slightly wider max-width. No shipped force_style — design one if you need it.\n\nInvent a third style if neither fits. Hard rules: subtitles LAST (Rule 1), output-timeline offsets (Rule 5).\n\n## Animations (when requested)\n\nAnimations match the content and the brand. **Get the palette, font, and visual language from the conversation** — never assume a default. If the user hasn't told you, propose a palette in the strategy phase and wait for confirmation before building anything.\n\n**Tool options:**\n\nPick the engine per animation slot. Do not default to Remotion just because the animation is web-adjacent.\n\n- **HyperFrames** — Browser-native HTML/CSS/GSAP video compositions: product UI motion, website-to-video or mockup-to-video captures, kinetic typography, landing-page/storyboard promos, data-driven UI states, transparent WebM overlays, and clips that need deterministic frame capture plus HyperFrames lint/validate/render checks. Best when the animation should be authored and verified like a web composition instead of a React component tree.\n- **Remotion** — React/CSS compositions with component state, reusable React primitives, or an existing Remotion brand system. Best when the user specifically asks for React/Remotion or when React composition is the simpler authoring model.\n- **Manim** — formal diagrams, state machines, equation derivations, graph morphs. Read `skills/manim-video/SKILL.md` and its references for depth.\n- **PIL + PNG sequence + ffmpeg** — simple overlay cards: counters, typewriter text, single bar reveals, progressive draws. Fast to iterate, any aesthetic you want. The launch video used this.\n\nFor HyperFrames slots, scaffold the slot inside `edit/animations/slot_<id>/` with `npx --yes hyperframes init . --example blank --non-interactive --skip-skills`, build the HTML composition there, run the HyperFrames checks that fit the slot (`lint`, `validate`, and a draft render when practical), then produce the final overlay video with `npx --yes hyperframes render . -o render.mp4` or `--format webm -o render.webm` when alpha is required. Point the EDL overlay `file` at the actual rendered path.\n\nFor Remotion slots, keep the Remotion project isolated inside the same slot directory, scaffold with `npx create-video@latest` or install Remotion locally there, render the composition to `render.mp4` with the project-local `remotion render` command, and verify duration and dimensions with `ffprobe`.\n\nNone is mandatory. Invent hybrids if useful (e.g., PIL background with a HyperFrames or Remotion layer on top).\n\n**Duration rules of thumb, context-dependent:**\n\n- **Sync-to-narration explanations.** A viewer needs to parse the content at 1×. Rough floor 3s, typical 5–7s for simple cards, 8–14s for complex diagrams. The launch video shipped at 5–7s per simple card.\n- **Beat-synced accents** (music video, fast montage). 0.5–2s is fine — they're visual accents, not information. The \"readable at 1×\" rule becomes *\"recognizable at 1×\"*, not *\"fully parseable.\"*\n- **Hold the final frame ≥ 1s** before the cut (universal).\n- **Over voiceover:** total duration ≥ `narration_length + 1s` (universal).\n- **Never parallel-reveal independent elements** — the eye can't track two new things at once. One thing, pause, next thing.\n\n**Animation payoff timing (rule for sync-to-narration):** get the payoff word's timestamp. Start the overlay `reveal_duration` seconds earlier so the landing frame coincides with the spoken payoff word. Without this sync the animation feels disconnected.\n\n**Easing** (universal — never `linear`, it looks robotic):\n\n```python\ndef ease_out_cubic(t):    return 1 - (1 - t) ** 3\ndef ease_in_out_cubic(t):\n    if t < 0.5: return 4 * t ** 3\n    return 1 - (-2 * t + 2) ** 3 / 2\n```\n\n`ease_out_cubic` for single reveals (slow landing). `ease_in_out_cubic` for continuous draws.\n\n**Typing text anchor trick:** center on the FULL string's width, not the partial-string width — otherwise text slides left during reveal.\n\n**Example palette** (the launch video — one aesthetic among infinite):\n- Background `(10, 10, 10)` near-black\n- Accent `#FF5A00` / `(255, 90, 0)` orange\n- Labels `(110, 110, 110)` dim gray\n- Font: Menlo Bold at `/System/Library/Fonts/Menlo.ttc` (index 1)\n- ≤ 2 accent colors, ~40% empty space, minimal chrome\n- Result: terminal / retro tech feel\n\nThis is one style. If the brand is warm and serif, use that. If it's colorful and playful, use that. If the user handed you a style guide, follow it. If they didn't, propose one and confirm.\n\n**Fonts fail silently.** A web font that didn't load renders in a fallback face with no error — the video ships in \"almost Arial\". In HyperFrames/Remotion, await the font load and then assert it: `if (!document.fonts.check('700 76px \"Inter\"')) throw new Error(...)`. In PIL, pass an explicit font path; never rely on the default.\n\n**Worked example — \"show the edit\" hook** (a launch video for this tool). Instead of a title card, the first 3s visualize the editing itself: each transcript word pops in on its Scribe timestamp, with bars under it drawn from the real audio envelope; a filler (\"ummm\") grows letter by letter while it is spoken, turns orange and is cut out on screen at the same frame the audio cuts, and the next line lands immediately. It works because the picture is *driven by the same data as the sound* — one composition (Remotion) reads frame-exact word/envelope JSON produced in Python, so nothing can drift. Use the idea whenever the story is \"we removed something\": make the removal visible.\n\n**Parallel sub-agent brief** — each animation is one sub-agent spawned via the `Agent` tool. Each prompt is self-contained (sub-agents have no parent context). Include:\n\n1. One-sentence goal: *\"Build ONE animation: [spec]. Nothing else.\"*\n2. Absolute output path (`<edit>/animations/slot_<id>/render.mp4`)\n3. Exact technical spec: resolution, fps, codec, pix_fmt, CRF, duration\n4. Style palette as concrete values (RGB tuples, hex, or reference to a design system)\n5. Font path with index\n6. Frame-by-frame timeline (what happens when, with easing)\n7. Anti-list (\"no chrome, no extras, no titles unless specified\")\n8. Code pattern reference (copy helpers inline, don't import across slots)\n9. Deliverable checklist (script, render, verify duration via ffprobe, report)\n10. **\"Do not ask questions. If anything is ambiguous, pick the most obvious interpretation and proceed.\"**\n\nOne sub-agent = one file (unique filenames, parallel agents don't overwrite each other).\n\n## Music and sound effects (when requested)\n\nSound is where generated videos sound cheap. Worked rules from launch edits:\n\n- **Fewer effects.** Every effect is tied to something visible (a cut, a landing, a click). ~20 stock whooshes/risers/impacts in 18s reads as generic; ~8 reads as designed.\n- **Hit on the frame.** Most effects have an attack (silence or a build before the transient). Measure it (first sample above ~-30 dBFS of the peak) and start the file `attack` seconds *before* the visible contact frame.\n- **Duck music under speech** (roughly -12 to -15 dB relative to its music-only level), and ramp it out before a stinger or end card instead of letting its own tail decay under your CTA.\n- **Master once:** mix to PCM, then two-pass loudnorm (-14 LUFS, true peak ≤ -1 dBTP) on the final mix. Then measure per section (see Self-eval).\n- **Music taste is the user's call.** Generated music defaults to \"hype\"; offer two contrasting beds and let the user listen. Don't claim a mix sounds good — you can only measure it.\n\n## Output spec\n\nMatch the source unless the user asked for something specific. Common targets: `1920×1080@24` cinematic, `1920×1080@30` screen content, `1080×1920@30` vertical social, `3840×2160@24` 4K cinema, `1080×1080@30` square. `render.py` defaults the scale to 1080p from any source; pass `--filter` or edit the extract command for other targets. Worth asking the user which delivery format matters.\n\n## EDL format\n\n```json\n{\n  \"version\": 1,\n  \"sources\": {\"C0103\": \"/abs/path/C0103.MP4\", \"C0108\": \"/abs/path/C0108.MP4\"},\n  \"ranges\": [\n    {\"source\": \"C0103\", \"start\": 2.42, \"end\": 6.85,\n     \"beat\": \"HOOK\", \"quote\": \"...\", \"reason\": \"Cleanest delivery, stops before slip at 38.46.\"},\n    {\"source\": \"C0108\", \"start\": 14.30, \"end\": 28.90,\n     \"beat\": \"SOLUTION\", \"quote\": \"...\", \"reason\": \"Only take without the false start.\"}\n  ],\n  \"grade\": \"warm_cinematic\",\n  \"overlays\": [\n    {\"file\": \"edit/animations/slot_1/render.mp4\", \"start_in_output\": 0.0, \"duration\": 5.0}\n  ],\n  \"subtitles\": \"edit/master.srt\",\n  \"total_duration_s\": 87.4\n}\n```\n\n`grade` is a preset name or raw ffmpeg filter. `overlays` are rendered animation clips. `subtitles` is optional and applied LAST.\n\n## Memory — `project.md`\n\nAppend one section per session at `<edit>/project.md`:\n\n```markdown\n## Session N — YYYY-MM-DD\n\n**Strategy:** one paragraph describing the approach\n**Decisions:** take choices, cuts, grades, animations + why\n**Reasoning log:** one-line rationale for non-obvious decisions\n**Outstanding:** deferred items\n```\n\nOn startup, read `project.md` if it exists and summarize the last session in one sentence before asking whether to continue.\n\n## Anti-patterns\n\nThings that consistently fail regardless of style:\n\n- **Hierarchical pre-computed codec formats** with USABILITY / tone tags / shot layers. Over-engineering. Derive from the transcript at decision time.\n- **Hand-tuned moment-scoring functions.** The LLM picks better than any heuristic you'll write.\n- **Whisper SRT / phrase-level output.** Loses sub-second gap data. Always word-level verbatim.\n- **Running Whisper locally on CPU.** Slow and it normalizes fillers. Use hosted Scribe.\n- **Burning subtitles into base before compositing overlays.** Overlays hide them. (Hard Rule 1.)\n- **Single-pass filtergraph when you have overlays.** Double re-encodes. Use per-segment extract → concat.\n- **Linear animation easing.** Looks robotic. Always cubic.\n- **Unverified web fonts.** A failed load silently falls back to a system face. Assert the font loaded before rendering.\n- **Stock SFX on every transition.** Tie each effect to a visible event; cap the count.\n- **Hard audio cuts at segment boundaries.** Audible pops. (Hard Rule 3.)\n- **Typing text centered on the partial string.** Text slides left as it grows.\n- **Sequential sub-agents for multiple animations.** Always parallel.\n- **Editing before confirming the strategy.** Never.\n- **Re-transcribing cached sources.** Immutable outputs of immutable inputs.\n- **Assuming what kind of video it is.** Look first, ask second, edit last.\n","skills/manim-video/README.md":"# Manim Video Skill\n\nProduction pipeline for mathematical and technical animations using [Manim Community Edition](https://www.manim.community/).\n\n## What it does\n\nCreates 3Blue1Brown-style animated videos from text prompts. The agent handles the full pipeline: creative planning, Python code generation, rendering, scene stitching, and iterative refinement.\n\n## Use cases\n\n- **Concept explainers** — \"Explain how neural networks learn\"\n- **Equation derivations** — \"Animate the proof of the Pythagorean theorem\"\n- **Algorithm visualizations** — \"Show how quicksort works step by step\"\n- **Data stories** — \"Animate our before/after performance metrics\"\n- **Architecture diagrams** — \"Show our microservice architecture building up\"\n\n## Prerequisites\n\nPython 3.10+, Manim CE (`pip install manim`), LaTeX, ffmpeg.\n\n```bash\nbash skills/creative/manim-video/scripts/setup.sh\n```\n","skills/manim-video/SKILL.md":"---\nname: manim-video\ndescription: \"Production pipeline for mathematical and technical animations using Manim Community Edition. Creates 3Blue1Brown-style explainer videos, algorithm visualizations, equation derivations, architecture diagrams, and data stories. Use when users request: animated explanations, math animations, concept visualizations, algorithm walkthroughs, technical explainers, 3Blue1Brown style videos, or any programmatic animation with geometric/mathematical content.\"\nversion: 1.0.0\n---\n\n# Manim Video Production Pipeline\n\n## Creative Standard\n\nThis is educational cinema. Every frame teaches. Every animation reveals structure.\n\n**Before writing a single line of code**, articulate the narrative arc. What misconception does this correct? What is the \"aha moment\"? What visual story takes the viewer from confusion to understanding? The user's prompt is a starting point — interpret it with pedagogical ambition.\n\n**Geometry before algebra.** Show the shape first, the equation second. Visual memory encodes faster than symbolic memory. When the viewer sees the geometric pattern before the formula, the equation feels earned.\n\n**First-render excellence is non-negotiable.** The output must be visually clear and aesthetically cohesive without revision rounds. If something looks cluttered, poorly timed, or like \"AI-generated slides,\" it is wrong.\n\n**Opacity layering directs attention.** Never show everything at full brightness. Primary elements at 1.0, contextual elements at 0.4, structural elements (axes, grids) at 0.15. The brain processes visual salience in layers.\n\n**Breathing room.** Every animation needs `self.wait()` after it. The viewer needs time to absorb what just appeared. Never rush from one animation to the next. A 2-second pause after a key reveal is never wasted.\n\n**Cohesive visual language.** All scenes share a color palette, consistent typography sizing, matching animation speeds. A technically correct video where every scene uses random different colors is an aesthetic failure.\n\n## Prerequisites\n\nRun `scripts/setup.sh` to verify all dependencies. Requires: Python 3.10+, Manim Community Edition v0.20+ (`pip install manim`), LaTeX (`texlive-full` on Linux, `mactex` on macOS), and ffmpeg. Reference docs tested against Manim CE v0.20.1.\n\n## Modes\n\n| Mode | Input | Output | Reference |\n|------|-------|--------|-----------|\n| **Concept explainer** | Topic/concept | Animated explanation with geometric intuition | `references/scene-planning.md` |\n| **Equation derivation** | Math expressions | Step-by-step animated proof | `references/equations.md` |\n| **Algorithm visualization** | Algorithm description | Step-by-step execution with data structures | `references/graphs-and-data.md` |\n| **Data story** | Data/metrics | Animated charts, comparisons, counters | `references/graphs-and-data.md` |\n| **Architecture diagram** | System description | Components building up with connections | `references/mobjects.md` |\n| **Paper explainer** | Research paper | Key findings and methods animated | `references/scene-planning.md` |\n| **3D visualization** | 3D concept | Rotating surfaces, parametric curves, spatial geometry | `references/camera-and-3d.md` |\n\n## Stack\n\nSingle Python script per project. No browser, no Node.js, no GPU required.\n\n| Layer | Tool | Purpose |\n|-------|------|---------|\n| Core | Manim Community Edition | Scene rendering, animation engine |\n| Math | LaTeX (texlive/MiKTeX) | Equation rendering via `MathTex` |\n| Video I/O | ffmpeg | Scene stitching, format conversion, audio muxing |\n| TTS | ElevenLabs / Qwen3-TTS (optional) | Narration voiceover |\n\n## Pipeline\n\n```\nPLAN --> CODE --> RENDER --> STITCH --> AUDIO (optional) --> REVIEW\n```\n\n1. **PLAN** — Write `plan.md` with narrative arc, scene list, visual elements, color palette, voiceover script\n2. **CODE** — Write `script.py` with one class per scene, each independently renderable\n3. **RENDER** — `manim -ql script.py Scene1 Scene2 ...` for draft, `-qh` for production\n4. **STITCH** — ffmpeg concat of scene clips into `final.mp4`\n5. **AUDIO** (optional) — Add voiceover and/or background music via ffmpeg. See `references/rendering.md`\n6. **REVIEW** — Render preview stills, verify against plan, adjust\n\n## Project Structure\n\n```\nproject-name/\n  plan.md                # Narrative arc, scene breakdown\n  script.py              # All scenes in one file\n  concat.txt             # ffmpeg scene list\n  final.mp4              # Stitched output\n  media/                 # Auto-generated by Manim\n    videos/script/480p15/\n```\n\n## Creative Direction\n\n### Color Palettes\n\n| Palette | Background | Primary | Secondary | Accent | Use case |\n|---------|-----------|---------|-----------|--------|----------|\n| **Classic 3B1B** | `#1C1C1C` | `#58C4DD` (BLUE) | `#83C167` (GREEN) | `#FFFF00` (YELLOW) | General math/CS |\n| **Warm academic** | `#2D2B55` | `#FF6B6B` | `#FFD93D` | `#6BCB77` | Approachable |\n| **Neon tech** | `#0A0A0A` | `#00F5FF` | `#FF00FF` | `#39FF14` | Systems, architecture |\n| **Monochrome** | `#1A1A2E` | `#EAEAEA` | `#888888` | `#FFFFFF` | Minimalist |\n\n### Animation Speed\n\n| Context | run_time | self.wait() after |\n|---------|----------|-------------------|\n| Title/intro appear | 1.5s | 1.0s |\n| Key equation reveal | 2.0s | 2.0s |\n| Transform/morph | 1.5s | 1.5s |\n| Supporting label | 0.8s | 0.5s |\n| FadeOut cleanup | 0.5s | 0.3s |\n| \"Aha moment\" reveal | 2.5s | 3.0s |\n\n### Typography Scale\n\n| Role | Font size | Usage |\n|------|-----------|-------|\n| Title | 48 | Scene titles, opening text |\n| Heading | 36 | Section headers within a scene |\n| Body | 30 | Explanatory text |\n| Label | 24 | Annotations, axis labels |\n| Caption | 20 | Subtitles, fine print |\n\n### Fonts\n\n**Use monospace fonts for all text.** Manim's Pango renderer produces broken kerning with proportional fonts at all sizes. See `references/visual-design.md` for full recommendations.\n\n```python\nMONO = \"Menlo\"  # define once at top of file\n\nText(\"Fourier Series\", font_size=48, font=MONO, weight=BOLD)  # titles\nText(\"n=1: sin(x)\", font_size=20, font=MONO)                  # labels\nMathTex(r\"\\nabla L\")                                            # math (uses LaTeX)\n```\n\nMinimum `font_size=18` for readability.\n\n### Per-Scene Variation\n\nNever use identical config for all scenes. For each scene:\n- **Different dominant color** from the palette\n- **Different layout** — don't always center everything\n- **Different animation entry** — vary between Write, FadeIn, GrowFromCenter, Create\n- **Different visual weight** — some scenes dense, others sparse\n\n## Workflow\n\n### Step 1: Plan (plan.md)\n\nBefore any code, write `plan.md`. See `references/scene-planning.md` for the comprehensive template.\n\n### Step 2: Code (script.py)\n\nOne class per scene. Every scene is independently renderable.\n\n```python\nfrom manim import *\n\nBG = \"#1C1C1C\"\nPRIMARY = \"#58C4DD\"\nSECONDARY = \"#83C167\"\nACCENT = \"#FFFF00\"\nMONO = \"Menlo\"\n\nclass Scene1_Introduction(Scene):\n    def construct(self):\n        self.camera.background_color = BG\n        title = Text(\"Why Does This Work?\", font_size=48, color=PRIMARY, weight=BOLD, font=MONO)\n        self.add_subcaption(\"Why does this work?\", duration=2)\n        self.play(Write(title), run_time=1.5)\n        self.wait(1.0)\n        self.play(FadeOut(title), run_time=0.5)\n```\n\nKey patterns:\n- **Subtitles** on every animation: `self.add_subcaption(\"text\", duration=N)` or `subcaption=\"text\"` on `self.play()`\n- **Shared color constants** at file top for cross-scene consistency\n- **`self.camera.background_color`** set in every scene\n- **Clean exits** — FadeOut all mobjects at scene end: `self.play(FadeOut(Group(*self.mobjects)))`\n\n### Step 3: Render\n\n```bash\nmanim -ql script.py Scene1_Introduction Scene2_CoreConcept  # draft\nmanim -qh script.py Scene1_Introduction Scene2_CoreConcept  # production\n```\n\n### Step 4: Stitch\n\n```bash\ncat > concat.txt << 'EOF'\nfile 'media/videos/script/480p15/Scene1_Introduction.mp4'\nfile 'media/videos/script/480p15/Scene2_CoreConcept.mp4'\nEOF\nffmpeg -y -f concat -safe 0 -i concat.txt -c copy final.mp4\n```\n\n### Step 5: Review\n\n```bash\nmanim -ql --format=png -s script.py Scene2_CoreConcept  # preview still\n```\n\n## Critical Implementation Notes\n\n### Raw Strings for LaTeX\n```python\n# WRONG: MathTex(\"\\frac{1}{2}\")\n# RIGHT:\nMathTex(r\"\\frac{1}{2}\")\n```\n\n### buff >= 0.5 for Edge Text\n```python\nlabel.to_edge(DOWN, buff=0.5)  # never < 0.5\n```\n\n### FadeOut Before Replacing Text\n```python\nself.play(ReplacementTransform(note1, note2))  # not Write(note2) on top\n```\n\n### Never Animate Non-Added Mobjects\n```python\nself.play(Create(circle))  # must add first\nself.play(circle.animate.set_color(RED))  # then animate\n```\n\n## Performance Targets\n\n| Quality | Resolution | FPS | Speed |\n|---------|-----------|-----|-------|\n| `-ql` (draft) | 854x480 | 15 | 5-15s/scene |\n| `-qm` (medium) | 1280x720 | 30 | 15-60s/scene |\n| `-qh` (production) | 1920x1080 | 60 | 30-120s/scene |\n\nAlways iterate at `-ql`. Only render `-qh` for final output.\n\n## References\n\n| File | Contents |\n|------|----------|\n| `references/animations.md` | Core animations, rate functions, composition, `.animate` syntax, timing patterns |\n| `references/mobjects.md` | Text, shapes, VGroup/Group, positioning, styling, custom mobjects |\n| `references/visual-design.md` | 12 design principles, opacity layering, layout templates, color palettes |\n| `references/equations.md` | LaTeX in Manim, TransformMatchingTex, derivation patterns |\n| `references/graphs-and-data.md` | Axes, plotting, BarChart, animated data, algorithm visualization |\n| `references/camera-and-3d.md` | MovingCameraScene, ThreeDScene, 3D surfaces, camera control |\n| `references/scene-planning.md` | Narrative arcs, layout templates, scene transitions, planning template |\n| `references/rendering.md` | CLI reference, quality presets, ffmpeg, voiceover workflow, GIF export |\n| `references/troubleshooting.md` | LaTeX errors, animation errors, common mistakes, debugging |\n| `references/animation-design-thinking.md` | When to animate vs show static, decomposition, pacing, narration sync |\n| `references/updaters-and-trackers.md` | ValueTracker, add_updater, always_redraw, time-based updaters, patterns |\n| `references/paper-explainer.md` | Turning research papers into animations — workflow, templates, domain patterns |\n| `references/decorations.md` | SurroundingRectangle, Brace, arrows, DashedLine, Angle, annotation lifecycle |\n| `references/production-quality.md` | Pre-code, pre-render, post-render checklists, spatial layout, color, tempo |\n\n---\n\n## Creative Divergence (use only when user requests experimental/creative/unique output)\n\nIf the user asks for creative, experimental, or unconventional explanatory approaches, select a strategy and reason through it BEFORE designing the animation.\n\n- **SCAMPER** — when the user wants a fresh take on a standard explanation\n- **Assumption Reversal** — when the user wants to challenge how something is typically taught\n\n### SCAMPER Transformation\nTake a standard mathematical/technical visualization and transform it:\n- **Substitute**: replace the standard visual metaphor (number line → winding path, matrix → city grid)\n- **Combine**: merge two explanation approaches (algebraic + geometric simultaneously)\n- **Reverse**: derive backward — start from the result and deconstruct to axioms\n- **Modify**: exaggerate a parameter to show why it matters (10x the learning rate, 1000x the sample size)\n- **Eliminate**: remove all notation — explain purely through animation and spatial relationships\n\n### Assumption Reversal\n1. List what's \"standard\" about how this topic is visualized (left-to-right, 2D, discrete steps, formal notation)\n2. Pick the most fundamental assumption\n3. Reverse it (right-to-left derivation, 3D embedding of a 2D concept, continuous morphing instead of steps, zero notation)\n4. Explore what the reversal reveals that the standard approach hides\n","skills/manim-video/references/animation-design-thinking.md":"# Animation Design Thinking\n\nHow to decide WHAT to animate and HOW to structure it — before writing any code.\n\n## Should I animate this?\n\nNot everything benefits from animation. Motion adds cognitive load. Bad animation is worse than a good static diagram.\n\n**Animate when:**\n- A sequence unfolds over time (algorithm steps, derivation, pipeline stages)\n- Spatial relationships change (transformation, deformation, rotation)\n- Something is built from parts (construction, assembly, accumulation)\n- You're comparing states (before/after, method A vs method B)\n- Temporal evolution is the point (training curves, wave propagation, gradient descent)\n\n**Show static when:**\n- The concept is a single labeled diagram (circuit, anatomy, architecture overview)\n- Motion would distract from spatial layout\n- The viewer needs to study it carefully (dense table, reference chart)\n- The concept is already intuitive from a well-labeled figure\n\n**Rule of thumb:** If you'd explain it with \"first X, then Y, then Z\" — animate it. If you'd explain it by pointing at parts of one picture — show it static.\n\n## Decomposing a concept into animation\n\n### Step 1: Write the narration first\n\nBefore any code, write what the narrator would say. This determines:\n- **Order** — what concept comes first\n- **Duration** — how long each idea gets\n- **Visuals** — what the viewer must SEE when they HEAR each sentence\n\nA scene where the narration says \"the gradient points uphill\" must show a gradient arrow at that moment. If the visual doesn't match the audio, the viewer's brain splits attention and both tracks are lost.\n\n### Step 2: Identify visual beats\n\nA \"beat\" is a moment where something changes on screen. Mark each beat in your narration:\n\n```\n\"Consider a function f of x.\"         → [BEAT: axes + curve appear]\n\"At this point...\"                     → [BEAT: dot appears on curve]\n\"...the slope is positive.\"            → [BEAT: tangent line drawn]\n\"So the gradient tells us to go left.\" → [BEAT: arrow points left, dot moves]\n```\n\nEach beat is one `self.play()` call or a small group of simultaneous animations.\n\n### Step 3: Choose the right tool per beat\n\n| Visual need | Manim approach |\n|-------------|----------------|\n| Object appears for first time | `Create`, `Write`, `FadeIn`, `GrowFromCenter` |\n| Object transforms into another | `Transform`, `ReplacementTransform`, `FadeTransform` |\n| Attention drawn to existing object | `Indicate`, `Circumscribe`, `Flash`, `ShowPassingFlash` |\n| Continuous relationship maintained | `add_updater`, `always_redraw`, `ValueTracker` |\n| Object leaves the scene | `FadeOut`, `Uncreate`, `ShrinkToCenter` |\n| Static context that stays visible | `self.add()` (no animation) |\n\n## Pacing: the universal mistake is too fast\n\n### Timing rules\n\n| Content type | Minimum on-screen time |\n|-------------|----------------------|\n| New equation appearing | 2.0s animation + 2.0s pause |\n| New concept label | 1.0s animation + 1.0s pause |\n| Key insight (\"aha moment\") | 2.5s animation + 3.0s pause |\n| Supporting annotation | 0.8s animation + 0.5s pause |\n| Scene transition (FadeOut all) | 0.5s animation + 0.3s pause |\n\n### Breathing room\n\nAfter every reveal, add `self.wait()`. The viewer needs time to:\n1. Read the new text\n2. Connect it to what's already on screen\n3. Form an expectation about what comes next\n\n**No wait = the viewer is always behind you.** They're still reading the equation when you've already started transforming it.\n\n### Tempo variation\n\nMonotonous pacing feels like a lecture. Vary the tempo:\n- **Slow build** for core concepts (long run_time, long pauses)\n- **Quick succession** for supporting details (short run_time, minimal pauses)\n- **Dramatic pause** before the key reveal (extra `self.wait(2.0)` before the \"aha\")\n- **Rapid montage** for \"and this applies to X, Y, Z...\" sequences (`LaggedStart` with tight lag_ratio)\n\n## Narration synchronization\n\n### The \"see then hear\" principle\n\nThe visual should appear slightly BEFORE the narration describes it. When the viewer sees a circle appear and THEN hears \"consider a circle,\" the visual primes their brain for the concept. The reverse — hearing first, seeing second — creates confusion because they're searching the screen for something that isn't there yet.\n\n### Practical timing\n\n```python\n# Scene duration should match narration duration.\n# If narration for this scene is 8 seconds:\n# Total animation run_times + total self.wait() times = ~8 seconds.\n\n# Use manim-voiceover for automatic sync:\nwith self.voiceover(text=\"The gradient points downhill\") as tracker:\n    self.play(GrowArrow(gradient_arrow), run_time=tracker.duration)\n```\n\n## Equation decomposition strategy\n\n### The \"dim and reveal\" pattern\n\nWhen building a complex equation step by step:\n1. Show the full equation dimmed at `opacity=0.2` (sets expectation for where you're going)\n2. Highlight the first term at full opacity\n3. Explain it\n4. Highlight the next term, dim the first to `0.5` (it's now context)\n5. Repeat until the full equation is bright\n\nThis is better than building left-to-right because the viewer always sees the destination.\n\n### Term ordering\n\nAnimate terms in the order the viewer needs to understand them, not in the order they appear in the equation. For `E = mc²`:\n- Show `E` (the thing we want to know)\n- Then `m` (the input)\n- Then `c²` (the constant that makes it work)\n- Then the `=` (connecting them)\n\n## Architecture and pipeline diagrams\n\n### Box granularity\n\nThe most common mistake: too many boxes. Each box is a concept the viewer must track. Five boxes with clear labels beats twelve boxes with abbreviations.\n\n**Rule:** If two consecutive boxes could be labeled \"X\" and \"process X output,\" merge them into one box.\n\n### Animation strategy\n\nBuild pipelines left-to-right (or top-to-bottom) with arrows connecting them:\n1. First box appears alone → explain it\n2. Arrow grows from first to second → \"the output feeds into...\"\n3. Second box appears → explain it\n4. Repeat\n\nThen show data flowing through: `ShowPassingFlash` along the arrows, or a colored dot traversing the path.\n\n### The zoom-and-return pattern\n\nFor complex systems:\n1. Show the full overview (all boxes, small)\n2. Zoom into one box (`MovingCameraScene.camera.frame.animate`)\n3. Expand that box into its internal components\n4. Zoom back out to the overview\n5. Zoom into the next box\n\n## Common design mistakes\n\n1. **Animating everything at once.** The viewer can track 1-2 simultaneous animations. More than that and nothing registers.\n2. **No visual hierarchy.** Everything at the same opacity/size/color means nothing stands out. Use opacity layering.\n3. **Equations without context.** An equation appearing alone means nothing. Always show the geometric/visual interpretation first or simultaneously.\n4. **Skipping the \"why.\"** Showing HOW a transformation works without WHY it matters. Add a sentence/label explaining the purpose.\n5. **Identical pacing throughout.** Every animation at run_time=1.5, every wait at 1.0. Vary it.\n6. **Forgetting the audience.** A video for high schoolers needs different pacing and complexity than one for PhD students. Decide the audience in the planning phase.\n","skills/manim-video/references/animations.md":"# Animations Reference\n\n## Core Concept\n\nAn animation is a Python object that computes intermediate visual states of a mobject over time. Animations are objects passed to `self.play()`, not functions.\n\n`run_time` controls seconds (default: 1). Always specify it explicitly for important animations.\n\n## Creation Animations\n\n```python\nself.play(Create(circle))          # traces outline\nself.play(Write(equation))         # simulates handwriting (for Text/MathTex)\nself.play(FadeIn(group))           # opacity 0 -> 1\nself.play(GrowFromCenter(dot))     # scale 0 -> 1 from center\nself.play(DrawBorderThenFill(sq))  # outline first, then fill\n```\n\n## Removal Animations\n\n```python\nself.play(FadeOut(mobject))         # opacity 1 -> 0\nself.play(Uncreate(circle))        # reverse of Create\nself.play(ShrinkToCenter(group))   # scale 1 -> 0\n```\n\n## Transform Animations\n\n```python\n# Transform -- modifies the original in place\nself.play(Transform(circle, square))\n# After: circle IS the square (same object, new appearance)\n\n# ReplacementTransform -- replaces old with new\nself.play(ReplacementTransform(circle, square))\n# After: circle removed, square on screen\n\n# TransformMatchingTex -- smart equation morphing\neq1 = MathTex(r\"a^2 + b^2\")\neq2 = MathTex(r\"a^2 + b^2 = c^2\")\nself.play(TransformMatchingTex(eq1, eq2))\n```\n\n**Critical**: After `Transform(A, B)`, variable `A` references the on-screen mobject. Variable `B` is NOT on screen. Use `ReplacementTransform` when you want to work with `B` afterwards.\n\n## The .animate Syntax\n\n```python\nself.play(circle.animate.set_color(RED))\nself.play(circle.animate.shift(RIGHT * 2).scale(0.5))  # chain multiple\n```\n\n## Additional Creation Animations\n\n```python\nself.play(GrowFromPoint(circle, LEFT * 3))     # scale 0 -> 1 from a specific point\nself.play(GrowFromEdge(rect, DOWN))             # grow from one edge\nself.play(SpinInFromNothing(square))            # scale up while rotating (default PI/2)\nself.play(GrowArrow(arrow))                     # grows arrow from start to tip\n```\n\n## Movement Animations\n\n```python\n# Move a mobject along an arbitrary path\npath = Arc(radius=2, angle=PI)\nself.play(MoveAlongPath(dot, path), run_time=2)\n\n# Rotate (as a Transform, not .animate — supports about_point)\nself.play(Rotate(square, angle=PI / 2, about_point=ORIGIN), run_time=1.5)\n\n# Rotating (continuous rotation, updater-style — good for spinning objects)\nself.play(Rotating(gear, angle=TAU, run_time=4, rate_func=linear))\n```\n\n`MoveAlongPath` takes any `VMobject` as the path — use `Arc`, `CubicBezier`, `Line`, or a custom `VMobject`. Position is computed via `path.point_from_proportion()`.\n\n## Emphasis Animations\n\n```python\nself.play(Indicate(mobject))             # brief yellow flash + scale\nself.play(Circumscribe(mobject))         # draw rectangle around it\nself.play(Flash(point))                  # radial flash\nself.play(Wiggle(mobject))               # shake side to side\n```\n\n## Rate Functions\n\n```python\nself.play(FadeIn(mob), rate_func=smooth)          # default: ease in/out\nself.play(FadeIn(mob), rate_func=linear)           # constant speed\nself.play(FadeIn(mob), rate_func=rush_into)        # start slow, end fast\nself.play(FadeIn(mob), rate_func=rush_from)        # start fast, end slow\nself.play(FadeIn(mob), rate_func=there_and_back)   # animate then reverse\n```\n\n## Composition\n\n```python\n# Simultaneous\nself.play(FadeIn(title), Create(circle), run_time=2)\n\n# AnimationGroup with lag\nself.play(AnimationGroup(*[FadeIn(i) for i in items], lag_ratio=0.2))\n\n# LaggedStart\nself.play(LaggedStart(*[Write(l) for l in lines], lag_ratio=0.3, run_time=3))\n\n# Succession (sequential in one play call)\nself.play(Succession(FadeIn(title), Wait(0.5), Write(subtitle)))\n```\n\n## Updaters\n\n```python\ntracker = ValueTracker(0)\ndot = Dot().add_updater(lambda m: m.move_to(axes.c2p(tracker.get_value(), 0)))\nself.play(tracker.animate.set_value(5), run_time=3)\n```\n\n## Subtitles\n\n```python\n# Method 1: standalone\nself.add_subcaption(\"Key insight\", duration=2)\nself.play(Write(equation), run_time=2.0)\n\n# Method 2: inline\nself.play(Write(equation), subcaption=\"Key insight\", subcaption_duration=2)\n```\n\nManim auto-generates `.srt` subtitle files. Always add subcaptions for accessibility.\n\n## Timing Patterns\n\n```python\n# Pause-after-reveal\nself.play(Write(key_equation), run_time=2.0)\nself.wait(2.0)\n\n# Dim-and-focus\nself.play(old_content.animate.set_opacity(0.3), FadeIn(new_content))\n\n# Clean exit\nself.play(FadeOut(Group(*self.mobjects)), run_time=0.5)\nself.wait(0.3)\n```\n\n## Reactive Mobjects: always_redraw()\n\nRebuild a mobject from scratch every frame — essential when its geometry depends on other animated objects:\n\n```python\n# Brace that follows a resizing square\nbrace = always_redraw(Brace, square, UP)\nself.add(brace)\nself.play(square.animate.scale(2))  # brace auto-adjusts\n\n# Horizontal line that tracks a moving dot\nh_line = always_redraw(lambda: axes.get_h_line(dot.get_left()))\n\n# Label that always stays next to another mobject\nlabel = always_redraw(lambda: Text(\"here\", font_size=20).next_to(dot, UP, buff=0.2))\n```\n\nNote: `always_redraw` recreates the mobject every frame. For simple property tracking, use `add_updater` instead (cheaper):\n```python\nlabel.add_updater(lambda m: m.next_to(dot, UP))\n```\n\n## TracedPath — Trajectory Tracing\n\nDraw the path a point has traveled:\n\n```python\ndot = Dot(color=YELLOW)\npath = TracedPath(dot.get_center, stroke_color=YELLOW, stroke_width=2)\nself.add(dot, path)\nself.play(dot.animate.shift(RIGHT * 3 + UP * 2), run_time=2)\n# path shows the trail the dot left behind\n\n# Fading trail (dissipates over time):\npath = TracedPath(dot.get_center, dissipating_time=0.5, stroke_opacity=[0, 1])\n```\n\nUse cases: gradient descent paths, planetary orbits, function tracing, particle trajectories.\n\n## FadeTransform — Smoother Cross-Fades\n\n`Transform` morphs shapes through ugly intermediate warping. `FadeTransform` cross-fades with position matching — use it when source and target look different:\n\n```python\n# UGLY: Transform warps circle into square through a blob\nself.play(Transform(circle, square))\n\n# SMOOTH: FadeTransform cross-fades cleanly\nself.play(FadeTransform(circle, square))\n\n# FadeTransformPieces: per-submobject FadeTransform\nself.play(FadeTransformPieces(group1, group2))\n\n# TransformFromCopy: animate a COPY while keeping the original visible\nself.play(TransformFromCopy(source, target))\n# source stays on screen, a copy morphs into target\n```\n\n**Recommendation:** Use `FadeTransform` as default for dissimilar shapes. Use `Transform`/`ReplacementTransform` only for similar shapes (circle→ellipse, equation→equation).\n\n## ApplyMatrix — Linear Transformation Visualization\n\nAnimate a matrix transformation on mobjects:\n\n```python\n# Apply a 2x2 matrix to a grid\nmatrix = [[2, 1], [1, 1]]\nself.play(ApplyMatrix(matrix, number_plane), run_time=2)\n\n# Also works on individual mobjects\nself.play(ApplyMatrix([[0, -1], [1, 0]], square))  # 90-degree rotation\n```\n\nPairs with `LinearTransformationScene` — see `camera-and-3d.md`.\n\n## squish_rate_func — Time-Window Staggering\n\nCompress any rate function into a time window within an animation. Enables overlapping stagger without `LaggedStart`:\n\n```python\nself.play(\n    FadeIn(a, rate_func=squish_rate_func(smooth, 0, 0.5)),    # 0% to 50%\n    FadeIn(b, rate_func=squish_rate_func(smooth, 0.25, 0.75)), # 25% to 75%\n    FadeIn(c, rate_func=squish_rate_func(smooth, 0.5, 1.0)),  # 50% to 100%\n    run_time=2\n)\n```\n\nMore precise than `LaggedStart` when you need exact overlap control.\n\n## Additional Rate Functions\n\n```python\nfrom manim import (\n    smooth, linear, rush_into, rush_from,\n    there_and_back, there_and_back_with_pause,\n    running_start, double_smooth, wiggle,\n    lingering, exponential_decay, not_quite_there,\n    squish_rate_func\n)\n\n# running_start: pulls back before going forward (anticipation)\nself.play(FadeIn(mob, rate_func=running_start))\n\n# there_and_back_with_pause: goes there, holds, comes back\nself.play(mob.animate.shift(UP), rate_func=there_and_back_with_pause)\n\n# not_quite_there: stops at a fraction of the full animation\nself.play(FadeIn(mob, rate_func=not_quite_there(0.7)))\n```\n\n## ShowIncreasingSubsets / ShowSubmobjectsOneByOne\n\nReveal group members progressively — ideal for algorithm visualization:\n\n```python\n# Reveal array elements one at a time\narray = Group(*[Square() for _ in range(8)]).arrange(RIGHT)\nself.play(ShowIncreasingSubsets(array), run_time=3)\n\n# Show submobjects with staggered appearance\nself.play(ShowSubmobjectsOneByOne(code_lines), run_time=4)\n```\n\n## ShowPassingFlash\n\nA flash of light travels along a path:\n\n```python\n# Flash traveling along a curve\nself.play(ShowPassingFlash(curve.copy().set_color(YELLOW), time_width=0.3))\n\n# Great for: data flow, electrical signals, network traffic\n```\n","skills/manim-video/references/camera-and-3d.md":"# Camera and 3D Reference\n\n## MovingCameraScene (2D Camera Control)\n\n```python\nclass ZoomExample(MovingCameraScene):\n    def construct(self):\n        circle = Circle(radius=2, color=BLUE)\n        self.play(Create(circle))\n        # Zoom in\n        self.play(self.camera.frame.animate.set(width=4).move_to(circle.get_top()), run_time=2)\n        self.wait(2)\n        # Zoom back out\n        self.play(self.camera.frame.animate.set(width=14.222).move_to(ORIGIN), run_time=2)\n```\n\n### Camera Operations\n\n```python\nself.camera.frame.animate.set(width=6)     # zoom in\nself.camera.frame.animate.set(width=20)    # zoom out\nself.camera.frame.animate.move_to(target)  # pan\nself.camera.frame.save_state()             # save\nself.play(Restore(self.camera.frame))      # restore\n```\n\n## ThreeDScene\n\n```python\nclass ThreeDExample(ThreeDScene):\n    def construct(self):\n        self.set_camera_orientation(phi=60*DEGREES, theta=-45*DEGREES)\n        axes = ThreeDAxes()\n        surface = Surface(\n            lambda u, v: axes.c2p(u, v, np.sin(u) * np.cos(v)),\n            u_range=[-PI, PI], v_range=[-PI, PI], resolution=(30, 30)\n        )\n        surface.set_color_by_gradient(BLUE, GREEN, YELLOW)\n        self.play(Create(axes), Create(surface))\n        self.begin_ambient_camera_rotation(rate=0.2)\n        self.wait(5)\n        self.stop_ambient_camera_rotation()\n```\n\n### Camera Control in 3D\n\n```python\nself.set_camera_orientation(phi=70*DEGREES, theta=-45*DEGREES)\nself.move_camera(phi=45*DEGREES, theta=30*DEGREES, run_time=2)\nself.begin_ambient_camera_rotation(rate=0.2)\n```\n\n### 3D Mobjects\n\n```python\nsphere = Sphere(radius=1).set_color(BLUE).set_opacity(0.7)\ncube = Cube(side_length=2, fill_color=GREEN, fill_opacity=0.5)\narrow = Arrow3D(start=ORIGIN, end=[2, 1, 1], color=RED)\n# 2D text facing camera:\nlabel = Text(\"Label\", font_size=30)\nself.add_fixed_in_frame_mobjects(label)\n```\n\n### Parametric Curves\n\n```python\nhelix = ParametricFunction(\n    lambda t: [np.cos(t), np.sin(t), t / (2*PI)],\n    t_range=[0, 4*PI], color=YELLOW\n)\n```\n\n## When to Use 3D\n- Surfaces, vector fields, spatial geometry, 3D transforms\n## When NOT to Use 3D\n- 2D concepts, text-heavy scenes, flat data (bar charts, time series)\n\n## ZoomedScene — Inset Zoom\n\nShow a magnified inset of a detail while keeping the full view visible:\n\n```python\nclass ZoomExample(ZoomedScene):\n    def __init__(self, **kwargs):\n        super().__init__(\n            zoom_factor=0.3,           # how much of the scene the zoom box covers\n            zoomed_display_height=3,   # size of the inset\n            zoomed_display_width=3,\n            zoomed_camera_frame_starting_position=ORIGIN,\n            **kwargs\n        )\n\n    def construct(self):\n        self.camera.background_color = BG\n        # ... create your scene content ...\n\n        # Activate the zoom\n        self.activate_zooming()\n\n        # Move the zoom frame to a point of interest\n        self.play(self.zoomed_camera.frame.animate.move_to(detail_point))\n        self.wait(2)\n\n        # Deactivate\n        self.play(self.get_zoomed_display_pop_out_animation(), rate_func=lambda t: smooth(1-t))\n```\n\nUse cases: zooming into a specific term in an equation, showing fine detail in a diagram, magnifying a region of a plot.\n\n## LinearTransformationScene — Linear Algebra\n\nPre-built scene with basis vectors and grid for visualizing matrix transformations:\n\n```python\nclass LinearTransformExample(LinearTransformationScene):\n    def __init__(self, **kwargs):\n        super().__init__(\n            show_coordinates=True,\n            show_basis_vectors=True,\n            **kwargs\n        )\n\n    def construct(self):\n        matrix = [[2, 1], [1, 1]]\n\n        # Add a vector before applying the transform\n        vector = self.get_vector([1, 2], color=YELLOW)\n        self.add_vector(vector)\n\n        # Apply the transformation — grid, basis vectors, and your vector all transform\n        self.apply_matrix(matrix)\n        self.wait(2)\n```\n\nThis produces the signature 3Blue1Brown \"Essence of Linear Algebra\" look — grid lines deforming, basis vectors stretching, determinant visualized through area change.\n","skills/manim-video/references/decorations.md":"# Decorations and Visual Polish\n\nDecorations are mobjects that annotate, highlight, or frame other mobjects. They turn a technically correct animation into a visually polished one.\n\n## SurroundingRectangle\n\nDraws a rectangle around any mobject. The go-to for highlighting:\n\n```python\nhighlight = SurroundingRectangle(\n    equation[2],            # the term to highlight\n    color=YELLOW,\n    buff=0.15,              # padding between content and border\n    corner_radius=0.1,      # rounded corners\n    stroke_width=2\n)\nself.play(Create(highlight))\nself.wait(1)\nself.play(FadeOut(highlight))\n```\n\n### Around part of an equation\n\n```python\neq = MathTex(r\"E\", r\"=\", r\"m\", r\"c^2\")\nbox = SurroundingRectangle(eq[2:], color=YELLOW, buff=0.1)  # highlight \"mc²\"\nlabel = Text(\"mass-energy\", font_size=18, font=\"Menlo\", color=YELLOW)\nlabel.next_to(box, DOWN, buff=0.2)\nself.play(Create(box), FadeIn(label))\n```\n\n## BackgroundRectangle\n\nSemi-transparent background behind text for readability over complex scenes:\n\n```python\nbg = BackgroundRectangle(equation, fill_opacity=0.7, buff=0.2, color=BLACK)\nself.play(FadeIn(bg), Write(equation))\n\n# Or using set_stroke for a \"backdrop\" effect on the text itself:\nlabel.set_stroke(BLACK, width=5, background=True)\n```\n\nThe `set_stroke(background=True)` approach is cleaner for text labels over graphs/diagrams.\n\n## Brace and BraceLabel\n\nCurly braces that annotate sections of a diagram or equation:\n\n```python\nbrace = Brace(equation[2:4], DOWN, color=YELLOW)\nbrace_label = brace.get_text(\"these terms\", font_size=20)\nself.play(GrowFromCenter(brace), FadeIn(brace_label))\n\n# Between two specific points\nbrace = BraceBetweenPoints(point_a, point_b, direction=UP)\n```\n\n### Brace placement\n\n```python\n# Below a group\nBrace(group, DOWN)\n# Above a group\nBrace(group, UP)\n# Left of a group\nBrace(group, LEFT)\n# Right of a group\nBrace(group, RIGHT)\n```\n\n## Arrows for Annotation\n\n### Straight arrows pointing to mobjects\n\n```python\narrow = Arrow(\n    start=label.get_bottom(),\n    end=target.get_top(),\n    color=YELLOW,\n    stroke_width=2,\n    buff=0.1,                    # gap between arrow tip and target\n    max_tip_length_to_length_ratio=0.15  # small arrowhead\n)\nself.play(GrowArrow(arrow), FadeIn(label))\n```\n\n### Curved arrows\n\n```python\narrow = CurvedArrow(\n    start_point=source.get_right(),\n    end_point=target.get_left(),\n    angle=PI/4,                  # curve angle\n    color=PRIMARY\n)\n```\n\n### Labeling with arrows\n\n```python\n# LabeledArrow: arrow with built-in text label\narr = LabeledArrow(\n    Text(\"gradient\", font_size=16, font=\"Menlo\"),\n    start=point_a, end=point_b, color=RED\n)\n```\n\n## DashedLine and DashedVMobject\n\n```python\n# Dashed line (for asymptotes, construction lines, implied connections)\nasymptote = DashedLine(\n    axes.c2p(2, -3), axes.c2p(2, 3),\n    color=YELLOW, dash_length=0.15\n)\n\n# Make any VMobject dashed\ndashed_circle = DashedVMobject(Circle(radius=2, color=BLUE), num_dashes=30)\n```\n\n## Angle and RightAngle Markers\n\n```python\nline1 = Line(ORIGIN, RIGHT * 2)\nline2 = Line(ORIGIN, UP * 2 + RIGHT)\n\n# Angle arc between two lines\nangle = Angle(line1, line2, radius=0.5, color=YELLOW)\nangle_value = angle.get_value()  # radians\n\n# Right angle marker (the small square)\nright_angle = RightAngle(line1, Line(ORIGIN, UP * 2), length=0.3, color=WHITE)\n```\n\n## Cross (strikethrough)\n\nMark something as wrong or deprecated:\n\n```python\ncross = Cross(old_equation, color=RED, stroke_width=4)\nself.play(Create(cross))\n# Then show the correct version\n```\n\n## Underline\n\n```python\nunderline = Underline(important_text, color=ACCENT, stroke_width=3)\nself.play(Create(underline))\n```\n\n## Color Highlighting Workflow\n\n### Method 1: At creation with t2c\n\n```python\ntext = Text(\"The gradient is negative here\", t2c={\"gradient\": BLUE, \"negative\": RED})\n```\n\n### Method 2: set_color_by_tex after creation\n\n```python\neq = MathTex(r\"\\nabla L = -\\frac{\\partial L}{\\partial w}\")\neq.set_color_by_tex(r\"\\nabla\", BLUE)\neq.set_color_by_tex(r\"\\partial\", RED)\n```\n\n### Method 3: Index into submobjects\n\n```python\neq = MathTex(r\"a\", r\"+\", r\"b\", r\"=\", r\"c\")\neq[0].set_color(RED)    # \"a\"\neq[2].set_color(BLUE)   # \"b\"\neq[4].set_color(GREEN)  # \"c\"\n```\n\n## Combining Annotations\n\nLayer multiple annotations for emphasis:\n\n```python\n# Highlight a term, add a brace, and an arrow — in sequence\nbox = SurroundingRectangle(eq[2], color=YELLOW, buff=0.1)\nbrace = Brace(eq[2], DOWN, color=YELLOW)\nlabel = brace.get_text(\"learning rate\", font_size=18)\n\nself.play(Create(box))\nself.wait(0.5)\nself.play(FadeOut(box), GrowFromCenter(brace), FadeIn(label))\nself.wait(1.5)\nself.play(FadeOut(brace), FadeOut(label))\n```\n\n### The annotation lifecycle\n\nAnnotations should follow a rhythm:\n1. **Appear** — draw attention (Create, GrowFromCenter)\n2. **Hold** — viewer reads and understands (self.wait)\n3. **Disappear** — clear the stage for the next thing (FadeOut)\n\nNever leave annotations on screen indefinitely — they become visual noise once their purpose is served.\n","skills/manim-video/references/equations.md":"# Equations and LaTeX Reference\n\n## Basic LaTeX\n\n```python\neq = MathTex(r\"E = mc^2\")\neq = MathTex(r\"f(x) &= x^2 + 2x + 1 \\\\ &= (x + 1)^2\")  # multi-line aligned\n```\n\n**Always use raw strings (`r\"\"`).**\n\n## Step-by-Step Derivations\n\n```python\nstep1 = MathTex(r\"a^2 + b^2 = c^2\")\nstep2 = MathTex(r\"a^2 = c^2 - b^2\")\nself.play(Write(step1), run_time=1.5)\nself.wait(1.5)\nself.play(TransformMatchingTex(step1, step2), run_time=1.5)\n```\n\n## Selective Color\n\n```python\neq = MathTex(r\"a^2\", r\"+\", r\"b^2\", r\"=\", r\"c^2\")\neq[0].set_color(RED)\neq[4].set_color(GREEN)\n```\n\n## Building Incrementally\n\n```python\nparts = MathTex(r\"f(x)\", r\"=\", r\"\\sum_{n=0}^{\\infty}\", r\"\\frac{f^{(n)}(a)}{n!}\", r\"(x-a)^n\")\nself.play(Write(parts[0:2]))\nself.wait(0.5)\nself.play(Write(parts[2]))\nself.wait(0.5)\nself.play(Write(parts[3:]))\n```\n\n## Highlighting\n\n```python\nhighlight = SurroundingRectangle(eq[2], color=YELLOW, buff=0.1)\nself.play(Create(highlight))\nself.play(Indicate(eq[4], color=YELLOW))\n```\n\n## Annotation\n\n```python\nbrace = Brace(eq, DOWN, color=YELLOW)\nlabel = brace.get_text(\"Fundamental Theorem\", font_size=24)\nself.play(GrowFromCenter(brace), Write(label))\n```\n\n## Common LaTeX\n\n```python\nMathTex(r\"\\frac{a}{b}\")                  # fraction\nMathTex(r\"\\alpha, \\beta, \\gamma\")         # Greek\nMathTex(r\"\\sum_{i=1}^{n} x_i\")           # summation\nMathTex(r\"\\int_{0}^{\\infty} e^{-x} dx\")  # integral\nMathTex(r\"\\vec{v}\")                       # vector\nMathTex(r\"\\lim_{x \\to \\infty} f(x)\")    # limit\n```\n\n## Matrices\n\n`MathTex` supports standard LaTeX matrix environments via `amsmath` (loaded by default):\n\n```python\n# Bracketed matrix\nMathTex(r\"\\begin{bmatrix} 1 & 0 \\\\ 0 & 1 \\end{bmatrix}\")\n\n# Parenthesized matrix\nMathTex(r\"\\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}\")\n\n# Determinant (vertical bars)\nMathTex(r\"\\begin{vmatrix} a & b \\\\ c & d \\end{vmatrix}\")\n\n# Plain (no delimiters)\nMathTex(r\"\\begin{matrix} x_1 \\\\ x_2 \\\\ x_3 \\end{matrix}\")\n```\n\nFor matrices you need to animate element-by-element or color individual entries, use the `IntegerMatrix`, `DecimalMatrix`, or `MobjectMatrix` mobjects instead — see `mobjects.md`.\n\n## Cases and Piecewise Functions\n\n```python\nMathTex(r\"\"\"\n    f(x) = \\begin{cases}\n        x^2    & \\text{if } x \\geq 0 \\\\\n        -x^2   & \\text{if } x < 0\n    \\end{cases}\n\"\"\")\n```\n\n## Aligned Environments\n\nFor multi-line derivations with alignment, use `aligned` inside `MathTex`:\n\n```python\nMathTex(r\"\"\"\n    \\begin{aligned}\n        \\nabla \\cdot \\mathbf{E} &= \\frac{\\rho}{\\epsilon_0} \\\\\n        \\nabla \\cdot \\mathbf{B} &= 0 \\\\\n        \\nabla \\times \\mathbf{E} &= -\\frac{\\partial \\mathbf{B}}{\\partial t} \\\\\n        \\nabla \\times \\mathbf{B} &= \\mu_0 \\mathbf{J} + \\mu_0 \\epsilon_0 \\frac{\\partial \\mathbf{E}}{\\partial t}\n    \\end{aligned}\n\"\"\")\n```\n\nNote: `MathTex` wraps content in `align*` by default. Override with `tex_environment` if needed:\n```python\nMathTex(r\"...\", tex_environment=\"gather*\")\n```\n\n## Derivation Pattern\n\n```python\nclass DerivationScene(Scene):\n    def construct(self):\n        self.camera.background_color = BG\n        s1 = MathTex(r\"ax^2 + bx + c = 0\")\n        self.play(Write(s1))\n        self.wait(1.5)\n        s2 = MathTex(r\"x^2 + \\frac{b}{a}x + \\frac{c}{a} = 0\")\n        s2.next_to(s1, DOWN, buff=0.8)\n        self.play(s1.animate.set_opacity(0.4), TransformMatchingTex(s1.copy(), s2))\n```\n\n## substrings_to_isolate for Complex Equations\n\nFor dense equations where manually splitting into parts is impractical, use `substrings_to_isolate` to tell Manim which substrings to track as individual elements:\n\n```python\n# Without isolation — the whole expression is one blob\nlagrangian = MathTex(\n    r\"\\mathcal{L} = \\bar{\\psi}(i \\gamma^\\mu D_\\mu - m)\\psi - \\tfrac{1}{4}F_{\\mu\\nu}F^{\\mu\\nu}\"\n)\n\n# With isolation — each named substring is a separate submobject\nlagrangian = MathTex(\n    r\"\\mathcal{L} = \\bar{\\psi}(i \\gamma^\\mu D_\\mu - m)\\psi - \\tfrac{1}{4}F_{\\mu\\nu}F^{\\mu\\nu}\",\n    substrings_to_isolate=[r\"\\psi\", r\"D_\\mu\", r\"\\gamma^\\mu\", r\"F_{\\mu\\nu}\"]\n)\n# Now you can color individual terms\nlagrangian.set_color_by_tex(r\"\\psi\", BLUE)\nlagrangian.set_color_by_tex(r\"F_{\\mu\\nu}\", YELLOW)\n```\n\nEssential for `TransformMatchingTex` on complex equations — without isolation, matching fails on dense expressions.\n\n## Multi-Line Complex Equations\n\nFor equations with multiple related lines, pass each line as a separate argument:\n\n```python\nmaxwell = MathTex(\n    r\"\\nabla \\cdot \\mathbf{E} = \\frac{\\rho}{\\epsilon_0}\",\n    r\"\\nabla \\times \\mathbf{B} = \\mu_0\\mathbf{J} + \\mu_0\\epsilon_0\\frac{\\partial \\mathbf{E}}{\\partial t}\"\n).arrange(DOWN)\n\n# Each line is a separate submobject — animate independently\nself.play(Write(maxwell[0]))\nself.wait(1)\nself.play(Write(maxwell[1]))\n```\n\n## TransformMatchingTex with key_map\n\nMap specific substrings between source and target equations during transformation:\n\n```python\neq1 = MathTex(r\"A^2 + B^2 = C^2\")\neq2 = MathTex(r\"A^2 = C^2 - B^2\")\n\nself.play(TransformMatchingTex(\n    eq1, eq2,\n    key_map={\"+\": \"-\"},   # map \"+\" in source to \"-\" in target\n    path_arc=PI / 2,      # arc the pieces into position\n))\n```\n\n## set_color_by_tex — Color by Substring\n\n```python\neq = MathTex(r\"E = mc^2\")\neq.set_color_by_tex(\"E\", BLUE)\neq.set_color_by_tex(\"m\", RED)\neq.set_color_by_tex(\"c\", GREEN)\n```\n\n## TransformMatchingTex with matched_keys\n\nWhen matching substrings are ambiguous, specify which to align explicitly:\n\n```python\nkw = dict(font_size=72, t2c={\"A\": BLUE, \"B\": TEAL, \"C\": GREEN})\nlines = [\n    MathTex(r\"A^2 + B^2 = C^2\", **kw),\n    MathTex(r\"A^2 = C^2 - B^2\", **kw),\n    MathTex(r\"A^2 = (C + B)(C - B)\", **kw),\n    MathTex(r\"A = \\sqrt{(C + B)(C - B)}\", **kw),\n]\n\nself.play(TransformMatchingTex(\n    lines[0].copy(), lines[1],\n    matched_keys=[\"A^2\", \"B^2\", \"C^2\"],  # explicitly match these\n    key_map={\"+\": \"-\"},                    # map + to -\n    path_arc=PI / 2,                       # arc pieces into position\n))\n```\n\nWithout `matched_keys`, the animation matches the longest common substrings, which can produce unexpected results on complex equations (e.g., \"^2 = C^2\" matching across terms).\n","skills/manim-video/references/graphs-and-data.md":"# Graphs, Plots, and Data Visualization\n\n## Axes\n\n```python\naxes = Axes(\n    x_range=[-3, 3, 1], y_range=[-2, 2, 1],\n    x_length=8, y_length=5,\n    axis_config={\"include_numbers\": True, \"font_size\": 24}\n)\naxes.set_opacity(0.15)  # structural element\nx_label = axes.get_x_axis_label(r\"x\")\n```\n\n## Plotting\n\n```python\ngraph = axes.plot(lambda x: x**2, color=BLUE)\ngraph_label = axes.get_graph_label(graph, label=r\"x^2\", x_val=2)\narea = axes.get_area(graph, x_range=[0, 2], color=BLUE, opacity=0.3)\n```\n\n## Animated Plotting\n\n```python\nself.play(Create(graph), run_time=3)  # trace the graph\n\n# Moving dot along curve\ndot = Dot(color=YELLOW).move_to(axes.c2p(0, 0))\nself.play(MoveAlongPath(dot, graph), run_time=3)\n\n# Dynamic parameter\ntracker = ValueTracker(1)\ndynamic = always_redraw(lambda: axes.plot(lambda x: tracker.get_value() * x**2, color=BLUE))\nself.add(dynamic)\nself.play(tracker.animate.set_value(3), run_time=2)\n```\n\n## Bar Charts\n\n```python\nchart = BarChart(\n    values=[4, 6, 2, 8, 5], bar_names=[\"A\", \"B\", \"C\", \"D\", \"E\"],\n    y_range=[0, 10, 2], bar_colors=[RED, GREEN, BLUE, YELLOW, PURPLE]\n)\nself.play(Create(chart), run_time=2)\nself.play(chart.animate.change_bar_values([6, 3, 7, 4, 9]))\n```\n\n## Number Lines\n\n```python\nnl = NumberLine(x_range=[0, 10, 1], length=10, include_numbers=True)\npointer = Arrow(nl.n2p(3) + UP * 0.5, nl.n2p(3), color=RED, buff=0)\ntracker = ValueTracker(3)\npointer.add_updater(lambda m: m.put_start_and_end_on(\n    nl.n2p(tracker.get_value()) + UP * 0.5, nl.n2p(tracker.get_value())))\nself.play(tracker.animate.set_value(8), run_time=2)\n```\n\n## Animated Counters\n\n```python\ncounter = DecimalNumber(0, font_size=72, num_decimal_places=0)\nself.play(counter.animate.set_value(1000), run_time=3, rate_func=rush_from)\n```\n\n## Algorithm Visualization Pattern\n\n```python\nvalues = [5, 2, 8, 1, 9, 3]\nbars = VGroup(*[\n    Rectangle(width=0.6, height=v * 0.4, color=BLUE, fill_opacity=0.7)\n    for v in values\n]).arrange(RIGHT, buff=0.2, aligned_edge=DOWN).move_to(ORIGIN)\nself.play(LaggedStart(*[GrowFromEdge(b, DOWN) for b in bars], lag_ratio=0.1))\n# Highlight, swap, etc.\n```\n\n## Data Story Pattern\n\n```python\n# Before/After comparison\nbefore = BarChart(values=[3, 5, 2], bar_colors=[RED]*3).shift(LEFT * 3)\nafter = BarChart(values=[8, 9, 7], bar_colors=[GREEN]*3).shift(RIGHT * 3)\nself.play(Create(before)); self.wait(1)\nself.play(Create(after)); self.wait(1)\narrow = Arrow(before.get_right(), after.get_left(), color=YELLOW)\nlabel = Text(\"+167%\", font_size=36, color=YELLOW).next_to(arrow, UP)\nself.play(GrowArrow(arrow), Write(label))\n```\n\n## Graph / DiGraph — Graph Theory Visualization\n\nBuilt-in graph mobjects with automatic layout:\n\n```python\n# Undirected graph\ng = Graph(\n    vertices=[1, 2, 3, 4, 5],\n    edges=[(1, 2), (2, 3), (3, 4), (4, 5), (5, 1), (1, 3)],\n    layout=\"spring\",  # or \"circular\", \"kamada_kawai\", \"planar\", \"tree\"\n    labels=True,\n    vertex_config={\"fill_color\": PRIMARY},\n    edge_config={\"stroke_color\": SUBTLE},\n)\nself.play(Create(g))\n\n# Directed graph\ndg = DiGraph(\n    vertices=[\"A\", \"B\", \"C\"],\n    edges=[(\"A\", \"B\"), (\"B\", \"C\"), (\"C\", \"A\")],\n    layout=\"circular\",\n    labels=True,\n    edge_config={(\"A\", \"B\"): {\"stroke_color\": RED}},\n)\n\n# Add/remove vertices and edges dynamically\nself.play(g.animate.add_vertices(6, positions={6: RIGHT * 2}))\nself.play(g.animate.add_edges((1, 6)))\nself.play(g.animate.remove_vertices(3))\n```\n\nLayout algorithms: `\"spring\"`, `\"circular\"`, `\"kamada_kawai\"`, `\"planar\"`, `\"spectral\"`, `\"tree\"` (for rooted trees, specify `root=`).\n\n## ArrowVectorField / StreamLines — Vector Fields\n\n```python\n# Arrow field: arrows showing direction at each point\nfield = ArrowVectorField(\n    lambda pos: np.array([-pos[1], pos[0], 0]),  # rotation field\n    x_range=[-3, 3], y_range=[-3, 3],\n    colors=[BLUE, GREEN, YELLOW, RED]\n)\nself.play(Create(field))\n\n# StreamLines: flowing particle traces through the field\nstream = StreamLines(\n    lambda pos: np.array([-pos[1], pos[0], 0]),\n    stroke_width=2, max_anchors_per_line=30\n)\nself.add(stream)\nstream.start_animation(warm_up=True, flow_speed=1.5)\nself.wait(3)\nstream.end_animation()\n```\n\nUse cases: electromagnetic fields, fluid flow, gradient fields, ODE phase portraits.\n\n## ComplexPlane / PolarPlane\n\n```python\n# Complex plane with Re/Im labels\ncplane = ComplexPlane().add_coordinates()\ndot = Dot(cplane.n2p(2 + 1j), color=YELLOW)\nlabel = Text(\"2+i\", font_size=20).next_to(dot, UR, buff=0.1)\n\n# Apply complex function to the plane\nself.play(cplane.animate.apply_complex_function(lambda z: z**2), run_time=3)\n\n# Polar plane\npolar = PolarPlane(radius_max=3).add_coordinates()\n```\n","skills/manim-video/references/mobjects.md":"# Mobjects Reference\n\nEverything visible on screen is a Mobject. They have position, color, opacity, and can be animated.\n\n## Text\n\n```python\ntitle = Text(\"Hello World\", font_size=48, color=BLUE)\neq = MathTex(r\"E = mc^2\", font_size=40)\n\n# Multi-part (for selective coloring)\neq = MathTex(r\"a^2\", r\"+\", r\"b^2\", r\"=\", r\"c^2\")\neq[0].set_color(RED)\neq[4].set_color(BLUE)\n\n# Mixed text and math\nt = Tex(r\"The area is $\\pi r^2$\", font_size=36)\n\n# Styled markup\nt = MarkupText('<span foreground=\"#58C4DD\">Blue</span> text', font_size=30)\n```\n\n**Always use raw strings (`r\"\"`) for any string with backslashes.**\n\n## Shapes\n\n```python\ncircle = Circle(radius=1, color=BLUE, fill_opacity=0.5)\nsquare = Square(side_length=2, color=RED)\nrect = Rectangle(width=4, height=2, color=GREEN)\ndot = Dot(point=ORIGIN, radius=0.08, color=YELLOW)\nline = Line(LEFT * 2, RIGHT * 2, color=WHITE)\narrow = Arrow(LEFT, RIGHT, color=ORANGE)\nrrect = RoundedRectangle(corner_radius=0.3, width=4, height=2)\nbrace = Brace(rect, DOWN, color=YELLOW)\n```\n\n## Polygons and Arcs\n\n```python\n# Arbitrary polygon from vertices\npoly = Polygon(LEFT, UP * 2, RIGHT, color=GREEN, fill_opacity=0.3)\n\n# Regular n-sided polygon\nhexagon = RegularPolygon(n=6, color=TEAL, fill_opacity=0.4)\n\n# Triangle (shorthand for RegularPolygon(n=3))\ntri = Triangle(color=YELLOW, fill_opacity=0.5)\n\n# Arc (portion of a circle)\narc = Arc(radius=2, start_angle=0, angle=PI / 2, color=BLUE)\n\n# Arc between two points\narc_between = ArcBetweenPoints(LEFT * 2, RIGHT * 2, angle=TAU / 4, color=RED)\n\n# Curved arrow (arc with tip)\ncurved_arrow = CurvedArrow(LEFT * 2, RIGHT * 2, color=ORANGE)\n```\n\n## Sectors and Annuli\n\n```python\n# Sector (pie slice)\nsector = Sector(outer_radius=2, start_angle=0, angle=PI / 3, fill_opacity=0.7, color=BLUE)\n\n# Annulus (ring)\nring = Annulus(inner_radius=1, outer_radius=2, fill_opacity=0.5, color=GREEN)\n\n# Annular sector (partial ring)\npartial_ring = AnnularSector(\n    inner_radius=1, outer_radius=2,\n    angle=PI / 2, start_angle=0,\n    fill_opacity=0.7, color=TEAL\n)\n\n# Cutout (punch holes in a shape)\nbackground = Square(side_length=4, fill_opacity=1, color=BLUE)\nhole = Circle(radius=0.5)\ncutout = Cutout(background, hole, fill_opacity=1, color=BLUE)\n```\n\nUse cases: pie charts, ring progress indicators, Venn diagrams with arcs, geometric proofs.\n\n## Positioning\n\n```python\nmob.move_to(ORIGIN)                        # center\nmob.move_to(UP * 2 + RIGHT)               # relative\nlabel.next_to(circle, DOWN, buff=0.3)     # next to another\ntitle.to_edge(UP, buff=0.5)               # screen edge (buff >= 0.5!)\nmob.to_corner(UL, buff=0.5)               # corner\n```\n\n## VGroup vs Group\n\n**VGroup** is for collections of shapes (VMobjects only — Circle, Square, Arrow, Line, MathTex):\n```python\nshapes = VGroup(circle, square, arrow)\nshapes.arrange(DOWN, buff=0.5)\nshapes.set_color(BLUE)\n```\n\n**Group** is for mixed collections (Text + shapes, or any Mobject types):\n```python\n# Text objects are Mobjects, not VMobjects — use Group when mixing\nlabeled_shape = Group(circle, Text(\"Label\").next_to(circle, DOWN))\nlabeled_shape.move_to(ORIGIN)\n\n# FadeOut everything on screen (may contain mixed types)\nself.play(FadeOut(Group(*self.mobjects)))\n```\n\n**Rule: if your group contains any `Text()` objects, use `Group`, not `VGroup`.** VGroup will raise a TypeError on Manim CE v0.20+. MathTex and Tex are VMobjects and work with VGroup.\n\nBoth support `arrange()`, `arrange_in_grid()`, `set_opacity()`, `shift()`, `scale()`, `move_to()`.\n\n## Styling\n\n```python\nmob.set_color(BLUE)\nmob.set_fill(RED, opacity=0.5)\nmob.set_stroke(WHITE, width=2)\nmob.set_opacity(0.4)\nmob.set_z_index(1)                         # layering\n```\n\n## Specialized Mobjects\n\n```python\nnl = NumberLine(x_range=[-3, 3, 1], length=8, include_numbers=True)\ntable = Table([[\"A\", \"B\"], [\"C\", \"D\"]], row_labels=[Text(\"R1\"), Text(\"R2\")])\ncode = Code(\"example.py\", tab_width=4, font_size=20, language=\"python\")\nhighlight = SurroundingRectangle(target, color=YELLOW, buff=0.2)\nbg = BackgroundRectangle(equation, fill_opacity=0.7, buff=0.2)\n```\n\n## Custom Mobjects\n\n```python\nclass NetworkNode(Group):\n    def __init__(self, label_text, color=BLUE, **kwargs):\n        super().__init__(**kwargs)\n        self.circle = Circle(radius=0.4, color=color, fill_opacity=0.3)\n        self.label = Text(label_text, font_size=20).move_to(self.circle)\n        self.add(self.circle, self.label)\n```\n\n## Matrix Mobjects\n\nDisplay matrices as grids of numbers or mobjects:\n\n```python\n# Integer matrix\nm = IntegerMatrix([[1, 2], [3, 4]])\n\n# Decimal matrix (control decimal places)\nm = DecimalMatrix([[1.5, 2.7], [3.1, 4.9]], element_to_mobject_config={\"num_decimal_places\": 2})\n\n# Mobject matrix (any mobject in each cell)\nm = MobjectMatrix([\n    [MathTex(r\"\\pi\"), MathTex(r\"e\")],\n    [MathTex(r\"\\phi\"), MathTex(r\"\\tau\")]\n])\n\n# Bracket types: \"(\" \"[\" \"|\" or \"\\\\{\"\nm = IntegerMatrix([[1, 0], [0, 1]], left_bracket=\"[\", right_bracket=\"]\")\n```\n\nUse cases: linear algebra, transformation matrices, system-of-equations coefficient display.\n\n## Constants\n\nDirections: `UP, DOWN, LEFT, RIGHT, ORIGIN, UL, UR, DL, DR`\nColors: `RED, BLUE, GREEN, YELLOW, WHITE, GRAY, ORANGE, PINK, PURPLE, TEAL, GOLD`\nFrame: `config.frame_width = 14.222, config.frame_height = 8.0`\n\n## SVGMobject — Import SVG Files\n\n```python\nlogo = SVGMobject(\"path/to/logo.svg\")\nlogo.set_color(WHITE).scale(0.5).to_corner(UR)\nself.play(FadeIn(logo))\n\n# SVG submobjects are individually animatable\nfor part in logo.submobjects:\n    self.play(part.animate.set_color(random_color()))\n```\n\n## ImageMobject — Display Images\n\n```python\nimg = ImageMobject(\"screenshot.png\")\nimg.set_height(3).to_edge(RIGHT)\nself.play(FadeIn(img))\n```\n\nNote: images cannot be animated with `.animate` (they're raster, not vector). Use `FadeIn`/`FadeOut` and `shift`/`scale` only.\n\n## Variable — Auto-Updating Display\n\n```python\nvar = Variable(0, Text(\"x\"), num_decimal_places=2)\nvar.move_to(ORIGIN)\nself.add(var)\n\n# Animate the value\nself.play(var.tracker.animate.set_value(5), run_time=2)\n# Display auto-updates: \"x = 5.00\"\n```\n\nCleaner than manual `DecimalNumber` + `add_updater` for simple labeled-value displays.\n\n## BulletedList\n\n```python\nbullets = BulletedList(\n    \"First key point\",\n    \"Second important fact\",\n    \"Third conclusion\",\n    font_size=28\n)\nbullets.to_edge(LEFT, buff=1.0)\nself.play(Write(bullets))\n\n# Highlight individual items\nself.play(bullets[1].animate.set_color(YELLOW))\n```\n\n## DashedLine and Angle Markers\n\n```python\n# Dashed line (asymptotes, construction lines)\ndashed = DashedLine(LEFT * 3, RIGHT * 3, color=SUBTLE, dash_length=0.15)\n\n# Angle marker between two lines\nline1 = Line(ORIGIN, RIGHT * 2)\nline2 = Line(ORIGIN, UP * 2 + RIGHT)\nangle = Angle(line1, line2, radius=0.5, color=YELLOW)\nangle_label = angle.get_value()  # returns the angle in radians\n\n# Right angle marker\nright_angle = RightAngle(line1, Line(ORIGIN, UP * 2), length=0.3, color=WHITE)\n```\n\n## Boolean Operations (CSG)\n\nCombine, subtract, or intersect 2D shapes:\n\n```python\ncircle = Circle(radius=1.5, color=BLUE, fill_opacity=0.5).shift(LEFT * 0.5)\nsquare = Square(side_length=2, color=RED, fill_opacity=0.5).shift(RIGHT * 0.5)\n\n# Union, Intersection, Difference, Exclusion\nunion = Union(circle, square, color=GREEN, fill_opacity=0.5)\nintersect = Intersection(circle, square, color=YELLOW, fill_opacity=0.5)\ndiff = Difference(circle, square, color=PURPLE, fill_opacity=0.5)\nexclude = Exclusion(circle, square, color=ORANGE, fill_opacity=0.5)\n```\n\nUse cases: Venn diagrams, set theory, geometric proofs, area calculations.\n\n## LabeledArrow / LabeledLine\n\n```python\n# Arrow with built-in label (auto-positioned)\narr = LabeledArrow(Text(\"force\", font_size=18), start=LEFT, end=RIGHT, color=RED)\n\n# Line with label\nline = LabeledLine(Text(\"d = 5m\", font_size=18), start=LEFT * 2, end=RIGHT * 2)\n```\n\nAuto-handles label positioning — cleaner than manual `Arrow` + `Text().next_to()`.\n\n## Text Color/Font/Style Per-Substring (t2c, t2f, t2s, t2w)\n\n```python\n# Color specific words (t2c = text-to-color)\ntext = Text(\n    \"Gradient descent minimizes the loss function\",\n    t2c={\"Gradient descent\": BLUE, \"loss function\": RED}\n)\n\n# Different fonts per word (t2f = text-to-font)\ntext = Text(\n    \"Use Menlo for code and Inter for prose\",\n    t2f={\"Menlo\": \"Menlo\", \"Inter\": \"Inter\"}\n)\n\n# Italic/slant per word (t2s = text-to-slant)\ntext = Text(\"Normal and italic text\", t2s={\"italic\": ITALIC})\n\n# Bold per word (t2w = text-to-weight)\ntext = Text(\"Normal and bold text\", t2w={\"bold\": BOLD})\n```\n\nThese are much cleaner than creating separate Text objects and grouping them.\n\n## Backstroke for Readability Over Backgrounds\n\nWhen text overlaps other content (graphs, diagrams, images), add a dark stroke behind it:\n\n```python\n# CE syntax:\nlabel.set_stroke(BLACK, width=5, background=True)\n\n# Apply to a group\nfor mob in labels:\n    mob.set_stroke(BLACK, width=4, background=True)\n```\n\nThis is how 3Blue1Brown keeps text readable over complex backgrounds without using BackgroundRectangle.\n\n## Complex Function Transforms\n\nApply complex functions to entire mobjects — transforms the plane:\n\n```python\nc_grid = ComplexPlane()\nmoving_grid = c_grid.copy()\nmoving_grid.prepare_for_nonlinear_transform()  # adds more sample points for smooth deformation\n\nself.play(\n    moving_grid.animate.apply_complex_function(lambda z: z**2),\n    run_time=5,\n)\n\n# Also works with R3->R3 functions:\nself.play(grid.animate.apply_function(\n    lambda p: [p[0] + 0.5 * math.sin(p[1]), p[1] + 0.5 * math.sin(p[0]), p[2]]\n), run_time=5)\n```\n\n**Critical:** Call `prepare_for_nonlinear_transform()` before applying nonlinear functions — without it, the grid has too few sample points and the deformation looks jagged.\n","skills/manim-video/references/paper-explainer.md":"# Paper Explainer Workflow\n\nHow to turn a research paper into an animated explainer video.\n\n## Why animate a paper?\n\nA research paper is optimized for precision and completeness. A video is optimized for understanding and retention. The translation is NOT \"read the paper aloud with pictures\" — it's \"extract the core insight and make it feel obvious through visual storytelling.\"\n\nThe paper has one job: prove the claim is true. The video has a different job: make the viewer understand WHY the claim is true, and WHY it matters.\n\n## Who is watching?\n\nBefore anything, decide the audience:\n\n| Audience | Prerequisites | Pacing | Depth |\n|----------|--------------|--------|-------|\n| General public | None | Slow, many analogies | Intuition only, skip proofs |\n| Undergrad students | Basic math/CS | Medium, some formalism | Key equations, skip derivations |\n| Grad students / researchers | Domain knowledge | Faster, more notation | Full equations, sketch proofs |\n\nThis determines everything: vocabulary, pacing, which sections to animate, how much math to show.\n\n## The 5-minute template\n\nMost paper explainers fit this structure (scale times proportionally for longer videos):\n\n| Section | Duration | Purpose |\n|---------|----------|---------|\n| **Hook** | 0:00-0:30 | Surprising result or provocative question |\n| **Problem** | 0:30-1:30 | What was broken/missing before this paper |\n| **Key insight** | 1:30-3:00 | The core idea, explained visually |\n| **How it works** | 3:00-4:00 | Method/algorithm, simplified |\n| **Evidence** | 4:00-4:30 | Key result that proves it works |\n| **Implications** | 4:30-5:00 | Why it matters, what it enables |\n\n### What to skip\n\n- Related work survey → one sentence: \"Previous approaches did X, which had problem Y\"\n- Implementation details → skip unless they're the contribution\n- Ablation studies → show one chart at most\n- Proofs → show the key step, not the full proof\n- Hyperparameter tuning → skip entirely\n\n### What to expand\n\n- The core insight → this gets the most screen time\n- Geometric/visual intuition → if the paper has math, show what it MEANS\n- Before/after comparison → the most compelling evidence\n\n## Pre-code workflow\n\n### Gate 1: Narration script\n\nWrite the full narration before any code. Every sentence maps to a visual beat. If you can't write the narration, you don't understand the paper well enough to animate it.\n\n```markdown\n## Hook (30s)\n\"What if I told you that a model with 7 billion parameters can outperform\none with 70 billion — if you train it on the right data?\"\n\n## Problem (60s)\n\"The standard approach is to scale up. More parameters, more compute.\n[VISUAL: bar chart showing model sizes growing exponentially]\nBut Chinchilla showed us that most models are undertrained...\"\n```\n\n### Gate 2: Scene list\n\nAfter the narration, break it into scenes. Each scene is one Manim class.\n\n```markdown\nScene 1: Hook — surprising stat with animated counter\nScene 2: Problem — model size bar chart growing\nScene 3: Key insight — training data vs parameters, animated 2D plot\nScene 4: Method — pipeline diagram building left to right\nScene 5: Results — before/after comparison with animated bars\nScene 6: Closing — implications text\n```\n\n### Gate 3: Style constants\n\nBefore coding scenes, define the visual language:\n\n```python\n# style.py — import in every scene file\nBG = \"#0D1117\"\nPRIMARY = \"#58C4DD\"\nSECONDARY = \"#83C167\"\nACCENT = \"#FFFF00\"\nHIGHLIGHT = \"#FF6B6B\"\nMONO = \"Menlo\"\n\n# Color meanings for THIS paper\nMODEL_COLOR = PRIMARY      # \"the model\"\nDATA_COLOR = SECONDARY     # \"training data\"\nBASELINE_COLOR = HIGHLIGHT # \"previous approach\"\nRESULT_COLOR = ACCENT      # \"our result\"\n```\n\n## First-principles equation explanation\n\nWhen the paper has a key equation, don't just show it — build it from intuition:\n\n### The \"what would you do?\" pattern\n\n1. Pose the problem in plain language\n2. Ask what the simplest solution would be\n3. Show why it doesn't work (animate the failure)\n4. Introduce the paper's solution as the fix\n5. THEN show the equation — it now feels earned\n\n```python\n# Scene: Why we need attention (for a Transformer paper)\n# Step 1: \"How do we let each word look at every other word?\"\n# Step 2: Show naive approach (fully connected = O(n²) everything)\n# Step 3: Show it breaks (information overload, no selectivity)\n# Step 4: \"What if each word could CHOOSE which words to attend to?\"\n# Step 5: Show attention equation — Q, K, V now mean something\n```\n\n### Equation reveal strategy\n\n```python\n# Show equation dimmed first (full destination)\neq = MathTex(r\"Attention(Q,K,V) = softmax\\left(\\frac{QK^T}{\\sqrt{d_k}}\\right)V\")\neq.set_opacity(0.15)\nself.play(FadeIn(eq))\n\n# Highlight Q, K, V one at a time with color + label\nfor part, color, label_text in [\n    (r\"Q\", PRIMARY, \"Query: what am I looking for?\"),\n    (r\"K\", SECONDARY, \"Key: what do I contain?\"),\n    (r\"V\", ACCENT, \"Value: what do I output?\"),\n]:\n    eq.set_color_by_tex(part, color)\n    label = Text(label_text, font_size=18, color=color, font=MONO)\n    # position label, animate it, wait, then dim it\n```\n\n## Building architecture diagrams\n\n### The progressive build pattern\n\nDon't show the full architecture at once. Build it:\n\n1. First component appears alone → explain\n2. Arrow grows → \"this feeds into...\"\n3. Second component appears → explain\n4. Repeat until complete\n\n```python\n# Component factory\ndef make_box(label, color, width=2.0, height=0.8):\n    box = RoundedRectangle(corner_radius=0.1, width=width, height=height,\n                           color=color, fill_opacity=0.1, stroke_width=1.5)\n    text = Text(label, font_size=18, font=MONO, color=color).move_to(box)\n    return Group(box, text)\n\nencoder = make_box(\"Encoder\", PRIMARY)\ndecoder = make_box(\"Decoder\", SECONDARY).next_to(encoder, RIGHT, buff=1.5)\narrow = Arrow(encoder.get_right(), decoder.get_left(), color=DIM, stroke_width=1.5)\n\nself.play(FadeIn(encoder))\nself.wait(1)  # explain encoder\nself.play(GrowArrow(arrow))\nself.play(FadeIn(decoder))\nself.wait(1)  # explain decoder\n```\n\n### Data flow animation\n\nAfter building the diagram, show data moving through it:\n\n```python\n# Dot traveling along the pipeline\ndata_dot = Dot(color=ACCENT, radius=0.1).move_to(encoder)\nself.play(FadeIn(data_dot))\nself.play(MoveAlongPath(data_dot, arrow), run_time=1)\nself.play(data_dot.animate.move_to(decoder), run_time=0.5)\nself.play(Flash(data_dot.get_center(), color=ACCENT), run_time=0.3)\n```\n\n## Animating results\n\n### Bar chart comparison (most common)\n\n```python\n# Before/after bars\nbefore_data = [45, 52, 38, 61]\nafter_data = [78, 85, 72, 91]\nlabels = [\"Task A\", \"Task B\", \"Task C\", \"Task D\"]\n\nbefore_chart = BarChart(before_data, bar_names=labels,\n    y_range=[0, 100, 20], bar_colors=[HIGHLIGHT]*4).scale(0.6).shift(LEFT*3)\nafter_chart = BarChart(after_data, bar_names=labels,\n    y_range=[0, 100, 20], bar_colors=[SECONDARY]*4).scale(0.6).shift(RIGHT*3)\n\nbefore_label = Text(\"Baseline\", font_size=20, color=HIGHLIGHT, font=MONO)\nafter_label = Text(\"Ours\", font_size=20, color=SECONDARY, font=MONO)\n\n# Reveal baseline first, then ours (dramatic comparison)\nself.play(Create(before_chart), FadeIn(before_label))\nself.wait(1.5)\nself.play(Create(after_chart), FadeIn(after_label))\nself.wait(0.5)\n\n# Highlight the improvement\nimprovement = Text(\"+35% avg\", font_size=24, color=ACCENT, font=MONO)\nself.play(FadeIn(improvement))\n```\n\n### Training curve (for ML papers)\n\n```python\ntracker = ValueTracker(0)\ncurve = always_redraw(lambda: axes.plot(\n    lambda x: 1 - 0.8 * np.exp(-x / 3),\n    x_range=[0, tracker.get_value()], color=PRIMARY\n))\nepoch_label = always_redraw(lambda: Text(\n    f\"Epoch {int(tracker.get_value())}\", font_size=18, font=MONO\n).to_corner(UR))\n\nself.add(curve, epoch_label)\nself.play(tracker.animate.set_value(10), run_time=5, rate_func=linear)\n```\n\n## Domain-specific patterns\n\n### ML papers\n- Show data flow through the model (animated pipeline)\n- Training curves with `ValueTracker`\n- Attention heatmaps as colored grids\n- Embedding space as 2D scatter (PCA/t-SNE visualization)\n- Loss landscape as 3D surface with gradient descent dot\n\n### Physics/math papers\n- Use `LinearTransformationScene` for linear algebra\n- Vector fields with `ArrowVectorField` / `StreamLines`\n- Phase spaces with `NumberPlane` + trajectories\n- Wave equations with time-parameterized plots\n\n### Systems/architecture papers\n- Pipeline diagrams built progressively\n- `ShowPassingFlash` for data flow along arrows\n- `ZoomedScene` for zooming into components\n- Before/after latency/throughput comparisons\n\n## Common mistakes\n\n1. **Trying to cover the whole paper.** A 5-minute video can explain ONE core insight well. Covering everything means explaining nothing.\n2. **Reading the abstract as narration.** Academic writing is designed for readers, not listeners. Rewrite in conversational language.\n3. **Showing notation without meaning.** Never show a symbol without first showing what it represents visually.\n4. **Skipping the motivation.** Jumping straight to \"here's our method\" without showing why the problem matters. The Problem section is what makes the viewer care.\n5. **Identical pacing throughout.** The hook and key insight need the most visual energy. The method section can be faster. Evidence should land with impact (pause after showing the big number).\n","skills/manim-video/references/production-quality.md":"# Production Quality Checklist\n\nStandards and checks for ensuring animation output is publication-ready.\n\n## Pre-Code Checklist\n\nBefore writing any Manim code:\n\n- [ ] Narration script written with visual beats marked\n- [ ] Scene list with purpose, duration, and layout for each\n- [ ] Color palette defined with meaning assignments (`PRIMARY` = main concept, etc.)\n- [ ] `MONO = \"Menlo\"` set as the font constant\n- [ ] Target resolution and aspect ratio decided\n\n## Text Quality\n\n### Overlap prevention\n\n```python\n# RULE: buff >= 0.5 for edge text\nlabel.to_edge(DOWN, buff=0.5)     # GOOD\nlabel.to_edge(DOWN, buff=0.3)     # BAD — may clip\n\n# RULE: FadeOut previous before adding new at same position\nself.play(ReplacementTransform(note1, note2))  # GOOD\nself.play(Write(note2))                          # BAD — overlaps note1\n\n# RULE: Reduce font size for dense scenes\n# When > 4 text elements visible, use font_size=20 not 28\n```\n\n### Width enforcement\n\nLong text strings overflow the frame:\n\n```python\n# RULE: Set max width for any text that might be long\ntext = Text(\"This is a potentially long description\", font_size=22, font=MONO)\nif text.width > config.frame_width - 1.0:\n    text.set_width(config.frame_width - 1.0)\n```\n\n### Font consistency\n\n```python\n# RULE: Define MONO once, use everywhere\nMONO = \"Menlo\"\n\n# WRONG: mixing fonts\nText(\"Title\", font=\"Helvetica\")\nText(\"Label\", font=\"Arial\")\nText(\"Code\", font=\"Courier\")\n\n# RIGHT: one font\nText(\"Title\", font=MONO, weight=BOLD, font_size=48)\nText(\"Label\", font=MONO, font_size=20)\nText(\"Code\", font=MONO, font_size=18)\n```\n\n## Spatial Layout\n\n### The coordinate budget\n\nThe visible frame is approximately 14.2 wide × 8.0 tall (default 16:9). With mandatory margins:\n\n```\nUsable area: x ∈ [-6.5, 6.5], y ∈ [-3.5, 3.5]\nTop title zone: y ∈ [2.5, 3.5]\nBottom note zone: y ∈ [-3.5, -2.5]\nMain content: y ∈ [-2.5, 2.5], x ∈ [-6.0, 6.0]\n```\n\n### Fill the frame\n\nEmpty scenes look unfinished. If the main content is small, add context:\n- A dimmed grid/axes behind the content\n- A title/subtitle at the top\n- A source citation at the bottom\n- Decorative geometry at low opacity\n\n### Maximum simultaneous elements\n\n**Hard limit: 6 actively visible elements.** Beyond that, the viewer can't track everything. If you need more:\n- Dim old elements to opacity 0.3\n- Remove elements that have served their purpose\n- Split into two scenes\n\n## Animation Quality\n\n### Variety audit\n\nCheck that no two consecutive scenes use the exact same:\n- Animation type (if Scene 3 uses Write for everything, Scene 4 should use FadeIn or Create)\n- Color emphasis (rotate through palette colors)\n- Layout (center, left-right, grid — alternate)\n- Pacing (if Scene 2 was slow and deliberate, Scene 3 can be faster)\n\n### Tempo curve\n\nA good video follows a tempo curve:\n\n```\nSlow ──→ Medium ──→ FAST (climax) ──→ Slow (conclusion)\n\nScene 1: Slow (introduction, setup)\nScene 2: Medium (building understanding)\nScene 3: Medium-Fast (core content, lots of animation)\nScene 4: FAST (montage of applications/results)\nScene 5: Slow (conclusion, key takeaway)\n```\n\n### Transition quality\n\nBetween scenes:\n- **Clean exit**: `self.play(FadeOut(Group(*self.mobjects)), run_time=0.5)`\n- **Brief pause**: `self.wait(0.3)` after fadeout, before next scene's first animation\n- **Never hard-cut**: always animate the transition\n\n## Color Quality\n\n### Dimming on dark backgrounds\n\nColors that look vibrant on white look muddy on dark backgrounds (#0D1117, #1C1C1C). Test your palette:\n\n```python\n# Colors that work well on dark backgrounds:\n# Bright and saturated: #58C4DD, #83C167, #FFFF00, #FF6B6B\n# Colors that DON'T work: #666666 (invisible), #2244AA (too dark)\n\n# RULE: Structural elements (axes, grids) at opacity 0.15\n# Context elements at 0.3-0.4\n# Primary elements at 1.0\n```\n\n### Color meaning consistency\n\nOnce a color is assigned a meaning, it keeps that meaning for the entire video:\n\n```python\n# If PRIMARY (#58C4DD) means \"the model\" in Scene 1,\n# it means \"the model\" in every scene.\n# Never reuse PRIMARY for a different concept later.\n```\n\n## Data Visualization Quality\n\n### Minimum requirements for charts\n\n- Axis labels on every axis\n- Y-axis range starts at 0 (or has a clear break indicator)\n- Bar/line colors match the legend\n- Numbers on notable data points (at least the maximum and the comparison point)\n\n### Animated counters\n\nWhen showing a number changing:\n```python\n# GOOD: DecimalNumber with smooth animation\ncounter = DecimalNumber(0, font_size=48, num_decimal_places=0, font=\"Menlo\")\nself.play(counter.animate.set_value(1000), run_time=3, rate_func=rush_from)\n\n# BAD: Text that jumps between values\n```\n\n## Pre-Render Checklist\n\nBefore running `manim -qh`:\n\n- [ ] All scenes render without errors at `-ql`\n- [ ] Preview stills at `-qm` for text-heavy scenes (check kerning)\n- [ ] Background color set in every scene (`self.camera.background_color = BG`)\n- [ ] `add_subcaption()` or `subcaption=` on every significant animation\n- [ ] No text smaller than font_size=18\n- [ ] No text using proportional fonts (use monospace)\n- [ ] buff >= 0.5 on all `.to_edge()` calls\n- [ ] Clean exit (FadeOut all) at end of every scene\n- [ ] `self.wait()` after every reveal\n- [ ] Color constants used (no hardcoded hex strings in scene code)\n- [ ] All scenes use the same quality flag (don't mix `-ql` and `-qh`)\n\n## Post-Render Checklist\n\nAfter stitching the final video:\n\n- [ ] Watch the complete video at 1x speed — does it feel rushed anywhere?\n- [ ] Is there a moment where two things animate simultaneously and it's confusing?\n- [ ] Does every text label have enough time to be read?\n- [ ] Are transitions between scenes smooth (no black frames, no jarring cuts)?\n- [ ] Is the audio in sync with the visuals (if using voiceover)?\n- [ ] Is the Gibbs-like \"first impression\" good? The first 5 seconds determine if someone keeps watching\n","skills/manim-video/references/rendering.md":"# Rendering Reference\n\n## Prerequisites\n\n```bash\nmanim --version       # Manim CE\npdflatex --version    # LaTeX\nffmpeg -version       # ffmpeg\n```\n\n## CLI Reference\n\n```bash\nmanim -ql script.py Scene1 Scene2    # draft (480p 15fps)\nmanim -qm script.py Scene1           # medium (720p 30fps)\nmanim -qh script.py Scene1           # production (1080p 60fps)\nmanim -ql --format=png -s script.py Scene1  # preview still (last frame)\nmanim -ql --format=gif script.py Scene1     # GIF output\n```\n\n## Quality Presets\n\n| Flag | Resolution | FPS | Use case |\n|------|-----------|-----|----------|\n| `-ql` | 854x480 | 15 | Draft iteration (layout, timing) |\n| `-qm` | 1280x720 | 30 | Preview (use for text-heavy scenes) |\n| `-qh` | 1920x1080 | 60 | Production |\n\n**Text rendering quality:** `-ql` (480p15) produces noticeably poor text kerning and readability. For scenes with significant text, preview stills at `-qm` to catch issues invisible at 480p. Use `-ql` only for testing layout and animation timing.\n\n## Output Structure\n\n```\nmedia/videos/script/480p15/Scene1_Intro.mp4\nmedia/images/script/Scene1_Intro.png  (from -s flag)\n```\n\n## Stitching with ffmpeg\n\n```bash\ncat > concat.txt << 'EOF'\nfile 'media/videos/script/480p15/Scene1_Intro.mp4'\nfile 'media/videos/script/480p15/Scene2_Core.mp4'\nEOF\nffmpeg -y -f concat -safe 0 -i concat.txt -c copy final.mp4\n```\n\n## Add Voiceover\n\n```bash\n# Mux narration\nffmpeg -y -i final.mp4 -i narration.mp3 -c:v copy -c:a aac -b:a 192k -shortest final_narrated.mp4\n\n# Concat per-scene audio first\ncat > audio_concat.txt << 'EOF'\nfile 'audio/scene1.mp3'\nfile 'audio/scene2.mp3'\nEOF\nffmpeg -y -f concat -safe 0 -i audio_concat.txt -c copy full_narration.mp3\n```\n\n## Add Background Music\n\n```bash\nffmpeg -y -i final.mp4 -i music.mp3 \\\n  -filter_complex \"[1:a]volume=0.15[bg];[0:a][bg]amix=inputs=2:duration=shortest\" \\\n  -c:v copy final_with_music.mp4\n```\n\n## GIF Export\n\n```bash\nffmpeg -y -i scene.mp4 \\\n  -vf \"fps=15,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse\" \\\n  output.gif\n```\n\n## Aspect Ratios\n\n```bash\nmanim -ql --resolution 1080,1920 script.py Scene  # 9:16 vertical\nmanim -ql --resolution 1080,1080 script.py Scene  # 1:1 square\n```\n\n## Render Workflow\n\n1. Draft render all scenes at `-ql`\n2. Preview stills at key moments (`-s`)\n3. Fix and re-render only broken scenes\n4. Stitch with ffmpeg\n5. Review stitched output\n6. Production render at `-qh`\n7. Re-stitch + add audio\n\n## manim.cfg — Project Configuration\n\nCreate `manim.cfg` in the project directory for per-project defaults:\n\n```ini\n[CLI]\nquality = low_quality\npreview = True\nmedia_dir = ./media\n\n[renderer]\nbackground_color = #0D1117\n\n[tex]\ntex_template_file = custom_template.tex\n```\n\nThis eliminates repetitive CLI flags and `self.camera.background_color` in every scene.\n\n## Sections — Chapter Markers\n\nMark sections within a scene for organized output:\n\n```python\nclass LongVideo(Scene):\n    def construct(self):\n        self.next_section(\"Introduction\")\n        # ... intro content ...\n\n        self.next_section(\"Main Concept\")\n        # ... main content ...\n\n        self.next_section(\"Conclusion\")\n        # ... closing ...\n```\n\nRender individual sections: `manim --save_sections script.py LongVideo`\nThis outputs separate video files per section — useful for long videos where you want to re-render only one part.\n\n## manim-voiceover Plugin (Recommended for Narrated Videos)\n\nThe official `manim-voiceover` plugin integrates TTS directly into scene code, auto-syncing animation duration to voiceover length. This is significantly cleaner than the manual ffmpeg muxing approach above.\n\n### Installation\n\n```bash\npip install \"manim-voiceover[elevenlabs]\"\n# Or for free/local TTS:\npip install \"manim-voiceover[gtts]\"    # Google TTS (free, lower quality)\npip install \"manim-voiceover[azure]\"   # Azure Cognitive Services\n```\n\n### Usage\n\n```python\nfrom manim import *\nfrom manim_voiceover import VoiceoverScene\nfrom manim_voiceover.services.elevenlabs import ElevenLabsService\n\nclass NarratedScene(VoiceoverScene):\n    def construct(self):\n        self.set_speech_service(ElevenLabsService(\n            voice_name=\"Alice\",\n            model_id=\"eleven_multilingual_v2\"\n        ))\n\n        # Voiceover auto-controls scene duration\n        with self.voiceover(text=\"Here is a circle being drawn.\") as tracker:\n            self.play(Create(Circle()), run_time=tracker.duration)\n\n        with self.voiceover(text=\"Now let's transform it into a square.\") as tracker:\n            self.play(Transform(circle, Square()), run_time=tracker.duration)\n```\n\n### Key Features\n\n- `tracker.duration` — total voiceover duration in seconds\n- `tracker.time_until_bookmark(\"mark1\")` — sync specific animations to specific words\n- Auto-generates subtitle `.srt` files\n- Caches audio locally — re-renders don't re-generate TTS\n- Works with: ElevenLabs, Azure, Google TTS, pyttsx3 (offline), and custom services\n\n### Bookmarks for Precise Sync\n\n```python\nwith self.voiceover(text='This is a <bookmark mark=\"circle\"/>circle.') as tracker:\n    self.wait_until_bookmark(\"circle\")\n    self.play(Create(Circle()), run_time=tracker.time_until_bookmark(\"circle\", limit=1))\n```\n\nThis is the recommended approach for any video with narration. The manual ffmpeg muxing workflow above is still useful for adding background music or post-production audio mixing.\n","skills/manim-video/references/scene-planning.md":"# Scene Planning Reference\n\n## Narrative Arc Structures\n\n### Discovery Arc (most common)\n1. Hook -- pose a question or surprising result\n2. Intuition -- build visual understanding\n3. Formalize -- introduce the equation/algorithm\n4. Reveal -- the \"aha moment\"\n5. Extend -- implications or generalizations\n\n### Problem-Solution Arc\n1. Problem -- what's broken\n2. Failed attempt -- obvious approach fails\n3. Key insight -- the idea that works\n4. Solution -- implement it\n5. Result -- show improvement\n\n### Comparison Arc\n1. Setup -- introduce two approaches\n2. Approach A -- how it works\n3. Approach B -- how it works\n4. Contrast -- differences\n5. Verdict -- which is better\n\n### Build-Up Arc (architecture/systems)\n1. Component A -- first piece\n2. Component B -- second piece\n3. Connection -- how they interact\n4. Scale -- add more pieces\n5. Full picture -- zoom out\n\n## Scene Transitions\n\n### Clean Break (default)\n```python\nself.play(FadeOut(Group(*self.mobjects)), run_time=0.5)\nself.wait(0.3)\n```\n\n### Carry-Forward\nKeep one element, fade the rest. Next scene starts with it still on screen.\n\n### Transform Bridge\nEnd scene with a shape, start next scene by transforming it.\n\n## Cross-Scene Consistency\n\n```python\n# Shared constants at file top\nBG = \"#1C1C1C\"\nPRIMARY = \"#58C4DD\"\nSECONDARY = \"#83C167\"\nACCENT = \"#FFFF00\"\nTITLE_SIZE = 48\nBODY_SIZE = 30\nLABEL_SIZE = 24\nFAST = 0.8; NORMAL = 1.5; SLOW = 2.5\n```\n\n## Scene Checklist\n\n- [ ] Background color set\n- [ ] Subcaptions on every animation\n- [ ] `self.wait()` after every reveal\n- [ ] Text buff >= 0.5 for edge positioning\n- [ ] No text overlap\n- [ ] Color constants used (not hardcoded)\n- [ ] Opacity layering applied\n- [ ] Clean exit at scene end\n- [ ] No more than 5-6 elements visible at once\n\n## Duration Estimation\n\n| Content | Duration |\n|---------|----------|\n| Title card | 3-5s |\n| Concept introduction | 10-20s |\n| Equation reveal | 15-25s |\n| Algorithm step | 5-10s |\n| Data comparison | 10-15s |\n| \"Aha moment\" | 15-30s |\n| Conclusion | 5-10s |\n\n## Planning Template\n\n```markdown\n# [Video Title]\n\n## Overview\n- **Topic**: [Core concept]\n- **Hook**: [Opening question]\n- **Aha moment**: [Key insight]\n- **Target audience**: [Prerequisites]\n- **Length**: [seconds/minutes]\n- **Resolution**: 480p (draft) / 1080p (final)\n\n## Color Palette\n- Background: #1C1C1C\n- Primary: #58C4DD -- [purpose]\n- Secondary: #83C167 -- [purpose]\n- Accent: #FFFF00 -- [purpose]\n\n## Arc: [Discovery / Problem-Solution / Comparison / Build-Up]\n\n## Scene 1: [Name] (~Ns)\n**Purpose**: [one sentence]\n**Layout**: [FULL_CENTER / LEFT_RIGHT / GRID / PROGRESSIVE]\n\n### Visual elements\n- [Mobject: type, position, color]\n\n### Animation sequence\n1. [Animation] -- [what it reveals] (~Ns)\n\n### Subtitle\n\"[text]\"\n```\n","skills/manim-video/references/troubleshooting.md":"# Troubleshooting\n\n## LaTeX Errors\n\n**Missing raw string** (the #1 error):\n```python\n# WRONG: MathTex(\"\\\\frac{1}{2}\")  -- \\\\f is form-feed\n# RIGHT: MathTex(r\"\\frac{1}{2}\")\n```\n\n**Unbalanced braces**: `MathTex(r\"\\frac{1}{2\")` -- missing closing brace.\n\n**LaTeX not installed**: `which pdflatex` -- install texlive-full or mactex.\n\n**Missing package**: Add to preamble:\n```python\ntex_template = TexTemplate()\ntex_template.add_to_preamble(r\"\\usepackage{mathrsfs}\")\nMathTex(r\"\\mathscr{L}\", tex_template=tex_template)\n```\n\n## VGroup TypeError\n\n**Error:** `TypeError: Only values of type VMobject can be added as submobjects of VGroup`\n\n**Cause:** `Text()` objects are `Mobject`, not `VMobject`. Mixing `Text` with shapes in a `VGroup` fails on Manim CE v0.20+.\n\n```python\n# WRONG: Text is not a VMobject\ngroup = VGroup(circle, Text(\"Label\"))\n\n# RIGHT: use Group for mixed types\ngroup = Group(circle, Text(\"Label\"))\n\n# RIGHT: VGroup is fine for shapes-only\nshapes = VGroup(circle, square, arrow)\n\n# RIGHT: MathTex IS a VMobject — VGroup works\nequations = VGroup(MathTex(r\"a\"), MathTex(r\"b\"))\n```\n\n**Rule:** If the group contains any `Text()`, use `Group`. If it's all shapes or all `MathTex`, `VGroup` is fine.\n\n**FadeOut everything:** Always use `Group(*self.mobjects)`, not `VGroup(*self.mobjects)`:\n```python\nself.play(FadeOut(Group(*self.mobjects)))  # safe for mixed types\n```\n\n## Group save_state() / restore() Not Supported\n\n**Error:** `NotImplementedError: Please override in a child class.`\n\n**Cause:** `Group.save_state()` and `Group.restore()` are not implemented in Manim CE v0.20+. Only `VGroup` and individual `Mobject` subclasses support save/restore.\n\n```python\n# WRONG: Group doesn't support save_state\ngroup = Group(circle, Text(\"label\"))\ngroup.save_state()  # NotImplementedError!\n\n# RIGHT: use FadeIn with shift/scale instead of save_state/restore\nself.play(FadeIn(group, shift=UP * 0.3, scale=0.8))\n\n# RIGHT: or save/restore on individual VMobjects\ncircle.save_state()\nself.play(circle.animate.shift(RIGHT))\nself.play(Restore(circle))\n```\n\n## letter_spacing Is Not a Valid Parameter\n\n**Error:** `TypeError: Mobject.__init__() got an unexpected keyword argument 'letter_spacing'`\n\n**Cause:** `Text()` does not accept `letter_spacing`. Manim uses Pango for text rendering and does not expose kerning controls on `Text()`.\n\n```python\n# WRONG\nText(\"HERMES\", letter_spacing=6)\n\n# RIGHT: use MarkupText with Pango attributes for spacing control\nMarkupText('<span letter_spacing=\"6000\">HERMES</span>', font_size=18)\n# Note: Pango letter_spacing is in 1/1024 of a point\n```\n\n## Animation Errors\n\n**Invisible animation** -- mobject never added:\n```python\n# WRONG: circle = Circle(); self.play(circle.animate.set_color(RED))\n# RIGHT: self.play(Create(circle)); self.play(circle.animate.set_color(RED))\n```\n\n**Transform confusion** -- after Transform(A, B), A is on screen, B is not. Use ReplacementTransform if you want B.\n\n**Duplicate animation** -- same mobject twice in one play():\n```python\n# WRONG: self.play(c.animate.shift(RIGHT), c.animate.set_color(RED))\n# RIGHT: self.play(c.animate.shift(RIGHT).set_color(RED))\n```\n\n**Updater fights animation**:\n```python\nmob.suspend_updating()\nself.play(mob.animate.shift(RIGHT))\nmob.resume_updating()\n```\n\n## Rendering Issues\n\n**Blurry output**: Using -ql (480p). Switch to -qm/-qh for final.\n\n**Slow render**: Use -ql during development. Reduce Surface resolution. Shorter self.wait().\n\n**Stale output**: `manim -ql --disable_caching script.py Scene`\n\n**ffmpeg concat fails**: All clips must match resolution/FPS/codec.\n\n## Common Mistakes\n\n**Text clips at edge**: `buff >= 0.5` for `.to_edge()`\n\n**Overlapping text**: Use `ReplacementTransform(old, new)`, not `Write(new)` on top.\n\n**Too crowded**: Max 5-6 elements visible. Split into scenes or use opacity layering.\n\n**No breathing room**: `self.wait(1.5)` minimum after reveals, `self.wait(2.0)` for key moments.\n\n**Missing background color**: Set `self.camera.background_color = BG` in every scene.\n\n## Debugging Strategy\n\n1. Render a still: `manim -ql -s script.py Scene` -- instant layout check\n2. Isolate the broken scene -- render only that one\n3. Replace `self.play()` with `self.add()` to see final state instantly\n4. Print positions: `print(mob.get_center())`\n5. Clear cache: delete `media/` directory\n","skills/manim-video/references/updaters-and-trackers.md":"# Updaters and Value Trackers\n\n## The problem updaters solve\n\nNormal animations are discrete: `self.play()` goes from state A to state B. But what if you need continuous relationships — a label that always hovers above a moving dot, or a line that always connects two points?\n\nWithout updaters, you'd manually reposition every dependent object before every `self.play()`. Five animations that move a dot means five manual repositioning calls for the label. Miss one and it freezes in the wrong spot.\n\nUpdaters let you declare a relationship ONCE. Manim calls the updater function EVERY FRAME (15-60 fps depending on quality) to enforce that relationship, no matter what else is happening.\n\n## ValueTracker: an invisible steering wheel\n\nA ValueTracker is an invisible Mobject that holds a single float. It never appears on screen. It exists so you can ANIMATE it while other objects REACT to its value.\n\nThink of it as a slider: drag the slider from 0 to 5, and every object wired to it responds in real time.\n\n```python\ntracker = ValueTracker(0)        # invisible, stores 0.0\ntracker.get_value()              # read: 0.0\ntracker.set_value(5)             # write: jump to 5.0 instantly\ntracker.animate.set_value(5)     # animate: smoothly interpolate to 5.0\n```\n\n### The three-step pattern\n\nEvery ValueTracker usage follows this:\n\n1. **Create the tracker** (the invisible slider)\n2. **Create visible objects that READ the tracker** via updaters\n3. **Animate the tracker** — all dependents update automatically\n\n```python\n# Step 1: Create tracker\nx_tracker = ValueTracker(1)\n\n# Step 2: Create dependent objects\ndot = always_redraw(lambda: Dot(axes.c2p(x_tracker.get_value(), 0), color=YELLOW))\nv_line = always_redraw(lambda: axes.get_vertical_line(\n    axes.c2p(x_tracker.get_value(), func(x_tracker.get_value())), color=BLUE\n))\nlabel = always_redraw(lambda: DecimalNumber(x_tracker.get_value(), font_size=24)\n    .next_to(dot, UP))\n\nself.add(dot, v_line, label)\n\n# Step 3: Animate the tracker — everything follows\nself.play(x_tracker.animate.set_value(5), run_time=3)\n```\n\n## Types of updaters\n\n### Lambda updater (most common)\n\nRuns a function every frame, passing the mobject itself:\n\n```python\n# Label always stays above the dot\nlabel.add_updater(lambda m: m.next_to(dot, UP, buff=0.2))\n\n# Line always connects two points\nline.add_updater(lambda m: m.put_start_and_end_on(\n    point_a.get_center(), point_b.get_center()\n))\n```\n\n### Time-based updater (with dt)\n\nThe second argument `dt` is the time since the last frame (~0.017s at 60fps):\n\n```python\n# Continuous rotation\nsquare.add_updater(lambda m, dt: m.rotate(0.5 * dt))\n\n# Continuous rightward drift\ndot.add_updater(lambda m, dt: m.shift(RIGHT * 0.3 * dt))\n\n# Oscillation\ndot.add_updater(lambda m, dt: m.move_to(\n    axes.c2p(m.get_center()[0], np.sin(self.time))\n))\n```\n\nUse `dt` updaters for physics simulations, continuous motion, and time-dependent effects.\n\n### always_redraw: full rebuild every frame\n\nCreates a new mobject from scratch each frame. More expensive than `add_updater` but handles cases where the mobject's structure changes (not just position/color):\n\n```python\n# Brace that follows a resizing square\nbrace = always_redraw(Brace, square, UP)\n\n# Area under curve that updates as function changes\narea = always_redraw(lambda: axes.get_area(\n    graph, x_range=[0, x_tracker.get_value()], color=BLUE, opacity=0.3\n))\n\n# Label that reconstructs its text\ncounter = always_redraw(lambda: Text(\n    f\"n = {int(x_tracker.get_value())}\", font_size=24, font=\"Menlo\"\n).to_corner(UR))\n```\n\n**When to use which:**\n- `add_updater` — position, color, opacity changes (cheap, preferred)\n- `always_redraw` — when the shape/structure itself changes (expensive, use sparingly)\n\n## DecimalNumber: showing live values\n\n```python\n# Counter that tracks a ValueTracker\ntracker = ValueTracker(0)\nnumber = DecimalNumber(0, font_size=48, num_decimal_places=1, color=PRIMARY)\nnumber.add_updater(lambda m: m.set_value(tracker.get_value()))\nnumber.add_updater(lambda m: m.next_to(dot, RIGHT, buff=0.3))\n\nself.add(number)\nself.play(tracker.animate.set_value(100), run_time=3)\n```\n\n### Variable: the labeled version\n\n```python\nvar = Variable(0, Text(\"x\", font_size=24, font=\"Menlo\"), num_decimal_places=2)\nself.add(var)\nself.play(var.tracker.animate.set_value(PI), run_time=2)\n# Displays: x = 3.14\n```\n\n## Removing updaters\n\n```python\n# Remove all updaters\nmobject.clear_updaters()\n\n# Suspend temporarily (during an animation that would fight the updater)\nmobject.suspend_updating()\nself.play(mobject.animate.shift(RIGHT))\nmobject.resume_updating()\n\n# Remove specific updater (if you stored a reference)\ndef my_updater(m):\n    m.next_to(dot, UP)\nlabel.add_updater(my_updater)\n# ... later ...\nlabel.remove_updater(my_updater)\n```\n\n## Animation-based updaters\n\n### UpdateFromFunc / UpdateFromAlphaFunc\n\nThese are ANIMATIONS (passed to `self.play`), not persistent updaters:\n\n```python\n# Call a function on each frame of the animation\nself.play(UpdateFromFunc(mobject, lambda m: m.next_to(moving_target, UP)), run_time=3)\n\n# With alpha (0 to 1) — useful for custom interpolation\nself.play(UpdateFromAlphaFunc(circle, lambda m, a: m.set_fill(opacity=a)), run_time=2)\n```\n\n### turn_animation_into_updater\n\nConvert a one-shot animation into a continuous updater:\n\n```python\nfrom manim import turn_animation_into_updater\n\n# This would normally play once — now it loops forever\nturn_animation_into_updater(Rotating(gear, rate=PI/4))\nself.add(gear)\nself.wait(5)  # gear rotates for 5 seconds\n```\n\n## Practical patterns\n\n### Pattern 1: Dot tracing a function\n\n```python\ntracker = ValueTracker(0)\ngraph = axes.plot(np.sin, x_range=[0, 2*PI], color=PRIMARY)\ndot = always_redraw(lambda: Dot(\n    axes.c2p(tracker.get_value(), np.sin(tracker.get_value())),\n    color=YELLOW\n))\ntangent = always_redraw(lambda: axes.get_secant_slope_group(\n    x=tracker.get_value(), graph=graph, dx=0.01,\n    secant_line_color=HIGHLIGHT, secant_line_length=3\n))\n\nself.add(graph, dot, tangent)\nself.play(tracker.animate.set_value(2*PI), run_time=6, rate_func=linear)\n```\n\n### Pattern 2: Live area under curve\n\n```python\ntracker = ValueTracker(0.5)\narea = always_redraw(lambda: axes.get_area(\n    graph, x_range=[0, tracker.get_value()],\n    color=PRIMARY, opacity=0.3\n))\narea_label = always_redraw(lambda: DecimalNumber(\n    # Numerical integration\n    sum(func(x) * 0.01 for x in np.arange(0, tracker.get_value(), 0.01)),\n    font_size=24\n).next_to(axes, RIGHT))\n\nself.add(area, area_label)\nself.play(tracker.animate.set_value(4), run_time=5)\n```\n\n### Pattern 3: Connected diagram\n\n```python\n# Nodes that can be moved, with edges that auto-follow\nnode_a = Dot(LEFT * 2, color=PRIMARY)\nnode_b = Dot(RIGHT * 2, color=SECONDARY)\nedge = Line().add_updater(lambda m: m.put_start_and_end_on(\n    node_a.get_center(), node_b.get_center()\n))\nlabel = Text(\"edge\", font_size=18, font=\"Menlo\").add_updater(\n    lambda m: m.move_to(edge.get_center() + UP * 0.3)\n)\n\nself.add(node_a, node_b, edge, label)\nself.play(node_a.animate.shift(UP * 2), run_time=2)\nself.play(node_b.animate.shift(DOWN + RIGHT), run_time=2)\n# Edge and label follow automatically\n```\n\n### Pattern 4: Parameter exploration\n\n```python\n# Explore how a parameter changes a curve\na_tracker = ValueTracker(1)\ncurve = always_redraw(lambda: axes.plot(\n    lambda x: a_tracker.get_value() * np.sin(x),\n    x_range=[0, 2*PI], color=PRIMARY\n))\nparam_label = always_redraw(lambda: Text(\n    f\"a = {a_tracker.get_value():.1f}\", font_size=24, font=\"Menlo\"\n).to_corner(UR))\n\nself.add(curve, param_label)\nself.play(a_tracker.animate.set_value(3), run_time=3)\nself.play(a_tracker.animate.set_value(0.5), run_time=2)\nself.play(a_tracker.animate.set_value(1), run_time=1)\n```\n\n## Common mistakes\n\n1. **Updater fights animation:** If a mobject has an updater that sets its position, and you try to animate it elsewhere, the updater wins every frame. Suspend updating first.\n\n2. **always_redraw for simple moves:** If you only need to reposition, use `add_updater`. `always_redraw` reconstructs the entire mobject every frame — expensive and unnecessary for position tracking.\n\n3. **Forgetting to add to scene:** Updaters only run on mobjects that are in the scene. `always_redraw` creates the mobject but you still need `self.add()`.\n\n4. **Updater creates new mobjects without cleanup:** If your updater creates Text objects every frame, they accumulate. Use `always_redraw` (which handles cleanup) or update properties in-place.\n","skills/manim-video/references/visual-design.md":"# Visual Design Principles\n\n## 12 Core Principles\n\n1. **Geometry Before Algebra** — Show the shape first, the equation second.\n2. **Opacity Layering** — PRIMARY=1.0, CONTEXT=0.4, GRID=0.15. Direct attention through brightness.\n3. **One New Idea Per Scene** — Each scene introduces exactly one concept.\n4. **Spatial Consistency** — Same concept occupies the same screen region throughout.\n5. **Color = Meaning** — Assign colors to concepts, not mobjects. If velocity is blue, it stays blue.\n6. **Progressive Disclosure** — Show simplest version first, add complexity incrementally.\n7. **Transform, Don't Replace** — Use Transform/ReplacementTransform to show connections.\n8. **Breathing Room** — `self.wait(1.5)` minimum after showing something new.\n9. **Visual Weight Balance** — Don't cluster everything on one side.\n10. **Consistent Motion Vocabulary** — Pick a small set of animation types and reuse them.\n11. **Dark Background, Light Content** — #1C1C1C to #2D2B55 backgrounds maximize contrast.\n12. **Intentional Empty Space** — Leave at least 15% of the frame empty.\n\n## Layout Templates\n\n### FULL_CENTER\nOne main element centered, title above, note below.\nBest for: single equations, single diagrams, title cards.\n\n### LEFT_RIGHT\nTwo elements side by side at x=-3.5 and x=3.5.\nBest for: equation + visual, before/after, comparison.\n\n### TOP_BOTTOM\nMain element at y=1.5, supporting content at y=-1.5.\nBest for: concept + examples, theorem + cases.\n\n### GRID\nMultiple elements via `arrange_in_grid()`.\nBest for: comparison matrices, multi-step processes.\n\n### PROGRESSIVE\nElements appear one at a time, arranged DOWN with aligned_edge=LEFT.\nBest for: algorithms, proofs, step-by-step processes.\n\n### ANNOTATED_DIAGRAM\nCentral diagram with floating labels connected by arrows.\nBest for: architecture diagrams, annotated figures.\n\n## Color Palettes\n\n### Classic 3B1B\n```python\nBG=\"#1C1C1C\"; PRIMARY=BLUE; SECONDARY=GREEN; ACCENT=YELLOW; HIGHLIGHT=RED\n```\n\n### Warm Academic\n```python\nBG=\"#2D2B55\"; PRIMARY=\"#FF6B6B\"; SECONDARY=\"#FFD93D\"; ACCENT=\"#6BCB77\"\n```\n\n### Neon Tech\n```python\nBG=\"#0A0A0A\"; PRIMARY=\"#00F5FF\"; SECONDARY=\"#FF00FF\"; ACCENT=\"#39FF14\"\n```\n\n## Font Selection\n\n**Use monospace fonts for all text.** Manim's Pango text renderer produces broken kerning with proportional fonts (Helvetica, Inter, SF Pro, Arial) at all sizes and resolutions. Characters overlap and spacing is inconsistent. This is a fundamental Pango limitation, not a Manim bug.\n\nMonospace fonts have fixed character widths — zero kerning issues by design.\n\n### Recommended Fonts\n\n| Use case | Font | Fallback |\n|----------|------|----------|\n| **All text (default)** | `\"Menlo\"` | `\"Courier New\"`, `\"DejaVu Sans Mono\"` |\n| Code, labels | `\"JetBrains Mono\"`, `\"SF Mono\"` | `\"Menlo\"` |\n| Math | Use `MathTex` (renders via LaTeX, not Pango) | — |\n\n```python\nMONO = \"Menlo\"  # define once at top of file\n\ntitle = Text(\"Fourier Series\", font_size=48, color=PRIMARY, weight=BOLD, font=MONO)\nlabel = Text(\"n=1: (4/pi) sin(x)\", font_size=20, color=BLUE, font=MONO)\nnote = Text(\"Convergence at discontinuities\", font_size=18, color=DIM, font=MONO)\n\n# Math — always use MathTex, not Text\nequation = MathTex(r\"\\nabla L = \\frac{\\partial L}{\\partial w}\")\n```\n\n### When Proportional Fonts Are Acceptable\n\nLarge title text (font_size >= 48) with short strings (1-3 words) can use proportional fonts without visible kerning issues. For anything else — labels, descriptions, multi-word text, small sizes — use monospace.\n\n### Font Availability\n\n- **macOS**: Menlo (pre-installed), SF Mono\n- **Linux**: DejaVu Sans Mono (pre-installed), Liberation Mono\n- **Cross-platform**: JetBrains Mono (install from jetbrains.com)\n\n`\"Menlo\"` is the safest default — pre-installed on macOS, and Linux systems fall back to DejaVu Sans Mono.\n\n### Fine-Grained Text Control\n\n`Text()` does not support `letter_spacing` or kerning parameters. For fine control, use `MarkupText` with Pango attributes:\n\n```python\n# Letter spacing (Pango units: 1/1024 of a point)\nMarkupText('<span letter_spacing=\"6000\">HERMES</span>', font_size=18, font=\"Menlo\")\n\n# Bold specific words\nMarkupText('This is <b>important</b>', font_size=24, font=\"Menlo\")\n\n# Color specific words\nMarkupText('Red <span foreground=\"#FF6B6B\">warning</span>', font_size=24, font=\"Menlo\")\n```\n\n### Minimum Font Size\n\n`font_size=18` is the minimum for readable text at any resolution. Below 18, characters become blurry at `-ql` and barely readable even at `-qh`.\n\n## Visual Hierarchy Checklist\n\nFor every frame:\n1. What is the ONE thing to look at? (brightest/largest)\n2. What is context? (dimmed to 0.3-0.4)\n3. What is structural? (dimmed to 0.15)\n4. Enough empty space? (>15%)\n5. All text readable at phone size?\n"},"files":{"SKILL.md":"---\nname: video-use\ndescription: Edit any video by conversation. Transcribe, cut, color grade, generate overlay animations, burn subtitles — for talking heads, montages, tutorials, travel, interviews. No presets, no menus. Ask questions, confirm the plan, execute, iterate, persist. Production-correctness rules are hard; everything else is artistic freedom.\n---\n\n# Video Use\n\n## Principle\n\n1. **LLM reasons from raw transcript + on-demand visuals.** The only derived artifact that earns its keep is a packed phrase-level transcript (`takes_packed.md`). Everything else — filler tagging, retake detection, shot classification, emphasis scoring — you derive at decision time.\n2. **Audio is primary, visuals follow.** Cut candidates come from speech boundaries and silence gaps. Drill into visuals only at decision points.\n3. **Ask → confirm → execute → iterate → persist.** Never touch the cut until the user has confirmed the strategy in plain English.\n4. **Generalize.** Do not assume what kind of video this is. Look at the material, ask the user, then edit.\n5. **Artistic freedom is the default.** Every specific value, preset, font, color, duration, pitch structure, and technique in this document is a *worked example* from one proven video — not a mandate. Read them to understand what's possible and why each worked. Then make your own taste calls based on what the material actually is and what the user actually wants. **The only things you MUST do are in the Hard Rules section below.** Everything else is yours.\n6. **Invent freely.** If the material calls for a technique not described here — split-screen, picture-in-picture, lower-third identity cards, reaction cuts, speed ramps, freeze frames, crossfades, match cuts, L-cuts, J-cuts, speed ramps over breath, whatever — build it. The helpers are ffmpeg and PIL. They can do anything the format supports. Do not wait for permission.\n7. **Verify your own output before showing it to the user.** If you wouldn't ship it, don't present it.\n\n## Hard Rules (production correctness — non-negotiable)\n\nThese are the things where deviation produces silent failures or broken output. They are not taste, they are correctness. Memorize them.\n\n1. **Subtitles are applied LAST in the filter chain**, after every overlay. Otherwise overlays hide captions. Silent failure.\n2. **Per-segment extract → lossless `-c copy` concat**, not single-pass filtergraph. Otherwise you double-encode every segment when overlays are added.\n3. **30ms audio fades at every segment boundary** (`afade=t=in:st=0:d=0.03,afade=t=out:st={dur-0.03}:d=0.03`). Otherwise audible pops at every cut.\n4. **Overlays use `setpts=PTS-STARTPTS+T/TB`** to shift the overlay's frame 0 to its window start. Otherwise you see the middle of the animation during the overlay window.\n5. **Master SRT uses output-timeline offsets**: `output_time = word.start - segment_start + segment_offset`. Otherwise captions misalign after segment concat.\n6. **Never cut inside a word.** Snap every cut edge to a word boundary from the Scribe transcript.\n7. **Pad every cut edge.** Working window: 30–200ms. Scribe timestamps drift 50–100ms — padding absorbs the drift. Tighter for fast-paced, looser for cinematic.\n8. **Word-level verbatim ASR only.** Never SRT/phrase mode (loses sub-second gap data). Never normalized fillers (loses editorial signal).\n9. **Cache transcripts per source.** Never re-transcribe unless the source file itself changed.\n10. **Parallel sub-agents for multiple animations.** Never sequential. Spawn N at once via the `Agent` tool; total wall time ≈ slowest one.\n11. **Strategy confirmation before execution.** Never touch the cut until the user has approved the plain-English plan.\n12. **All session outputs in `<videos_dir>/edit/`.** Never write inside the `video-use/` project directory.\n\nEverything else in this document is a worked example. Deviate whenever the material calls for it.\n\n## Directory layout\n\nThe skill lives in `video-use/`. User footage lives wherever they put it. All session outputs go into `<videos_dir>/edit/`.\n\n```\n<videos_dir>/\n├── <source files, untouched>\n└── edit/\n    ├── project.md               ← memory; appended every session\n    ├── takes_packed.md          ← phrase-level transcripts, the LLM's primary reading view\n    ├── edl.json                 ← cut decisions\n    ├── transcripts/<name>.json  ← cached raw Scribe JSON\n    ├── animations/slot_<id>/    ← per-animation source + render + reasoning\n    ├── clips_graded/            ← per-segment extracts with grade + fades\n    ├── master.srt               ← output-timeline subtitles\n    ├── downloads/               ← yt-dlp outputs\n    ├── verify/                  ← debug frames / timeline PNGs\n    ├── preview.mp4\n    └── final.mp4\n```\n\n## Setup\n\nFirst-time install lives in `install.md` (clone, deps, ffmpeg, skill registration, API key). Don't re-run it every session; on cold start just verify:\n\n- `ELEVENLABS_API_KEY` resolves — either in the environment or in `.env` at the video-use repo root. If missing, ask the user to paste one and write it to `.env` (never to the user's `<videos_dir>`).\n- `ffmpeg` + `ffprobe` on PATH.\n- Python deps installed (`uv sync` or `pip install -e .` inside the repo).\n- Node.js + npm available if the session needs HyperFrames or Remotion slots. HyperFrames currently requires Node.js 22+.\n- `yt-dlp`, HyperFrames, Remotion, Manim installed only on first use.\n- First-use animation setup happens inside the slot directory, never at the video-use repo root. HyperFrames can be invoked with `npx --yes hyperframes ...`; Remotion can be scaffolded with `npx create-video@latest` or installed as a project-local dependency before using its `remotion render` command.\n- This skill vendors `skills/manim-video/`. Read its SKILL.md when building a Manim slot.\n\nHelpers (`helpers/transcribe.py`, `helpers/render.py`, etc.) live alongside this SKILL.md. Resolve their paths relative to the directory containing this file — the skill is typically symlinked at `~/.claude/skills/video-use/` or `~/.codex/skills/video-use/`.\n\n## Helpers\n\n- **`transcribe.py <video>`** — single-file Scribe call. `--num-speakers N` optional. Cached.\n- **`transcribe_batch.py <videos_dir>`** — 4-worker parallel transcription. Use for multi-take.\n- **`pack_transcripts.py --edit-dir <dir>`** — `transcripts/*.json` → `takes_packed.md` (phrase-level, break on silence ≥ 0.5s).\n- **`timeline_view.py <video> <start> <end>`** — filmstrip + waveform PNG. On-demand visual drill-down. **Not a scan tool** — use it at decision points, not constantly.\n- **`render.py <edl.json> -o <out>`** — per-segment extract → concat → overlays (PTS-shifted) → subtitles LAST. `--preview` for 720p fast. `--build-subtitles` to generate master.srt inline.\n- **`grade.py <in> -o <out>`** — ffmpeg filter chain grade. Presets + `--filter '<raw>'` for custom.\n\nFor animations, create `<edit>/animations/slot_<id>/` with `Bash` and spawn a sub-agent via the `Agent` tool.\n\n## The process\n\n1. **Inventory.** `ffprobe` every source. `transcribe_batch.py` on the directory. `pack_transcripts.py` to produce `takes_packed.md`. Sample one or two `timeline_view`s for a visual first impression.\n2. **Pre-scan for problems.** One pass over `takes_packed.md` to note verbal slips, obvious mis-speaks, or phrasings to avoid. Plain list, feed into the editor brief.\n3. **Converse.** Describe what you see in plain English. Ask questions *shaped by the material*. Collect: content type, target length/aspect, aesthetic/brand direction, pacing feel, must-preserve moments, must-cut moments, animation and grade preferences, subtitle needs. Do not use a fixed checklist — the right questions are different every time.\n4. **Propose strategy.** 4–8 sentences: shape, take choices, cut direction, animation plan, grade direction, subtitle style, length estimate. **Wait for confirmation.**\n5. **Execute.** Produce `edl.json` via the editor sub-agent brief. Drill into `timeline_view` at ambiguous moments. Build animations in parallel sub-agents. Apply grade per-segment. Compose via `render.py`.\n6. **Preview.** `render.py --preview`.\n7. **Self-eval (before showing the user).** Run `timeline_view` on the **rendered output** (not the sources) at every cut boundary (±1.5s window). Check each image for:\n   - Visual discontinuity / flash / jump at the cut\n   - Waveform spike at the boundary (audio pop that slipped past the 30ms fade)\n   - Subtitle hidden behind an overlay (Rule 1 violation)\n   - Overlay misaligned or showing wrong frames (Rule 4 violation)\n\n   Also sample: first 2s, last 2s, and 2–3 mid-points — check grade consistency, subtitle readability, overall coherence. Run `ffprobe` on the output to verify duration matches the EDL expectation.\n\n   Measure the audio, don't assume it: `ffmpeg -i out.mp4 -af ebur128=peak=true -f null -` for integrated loudness and true peak, plus RMS per section (dialogue, music-only, end card). An end card 15 dB under the dialogue, or effects louder than speech, is a bug. You cannot listen: say so, and report the numbers.\n\n   For anything the user will publish (launch, promo, ad), also spawn one **critic sub-agent** with the rendered file, the EDL, and any reference videos the user gave. Brief it to roast, not to praise: a verdict, ranked problems with timecodes and evidence (frames, levels), and the 5 fixes to do first. Fresh eyes catch what the author stopped seeing — cut-off payoff lines, 0.5s memes, unreadable 28px text at phone size.\n\n   If anything fails: fix → re-render → re-eval. **Cap at 3 self-eval passes** — if issues remain after 3, flag them to the user rather than looping forever. Only present the preview once the self-eval passes.\n8. **Iterate + persist.** Natural-language feedback, re-plan, re-render. Never re-transcribe. Final render on confirmation. Append to `project.md`.\n\n## Cut craft (techniques)\n\n- **Audio-first.** Candidate cuts from word boundaries and silence gaps.\n- **Preserve peaks.** Laughs, punchlines, emphasis beats. Extend past punchlines to include reactions — the laugh IS the beat.\n- **Speaker handoffs** benefit from air between utterances. Common values: 400–600ms. Less for fast-paced, more for cinematic. Taste call.\n- **Audio events as signals.** `(laughs)`, `(sighs)`, `(applause)` mark beats. Extend past them.\n- **Silence gaps are cut candidates.** Silences ≥400ms are usually the cleanest. 150–400ms phrase boundaries are usable with a visual check. <150ms is unsafe (mid-phrase).\n- **Example cut padding** (the launch video shipped with this): 50ms before the first kept word, 80ms after the last. Tighter for montage energy, looser for documentary. Stay in the 30–200ms working window (Hard Rule 7).\n- **Never reason audio and video independently.** Every cut must work on both tracks.\n\n## The packed transcript (primary reading view)\n\n`pack_transcripts.py` reads all `transcripts/*.json` and produces one markdown file where each take is a list of phrase-level lines, each prefixed with its `[start-end]` time range. Phrases break on any silence ≥ 0.5s OR speaker change. This is the artifact the editor sub-agent reads to pick cuts — it gives word-boundary precision from text alone at 1/10 the tokens of raw JSON.\n\nExample line:\n```\n## C0103  (duration: 43.0s, 8 phrases)\n  [002.52-005.36] S0 Ninety percent of what a web agent does is completely wasted.\n  [006.08-006.74] S0 We fixed this.\n```\n\n## Editor sub-agent brief (for multi-take selection)\n\nWhen the task is \"pick the best take of each beat across many clips,\" spawn a dedicated sub-agent with a brief shaped like this. The structure is load-bearing; the pitch-shape example is not.\n\n```\nYou are editing a <type> video. Pick the best take of each beat and \nassemble them chronologically by beat, not by source clip order.\n\nINPUTS:\n  - takes_packed.md (time-annotated phrase-level transcripts of all takes)\n  - Product/narrative context: <2 sentences from the user>\n  - Speaker(s): <name, role, delivery style note>\n  - Expected structure: <pick an archetype or invent one>\n  - Verbal slips to avoid: <list from the pre-scan pass>\n  - Target runtime: <seconds>\n\nCommon structural archetypes (pick, adapt, or invent):\n  - Tech launch / demo:   HOOK → PROBLEM → SOLUTION → BENEFIT → EXAMPLE → CTA\n  - Tutorial:             INTRO → SETUP → STEPS → GOTCHAS → RECAP\n  - Interview:            (QUESTION → ANSWER → FOLLOWUP) repeat\n  - Travel / event:       ARRIVAL → HIGHLIGHTS → QUIET MOMENTS → DEPARTURE\n  - Documentary:          THESIS → EVIDENCE → COUNTERPOINT → CONCLUSION\n  - Music / performance:  INTRO → VERSE → CHORUS → BRIDGE → OUTRO\n  - Or invent your own.\n\nRULES:\n  - Start/end times must fall on word boundaries from the transcript.\n  - Pad cut boundaries (working window 30–200ms).\n  - Prefer silences ≥ 400ms as cut targets.\n  - Unavoidable slips are kept if no better take exists. Note them in \"reason\".\n  - If over budget, revise: drop a beat or trim tails. Report total and self-correct.\n\nOUTPUT (JSON array, no prose):\n  [{\"source\": \"C0103\", \"start\": 2.42, \"end\": 6.85, \"beat\": \"HOOK\",\n    \"quote\": \"...\", \"reason\": \"...\"}, ...]\n\nReturn the final EDL and a one-line total runtime check.\n```\n\n## Color grade (when requested)\n\nYour job is to **reason about the image**, not apply a preset. Look at a frame (via `timeline_view`), decide what's wrong, adjust one thing, look again.\n\nMental model is ASC CDL. Per channel: `out = (in * slope + offset) ** power`, then global saturation. `slope` → highlights, `offset` → shadows, `power` → midtones.\n\n**Example filter chains** (`grade.py` has `--list-presets`; use them as starting points or mix your own):\n\n- **`warm_cinematic`** — retro/technical, subtle teal/orange split, desaturated. Shipped in a real launch video. Safe for talking heads.\n- **`neutral_punch`** — minimal corrective: contrast bump + gentle S-curve. No hue shifts.\n- **`none`** — straight copy. Default when the user hasn't asked.\n\nFor anything else — portraiture, nature, product, music video, documentary — invent your own chain. `grade.py --filter '<raw ffmpeg>'` accepts any filter string.\n\nHard rules: apply **per-segment during extraction** (not post-concat, which re-encodes twice). Never go aggressive without testing skin tones.\n\n## Subtitles (when requested)\n\nSubtitles have three dimensions worth reasoning about: **chunking** (1/2/3/sentence per line), **case** (UPPER/Title/Natural), and **placement** (margin from bottom). The right combo depends on content.\n\n**Worked styles** — pick, adapt, or invent:\n\n**`bold-overlay`** — short-form tech launch, fast-paced social. ~2-word chunks, UPPERCASE, break on punctuation and pauses ≥ 0.3s, grow to 3 words rather than flash a cue < 0.35s (`chunk_words` in `render.py`), Helvetica 18 Bold, white-on-outline, `MarginV=35`. `render.py` ships with this as `SUB_FORCE_STYLE`.\n\n```\nFontName=Helvetica,FontSize=18,Bold=1,\nPrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BackColour=&H00000000,\nBorderStyle=1,Outline=2,Shadow=0,\nAlignment=2,MarginV=35\n```\n\n**`natural-sentence`** (if you invent this mode) — narrative, documentary, education. 4–7 word chunks, sentence case, break on natural pauses, `MarginV=60–80`, larger font for readability, slightly wider max-width. No shipped force_style — design one if you need it.\n\nInvent a third style if neither fits. Hard rules: subtitles LAST (Rule 1), output-timeline offsets (Rule 5).\n\n## Animations (when requested)\n\nAnimations match the content and the brand. **Get the palette, font, and visual language from the conversation** — never assume a default. If the user hasn't told you, propose a palette in the strategy phase and wait for confirmation before building anything.\n\n**Tool options:**\n\nPick the engine per animation slot. Do not default to Remotion just because the animation is web-adjacent.\n\n- **HyperFrames** — Browser-native HTML/CSS/GSAP video compositions: product UI motion, website-to-video or mockup-to-video captures, kinetic typography, landing-page/storyboard promos, data-driven UI states, transparent WebM overlays, and clips that need deterministic frame capture plus HyperFrames lint/validate/render checks. Best when the animation should be authored and verified like a web composition instead of a React component tree.\n- **Remotion** — React/CSS compositions with component state, reusable React primitives, or an existing Remotion brand system. Best when the user specifically asks for React/Remotion or when React composition is the simpler authoring model.\n- **Manim** — formal diagrams, state machines, equation derivations, graph morphs. Read `skills/manim-video/SKILL.md` and its references for depth.\n- **PIL + PNG sequence + ffmpeg** — simple overlay cards: counters, typewriter text, single bar reveals, progressive draws. Fast to iterate, any aesthetic you want. The launch video used this.\n\nFor HyperFrames slots, scaffold the slot inside `edit/animations/slot_<id>/` with `npx --yes hyperframes init . --example blank --non-interactive --skip-skills`, build the HTML composition there, run the HyperFrames checks that fit the slot (`lint`, `validate`, and a draft render when practical), then produce the final overlay video with `npx --yes hyperframes render . -o render.mp4` or `--format webm -o render.webm` when alpha is required. Point the EDL overlay `file` at the actual rendered path.\n\nFor Remotion slots, keep the Remotion project isolated inside the same slot directory, scaffold with `npx create-video@latest` or install Remotion locally there, render the composition to `render.mp4` with the project-local `remotion render` command, and verify duration and dimensions with `ffprobe`.\n\nNone is mandatory. Invent hybrids if useful (e.g., PIL background with a HyperFrames or Remotion layer on top).\n\n**Duration rules of thumb, context-dependent:**\n\n- **Sync-to-narration explanations.** A viewer needs to parse the content at 1×. Rough floor 3s, typical 5–7s for simple cards, 8–14s for complex diagrams. The launch video shipped at 5–7s per simple card.\n- **Beat-synced accents** (music video, fast montage). 0.5–2s is fine — they're visual accents, not information. The \"readable at 1×\" rule becomes *\"recognizable at 1×\"*, not *\"fully parseable.\"*\n- **Hold the final frame ≥ 1s** before the cut (universal).\n- **Over voiceover:** total duration ≥ `narration_length + 1s` (universal).\n- **Never parallel-reveal independent elements** — the eye can't track two new things at once. One thing, pause, next thing.\n\n**Animation payoff timing (rule for sync-to-narration):** get the payoff word's timestamp. Start the overlay `reveal_duration` seconds earlier so the landing frame coincides with the spoken payoff word. Without this sync the animation feels disconnected.\n\n**Easing** (universal — never `linear`, it looks robotic):\n\n```python\ndef ease_out_cubic(t):    return 1 - (1 - t) ** 3\ndef ease_in_out_cubic(t):\n    if t < 0.5: return 4 * t ** 3\n    return 1 - (-2 * t + 2) ** 3 / 2\n```\n\n`ease_out_cubic` for single reveals (slow landing). `ease_in_out_cubic` for continuous draws.\n\n**Typing text anchor trick:** center on the FULL string's width, not the partial-string width — otherwise text slides left during reveal.\n\n**Example palette** (the launch video — one aesthetic among infinite):\n- Background `(10, 10, 10)` near-black\n- Accent `#FF5A00` / `(255, 90, 0)` orange\n- Labels `(110, 110, 110)` dim gray\n- Font: Menlo Bold at `/System/Library/Fonts/Menlo.ttc` (index 1)\n- ≤ 2 accent colors, ~40% empty space, minimal chrome\n- Result: terminal / retro tech feel\n\nThis is one style. If the brand is warm and serif, use that. If it's colorful and playful, use that. If the user handed you a style guide, follow it. If they didn't, propose one and confirm.\n\n**Fonts fail silently.** A web font that didn't load renders in a fallback face with no error — the video ships in \"almost Arial\". In HyperFrames/Remotion, await the font load and then assert it: `if (!document.fonts.check('700 76px \"Inter\"')) throw new Error(...)`. In PIL, pass an explicit font path; never rely on the default.\n\n**Worked example — \"show the edit\" hook** (a launch video for this tool). Instead of a title card, the first 3s visualize the editing itself: each transcript word pops in on its Scribe timestamp, with bars under it drawn from the real audio envelope; a filler (\"ummm\") grows letter by letter while it is spoken, turns orange and is cut out on screen at the same frame the audio cuts, and the next line lands immediately. It works because the picture is *driven by the same data as the sound* — one composition (Remotion) reads frame-exact word/envelope JSON produced in Python, so nothing can drift. Use the idea whenever the story is \"we removed something\": make the removal visible.\n\n**Parallel sub-agent brief** — each animation is one sub-agent spawned via the `Agent` tool. Each prompt is self-contained (sub-agents have no parent context). Include:\n\n1. One-sentence goal: *\"Build ONE animation: [spec]. Nothing else.\"*\n2. Absolute output path (`<edit>/animations/slot_<id>/render.mp4`)\n3. Exact technical spec: resolution, fps, codec, pix_fmt, CRF, duration\n4. Style palette as concrete values (RGB tuples, hex, or reference to a design system)\n5. Font path with index\n6. Frame-by-frame timeline (what happens when, with easing)\n7. Anti-list (\"no chrome, no extras, no titles unless specified\")\n8. Code pattern reference (copy helpers inline, don't import across slots)\n9. Deliverable checklist (script, render, verify duration via ffprobe, report)\n10. **\"Do not ask questions. If anything is ambiguous, pick the most obvious interpretation and proceed.\"**\n\nOne sub-agent = one file (unique filenames, parallel agents don't overwrite each other).\n\n## Music and sound effects (when requested)\n\nSound is where generated videos sound cheap. Worked rules from launch edits:\n\n- **Fewer effects.** Every effect is tied to something visible (a cut, a landing, a click). ~20 stock whooshes/risers/impacts in 18s reads as generic; ~8 reads as designed.\n- **Hit on the frame.** Most effects have an attack (silence or a build before the transient). Measure it (first sample above ~-30 dBFS of the peak) and start the file `attack` seconds *before* the visible contact frame.\n- **Duck music under speech** (roughly -12 to -15 dB relative to its music-only level), and ramp it out before a stinger or end card instead of letting its own tail decay under your CTA.\n- **Master once:** mix to PCM, then two-pass loudnorm (-14 LUFS, true peak ≤ -1 dBTP) on the final mix. Then measure per section (see Self-eval).\n- **Music taste is the user's call.** Generated music defaults to \"hype\"; offer two contrasting beds and let the user listen. Don't claim a mix sounds good — you can only measure it.\n\n## Output spec\n\nMatch the source unless the user asked for something specific. Common targets: `1920×1080@24` cinematic, `1920×1080@30` screen content, `1080×1920@30` vertical social, `3840×2160@24` 4K cinema, `1080×1080@30` square. `render.py` defaults the scale to 1080p from any source; pass `--filter` or edit the extract command for other targets. Worth asking the user which delivery format matters.\n\n## EDL format\n\n```json\n{\n  \"version\": 1,\n  \"sources\": {\"C0103\": \"/abs/path/C0103.MP4\", \"C0108\": \"/abs/path/C0108.MP4\"},\n  \"ranges\": [\n    {\"source\": \"C0103\", \"start\": 2.42, \"end\": 6.85,\n     \"beat\": \"HOOK\", \"quote\": \"...\", \"reason\": \"Cleanest delivery, stops before slip at 38.46.\"},\n    {\"source\": \"C0108\", \"start\": 14.30, \"end\": 28.90,\n     \"beat\": \"SOLUTION\", \"quote\": \"...\", \"reason\": \"Only take without the false start.\"}\n  ],\n  \"grade\": \"warm_cinematic\",\n  \"overlays\": [\n    {\"file\": \"edit/animations/slot_1/render.mp4\", \"start_in_output\": 0.0, \"duration\": 5.0}\n  ],\n  \"subtitles\": \"edit/master.srt\",\n  \"total_duration_s\": 87.4\n}\n```\n\n`grade` is a preset name or raw ffmpeg filter. `overlays` are rendered animation clips. `subtitles` is optional and applied LAST.\n\n## Memory — `project.md`\n\nAppend one section per session at `<edit>/project.md`:\n\n```markdown\n## Session N — YYYY-MM-DD\n\n**Strategy:** one paragraph describing the approach\n**Decisions:** take choices, cuts, grades, animations + why\n**Reasoning log:** one-line rationale for non-obvious decisions\n**Outstanding:** deferred items\n```\n\nOn startup, read `project.md` if it exists and summarize the last session in one sentence before asking whether to continue.\n\n## Anti-patterns\n\nThings that consistently fail regardless of style:\n\n- **Hierarchical pre-computed codec formats** with USABILITY / tone tags / shot layers. Over-engineering. Derive from the transcript at decision time.\n- **Hand-tuned moment-scoring functions.** The LLM picks better than any heuristic you'll write.\n- **Whisper SRT / phrase-level output.** Loses sub-second gap data. Always word-level verbatim.\n- **Running Whisper locally on CPU.** Slow and it normalizes fillers. Use hosted Scribe.\n- **Burning subtitles into base before compositing overlays.** Overlays hide them. (Hard Rule 1.)\n- **Single-pass filtergraph when you have overlays.** Double re-encodes. Use per-segment extract → concat.\n- **Linear animation easing.** Looks robotic. Always cubic.\n- **Unverified web fonts.** A failed load silently falls back to a system face. Assert the font loaded before rendering.\n- **Stock SFX on every transition.** Tie each effect to a visible event; cap the count.\n- **Hard audio cuts at segment boundaries.** Audible pops. (Hard Rule 3.)\n- **Typing text centered on the partial string.** Text slides left as it grows.\n- **Sequential sub-agents for multiple animations.** Always parallel.\n- **Editing before confirming the strategy.** Never.\n- **Re-transcribing cached sources.** Immutable outputs of immutable inputs.\n- **Assuming what kind of video it is.** Look first, ask second, edit last.\n","skills/manim-video/README.md":"# Manim Video Skill\n\nProduction pipeline for mathematical and technical animations using [Manim Community Edition](https://www.manim.community/).\n\n## What it does\n\nCreates 3Blue1Brown-style animated videos from text prompts. The agent handles the full pipeline: creative planning, Python code generation, rendering, scene stitching, and iterative refinement.\n\n## Use cases\n\n- **Concept explainers** — \"Explain how neural networks learn\"\n- **Equation derivations** — \"Animate the proof of the Pythagorean theorem\"\n- **Algorithm visualizations** — \"Show how quicksort works step by step\"\n- **Data stories** — \"Animate our before/after performance metrics\"\n- **Architecture diagrams** — \"Show our microservice architecture building up\"\n\n## Prerequisites\n\nPython 3.10+, Manim CE (`pip install manim`), LaTeX, ffmpeg.\n\n```bash\nbash skills/creative/manim-video/scripts/setup.sh\n```\n","skills/manim-video/SKILL.md":"---\nname: manim-video\ndescription: \"Production pipeline for mathematical and technical animations using Manim Community Edition. Creates 3Blue1Brown-style explainer videos, algorithm visualizations, equation derivations, architecture diagrams, and data stories. Use when users request: animated explanations, math animations, concept visualizations, algorithm walkthroughs, technical explainers, 3Blue1Brown style videos, or any programmatic animation with geometric/mathematical content.\"\nversion: 1.0.0\n---\n\n# Manim Video Production Pipeline\n\n## Creative Standard\n\nThis is educational cinema. Every frame teaches. Every animation reveals structure.\n\n**Before writing a single line of code**, articulate the narrative arc. What misconception does this correct? What is the \"aha moment\"? What visual story takes the viewer from confusion to understanding? The user's prompt is a starting point — interpret it with pedagogical ambition.\n\n**Geometry before algebra.** Show the shape first, the equation second. Visual memory encodes faster than symbolic memory. When the viewer sees the geometric pattern before the formula, the equation feels earned.\n\n**First-render excellence is non-negotiable.** The output must be visually clear and aesthetically cohesive without revision rounds. If something looks cluttered, poorly timed, or like \"AI-generated slides,\" it is wrong.\n\n**Opacity layering directs attention.** Never show everything at full brightness. Primary elements at 1.0, contextual elements at 0.4, structural elements (axes, grids) at 0.15. The brain processes visual salience in layers.\n\n**Breathing room.** Every animation needs `self.wait()` after it. The viewer needs time to absorb what just appeared. Never rush from one animation to the next. A 2-second pause after a key reveal is never wasted.\n\n**Cohesive visual language.** All scenes share a color palette, consistent typography sizing, matching animation speeds. A technically correct video where every scene uses random different colors is an aesthetic failure.\n\n## Prerequisites\n\nRun `scripts/setup.sh` to verify all dependencies. Requires: Python 3.10+, Manim Community Edition v0.20+ (`pip install manim`), LaTeX (`texlive-full` on Linux, `mactex` on macOS), and ffmpeg. Reference docs tested against Manim CE v0.20.1.\n\n## Modes\n\n| Mode | Input | Output | Reference |\n|------|-------|--------|-----------|\n| **Concept explainer** | Topic/concept | Animated explanation with geometric intuition | `references/scene-planning.md` |\n| **Equation derivation** | Math expressions | Step-by-step animated proof | `references/equations.md` |\n| **Algorithm visualization** | Algorithm description | Step-by-step execution with data structures | `references/graphs-and-data.md` |\n| **Data story** | Data/metrics | Animated charts, comparisons, counters | `references/graphs-and-data.md` |\n| **Architecture diagram** | System description | Components building up with connections | `references/mobjects.md` |\n| **Paper explainer** | Research paper | Key findings and methods animated | `references/scene-planning.md` |\n| **3D visualization** | 3D concept | Rotating surfaces, parametric curves, spatial geometry | `references/camera-and-3d.md` |\n\n## Stack\n\nSingle Python script per project. No browser, no Node.js, no GPU required.\n\n| Layer | Tool | Purpose |\n|-------|------|---------|\n| Core | Manim Community Edition | Scene rendering, animation engine |\n| Math | LaTeX (texlive/MiKTeX) | Equation rendering via `MathTex` |\n| Video I/O | ffmpeg | Scene stitching, format conversion, audio muxing |\n| TTS | ElevenLabs / Qwen3-TTS (optional) | Narration voiceover |\n\n## Pipeline\n\n```\nPLAN --> CODE --> RENDER --> STITCH --> AUDIO (optional) --> REVIEW\n```\n\n1. **PLAN** — Write `plan.md` with narrative arc, scene list, visual elements, color palette, voiceover script\n2. **CODE** — Write `script.py` with one class per scene, each independently renderable\n3. **RENDER** — `manim -ql script.py Scene1 Scene2 ...` for draft, `-qh` for production\n4. **STITCH** — ffmpeg concat of scene clips into `final.mp4`\n5. **AUDIO** (optional) — Add voiceover and/or background music via ffmpeg. See `references/rendering.md`\n6. **REVIEW** — Render preview stills, verify against plan, adjust\n\n## Project Structure\n\n```\nproject-name/\n  plan.md                # Narrative arc, scene breakdown\n  script.py              # All scenes in one file\n  concat.txt             # ffmpeg scene list\n  final.mp4              # Stitched output\n  media/                 # Auto-generated by Manim\n    videos/script/480p15/\n```\n\n## Creative Direction\n\n### Color Palettes\n\n| Palette | Background | Primary | Secondary | Accent | Use case |\n|---------|-----------|---------|-----------|--------|----------|\n| **Classic 3B1B** | `#1C1C1C` | `#58C4DD` (BLUE) | `#83C167` (GREEN) | `#FFFF00` (YELLOW) | General math/CS |\n| **Warm academic** | `#2D2B55` | `#FF6B6B` | `#FFD93D` | `#6BCB77` | Approachable |\n| **Neon tech** | `#0A0A0A` | `#00F5FF` | `#FF00FF` | `#39FF14` | Systems, architecture |\n| **Monochrome** | `#1A1A2E` | `#EAEAEA` | `#888888` | `#FFFFFF` | Minimalist |\n\n### Animation Speed\n\n| Context | run_time | self.wait() after |\n|---------|----------|-------------------|\n| Title/intro appear | 1.5s | 1.0s |\n| Key equation reveal | 2.0s | 2.0s |\n| Transform/morph | 1.5s | 1.5s |\n| Supporting label | 0.8s | 0.5s |\n| FadeOut cleanup | 0.5s | 0.3s |\n| \"Aha moment\" reveal | 2.5s | 3.0s |\n\n### Typography Scale\n\n| Role | Font size | Usage |\n|------|-----------|-------|\n| Title | 48 | Scene titles, opening text |\n| Heading | 36 | Section headers within a scene |\n| Body | 30 | Explanatory text |\n| Label | 24 | Annotations, axis labels |\n| Caption | 20 | Subtitles, fine print |\n\n### Fonts\n\n**Use monospace fonts for all text.** Manim's Pango renderer produces broken kerning with proportional fonts at all sizes. See `references/visual-design.md` for full recommendations.\n\n```python\nMONO = \"Menlo\"  # define once at top of file\n\nText(\"Fourier Series\", font_size=48, font=MONO, weight=BOLD)  # titles\nText(\"n=1: sin(x)\", font_size=20, font=MONO)                  # labels\nMathTex(r\"\\nabla L\")                                            # math (uses LaTeX)\n```\n\nMinimum `font_size=18` for readability.\n\n### Per-Scene Variation\n\nNever use identical config for all scenes. For each scene:\n- **Different dominant color** from the palette\n- **Different layout** — don't always center everything\n- **Different animation entry** — vary between Write, FadeIn, GrowFromCenter, Create\n- **Different visual weight** — some scenes dense, others sparse\n\n## Workflow\n\n### Step 1: Plan (plan.md)\n\nBefore any code, write `plan.md`. See `references/scene-planning.md` for the comprehensive template.\n\n### Step 2: Code (script.py)\n\nOne class per scene. Every scene is independently renderable.\n\n```python\nfrom manim import *\n\nBG = \"#1C1C1C\"\nPRIMARY = \"#58C4DD\"\nSECONDARY = \"#83C167\"\nACCENT = \"#FFFF00\"\nMONO = \"Menlo\"\n\nclass Scene1_Introduction(Scene):\n    def construct(self):\n        self.camera.background_color = BG\n        title = Text(\"Why Does This Work?\", font_size=48, color=PRIMARY, weight=BOLD, font=MONO)\n        self.add_subcaption(\"Why does this work?\", duration=2)\n        self.play(Write(title), run_time=1.5)\n        self.wait(1.0)\n        self.play(FadeOut(title), run_time=0.5)\n```\n\nKey patterns:\n- **Subtitles** on every animation: `self.add_subcaption(\"text\", duration=N)` or `subcaption=\"text\"` on `self.play()`\n- **Shared color constants** at file top for cross-scene consistency\n- **`self.camera.background_color`** set in every scene\n- **Clean exits** — FadeOut all mobjects at scene end: `self.play(FadeOut(Group(*self.mobjects)))`\n\n### Step 3: Render\n\n```bash\nmanim -ql script.py Scene1_Introduction Scene2_CoreConcept  # draft\nmanim -qh script.py Scene1_Introduction Scene2_CoreConcept  # production\n```\n\n### Step 4: Stitch\n\n```bash\ncat > concat.txt << 'EOF'\nfile 'media/videos/script/480p15/Scene1_Introduction.mp4'\nfile 'media/videos/script/480p15/Scene2_CoreConcept.mp4'\nEOF\nffmpeg -y -f concat -safe 0 -i concat.txt -c copy final.mp4\n```\n\n### Step 5: Review\n\n```bash\nmanim -ql --format=png -s script.py Scene2_CoreConcept  # preview still\n```\n\n## Critical Implementation Notes\n\n### Raw Strings for LaTeX\n```python\n# WRONG: MathTex(\"\\frac{1}{2}\")\n# RIGHT:\nMathTex(r\"\\frac{1}{2}\")\n```\n\n### buff >= 0.5 for Edge Text\n```python\nlabel.to_edge(DOWN, buff=0.5)  # never < 0.5\n```\n\n### FadeOut Before Replacing Text\n```python\nself.play(ReplacementTransform(note1, note2))  # not Write(note2) on top\n```\n\n### Never Animate Non-Added Mobjects\n```python\nself.play(Create(circle))  # must add first\nself.play(circle.animate.set_color(RED))  # then animate\n```\n\n## Performance Targets\n\n| Quality | Resolution | FPS | Speed |\n|---------|-----------|-----|-------|\n| `-ql` (draft) | 854x480 | 15 | 5-15s/scene |\n| `-qm` (medium) | 1280x720 | 30 | 15-60s/scene |\n| `-qh` (production) | 1920x1080 | 60 | 30-120s/scene |\n\nAlways iterate at `-ql`. Only render `-qh` for final output.\n\n## References\n\n| File | Contents |\n|------|----------|\n| `references/animations.md` | Core animations, rate functions, composition, `.animate` syntax, timing patterns |\n| `references/mobjects.md` | Text, shapes, VGroup/Group, positioning, styling, custom mobjects |\n| `references/visual-design.md` | 12 design principles, opacity layering, layout templates, color palettes |\n| `references/equations.md` | LaTeX in Manim, TransformMatchingTex, derivation patterns |\n| `references/graphs-and-data.md` | Axes, plotting, BarChart, animated data, algorithm visualization |\n| `references/camera-and-3d.md` | MovingCameraScene, ThreeDScene, 3D surfaces, camera control |\n| `references/scene-planning.md` | Narrative arcs, layout templates, scene transitions, planning template |\n| `references/rendering.md` | CLI reference, quality presets, ffmpeg, voiceover workflow, GIF export |\n| `references/troubleshooting.md` | LaTeX errors, animation errors, common mistakes, debugging |\n| `references/animation-design-thinking.md` | When to animate vs show static, decomposition, pacing, narration sync |\n| `references/updaters-and-trackers.md` | ValueTracker, add_updater, always_redraw, time-based updaters, patterns |\n| `references/paper-explainer.md` | Turning research papers into animations — workflow, templates, domain patterns |\n| `references/decorations.md` | SurroundingRectangle, Brace, arrows, DashedLine, Angle, annotation lifecycle |\n| `references/production-quality.md` | Pre-code, pre-render, post-render checklists, spatial layout, color, tempo |\n\n---\n\n## Creative Divergence (use only when user requests experimental/creative/unique output)\n\nIf the user asks for creative, experimental, or unconventional explanatory approaches, select a strategy and reason through it BEFORE designing the animation.\n\n- **SCAMPER** — when the user wants a fresh take on a standard explanation\n- **Assumption Reversal** — when the user wants to challenge how something is typically taught\n\n### SCAMPER Transformation\nTake a standard mathematical/technical visualization and transform it:\n- **Substitute**: replace the standard visual metaphor (number line → winding path, matrix → city grid)\n- **Combine**: merge two explanation approaches (algebraic + geometric simultaneously)\n- **Reverse**: derive backward — start from the result and deconstruct to axioms\n- **Modify**: exaggerate a parameter to show why it matters (10x the learning rate, 1000x the sample size)\n- **Eliminate**: remove all notation — explain purely through animation and spatial relationships\n\n### Assumption Reversal\n1. List what's \"standard\" about how this topic is visualized (left-to-right, 2D, discrete steps, formal notation)\n2. Pick the most fundamental assumption\n3. Reverse it (right-to-left derivation, 3D embedding of a 2D concept, continuous morphing instead of steps, zero notation)\n4. Explore what the reversal reveals that the standard approach hides\n","skills/manim-video/references/animation-design-thinking.md":"# Animation Design Thinking\n\nHow to decide WHAT to animate and HOW to structure it — before writing any code.\n\n## Should I animate this?\n\nNot everything benefits from animation. Motion adds cognitive load. Bad animation is worse than a good static diagram.\n\n**Animate when:**\n- A sequence unfolds over time (algorithm steps, derivation, pipeline stages)\n- Spatial relationships change (transformation, deformation, rotation)\n- Something is built from parts (construction, assembly, accumulation)\n- You're comparing states (before/after, method A vs method B)\n- Temporal evolution is the point (training curves, wave propagation, gradient descent)\n\n**Show static when:**\n- The concept is a single labeled diagram (circuit, anatomy, architecture overview)\n- Motion would distract from spatial layout\n- The viewer needs to study it carefully (dense table, reference chart)\n- The concept is already intuitive from a well-labeled figure\n\n**Rule of thumb:** If you'd explain it with \"first X, then Y, then Z\" — animate it. If you'd explain it by pointing at parts of one picture — show it static.\n\n## Decomposing a concept into animation\n\n### Step 1: Write the narration first\n\nBefore any code, write what the narrator would say. This determines:\n- **Order** — what concept comes first\n- **Duration** — how long each idea gets\n- **Visuals** — what the viewer must SEE when they HEAR each sentence\n\nA scene where the narration says \"the gradient points uphill\" must show a gradient arrow at that moment. If the visual doesn't match the audio, the viewer's brain splits attention and both tracks are lost.\n\n### Step 2: Identify visual beats\n\nA \"beat\" is a moment where something changes on screen. Mark each beat in your narration:\n\n```\n\"Consider a function f of x.\"         → [BEAT: axes + curve appear]\n\"At this point...\"                     → [BEAT: dot appears on curve]\n\"...the slope is positive.\"            → [BEAT: tangent line drawn]\n\"So the gradient tells us to go left.\" → [BEAT: arrow points left, dot moves]\n```\n\nEach beat is one `self.play()` call or a small group of simultaneous animations.\n\n### Step 3: Choose the right tool per beat\n\n| Visual need | Manim approach |\n|-------------|----------------|\n| Object appears for first time | `Create`, `Write`, `FadeIn`, `GrowFromCenter` |\n| Object transforms into another | `Transform`, `ReplacementTransform`, `FadeTransform` |\n| Attention drawn to existing object | `Indicate`, `Circumscribe`, `Flash`, `ShowPassingFlash` |\n| Continuous relationship maintained | `add_updater`, `always_redraw`, `ValueTracker` |\n| Object leaves the scene | `FadeOut`, `Uncreate`, `ShrinkToCenter` |\n| Static context that stays visible | `self.add()` (no animation) |\n\n## Pacing: the universal mistake is too fast\n\n### Timing rules\n\n| Content type | Minimum on-screen time |\n|-------------|----------------------|\n| New equation appearing | 2.0s animation + 2.0s pause |\n| New concept label | 1.0s animation + 1.0s pause |\n| Key insight (\"aha moment\") | 2.5s animation + 3.0s pause |\n| Supporting annotation | 0.8s animation + 0.5s pause |\n| Scene transition (FadeOut all) | 0.5s animation + 0.3s pause |\n\n### Breathing room\n\nAfter every reveal, add `self.wait()`. The viewer needs time to:\n1. Read the new text\n2. Connect it to what's already on screen\n3. Form an expectation about what comes next\n\n**No wait = the viewer is always behind you.** They're still reading the equation when you've already started transforming it.\n\n### Tempo variation\n\nMonotonous pacing feels like a lecture. Vary the tempo:\n- **Slow build** for core concepts (long run_time, long pauses)\n- **Quick succession** for supporting details (short run_time, minimal pauses)\n- **Dramatic pause** before the key reveal (extra `self.wait(2.0)` before the \"aha\")\n- **Rapid montage** for \"and this applies to X, Y, Z...\" sequences (`LaggedStart` with tight lag_ratio)\n\n## Narration synchronization\n\n### The \"see then hear\" principle\n\nThe visual should appear slightly BEFORE the narration describes it. When the viewer sees a circle appear and THEN hears \"consider a circle,\" the visual primes their brain for the concept. The reverse — hearing first, seeing second — creates confusion because they're searching the screen for something that isn't there yet.\n\n### Practical timing\n\n```python\n# Scene duration should match narration duration.\n# If narration for this scene is 8 seconds:\n# Total animation run_times + total self.wait() times = ~8 seconds.\n\n# Use manim-voiceover for automatic sync:\nwith self.voiceover(text=\"The gradient points downhill\") as tracker:\n    self.play(GrowArrow(gradient_arrow), run_time=tracker.duration)\n```\n\n## Equation decomposition strategy\n\n### The \"dim and reveal\" pattern\n\nWhen building a complex equation step by step:\n1. Show the full equation dimmed at `opacity=0.2` (sets expectation for where you're going)\n2. Highlight the first term at full opacity\n3. Explain it\n4. Highlight the next term, dim the first to `0.5` (it's now context)\n5. Repeat until the full equation is bright\n\nThis is better than building left-to-right because the viewer always sees the destination.\n\n### Term ordering\n\nAnimate terms in the order the viewer needs to understand them, not in the order they appear in the equation. For `E = mc²`:\n- Show `E` (the thing we want to know)\n- Then `m` (the input)\n- Then `c²` (the constant that makes it work)\n- Then the `=` (connecting them)\n\n## Architecture and pipeline diagrams\n\n### Box granularity\n\nThe most common mistake: too many boxes. Each box is a concept the viewer must track. Five boxes with clear labels beats twelve boxes with abbreviations.\n\n**Rule:** If two consecutive boxes could be labeled \"X\" and \"process X output,\" merge them into one box.\n\n### Animation strategy\n\nBuild pipelines left-to-right (or top-to-bottom) with arrows connecting them:\n1. First box appears alone → explain it\n2. Arrow grows from first to second → \"the output feeds into...\"\n3. Second box appears → explain it\n4. Repeat\n\nThen show data flowing through: `ShowPassingFlash` along the arrows, or a colored dot traversing the path.\n\n### The zoom-and-return pattern\n\nFor complex systems:\n1. Show the full overview (all boxes, small)\n2. Zoom into one box (`MovingCameraScene.camera.frame.animate`)\n3. Expand that box into its internal components\n4. Zoom back out to the overview\n5. Zoom into the next box\n\n## Common design mistakes\n\n1. **Animating everything at once.** The viewer can track 1-2 simultaneous animations. More than that and nothing registers.\n2. **No visual hierarchy.** Everything at the same opacity/size/color means nothing stands out. Use opacity layering.\n3. **Equations without context.** An equation appearing alone means nothing. Always show the geometric/visual interpretation first or simultaneously.\n4. **Skipping the \"why.\"** Showing HOW a transformation works without WHY it matters. Add a sentence/label explaining the purpose.\n5. **Identical pacing throughout.** Every animation at run_time=1.5, every wait at 1.0. Vary it.\n6. **Forgetting the audience.** A video for high schoolers needs different pacing and complexity than one for PhD students. Decide the audience in the planning phase.\n","skills/manim-video/references/animations.md":"# Animations Reference\n\n## Core Concept\n\nAn animation is a Python object that computes intermediate visual states of a mobject over time. Animations are objects passed to `self.play()`, not functions.\n\n`run_time` controls seconds (default: 1). Always specify it explicitly for important animations.\n\n## Creation Animations\n\n```python\nself.play(Create(circle))          # traces outline\nself.play(Write(equation))         # simulates handwriting (for Text/MathTex)\nself.play(FadeIn(group))           # opacity 0 -> 1\nself.play(GrowFromCenter(dot))     # scale 0 -> 1 from center\nself.play(DrawBorderThenFill(sq))  # outline first, then fill\n```\n\n## Removal Animations\n\n```python\nself.play(FadeOut(mobject))         # opacity 1 -> 0\nself.play(Uncreate(circle))        # reverse of Create\nself.play(ShrinkToCenter(group))   # scale 1 -> 0\n```\n\n## Transform Animations\n\n```python\n# Transform -- modifies the original in place\nself.play(Transform(circle, square))\n# After: circle IS the square (same object, new appearance)\n\n# ReplacementTransform -- replaces old with new\nself.play(ReplacementTransform(circle, square))\n# After: circle removed, square on screen\n\n# TransformMatchingTex -- smart equation morphing\neq1 = MathTex(r\"a^2 + b^2\")\neq2 = MathTex(r\"a^2 + b^2 = c^2\")\nself.play(TransformMatchingTex(eq1, eq2))\n```\n\n**Critical**: After `Transform(A, B)`, variable `A` references the on-screen mobject. Variable `B` is NOT on screen. Use `ReplacementTransform` when you want to work with `B` afterwards.\n\n## The .animate Syntax\n\n```python\nself.play(circle.animate.set_color(RED))\nself.play(circle.animate.shift(RIGHT * 2).scale(0.5))  # chain multiple\n```\n\n## Additional Creation Animations\n\n```python\nself.play(GrowFromPoint(circle, LEFT * 3))     # scale 0 -> 1 from a specific point\nself.play(GrowFromEdge(rect, DOWN))             # grow from one edge\nself.play(SpinInFromNothing(square))            # scale up while rotating (default PI/2)\nself.play(GrowArrow(arrow))                     # grows arrow from start to tip\n```\n\n## Movement Animations\n\n```python\n# Move a mobject along an arbitrary path\npath = Arc(radius=2, angle=PI)\nself.play(MoveAlongPath(dot, path), run_time=2)\n\n# Rotate (as a Transform, not .animate — supports about_point)\nself.play(Rotate(square, angle=PI / 2, about_point=ORIGIN), run_time=1.5)\n\n# Rotating (continuous rotation, updater-style — good for spinning objects)\nself.play(Rotating(gear, angle=TAU, run_time=4, rate_func=linear))\n```\n\n`MoveAlongPath` takes any `VMobject` as the path — use `Arc`, `CubicBezier`, `Line`, or a custom `VMobject`. Position is computed via `path.point_from_proportion()`.\n\n## Emphasis Animations\n\n```python\nself.play(Indicate(mobject))             # brief yellow flash + scale\nself.play(Circumscribe(mobject))         # draw rectangle around it\nself.play(Flash(point))                  # radial flash\nself.play(Wiggle(mobject))               # shake side to side\n```\n\n## Rate Functions\n\n```python\nself.play(FadeIn(mob), rate_func=smooth)          # default: ease in/out\nself.play(FadeIn(mob), rate_func=linear)           # constant speed\nself.play(FadeIn(mob), rate_func=rush_into)        # start slow, end fast\nself.play(FadeIn(mob), rate_func=rush_from)        # start fast, end slow\nself.play(FadeIn(mob), rate_func=there_and_back)   # animate then reverse\n```\n\n## Composition\n\n```python\n# Simultaneous\nself.play(FadeIn(title), Create(circle), run_time=2)\n\n# AnimationGroup with lag\nself.play(AnimationGroup(*[FadeIn(i) for i in items], lag_ratio=0.2))\n\n# LaggedStart\nself.play(LaggedStart(*[Write(l) for l in lines], lag_ratio=0.3, run_time=3))\n\n# Succession (sequential in one play call)\nself.play(Succession(FadeIn(title), Wait(0.5), Write(subtitle)))\n```\n\n## Updaters\n\n```python\ntracker = ValueTracker(0)\ndot = Dot().add_updater(lambda m: m.move_to(axes.c2p(tracker.get_value(), 0)))\nself.play(tracker.animate.set_value(5), run_time=3)\n```\n\n## Subtitles\n\n```python\n# Method 1: standalone\nself.add_subcaption(\"Key insight\", duration=2)\nself.play(Write(equation), run_time=2.0)\n\n# Method 2: inline\nself.play(Write(equation), subcaption=\"Key insight\", subcaption_duration=2)\n```\n\nManim auto-generates `.srt` subtitle files. Always add subcaptions for accessibility.\n\n## Timing Patterns\n\n```python\n# Pause-after-reveal\nself.play(Write(key_equation), run_time=2.0)\nself.wait(2.0)\n\n# Dim-and-focus\nself.play(old_content.animate.set_opacity(0.3), FadeIn(new_content))\n\n# Clean exit\nself.play(FadeOut(Group(*self.mobjects)), run_time=0.5)\nself.wait(0.3)\n```\n\n## Reactive Mobjects: always_redraw()\n\nRebuild a mobject from scratch every frame — essential when its geometry depends on other animated objects:\n\n```python\n# Brace that follows a resizing square\nbrace = always_redraw(Brace, square, UP)\nself.add(brace)\nself.play(square.animate.scale(2))  # brace auto-adjusts\n\n# Horizontal line that tracks a moving dot\nh_line = always_redraw(lambda: axes.get_h_line(dot.get_left()))\n\n# Label that always stays next to another mobject\nlabel = always_redraw(lambda: Text(\"here\", font_size=20).next_to(dot, UP, buff=0.2))\n```\n\nNote: `always_redraw` recreates the mobject every frame. For simple property tracking, use `add_updater` instead (cheaper):\n```python\nlabel.add_updater(lambda m: m.next_to(dot, UP))\n```\n\n## TracedPath — Trajectory Tracing\n\nDraw the path a point has traveled:\n\n```python\ndot = Dot(color=YELLOW)\npath = TracedPath(dot.get_center, stroke_color=YELLOW, stroke_width=2)\nself.add(dot, path)\nself.play(dot.animate.shift(RIGHT * 3 + UP * 2), run_time=2)\n# path shows the trail the dot left behind\n\n# Fading trail (dissipates over time):\npath = TracedPath(dot.get_center, dissipating_time=0.5, stroke_opacity=[0, 1])\n```\n\nUse cases: gradient descent paths, planetary orbits, function tracing, particle trajectories.\n\n## FadeTransform — Smoother Cross-Fades\n\n`Transform` morphs shapes through ugly intermediate warping. `FadeTransform` cross-fades with position matching — use it when source and target look different:\n\n```python\n# UGLY: Transform warps circle into square through a blob\nself.play(Transform(circle, square))\n\n# SMOOTH: FadeTransform cross-fades cleanly\nself.play(FadeTransform(circle, square))\n\n# FadeTransformPieces: per-submobject FadeTransform\nself.play(FadeTransformPieces(group1, group2))\n\n# TransformFromCopy: animate a COPY while keeping the original visible\nself.play(TransformFromCopy(source, target))\n# source stays on screen, a copy morphs into target\n```\n\n**Recommendation:** Use `FadeTransform` as default for dissimilar shapes. Use `Transform`/`ReplacementTransform` only for similar shapes (circle→ellipse, equation→equation).\n\n## ApplyMatrix — Linear Transformation Visualization\n\nAnimate a matrix transformation on mobjects:\n\n```python\n# Apply a 2x2 matrix to a grid\nmatrix = [[2, 1], [1, 1]]\nself.play(ApplyMatrix(matrix, number_plane), run_time=2)\n\n# Also works on individual mobjects\nself.play(ApplyMatrix([[0, -1], [1, 0]], square))  # 90-degree rotation\n```\n\nPairs with `LinearTransformationScene` — see `camera-and-3d.md`.\n\n## squish_rate_func — Time-Window Staggering\n\nCompress any rate function into a time window within an animation. Enables overlapping stagger without `LaggedStart`:\n\n```python\nself.play(\n    FadeIn(a, rate_func=squish_rate_func(smooth, 0, 0.5)),    # 0% to 50%\n    FadeIn(b, rate_func=squish_rate_func(smooth, 0.25, 0.75)), # 25% to 75%\n    FadeIn(c, rate_func=squish_rate_func(smooth, 0.5, 1.0)),  # 50% to 100%\n    run_time=2\n)\n```\n\nMore precise than `LaggedStart` when you need exact overlap control.\n\n## Additional Rate Functions\n\n```python\nfrom manim import (\n    smooth, linear, rush_into, rush_from,\n    there_and_back, there_and_back_with_pause,\n    running_start, double_smooth, wiggle,\n    lingering, exponential_decay, not_quite_there,\n    squish_rate_func\n)\n\n# running_start: pulls back before going forward (anticipation)\nself.play(FadeIn(mob, rate_func=running_start))\n\n# there_and_back_with_pause: goes there, holds, comes back\nself.play(mob.animate.shift(UP), rate_func=there_and_back_with_pause)\n\n# not_quite_there: stops at a fraction of the full animation\nself.play(FadeIn(mob, rate_func=not_quite_there(0.7)))\n```\n\n## ShowIncreasingSubsets / ShowSubmobjectsOneByOne\n\nReveal group members progressively — ideal for algorithm visualization:\n\n```python\n# Reveal array elements one at a time\narray = Group(*[Square() for _ in range(8)]).arrange(RIGHT)\nself.play(ShowIncreasingSubsets(array), run_time=3)\n\n# Show submobjects with staggered appearance\nself.play(ShowSubmobjectsOneByOne(code_lines), run_time=4)\n```\n\n## ShowPassingFlash\n\nA flash of light travels along a path:\n\n```python\n# Flash traveling along a curve\nself.play(ShowPassingFlash(curve.copy().set_color(YELLOW), time_width=0.3))\n\n# Great for: data flow, electrical signals, network traffic\n```\n","skills/manim-video/references/camera-and-3d.md":"# Camera and 3D Reference\n\n## MovingCameraScene (2D Camera Control)\n\n```python\nclass ZoomExample(MovingCameraScene):\n    def construct(self):\n        circle = Circle(radius=2, color=BLUE)\n        self.play(Create(circle))\n        # Zoom in\n        self.play(self.camera.frame.animate.set(width=4).move_to(circle.get_top()), run_time=2)\n        self.wait(2)\n        # Zoom back out\n        self.play(self.camera.frame.animate.set(width=14.222).move_to(ORIGIN), run_time=2)\n```\n\n### Camera Operations\n\n```python\nself.camera.frame.animate.set(width=6)     # zoom in\nself.camera.frame.animate.set(width=20)    # zoom out\nself.camera.frame.animate.move_to(target)  # pan\nself.camera.frame.save_state()             # save\nself.play(Restore(self.camera.frame))      # restore\n```\n\n## ThreeDScene\n\n```python\nclass ThreeDExample(ThreeDScene):\n    def construct(self):\n        self.set_camera_orientation(phi=60*DEGREES, theta=-45*DEGREES)\n        axes = ThreeDAxes()\n        surface = Surface(\n            lambda u, v: axes.c2p(u, v, np.sin(u) * np.cos(v)),\n            u_range=[-PI, PI], v_range=[-PI, PI], resolution=(30, 30)\n        )\n        surface.set_color_by_gradient(BLUE, GREEN, YELLOW)\n        self.play(Create(axes), Create(surface))\n        self.begin_ambient_camera_rotation(rate=0.2)\n        self.wait(5)\n        self.stop_ambient_camera_rotation()\n```\n\n### Camera Control in 3D\n\n```python\nself.set_camera_orientation(phi=70*DEGREES, theta=-45*DEGREES)\nself.move_camera(phi=45*DEGREES, theta=30*DEGREES, run_time=2)\nself.begin_ambient_camera_rotation(rate=0.2)\n```\n\n### 3D Mobjects\n\n```python\nsphere = Sphere(radius=1).set_color(BLUE).set_opacity(0.7)\ncube = Cube(side_length=2, fill_color=GREEN, fill_opacity=0.5)\narrow = Arrow3D(start=ORIGIN, end=[2, 1, 1], color=RED)\n# 2D text facing camera:\nlabel = Text(\"Label\", font_size=30)\nself.add_fixed_in_frame_mobjects(label)\n```\n\n### Parametric Curves\n\n```python\nhelix = ParametricFunction(\n    lambda t: [np.cos(t), np.sin(t), t / (2*PI)],\n    t_range=[0, 4*PI], color=YELLOW\n)\n```\n\n## When to Use 3D\n- Surfaces, vector fields, spatial geometry, 3D transforms\n## When NOT to Use 3D\n- 2D concepts, text-heavy scenes, flat data (bar charts, time series)\n\n## ZoomedScene — Inset Zoom\n\nShow a magnified inset of a detail while keeping the full view visible:\n\n```python\nclass ZoomExample(ZoomedScene):\n    def __init__(self, **kwargs):\n        super().__init__(\n            zoom_factor=0.3,           # how much of the scene the zoom box covers\n            zoomed_display_height=3,   # size of the inset\n            zoomed_display_width=3,\n            zoomed_camera_frame_starting_position=ORIGIN,\n            **kwargs\n        )\n\n    def construct(self):\n        self.camera.background_color = BG\n        # ... create your scene content ...\n\n        # Activate the zoom\n        self.activate_zooming()\n\n        # Move the zoom frame to a point of interest\n        self.play(self.zoomed_camera.frame.animate.move_to(detail_point))\n        self.wait(2)\n\n        # Deactivate\n        self.play(self.get_zoomed_display_pop_out_animation(), rate_func=lambda t: smooth(1-t))\n```\n\nUse cases: zooming into a specific term in an equation, showing fine detail in a diagram, magnifying a region of a plot.\n\n## LinearTransformationScene — Linear Algebra\n\nPre-built scene with basis vectors and grid for visualizing matrix transformations:\n\n```python\nclass LinearTransformExample(LinearTransformationScene):\n    def __init__(self, **kwargs):\n        super().__init__(\n            show_coordinates=True,\n            show_basis_vectors=True,\n            **kwargs\n        )\n\n    def construct(self):\n        matrix = [[2, 1], [1, 1]]\n\n        # Add a vector before applying the transform\n        vector = self.get_vector([1, 2], color=YELLOW)\n        self.add_vector(vector)\n\n        # Apply the transformation — grid, basis vectors, and your vector all transform\n        self.apply_matrix(matrix)\n        self.wait(2)\n```\n\nThis produces the signature 3Blue1Brown \"Essence of Linear Algebra\" look — grid lines deforming, basis vectors stretching, determinant visualized through area change.\n","skills/manim-video/references/decorations.md":"# Decorations and Visual Polish\n\nDecorations are mobjects that annotate, highlight, or frame other mobjects. They turn a technically correct animation into a visually polished one.\n\n## SurroundingRectangle\n\nDraws a rectangle around any mobject. The go-to for highlighting:\n\n```python\nhighlight = SurroundingRectangle(\n    equation[2],            # the term to highlight\n    color=YELLOW,\n    buff=0.15,              # padding between content and border\n    corner_radius=0.1,      # rounded corners\n    stroke_width=2\n)\nself.play(Create(highlight))\nself.wait(1)\nself.play(FadeOut(highlight))\n```\n\n### Around part of an equation\n\n```python\neq = MathTex(r\"E\", r\"=\", r\"m\", r\"c^2\")\nbox = SurroundingRectangle(eq[2:], color=YELLOW, buff=0.1)  # highlight \"mc²\"\nlabel = Text(\"mass-energy\", font_size=18, font=\"Menlo\", color=YELLOW)\nlabel.next_to(box, DOWN, buff=0.2)\nself.play(Create(box), FadeIn(label))\n```\n\n## BackgroundRectangle\n\nSemi-transparent background behind text for readability over complex scenes:\n\n```python\nbg = BackgroundRectangle(equation, fill_opacity=0.7, buff=0.2, color=BLACK)\nself.play(FadeIn(bg), Write(equation))\n\n# Or using set_stroke for a \"backdrop\" effect on the text itself:\nlabel.set_stroke(BLACK, width=5, background=True)\n```\n\nThe `set_stroke(background=True)` approach is cleaner for text labels over graphs/diagrams.\n\n## Brace and BraceLabel\n\nCurly braces that annotate sections of a diagram or equation:\n\n```python\nbrace = Brace(equation[2:4], DOWN, color=YELLOW)\nbrace_label = brace.get_text(\"these terms\", font_size=20)\nself.play(GrowFromCenter(brace), FadeIn(brace_label))\n\n# Between two specific points\nbrace = BraceBetweenPoints(point_a, point_b, direction=UP)\n```\n\n### Brace placement\n\n```python\n# Below a group\nBrace(group, DOWN)\n# Above a group\nBrace(group, UP)\n# Left of a group\nBrace(group, LEFT)\n# Right of a group\nBrace(group, RIGHT)\n```\n\n## Arrows for Annotation\n\n### Straight arrows pointing to mobjects\n\n```python\narrow = Arrow(\n    start=label.get_bottom(),\n    end=target.get_top(),\n    color=YELLOW,\n    stroke_width=2,\n    buff=0.1,                    # gap between arrow tip and target\n    max_tip_length_to_length_ratio=0.15  # small arrowhead\n)\nself.play(GrowArrow(arrow), FadeIn(label))\n```\n\n### Curved arrows\n\n```python\narrow = CurvedArrow(\n    start_point=source.get_right(),\n    end_point=target.get_left(),\n    angle=PI/4,                  # curve angle\n    color=PRIMARY\n)\n```\n\n### Labeling with arrows\n\n```python\n# LabeledArrow: arrow with built-in text label\narr = LabeledArrow(\n    Text(\"gradient\", font_size=16, font=\"Menlo\"),\n    start=point_a, end=point_b, color=RED\n)\n```\n\n## DashedLine and DashedVMobject\n\n```python\n# Dashed line (for asymptotes, construction lines, implied connections)\nasymptote = DashedLine(\n    axes.c2p(2, -3), axes.c2p(2, 3),\n    color=YELLOW, dash_length=0.15\n)\n\n# Make any VMobject dashed\ndashed_circle = DashedVMobject(Circle(radius=2, color=BLUE), num_dashes=30)\n```\n\n## Angle and RightAngle Markers\n\n```python\nline1 = Line(ORIGIN, RIGHT * 2)\nline2 = Line(ORIGIN, UP * 2 + RIGHT)\n\n# Angle arc between two lines\nangle = Angle(line1, line2, radius=0.5, color=YELLOW)\nangle_value = angle.get_value()  # radians\n\n# Right angle marker (the small square)\nright_angle = RightAngle(line1, Line(ORIGIN, UP * 2), length=0.3, color=WHITE)\n```\n\n## Cross (strikethrough)\n\nMark something as wrong or deprecated:\n\n```python\ncross = Cross(old_equation, color=RED, stroke_width=4)\nself.play(Create(cross))\n# Then show the correct version\n```\n\n## Underline\n\n```python\nunderline = Underline(important_text, color=ACCENT, stroke_width=3)\nself.play(Create(underline))\n```\n\n## Color Highlighting Workflow\n\n### Method 1: At creation with t2c\n\n```python\ntext = Text(\"The gradient is negative here\", t2c={\"gradient\": BLUE, \"negative\": RED})\n```\n\n### Method 2: set_color_by_tex after creation\n\n```python\neq = MathTex(r\"\\nabla L = -\\frac{\\partial L}{\\partial w}\")\neq.set_color_by_tex(r\"\\nabla\", BLUE)\neq.set_color_by_tex(r\"\\partial\", RED)\n```\n\n### Method 3: Index into submobjects\n\n```python\neq = MathTex(r\"a\", r\"+\", r\"b\", r\"=\", r\"c\")\neq[0].set_color(RED)    # \"a\"\neq[2].set_color(BLUE)   # \"b\"\neq[4].set_color(GREEN)  # \"c\"\n```\n\n## Combining Annotations\n\nLayer multiple annotations for emphasis:\n\n```python\n# Highlight a term, add a brace, and an arrow — in sequence\nbox = SurroundingRectangle(eq[2], color=YELLOW, buff=0.1)\nbrace = Brace(eq[2], DOWN, color=YELLOW)\nlabel = brace.get_text(\"learning rate\", font_size=18)\n\nself.play(Create(box))\nself.wait(0.5)\nself.play(FadeOut(box), GrowFromCenter(brace), FadeIn(label))\nself.wait(1.5)\nself.play(FadeOut(brace), FadeOut(label))\n```\n\n### The annotation lifecycle\n\nAnnotations should follow a rhythm:\n1. **Appear** — draw attention (Create, GrowFromCenter)\n2. **Hold** — viewer reads and understands (self.wait)\n3. **Disappear** — clear the stage for the next thing (FadeOut)\n\nNever leave annotations on screen indefinitely — they become visual noise once their purpose is served.\n","skills/manim-video/references/equations.md":"# Equations and LaTeX Reference\n\n## Basic LaTeX\n\n```python\neq = MathTex(r\"E = mc^2\")\neq = MathTex(r\"f(x) &= x^2 + 2x + 1 \\\\ &= (x + 1)^2\")  # multi-line aligned\n```\n\n**Always use raw strings (`r\"\"`).**\n\n## Step-by-Step Derivations\n\n```python\nstep1 = MathTex(r\"a^2 + b^2 = c^2\")\nstep2 = MathTex(r\"a^2 = c^2 - b^2\")\nself.play(Write(step1), run_time=1.5)\nself.wait(1.5)\nself.play(TransformMatchingTex(step1, step2), run_time=1.5)\n```\n\n## Selective Color\n\n```python\neq = MathTex(r\"a^2\", r\"+\", r\"b^2\", r\"=\", r\"c^2\")\neq[0].set_color(RED)\neq[4].set_color(GREEN)\n```\n\n## Building Incrementally\n\n```python\nparts = MathTex(r\"f(x)\", r\"=\", r\"\\sum_{n=0}^{\\infty}\", r\"\\frac{f^{(n)}(a)}{n!}\", r\"(x-a)^n\")\nself.play(Write(parts[0:2]))\nself.wait(0.5)\nself.play(Write(parts[2]))\nself.wait(0.5)\nself.play(Write(parts[3:]))\n```\n\n## Highlighting\n\n```python\nhighlight = SurroundingRectangle(eq[2], color=YELLOW, buff=0.1)\nself.play(Create(highlight))\nself.play(Indicate(eq[4], color=YELLOW))\n```\n\n## Annotation\n\n```python\nbrace = Brace(eq, DOWN, color=YELLOW)\nlabel = brace.get_text(\"Fundamental Theorem\", font_size=24)\nself.play(GrowFromCenter(brace), Write(label))\n```\n\n## Common LaTeX\n\n```python\nMathTex(r\"\\frac{a}{b}\")                  # fraction\nMathTex(r\"\\alpha, \\beta, \\gamma\")         # Greek\nMathTex(r\"\\sum_{i=1}^{n} x_i\")           # summation\nMathTex(r\"\\int_{0}^{\\infty} e^{-x} dx\")  # integral\nMathTex(r\"\\vec{v}\")                       # vector\nMathTex(r\"\\lim_{x \\to \\infty} f(x)\")    # limit\n```\n\n## Matrices\n\n`MathTex` supports standard LaTeX matrix environments via `amsmath` (loaded by default):\n\n```python\n# Bracketed matrix\nMathTex(r\"\\begin{bmatrix} 1 & 0 \\\\ 0 & 1 \\end{bmatrix}\")\n\n# Parenthesized matrix\nMathTex(r\"\\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}\")\n\n# Determinant (vertical bars)\nMathTex(r\"\\begin{vmatrix} a & b \\\\ c & d \\end{vmatrix}\")\n\n# Plain (no delimiters)\nMathTex(r\"\\begin{matrix} x_1 \\\\ x_2 \\\\ x_3 \\end{matrix}\")\n```\n\nFor matrices you need to animate element-by-element or color individual entries, use the `IntegerMatrix`, `DecimalMatrix`, or `MobjectMatrix` mobjects instead — see `mobjects.md`.\n\n## Cases and Piecewise Functions\n\n```python\nMathTex(r\"\"\"\n    f(x) = \\begin{cases}\n        x^2    & \\text{if } x \\geq 0 \\\\\n        -x^2   & \\text{if } x < 0\n    \\end{cases}\n\"\"\")\n```\n\n## Aligned Environments\n\nFor multi-line derivations with alignment, use `aligned` inside `MathTex`:\n\n```python\nMathTex(r\"\"\"\n    \\begin{aligned}\n        \\nabla \\cdot \\mathbf{E} &= \\frac{\\rho}{\\epsilon_0} \\\\\n        \\nabla \\cdot \\mathbf{B} &= 0 \\\\\n        \\nabla \\times \\mathbf{E} &= -\\frac{\\partial \\mathbf{B}}{\\partial t} \\\\\n        \\nabla \\times \\mathbf{B} &= \\mu_0 \\mathbf{J} + \\mu_0 \\epsilon_0 \\frac{\\partial \\mathbf{E}}{\\partial t}\n    \\end{aligned}\n\"\"\")\n```\n\nNote: `MathTex` wraps content in `align*` by default. Override with `tex_environment` if needed:\n```python\nMathTex(r\"...\", tex_environment=\"gather*\")\n```\n\n## Derivation Pattern\n\n```python\nclass DerivationScene(Scene):\n    def construct(self):\n        self.camera.background_color = BG\n        s1 = MathTex(r\"ax^2 + bx + c = 0\")\n        self.play(Write(s1))\n        self.wait(1.5)\n        s2 = MathTex(r\"x^2 + \\frac{b}{a}x + \\frac{c}{a} = 0\")\n        s2.next_to(s1, DOWN, buff=0.8)\n        self.play(s1.animate.set_opacity(0.4), TransformMatchingTex(s1.copy(), s2))\n```\n\n## substrings_to_isolate for Complex Equations\n\nFor dense equations where manually splitting into parts is impractical, use `substrings_to_isolate` to tell Manim which substrings to track as individual elements:\n\n```python\n# Without isolation — the whole expression is one blob\nlagrangian = MathTex(\n    r\"\\mathcal{L} = \\bar{\\psi}(i \\gamma^\\mu D_\\mu - m)\\psi - \\tfrac{1}{4}F_{\\mu\\nu}F^{\\mu\\nu}\"\n)\n\n# With isolation — each named substring is a separate submobject\nlagrangian = MathTex(\n    r\"\\mathcal{L} = \\bar{\\psi}(i \\gamma^\\mu D_\\mu - m)\\psi - \\tfrac{1}{4}F_{\\mu\\nu}F^{\\mu\\nu}\",\n    substrings_to_isolate=[r\"\\psi\", r\"D_\\mu\", r\"\\gamma^\\mu\", r\"F_{\\mu\\nu}\"]\n)\n# Now you can color individual terms\nlagrangian.set_color_by_tex(r\"\\psi\", BLUE)\nlagrangian.set_color_by_tex(r\"F_{\\mu\\nu}\", YELLOW)\n```\n\nEssential for `TransformMatchingTex` on complex equations — without isolation, matching fails on dense expressions.\n\n## Multi-Line Complex Equations\n\nFor equations with multiple related lines, pass each line as a separate argument:\n\n```python\nmaxwell = MathTex(\n    r\"\\nabla \\cdot \\mathbf{E} = \\frac{\\rho}{\\epsilon_0}\",\n    r\"\\nabla \\times \\mathbf{B} = \\mu_0\\mathbf{J} + \\mu_0\\epsilon_0\\frac{\\partial \\mathbf{E}}{\\partial t}\"\n).arrange(DOWN)\n\n# Each line is a separate submobject — animate independently\nself.play(Write(maxwell[0]))\nself.wait(1)\nself.play(Write(maxwell[1]))\n```\n\n## TransformMatchingTex with key_map\n\nMap specific substrings between source and target equations during transformation:\n\n```python\neq1 = MathTex(r\"A^2 + B^2 = C^2\")\neq2 = MathTex(r\"A^2 = C^2 - B^2\")\n\nself.play(TransformMatchingTex(\n    eq1, eq2,\n    key_map={\"+\": \"-\"},   # map \"+\" in source to \"-\" in target\n    path_arc=PI / 2,      # arc the pieces into position\n))\n```\n\n## set_color_by_tex — Color by Substring\n\n```python\neq = MathTex(r\"E = mc^2\")\neq.set_color_by_tex(\"E\", BLUE)\neq.set_color_by_tex(\"m\", RED)\neq.set_color_by_tex(\"c\", GREEN)\n```\n\n## TransformMatchingTex with matched_keys\n\nWhen matching substrings are ambiguous, specify which to align explicitly:\n\n```python\nkw = dict(font_size=72, t2c={\"A\": BLUE, \"B\": TEAL, \"C\": GREEN})\nlines = [\n    MathTex(r\"A^2 + B^2 = C^2\", **kw),\n    MathTex(r\"A^2 = C^2 - B^2\", **kw),\n    MathTex(r\"A^2 = (C + B)(C - B)\", **kw),\n    MathTex(r\"A = \\sqrt{(C + B)(C - B)}\", **kw),\n]\n\nself.play(TransformMatchingTex(\n    lines[0].copy(), lines[1],\n    matched_keys=[\"A^2\", \"B^2\", \"C^2\"],  # explicitly match these\n    key_map={\"+\": \"-\"},                    # map + to -\n    path_arc=PI / 2,                       # arc pieces into position\n))\n```\n\nWithout `matched_keys`, the animation matches the longest common substrings, which can produce unexpected results on complex equations (e.g., \"^2 = C^2\" matching across terms).\n","skills/manim-video/references/graphs-and-data.md":"# Graphs, Plots, and Data Visualization\n\n## Axes\n\n```python\naxes = Axes(\n    x_range=[-3, 3, 1], y_range=[-2, 2, 1],\n    x_length=8, y_length=5,\n    axis_config={\"include_numbers\": True, \"font_size\": 24}\n)\naxes.set_opacity(0.15)  # structural element\nx_label = axes.get_x_axis_label(r\"x\")\n```\n\n## Plotting\n\n```python\ngraph = axes.plot(lambda x: x**2, color=BLUE)\ngraph_label = axes.get_graph_label(graph, label=r\"x^2\", x_val=2)\narea = axes.get_area(graph, x_range=[0, 2], color=BLUE, opacity=0.3)\n```\n\n## Animated Plotting\n\n```python\nself.play(Create(graph), run_time=3)  # trace the graph\n\n# Moving dot along curve\ndot = Dot(color=YELLOW).move_to(axes.c2p(0, 0))\nself.play(MoveAlongPath(dot, graph), run_time=3)\n\n# Dynamic parameter\ntracker = ValueTracker(1)\ndynamic = always_redraw(lambda: axes.plot(lambda x: tracker.get_value() * x**2, color=BLUE))\nself.add(dynamic)\nself.play(tracker.animate.set_value(3), run_time=2)\n```\n\n## Bar Charts\n\n```python\nchart = BarChart(\n    values=[4, 6, 2, 8, 5], bar_names=[\"A\", \"B\", \"C\", \"D\", \"E\"],\n    y_range=[0, 10, 2], bar_colors=[RED, GREEN, BLUE, YELLOW, PURPLE]\n)\nself.play(Create(chart), run_time=2)\nself.play(chart.animate.change_bar_values([6, 3, 7, 4, 9]))\n```\n\n## Number Lines\n\n```python\nnl = NumberLine(x_range=[0, 10, 1], length=10, include_numbers=True)\npointer = Arrow(nl.n2p(3) + UP * 0.5, nl.n2p(3), color=RED, buff=0)\ntracker = ValueTracker(3)\npointer.add_updater(lambda m: m.put_start_and_end_on(\n    nl.n2p(tracker.get_value()) + UP * 0.5, nl.n2p(tracker.get_value())))\nself.play(tracker.animate.set_value(8), run_time=2)\n```\n\n## Animated Counters\n\n```python\ncounter = DecimalNumber(0, font_size=72, num_decimal_places=0)\nself.play(counter.animate.set_value(1000), run_time=3, rate_func=rush_from)\n```\n\n## Algorithm Visualization Pattern\n\n```python\nvalues = [5, 2, 8, 1, 9, 3]\nbars = VGroup(*[\n    Rectangle(width=0.6, height=v * 0.4, color=BLUE, fill_opacity=0.7)\n    for v in values\n]).arrange(RIGHT, buff=0.2, aligned_edge=DOWN).move_to(ORIGIN)\nself.play(LaggedStart(*[GrowFromEdge(b, DOWN) for b in bars], lag_ratio=0.1))\n# Highlight, swap, etc.\n```\n\n## Data Story Pattern\n\n```python\n# Before/After comparison\nbefore = BarChart(values=[3, 5, 2], bar_colors=[RED]*3).shift(LEFT * 3)\nafter = BarChart(values=[8, 9, 7], bar_colors=[GREEN]*3).shift(RIGHT * 3)\nself.play(Create(before)); self.wait(1)\nself.play(Create(after)); self.wait(1)\narrow = Arrow(before.get_right(), after.get_left(), color=YELLOW)\nlabel = Text(\"+167%\", font_size=36, color=YELLOW).next_to(arrow, UP)\nself.play(GrowArrow(arrow), Write(label))\n```\n\n## Graph / DiGraph — Graph Theory Visualization\n\nBuilt-in graph mobjects with automatic layout:\n\n```python\n# Undirected graph\ng = Graph(\n    vertices=[1, 2, 3, 4, 5],\n    edges=[(1, 2), (2, 3), (3, 4), (4, 5), (5, 1), (1, 3)],\n    layout=\"spring\",  # or \"circular\", \"kamada_kawai\", \"planar\", \"tree\"\n    labels=True,\n    vertex_config={\"fill_color\": PRIMARY},\n    edge_config={\"stroke_color\": SUBTLE},\n)\nself.play(Create(g))\n\n# Directed graph\ndg = DiGraph(\n    vertices=[\"A\", \"B\", \"C\"],\n    edges=[(\"A\", \"B\"), (\"B\", \"C\"), (\"C\", \"A\")],\n    layout=\"circular\",\n    labels=True,\n    edge_config={(\"A\", \"B\"): {\"stroke_color\": RED}},\n)\n\n# Add/remove vertices and edges dynamically\nself.play(g.animate.add_vertices(6, positions={6: RIGHT * 2}))\nself.play(g.animate.add_edges((1, 6)))\nself.play(g.animate.remove_vertices(3))\n```\n\nLayout algorithms: `\"spring\"`, `\"circular\"`, `\"kamada_kawai\"`, `\"planar\"`, `\"spectral\"`, `\"tree\"` (for rooted trees, specify `root=`).\n\n## ArrowVectorField / StreamLines — Vector Fields\n\n```python\n# Arrow field: arrows showing direction at each point\nfield = ArrowVectorField(\n    lambda pos: np.array([-pos[1], pos[0], 0]),  # rotation field\n    x_range=[-3, 3], y_range=[-3, 3],\n    colors=[BLUE, GREEN, YELLOW, RED]\n)\nself.play(Create(field))\n\n# StreamLines: flowing particle traces through the field\nstream = StreamLines(\n    lambda pos: np.array([-pos[1], pos[0], 0]),\n    stroke_width=2, max_anchors_per_line=30\n)\nself.add(stream)\nstream.start_animation(warm_up=True, flow_speed=1.5)\nself.wait(3)\nstream.end_animation()\n```\n\nUse cases: electromagnetic fields, fluid flow, gradient fields, ODE phase portraits.\n\n## ComplexPlane / PolarPlane\n\n```python\n# Complex plane with Re/Im labels\ncplane = ComplexPlane().add_coordinates()\ndot = Dot(cplane.n2p(2 + 1j), color=YELLOW)\nlabel = Text(\"2+i\", font_size=20).next_to(dot, UR, buff=0.1)\n\n# Apply complex function to the plane\nself.play(cplane.animate.apply_complex_function(lambda z: z**2), run_time=3)\n\n# Polar plane\npolar = PolarPlane(radius_max=3).add_coordinates()\n```\n","skills/manim-video/references/mobjects.md":"# Mobjects Reference\n\nEverything visible on screen is a Mobject. They have position, color, opacity, and can be animated.\n\n## Text\n\n```python\ntitle = Text(\"Hello World\", font_size=48, color=BLUE)\neq = MathTex(r\"E = mc^2\", font_size=40)\n\n# Multi-part (for selective coloring)\neq = MathTex(r\"a^2\", r\"+\", r\"b^2\", r\"=\", r\"c^2\")\neq[0].set_color(RED)\neq[4].set_color(BLUE)\n\n# Mixed text and math\nt = Tex(r\"The area is $\\pi r^2$\", font_size=36)\n\n# Styled markup\nt = MarkupText('<span foreground=\"#58C4DD\">Blue</span> text', font_size=30)\n```\n\n**Always use raw strings (`r\"\"`) for any string with backslashes.**\n\n## Shapes\n\n```python\ncircle = Circle(radius=1, color=BLUE, fill_opacity=0.5)\nsquare = Square(side_length=2, color=RED)\nrect = Rectangle(width=4, height=2, color=GREEN)\ndot = Dot(point=ORIGIN, radius=0.08, color=YELLOW)\nline = Line(LEFT * 2, RIGHT * 2, color=WHITE)\narrow = Arrow(LEFT, RIGHT, color=ORANGE)\nrrect = RoundedRectangle(corner_radius=0.3, width=4, height=2)\nbrace = Brace(rect, DOWN, color=YELLOW)\n```\n\n## Polygons and Arcs\n\n```python\n# Arbitrary polygon from vertices\npoly = Polygon(LEFT, UP * 2, RIGHT, color=GREEN, fill_opacity=0.3)\n\n# Regular n-sided polygon\nhexagon = RegularPolygon(n=6, color=TEAL, fill_opacity=0.4)\n\n# Triangle (shorthand for RegularPolygon(n=3))\ntri = Triangle(color=YELLOW, fill_opacity=0.5)\n\n# Arc (portion of a circle)\narc = Arc(radius=2, start_angle=0, angle=PI / 2, color=BLUE)\n\n# Arc between two points\narc_between = ArcBetweenPoints(LEFT * 2, RIGHT * 2, angle=TAU / 4, color=RED)\n\n# Curved arrow (arc with tip)\ncurved_arrow = CurvedArrow(LEFT * 2, RIGHT * 2, color=ORANGE)\n```\n\n## Sectors and Annuli\n\n```python\n# Sector (pie slice)\nsector = Sector(outer_radius=2, start_angle=0, angle=PI / 3, fill_opacity=0.7, color=BLUE)\n\n# Annulus (ring)\nring = Annulus(inner_radius=1, outer_radius=2, fill_opacity=0.5, color=GREEN)\n\n# Annular sector (partial ring)\npartial_ring = AnnularSector(\n    inner_radius=1, outer_radius=2,\n    angle=PI / 2, start_angle=0,\n    fill_opacity=0.7, color=TEAL\n)\n\n# Cutout (punch holes in a shape)\nbackground = Square(side_length=4, fill_opacity=1, color=BLUE)\nhole = Circle(radius=0.5)\ncutout = Cutout(background, hole, fill_opacity=1, color=BLUE)\n```\n\nUse cases: pie charts, ring progress indicators, Venn diagrams with arcs, geometric proofs.\n\n## Positioning\n\n```python\nmob.move_to(ORIGIN)                        # center\nmob.move_to(UP * 2 + RIGHT)               # relative\nlabel.next_to(circle, DOWN, buff=0.3)     # next to another\ntitle.to_edge(UP, buff=0.5)               # screen edge (buff >= 0.5!)\nmob.to_corner(UL, buff=0.5)               # corner\n```\n\n## VGroup vs Group\n\n**VGroup** is for collections of shapes (VMobjects only — Circle, Square, Arrow, Line, MathTex):\n```python\nshapes = VGroup(circle, square, arrow)\nshapes.arrange(DOWN, buff=0.5)\nshapes.set_color(BLUE)\n```\n\n**Group** is for mixed collections (Text + shapes, or any Mobject types):\n```python\n# Text objects are Mobjects, not VMobjects — use Group when mixing\nlabeled_shape = Group(circle, Text(\"Label\").next_to(circle, DOWN))\nlabeled_shape.move_to(ORIGIN)\n\n# FadeOut everything on screen (may contain mixed types)\nself.play(FadeOut(Group(*self.mobjects)))\n```\n\n**Rule: if your group contains any `Text()` objects, use `Group`, not `VGroup`.** VGroup will raise a TypeError on Manim CE v0.20+. MathTex and Tex are VMobjects and work with VGroup.\n\nBoth support `arrange()`, `arrange_in_grid()`, `set_opacity()`, `shift()`, `scale()`, `move_to()`.\n\n## Styling\n\n```python\nmob.set_color(BLUE)\nmob.set_fill(RED, opacity=0.5)\nmob.set_stroke(WHITE, width=2)\nmob.set_opacity(0.4)\nmob.set_z_index(1)                         # layering\n```\n\n## Specialized Mobjects\n\n```python\nnl = NumberLine(x_range=[-3, 3, 1], length=8, include_numbers=True)\ntable = Table([[\"A\", \"B\"], [\"C\", \"D\"]], row_labels=[Text(\"R1\"), Text(\"R2\")])\ncode = Code(\"example.py\", tab_width=4, font_size=20, language=\"python\")\nhighlight = SurroundingRectangle(target, color=YELLOW, buff=0.2)\nbg = BackgroundRectangle(equation, fill_opacity=0.7, buff=0.2)\n```\n\n## Custom Mobjects\n\n```python\nclass NetworkNode(Group):\n    def __init__(self, label_text, color=BLUE, **kwargs):\n        super().__init__(**kwargs)\n        self.circle = Circle(radius=0.4, color=color, fill_opacity=0.3)\n        self.label = Text(label_text, font_size=20).move_to(self.circle)\n        self.add(self.circle, self.label)\n```\n\n## Matrix Mobjects\n\nDisplay matrices as grids of numbers or mobjects:\n\n```python\n# Integer matrix\nm = IntegerMatrix([[1, 2], [3, 4]])\n\n# Decimal matrix (control decimal places)\nm = DecimalMatrix([[1.5, 2.7], [3.1, 4.9]], element_to_mobject_config={\"num_decimal_places\": 2})\n\n# Mobject matrix (any mobject in each cell)\nm = MobjectMatrix([\n    [MathTex(r\"\\pi\"), MathTex(r\"e\")],\n    [MathTex(r\"\\phi\"), MathTex(r\"\\tau\")]\n])\n\n# Bracket types: \"(\" \"[\" \"|\" or \"\\\\{\"\nm = IntegerMatrix([[1, 0], [0, 1]], left_bracket=\"[\", right_bracket=\"]\")\n```\n\nUse cases: linear algebra, transformation matrices, system-of-equations coefficient display.\n\n## Constants\n\nDirections: `UP, DOWN, LEFT, RIGHT, ORIGIN, UL, UR, DL, DR`\nColors: `RED, BLUE, GREEN, YELLOW, WHITE, GRAY, ORANGE, PINK, PURPLE, TEAL, GOLD`\nFrame: `config.frame_width = 14.222, config.frame_height = 8.0`\n\n## SVGMobject — Import SVG Files\n\n```python\nlogo = SVGMobject(\"path/to/logo.svg\")\nlogo.set_color(WHITE).scale(0.5).to_corner(UR)\nself.play(FadeIn(logo))\n\n# SVG submobjects are individually animatable\nfor part in logo.submobjects:\n    self.play(part.animate.set_color(random_color()))\n```\n\n## ImageMobject — Display Images\n\n```python\nimg = ImageMobject(\"screenshot.png\")\nimg.set_height(3).to_edge(RIGHT)\nself.play(FadeIn(img))\n```\n\nNote: images cannot be animated with `.animate` (they're raster, not vector). Use `FadeIn`/`FadeOut` and `shift`/`scale` only.\n\n## Variable — Auto-Updating Display\n\n```python\nvar = Variable(0, Text(\"x\"), num_decimal_places=2)\nvar.move_to(ORIGIN)\nself.add(var)\n\n# Animate the value\nself.play(var.tracker.animate.set_value(5), run_time=2)\n# Display auto-updates: \"x = 5.00\"\n```\n\nCleaner than manual `DecimalNumber` + `add_updater` for simple labeled-value displays.\n\n## BulletedList\n\n```python\nbullets = BulletedList(\n    \"First key point\",\n    \"Second important fact\",\n    \"Third conclusion\",\n    font_size=28\n)\nbullets.to_edge(LEFT, buff=1.0)\nself.play(Write(bullets))\n\n# Highlight individual items\nself.play(bullets[1].animate.set_color(YELLOW))\n```\n\n## DashedLine and Angle Markers\n\n```python\n# Dashed line (asymptotes, construction lines)\ndashed = DashedLine(LEFT * 3, RIGHT * 3, color=SUBTLE, dash_length=0.15)\n\n# Angle marker between two lines\nline1 = Line(ORIGIN, RIGHT * 2)\nline2 = Line(ORIGIN, UP * 2 + RIGHT)\nangle = Angle(line1, line2, radius=0.5, color=YELLOW)\nangle_label = angle.get_value()  # returns the angle in radians\n\n# Right angle marker\nright_angle = RightAngle(line1, Line(ORIGIN, UP * 2), length=0.3, color=WHITE)\n```\n\n## Boolean Operations (CSG)\n\nCombine, subtract, or intersect 2D shapes:\n\n```python\ncircle = Circle(radius=1.5, color=BLUE, fill_opacity=0.5).shift(LEFT * 0.5)\nsquare = Square(side_length=2, color=RED, fill_opacity=0.5).shift(RIGHT * 0.5)\n\n# Union, Intersection, Difference, Exclusion\nunion = Union(circle, square, color=GREEN, fill_opacity=0.5)\nintersect = Intersection(circle, square, color=YELLOW, fill_opacity=0.5)\ndiff = Difference(circle, square, color=PURPLE, fill_opacity=0.5)\nexclude = Exclusion(circle, square, color=ORANGE, fill_opacity=0.5)\n```\n\nUse cases: Venn diagrams, set theory, geometric proofs, area calculations.\n\n## LabeledArrow / LabeledLine\n\n```python\n# Arrow with built-in label (auto-positioned)\narr = LabeledArrow(Text(\"force\", font_size=18), start=LEFT, end=RIGHT, color=RED)\n\n# Line with label\nline = LabeledLine(Text(\"d = 5m\", font_size=18), start=LEFT * 2, end=RIGHT * 2)\n```\n\nAuto-handles label positioning — cleaner than manual `Arrow` + `Text().next_to()`.\n\n## Text Color/Font/Style Per-Substring (t2c, t2f, t2s, t2w)\n\n```python\n# Color specific words (t2c = text-to-color)\ntext = Text(\n    \"Gradient descent minimizes the loss function\",\n    t2c={\"Gradient descent\": BLUE, \"loss function\": RED}\n)\n\n# Different fonts per word (t2f = text-to-font)\ntext = Text(\n    \"Use Menlo for code and Inter for prose\",\n    t2f={\"Menlo\": \"Menlo\", \"Inter\": \"Inter\"}\n)\n\n# Italic/slant per word (t2s = text-to-slant)\ntext = Text(\"Normal and italic text\", t2s={\"italic\": ITALIC})\n\n# Bold per word (t2w = text-to-weight)\ntext = Text(\"Normal and bold text\", t2w={\"bold\": BOLD})\n```\n\nThese are much cleaner than creating separate Text objects and grouping them.\n\n## Backstroke for Readability Over Backgrounds\n\nWhen text overlaps other content (graphs, diagrams, images), add a dark stroke behind it:\n\n```python\n# CE syntax:\nlabel.set_stroke(BLACK, width=5, background=True)\n\n# Apply to a group\nfor mob in labels:\n    mob.set_stroke(BLACK, width=4, background=True)\n```\n\nThis is how 3Blue1Brown keeps text readable over complex backgrounds without using BackgroundRectangle.\n\n## Complex Function Transforms\n\nApply complex functions to entire mobjects — transforms the plane:\n\n```python\nc_grid = ComplexPlane()\nmoving_grid = c_grid.copy()\nmoving_grid.prepare_for_nonlinear_transform()  # adds more sample points for smooth deformation\n\nself.play(\n    moving_grid.animate.apply_complex_function(lambda z: z**2),\n    run_time=5,\n)\n\n# Also works with R3->R3 functions:\nself.play(grid.animate.apply_function(\n    lambda p: [p[0] + 0.5 * math.sin(p[1]), p[1] + 0.5 * math.sin(p[0]), p[2]]\n), run_time=5)\n```\n\n**Critical:** Call `prepare_for_nonlinear_transform()` before applying nonlinear functions — without it, the grid has too few sample points and the deformation looks jagged.\n","skills/manim-video/references/paper-explainer.md":"# Paper Explainer Workflow\n\nHow to turn a research paper into an animated explainer video.\n\n## Why animate a paper?\n\nA research paper is optimized for precision and completeness. A video is optimized for understanding and retention. The translation is NOT \"read the paper aloud with pictures\" — it's \"extract the core insight and make it feel obvious through visual storytelling.\"\n\nThe paper has one job: prove the claim is true. The video has a different job: make the viewer understand WHY the claim is true, and WHY it matters.\n\n## Who is watching?\n\nBefore anything, decide the audience:\n\n| Audience | Prerequisites | Pacing | Depth |\n|----------|--------------|--------|-------|\n| General public | None | Slow, many analogies | Intuition only, skip proofs |\n| Undergrad students | Basic math/CS | Medium, some formalism | Key equations, skip derivations |\n| Grad students / researchers | Domain knowledge | Faster, more notation | Full equations, sketch proofs |\n\nThis determines everything: vocabulary, pacing, which sections to animate, how much math to show.\n\n## The 5-minute template\n\nMost paper explainers fit this structure (scale times proportionally for longer videos):\n\n| Section | Duration | Purpose |\n|---------|----------|---------|\n| **Hook** | 0:00-0:30 | Surprising result or provocative question |\n| **Problem** | 0:30-1:30 | What was broken/missing before this paper |\n| **Key insight** | 1:30-3:00 | The core idea, explained visually |\n| **How it works** | 3:00-4:00 | Method/algorithm, simplified |\n| **Evidence** | 4:00-4:30 | Key result that proves it works |\n| **Implications** | 4:30-5:00 | Why it matters, what it enables |\n\n### What to skip\n\n- Related work survey → one sentence: \"Previous approaches did X, which had problem Y\"\n- Implementation details → skip unless they're the contribution\n- Ablation studies → show one chart at most\n- Proofs → show the key step, not the full proof\n- Hyperparameter tuning → skip entirely\n\n### What to expand\n\n- The core insight → this gets the most screen time\n- Geometric/visual intuition → if the paper has math, show what it MEANS\n- Before/after comparison → the most compelling evidence\n\n## Pre-code workflow\n\n### Gate 1: Narration script\n\nWrite the full narration before any code. Every sentence maps to a visual beat. If you can't write the narration, you don't understand the paper well enough to animate it.\n\n```markdown\n## Hook (30s)\n\"What if I told you that a model with 7 billion parameters can outperform\none with 70 billion — if you train it on the right data?\"\n\n## Problem (60s)\n\"The standard approach is to scale up. More parameters, more compute.\n[VISUAL: bar chart showing model sizes growing exponentially]\nBut Chinchilla showed us that most models are undertrained...\"\n```\n\n### Gate 2: Scene list\n\nAfter the narration, break it into scenes. Each scene is one Manim class.\n\n```markdown\nScene 1: Hook — surprising stat with animated counter\nScene 2: Problem — model size bar chart growing\nScene 3: Key insight — training data vs parameters, animated 2D plot\nScene 4: Method — pipeline diagram building left to right\nScene 5: Results — before/after comparison with animated bars\nScene 6: Closing — implications text\n```\n\n### Gate 3: Style constants\n\nBefore coding scenes, define the visual language:\n\n```python\n# style.py — import in every scene file\nBG = \"#0D1117\"\nPRIMARY = \"#58C4DD\"\nSECONDARY = \"#83C167\"\nACCENT = \"#FFFF00\"\nHIGHLIGHT = \"#FF6B6B\"\nMONO = \"Menlo\"\n\n# Color meanings for THIS paper\nMODEL_COLOR = PRIMARY      # \"the model\"\nDATA_COLOR = SECONDARY     # \"training data\"\nBASELINE_COLOR = HIGHLIGHT # \"previous approach\"\nRESULT_COLOR = ACCENT      # \"our result\"\n```\n\n## First-principles equation explanation\n\nWhen the paper has a key equation, don't just show it — build it from intuition:\n\n### The \"what would you do?\" pattern\n\n1. Pose the problem in plain language\n2. Ask what the simplest solution would be\n3. Show why it doesn't work (animate the failure)\n4. Introduce the paper's solution as the fix\n5. THEN show the equation — it now feels earned\n\n```python\n# Scene: Why we need attention (for a Transformer paper)\n# Step 1: \"How do we let each word look at every other word?\"\n# Step 2: Show naive approach (fully connected = O(n²) everything)\n# Step 3: Show it breaks (information overload, no selectivity)\n# Step 4: \"What if each word could CHOOSE which words to attend to?\"\n# Step 5: Show attention equation — Q, K, V now mean something\n```\n\n### Equation reveal strategy\n\n```python\n# Show equation dimmed first (full destination)\neq = MathTex(r\"Attention(Q,K,V) = softmax\\left(\\frac{QK^T}{\\sqrt{d_k}}\\right)V\")\neq.set_opacity(0.15)\nself.play(FadeIn(eq))\n\n# Highlight Q, K, V one at a time with color + label\nfor part, color, label_text in [\n    (r\"Q\", PRIMARY, \"Query: what am I looking for?\"),\n    (r\"K\", SECONDARY, \"Key: what do I contain?\"),\n    (r\"V\", ACCENT, \"Value: what do I output?\"),\n]:\n    eq.set_color_by_tex(part, color)\n    label = Text(label_text, font_size=18, color=color, font=MONO)\n    # position label, animate it, wait, then dim it\n```\n\n## Building architecture diagrams\n\n### The progressive build pattern\n\nDon't show the full architecture at once. Build it:\n\n1. First component appears alone → explain\n2. Arrow grows → \"this feeds into...\"\n3. Second component appears → explain\n4. Repeat until complete\n\n```python\n# Component factory\ndef make_box(label, color, width=2.0, height=0.8):\n    box = RoundedRectangle(corner_radius=0.1, width=width, height=height,\n                           color=color, fill_opacity=0.1, stroke_width=1.5)\n    text = Text(label, font_size=18, font=MONO, color=color).move_to(box)\n    return Group(box, text)\n\nencoder = make_box(\"Encoder\", PRIMARY)\ndecoder = make_box(\"Decoder\", SECONDARY).next_to(encoder, RIGHT, buff=1.5)\narrow = Arrow(encoder.get_right(), decoder.get_left(), color=DIM, stroke_width=1.5)\n\nself.play(FadeIn(encoder))\nself.wait(1)  # explain encoder\nself.play(GrowArrow(arrow))\nself.play(FadeIn(decoder))\nself.wait(1)  # explain decoder\n```\n\n### Data flow animation\n\nAfter building the diagram, show data moving through it:\n\n```python\n# Dot traveling along the pipeline\ndata_dot = Dot(color=ACCENT, radius=0.1).move_to(encoder)\nself.play(FadeIn(data_dot))\nself.play(MoveAlongPath(data_dot, arrow), run_time=1)\nself.play(data_dot.animate.move_to(decoder), run_time=0.5)\nself.play(Flash(data_dot.get_center(), color=ACCENT), run_time=0.3)\n```\n\n## Animating results\n\n### Bar chart comparison (most common)\n\n```python\n# Before/after bars\nbefore_data = [45, 52, 38, 61]\nafter_data = [78, 85, 72, 91]\nlabels = [\"Task A\", \"Task B\", \"Task C\", \"Task D\"]\n\nbefore_chart = BarChart(before_data, bar_names=labels,\n    y_range=[0, 100, 20], bar_colors=[HIGHLIGHT]*4).scale(0.6).shift(LEFT*3)\nafter_chart = BarChart(after_data, bar_names=labels,\n    y_range=[0, 100, 20], bar_colors=[SECONDARY]*4).scale(0.6).shift(RIGHT*3)\n\nbefore_label = Text(\"Baseline\", font_size=20, color=HIGHLIGHT, font=MONO)\nafter_label = Text(\"Ours\", font_size=20, color=SECONDARY, font=MONO)\n\n# Reveal baseline first, then ours (dramatic comparison)\nself.play(Create(before_chart), FadeIn(before_label))\nself.wait(1.5)\nself.play(Create(after_chart), FadeIn(after_label))\nself.wait(0.5)\n\n# Highlight the improvement\nimprovement = Text(\"+35% avg\", font_size=24, color=ACCENT, font=MONO)\nself.play(FadeIn(improvement))\n```\n\n### Training curve (for ML papers)\n\n```python\ntracker = ValueTracker(0)\ncurve = always_redraw(lambda: axes.plot(\n    lambda x: 1 - 0.8 * np.exp(-x / 3),\n    x_range=[0, tracker.get_value()], color=PRIMARY\n))\nepoch_label = always_redraw(lambda: Text(\n    f\"Epoch {int(tracker.get_value())}\", font_size=18, font=MONO\n).to_corner(UR))\n\nself.add(curve, epoch_label)\nself.play(tracker.animate.set_value(10), run_time=5, rate_func=linear)\n```\n\n## Domain-specific patterns\n\n### ML papers\n- Show data flow through the model (animated pipeline)\n- Training curves with `ValueTracker`\n- Attention heatmaps as colored grids\n- Embedding space as 2D scatter (PCA/t-SNE visualization)\n- Loss landscape as 3D surface with gradient descent dot\n\n### Physics/math papers\n- Use `LinearTransformationScene` for linear algebra\n- Vector fields with `ArrowVectorField` / `StreamLines`\n- Phase spaces with `NumberPlane` + trajectories\n- Wave equations with time-parameterized plots\n\n### Systems/architecture papers\n- Pipeline diagrams built progressively\n- `ShowPassingFlash` for data flow along arrows\n- `ZoomedScene` for zooming into components\n- Before/after latency/throughput comparisons\n\n## Common mistakes\n\n1. **Trying to cover the whole paper.** A 5-minute video can explain ONE core insight well. Covering everything means explaining nothing.\n2. **Reading the abstract as narration.** Academic writing is designed for readers, not listeners. Rewrite in conversational language.\n3. **Showing notation without meaning.** Never show a symbol without first showing what it represents visually.\n4. **Skipping the motivation.** Jumping straight to \"here's our method\" without showing why the problem matters. The Problem section is what makes the viewer care.\n5. **Identical pacing throughout.** The hook and key insight need the most visual energy. The method section can be faster. Evidence should land with impact (pause after showing the big number).\n","skills/manim-video/references/production-quality.md":"# Production Quality Checklist\n\nStandards and checks for ensuring animation output is publication-ready.\n\n## Pre-Code Checklist\n\nBefore writing any Manim code:\n\n- [ ] Narration script written with visual beats marked\n- [ ] Scene list with purpose, duration, and layout for each\n- [ ] Color palette defined with meaning assignments (`PRIMARY` = main concept, etc.)\n- [ ] `MONO = \"Menlo\"` set as the font constant\n- [ ] Target resolution and aspect ratio decided\n\n## Text Quality\n\n### Overlap prevention\n\n```python\n# RULE: buff >= 0.5 for edge text\nlabel.to_edge(DOWN, buff=0.5)     # GOOD\nlabel.to_edge(DOWN, buff=0.3)     # BAD — may clip\n\n# RULE: FadeOut previous before adding new at same position\nself.play(ReplacementTransform(note1, note2))  # GOOD\nself.play(Write(note2))                          # BAD — overlaps note1\n\n# RULE: Reduce font size for dense scenes\n# When > 4 text elements visible, use font_size=20 not 28\n```\n\n### Width enforcement\n\nLong text strings overflow the frame:\n\n```python\n# RULE: Set max width for any text that might be long\ntext = Text(\"This is a potentially long description\", font_size=22, font=MONO)\nif text.width > config.frame_width - 1.0:\n    text.set_width(config.frame_width - 1.0)\n```\n\n### Font consistency\n\n```python\n# RULE: Define MONO once, use everywhere\nMONO = \"Menlo\"\n\n# WRONG: mixing fonts\nText(\"Title\", font=\"Helvetica\")\nText(\"Label\", font=\"Arial\")\nText(\"Code\", font=\"Courier\")\n\n# RIGHT: one font\nText(\"Title\", font=MONO, weight=BOLD, font_size=48)\nText(\"Label\", font=MONO, font_size=20)\nText(\"Code\", font=MONO, font_size=18)\n```\n\n## Spatial Layout\n\n### The coordinate budget\n\nThe visible frame is approximately 14.2 wide × 8.0 tall (default 16:9). With mandatory margins:\n\n```\nUsable area: x ∈ [-6.5, 6.5], y ∈ [-3.5, 3.5]\nTop title zone: y ∈ [2.5, 3.5]\nBottom note zone: y ∈ [-3.5, -2.5]\nMain content: y ∈ [-2.5, 2.5], x ∈ [-6.0, 6.0]\n```\n\n### Fill the frame\n\nEmpty scenes look unfinished. If the main content is small, add context:\n- A dimmed grid/axes behind the content\n- A title/subtitle at the top\n- A source citation at the bottom\n- Decorative geometry at low opacity\n\n### Maximum simultaneous elements\n\n**Hard limit: 6 actively visible elements.** Beyond that, the viewer can't track everything. If you need more:\n- Dim old elements to opacity 0.3\n- Remove elements that have served their purpose\n- Split into two scenes\n\n## Animation Quality\n\n### Variety audit\n\nCheck that no two consecutive scenes use the exact same:\n- Animation type (if Scene 3 uses Write for everything, Scene 4 should use FadeIn or Create)\n- Color emphasis (rotate through palette colors)\n- Layout (center, left-right, grid — alternate)\n- Pacing (if Scene 2 was slow and deliberate, Scene 3 can be faster)\n\n### Tempo curve\n\nA good video follows a tempo curve:\n\n```\nSlow ──→ Medium ──→ FAST (climax) ──→ Slow (conclusion)\n\nScene 1: Slow (introduction, setup)\nScene 2: Medium (building understanding)\nScene 3: Medium-Fast (core content, lots of animation)\nScene 4: FAST (montage of applications/results)\nScene 5: Slow (conclusion, key takeaway)\n```\n\n### Transition quality\n\nBetween scenes:\n- **Clean exit**: `self.play(FadeOut(Group(*self.mobjects)), run_time=0.5)`\n- **Brief pause**: `self.wait(0.3)` after fadeout, before next scene's first animation\n- **Never hard-cut**: always animate the transition\n\n## Color Quality\n\n### Dimming on dark backgrounds\n\nColors that look vibrant on white look muddy on dark backgrounds (#0D1117, #1C1C1C). Test your palette:\n\n```python\n# Colors that work well on dark backgrounds:\n# Bright and saturated: #58C4DD, #83C167, #FFFF00, #FF6B6B\n# Colors that DON'T work: #666666 (invisible), #2244AA (too dark)\n\n# RULE: Structural elements (axes, grids) at opacity 0.15\n# Context elements at 0.3-0.4\n# Primary elements at 1.0\n```\n\n### Color meaning consistency\n\nOnce a color is assigned a meaning, it keeps that meaning for the entire video:\n\n```python\n# If PRIMARY (#58C4DD) means \"the model\" in Scene 1,\n# it means \"the model\" in every scene.\n# Never reuse PRIMARY for a different concept later.\n```\n\n## Data Visualization Quality\n\n### Minimum requirements for charts\n\n- Axis labels on every axis\n- Y-axis range starts at 0 (or has a clear break indicator)\n- Bar/line colors match the legend\n- Numbers on notable data points (at least the maximum and the comparison point)\n\n### Animated counters\n\nWhen showing a number changing:\n```python\n# GOOD: DecimalNumber with smooth animation\ncounter = DecimalNumber(0, font_size=48, num_decimal_places=0, font=\"Menlo\")\nself.play(counter.animate.set_value(1000), run_time=3, rate_func=rush_from)\n\n# BAD: Text that jumps between values\n```\n\n## Pre-Render Checklist\n\nBefore running `manim -qh`:\n\n- [ ] All scenes render without errors at `-ql`\n- [ ] Preview stills at `-qm` for text-heavy scenes (check kerning)\n- [ ] Background color set in every scene (`self.camera.background_color = BG`)\n- [ ] `add_subcaption()` or `subcaption=` on every significant animation\n- [ ] No text smaller than font_size=18\n- [ ] No text using proportional fonts (use monospace)\n- [ ] buff >= 0.5 on all `.to_edge()` calls\n- [ ] Clean exit (FadeOut all) at end of every scene\n- [ ] `self.wait()` after every reveal\n- [ ] Color constants used (no hardcoded hex strings in scene code)\n- [ ] All scenes use the same quality flag (don't mix `-ql` and `-qh`)\n\n## Post-Render Checklist\n\nAfter stitching the final video:\n\n- [ ] Watch the complete video at 1x speed — does it feel rushed anywhere?\n- [ ] Is there a moment where two things animate simultaneously and it's confusing?\n- [ ] Does every text label have enough time to be read?\n- [ ] Are transitions between scenes smooth (no black frames, no jarring cuts)?\n- [ ] Is the audio in sync with the visuals (if using voiceover)?\n- [ ] Is the Gibbs-like \"first impression\" good? The first 5 seconds determine if someone keeps watching\n","skills/manim-video/references/rendering.md":"# Rendering Reference\n\n## Prerequisites\n\n```bash\nmanim --version       # Manim CE\npdflatex --version    # LaTeX\nffmpeg -version       # ffmpeg\n```\n\n## CLI Reference\n\n```bash\nmanim -ql script.py Scene1 Scene2    # draft (480p 15fps)\nmanim -qm script.py Scene1           # medium (720p 30fps)\nmanim -qh script.py Scene1           # production (1080p 60fps)\nmanim -ql --format=png -s script.py Scene1  # preview still (last frame)\nmanim -ql --format=gif script.py Scene1     # GIF output\n```\n\n## Quality Presets\n\n| Flag | Resolution | FPS | Use case |\n|------|-----------|-----|----------|\n| `-ql` | 854x480 | 15 | Draft iteration (layout, timing) |\n| `-qm` | 1280x720 | 30 | Preview (use for text-heavy scenes) |\n| `-qh` | 1920x1080 | 60 | Production |\n\n**Text rendering quality:** `-ql` (480p15) produces noticeably poor text kerning and readability. For scenes with significant text, preview stills at `-qm` to catch issues invisible at 480p. Use `-ql` only for testing layout and animation timing.\n\n## Output Structure\n\n```\nmedia/videos/script/480p15/Scene1_Intro.mp4\nmedia/images/script/Scene1_Intro.png  (from -s flag)\n```\n\n## Stitching with ffmpeg\n\n```bash\ncat > concat.txt << 'EOF'\nfile 'media/videos/script/480p15/Scene1_Intro.mp4'\nfile 'media/videos/script/480p15/Scene2_Core.mp4'\nEOF\nffmpeg -y -f concat -safe 0 -i concat.txt -c copy final.mp4\n```\n\n## Add Voiceover\n\n```bash\n# Mux narration\nffmpeg -y -i final.mp4 -i narration.mp3 -c:v copy -c:a aac -b:a 192k -shortest final_narrated.mp4\n\n# Concat per-scene audio first\ncat > audio_concat.txt << 'EOF'\nfile 'audio/scene1.mp3'\nfile 'audio/scene2.mp3'\nEOF\nffmpeg -y -f concat -safe 0 -i audio_concat.txt -c copy full_narration.mp3\n```\n\n## Add Background Music\n\n```bash\nffmpeg -y -i final.mp4 -i music.mp3 \\\n  -filter_complex \"[1:a]volume=0.15[bg];[0:a][bg]amix=inputs=2:duration=shortest\" \\\n  -c:v copy final_with_music.mp4\n```\n\n## GIF Export\n\n```bash\nffmpeg -y -i scene.mp4 \\\n  -vf \"fps=15,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse\" \\\n  output.gif\n```\n\n## Aspect Ratios\n\n```bash\nmanim -ql --resolution 1080,1920 script.py Scene  # 9:16 vertical\nmanim -ql --resolution 1080,1080 script.py Scene  # 1:1 square\n```\n\n## Render Workflow\n\n1. Draft render all scenes at `-ql`\n2. Preview stills at key moments (`-s`)\n3. Fix and re-render only broken scenes\n4. Stitch with ffmpeg\n5. Review stitched output\n6. Production render at `-qh`\n7. Re-stitch + add audio\n\n## manim.cfg — Project Configuration\n\nCreate `manim.cfg` in the project directory for per-project defaults:\n\n```ini\n[CLI]\nquality = low_quality\npreview = True\nmedia_dir = ./media\n\n[renderer]\nbackground_color = #0D1117\n\n[tex]\ntex_template_file = custom_template.tex\n```\n\nThis eliminates repetitive CLI flags and `self.camera.background_color` in every scene.\n\n## Sections — Chapter Markers\n\nMark sections within a scene for organized output:\n\n```python\nclass LongVideo(Scene):\n    def construct(self):\n        self.next_section(\"Introduction\")\n        # ... intro content ...\n\n        self.next_section(\"Main Concept\")\n        # ... main content ...\n\n        self.next_section(\"Conclusion\")\n        # ... closing ...\n```\n\nRender individual sections: `manim --save_sections script.py LongVideo`\nThis outputs separate video files per section — useful for long videos where you want to re-render only one part.\n\n## manim-voiceover Plugin (Recommended for Narrated Videos)\n\nThe official `manim-voiceover` plugin integrates TTS directly into scene code, auto-syncing animation duration to voiceover length. This is significantly cleaner than the manual ffmpeg muxing approach above.\n\n### Installation\n\n```bash\npip install \"manim-voiceover[elevenlabs]\"\n# Or for free/local TTS:\npip install \"manim-voiceover[gtts]\"    # Google TTS (free, lower quality)\npip install \"manim-voiceover[azure]\"   # Azure Cognitive Services\n```\n\n### Usage\n\n```python\nfrom manim import *\nfrom manim_voiceover import VoiceoverScene\nfrom manim_voiceover.services.elevenlabs import ElevenLabsService\n\nclass NarratedScene(VoiceoverScene):\n    def construct(self):\n        self.set_speech_service(ElevenLabsService(\n            voice_name=\"Alice\",\n            model_id=\"eleven_multilingual_v2\"\n        ))\n\n        # Voiceover auto-controls scene duration\n        with self.voiceover(text=\"Here is a circle being drawn.\") as tracker:\n            self.play(Create(Circle()), run_time=tracker.duration)\n\n        with self.voiceover(text=\"Now let's transform it into a square.\") as tracker:\n            self.play(Transform(circle, Square()), run_time=tracker.duration)\n```\n\n### Key Features\n\n- `tracker.duration` — total voiceover duration in seconds\n- `tracker.time_until_bookmark(\"mark1\")` — sync specific animations to specific words\n- Auto-generates subtitle `.srt` files\n- Caches audio locally — re-renders don't re-generate TTS\n- Works with: ElevenLabs, Azure, Google TTS, pyttsx3 (offline), and custom services\n\n### Bookmarks for Precise Sync\n\n```python\nwith self.voiceover(text='This is a <bookmark mark=\"circle\"/>circle.') as tracker:\n    self.wait_until_bookmark(\"circle\")\n    self.play(Create(Circle()), run_time=tracker.time_until_bookmark(\"circle\", limit=1))\n```\n\nThis is the recommended approach for any video with narration. The manual ffmpeg muxing workflow above is still useful for adding background music or post-production audio mixing.\n","skills/manim-video/references/scene-planning.md":"# Scene Planning Reference\n\n## Narrative Arc Structures\n\n### Discovery Arc (most common)\n1. Hook -- pose a question or surprising result\n2. Intuition -- build visual understanding\n3. Formalize -- introduce the equation/algorithm\n4. Reveal -- the \"aha moment\"\n5. Extend -- implications or generalizations\n\n### Problem-Solution Arc\n1. Problem -- what's broken\n2. Failed attempt -- obvious approach fails\n3. Key insight -- the idea that works\n4. Solution -- implement it\n5. Result -- show improvement\n\n### Comparison Arc\n1. Setup -- introduce two approaches\n2. Approach A -- how it works\n3. Approach B -- how it works\n4. Contrast -- differences\n5. Verdict -- which is better\n\n### Build-Up Arc (architecture/systems)\n1. Component A -- first piece\n2. Component B -- second piece\n3. Connection -- how they interact\n4. Scale -- add more pieces\n5. Full picture -- zoom out\n\n## Scene Transitions\n\n### Clean Break (default)\n```python\nself.play(FadeOut(Group(*self.mobjects)), run_time=0.5)\nself.wait(0.3)\n```\n\n### Carry-Forward\nKeep one element, fade the rest. Next scene starts with it still on screen.\n\n### Transform Bridge\nEnd scene with a shape, start next scene by transforming it.\n\n## Cross-Scene Consistency\n\n```python\n# Shared constants at file top\nBG = \"#1C1C1C\"\nPRIMARY = \"#58C4DD\"\nSECONDARY = \"#83C167\"\nACCENT = \"#FFFF00\"\nTITLE_SIZE = 48\nBODY_SIZE = 30\nLABEL_SIZE = 24\nFAST = 0.8; NORMAL = 1.5; SLOW = 2.5\n```\n\n## Scene Checklist\n\n- [ ] Background color set\n- [ ] Subcaptions on every animation\n- [ ] `self.wait()` after every reveal\n- [ ] Text buff >= 0.5 for edge positioning\n- [ ] No text overlap\n- [ ] Color constants used (not hardcoded)\n- [ ] Opacity layering applied\n- [ ] Clean exit at scene end\n- [ ] No more than 5-6 elements visible at once\n\n## Duration Estimation\n\n| Content | Duration |\n|---------|----------|\n| Title card | 3-5s |\n| Concept introduction | 10-20s |\n| Equation reveal | 15-25s |\n| Algorithm step | 5-10s |\n| Data comparison | 10-15s |\n| \"Aha moment\" | 15-30s |\n| Conclusion | 5-10s |\n\n## Planning Template\n\n```markdown\n# [Video Title]\n\n## Overview\n- **Topic**: [Core concept]\n- **Hook**: [Opening question]\n- **Aha moment**: [Key insight]\n- **Target audience**: [Prerequisites]\n- **Length**: [seconds/minutes]\n- **Resolution**: 480p (draft) / 1080p (final)\n\n## Color Palette\n- Background: #1C1C1C\n- Primary: #58C4DD -- [purpose]\n- Secondary: #83C167 -- [purpose]\n- Accent: #FFFF00 -- [purpose]\n\n## Arc: [Discovery / Problem-Solution / Comparison / Build-Up]\n\n## Scene 1: [Name] (~Ns)\n**Purpose**: [one sentence]\n**Layout**: [FULL_CENTER / LEFT_RIGHT / GRID / PROGRESSIVE]\n\n### Visual elements\n- [Mobject: type, position, color]\n\n### Animation sequence\n1. [Animation] -- [what it reveals] (~Ns)\n\n### Subtitle\n\"[text]\"\n```\n","skills/manim-video/references/troubleshooting.md":"# Troubleshooting\n\n## LaTeX Errors\n\n**Missing raw string** (the #1 error):\n```python\n# WRONG: MathTex(\"\\\\frac{1}{2}\")  -- \\\\f is form-feed\n# RIGHT: MathTex(r\"\\frac{1}{2}\")\n```\n\n**Unbalanced braces**: `MathTex(r\"\\frac{1}{2\")` -- missing closing brace.\n\n**LaTeX not installed**: `which pdflatex` -- install texlive-full or mactex.\n\n**Missing package**: Add to preamble:\n```python\ntex_template = TexTemplate()\ntex_template.add_to_preamble(r\"\\usepackage{mathrsfs}\")\nMathTex(r\"\\mathscr{L}\", tex_template=tex_template)\n```\n\n## VGroup TypeError\n\n**Error:** `TypeError: Only values of type VMobject can be added as submobjects of VGroup`\n\n**Cause:** `Text()` objects are `Mobject`, not `VMobject`. Mixing `Text` with shapes in a `VGroup` fails on Manim CE v0.20+.\n\n```python\n# WRONG: Text is not a VMobject\ngroup = VGroup(circle, Text(\"Label\"))\n\n# RIGHT: use Group for mixed types\ngroup = Group(circle, Text(\"Label\"))\n\n# RIGHT: VGroup is fine for shapes-only\nshapes = VGroup(circle, square, arrow)\n\n# RIGHT: MathTex IS a VMobject — VGroup works\nequations = VGroup(MathTex(r\"a\"), MathTex(r\"b\"))\n```\n\n**Rule:** If the group contains any `Text()`, use `Group`. If it's all shapes or all `MathTex`, `VGroup` is fine.\n\n**FadeOut everything:** Always use `Group(*self.mobjects)`, not `VGroup(*self.mobjects)`:\n```python\nself.play(FadeOut(Group(*self.mobjects)))  # safe for mixed types\n```\n\n## Group save_state() / restore() Not Supported\n\n**Error:** `NotImplementedError: Please override in a child class.`\n\n**Cause:** `Group.save_state()` and `Group.restore()` are not implemented in Manim CE v0.20+. Only `VGroup` and individual `Mobject` subclasses support save/restore.\n\n```python\n# WRONG: Group doesn't support save_state\ngroup = Group(circle, Text(\"label\"))\ngroup.save_state()  # NotImplementedError!\n\n# RIGHT: use FadeIn with shift/scale instead of save_state/restore\nself.play(FadeIn(group, shift=UP * 0.3, scale=0.8))\n\n# RIGHT: or save/restore on individual VMobjects\ncircle.save_state()\nself.play(circle.animate.shift(RIGHT))\nself.play(Restore(circle))\n```\n\n## letter_spacing Is Not a Valid Parameter\n\n**Error:** `TypeError: Mobject.__init__() got an unexpected keyword argument 'letter_spacing'`\n\n**Cause:** `Text()` does not accept `letter_spacing`. Manim uses Pango for text rendering and does not expose kerning controls on `Text()`.\n\n```python\n# WRONG\nText(\"HERMES\", letter_spacing=6)\n\n# RIGHT: use MarkupText with Pango attributes for spacing control\nMarkupText('<span letter_spacing=\"6000\">HERMES</span>', font_size=18)\n# Note: Pango letter_spacing is in 1/1024 of a point\n```\n\n## Animation Errors\n\n**Invisible animation** -- mobject never added:\n```python\n# WRONG: circle = Circle(); self.play(circle.animate.set_color(RED))\n# RIGHT: self.play(Create(circle)); self.play(circle.animate.set_color(RED))\n```\n\n**Transform confusion** -- after Transform(A, B), A is on screen, B is not. Use ReplacementTransform if you want B.\n\n**Duplicate animation** -- same mobject twice in one play():\n```python\n# WRONG: self.play(c.animate.shift(RIGHT), c.animate.set_color(RED))\n# RIGHT: self.play(c.animate.shift(RIGHT).set_color(RED))\n```\n\n**Updater fights animation**:\n```python\nmob.suspend_updating()\nself.play(mob.animate.shift(RIGHT))\nmob.resume_updating()\n```\n\n## Rendering Issues\n\n**Blurry output**: Using -ql (480p). Switch to -qm/-qh for final.\n\n**Slow render**: Use -ql during development. Reduce Surface resolution. Shorter self.wait().\n\n**Stale output**: `manim -ql --disable_caching script.py Scene`\n\n**ffmpeg concat fails**: All clips must match resolution/FPS/codec.\n\n## Common Mistakes\n\n**Text clips at edge**: `buff >= 0.5` for `.to_edge()`\n\n**Overlapping text**: Use `ReplacementTransform(old, new)`, not `Write(new)` on top.\n\n**Too crowded**: Max 5-6 elements visible. Split into scenes or use opacity layering.\n\n**No breathing room**: `self.wait(1.5)` minimum after reveals, `self.wait(2.0)` for key moments.\n\n**Missing background color**: Set `self.camera.background_color = BG` in every scene.\n\n## Debugging Strategy\n\n1. Render a still: `manim -ql -s script.py Scene` -- instant layout check\n2. Isolate the broken scene -- render only that one\n3. Replace `self.play()` with `self.add()` to see final state instantly\n4. Print positions: `print(mob.get_center())`\n5. Clear cache: delete `media/` directory\n","skills/manim-video/references/updaters-and-trackers.md":"# Updaters and Value Trackers\n\n## The problem updaters solve\n\nNormal animations are discrete: `self.play()` goes from state A to state B. But what if you need continuous relationships — a label that always hovers above a moving dot, or a line that always connects two points?\n\nWithout updaters, you'd manually reposition every dependent object before every `self.play()`. Five animations that move a dot means five manual repositioning calls for the label. Miss one and it freezes in the wrong spot.\n\nUpdaters let you declare a relationship ONCE. Manim calls the updater function EVERY FRAME (15-60 fps depending on quality) to enforce that relationship, no matter what else is happening.\n\n## ValueTracker: an invisible steering wheel\n\nA ValueTracker is an invisible Mobject that holds a single float. It never appears on screen. It exists so you can ANIMATE it while other objects REACT to its value.\n\nThink of it as a slider: drag the slider from 0 to 5, and every object wired to it responds in real time.\n\n```python\ntracker = ValueTracker(0)        # invisible, stores 0.0\ntracker.get_value()              # read: 0.0\ntracker.set_value(5)             # write: jump to 5.0 instantly\ntracker.animate.set_value(5)     # animate: smoothly interpolate to 5.0\n```\n\n### The three-step pattern\n\nEvery ValueTracker usage follows this:\n\n1. **Create the tracker** (the invisible slider)\n2. **Create visible objects that READ the tracker** via updaters\n3. **Animate the tracker** — all dependents update automatically\n\n```python\n# Step 1: Create tracker\nx_tracker = ValueTracker(1)\n\n# Step 2: Create dependent objects\ndot = always_redraw(lambda: Dot(axes.c2p(x_tracker.get_value(), 0), color=YELLOW))\nv_line = always_redraw(lambda: axes.get_vertical_line(\n    axes.c2p(x_tracker.get_value(), func(x_tracker.get_value())), color=BLUE\n))\nlabel = always_redraw(lambda: DecimalNumber(x_tracker.get_value(), font_size=24)\n    .next_to(dot, UP))\n\nself.add(dot, v_line, label)\n\n# Step 3: Animate the tracker — everything follows\nself.play(x_tracker.animate.set_value(5), run_time=3)\n```\n\n## Types of updaters\n\n### Lambda updater (most common)\n\nRuns a function every frame, passing the mobject itself:\n\n```python\n# Label always stays above the dot\nlabel.add_updater(lambda m: m.next_to(dot, UP, buff=0.2))\n\n# Line always connects two points\nline.add_updater(lambda m: m.put_start_and_end_on(\n    point_a.get_center(), point_b.get_center()\n))\n```\n\n### Time-based updater (with dt)\n\nThe second argument `dt` is the time since the last frame (~0.017s at 60fps):\n\n```python\n# Continuous rotation\nsquare.add_updater(lambda m, dt: m.rotate(0.5 * dt))\n\n# Continuous rightward drift\ndot.add_updater(lambda m, dt: m.shift(RIGHT * 0.3 * dt))\n\n# Oscillation\ndot.add_updater(lambda m, dt: m.move_to(\n    axes.c2p(m.get_center()[0], np.sin(self.time))\n))\n```\n\nUse `dt` updaters for physics simulations, continuous motion, and time-dependent effects.\n\n### always_redraw: full rebuild every frame\n\nCreates a new mobject from scratch each frame. More expensive than `add_updater` but handles cases where the mobject's structure changes (not just position/color):\n\n```python\n# Brace that follows a resizing square\nbrace = always_redraw(Brace, square, UP)\n\n# Area under curve that updates as function changes\narea = always_redraw(lambda: axes.get_area(\n    graph, x_range=[0, x_tracker.get_value()], color=BLUE, opacity=0.3\n))\n\n# Label that reconstructs its text\ncounter = always_redraw(lambda: Text(\n    f\"n = {int(x_tracker.get_value())}\", font_size=24, font=\"Menlo\"\n).to_corner(UR))\n```\n\n**When to use which:**\n- `add_updater` — position, color, opacity changes (cheap, preferred)\n- `always_redraw` — when the shape/structure itself changes (expensive, use sparingly)\n\n## DecimalNumber: showing live values\n\n```python\n# Counter that tracks a ValueTracker\ntracker = ValueTracker(0)\nnumber = DecimalNumber(0, font_size=48, num_decimal_places=1, color=PRIMARY)\nnumber.add_updater(lambda m: m.set_value(tracker.get_value()))\nnumber.add_updater(lambda m: m.next_to(dot, RIGHT, buff=0.3))\n\nself.add(number)\nself.play(tracker.animate.set_value(100), run_time=3)\n```\n\n### Variable: the labeled version\n\n```python\nvar = Variable(0, Text(\"x\", font_size=24, font=\"Menlo\"), num_decimal_places=2)\nself.add(var)\nself.play(var.tracker.animate.set_value(PI), run_time=2)\n# Displays: x = 3.14\n```\n\n## Removing updaters\n\n```python\n# Remove all updaters\nmobject.clear_updaters()\n\n# Suspend temporarily (during an animation that would fight the updater)\nmobject.suspend_updating()\nself.play(mobject.animate.shift(RIGHT))\nmobject.resume_updating()\n\n# Remove specific updater (if you stored a reference)\ndef my_updater(m):\n    m.next_to(dot, UP)\nlabel.add_updater(my_updater)\n# ... later ...\nlabel.remove_updater(my_updater)\n```\n\n## Animation-based updaters\n\n### UpdateFromFunc / UpdateFromAlphaFunc\n\nThese are ANIMATIONS (passed to `self.play`), not persistent updaters:\n\n```python\n# Call a function on each frame of the animation\nself.play(UpdateFromFunc(mobject, lambda m: m.next_to(moving_target, UP)), run_time=3)\n\n# With alpha (0 to 1) — useful for custom interpolation\nself.play(UpdateFromAlphaFunc(circle, lambda m, a: m.set_fill(opacity=a)), run_time=2)\n```\n\n### turn_animation_into_updater\n\nConvert a one-shot animation into a continuous updater:\n\n```python\nfrom manim import turn_animation_into_updater\n\n# This would normally play once — now it loops forever\nturn_animation_into_updater(Rotating(gear, rate=PI/4))\nself.add(gear)\nself.wait(5)  # gear rotates for 5 seconds\n```\n\n## Practical patterns\n\n### Pattern 1: Dot tracing a function\n\n```python\ntracker = ValueTracker(0)\ngraph = axes.plot(np.sin, x_range=[0, 2*PI], color=PRIMARY)\ndot = always_redraw(lambda: Dot(\n    axes.c2p(tracker.get_value(), np.sin(tracker.get_value())),\n    color=YELLOW\n))\ntangent = always_redraw(lambda: axes.get_secant_slope_group(\n    x=tracker.get_value(), graph=graph, dx=0.01,\n    secant_line_color=HIGHLIGHT, secant_line_length=3\n))\n\nself.add(graph, dot, tangent)\nself.play(tracker.animate.set_value(2*PI), run_time=6, rate_func=linear)\n```\n\n### Pattern 2: Live area under curve\n\n```python\ntracker = ValueTracker(0.5)\narea = always_redraw(lambda: axes.get_area(\n    graph, x_range=[0, tracker.get_value()],\n    color=PRIMARY, opacity=0.3\n))\narea_label = always_redraw(lambda: DecimalNumber(\n    # Numerical integration\n    sum(func(x) * 0.01 for x in np.arange(0, tracker.get_value(), 0.01)),\n    font_size=24\n).next_to(axes, RIGHT))\n\nself.add(area, area_label)\nself.play(tracker.animate.set_value(4), run_time=5)\n```\n\n### Pattern 3: Connected diagram\n\n```python\n# Nodes that can be moved, with edges that auto-follow\nnode_a = Dot(LEFT * 2, color=PRIMARY)\nnode_b = Dot(RIGHT * 2, color=SECONDARY)\nedge = Line().add_updater(lambda m: m.put_start_and_end_on(\n    node_a.get_center(), node_b.get_center()\n))\nlabel = Text(\"edge\", font_size=18, font=\"Menlo\").add_updater(\n    lambda m: m.move_to(edge.get_center() + UP * 0.3)\n)\n\nself.add(node_a, node_b, edge, label)\nself.play(node_a.animate.shift(UP * 2), run_time=2)\nself.play(node_b.animate.shift(DOWN + RIGHT), run_time=2)\n# Edge and label follow automatically\n```\n\n### Pattern 4: Parameter exploration\n\n```python\n# Explore how a parameter changes a curve\na_tracker = ValueTracker(1)\ncurve = always_redraw(lambda: axes.plot(\n    lambda x: a_tracker.get_value() * np.sin(x),\n    x_range=[0, 2*PI], color=PRIMARY\n))\nparam_label = always_redraw(lambda: Text(\n    f\"a = {a_tracker.get_value():.1f}\", font_size=24, font=\"Menlo\"\n).to_corner(UR))\n\nself.add(curve, param_label)\nself.play(a_tracker.animate.set_value(3), run_time=3)\nself.play(a_tracker.animate.set_value(0.5), run_time=2)\nself.play(a_tracker.animate.set_value(1), run_time=1)\n```\n\n## Common mistakes\n\n1. **Updater fights animation:** If a mobject has an updater that sets its position, and you try to animate it elsewhere, the updater wins every frame. Suspend updating first.\n\n2. **always_redraw for simple moves:** If you only need to reposition, use `add_updater`. `always_redraw` reconstructs the entire mobject every frame — expensive and unnecessary for position tracking.\n\n3. **Forgetting to add to scene:** Updaters only run on mobjects that are in the scene. `always_redraw` creates the mobject but you still need `self.add()`.\n\n4. **Updater creates new mobjects without cleanup:** If your updater creates Text objects every frame, they accumulate. Use `always_redraw` (which handles cleanup) or update properties in-place.\n","skills/manim-video/references/visual-design.md":"# Visual Design Principles\n\n## 12 Core Principles\n\n1. **Geometry Before Algebra** — Show the shape first, the equation second.\n2. **Opacity Layering** — PRIMARY=1.0, CONTEXT=0.4, GRID=0.15. Direct attention through brightness.\n3. **One New Idea Per Scene** — Each scene introduces exactly one concept.\n4. **Spatial Consistency** — Same concept occupies the same screen region throughout.\n5. **Color = Meaning** — Assign colors to concepts, not mobjects. If velocity is blue, it stays blue.\n6. **Progressive Disclosure** — Show simplest version first, add complexity incrementally.\n7. **Transform, Don't Replace** — Use Transform/ReplacementTransform to show connections.\n8. **Breathing Room** — `self.wait(1.5)` minimum after showing something new.\n9. **Visual Weight Balance** — Don't cluster everything on one side.\n10. **Consistent Motion Vocabulary** — Pick a small set of animation types and reuse them.\n11. **Dark Background, Light Content** — #1C1C1C to #2D2B55 backgrounds maximize contrast.\n12. **Intentional Empty Space** — Leave at least 15% of the frame empty.\n\n## Layout Templates\n\n### FULL_CENTER\nOne main element centered, title above, note below.\nBest for: single equations, single diagrams, title cards.\n\n### LEFT_RIGHT\nTwo elements side by side at x=-3.5 and x=3.5.\nBest for: equation + visual, before/after, comparison.\n\n### TOP_BOTTOM\nMain element at y=1.5, supporting content at y=-1.5.\nBest for: concept + examples, theorem + cases.\n\n### GRID\nMultiple elements via `arrange_in_grid()`.\nBest for: comparison matrices, multi-step processes.\n\n### PROGRESSIVE\nElements appear one at a time, arranged DOWN with aligned_edge=LEFT.\nBest for: algorithms, proofs, step-by-step processes.\n\n### ANNOTATED_DIAGRAM\nCentral diagram with floating labels connected by arrows.\nBest for: architecture diagrams, annotated figures.\n\n## Color Palettes\n\n### Classic 3B1B\n```python\nBG=\"#1C1C1C\"; PRIMARY=BLUE; SECONDARY=GREEN; ACCENT=YELLOW; HIGHLIGHT=RED\n```\n\n### Warm Academic\n```python\nBG=\"#2D2B55\"; PRIMARY=\"#FF6B6B\"; SECONDARY=\"#FFD93D\"; ACCENT=\"#6BCB77\"\n```\n\n### Neon Tech\n```python\nBG=\"#0A0A0A\"; PRIMARY=\"#00F5FF\"; SECONDARY=\"#FF00FF\"; ACCENT=\"#39FF14\"\n```\n\n## Font Selection\n\n**Use monospace fonts for all text.** Manim's Pango text renderer produces broken kerning with proportional fonts (Helvetica, Inter, SF Pro, Arial) at all sizes and resolutions. Characters overlap and spacing is inconsistent. This is a fundamental Pango limitation, not a Manim bug.\n\nMonospace fonts have fixed character widths — zero kerning issues by design.\n\n### Recommended Fonts\n\n| Use case | Font | Fallback |\n|----------|------|----------|\n| **All text (default)** | `\"Menlo\"` | `\"Courier New\"`, `\"DejaVu Sans Mono\"` |\n| Code, labels | `\"JetBrains Mono\"`, `\"SF Mono\"` | `\"Menlo\"` |\n| Math | Use `MathTex` (renders via LaTeX, not Pango) | — |\n\n```python\nMONO = \"Menlo\"  # define once at top of file\n\ntitle = Text(\"Fourier Series\", font_size=48, color=PRIMARY, weight=BOLD, font=MONO)\nlabel = Text(\"n=1: (4/pi) sin(x)\", font_size=20, color=BLUE, font=MONO)\nnote = Text(\"Convergence at discontinuities\", font_size=18, color=DIM, font=MONO)\n\n# Math — always use MathTex, not Text\nequation = MathTex(r\"\\nabla L = \\frac{\\partial L}{\\partial w}\")\n```\n\n### When Proportional Fonts Are Acceptable\n\nLarge title text (font_size >= 48) with short strings (1-3 words) can use proportional fonts without visible kerning issues. For anything else — labels, descriptions, multi-word text, small sizes — use monospace.\n\n### Font Availability\n\n- **macOS**: Menlo (pre-installed), SF Mono\n- **Linux**: DejaVu Sans Mono (pre-installed), Liberation Mono\n- **Cross-platform**: JetBrains Mono (install from jetbrains.com)\n\n`\"Menlo\"` is the safest default — pre-installed on macOS, and Linux systems fall back to DejaVu Sans Mono.\n\n### Fine-Grained Text Control\n\n`Text()` does not support `letter_spacing` or kerning parameters. For fine control, use `MarkupText` with Pango attributes:\n\n```python\n# Letter spacing (Pango units: 1/1024 of a point)\nMarkupText('<span letter_spacing=\"6000\">HERMES</span>', font_size=18, font=\"Menlo\")\n\n# Bold specific words\nMarkupText('This is <b>important</b>', font_size=24, font=\"Menlo\")\n\n# Color specific words\nMarkupText('Red <span foreground=\"#FF6B6B\">warning</span>', font_size=24, font=\"Menlo\")\n```\n\n### Minimum Font Size\n\n`font_size=18` is the minimum for readable text at any resolution. Below 18, characters become blurry at `-ql` and barely readable even at `-qh`.\n\n## Visual Hierarchy Checklist\n\nFor every frame:\n1. What is the ONE thing to look at? (brightest/largest)\n2. What is context? (dimmed to 0.3-0.4)\n3. What is structural? (dimmed to 0.15)\n4. Enough empty space? (>15%)\n5. All text readable at phone size?\n"},"items":[{"name":"SKILL.md","path":"SKILL.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/SKILL.md","title":"video-use","category":"anthropic-skill","format":"markdown","content":"---\nname: video-use\ndescription: Edit any video by conversation. Transcribe, cut, color grade, generate overlay animations, burn subtitles — for talking heads, montages, tutorials, travel, interviews. No presets, no menus. Ask questions, confirm the plan, execute, iterate, persist. Production-correctness rules are hard; everything else is artistic freedom.\n---\n\n# Video Use\n\n## Principle\n\n1. **LLM reasons from raw transcript + on-demand visuals.** The only derived artifact that earns its keep is a packed phrase-level transcript (`takes_packed.md`). Everything else — filler tagging, retake detection, shot classification, emphasis scoring — you derive at decision time.\n2. **Audio is primary, visuals follow.** Cut candidates come from speech boundaries and silence gaps. Drill into visuals only at decision points.\n3. **Ask → confirm → execute → iterate → persist.** Never touch the cut until the user has confirmed the strategy in plain English.\n4. **Generalize.** Do not assume what kind of video this is. Look at the material, ask the user, then edit.\n5. **Artistic freedom is the default.** Every specific value, preset, font, color, duration, pitch structure, and technique in this document is a *worked example* from one proven video — not a mandate. Read them to understand what's possible and why each worked. Then make your own taste calls based on what the material actually is and what the user actually wants. **The only things you MUST do are in the Hard Rules section below.** Everything else is yours.\n6. **Invent freely.** If the material calls for a technique not described here — split-screen, picture-in-picture, lower-third identity cards, reaction cuts, speed ramps, freeze frames, crossfades, match cuts, L-cuts, J-cuts, speed ramps over breath, whatever — build it. The helpers are ffmpeg and PIL. They can do anything the format supports. Do not wait for permission.\n7. **Verify your own output before showing it to the user.** If you wouldn't ship it, don't present it.\n\n## Hard Rules (production correctness — non-negotiable)\n\nThese are the things where deviation produces silent failures or broken output. They are not taste, they are correctness. Memorize them.\n\n1. **Subtitles are applied LAST in the filter chain**, after every overlay. Otherwise overlays hide captions. Silent failure.\n2. **Per-segment extract → lossless `-c copy` concat**, not single-pass filtergraph. Otherwise you double-encode every segment when overlays are added.\n3. **30ms audio fades at every segment boundary** (`afade=t=in:st=0:d=0.03,afade=t=out:st={dur-0.03}:d=0.03`). Otherwise audible pops at every cut.\n4. **Overlays use `setpts=PTS-STARTPTS+T/TB`** to shift the overlay's frame 0 to its window start. Otherwise you see the middle of the animation during the overlay window.\n5. **Master SRT uses output-timeline offsets**: `output_time = word.start - segment_start + segment_offset`. Otherwise captions misalign after segment concat.\n6. **Never cut inside a word.** Snap every cut edge to a word boundary from the Scribe transcript.\n7. **Pad every cut edge.** Working window: 30–200ms. Scribe timestamps drift 50–100ms — padding absorbs the drift. Tighter for fast-paced, looser for cinematic.\n8. **Word-level verbatim ASR only.** Never SRT/phrase mode (loses sub-second gap data). Never normalized fillers (loses editorial signal).\n9. **Cache transcripts per source.** Never re-transcribe unless the source file itself changed.\n10. **Parallel sub-agents for multiple animations.** Never sequential. Spawn N at once via the `Agent` tool; total wall time ≈ slowest one.\n11. **Strategy confirmation before execution.** Never touch the cut until the user has approved the plain-English plan.\n12. **All session outputs in `<videos_dir>/edit/`.** Never write inside the `video-use/` project directory.\n\nEverything else in this document is a worked example. Deviate whenever the material calls for it.\n\n## Directory layout\n\nThe skill lives in `video-use/`. User footage lives wherever they put it. All session outputs go into `<videos_dir>/edit/`.\n\n```\n<videos_dir>/\n├── <source files, untouched>\n└── edit/\n    ├── project.md               ← memory; appended every session\n    ├── takes_packed.md          ← phrase-level transcripts, the LLM's primary reading view\n    ├── edl.json                 ← cut decisions\n    ├── transcripts/<name>.json  ← cached raw Scribe JSON\n    ├── animations/slot_<id>/    ← per-animation source + render + reasoning\n    ├── clips_graded/            ← per-segment extracts with grade + fades\n    ├── master.srt               ← output-timeline subtitles\n    ├── downloads/               ← yt-dlp outputs\n    ├── verify/                  ← debug frames / timeline PNGs\n    ├── preview.mp4\n    └── final.mp4\n```\n\n## Setup\n\nFirst-time install lives in `install.md` (clone, deps, ffmpeg, skill registration, API key). Don't re-run it every session; on cold start just verify:\n\n- `ELEVENLABS_API_KEY` resolves — either in the environment or in `.env` at the video-use repo root. If missing, ask the user to paste one and write it to `.env` (never to the user's `<videos_dir>`).\n- `ffmpeg` + `ffprobe` on PATH.\n- Python deps installed (`uv sync` or `pip install -e .` inside the repo).\n- Node.js + npm available if the session needs HyperFrames or Remotion slots. HyperFrames currently requires Node.js 22+.\n- `yt-dlp`, HyperFrames, Remotion, Manim installed only on first use.\n- First-use animation setup happens inside the slot directory, never at the video-use repo root. HyperFrames can be invoked with `npx --yes hyperframes ...`; Remotion can be scaffolded with `npx create-video@latest` or installed as a project-local dependency before using its `remotion render` command.\n- This skill vendors `skills/manim-video/`. Read its SKILL.md when building a Manim slot.\n\nHelpers (`helpers/transcribe.py`, `helpers/render.py`, etc.) live alongside this SKILL.md. Resolve their paths relative to the directory containing this file — the skill is typically symlinked at `~/.claude/skills/video-use/` or `~/.codex/skills/video-use/`.\n\n## Helpers\n\n- **`transcribe.py <video>`** — single-file Scribe call. `--num-speakers N` optional. Cached.\n- **`transcribe_batch.py <videos_dir>`** — 4-worker parallel transcription. Use for multi-take.\n- **`pack_transcripts.py --edit-dir <dir>`** — `transcripts/*.json` → `takes_packed.md` (phrase-level, break on silence ≥ 0.5s).\n- **`timeline_view.py <video> <start> <end>`** — filmstrip + waveform PNG. On-demand visual drill-down. **Not a scan tool** — use it at decision points, not constantly.\n- **`render.py <edl.json> -o <out>`** — per-segment extract → concat → overlays (PTS-shifted) → subtitles LAST. `--preview` for 720p fast. `--build-subtitles` to generate master.srt inline.\n- **`grade.py <in> -o <out>`** — ffmpeg filter chain grade. Presets + `--filter '<raw>'` for custom.\n\nFor animations, create `<edit>/animations/slot_<id>/` with `Bash` and spawn a sub-agent via the `Agent` tool.\n\n## The process\n\n1. **Inventory.** `ffprobe` every source. `transcribe_batch.py` on the directory. `pack_transcripts.py` to produce `takes_packed.md`. Sample one or two `timeline_view`s for a visual first impression.\n2. **Pre-scan for problems.** One pass over `takes_packed.md` to note verbal slips, obvious mis-speaks, or phrasings to avoid. Plain list, feed into the editor brief.\n3. **Converse.** Describe what you see in plain English. Ask questions *shaped by the material*. Collect: content type, target length/aspect, aesthetic/brand direction, pacing feel, must-preserve moments, must-cut moments, animation and grade preferences, subtitle needs. Do not use a fixed checklist — the right questions are different every time.\n4. **Propose strategy.** 4–8 sentences: shape, take choices, cut direction, animation plan, grade direction, subtitle style, length estimate. **Wait for confirmation.**\n5. **Execute.** Produce `edl.json` via the editor sub-agent brief. Drill into `timeline_view` at ambiguous moments. Build animations in parallel sub-agents. Apply grade per-segment. Compose via `render.py`.\n6. **Preview.** `render.py --preview`.\n7. **Self-eval (before showing the user).** Run `timeline_view` on the **rendered output** (not the sources) at every cut boundary (±1.5s window). Check each image for:\n   - Visual discontinuity / flash / jump at the cut\n   - Waveform spike at the boundary (audio pop that slipped past the 30ms fade)\n   - Subtitle hidden behind an overlay (Rule 1 violation)\n   - Overlay misaligned or showing wrong frames (Rule 4 violation)\n\n   Also sample: first 2s, last 2s, and 2–3 mid-points — check grade consistency, subtitle readability, overall coherence. Run `ffprobe` on the output to verify duration matches the EDL expectation.\n\n   Measure the audio, don't assume it: `ffmpeg -i out.mp4 -af ebur128=peak=true -f null -` for integrated loudness and true peak, plus RMS per section (dialogue, music-only, end card). An end card 15 dB under the dialogue, or effects louder than speech, is a bug. You cannot listen: say so, and report the numbers.\n\n   For anything the user will publish (launch, promo, ad), also spawn one **critic sub-agent** with the rendered file, the EDL, and any reference videos the user gave. Brief it to roast, not to praise: a verdict, ranked problems with timecodes and evidence (frames, levels), and the 5 fixes to do first. Fresh eyes catch what the author stopped seeing — cut-off payoff lines, 0.5s memes, unreadable 28px text at phone size.\n\n   If anything fails: fix → re-render → re-eval. **Cap at 3 self-eval passes** — if issues remain after 3, flag them to the user rather than looping forever. Only present the preview once the self-eval passes.\n8. **Iterate + persist.** Natural-language feedback, re-plan, re-render. Never re-transcribe. Final render on confirmation. Append to `project.md`.\n\n## Cut craft (techniques)\n\n- **Audio-first.** Candidate cuts from word boundaries and silence gaps.\n- **Preserve peaks.** Laughs, punchlines, emphasis beats. Extend past punchlines to include reactions — the laugh IS the beat.\n- **Speaker handoffs** benefit from air between utterances. Common values: 400–600ms. Less for fast-paced, more for cinematic. Taste call.\n- **Audio events as signals.** `(laughs)`, `(sighs)`, `(applause)` mark beats. Extend past them.\n- **Silence gaps are cut candidates.** Silences ≥400ms are usually the cleanest. 150–400ms phrase boundaries are usable with a visual check. <150ms is unsafe (mid-phrase).\n- **Example cut padding** (the launch video shipped with this): 50ms before the first kept word, 80ms after the last. Tighter for montage energy, looser for documentary. Stay in the 30–200ms working window (Hard Rule 7).\n- **Never reason audio and video independently.** Every cut must work on both tracks.\n\n## The packed transcript (primary reading view)\n\n`pack_transcripts.py` reads all `transcripts/*.json` and produces one markdown file where each take is a list of phrase-level lines, each prefixed with its `[start-end]` time range. Phrases break on any silence ≥ 0.5s OR speaker change. This is the artifact the editor sub-agent reads to pick cuts — it gives word-boundary precision from text alone at 1/10 the tokens of raw JSON.\n\nExample line:\n```\n## C0103  (duration: 43.0s, 8 phrases)\n  [002.52-005.36] S0 Ninety percent of what a web agent does is completely wasted.\n  [006.08-006.74] S0 We fixed this.\n```\n\n## Editor sub-agent brief (for multi-take selection)\n\nWhen the task is \"pick the best take of each beat across many clips,\" spawn a dedicated sub-agent with a brief shaped like this. The structure is load-bearing; the pitch-shape example is not.\n\n```\nYou are editing a <type> video. Pick the best take of each beat and \nassemble them chronologically by beat, not by source clip order.\n\nINPUTS:\n  - takes_packed.md (time-annotated phrase-level transcripts of all takes)\n  - Product/narrative context: <2 sentences from the user>\n  - Speaker(s): <name, role, delivery style note>\n  - Expected structure: <pick an archetype or invent one>\n  - Verbal slips to avoid: <list from the pre-scan pass>\n  - Target runtime: <seconds>\n\nCommon structural archetypes (pick, adapt, or invent):\n  - Tech launch / demo:   HOOK → PROBLEM → SOLUTION → BENEFIT → EXAMPLE → CTA\n  - Tutorial:             INTRO → SETUP → STEPS → GOTCHAS → RECAP\n  - Interview:            (QUESTION → ANSWER → FOLLOWUP) repeat\n  - Travel / event:       ARRIVAL → HIGHLIGHTS → QUIET MOMENTS → DEPARTURE\n  - Documentary:          THESIS → EVIDENCE → COUNTERPOINT → CONCLUSION\n  - Music / performance:  INTRO → VERSE → CHORUS → BRIDGE → OUTRO\n  - Or invent your own.\n\nRULES:\n  - Start/end times must fall on word boundaries from the transcript.\n  - Pad cut boundaries (working window 30–200ms).\n  - Prefer silences ≥ 400ms as cut targets.\n  - Unavoidable slips are kept if no better take exists. Note them in \"reason\".\n  - If over budget, revise: drop a beat or trim tails. Report total and self-correct.\n\nOUTPUT (JSON array, no prose):\n  [{\"source\": \"C0103\", \"start\": 2.42, \"end\": 6.85, \"beat\": \"HOOK\",\n    \"quote\": \"...\", \"reason\": \"...\"}, ...]\n\nReturn the final EDL and a one-line total runtime check.\n```\n\n## Color grade (when requested)\n\nYour job is to **reason about the image**, not apply a preset. Look at a frame (via `timeline_view`), decide what's wrong, adjust one thing, look again.\n\nMental model is ASC CDL. Per channel: `out = (in * slope + offset) ** power`, then global saturation. `slope` → highlights, `offset` → shadows, `power` → midtones.\n\n**Example filter chains** (`grade.py` has `--list-presets`; use them as starting points or mix your own):\n\n- **`warm_cinematic`** — retro/technical, subtle teal/orange split, desaturated. Shipped in a real launch video. Safe for talking heads.\n- **`neutral_punch`** — minimal corrective: contrast bump + gentle S-curve. No hue shifts.\n- **`none`** — straight copy. Default when the user hasn't asked.\n\nFor anything else — portraiture, nature, product, music video, documentary — invent your own chain. `grade.py --filter '<raw ffmpeg>'` accepts any filter string.\n\nHard rules: apply **per-segment during extraction** (not post-concat, which re-encodes twice). Never go aggressive without testing skin tones.\n\n## Subtitles (when requested)\n\nSubtitles have three dimensions worth reasoning about: **chunking** (1/2/3/sentence per line), **case** (UPPER/Title/Natural), and **placement** (margin from bottom). The right combo depends on content.\n\n**Worked styles** — pick, adapt, or invent:\n\n**`bold-overlay`** — short-form tech launch, fast-paced social. ~2-word chunks, UPPERCASE, break on punctuation and pauses ≥ 0.3s, grow to 3 words rather than flash a cue < 0.35s (`chunk_words` in `render.py`), Helvetica 18 Bold, white-on-outline, `MarginV=35`. `render.py` ships with this as `SUB_FORCE_STYLE`.\n\n```\nFontName=Helvetica,FontSize=18,Bold=1,\nPrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BackColour=&H00000000,\nBorderStyle=1,Outline=2,Shadow=0,\nAlignment=2,MarginV=35\n```\n\n**`natural-sentence`** (if you invent this mode) — narrative, documentary, education. 4–7 word chunks, sentence case, break on natural pauses, `MarginV=60–80`, larger font for readability, slightly wider max-width. No shipped force_style — design one if you need it.\n\nInvent a third style if neither fits. Hard rules: subtitles LAST (Rule 1), output-timeline offsets (Rule 5).\n\n## Animations (when requested)\n\nAnimations match the content and the brand. **Get the palette, font, and visual language from the conversation** — never assume a default. If the user hasn't told you, propose a palette in the strategy phase and wait for confirmation before building anything.\n\n**Tool options:**\n\nPick the engine per animation slot. Do not default to Remotion just because the animation is web-adjacent.\n\n- **HyperFrames** — Browser-native HTML/CSS/GSAP video compositions: product UI motion, website-to-video or mockup-to-video captures, kinetic typography, landing-page/storyboard promos, data-driven UI states, transparent WebM overlays, and clips that need deterministic frame capture plus HyperFrames lint/validate/render checks. Best when the animation should be authored and verified like a web composition instead of a React component tree.\n- **Remotion** — React/CSS compositions with component state, reusable React primitives, or an existing Remotion brand system. Best when the user specifically asks for React/Remotion or when React composition is the simpler authoring model.\n- **Manim** — formal diagrams, state machines, equation derivations, graph morphs. Read `skills/manim-video/SKILL.md` and its references for depth.\n- **PIL + PNG sequence + ffmpeg** — simple overlay cards: counters, typewriter text, single bar reveals, progressive draws. Fast to iterate, any aesthetic you want. The launch video used this.\n\nFor HyperFrames slots, scaffold the slot inside `edit/animations/slot_<id>/` with `npx --yes hyperframes init . --example blank --non-interactive --skip-skills`, build the HTML composition there, run the HyperFrames checks that fit the slot (`lint`, `validate`, and a draft render when practical), then produce the final overlay video with `npx --yes hyperframes render . -o render.mp4` or `--format webm -o render.webm` when alpha is required. Point the EDL overlay `file` at the actual rendered path.\n\nFor Remotion slots, keep the Remotion project isolated inside the same slot directory, scaffold with `npx create-video@latest` or install Remotion locally there, render the composition to `render.mp4` with the project-local `remotion render` command, and verify duration and dimensions with `ffprobe`.\n\nNone is mandatory. Invent hybrids if useful (e.g., PIL background with a HyperFrames or Remotion layer on top).\n\n**Duration rules of thumb, context-dependent:**\n\n- **Sync-to-narration explanations.** A viewer needs to parse the content at 1×. Rough floor 3s, typical 5–7s for simple cards, 8–14s for complex diagrams. The launch video shipped at 5–7s per simple card.\n- **Beat-synced accents** (music video, fast montage). 0.5–2s is fine — they're visual accents, not information. The \"readable at 1×\" rule becomes *\"recognizable at 1×\"*, not *\"fully parseable.\"*\n- **Hold the final frame ≥ 1s** before the cut (universal).\n- **Over voiceover:** total duration ≥ `narration_length + 1s` (universal).\n- **Never parallel-reveal independent elements** — the eye can't track two new things at once. One thing, pause, next thing.\n\n**Animation payoff timing (rule for sync-to-narration):** get the payoff word's timestamp. Start the overlay `reveal_duration` seconds earlier so the landing frame coincides with the spoken payoff word. Without this sync the animation feels disconnected.\n\n**Easing** (universal — never `linear`, it looks robotic):\n\n```python\ndef ease_out_cubic(t):    return 1 - (1 - t) ** 3\ndef ease_in_out_cubic(t):\n    if t < 0.5: return 4 * t ** 3\n    return 1 - (-2 * t + 2) ** 3 / 2\n```\n\n`ease_out_cubic` for single reveals (slow landing). `ease_in_out_cubic` for continuous draws.\n\n**Typing text anchor trick:** center on the FULL string's width, not the partial-string width — otherwise text slides left during reveal.\n\n**Example palette** (the launch video — one aesthetic among infinite):\n- Background `(10, 10, 10)` near-black\n- Accent `#FF5A00` / `(255, 90, 0)` orange\n- Labels `(110, 110, 110)` dim gray\n- Font: Menlo Bold at `/System/Library/Fonts/Menlo.ttc` (index 1)\n- ≤ 2 accent colors, ~40% empty space, minimal chrome\n- Result: terminal / retro tech feel\n\nThis is one style. If the brand is warm and serif, use that. If it's colorful and playful, use that. If the user handed you a style guide, follow it. If they didn't, propose one and confirm.\n\n**Fonts fail silently.** A web font that didn't load renders in a fallback face with no error — the video ships in \"almost Arial\". In HyperFrames/Remotion, await the font load and then assert it: `if (!document.fonts.check('700 76px \"Inter\"')) throw new Error(...)`. In PIL, pass an explicit font path; never rely on the default.\n\n**Worked example — \"show the edit\" hook** (a launch video for this tool). Instead of a title card, the first 3s visualize the editing itself: each transcript word pops in on its Scribe timestamp, with bars under it drawn from the real audio envelope; a filler (\"ummm\") grows letter by letter while it is spoken, turns orange and is cut out on screen at the same frame the audio cuts, and the next line lands immediately. It works because the picture is *driven by the same data as the sound* — one composition (Remotion) reads frame-exact word/envelope JSON produced in Python, so nothing can drift. Use the idea whenever the story is \"we removed something\": make the removal visible.\n\n**Parallel sub-agent brief** — each animation is one sub-agent spawned via the `Agent` tool. Each prompt is self-contained (sub-agents have no parent context). Include:\n\n1. One-sentence goal: *\"Build ONE animation: [spec]. Nothing else.\"*\n2. Absolute output path (`<edit>/animations/slot_<id>/render.mp4`)\n3. Exact technical spec: resolution, fps, codec, pix_fmt, CRF, duration\n4. Style palette as concrete values (RGB tuples, hex, or reference to a design system)\n5. Font path with index\n6. Frame-by-frame timeline (what happens when, with easing)\n7. Anti-list (\"no chrome, no extras, no titles unless specified\")\n8. Code pattern reference (copy helpers inline, don't import across slots)\n9. Deliverable checklist (script, render, verify duration via ffprobe, report)\n10. **\"Do not ask questions. If anything is ambiguous, pick the most obvious interpretation and proceed.\"**\n\nOne sub-agent = one file (unique filenames, parallel agents don't overwrite each other).\n\n## Music and sound effects (when requested)\n\nSound is where generated videos sound cheap. Worked rules from launch edits:\n\n- **Fewer effects.** Every effect is tied to something visible (a cut, a landing, a click). ~20 stock whooshes/risers/impacts in 18s reads as generic; ~8 reads as designed.\n- **Hit on the frame.** Most effects have an attack (silence or a build before the transient). Measure it (first sample above ~-30 dBFS of the peak) and start the file `attack` seconds *before* the visible contact frame.\n- **Duck music under speech** (roughly -12 to -15 dB relative to its music-only level), and ramp it out before a stinger or end card instead of letting its own tail decay under your CTA.\n- **Master once:** mix to PCM, then two-pass loudnorm (-14 LUFS, true peak ≤ -1 dBTP) on the final mix. Then measure per section (see Self-eval).\n- **Music taste is the user's call.** Generated music defaults to \"hype\"; offer two contrasting beds and let the user listen. Don't claim a mix sounds good — you can only measure it.\n\n## Output spec\n\nMatch the source unless the user asked for something specific. Common targets: `1920×1080@24` cinematic, `1920×1080@30` screen content, `1080×1920@30` vertical social, `3840×2160@24` 4K cinema, `1080×1080@30` square. `render.py` defaults the scale to 1080p from any source; pass `--filter` or edit the extract command for other targets. Worth asking the user which delivery format matters.\n\n## EDL format\n\n```json\n{\n  \"version\": 1,\n  \"sources\": {\"C0103\": \"/abs/path/C0103.MP4\", \"C0108\": \"/abs/path/C0108.MP4\"},\n  \"ranges\": [\n    {\"source\": \"C0103\", \"start\": 2.42, \"end\": 6.85,\n     \"beat\": \"HOOK\", \"quote\": \"...\", \"reason\": \"Cleanest delivery, stops before slip at 38.46.\"},\n    {\"source\": \"C0108\", \"start\": 14.30, \"end\": 28.90,\n     \"beat\": \"SOLUTION\", \"quote\": \"...\", \"reason\": \"Only take without the false start.\"}\n  ],\n  \"grade\": \"warm_cinematic\",\n  \"overlays\": [\n    {\"file\": \"edit/animations/slot_1/render.mp4\", \"start_in_output\": 0.0, \"duration\": 5.0}\n  ],\n  \"subtitles\": \"edit/master.srt\",\n  \"total_duration_s\": 87.4\n}\n```\n\n`grade` is a preset name or raw ffmpeg filter. `overlays` are rendered animation clips. `subtitles` is optional and applied LAST.\n\n## Memory — `project.md`\n\nAppend one section per session at `<edit>/project.md`:\n\n```markdown\n## Session N — YYYY-MM-DD\n\n**Strategy:** one paragraph describing the approach\n**Decisions:** take choices, cuts, grades, animations + why\n**Reasoning log:** one-line rationale for non-obvious decisions\n**Outstanding:** deferred items\n```\n\nOn startup, read `project.md` if it exists and summarize the last session in one sentence before asking whether to continue.\n\n## Anti-patterns\n\nThings that consistently fail regardless of style:\n\n- **Hierarchical pre-computed codec formats** with USABILITY / tone tags / shot layers. Over-engineering. Derive from the transcript at decision time.\n- **Hand-tuned moment-scoring functions.** The LLM picks better than any heuristic you'll write.\n- **Whisper SRT / phrase-level output.** Loses sub-second gap data. Always word-level verbatim.\n- **Running Whisper locally on CPU.** Slow and it normalizes fillers. Use hosted Scribe.\n- **Burning subtitles into base before compositing overlays.** Overlays hide them. (Hard Rule 1.)\n- **Single-pass filtergraph when you have overlays.** Double re-encodes. Use per-segment extract → concat.\n- **Linear animation easing.** Looks robotic. Always cubic.\n- **Unverified web fonts.** A failed load silently falls back to a system face. Assert the font loaded before rendering.\n- **Stock SFX on every transition.** Tie each effect to a visible event; cap the count.\n- **Hard audio cuts at segment boundaries.** Audible pops. (Hard Rule 3.)\n- **Typing text centered on the partial string.** Text slides left as it grows.\n- **Sequential sub-agents for multiple animations.** Always parallel.\n- **Editing before confirming the strategy.** Never.\n- **Re-transcribing cached sources.** Immutable outputs of immutable inputs.\n- **Assuming what kind of video it is.** Look first, ask second, edit last.\n","frontmatter":{"name":"video-use","description":"Edit any video by conversation. Transcribe, cut, color grade, generate overlay animations, burn subtitles — for talking heads, montages, tutorials, travel, interviews. No presets, no menus. Ask questions, confirm the plan, execute, iterate, persist. Production-correctness rules are hard; everything else is artistic freedom."},"isInternal":false,"tokens":6444,"sizeBytes":26098},{"name":"README.md","path":"skills/manim-video/README.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/README.md","title":"Manim-video Skill","category":"anthropic-skill","format":"markdown","content":"# Manim Video Skill\n\nProduction pipeline for mathematical and technical animations using [Manim Community Edition](https://www.manim.community/).\n\n## What it does\n\nCreates 3Blue1Brown-style animated videos from text prompts. The agent handles the full pipeline: creative planning, Python code generation, rendering, scene stitching, and iterative refinement.\n\n## Use cases\n\n- **Concept explainers** — \"Explain how neural networks learn\"\n- **Equation derivations** — \"Animate the proof of the Pythagorean theorem\"\n- **Algorithm visualizations** — \"Show how quicksort works step by step\"\n- **Data stories** — \"Animate our before/after performance metrics\"\n- **Architecture diagrams** — \"Show our microservice architecture building up\"\n\n## Prerequisites\n\nPython 3.10+, Manim CE (`pip install manim`), LaTeX, ffmpeg.\n\n```bash\nbash skills/creative/manim-video/scripts/setup.sh\n```\n","isInternal":false,"tokens":194,"sizeBytes":886},{"name":"animation-design-thinking.md","path":"skills/manim-video/references/animation-design-thinking.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/references/animation-design-thinking.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Animation Design Thinking\n\nHow to decide WHAT to animate and HOW to structure it — before writing any code.\n\n## Should I animate this?\n\nNot everything benefits from animation. Motion adds cognitive load. Bad animation is worse than a good static diagram.\n\n**Animate when:**\n- A sequence unfolds over time (algorithm steps, derivation, pipeline stages)\n- Spatial relationships change (transformation, deformation, rotation)\n- Something is built from parts (construction, assembly, accumulation)\n- You're comparing states (before/after, method A vs method B)\n- Temporal evolution is the point (training curves, wave propagation, gradient descent)\n\n**Show static when:**\n- The concept is a single labeled diagram (circuit, anatomy, architecture overview)\n- Motion would distract from spatial layout\n- The viewer needs to study it carefully (dense table, reference chart)\n- The concept is already intuitive from a well-labeled figure\n\n**Rule of thumb:** If you'd explain it with \"first X, then Y, then Z\" — animate it. If you'd explain it by pointing at parts of one picture — show it static.\n\n## Decomposing a concept into animation\n\n### Step 1: Write the narration first\n\nBefore any code, write what the narrator would say. This determines:\n- **Order** — what concept comes first\n- **Duration** — how long each idea gets\n- **Visuals** — what the viewer must SEE when they HEAR each sentence\n\nA scene where the narration says \"the gradient points uphill\" must show a gradient arrow at that moment. If the visual doesn't match the audio, the viewer's brain splits attention and both tracks are lost.\n\n### Step 2: Identify visual beats\n\nA \"beat\" is a moment where something changes on screen. Mark each beat in your narration:\n\n```\n\"Consider a function f of x.\"         → [BEAT: axes + curve appear]\n\"At this point...\"                     → [BEAT: dot appears on curve]\n\"...the slope is positive.\"            → [BEAT: tangent line drawn]\n\"So the gradient tells us to go left.\" → [BEAT: arrow points left, dot moves]\n```\n\nEach beat is one `self.play()` call or a small group of simultaneous animations.\n\n### Step 3: Choose the right tool per beat\n\n| Visual need | Manim approach |\n|-------------|----------------|\n| Object appears for first time | `Create`, `Write`, `FadeIn`, `GrowFromCenter` |\n| Object transforms into another | `Transform`, `ReplacementTransform`, `FadeTransform` |\n| Attention drawn to existing object | `Indicate`, `Circumscribe`, `Flash`, `ShowPassingFlash` |\n| Continuous relationship maintained | `add_updater`, `always_redraw`, `ValueTracker` |\n| Object leaves the scene | `FadeOut`, `Uncreate`, `ShrinkToCenter` |\n| Static context that stays visible | `self.add()` (no animation) |\n\n## Pacing: the universal mistake is too fast\n\n### Timing rules\n\n| Content type | Minimum on-screen time |\n|-------------|----------------------|\n| New equation appearing | 2.0s animation + 2.0s pause |\n| New concept label | 1.0s animation + 1.0s pause |\n| Key insight (\"aha moment\") | 2.5s animation + 3.0s pause |\n| Supporting annotation | 0.8s animation + 0.5s pause |\n| Scene transition (FadeOut all) | 0.5s animation + 0.3s pause |\n\n### Breathing room\n\nAfter every reveal, add `self.wait()`. The viewer needs time to:\n1. Read the new text\n2. Connect it to what's already on screen\n3. Form an expectation about what comes next\n\n**No wait = the viewer is always behind you.** They're still reading the equation when you've already started transforming it.\n\n### Tempo variation\n\nMonotonous pacing feels like a lecture. Vary the tempo:\n- **Slow build** for core concepts (long run_time, long pauses)\n- **Quick succession** for supporting details (short run_time, minimal pauses)\n- **Dramatic pause** before the key reveal (extra `self.wait(2.0)` before the \"aha\")\n- **Rapid montage** for \"and this applies to X, Y, Z...\" sequences (`LaggedStart` with tight lag_ratio)\n\n## Narration synchronization\n\n### The \"see then hear\" principle\n\nThe visual should appear slightly BEFORE the narration describes it. When the viewer sees a circle appear and THEN hears \"consider a circle,\" the visual primes their brain for the concept. The reverse — hearing first, seeing second — creates confusion because they're searching the screen for something that isn't there yet.\n\n### Practical timing\n\n```python\n# Scene duration should match narration duration.\n# If narration for this scene is 8 seconds:\n# Total animation run_times + total self.wait() times = ~8 seconds.\n\n# Use manim-voiceover for automatic sync:\nwith self.voiceover(text=\"The gradient points downhill\") as tracker:\n    self.play(GrowArrow(gradient_arrow), run_time=tracker.duration)\n```\n\n## Equation decomposition strategy\n\n### The \"dim and reveal\" pattern\n\nWhen building a complex equation step by step:\n1. Show the full equation dimmed at `opacity=0.2` (sets expectation for where you're going)\n2. Highlight the first term at full opacity\n3. Explain it\n4. Highlight the next term, dim the first to `0.5` (it's now context)\n5. Repeat until the full equation is bright\n\nThis is better than building left-to-right because the viewer always sees the destination.\n\n### Term ordering\n\nAnimate terms in the order the viewer needs to understand them, not in the order they appear in the equation. For `E = mc²`:\n- Show `E` (the thing we want to know)\n- Then `m` (the input)\n- Then `c²` (the constant that makes it work)\n- Then the `=` (connecting them)\n\n## Architecture and pipeline diagrams\n\n### Box granularity\n\nThe most common mistake: too many boxes. Each box is a concept the viewer must track. Five boxes with clear labels beats twelve boxes with abbreviations.\n\n**Rule:** If two consecutive boxes could be labeled \"X\" and \"process X output,\" merge them into one box.\n\n### Animation strategy\n\nBuild pipelines left-to-right (or top-to-bottom) with arrows connecting them:\n1. First box appears alone → explain it\n2. Arrow grows from first to second → \"the output feeds into...\"\n3. Second box appears → explain it\n4. Repeat\n\nThen show data flowing through: `ShowPassingFlash` along the arrows, or a colored dot traversing the path.\n\n### The zoom-and-return pattern\n\nFor complex systems:\n1. Show the full overview (all boxes, small)\n2. Zoom into one box (`MovingCameraScene.camera.frame.animate`)\n3. Expand that box into its internal components\n4. Zoom back out to the overview\n5. Zoom into the next box\n\n## Common design mistakes\n\n1. **Animating everything at once.** The viewer can track 1-2 simultaneous animations. More than that and nothing registers.\n2. **No visual hierarchy.** Everything at the same opacity/size/color means nothing stands out. Use opacity layering.\n3. **Equations without context.** An equation appearing alone means nothing. Always show the geometric/visual interpretation first or simultaneously.\n4. **Skipping the \"why.\"** Showing HOW a transformation works without WHY it matters. Add a sentence/label explaining the purpose.\n5. **Identical pacing throughout.** Every animation at run_time=1.5, every wait at 1.0. Vary it.\n6. **Forgetting the audience.** A video for high schoolers needs different pacing and complexity than one for PhD students. Decide the audience in the planning phase.\n","isInternal":false,"tokens":1663,"sizeBytes":7205},{"name":"animations.md","path":"skills/manim-video/references/animations.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/references/animations.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Animations Reference\n\n## Core Concept\n\nAn animation is a Python object that computes intermediate visual states of a mobject over time. Animations are objects passed to `self.play()`, not functions.\n\n`run_time` controls seconds (default: 1). Always specify it explicitly for important animations.\n\n## Creation Animations\n\n```python\nself.play(Create(circle))          # traces outline\nself.play(Write(equation))         # simulates handwriting (for Text/MathTex)\nself.play(FadeIn(group))           # opacity 0 -> 1\nself.play(GrowFromCenter(dot))     # scale 0 -> 1 from center\nself.play(DrawBorderThenFill(sq))  # outline first, then fill\n```\n\n## Removal Animations\n\n```python\nself.play(FadeOut(mobject))         # opacity 1 -> 0\nself.play(Uncreate(circle))        # reverse of Create\nself.play(ShrinkToCenter(group))   # scale 1 -> 0\n```\n\n## Transform Animations\n\n```python\n# Transform -- modifies the original in place\nself.play(Transform(circle, square))\n# After: circle IS the square (same object, new appearance)\n\n# ReplacementTransform -- replaces old with new\nself.play(ReplacementTransform(circle, square))\n# After: circle removed, square on screen\n\n# TransformMatchingTex -- smart equation morphing\neq1 = MathTex(r\"a^2 + b^2\")\neq2 = MathTex(r\"a^2 + b^2 = c^2\")\nself.play(TransformMatchingTex(eq1, eq2))\n```\n\n**Critical**: After `Transform(A, B)`, variable `A` references the on-screen mobject. Variable `B` is NOT on screen. Use `ReplacementTransform` when you want to work with `B` afterwards.\n\n## The .animate Syntax\n\n```python\nself.play(circle.animate.set_color(RED))\nself.play(circle.animate.shift(RIGHT * 2).scale(0.5))  # chain multiple\n```\n\n## Additional Creation Animations\n\n```python\nself.play(GrowFromPoint(circle, LEFT * 3))     # scale 0 -> 1 from a specific point\nself.play(GrowFromEdge(rect, DOWN))             # grow from one edge\nself.play(SpinInFromNothing(square))            # scale up while rotating (default PI/2)\nself.play(GrowArrow(arrow))                     # grows arrow from start to tip\n```\n\n## Movement Animations\n\n```python\n# Move a mobject along an arbitrary path\npath = Arc(radius=2, angle=PI)\nself.play(MoveAlongPath(dot, path), run_time=2)\n\n# Rotate (as a Transform, not .animate — supports about_point)\nself.play(Rotate(square, angle=PI / 2, about_point=ORIGIN), run_time=1.5)\n\n# Rotating (continuous rotation, updater-style — good for spinning objects)\nself.play(Rotating(gear, angle=TAU, run_time=4, rate_func=linear))\n```\n\n`MoveAlongPath` takes any `VMobject` as the path — use `Arc`, `CubicBezier`, `Line`, or a custom `VMobject`. Position is computed via `path.point_from_proportion()`.\n\n## Emphasis Animations\n\n```python\nself.play(Indicate(mobject))             # brief yellow flash + scale\nself.play(Circumscribe(mobject))         # draw rectangle around it\nself.play(Flash(point))                  # radial flash\nself.play(Wiggle(mobject))               # shake side to side\n```\n\n## Rate Functions\n\n```python\nself.play(FadeIn(mob), rate_func=smooth)          # default: ease in/out\nself.play(FadeIn(mob), rate_func=linear)           # constant speed\nself.play(FadeIn(mob), rate_func=rush_into)        # start slow, end fast\nself.play(FadeIn(mob), rate_func=rush_from)        # start fast, end slow\nself.play(FadeIn(mob), rate_func=there_and_back)   # animate then reverse\n```\n\n## Composition\n\n```python\n# Simultaneous\nself.play(FadeIn(title), Create(circle), run_time=2)\n\n# AnimationGroup with lag\nself.play(AnimationGroup(*[FadeIn(i) for i in items], lag_ratio=0.2))\n\n# LaggedStart\nself.play(LaggedStart(*[Write(l) for l in lines], lag_ratio=0.3, run_time=3))\n\n# Succession (sequential in one play call)\nself.play(Succession(FadeIn(title), Wait(0.5), Write(subtitle)))\n```\n\n## Updaters\n\n```python\ntracker = ValueTracker(0)\ndot = Dot().add_updater(lambda m: m.move_to(axes.c2p(tracker.get_value(), 0)))\nself.play(tracker.animate.set_value(5), run_time=3)\n```\n\n## Subtitles\n\n```python\n# Method 1: standalone\nself.add_subcaption(\"Key insight\", duration=2)\nself.play(Write(equation), run_time=2.0)\n\n# Method 2: inline\nself.play(Write(equation), subcaption=\"Key insight\", subcaption_duration=2)\n```\n\nManim auto-generates `.srt` subtitle files. Always add subcaptions for accessibility.\n\n## Timing Patterns\n\n```python\n# Pause-after-reveal\nself.play(Write(key_equation), run_time=2.0)\nself.wait(2.0)\n\n# Dim-and-focus\nself.play(old_content.animate.set_opacity(0.3), FadeIn(new_content))\n\n# Clean exit\nself.play(FadeOut(Group(*self.mobjects)), run_time=0.5)\nself.wait(0.3)\n```\n\n## Reactive Mobjects: always_redraw()\n\nRebuild a mobject from scratch every frame — essential when its geometry depends on other animated objects:\n\n```python\n# Brace that follows a resizing square\nbrace = always_redraw(Brace, square, UP)\nself.add(brace)\nself.play(square.animate.scale(2))  # brace auto-adjusts\n\n# Horizontal line that tracks a moving dot\nh_line = always_redraw(lambda: axes.get_h_line(dot.get_left()))\n\n# Label that always stays next to another mobject\nlabel = always_redraw(lambda: Text(\"here\", font_size=20).next_to(dot, UP, buff=0.2))\n```\n\nNote: `always_redraw` recreates the mobject every frame. For simple property tracking, use `add_updater` instead (cheaper):\n```python\nlabel.add_updater(lambda m: m.next_to(dot, UP))\n```\n\n## TracedPath — Trajectory Tracing\n\nDraw the path a point has traveled:\n\n```python\ndot = Dot(color=YELLOW)\npath = TracedPath(dot.get_center, stroke_color=YELLOW, stroke_width=2)\nself.add(dot, path)\nself.play(dot.animate.shift(RIGHT * 3 + UP * 2), run_time=2)\n# path shows the trail the dot left behind\n\n# Fading trail (dissipates over time):\npath = TracedPath(dot.get_center, dissipating_time=0.5, stroke_opacity=[0, 1])\n```\n\nUse cases: gradient descent paths, planetary orbits, function tracing, particle trajectories.\n\n## FadeTransform — Smoother Cross-Fades\n\n`Transform` morphs shapes through ugly intermediate warping. `FadeTransform` cross-fades with position matching — use it when source and target look different:\n\n```python\n# UGLY: Transform warps circle into square through a blob\nself.play(Transform(circle, square))\n\n# SMOOTH: FadeTransform cross-fades cleanly\nself.play(FadeTransform(circle, square))\n\n# FadeTransformPieces: per-submobject FadeTransform\nself.play(FadeTransformPieces(group1, group2))\n\n# TransformFromCopy: animate a COPY while keeping the original visible\nself.play(TransformFromCopy(source, target))\n# source stays on screen, a copy morphs into target\n```\n\n**Recommendation:** Use `FadeTransform` as default for dissimilar shapes. Use `Transform`/`ReplacementTransform` only for similar shapes (circle→ellipse, equation→equation).\n\n## ApplyMatrix — Linear Transformation Visualization\n\nAnimate a matrix transformation on mobjects:\n\n```python\n# Apply a 2x2 matrix to a grid\nmatrix = [[2, 1], [1, 1]]\nself.play(ApplyMatrix(matrix, number_plane), run_time=2)\n\n# Also works on individual mobjects\nself.play(ApplyMatrix([[0, -1], [1, 0]], square))  # 90-degree rotation\n```\n\nPairs with `LinearTransformationScene` — see `camera-and-3d.md`.\n\n## squish_rate_func — Time-Window Staggering\n\nCompress any rate function into a time window within an animation. Enables overlapping stagger without `LaggedStart`:\n\n```python\nself.play(\n    FadeIn(a, rate_func=squish_rate_func(smooth, 0, 0.5)),    # 0% to 50%\n    FadeIn(b, rate_func=squish_rate_func(smooth, 0.25, 0.75)), # 25% to 75%\n    FadeIn(c, rate_func=squish_rate_func(smooth, 0.5, 1.0)),  # 50% to 100%\n    run_time=2\n)\n```\n\nMore precise than `LaggedStart` when you need exact overlap control.\n\n## Additional Rate Functions\n\n```python\nfrom manim import (\n    smooth, linear, rush_into, rush_from,\n    there_and_back, there_and_back_with_pause,\n    running_start, double_smooth, wiggle,\n    lingering, exponential_decay, not_quite_there,\n    squish_rate_func\n)\n\n# running_start: pulls back before going forward (anticipation)\nself.play(FadeIn(mob, rate_func=running_start))\n\n# there_and_back_with_pause: goes there, holds, comes back\nself.play(mob.animate.shift(UP), rate_func=there_and_back_with_pause)\n\n# not_quite_there: stops at a fraction of the full animation\nself.play(FadeIn(mob, rate_func=not_quite_there(0.7)))\n```\n\n## ShowIncreasingSubsets / ShowSubmobjectsOneByOne\n\nReveal group members progressively — ideal for algorithm visualization:\n\n```python\n# Reveal array elements one at a time\narray = Group(*[Square() for _ in range(8)]).arrange(RIGHT)\nself.play(ShowIncreasingSubsets(array), run_time=3)\n\n# Show submobjects with staggered appearance\nself.play(ShowSubmobjectsOneByOne(code_lines), run_time=4)\n```\n\n## ShowPassingFlash\n\nA flash of light travels along a path:\n\n```python\n# Flash traveling along a curve\nself.play(ShowPassingFlash(curve.copy().set_color(YELLOW), time_width=0.3))\n\n# Great for: data flow, electrical signals, network traffic\n```\n","isInternal":false,"tokens":2342,"sizeBytes":8836},{"name":"camera-and-3d.md","path":"skills/manim-video/references/camera-and-3d.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/references/camera-and-3d.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Camera and 3D Reference\n\n## MovingCameraScene (2D Camera Control)\n\n```python\nclass ZoomExample(MovingCameraScene):\n    def construct(self):\n        circle = Circle(radius=2, color=BLUE)\n        self.play(Create(circle))\n        # Zoom in\n        self.play(self.camera.frame.animate.set(width=4).move_to(circle.get_top()), run_time=2)\n        self.wait(2)\n        # Zoom back out\n        self.play(self.camera.frame.animate.set(width=14.222).move_to(ORIGIN), run_time=2)\n```\n\n### Camera Operations\n\n```python\nself.camera.frame.animate.set(width=6)     # zoom in\nself.camera.frame.animate.set(width=20)    # zoom out\nself.camera.frame.animate.move_to(target)  # pan\nself.camera.frame.save_state()             # save\nself.play(Restore(self.camera.frame))      # restore\n```\n\n## ThreeDScene\n\n```python\nclass ThreeDExample(ThreeDScene):\n    def construct(self):\n        self.set_camera_orientation(phi=60*DEGREES, theta=-45*DEGREES)\n        axes = ThreeDAxes()\n        surface = Surface(\n            lambda u, v: axes.c2p(u, v, np.sin(u) * np.cos(v)),\n            u_range=[-PI, PI], v_range=[-PI, PI], resolution=(30, 30)\n        )\n        surface.set_color_by_gradient(BLUE, GREEN, YELLOW)\n        self.play(Create(axes), Create(surface))\n        self.begin_ambient_camera_rotation(rate=0.2)\n        self.wait(5)\n        self.stop_ambient_camera_rotation()\n```\n\n### Camera Control in 3D\n\n```python\nself.set_camera_orientation(phi=70*DEGREES, theta=-45*DEGREES)\nself.move_camera(phi=45*DEGREES, theta=30*DEGREES, run_time=2)\nself.begin_ambient_camera_rotation(rate=0.2)\n```\n\n### 3D Mobjects\n\n```python\nsphere = Sphere(radius=1).set_color(BLUE).set_opacity(0.7)\ncube = Cube(side_length=2, fill_color=GREEN, fill_opacity=0.5)\narrow = Arrow3D(start=ORIGIN, end=[2, 1, 1], color=RED)\n# 2D text facing camera:\nlabel = Text(\"Label\", font_size=30)\nself.add_fixed_in_frame_mobjects(label)\n```\n\n### Parametric Curves\n\n```python\nhelix = ParametricFunction(\n    lambda t: [np.cos(t), np.sin(t), t / (2*PI)],\n    t_range=[0, 4*PI], color=YELLOW\n)\n```\n\n## When to Use 3D\n- Surfaces, vector fields, spatial geometry, 3D transforms\n## When NOT to Use 3D\n- 2D concepts, text-heavy scenes, flat data (bar charts, time series)\n\n## ZoomedScene — Inset Zoom\n\nShow a magnified inset of a detail while keeping the full view visible:\n\n```python\nclass ZoomExample(ZoomedScene):\n    def __init__(self, **kwargs):\n        super().__init__(\n            zoom_factor=0.3,           # how much of the scene the zoom box covers\n            zoomed_display_height=3,   # size of the inset\n            zoomed_display_width=3,\n            zoomed_camera_frame_starting_position=ORIGIN,\n            **kwargs\n        )\n\n    def construct(self):\n        self.camera.background_color = BG\n        # ... create your scene content ...\n\n        # Activate the zoom\n        self.activate_zooming()\n\n        # Move the zoom frame to a point of interest\n        self.play(self.zoomed_camera.frame.animate.move_to(detail_point))\n        self.wait(2)\n\n        # Deactivate\n        self.play(self.get_zoomed_display_pop_out_animation(), rate_func=lambda t: smooth(1-t))\n```\n\nUse cases: zooming into a specific term in an equation, showing fine detail in a diagram, magnifying a region of a plot.\n\n## LinearTransformationScene — Linear Algebra\n\nPre-built scene with basis vectors and grid for visualizing matrix transformations:\n\n```python\nclass LinearTransformExample(LinearTransformationScene):\n    def __init__(self, **kwargs):\n        super().__init__(\n            show_coordinates=True,\n            show_basis_vectors=True,\n            **kwargs\n        )\n\n    def construct(self):\n        matrix = [[2, 1], [1, 1]]\n\n        # Add a vector before applying the transform\n        vector = self.get_vector([1, 2], color=YELLOW)\n        self.add_vector(vector)\n\n        # Apply the transformation — grid, basis vectors, and your vector all transform\n        self.apply_matrix(matrix)\n        self.wait(2)\n```\n\nThis produces the signature 3Blue1Brown \"Essence of Linear Algebra\" look — grid lines deforming, basis vectors stretching, determinant visualized through area change.\n","isInternal":false,"tokens":1042,"sizeBytes":4132},{"name":"decorations.md","path":"skills/manim-video/references/decorations.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/references/decorations.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Decorations and Visual Polish\n\nDecorations are mobjects that annotate, highlight, or frame other mobjects. They turn a technically correct animation into a visually polished one.\n\n## SurroundingRectangle\n\nDraws a rectangle around any mobject. The go-to for highlighting:\n\n```python\nhighlight = SurroundingRectangle(\n    equation[2],            # the term to highlight\n    color=YELLOW,\n    buff=0.15,              # padding between content and border\n    corner_radius=0.1,      # rounded corners\n    stroke_width=2\n)\nself.play(Create(highlight))\nself.wait(1)\nself.play(FadeOut(highlight))\n```\n\n### Around part of an equation\n\n```python\neq = MathTex(r\"E\", r\"=\", r\"m\", r\"c^2\")\nbox = SurroundingRectangle(eq[2:], color=YELLOW, buff=0.1)  # highlight \"mc²\"\nlabel = Text(\"mass-energy\", font_size=18, font=\"Menlo\", color=YELLOW)\nlabel.next_to(box, DOWN, buff=0.2)\nself.play(Create(box), FadeIn(label))\n```\n\n## BackgroundRectangle\n\nSemi-transparent background behind text for readability over complex scenes:\n\n```python\nbg = BackgroundRectangle(equation, fill_opacity=0.7, buff=0.2, color=BLACK)\nself.play(FadeIn(bg), Write(equation))\n\n# Or using set_stroke for a \"backdrop\" effect on the text itself:\nlabel.set_stroke(BLACK, width=5, background=True)\n```\n\nThe `set_stroke(background=True)` approach is cleaner for text labels over graphs/diagrams.\n\n## Brace and BraceLabel\n\nCurly braces that annotate sections of a diagram or equation:\n\n```python\nbrace = Brace(equation[2:4], DOWN, color=YELLOW)\nbrace_label = brace.get_text(\"these terms\", font_size=20)\nself.play(GrowFromCenter(brace), FadeIn(brace_label))\n\n# Between two specific points\nbrace = BraceBetweenPoints(point_a, point_b, direction=UP)\n```\n\n### Brace placement\n\n```python\n# Below a group\nBrace(group, DOWN)\n# Above a group\nBrace(group, UP)\n# Left of a group\nBrace(group, LEFT)\n# Right of a group\nBrace(group, RIGHT)\n```\n\n## Arrows for Annotation\n\n### Straight arrows pointing to mobjects\n\n```python\narrow = Arrow(\n    start=label.get_bottom(),\n    end=target.get_top(),\n    color=YELLOW,\n    stroke_width=2,\n    buff=0.1,                    # gap between arrow tip and target\n    max_tip_length_to_length_ratio=0.15  # small arrowhead\n)\nself.play(GrowArrow(arrow), FadeIn(label))\n```\n\n### Curved arrows\n\n```python\narrow = CurvedArrow(\n    start_point=source.get_right(),\n    end_point=target.get_left(),\n    angle=PI/4,                  # curve angle\n    color=PRIMARY\n)\n```\n\n### Labeling with arrows\n\n```python\n# LabeledArrow: arrow with built-in text label\narr = LabeledArrow(\n    Text(\"gradient\", font_size=16, font=\"Menlo\"),\n    start=point_a, end=point_b, color=RED\n)\n```\n\n## DashedLine and DashedVMobject\n\n```python\n# Dashed line (for asymptotes, construction lines, implied connections)\nasymptote = DashedLine(\n    axes.c2p(2, -3), axes.c2p(2, 3),\n    color=YELLOW, dash_length=0.15\n)\n\n# Make any VMobject dashed\ndashed_circle = DashedVMobject(Circle(radius=2, color=BLUE), num_dashes=30)\n```\n\n## Angle and RightAngle Markers\n\n```python\nline1 = Line(ORIGIN, RIGHT * 2)\nline2 = Line(ORIGIN, UP * 2 + RIGHT)\n\n# Angle arc between two lines\nangle = Angle(line1, line2, radius=0.5, color=YELLOW)\nangle_value = angle.get_value()  # radians\n\n# Right angle marker (the small square)\nright_angle = RightAngle(line1, Line(ORIGIN, UP * 2), length=0.3, color=WHITE)\n```\n\n## Cross (strikethrough)\n\nMark something as wrong or deprecated:\n\n```python\ncross = Cross(old_equation, color=RED, stroke_width=4)\nself.play(Create(cross))\n# Then show the correct version\n```\n\n## Underline\n\n```python\nunderline = Underline(important_text, color=ACCENT, stroke_width=3)\nself.play(Create(underline))\n```\n\n## Color Highlighting Workflow\n\n### Method 1: At creation with t2c\n\n```python\ntext = Text(\"The gradient is negative here\", t2c={\"gradient\": BLUE, \"negative\": RED})\n```\n\n### Method 2: set_color_by_tex after creation\n\n```python\neq = MathTex(r\"\\nabla L = -\\frac{\\partial L}{\\partial w}\")\neq.set_color_by_tex(r\"\\nabla\", BLUE)\neq.set_color_by_tex(r\"\\partial\", RED)\n```\n\n### Method 3: Index into submobjects\n\n```python\neq = MathTex(r\"a\", r\"+\", r\"b\", r\"=\", r\"c\")\neq[0].set_color(RED)    # \"a\"\neq[2].set_color(BLUE)   # \"b\"\neq[4].set_color(GREEN)  # \"c\"\n```\n\n## Combining Annotations\n\nLayer multiple annotations for emphasis:\n\n```python\n# Highlight a term, add a brace, and an arrow — in sequence\nbox = SurroundingRectangle(eq[2], color=YELLOW, buff=0.1)\nbrace = Brace(eq[2], DOWN, color=YELLOW)\nlabel = brace.get_text(\"learning rate\", font_size=18)\n\nself.play(Create(box))\nself.wait(0.5)\nself.play(FadeOut(box), GrowFromCenter(brace), FadeIn(label))\nself.wait(1.5)\nself.play(FadeOut(brace), FadeOut(label))\n```\n\n### The annotation lifecycle\n\nAnnotations should follow a rhythm:\n1. **Appear** — draw attention (Create, GrowFromCenter)\n2. **Hold** — viewer reads and understands (self.wait)\n3. **Disappear** — clear the stage for the next thing (FadeOut)\n\nNever leave annotations on screen indefinitely — they become visual noise once their purpose is served.\n","isInternal":false,"tokens":1384,"sizeBytes":5006},{"name":"equations.md","path":"skills/manim-video/references/equations.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/references/equations.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Equations and LaTeX Reference\n\n## Basic LaTeX\n\n```python\neq = MathTex(r\"E = mc^2\")\neq = MathTex(r\"f(x) &= x^2 + 2x + 1 \\\\ &= (x + 1)^2\")  # multi-line aligned\n```\n\n**Always use raw strings (`r\"\"`).**\n\n## Step-by-Step Derivations\n\n```python\nstep1 = MathTex(r\"a^2 + b^2 = c^2\")\nstep2 = MathTex(r\"a^2 = c^2 - b^2\")\nself.play(Write(step1), run_time=1.5)\nself.wait(1.5)\nself.play(TransformMatchingTex(step1, step2), run_time=1.5)\n```\n\n## Selective Color\n\n```python\neq = MathTex(r\"a^2\", r\"+\", r\"b^2\", r\"=\", r\"c^2\")\neq[0].set_color(RED)\neq[4].set_color(GREEN)\n```\n\n## Building Incrementally\n\n```python\nparts = MathTex(r\"f(x)\", r\"=\", r\"\\sum_{n=0}^{\\infty}\", r\"\\frac{f^{(n)}(a)}{n!}\", r\"(x-a)^n\")\nself.play(Write(parts[0:2]))\nself.wait(0.5)\nself.play(Write(parts[2]))\nself.wait(0.5)\nself.play(Write(parts[3:]))\n```\n\n## Highlighting\n\n```python\nhighlight = SurroundingRectangle(eq[2], color=YELLOW, buff=0.1)\nself.play(Create(highlight))\nself.play(Indicate(eq[4], color=YELLOW))\n```\n\n## Annotation\n\n```python\nbrace = Brace(eq, DOWN, color=YELLOW)\nlabel = brace.get_text(\"Fundamental Theorem\", font_size=24)\nself.play(GrowFromCenter(brace), Write(label))\n```\n\n## Common LaTeX\n\n```python\nMathTex(r\"\\frac{a}{b}\")                  # fraction\nMathTex(r\"\\alpha, \\beta, \\gamma\")         # Greek\nMathTex(r\"\\sum_{i=1}^{n} x_i\")           # summation\nMathTex(r\"\\int_{0}^{\\infty} e^{-x} dx\")  # integral\nMathTex(r\"\\vec{v}\")                       # vector\nMathTex(r\"\\lim_{x \\to \\infty} f(x)\")    # limit\n```\n\n## Matrices\n\n`MathTex` supports standard LaTeX matrix environments via `amsmath` (loaded by default):\n\n```python\n# Bracketed matrix\nMathTex(r\"\\begin{bmatrix} 1 & 0 \\\\ 0 & 1 \\end{bmatrix}\")\n\n# Parenthesized matrix\nMathTex(r\"\\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}\")\n\n# Determinant (vertical bars)\nMathTex(r\"\\begin{vmatrix} a & b \\\\ c & d \\end{vmatrix}\")\n\n# Plain (no delimiters)\nMathTex(r\"\\begin{matrix} x_1 \\\\ x_2 \\\\ x_3 \\end{matrix}\")\n```\n\nFor matrices you need to animate element-by-element or color individual entries, use the `IntegerMatrix`, `DecimalMatrix`, or `MobjectMatrix` mobjects instead — see `mobjects.md`.\n\n## Cases and Piecewise Functions\n\n```python\nMathTex(r\"\"\"\n    f(x) = \\begin{cases}\n        x^2    & \\text{if } x \\geq 0 \\\\\n        -x^2   & \\text{if } x < 0\n    \\end{cases}\n\"\"\")\n```\n\n## Aligned Environments\n\nFor multi-line derivations with alignment, use `aligned` inside `MathTex`:\n\n```python\nMathTex(r\"\"\"\n    \\begin{aligned}\n        \\nabla \\cdot \\mathbf{E} &= \\frac{\\rho}{\\epsilon_0} \\\\\n        \\nabla \\cdot \\mathbf{B} &= 0 \\\\\n        \\nabla \\times \\mathbf{E} &= -\\frac{\\partial \\mathbf{B}}{\\partial t} \\\\\n        \\nabla \\times \\mathbf{B} &= \\mu_0 \\mathbf{J} + \\mu_0 \\epsilon_0 \\frac{\\partial \\mathbf{E}}{\\partial t}\n    \\end{aligned}\n\"\"\")\n```\n\nNote: `MathTex` wraps content in `align*` by default. Override with `tex_environment` if needed:\n```python\nMathTex(r\"...\", tex_environment=\"gather*\")\n```\n\n## Derivation Pattern\n\n```python\nclass DerivationScene(Scene):\n    def construct(self):\n        self.camera.background_color = BG\n        s1 = MathTex(r\"ax^2 + bx + c = 0\")\n        self.play(Write(s1))\n        self.wait(1.5)\n        s2 = MathTex(r\"x^2 + \\frac{b}{a}x + \\frac{c}{a} = 0\")\n        s2.next_to(s1, DOWN, buff=0.8)\n        self.play(s1.animate.set_opacity(0.4), TransformMatchingTex(s1.copy(), s2))\n```\n\n## substrings_to_isolate for Complex Equations\n\nFor dense equations where manually splitting into parts is impractical, use `substrings_to_isolate` to tell Manim which substrings to track as individual elements:\n\n```python\n# Without isolation — the whole expression is one blob\nlagrangian = MathTex(\n    r\"\\mathcal{L} = \\bar{\\psi}(i \\gamma^\\mu D_\\mu - m)\\psi - \\tfrac{1}{4}F_{\\mu\\nu}F^{\\mu\\nu}\"\n)\n\n# With isolation — each named substring is a separate submobject\nlagrangian = MathTex(\n    r\"\\mathcal{L} = \\bar{\\psi}(i \\gamma^\\mu D_\\mu - m)\\psi - \\tfrac{1}{4}F_{\\mu\\nu}F^{\\mu\\nu}\",\n    substrings_to_isolate=[r\"\\psi\", r\"D_\\mu\", r\"\\gamma^\\mu\", r\"F_{\\mu\\nu}\"]\n)\n# Now you can color individual terms\nlagrangian.set_color_by_tex(r\"\\psi\", BLUE)\nlagrangian.set_color_by_tex(r\"F_{\\mu\\nu}\", YELLOW)\n```\n\nEssential for `TransformMatchingTex` on complex equations — without isolation, matching fails on dense expressions.\n\n## Multi-Line Complex Equations\n\nFor equations with multiple related lines, pass each line as a separate argument:\n\n```python\nmaxwell = MathTex(\n    r\"\\nabla \\cdot \\mathbf{E} = \\frac{\\rho}{\\epsilon_0}\",\n    r\"\\nabla \\times \\mathbf{B} = \\mu_0\\mathbf{J} + \\mu_0\\epsilon_0\\frac{\\partial \\mathbf{E}}{\\partial t}\"\n).arrange(DOWN)\n\n# Each line is a separate submobject — animate independently\nself.play(Write(maxwell[0]))\nself.wait(1)\nself.play(Write(maxwell[1]))\n```\n\n## TransformMatchingTex with key_map\n\nMap specific substrings between source and target equations during transformation:\n\n```python\neq1 = MathTex(r\"A^2 + B^2 = C^2\")\neq2 = MathTex(r\"A^2 = C^2 - B^2\")\n\nself.play(TransformMatchingTex(\n    eq1, eq2,\n    key_map={\"+\": \"-\"},   # map \"+\" in source to \"-\" in target\n    path_arc=PI / 2,      # arc the pieces into position\n))\n```\n\n## set_color_by_tex — Color by Substring\n\n```python\neq = MathTex(r\"E = mc^2\")\neq.set_color_by_tex(\"E\", BLUE)\neq.set_color_by_tex(\"m\", RED)\neq.set_color_by_tex(\"c\", GREEN)\n```\n\n## TransformMatchingTex with matched_keys\n\nWhen matching substrings are ambiguous, specify which to align explicitly:\n\n```python\nkw = dict(font_size=72, t2c={\"A\": BLUE, \"B\": TEAL, \"C\": GREEN})\nlines = [\n    MathTex(r\"A^2 + B^2 = C^2\", **kw),\n    MathTex(r\"A^2 = C^2 - B^2\", **kw),\n    MathTex(r\"A^2 = (C + B)(C - B)\", **kw),\n    MathTex(r\"A = \\sqrt{(C + B)(C - B)}\", **kw),\n]\n\nself.play(TransformMatchingTex(\n    lines[0].copy(), lines[1],\n    matched_keys=[\"A^2\", \"B^2\", \"C^2\"],  # explicitly match these\n    key_map={\"+\": \"-\"},                    # map + to -\n    path_arc=PI / 2,                       # arc pieces into position\n))\n```\n\nWithout `matched_keys`, the animation matches the longest common substrings, which can produce unexpected results on complex equations (e.g., \"^2 = C^2\" matching across terms).\n","isInternal":false,"tokens":1956,"sizeBytes":6086},{"name":"graphs-and-data.md","path":"skills/manim-video/references/graphs-and-data.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/references/graphs-and-data.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Graphs, Plots, and Data Visualization\n\n## Axes\n\n```python\naxes = Axes(\n    x_range=[-3, 3, 1], y_range=[-2, 2, 1],\n    x_length=8, y_length=5,\n    axis_config={\"include_numbers\": True, \"font_size\": 24}\n)\naxes.set_opacity(0.15)  # structural element\nx_label = axes.get_x_axis_label(r\"x\")\n```\n\n## Plotting\n\n```python\ngraph = axes.plot(lambda x: x**2, color=BLUE)\ngraph_label = axes.get_graph_label(graph, label=r\"x^2\", x_val=2)\narea = axes.get_area(graph, x_range=[0, 2], color=BLUE, opacity=0.3)\n```\n\n## Animated Plotting\n\n```python\nself.play(Create(graph), run_time=3)  # trace the graph\n\n# Moving dot along curve\ndot = Dot(color=YELLOW).move_to(axes.c2p(0, 0))\nself.play(MoveAlongPath(dot, graph), run_time=3)\n\n# Dynamic parameter\ntracker = ValueTracker(1)\ndynamic = always_redraw(lambda: axes.plot(lambda x: tracker.get_value() * x**2, color=BLUE))\nself.add(dynamic)\nself.play(tracker.animate.set_value(3), run_time=2)\n```\n\n## Bar Charts\n\n```python\nchart = BarChart(\n    values=[4, 6, 2, 8, 5], bar_names=[\"A\", \"B\", \"C\", \"D\", \"E\"],\n    y_range=[0, 10, 2], bar_colors=[RED, GREEN, BLUE, YELLOW, PURPLE]\n)\nself.play(Create(chart), run_time=2)\nself.play(chart.animate.change_bar_values([6, 3, 7, 4, 9]))\n```\n\n## Number Lines\n\n```python\nnl = NumberLine(x_range=[0, 10, 1], length=10, include_numbers=True)\npointer = Arrow(nl.n2p(3) + UP * 0.5, nl.n2p(3), color=RED, buff=0)\ntracker = ValueTracker(3)\npointer.add_updater(lambda m: m.put_start_and_end_on(\n    nl.n2p(tracker.get_value()) + UP * 0.5, nl.n2p(tracker.get_value())))\nself.play(tracker.animate.set_value(8), run_time=2)\n```\n\n## Animated Counters\n\n```python\ncounter = DecimalNumber(0, font_size=72, num_decimal_places=0)\nself.play(counter.animate.set_value(1000), run_time=3, rate_func=rush_from)\n```\n\n## Algorithm Visualization Pattern\n\n```python\nvalues = [5, 2, 8, 1, 9, 3]\nbars = VGroup(*[\n    Rectangle(width=0.6, height=v * 0.4, color=BLUE, fill_opacity=0.7)\n    for v in values\n]).arrange(RIGHT, buff=0.2, aligned_edge=DOWN).move_to(ORIGIN)\nself.play(LaggedStart(*[GrowFromEdge(b, DOWN) for b in bars], lag_ratio=0.1))\n# Highlight, swap, etc.\n```\n\n## Data Story Pattern\n\n```python\n# Before/After comparison\nbefore = BarChart(values=[3, 5, 2], bar_colors=[RED]*3).shift(LEFT * 3)\nafter = BarChart(values=[8, 9, 7], bar_colors=[GREEN]*3).shift(RIGHT * 3)\nself.play(Create(before)); self.wait(1)\nself.play(Create(after)); self.wait(1)\narrow = Arrow(before.get_right(), after.get_left(), color=YELLOW)\nlabel = Text(\"+167%\", font_size=36, color=YELLOW).next_to(arrow, UP)\nself.play(GrowArrow(arrow), Write(label))\n```\n\n## Graph / DiGraph — Graph Theory Visualization\n\nBuilt-in graph mobjects with automatic layout:\n\n```python\n# Undirected graph\ng = Graph(\n    vertices=[1, 2, 3, 4, 5],\n    edges=[(1, 2), (2, 3), (3, 4), (4, 5), (5, 1), (1, 3)],\n    layout=\"spring\",  # or \"circular\", \"kamada_kawai\", \"planar\", \"tree\"\n    labels=True,\n    vertex_config={\"fill_color\": PRIMARY},\n    edge_config={\"stroke_color\": SUBTLE},\n)\nself.play(Create(g))\n\n# Directed graph\ndg = DiGraph(\n    vertices=[\"A\", \"B\", \"C\"],\n    edges=[(\"A\", \"B\"), (\"B\", \"C\"), (\"C\", \"A\")],\n    layout=\"circular\",\n    labels=True,\n    edge_config={(\"A\", \"B\"): {\"stroke_color\": RED}},\n)\n\n# Add/remove vertices and edges dynamically\nself.play(g.animate.add_vertices(6, positions={6: RIGHT * 2}))\nself.play(g.animate.add_edges((1, 6)))\nself.play(g.animate.remove_vertices(3))\n```\n\nLayout algorithms: `\"spring\"`, `\"circular\"`, `\"kamada_kawai\"`, `\"planar\"`, `\"spectral\"`, `\"tree\"` (for rooted trees, specify `root=`).\n\n## ArrowVectorField / StreamLines — Vector Fields\n\n```python\n# Arrow field: arrows showing direction at each point\nfield = ArrowVectorField(\n    lambda pos: np.array([-pos[1], pos[0], 0]),  # rotation field\n    x_range=[-3, 3], y_range=[-3, 3],\n    colors=[BLUE, GREEN, YELLOW, RED]\n)\nself.play(Create(field))\n\n# StreamLines: flowing particle traces through the field\nstream = StreamLines(\n    lambda pos: np.array([-pos[1], pos[0], 0]),\n    stroke_width=2, max_anchors_per_line=30\n)\nself.add(stream)\nstream.start_animation(warm_up=True, flow_speed=1.5)\nself.wait(3)\nstream.end_animation()\n```\n\nUse cases: electromagnetic fields, fluid flow, gradient fields, ODE phase portraits.\n\n## ComplexPlane / PolarPlane\n\n```python\n# Complex plane with Re/Im labels\ncplane = ComplexPlane().add_coordinates()\ndot = Dot(cplane.n2p(2 + 1j), color=YELLOW)\nlabel = Text(\"2+i\", font_size=20).next_to(dot, UR, buff=0.1)\n\n# Apply complex function to the plane\nself.play(cplane.animate.apply_complex_function(lambda z: z**2), run_time=3)\n\n# Polar plane\npolar = PolarPlane(radius_max=3).add_coordinates()\n```\n","isInternal":false,"tokens":1449,"sizeBytes":4628},{"name":"mobjects.md","path":"skills/manim-video/references/mobjects.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/references/mobjects.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Mobjects Reference\n\nEverything visible on screen is a Mobject. They have position, color, opacity, and can be animated.\n\n## Text\n\n```python\ntitle = Text(\"Hello World\", font_size=48, color=BLUE)\neq = MathTex(r\"E = mc^2\", font_size=40)\n\n# Multi-part (for selective coloring)\neq = MathTex(r\"a^2\", r\"+\", r\"b^2\", r\"=\", r\"c^2\")\neq[0].set_color(RED)\neq[4].set_color(BLUE)\n\n# Mixed text and math\nt = Tex(r\"The area is $\\pi r^2$\", font_size=36)\n\n# Styled markup\nt = MarkupText('<span foreground=\"#58C4DD\">Blue</span> text', font_size=30)\n```\n\n**Always use raw strings (`r\"\"`) for any string with backslashes.**\n\n## Shapes\n\n```python\ncircle = Circle(radius=1, color=BLUE, fill_opacity=0.5)\nsquare = Square(side_length=2, color=RED)\nrect = Rectangle(width=4, height=2, color=GREEN)\ndot = Dot(point=ORIGIN, radius=0.08, color=YELLOW)\nline = Line(LEFT * 2, RIGHT * 2, color=WHITE)\narrow = Arrow(LEFT, RIGHT, color=ORANGE)\nrrect = RoundedRectangle(corner_radius=0.3, width=4, height=2)\nbrace = Brace(rect, DOWN, color=YELLOW)\n```\n\n## Polygons and Arcs\n\n```python\n# Arbitrary polygon from vertices\npoly = Polygon(LEFT, UP * 2, RIGHT, color=GREEN, fill_opacity=0.3)\n\n# Regular n-sided polygon\nhexagon = RegularPolygon(n=6, color=TEAL, fill_opacity=0.4)\n\n# Triangle (shorthand for RegularPolygon(n=3))\ntri = Triangle(color=YELLOW, fill_opacity=0.5)\n\n# Arc (portion of a circle)\narc = Arc(radius=2, start_angle=0, angle=PI / 2, color=BLUE)\n\n# Arc between two points\narc_between = ArcBetweenPoints(LEFT * 2, RIGHT * 2, angle=TAU / 4, color=RED)\n\n# Curved arrow (arc with tip)\ncurved_arrow = CurvedArrow(LEFT * 2, RIGHT * 2, color=ORANGE)\n```\n\n## Sectors and Annuli\n\n```python\n# Sector (pie slice)\nsector = Sector(outer_radius=2, start_angle=0, angle=PI / 3, fill_opacity=0.7, color=BLUE)\n\n# Annulus (ring)\nring = Annulus(inner_radius=1, outer_radius=2, fill_opacity=0.5, color=GREEN)\n\n# Annular sector (partial ring)\npartial_ring = AnnularSector(\n    inner_radius=1, outer_radius=2,\n    angle=PI / 2, start_angle=0,\n    fill_opacity=0.7, color=TEAL\n)\n\n# Cutout (punch holes in a shape)\nbackground = Square(side_length=4, fill_opacity=1, color=BLUE)\nhole = Circle(radius=0.5)\ncutout = Cutout(background, hole, fill_opacity=1, color=BLUE)\n```\n\nUse cases: pie charts, ring progress indicators, Venn diagrams with arcs, geometric proofs.\n\n## Positioning\n\n```python\nmob.move_to(ORIGIN)                        # center\nmob.move_to(UP * 2 + RIGHT)               # relative\nlabel.next_to(circle, DOWN, buff=0.3)     # next to another\ntitle.to_edge(UP, buff=0.5)               # screen edge (buff >= 0.5!)\nmob.to_corner(UL, buff=0.5)               # corner\n```\n\n## VGroup vs Group\n\n**VGroup** is for collections of shapes (VMobjects only — Circle, Square, Arrow, Line, MathTex):\n```python\nshapes = VGroup(circle, square, arrow)\nshapes.arrange(DOWN, buff=0.5)\nshapes.set_color(BLUE)\n```\n\n**Group** is for mixed collections (Text + shapes, or any Mobject types):\n```python\n# Text objects are Mobjects, not VMobjects — use Group when mixing\nlabeled_shape = Group(circle, Text(\"Label\").next_to(circle, DOWN))\nlabeled_shape.move_to(ORIGIN)\n\n# FadeOut everything on screen (may contain mixed types)\nself.play(FadeOut(Group(*self.mobjects)))\n```\n\n**Rule: if your group contains any `Text()` objects, use `Group`, not `VGroup`.** VGroup will raise a TypeError on Manim CE v0.20+. MathTex and Tex are VMobjects and work with VGroup.\n\nBoth support `arrange()`, `arrange_in_grid()`, `set_opacity()`, `shift()`, `scale()`, `move_to()`.\n\n## Styling\n\n```python\nmob.set_color(BLUE)\nmob.set_fill(RED, opacity=0.5)\nmob.set_stroke(WHITE, width=2)\nmob.set_opacity(0.4)\nmob.set_z_index(1)                         # layering\n```\n\n## Specialized Mobjects\n\n```python\nnl = NumberLine(x_range=[-3, 3, 1], length=8, include_numbers=True)\ntable = Table([[\"A\", \"B\"], [\"C\", \"D\"]], row_labels=[Text(\"R1\"), Text(\"R2\")])\ncode = Code(\"example.py\", tab_width=4, font_size=20, language=\"python\")\nhighlight = SurroundingRectangle(target, color=YELLOW, buff=0.2)\nbg = BackgroundRectangle(equation, fill_opacity=0.7, buff=0.2)\n```\n\n## Custom Mobjects\n\n```python\nclass NetworkNode(Group):\n    def __init__(self, label_text, color=BLUE, **kwargs):\n        super().__init__(**kwargs)\n        self.circle = Circle(radius=0.4, color=color, fill_opacity=0.3)\n        self.label = Text(label_text, font_size=20).move_to(self.circle)\n        self.add(self.circle, self.label)\n```\n\n## Matrix Mobjects\n\nDisplay matrices as grids of numbers or mobjects:\n\n```python\n# Integer matrix\nm = IntegerMatrix([[1, 2], [3, 4]])\n\n# Decimal matrix (control decimal places)\nm = DecimalMatrix([[1.5, 2.7], [3.1, 4.9]], element_to_mobject_config={\"num_decimal_places\": 2})\n\n# Mobject matrix (any mobject in each cell)\nm = MobjectMatrix([\n    [MathTex(r\"\\pi\"), MathTex(r\"e\")],\n    [MathTex(r\"\\phi\"), MathTex(r\"\\tau\")]\n])\n\n# Bracket types: \"(\" \"[\" \"|\" or \"\\\\{\"\nm = IntegerMatrix([[1, 0], [0, 1]], left_bracket=\"[\", right_bracket=\"]\")\n```\n\nUse cases: linear algebra, transformation matrices, system-of-equations coefficient display.\n\n## Constants\n\nDirections: `UP, DOWN, LEFT, RIGHT, ORIGIN, UL, UR, DL, DR`\nColors: `RED, BLUE, GREEN, YELLOW, WHITE, GRAY, ORANGE, PINK, PURPLE, TEAL, GOLD`\nFrame: `config.frame_width = 14.222, config.frame_height = 8.0`\n\n## SVGMobject — Import SVG Files\n\n```python\nlogo = SVGMobject(\"path/to/logo.svg\")\nlogo.set_color(WHITE).scale(0.5).to_corner(UR)\nself.play(FadeIn(logo))\n\n# SVG submobjects are individually animatable\nfor part in logo.submobjects:\n    self.play(part.animate.set_color(random_color()))\n```\n\n## ImageMobject — Display Images\n\n```python\nimg = ImageMobject(\"screenshot.png\")\nimg.set_height(3).to_edge(RIGHT)\nself.play(FadeIn(img))\n```\n\nNote: images cannot be animated with `.animate` (they're raster, not vector). Use `FadeIn`/`FadeOut` and `shift`/`scale` only.\n\n## Variable — Auto-Updating Display\n\n```python\nvar = Variable(0, Text(\"x\"), num_decimal_places=2)\nvar.move_to(ORIGIN)\nself.add(var)\n\n# Animate the value\nself.play(var.tracker.animate.set_value(5), run_time=2)\n# Display auto-updates: \"x = 5.00\"\n```\n\nCleaner than manual `DecimalNumber` + `add_updater` for simple labeled-value displays.\n\n## BulletedList\n\n```python\nbullets = BulletedList(\n    \"First key point\",\n    \"Second important fact\",\n    \"Third conclusion\",\n    font_size=28\n)\nbullets.to_edge(LEFT, buff=1.0)\nself.play(Write(bullets))\n\n# Highlight individual items\nself.play(bullets[1].animate.set_color(YELLOW))\n```\n\n## DashedLine and Angle Markers\n\n```python\n# Dashed line (asymptotes, construction lines)\ndashed = DashedLine(LEFT * 3, RIGHT * 3, color=SUBTLE, dash_length=0.15)\n\n# Angle marker between two lines\nline1 = Line(ORIGIN, RIGHT * 2)\nline2 = Line(ORIGIN, UP * 2 + RIGHT)\nangle = Angle(line1, line2, radius=0.5, color=YELLOW)\nangle_label = angle.get_value()  # returns the angle in radians\n\n# Right angle marker\nright_angle = RightAngle(line1, Line(ORIGIN, UP * 2), length=0.3, color=WHITE)\n```\n\n## Boolean Operations (CSG)\n\nCombine, subtract, or intersect 2D shapes:\n\n```python\ncircle = Circle(radius=1.5, color=BLUE, fill_opacity=0.5).shift(LEFT * 0.5)\nsquare = Square(side_length=2, color=RED, fill_opacity=0.5).shift(RIGHT * 0.5)\n\n# Union, Intersection, Difference, Exclusion\nunion = Union(circle, square, color=GREEN, fill_opacity=0.5)\nintersect = Intersection(circle, square, color=YELLOW, fill_opacity=0.5)\ndiff = Difference(circle, square, color=PURPLE, fill_opacity=0.5)\nexclude = Exclusion(circle, square, color=ORANGE, fill_opacity=0.5)\n```\n\nUse cases: Venn diagrams, set theory, geometric proofs, area calculations.\n\n## LabeledArrow / LabeledLine\n\n```python\n# Arrow with built-in label (auto-positioned)\narr = LabeledArrow(Text(\"force\", font_size=18), start=LEFT, end=RIGHT, color=RED)\n\n# Line with label\nline = LabeledLine(Text(\"d = 5m\", font_size=18), start=LEFT * 2, end=RIGHT * 2)\n```\n\nAuto-handles label positioning — cleaner than manual `Arrow` + `Text().next_to()`.\n\n## Text Color/Font/Style Per-Substring (t2c, t2f, t2s, t2w)\n\n```python\n# Color specific words (t2c = text-to-color)\ntext = Text(\n    \"Gradient descent minimizes the loss function\",\n    t2c={\"Gradient descent\": BLUE, \"loss function\": RED}\n)\n\n# Different fonts per word (t2f = text-to-font)\ntext = Text(\n    \"Use Menlo for code and Inter for prose\",\n    t2f={\"Menlo\": \"Menlo\", \"Inter\": \"Inter\"}\n)\n\n# Italic/slant per word (t2s = text-to-slant)\ntext = Text(\"Normal and italic text\", t2s={\"italic\": ITALIC})\n\n# Bold per word (t2w = text-to-weight)\ntext = Text(\"Normal and bold text\", t2w={\"bold\": BOLD})\n```\n\nThese are much cleaner than creating separate Text objects and grouping them.\n\n## Backstroke for Readability Over Backgrounds\n\nWhen text overlaps other content (graphs, diagrams, images), add a dark stroke behind it:\n\n```python\n# CE syntax:\nlabel.set_stroke(BLACK, width=5, background=True)\n\n# Apply to a group\nfor mob in labels:\n    mob.set_stroke(BLACK, width=4, background=True)\n```\n\nThis is how 3Blue1Brown keeps text readable over complex backgrounds without using BackgroundRectangle.\n\n## Complex Function Transforms\n\nApply complex functions to entire mobjects — transforms the plane:\n\n```python\nc_grid = ComplexPlane()\nmoving_grid = c_grid.copy()\nmoving_grid.prepare_for_nonlinear_transform()  # adds more sample points for smooth deformation\n\nself.play(\n    moving_grid.animate.apply_complex_function(lambda z: z**2),\n    run_time=5,\n)\n\n# Also works with R3->R3 functions:\nself.play(grid.animate.apply_function(\n    lambda p: [p[0] + 0.5 * math.sin(p[1]), p[1] + 0.5 * math.sin(p[0]), p[2]]\n), run_time=5)\n```\n\n**Critical:** Call `prepare_for_nonlinear_transform()` before applying nonlinear functions — without it, the grid has too few sample points and the deformation looks jagged.\n","isInternal":false,"tokens":2847,"sizeBytes":9739},{"name":"paper-explainer.md","path":"skills/manim-video/references/paper-explainer.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/references/paper-explainer.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Paper Explainer Workflow\n\nHow to turn a research paper into an animated explainer video.\n\n## Why animate a paper?\n\nA research paper is optimized for precision and completeness. A video is optimized for understanding and retention. The translation is NOT \"read the paper aloud with pictures\" — it's \"extract the core insight and make it feel obvious through visual storytelling.\"\n\nThe paper has one job: prove the claim is true. The video has a different job: make the viewer understand WHY the claim is true, and WHY it matters.\n\n## Who is watching?\n\nBefore anything, decide the audience:\n\n| Audience | Prerequisites | Pacing | Depth |\n|----------|--------------|--------|-------|\n| General public | None | Slow, many analogies | Intuition only, skip proofs |\n| Undergrad students | Basic math/CS | Medium, some formalism | Key equations, skip derivations |\n| Grad students / researchers | Domain knowledge | Faster, more notation | Full equations, sketch proofs |\n\nThis determines everything: vocabulary, pacing, which sections to animate, how much math to show.\n\n## The 5-minute template\n\nMost paper explainers fit this structure (scale times proportionally for longer videos):\n\n| Section | Duration | Purpose |\n|---------|----------|---------|\n| **Hook** | 0:00-0:30 | Surprising result or provocative question |\n| **Problem** | 0:30-1:30 | What was broken/missing before this paper |\n| **Key insight** | 1:30-3:00 | The core idea, explained visually |\n| **How it works** | 3:00-4:00 | Method/algorithm, simplified |\n| **Evidence** | 4:00-4:30 | Key result that proves it works |\n| **Implications** | 4:30-5:00 | Why it matters, what it enables |\n\n### What to skip\n\n- Related work survey → one sentence: \"Previous approaches did X, which had problem Y\"\n- Implementation details → skip unless they're the contribution\n- Ablation studies → show one chart at most\n- Proofs → show the key step, not the full proof\n- Hyperparameter tuning → skip entirely\n\n### What to expand\n\n- The core insight → this gets the most screen time\n- Geometric/visual intuition → if the paper has math, show what it MEANS\n- Before/after comparison → the most compelling evidence\n\n## Pre-code workflow\n\n### Gate 1: Narration script\n\nWrite the full narration before any code. Every sentence maps to a visual beat. If you can't write the narration, you don't understand the paper well enough to animate it.\n\n```markdown\n## Hook (30s)\n\"What if I told you that a model with 7 billion parameters can outperform\none with 70 billion — if you train it on the right data?\"\n\n## Problem (60s)\n\"The standard approach is to scale up. More parameters, more compute.\n[VISUAL: bar chart showing model sizes growing exponentially]\nBut Chinchilla showed us that most models are undertrained...\"\n```\n\n### Gate 2: Scene list\n\nAfter the narration, break it into scenes. Each scene is one Manim class.\n\n```markdown\nScene 1: Hook — surprising stat with animated counter\nScene 2: Problem — model size bar chart growing\nScene 3: Key insight — training data vs parameters, animated 2D plot\nScene 4: Method — pipeline diagram building left to right\nScene 5: Results — before/after comparison with animated bars\nScene 6: Closing — implications text\n```\n\n### Gate 3: Style constants\n\nBefore coding scenes, define the visual language:\n\n```python\n# style.py — import in every scene file\nBG = \"#0D1117\"\nPRIMARY = \"#58C4DD\"\nSECONDARY = \"#83C167\"\nACCENT = \"#FFFF00\"\nHIGHLIGHT = \"#FF6B6B\"\nMONO = \"Menlo\"\n\n# Color meanings for THIS paper\nMODEL_COLOR = PRIMARY      # \"the model\"\nDATA_COLOR = SECONDARY     # \"training data\"\nBASELINE_COLOR = HIGHLIGHT # \"previous approach\"\nRESULT_COLOR = ACCENT      # \"our result\"\n```\n\n## First-principles equation explanation\n\nWhen the paper has a key equation, don't just show it — build it from intuition:\n\n### The \"what would you do?\" pattern\n\n1. Pose the problem in plain language\n2. Ask what the simplest solution would be\n3. Show why it doesn't work (animate the failure)\n4. Introduce the paper's solution as the fix\n5. THEN show the equation — it now feels earned\n\n```python\n# Scene: Why we need attention (for a Transformer paper)\n# Step 1: \"How do we let each word look at every other word?\"\n# Step 2: Show naive approach (fully connected = O(n²) everything)\n# Step 3: Show it breaks (information overload, no selectivity)\n# Step 4: \"What if each word could CHOOSE which words to attend to?\"\n# Step 5: Show attention equation — Q, K, V now mean something\n```\n\n### Equation reveal strategy\n\n```python\n# Show equation dimmed first (full destination)\neq = MathTex(r\"Attention(Q,K,V) = softmax\\left(\\frac{QK^T}{\\sqrt{d_k}}\\right)V\")\neq.set_opacity(0.15)\nself.play(FadeIn(eq))\n\n# Highlight Q, K, V one at a time with color + label\nfor part, color, label_text in [\n    (r\"Q\", PRIMARY, \"Query: what am I looking for?\"),\n    (r\"K\", SECONDARY, \"Key: what do I contain?\"),\n    (r\"V\", ACCENT, \"Value: what do I output?\"),\n]:\n    eq.set_color_by_tex(part, color)\n    label = Text(label_text, font_size=18, color=color, font=MONO)\n    # position label, animate it, wait, then dim it\n```\n\n## Building architecture diagrams\n\n### The progressive build pattern\n\nDon't show the full architecture at once. Build it:\n\n1. First component appears alone → explain\n2. Arrow grows → \"this feeds into...\"\n3. Second component appears → explain\n4. Repeat until complete\n\n```python\n# Component factory\ndef make_box(label, color, width=2.0, height=0.8):\n    box = RoundedRectangle(corner_radius=0.1, width=width, height=height,\n                           color=color, fill_opacity=0.1, stroke_width=1.5)\n    text = Text(label, font_size=18, font=MONO, color=color).move_to(box)\n    return Group(box, text)\n\nencoder = make_box(\"Encoder\", PRIMARY)\ndecoder = make_box(\"Decoder\", SECONDARY).next_to(encoder, RIGHT, buff=1.5)\narrow = Arrow(encoder.get_right(), decoder.get_left(), color=DIM, stroke_width=1.5)\n\nself.play(FadeIn(encoder))\nself.wait(1)  # explain encoder\nself.play(GrowArrow(arrow))\nself.play(FadeIn(decoder))\nself.wait(1)  # explain decoder\n```\n\n### Data flow animation\n\nAfter building the diagram, show data moving through it:\n\n```python\n# Dot traveling along the pipeline\ndata_dot = Dot(color=ACCENT, radius=0.1).move_to(encoder)\nself.play(FadeIn(data_dot))\nself.play(MoveAlongPath(data_dot, arrow), run_time=1)\nself.play(data_dot.animate.move_to(decoder), run_time=0.5)\nself.play(Flash(data_dot.get_center(), color=ACCENT), run_time=0.3)\n```\n\n## Animating results\n\n### Bar chart comparison (most common)\n\n```python\n# Before/after bars\nbefore_data = [45, 52, 38, 61]\nafter_data = [78, 85, 72, 91]\nlabels = [\"Task A\", \"Task B\", \"Task C\", \"Task D\"]\n\nbefore_chart = BarChart(before_data, bar_names=labels,\n    y_range=[0, 100, 20], bar_colors=[HIGHLIGHT]*4).scale(0.6).shift(LEFT*3)\nafter_chart = BarChart(after_data, bar_names=labels,\n    y_range=[0, 100, 20], bar_colors=[SECONDARY]*4).scale(0.6).shift(RIGHT*3)\n\nbefore_label = Text(\"Baseline\", font_size=20, color=HIGHLIGHT, font=MONO)\nafter_label = Text(\"Ours\", font_size=20, color=SECONDARY, font=MONO)\n\n# Reveal baseline first, then ours (dramatic comparison)\nself.play(Create(before_chart), FadeIn(before_label))\nself.wait(1.5)\nself.play(Create(after_chart), FadeIn(after_label))\nself.wait(0.5)\n\n# Highlight the improvement\nimprovement = Text(\"+35% avg\", font_size=24, color=ACCENT, font=MONO)\nself.play(FadeIn(improvement))\n```\n\n### Training curve (for ML papers)\n\n```python\ntracker = ValueTracker(0)\ncurve = always_redraw(lambda: axes.plot(\n    lambda x: 1 - 0.8 * np.exp(-x / 3),\n    x_range=[0, tracker.get_value()], color=PRIMARY\n))\nepoch_label = always_redraw(lambda: Text(\n    f\"Epoch {int(tracker.get_value())}\", font_size=18, font=MONO\n).to_corner(UR))\n\nself.add(curve, epoch_label)\nself.play(tracker.animate.set_value(10), run_time=5, rate_func=linear)\n```\n\n## Domain-specific patterns\n\n### ML papers\n- Show data flow through the model (animated pipeline)\n- Training curves with `ValueTracker`\n- Attention heatmaps as colored grids\n- Embedding space as 2D scatter (PCA/t-SNE visualization)\n- Loss landscape as 3D surface with gradient descent dot\n\n### Physics/math papers\n- Use `LinearTransformationScene` for linear algebra\n- Vector fields with `ArrowVectorField` / `StreamLines`\n- Phase spaces with `NumberPlane` + trajectories\n- Wave equations with time-parameterized plots\n\n### Systems/architecture papers\n- Pipeline diagrams built progressively\n- `ShowPassingFlash` for data flow along arrows\n- `ZoomedScene` for zooming into components\n- Before/after latency/throughput comparisons\n\n## Common mistakes\n\n1. **Trying to cover the whole paper.** A 5-minute video can explain ONE core insight well. Covering everything means explaining nothing.\n2. **Reading the abstract as narration.** Academic writing is designed for readers, not listeners. Rewrite in conversational language.\n3. **Showing notation without meaning.** Never show a symbol without first showing what it represents visually.\n4. **Skipping the motivation.** Jumping straight to \"here's our method\" without showing why the problem matters. The Problem section is what makes the viewer care.\n5. **Identical pacing throughout.** The hook and key insight need the most visual energy. The method section can be faster. Evidence should land with impact (pause after showing the big number).\n","isInternal":false,"tokens":2375,"sizeBytes":9333},{"name":"production-quality.md","path":"skills/manim-video/references/production-quality.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/references/production-quality.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Production Quality Checklist\n\nStandards and checks for ensuring animation output is publication-ready.\n\n## Pre-Code Checklist\n\nBefore writing any Manim code:\n\n- [ ] Narration script written with visual beats marked\n- [ ] Scene list with purpose, duration, and layout for each\n- [ ] Color palette defined with meaning assignments (`PRIMARY` = main concept, etc.)\n- [ ] `MONO = \"Menlo\"` set as the font constant\n- [ ] Target resolution and aspect ratio decided\n\n## Text Quality\n\n### Overlap prevention\n\n```python\n# RULE: buff >= 0.5 for edge text\nlabel.to_edge(DOWN, buff=0.5)     # GOOD\nlabel.to_edge(DOWN, buff=0.3)     # BAD — may clip\n\n# RULE: FadeOut previous before adding new at same position\nself.play(ReplacementTransform(note1, note2))  # GOOD\nself.play(Write(note2))                          # BAD — overlaps note1\n\n# RULE: Reduce font size for dense scenes\n# When > 4 text elements visible, use font_size=20 not 28\n```\n\n### Width enforcement\n\nLong text strings overflow the frame:\n\n```python\n# RULE: Set max width for any text that might be long\ntext = Text(\"This is a potentially long description\", font_size=22, font=MONO)\nif text.width > config.frame_width - 1.0:\n    text.set_width(config.frame_width - 1.0)\n```\n\n### Font consistency\n\n```python\n# RULE: Define MONO once, use everywhere\nMONO = \"Menlo\"\n\n# WRONG: mixing fonts\nText(\"Title\", font=\"Helvetica\")\nText(\"Label\", font=\"Arial\")\nText(\"Code\", font=\"Courier\")\n\n# RIGHT: one font\nText(\"Title\", font=MONO, weight=BOLD, font_size=48)\nText(\"Label\", font=MONO, font_size=20)\nText(\"Code\", font=MONO, font_size=18)\n```\n\n## Spatial Layout\n\n### The coordinate budget\n\nThe visible frame is approximately 14.2 wide × 8.0 tall (default 16:9). With mandatory margins:\n\n```\nUsable area: x ∈ [-6.5, 6.5], y ∈ [-3.5, 3.5]\nTop title zone: y ∈ [2.5, 3.5]\nBottom note zone: y ∈ [-3.5, -2.5]\nMain content: y ∈ [-2.5, 2.5], x ∈ [-6.0, 6.0]\n```\n\n### Fill the frame\n\nEmpty scenes look unfinished. If the main content is small, add context:\n- A dimmed grid/axes behind the content\n- A title/subtitle at the top\n- A source citation at the bottom\n- Decorative geometry at low opacity\n\n### Maximum simultaneous elements\n\n**Hard limit: 6 actively visible elements.** Beyond that, the viewer can't track everything. If you need more:\n- Dim old elements to opacity 0.3\n- Remove elements that have served their purpose\n- Split into two scenes\n\n## Animation Quality\n\n### Variety audit\n\nCheck that no two consecutive scenes use the exact same:\n- Animation type (if Scene 3 uses Write for everything, Scene 4 should use FadeIn or Create)\n- Color emphasis (rotate through palette colors)\n- Layout (center, left-right, grid — alternate)\n- Pacing (if Scene 2 was slow and deliberate, Scene 3 can be faster)\n\n### Tempo curve\n\nA good video follows a tempo curve:\n\n```\nSlow ──→ Medium ──→ FAST (climax) ──→ Slow (conclusion)\n\nScene 1: Slow (introduction, setup)\nScene 2: Medium (building understanding)\nScene 3: Medium-Fast (core content, lots of animation)\nScene 4: FAST (montage of applications/results)\nScene 5: Slow (conclusion, key takeaway)\n```\n\n### Transition quality\n\nBetween scenes:\n- **Clean exit**: `self.play(FadeOut(Group(*self.mobjects)), run_time=0.5)`\n- **Brief pause**: `self.wait(0.3)` after fadeout, before next scene's first animation\n- **Never hard-cut**: always animate the transition\n\n## Color Quality\n\n### Dimming on dark backgrounds\n\nColors that look vibrant on white look muddy on dark backgrounds (#0D1117, #1C1C1C). Test your palette:\n\n```python\n# Colors that work well on dark backgrounds:\n# Bright and saturated: #58C4DD, #83C167, #FFFF00, #FF6B6B\n# Colors that DON'T work: #666666 (invisible), #2244AA (too dark)\n\n# RULE: Structural elements (axes, grids) at opacity 0.15\n# Context elements at 0.3-0.4\n# Primary elements at 1.0\n```\n\n### Color meaning consistency\n\nOnce a color is assigned a meaning, it keeps that meaning for the entire video:\n\n```python\n# If PRIMARY (#58C4DD) means \"the model\" in Scene 1,\n# it means \"the model\" in every scene.\n# Never reuse PRIMARY for a different concept later.\n```\n\n## Data Visualization Quality\n\n### Minimum requirements for charts\n\n- Axis labels on every axis\n- Y-axis range starts at 0 (or has a clear break indicator)\n- Bar/line colors match the legend\n- Numbers on notable data points (at least the maximum and the comparison point)\n\n### Animated counters\n\nWhen showing a number changing:\n```python\n# GOOD: DecimalNumber with smooth animation\ncounter = DecimalNumber(0, font_size=48, num_decimal_places=0, font=\"Menlo\")\nself.play(counter.animate.set_value(1000), run_time=3, rate_func=rush_from)\n\n# BAD: Text that jumps between values\n```\n\n## Pre-Render Checklist\n\nBefore running `manim -qh`:\n\n- [ ] All scenes render without errors at `-ql`\n- [ ] Preview stills at `-qm` for text-heavy scenes (check kerning)\n- [ ] Background color set in every scene (`self.camera.background_color = BG`)\n- [ ] `add_subcaption()` or `subcaption=` on every significant animation\n- [ ] No text smaller than font_size=18\n- [ ] No text using proportional fonts (use monospace)\n- [ ] buff >= 0.5 on all `.to_edge()` calls\n- [ ] Clean exit (FadeOut all) at end of every scene\n- [ ] `self.wait()` after every reveal\n- [ ] Color constants used (no hardcoded hex strings in scene code)\n- [ ] All scenes use the same quality flag (don't mix `-ql` and `-qh`)\n\n## Post-Render Checklist\n\nAfter stitching the final video:\n\n- [ ] Watch the complete video at 1x speed — does it feel rushed anywhere?\n- [ ] Is there a moment where two things animate simultaneously and it's confusing?\n- [ ] Does every text label have enough time to be read?\n- [ ] Are transitions between scenes smooth (no black frames, no jarring cuts)?\n- [ ] Is the audio in sync with the visuals (if using voiceover)?\n- [ ] Is the Gibbs-like \"first impression\" good? The first 5 seconds determine if someone keeps watching\n","isInternal":false,"tokens":1561,"sizeBytes":5904},{"name":"rendering.md","path":"skills/manim-video/references/rendering.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/references/rendering.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Rendering Reference\n\n## Prerequisites\n\n```bash\nmanim --version       # Manim CE\npdflatex --version    # LaTeX\nffmpeg -version       # ffmpeg\n```\n\n## CLI Reference\n\n```bash\nmanim -ql script.py Scene1 Scene2    # draft (480p 15fps)\nmanim -qm script.py Scene1           # medium (720p 30fps)\nmanim -qh script.py Scene1           # production (1080p 60fps)\nmanim -ql --format=png -s script.py Scene1  # preview still (last frame)\nmanim -ql --format=gif script.py Scene1     # GIF output\n```\n\n## Quality Presets\n\n| Flag | Resolution | FPS | Use case |\n|------|-----------|-----|----------|\n| `-ql` | 854x480 | 15 | Draft iteration (layout, timing) |\n| `-qm` | 1280x720 | 30 | Preview (use for text-heavy scenes) |\n| `-qh` | 1920x1080 | 60 | Production |\n\n**Text rendering quality:** `-ql` (480p15) produces noticeably poor text kerning and readability. For scenes with significant text, preview stills at `-qm` to catch issues invisible at 480p. Use `-ql` only for testing layout and animation timing.\n\n## Output Structure\n\n```\nmedia/videos/script/480p15/Scene1_Intro.mp4\nmedia/images/script/Scene1_Intro.png  (from -s flag)\n```\n\n## Stitching with ffmpeg\n\n```bash\ncat > concat.txt << 'EOF'\nfile 'media/videos/script/480p15/Scene1_Intro.mp4'\nfile 'media/videos/script/480p15/Scene2_Core.mp4'\nEOF\nffmpeg -y -f concat -safe 0 -i concat.txt -c copy final.mp4\n```\n\n## Add Voiceover\n\n```bash\n# Mux narration\nffmpeg -y -i final.mp4 -i narration.mp3 -c:v copy -c:a aac -b:a 192k -shortest final_narrated.mp4\n\n# Concat per-scene audio first\ncat > audio_concat.txt << 'EOF'\nfile 'audio/scene1.mp3'\nfile 'audio/scene2.mp3'\nEOF\nffmpeg -y -f concat -safe 0 -i audio_concat.txt -c copy full_narration.mp3\n```\n\n## Add Background Music\n\n```bash\nffmpeg -y -i final.mp4 -i music.mp3 \\\n  -filter_complex \"[1:a]volume=0.15[bg];[0:a][bg]amix=inputs=2:duration=shortest\" \\\n  -c:v copy final_with_music.mp4\n```\n\n## GIF Export\n\n```bash\nffmpeg -y -i scene.mp4 \\\n  -vf \"fps=15,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse\" \\\n  output.gif\n```\n\n## Aspect Ratios\n\n```bash\nmanim -ql --resolution 1080,1920 script.py Scene  # 9:16 vertical\nmanim -ql --resolution 1080,1080 script.py Scene  # 1:1 square\n```\n\n## Render Workflow\n\n1. Draft render all scenes at `-ql`\n2. Preview stills at key moments (`-s`)\n3. Fix and re-render only broken scenes\n4. Stitch with ffmpeg\n5. Review stitched output\n6. Production render at `-qh`\n7. Re-stitch + add audio\n\n## manim.cfg — Project Configuration\n\nCreate `manim.cfg` in the project directory for per-project defaults:\n\n```ini\n[CLI]\nquality = low_quality\npreview = True\nmedia_dir = ./media\n\n[renderer]\nbackground_color = #0D1117\n\n[tex]\ntex_template_file = custom_template.tex\n```\n\nThis eliminates repetitive CLI flags and `self.camera.background_color` in every scene.\n\n## Sections — Chapter Markers\n\nMark sections within a scene for organized output:\n\n```python\nclass LongVideo(Scene):\n    def construct(self):\n        self.next_section(\"Introduction\")\n        # ... intro content ...\n\n        self.next_section(\"Main Concept\")\n        # ... main content ...\n\n        self.next_section(\"Conclusion\")\n        # ... closing ...\n```\n\nRender individual sections: `manim --save_sections script.py LongVideo`\nThis outputs separate video files per section — useful for long videos where you want to re-render only one part.\n\n## manim-voiceover Plugin (Recommended for Narrated Videos)\n\nThe official `manim-voiceover` plugin integrates TTS directly into scene code, auto-syncing animation duration to voiceover length. This is significantly cleaner than the manual ffmpeg muxing approach above.\n\n### Installation\n\n```bash\npip install \"manim-voiceover[elevenlabs]\"\n# Or for free/local TTS:\npip install \"manim-voiceover[gtts]\"    # Google TTS (free, lower quality)\npip install \"manim-voiceover[azure]\"   # Azure Cognitive Services\n```\n\n### Usage\n\n```python\nfrom manim import *\nfrom manim_voiceover import VoiceoverScene\nfrom manim_voiceover.services.elevenlabs import ElevenLabsService\n\nclass NarratedScene(VoiceoverScene):\n    def construct(self):\n        self.set_speech_service(ElevenLabsService(\n            voice_name=\"Alice\",\n            model_id=\"eleven_multilingual_v2\"\n        ))\n\n        # Voiceover auto-controls scene duration\n        with self.voiceover(text=\"Here is a circle being drawn.\") as tracker:\n            self.play(Create(Circle()), run_time=tracker.duration)\n\n        with self.voiceover(text=\"Now let's transform it into a square.\") as tracker:\n            self.play(Transform(circle, Square()), run_time=tracker.duration)\n```\n\n### Key Features\n\n- `tracker.duration` — total voiceover duration in seconds\n- `tracker.time_until_bookmark(\"mark1\")` — sync specific animations to specific words\n- Auto-generates subtitle `.srt` files\n- Caches audio locally — re-renders don't re-generate TTS\n- Works with: ElevenLabs, Azure, Google TTS, pyttsx3 (offline), and custom services\n\n### Bookmarks for Precise Sync\n\n```python\nwith self.voiceover(text='This is a <bookmark mark=\"circle\"/>circle.') as tracker:\n    self.wait_until_bookmark(\"circle\")\n    self.play(Create(Circle()), run_time=tracker.time_until_bookmark(\"circle\", limit=1))\n```\n\nThis is the recommended approach for any video with narration. The manual ffmpeg muxing workflow above is still useful for adding background music or post-production audio mixing.\n","isInternal":false,"tokens":1430,"sizeBytes":5375},{"name":"scene-planning.md","path":"skills/manim-video/references/scene-planning.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/references/scene-planning.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Scene Planning Reference\n\n## Narrative Arc Structures\n\n### Discovery Arc (most common)\n1. Hook -- pose a question or surprising result\n2. Intuition -- build visual understanding\n3. Formalize -- introduce the equation/algorithm\n4. Reveal -- the \"aha moment\"\n5. Extend -- implications or generalizations\n\n### Problem-Solution Arc\n1. Problem -- what's broken\n2. Failed attempt -- obvious approach fails\n3. Key insight -- the idea that works\n4. Solution -- implement it\n5. Result -- show improvement\n\n### Comparison Arc\n1. Setup -- introduce two approaches\n2. Approach A -- how it works\n3. Approach B -- how it works\n4. Contrast -- differences\n5. Verdict -- which is better\n\n### Build-Up Arc (architecture/systems)\n1. Component A -- first piece\n2. Component B -- second piece\n3. Connection -- how they interact\n4. Scale -- add more pieces\n5. Full picture -- zoom out\n\n## Scene Transitions\n\n### Clean Break (default)\n```python\nself.play(FadeOut(Group(*self.mobjects)), run_time=0.5)\nself.wait(0.3)\n```\n\n### Carry-Forward\nKeep one element, fade the rest. Next scene starts with it still on screen.\n\n### Transform Bridge\nEnd scene with a shape, start next scene by transforming it.\n\n## Cross-Scene Consistency\n\n```python\n# Shared constants at file top\nBG = \"#1C1C1C\"\nPRIMARY = \"#58C4DD\"\nSECONDARY = \"#83C167\"\nACCENT = \"#FFFF00\"\nTITLE_SIZE = 48\nBODY_SIZE = 30\nLABEL_SIZE = 24\nFAST = 0.8; NORMAL = 1.5; SLOW = 2.5\n```\n\n## Scene Checklist\n\n- [ ] Background color set\n- [ ] Subcaptions on every animation\n- [ ] `self.wait()` after every reveal\n- [ ] Text buff >= 0.5 for edge positioning\n- [ ] No text overlap\n- [ ] Color constants used (not hardcoded)\n- [ ] Opacity layering applied\n- [ ] Clean exit at scene end\n- [ ] No more than 5-6 elements visible at once\n\n## Duration Estimation\n\n| Content | Duration |\n|---------|----------|\n| Title card | 3-5s |\n| Concept introduction | 10-20s |\n| Equation reveal | 15-25s |\n| Algorithm step | 5-10s |\n| Data comparison | 10-15s |\n| \"Aha moment\" | 15-30s |\n| Conclusion | 5-10s |\n\n## Planning Template\n\n```markdown\n# [Video Title]\n\n## Overview\n- **Topic**: [Core concept]\n- **Hook**: [Opening question]\n- **Aha moment**: [Key insight]\n- **Target audience**: [Prerequisites]\n- **Length**: [seconds/minutes]\n- **Resolution**: 480p (draft) / 1080p (final)\n\n## Color Palette\n- Background: #1C1C1C\n- Primary: #58C4DD -- [purpose]\n- Secondary: #83C167 -- [purpose]\n- Accent: #FFFF00 -- [purpose]\n\n## Arc: [Discovery / Problem-Solution / Comparison / Build-Up]\n\n## Scene 1: [Name] (~Ns)\n**Purpose**: [one sentence]\n**Layout**: [FULL_CENTER / LEFT_RIGHT / GRID / PROGRESSIVE]\n\n### Visual elements\n- [Mobject: type, position, color]\n\n### Animation sequence\n1. [Animation] -- [what it reveals] (~Ns)\n\n### Subtitle\n\"[text]\"\n```\n","isInternal":false,"tokens":779,"sizeBytes":2751},{"name":"troubleshooting.md","path":"skills/manim-video/references/troubleshooting.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/references/troubleshooting.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Troubleshooting\n\n## LaTeX Errors\n\n**Missing raw string** (the #1 error):\n```python\n# WRONG: MathTex(\"\\\\frac{1}{2}\")  -- \\\\f is form-feed\n# RIGHT: MathTex(r\"\\frac{1}{2}\")\n```\n\n**Unbalanced braces**: `MathTex(r\"\\frac{1}{2\")` -- missing closing brace.\n\n**LaTeX not installed**: `which pdflatex` -- install texlive-full or mactex.\n\n**Missing package**: Add to preamble:\n```python\ntex_template = TexTemplate()\ntex_template.add_to_preamble(r\"\\usepackage{mathrsfs}\")\nMathTex(r\"\\mathscr{L}\", tex_template=tex_template)\n```\n\n## VGroup TypeError\n\n**Error:** `TypeError: Only values of type VMobject can be added as submobjects of VGroup`\n\n**Cause:** `Text()` objects are `Mobject`, not `VMobject`. Mixing `Text` with shapes in a `VGroup` fails on Manim CE v0.20+.\n\n```python\n# WRONG: Text is not a VMobject\ngroup = VGroup(circle, Text(\"Label\"))\n\n# RIGHT: use Group for mixed types\ngroup = Group(circle, Text(\"Label\"))\n\n# RIGHT: VGroup is fine for shapes-only\nshapes = VGroup(circle, square, arrow)\n\n# RIGHT: MathTex IS a VMobject — VGroup works\nequations = VGroup(MathTex(r\"a\"), MathTex(r\"b\"))\n```\n\n**Rule:** If the group contains any `Text()`, use `Group`. If it's all shapes or all `MathTex`, `VGroup` is fine.\n\n**FadeOut everything:** Always use `Group(*self.mobjects)`, not `VGroup(*self.mobjects)`:\n```python\nself.play(FadeOut(Group(*self.mobjects)))  # safe for mixed types\n```\n\n## Group save_state() / restore() Not Supported\n\n**Error:** `NotImplementedError: Please override in a child class.`\n\n**Cause:** `Group.save_state()` and `Group.restore()` are not implemented in Manim CE v0.20+. Only `VGroup` and individual `Mobject` subclasses support save/restore.\n\n```python\n# WRONG: Group doesn't support save_state\ngroup = Group(circle, Text(\"label\"))\ngroup.save_state()  # NotImplementedError!\n\n# RIGHT: use FadeIn with shift/scale instead of save_state/restore\nself.play(FadeIn(group, shift=UP * 0.3, scale=0.8))\n\n# RIGHT: or save/restore on individual VMobjects\ncircle.save_state()\nself.play(circle.animate.shift(RIGHT))\nself.play(Restore(circle))\n```\n\n## letter_spacing Is Not a Valid Parameter\n\n**Error:** `TypeError: Mobject.__init__() got an unexpected keyword argument 'letter_spacing'`\n\n**Cause:** `Text()` does not accept `letter_spacing`. Manim uses Pango for text rendering and does not expose kerning controls on `Text()`.\n\n```python\n# WRONG\nText(\"HERMES\", letter_spacing=6)\n\n# RIGHT: use MarkupText with Pango attributes for spacing control\nMarkupText('<span letter_spacing=\"6000\">HERMES</span>', font_size=18)\n# Note: Pango letter_spacing is in 1/1024 of a point\n```\n\n## Animation Errors\n\n**Invisible animation** -- mobject never added:\n```python\n# WRONG: circle = Circle(); self.play(circle.animate.set_color(RED))\n# RIGHT: self.play(Create(circle)); self.play(circle.animate.set_color(RED))\n```\n\n**Transform confusion** -- after Transform(A, B), A is on screen, B is not. Use ReplacementTransform if you want B.\n\n**Duplicate animation** -- same mobject twice in one play():\n```python\n# WRONG: self.play(c.animate.shift(RIGHT), c.animate.set_color(RED))\n# RIGHT: self.play(c.animate.shift(RIGHT).set_color(RED))\n```\n\n**Updater fights animation**:\n```python\nmob.suspend_updating()\nself.play(mob.animate.shift(RIGHT))\nmob.resume_updating()\n```\n\n## Rendering Issues\n\n**Blurry output**: Using -ql (480p). Switch to -qm/-qh for final.\n\n**Slow render**: Use -ql during development. Reduce Surface resolution. Shorter self.wait().\n\n**Stale output**: `manim -ql --disable_caching script.py Scene`\n\n**ffmpeg concat fails**: All clips must match resolution/FPS/codec.\n\n## Common Mistakes\n\n**Text clips at edge**: `buff >= 0.5` for `.to_edge()`\n\n**Overlapping text**: Use `ReplacementTransform(old, new)`, not `Write(new)` on top.\n\n**Too crowded**: Max 5-6 elements visible. Split into scenes or use opacity layering.\n\n**No breathing room**: `self.wait(1.5)` minimum after reveals, `self.wait(2.0)` for key moments.\n\n**Missing background color**: Set `self.camera.background_color = BG` in every scene.\n\n## Debugging Strategy\n\n1. Render a still: `manim -ql -s script.py Scene` -- instant layout check\n2. Isolate the broken scene -- render only that one\n3. Replace `self.play()` with `self.add()` to see final state instantly\n4. Print positions: `print(mob.get_center())`\n5. Clear cache: delete `media/` directory\n","isInternal":false,"tokens":1139,"sizeBytes":4319},{"name":"updaters-and-trackers.md","path":"skills/manim-video/references/updaters-and-trackers.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/references/updaters-and-trackers.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Updaters and Value Trackers\n\n## The problem updaters solve\n\nNormal animations are discrete: `self.play()` goes from state A to state B. But what if you need continuous relationships — a label that always hovers above a moving dot, or a line that always connects two points?\n\nWithout updaters, you'd manually reposition every dependent object before every `self.play()`. Five animations that move a dot means five manual repositioning calls for the label. Miss one and it freezes in the wrong spot.\n\nUpdaters let you declare a relationship ONCE. Manim calls the updater function EVERY FRAME (15-60 fps depending on quality) to enforce that relationship, no matter what else is happening.\n\n## ValueTracker: an invisible steering wheel\n\nA ValueTracker is an invisible Mobject that holds a single float. It never appears on screen. It exists so you can ANIMATE it while other objects REACT to its value.\n\nThink of it as a slider: drag the slider from 0 to 5, and every object wired to it responds in real time.\n\n```python\ntracker = ValueTracker(0)        # invisible, stores 0.0\ntracker.get_value()              # read: 0.0\ntracker.set_value(5)             # write: jump to 5.0 instantly\ntracker.animate.set_value(5)     # animate: smoothly interpolate to 5.0\n```\n\n### The three-step pattern\n\nEvery ValueTracker usage follows this:\n\n1. **Create the tracker** (the invisible slider)\n2. **Create visible objects that READ the tracker** via updaters\n3. **Animate the tracker** — all dependents update automatically\n\n```python\n# Step 1: Create tracker\nx_tracker = ValueTracker(1)\n\n# Step 2: Create dependent objects\ndot = always_redraw(lambda: Dot(axes.c2p(x_tracker.get_value(), 0), color=YELLOW))\nv_line = always_redraw(lambda: axes.get_vertical_line(\n    axes.c2p(x_tracker.get_value(), func(x_tracker.get_value())), color=BLUE\n))\nlabel = always_redraw(lambda: DecimalNumber(x_tracker.get_value(), font_size=24)\n    .next_to(dot, UP))\n\nself.add(dot, v_line, label)\n\n# Step 3: Animate the tracker — everything follows\nself.play(x_tracker.animate.set_value(5), run_time=3)\n```\n\n## Types of updaters\n\n### Lambda updater (most common)\n\nRuns a function every frame, passing the mobject itself:\n\n```python\n# Label always stays above the dot\nlabel.add_updater(lambda m: m.next_to(dot, UP, buff=0.2))\n\n# Line always connects two points\nline.add_updater(lambda m: m.put_start_and_end_on(\n    point_a.get_center(), point_b.get_center()\n))\n```\n\n### Time-based updater (with dt)\n\nThe second argument `dt` is the time since the last frame (~0.017s at 60fps):\n\n```python\n# Continuous rotation\nsquare.add_updater(lambda m, dt: m.rotate(0.5 * dt))\n\n# Continuous rightward drift\ndot.add_updater(lambda m, dt: m.shift(RIGHT * 0.3 * dt))\n\n# Oscillation\ndot.add_updater(lambda m, dt: m.move_to(\n    axes.c2p(m.get_center()[0], np.sin(self.time))\n))\n```\n\nUse `dt` updaters for physics simulations, continuous motion, and time-dependent effects.\n\n### always_redraw: full rebuild every frame\n\nCreates a new mobject from scratch each frame. More expensive than `add_updater` but handles cases where the mobject's structure changes (not just position/color):\n\n```python\n# Brace that follows a resizing square\nbrace = always_redraw(Brace, square, UP)\n\n# Area under curve that updates as function changes\narea = always_redraw(lambda: axes.get_area(\n    graph, x_range=[0, x_tracker.get_value()], color=BLUE, opacity=0.3\n))\n\n# Label that reconstructs its text\ncounter = always_redraw(lambda: Text(\n    f\"n = {int(x_tracker.get_value())}\", font_size=24, font=\"Menlo\"\n).to_corner(UR))\n```\n\n**When to use which:**\n- `add_updater` — position, color, opacity changes (cheap, preferred)\n- `always_redraw` — when the shape/structure itself changes (expensive, use sparingly)\n\n## DecimalNumber: showing live values\n\n```python\n# Counter that tracks a ValueTracker\ntracker = ValueTracker(0)\nnumber = DecimalNumber(0, font_size=48, num_decimal_places=1, color=PRIMARY)\nnumber.add_updater(lambda m: m.set_value(tracker.get_value()))\nnumber.add_updater(lambda m: m.next_to(dot, RIGHT, buff=0.3))\n\nself.add(number)\nself.play(tracker.animate.set_value(100), run_time=3)\n```\n\n### Variable: the labeled version\n\n```python\nvar = Variable(0, Text(\"x\", font_size=24, font=\"Menlo\"), num_decimal_places=2)\nself.add(var)\nself.play(var.tracker.animate.set_value(PI), run_time=2)\n# Displays: x = 3.14\n```\n\n## Removing updaters\n\n```python\n# Remove all updaters\nmobject.clear_updaters()\n\n# Suspend temporarily (during an animation that would fight the updater)\nmobject.suspend_updating()\nself.play(mobject.animate.shift(RIGHT))\nmobject.resume_updating()\n\n# Remove specific updater (if you stored a reference)\ndef my_updater(m):\n    m.next_to(dot, UP)\nlabel.add_updater(my_updater)\n# ... later ...\nlabel.remove_updater(my_updater)\n```\n\n## Animation-based updaters\n\n### UpdateFromFunc / UpdateFromAlphaFunc\n\nThese are ANIMATIONS (passed to `self.play`), not persistent updaters:\n\n```python\n# Call a function on each frame of the animation\nself.play(UpdateFromFunc(mobject, lambda m: m.next_to(moving_target, UP)), run_time=3)\n\n# With alpha (0 to 1) — useful for custom interpolation\nself.play(UpdateFromAlphaFunc(circle, lambda m, a: m.set_fill(opacity=a)), run_time=2)\n```\n\n### turn_animation_into_updater\n\nConvert a one-shot animation into a continuous updater:\n\n```python\nfrom manim import turn_animation_into_updater\n\n# This would normally play once — now it loops forever\nturn_animation_into_updater(Rotating(gear, rate=PI/4))\nself.add(gear)\nself.wait(5)  # gear rotates for 5 seconds\n```\n\n## Practical patterns\n\n### Pattern 1: Dot tracing a function\n\n```python\ntracker = ValueTracker(0)\ngraph = axes.plot(np.sin, x_range=[0, 2*PI], color=PRIMARY)\ndot = always_redraw(lambda: Dot(\n    axes.c2p(tracker.get_value(), np.sin(tracker.get_value())),\n    color=YELLOW\n))\ntangent = always_redraw(lambda: axes.get_secant_slope_group(\n    x=tracker.get_value(), graph=graph, dx=0.01,\n    secant_line_color=HIGHLIGHT, secant_line_length=3\n))\n\nself.add(graph, dot, tangent)\nself.play(tracker.animate.set_value(2*PI), run_time=6, rate_func=linear)\n```\n\n### Pattern 2: Live area under curve\n\n```python\ntracker = ValueTracker(0.5)\narea = always_redraw(lambda: axes.get_area(\n    graph, x_range=[0, tracker.get_value()],\n    color=PRIMARY, opacity=0.3\n))\narea_label = always_redraw(lambda: DecimalNumber(\n    # Numerical integration\n    sum(func(x) * 0.01 for x in np.arange(0, tracker.get_value(), 0.01)),\n    font_size=24\n).next_to(axes, RIGHT))\n\nself.add(area, area_label)\nself.play(tracker.animate.set_value(4), run_time=5)\n```\n\n### Pattern 3: Connected diagram\n\n```python\n# Nodes that can be moved, with edges that auto-follow\nnode_a = Dot(LEFT * 2, color=PRIMARY)\nnode_b = Dot(RIGHT * 2, color=SECONDARY)\nedge = Line().add_updater(lambda m: m.put_start_and_end_on(\n    node_a.get_center(), node_b.get_center()\n))\nlabel = Text(\"edge\", font_size=18, font=\"Menlo\").add_updater(\n    lambda m: m.move_to(edge.get_center() + UP * 0.3)\n)\n\nself.add(node_a, node_b, edge, label)\nself.play(node_a.animate.shift(UP * 2), run_time=2)\nself.play(node_b.animate.shift(DOWN + RIGHT), run_time=2)\n# Edge and label follow automatically\n```\n\n### Pattern 4: Parameter exploration\n\n```python\n# Explore how a parameter changes a curve\na_tracker = ValueTracker(1)\ncurve = always_redraw(lambda: axes.plot(\n    lambda x: a_tracker.get_value() * np.sin(x),\n    x_range=[0, 2*PI], color=PRIMARY\n))\nparam_label = always_redraw(lambda: Text(\n    f\"a = {a_tracker.get_value():.1f}\", font_size=24, font=\"Menlo\"\n).to_corner(UR))\n\nself.add(curve, param_label)\nself.play(a_tracker.animate.set_value(3), run_time=3)\nself.play(a_tracker.animate.set_value(0.5), run_time=2)\nself.play(a_tracker.animate.set_value(1), run_time=1)\n```\n\n## Common mistakes\n\n1. **Updater fights animation:** If a mobject has an updater that sets its position, and you try to animate it elsewhere, the updater wins every frame. Suspend updating first.\n\n2. **always_redraw for simple moves:** If you only need to reposition, use `add_updater`. `always_redraw` reconstructs the entire mobject every frame — expensive and unnecessary for position tracking.\n\n3. **Forgetting to add to scene:** Updaters only run on mobjects that are in the scene. `always_redraw` creates the mobject but you still need `self.add()`.\n\n4. **Updater creates new mobjects without cleanup:** If your updater creates Text objects every frame, they accumulate. Use `always_redraw` (which handles cleanup) or update properties in-place.\n","isInternal":false,"tokens":2224,"sizeBytes":8500},{"name":"visual-design.md","path":"skills/manim-video/references/visual-design.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/references/visual-design.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# Visual Design Principles\n\n## 12 Core Principles\n\n1. **Geometry Before Algebra** — Show the shape first, the equation second.\n2. **Opacity Layering** — PRIMARY=1.0, CONTEXT=0.4, GRID=0.15. Direct attention through brightness.\n3. **One New Idea Per Scene** — Each scene introduces exactly one concept.\n4. **Spatial Consistency** — Same concept occupies the same screen region throughout.\n5. **Color = Meaning** — Assign colors to concepts, not mobjects. If velocity is blue, it stays blue.\n6. **Progressive Disclosure** — Show simplest version first, add complexity incrementally.\n7. **Transform, Don't Replace** — Use Transform/ReplacementTransform to show connections.\n8. **Breathing Room** — `self.wait(1.5)` minimum after showing something new.\n9. **Visual Weight Balance** — Don't cluster everything on one side.\n10. **Consistent Motion Vocabulary** — Pick a small set of animation types and reuse them.\n11. **Dark Background, Light Content** — #1C1C1C to #2D2B55 backgrounds maximize contrast.\n12. **Intentional Empty Space** — Leave at least 15% of the frame empty.\n\n## Layout Templates\n\n### FULL_CENTER\nOne main element centered, title above, note below.\nBest for: single equations, single diagrams, title cards.\n\n### LEFT_RIGHT\nTwo elements side by side at x=-3.5 and x=3.5.\nBest for: equation + visual, before/after, comparison.\n\n### TOP_BOTTOM\nMain element at y=1.5, supporting content at y=-1.5.\nBest for: concept + examples, theorem + cases.\n\n### GRID\nMultiple elements via `arrange_in_grid()`.\nBest for: comparison matrices, multi-step processes.\n\n### PROGRESSIVE\nElements appear one at a time, arranged DOWN with aligned_edge=LEFT.\nBest for: algorithms, proofs, step-by-step processes.\n\n### ANNOTATED_DIAGRAM\nCentral diagram with floating labels connected by arrows.\nBest for: architecture diagrams, annotated figures.\n\n## Color Palettes\n\n### Classic 3B1B\n```python\nBG=\"#1C1C1C\"; PRIMARY=BLUE; SECONDARY=GREEN; ACCENT=YELLOW; HIGHLIGHT=RED\n```\n\n### Warm Academic\n```python\nBG=\"#2D2B55\"; PRIMARY=\"#FF6B6B\"; SECONDARY=\"#FFD93D\"; ACCENT=\"#6BCB77\"\n```\n\n### Neon Tech\n```python\nBG=\"#0A0A0A\"; PRIMARY=\"#00F5FF\"; SECONDARY=\"#FF00FF\"; ACCENT=\"#39FF14\"\n```\n\n## Font Selection\n\n**Use monospace fonts for all text.** Manim's Pango text renderer produces broken kerning with proportional fonts (Helvetica, Inter, SF Pro, Arial) at all sizes and resolutions. Characters overlap and spacing is inconsistent. This is a fundamental Pango limitation, not a Manim bug.\n\nMonospace fonts have fixed character widths — zero kerning issues by design.\n\n### Recommended Fonts\n\n| Use case | Font | Fallback |\n|----------|------|----------|\n| **All text (default)** | `\"Menlo\"` | `\"Courier New\"`, `\"DejaVu Sans Mono\"` |\n| Code, labels | `\"JetBrains Mono\"`, `\"SF Mono\"` | `\"Menlo\"` |\n| Math | Use `MathTex` (renders via LaTeX, not Pango) | — |\n\n```python\nMONO = \"Menlo\"  # define once at top of file\n\ntitle = Text(\"Fourier Series\", font_size=48, color=PRIMARY, weight=BOLD, font=MONO)\nlabel = Text(\"n=1: (4/pi) sin(x)\", font_size=20, color=BLUE, font=MONO)\nnote = Text(\"Convergence at discontinuities\", font_size=18, color=DIM, font=MONO)\n\n# Math — always use MathTex, not Text\nequation = MathTex(r\"\\nabla L = \\frac{\\partial L}{\\partial w}\")\n```\n\n### When Proportional Fonts Are Acceptable\n\nLarge title text (font_size >= 48) with short strings (1-3 words) can use proportional fonts without visible kerning issues. For anything else — labels, descriptions, multi-word text, small sizes — use monospace.\n\n### Font Availability\n\n- **macOS**: Menlo (pre-installed), SF Mono\n- **Linux**: DejaVu Sans Mono (pre-installed), Liberation Mono\n- **Cross-platform**: JetBrains Mono (install from jetbrains.com)\n\n`\"Menlo\"` is the safest default — pre-installed on macOS, and Linux systems fall back to DejaVu Sans Mono.\n\n### Fine-Grained Text Control\n\n`Text()` does not support `letter_spacing` or kerning parameters. For fine control, use `MarkupText` with Pango attributes:\n\n```python\n# Letter spacing (Pango units: 1/1024 of a point)\nMarkupText('<span letter_spacing=\"6000\">HERMES</span>', font_size=18, font=\"Menlo\")\n\n# Bold specific words\nMarkupText('This is <b>important</b>', font_size=24, font=\"Menlo\")\n\n# Color specific words\nMarkupText('Red <span foreground=\"#FF6B6B\">warning</span>', font_size=24, font=\"Menlo\")\n```\n\n### Minimum Font Size\n\n`font_size=18` is the minimum for readable text at any resolution. Below 18, characters become blurry at `-ql` and barely readable even at `-qh`.\n\n## Visual Hierarchy Checklist\n\nFor every frame:\n1. What is the ONE thing to look at? (brightest/largest)\n2. What is context? (dimmed to 0.3-0.4)\n3. What is structural? (dimmed to 0.15)\n4. Enough empty space? (>15%)\n5. All text readable at phone size?\n","isInternal":false,"tokens":1269,"sizeBytes":4762},{"name":"SKILL.md","path":"skills/manim-video/SKILL.md","rawUrl":"https://raw.githubusercontent.com/browser-use/video-use/HEAD/skills/manim-video/SKILL.md","title":"manim-video","category":"anthropic-skill","format":"markdown","content":"---\nname: manim-video\ndescription: \"Production pipeline for mathematical and technical animations using Manim Community Edition. Creates 3Blue1Brown-style explainer videos, algorithm visualizations, equation derivations, architecture diagrams, and data stories. Use when users request: animated explanations, math animations, concept visualizations, algorithm walkthroughs, technical explainers, 3Blue1Brown style videos, or any programmatic animation with geometric/mathematical content.\"\nversion: 1.0.0\n---\n\n# Manim Video Production Pipeline\n\n## Creative Standard\n\nThis is educational cinema. Every frame teaches. Every animation reveals structure.\n\n**Before writing a single line of code**, articulate the narrative arc. What misconception does this correct? What is the \"aha moment\"? What visual story takes the viewer from confusion to understanding? The user's prompt is a starting point — interpret it with pedagogical ambition.\n\n**Geometry before algebra.** Show the shape first, the equation second. Visual memory encodes faster than symbolic memory. When the viewer sees the geometric pattern before the formula, the equation feels earned.\n\n**First-render excellence is non-negotiable.** The output must be visually clear and aesthetically cohesive without revision rounds. If something looks cluttered, poorly timed, or like \"AI-generated slides,\" it is wrong.\n\n**Opacity layering directs attention.** Never show everything at full brightness. Primary elements at 1.0, contextual elements at 0.4, structural elements (axes, grids) at 0.15. The brain processes visual salience in layers.\n\n**Breathing room.** Every animation needs `self.wait()` after it. The viewer needs time to absorb what just appeared. Never rush from one animation to the next. A 2-second pause after a key reveal is never wasted.\n\n**Cohesive visual language.** All scenes share a color palette, consistent typography sizing, matching animation speeds. A technically correct video where every scene uses random different colors is an aesthetic failure.\n\n## Prerequisites\n\nRun `scripts/setup.sh` to verify all dependencies. Requires: Python 3.10+, Manim Community Edition v0.20+ (`pip install manim`), LaTeX (`texlive-full` on Linux, `mactex` on macOS), and ffmpeg. Reference docs tested against Manim CE v0.20.1.\n\n## Modes\n\n| Mode | Input | Output | Reference |\n|------|-------|--------|-----------|\n| **Concept explainer** | Topic/concept | Animated explanation with geometric intuition | `references/scene-planning.md` |\n| **Equation derivation** | Math expressions | Step-by-step animated proof | `references/equations.md` |\n| **Algorithm visualization** | Algorithm description | Step-by-step execution with data structures | `references/graphs-and-data.md` |\n| **Data story** | Data/metrics | Animated charts, comparisons, counters | `references/graphs-and-data.md` |\n| **Architecture diagram** | System description | Components building up with connections | `references/mobjects.md` |\n| **Paper explainer** | Research paper | Key findings and methods animated | `references/scene-planning.md` |\n| **3D visualization** | 3D concept | Rotating surfaces, parametric curves, spatial geometry | `references/camera-and-3d.md` |\n\n## Stack\n\nSingle Python script per project. No browser, no Node.js, no GPU required.\n\n| Layer | Tool | Purpose |\n|-------|------|---------|\n| Core | Manim Community Edition | Scene rendering, animation engine |\n| Math | LaTeX (texlive/MiKTeX) | Equation rendering via `MathTex` |\n| Video I/O | ffmpeg | Scene stitching, format conversion, audio muxing |\n| TTS | ElevenLabs / Qwen3-TTS (optional) | Narration voiceover |\n\n## Pipeline\n\n```\nPLAN --> CODE --> RENDER --> STITCH --> AUDIO (optional) --> REVIEW\n```\n\n1. **PLAN** — Write `plan.md` with narrative arc, scene list, visual elements, color palette, voiceover script\n2. **CODE** — Write `script.py` with one class per scene, each independently renderable\n3. **RENDER** — `manim -ql script.py Scene1 Scene2 ...` for draft, `-qh` for production\n4. **STITCH** — ffmpeg concat of scene clips into `final.mp4`\n5. **AUDIO** (optional) — Add voiceover and/or background music via ffmpeg. See `references/rendering.md`\n6. **REVIEW** — Render preview stills, verify against plan, adjust\n\n## Project Structure\n\n```\nproject-name/\n  plan.md                # Narrative arc, scene breakdown\n  script.py              # All scenes in one file\n  concat.txt             # ffmpeg scene list\n  final.mp4              # Stitched output\n  media/                 # Auto-generated by Manim\n    videos/script/480p15/\n```\n\n## Creative Direction\n\n### Color Palettes\n\n| Palette | Background | Primary | Secondary | Accent | Use case |\n|---------|-----------|---------|-----------|--------|----------|\n| **Classic 3B1B** | `#1C1C1C` | `#58C4DD` (BLUE) | `#83C167` (GREEN) | `#FFFF00` (YELLOW) | General math/CS |\n| **Warm academic** | `#2D2B55` | `#FF6B6B` | `#FFD93D` | `#6BCB77` | Approachable |\n| **Neon tech** | `#0A0A0A` | `#00F5FF` | `#FF00FF` | `#39FF14` | Systems, architecture |\n| **Monochrome** | `#1A1A2E` | `#EAEAEA` | `#888888` | `#FFFFFF` | Minimalist |\n\n### Animation Speed\n\n| Context | run_time | self.wait() after |\n|---------|----------|-------------------|\n| Title/intro appear | 1.5s | 1.0s |\n| Key equation reveal | 2.0s | 2.0s |\n| Transform/morph | 1.5s | 1.5s |\n| Supporting label | 0.8s | 0.5s |\n| FadeOut cleanup | 0.5s | 0.3s |\n| \"Aha moment\" reveal | 2.5s | 3.0s |\n\n### Typography Scale\n\n| Role | Font size | Usage |\n|------|-----------|-------|\n| Title | 48 | Scene titles, opening text |\n| Heading | 36 | Section headers within a scene |\n| Body | 30 | Explanatory text |\n| Label | 24 | Annotations, axis labels |\n| Caption | 20 | Subtitles, fine print |\n\n### Fonts\n\n**Use monospace fonts for all text.** Manim's Pango renderer produces broken kerning with proportional fonts at all sizes. See `references/visual-design.md` for full recommendations.\n\n```python\nMONO = \"Menlo\"  # define once at top of file\n\nText(\"Fourier Series\", font_size=48, font=MONO, weight=BOLD)  # titles\nText(\"n=1: sin(x)\", font_size=20, font=MONO)                  # labels\nMathTex(r\"\\nabla L\")                                            # math (uses LaTeX)\n```\n\nMinimum `font_size=18` for readability.\n\n### Per-Scene Variation\n\nNever use identical config for all scenes. For each scene:\n- **Different dominant color** from the palette\n- **Different layout** — don't always center everything\n- **Different animation entry** — vary between Write, FadeIn, GrowFromCenter, Create\n- **Different visual weight** — some scenes dense, others sparse\n\n## Workflow\n\n### Step 1: Plan (plan.md)\n\nBefore any code, write `plan.md`. See `references/scene-planning.md` for the comprehensive template.\n\n### Step 2: Code (script.py)\n\nOne class per scene. Every scene is independently renderable.\n\n```python\nfrom manim import *\n\nBG = \"#1C1C1C\"\nPRIMARY = \"#58C4DD\"\nSECONDARY = \"#83C167\"\nACCENT = \"#FFFF00\"\nMONO = \"Menlo\"\n\nclass Scene1_Introduction(Scene):\n    def construct(self):\n        self.camera.background_color = BG\n        title = Text(\"Why Does This Work?\", font_size=48, color=PRIMARY, weight=BOLD, font=MONO)\n        self.add_subcaption(\"Why does this work?\", duration=2)\n        self.play(Write(title), run_time=1.5)\n        self.wait(1.0)\n        self.play(FadeOut(title), run_time=0.5)\n```\n\nKey patterns:\n- **Subtitles** on every animation: `self.add_subcaption(\"text\", duration=N)` or `subcaption=\"text\"` on `self.play()`\n- **Shared color constants** at file top for cross-scene consistency\n- **`self.camera.background_color`** set in every scene\n- **Clean exits** — FadeOut all mobjects at scene end: `self.play(FadeOut(Group(*self.mobjects)))`\n\n### Step 3: Render\n\n```bash\nmanim -ql script.py Scene1_Introduction Scene2_CoreConcept  # draft\nmanim -qh script.py Scene1_Introduction Scene2_CoreConcept  # production\n```\n\n### Step 4: Stitch\n\n```bash\ncat > concat.txt << 'EOF'\nfile 'media/videos/script/480p15/Scene1_Introduction.mp4'\nfile 'media/videos/script/480p15/Scene2_CoreConcept.mp4'\nEOF\nffmpeg -y -f concat -safe 0 -i concat.txt -c copy final.mp4\n```\n\n### Step 5: Review\n\n```bash\nmanim -ql --format=png -s script.py Scene2_CoreConcept  # preview still\n```\n\n## Critical Implementation Notes\n\n### Raw Strings for LaTeX\n```python\n# WRONG: MathTex(\"\\frac{1}{2}\")\n# RIGHT:\nMathTex(r\"\\frac{1}{2}\")\n```\n\n### buff >= 0.5 for Edge Text\n```python\nlabel.to_edge(DOWN, buff=0.5)  # never < 0.5\n```\n\n### FadeOut Before Replacing Text\n```python\nself.play(ReplacementTransform(note1, note2))  # not Write(note2) on top\n```\n\n### Never Animate Non-Added Mobjects\n```python\nself.play(Create(circle))  # must add first\nself.play(circle.animate.set_color(RED))  # then animate\n```\n\n## Performance Targets\n\n| Quality | Resolution | FPS | Speed |\n|---------|-----------|-----|-------|\n| `-ql` (draft) | 854x480 | 15 | 5-15s/scene |\n| `-qm` (medium) | 1280x720 | 30 | 15-60s/scene |\n| `-qh` (production) | 1920x1080 | 60 | 30-120s/scene |\n\nAlways iterate at `-ql`. Only render `-qh` for final output.\n\n## References\n\n| File | Contents |\n|------|----------|\n| `references/animations.md` | Core animations, rate functions, composition, `.animate` syntax, timing patterns |\n| `references/mobjects.md` | Text, shapes, VGroup/Group, positioning, styling, custom mobjects |\n| `references/visual-design.md` | 12 design principles, opacity layering, layout templates, color palettes |\n| `references/equations.md` | LaTeX in Manim, TransformMatchingTex, derivation patterns |\n| `references/graphs-and-data.md` | Axes, plotting, BarChart, animated data, algorithm visualization |\n| `references/camera-and-3d.md` | MovingCameraScene, ThreeDScene, 3D surfaces, camera control |\n| `references/scene-planning.md` | Narrative arcs, layout templates, scene transitions, planning template |\n| `references/rendering.md` | CLI reference, quality presets, ffmpeg, voiceover workflow, GIF export |\n| `references/troubleshooting.md` | LaTeX errors, animation errors, common mistakes, debugging |\n| `references/animation-design-thinking.md` | When to animate vs show static, decomposition, pacing, narration sync |\n| `references/updaters-and-trackers.md` | ValueTracker, add_updater, always_redraw, time-based updaters, patterns |\n| `references/paper-explainer.md` | Turning research papers into animations — workflow, templates, domain patterns |\n| `references/decorations.md` | SurroundingRectangle, Brace, arrows, DashedLine, Angle, annotation lifecycle |\n| `references/production-quality.md` | Pre-code, pre-render, post-render checklists, spatial layout, color, tempo |\n\n---\n\n## Creative Divergence (use only when user requests experimental/creative/unique output)\n\nIf the user asks for creative, experimental, or unconventional explanatory approaches, select a strategy and reason through it BEFORE designing the animation.\n\n- **SCAMPER** — when the user wants a fresh take on a standard explanation\n- **Assumption Reversal** — when the user wants to challenge how something is typically taught\n\n### SCAMPER Transformation\nTake a standard mathematical/technical visualization and transform it:\n- **Substitute**: replace the standard visual metaphor (number line → winding path, matrix → city grid)\n- **Combine**: merge two explanation approaches (algebraic + geometric simultaneously)\n- **Reverse**: derive backward — start from the result and deconstruct to axioms\n- **Modify**: exaggerate a parameter to show why it matters (10x the learning rate, 1000x the sample size)\n- **Eliminate**: remove all notation — explain purely through animation and spatial relationships\n\n### Assumption Reversal\n1. List what's \"standard\" about how this topic is visualized (left-to-right, 2D, discrete steps, formal notation)\n2. Pick the most fundamental assumption\n3. Reverse it (right-to-left derivation, 3D embedding of a 2D concept, continuous morphing instead of steps, zero notation)\n4. Explore what the reversal reveals that the standard approach hides\n","frontmatter":{"name":"manim-video","description":"Production pipeline for mathematical and technical animations using Manim Community Edition. Creates 3Blue1Brown-style explainer videos, algorithm visualizations, equation derivations, architecture diagrams, and data stories. Use when users request: animated explanations, math animations, concept visualizations, algorithm walkthroughs, technical explainers, 3Blue1Brown style videos, or any programmatic animation with geometric/mathematical content.","version":"1.0.0"},"isInternal":false,"tokens":3028,"sizeBytes":11983}],"systemPromptSnippet":"<yakaai_skills repo=\"browser-use/video-use\">\n<!-- File: SKILL.md (Tokens: ~6444 | Category: anthropic-skill) -->\n---\nname: video-use\ndescription: Edit any video by conversation. Transcribe, cut, color grade, generate overlay animations, burn subtitles — for talking heads, montages, tutorials, travel, interviews. No presets, no menus. Ask questions, confirm the plan, execute, iterate, persist. Production-correctness rules are hard; everything else is artistic freedom.\n---\n\n# Video Use\n\n## Principle\n\n1. **LLM reasons from raw transcript + on-demand visuals.** The only derived artifact that earns its keep is a packed phrase-level transcript (`takes_packed.md`). Everything else — filler tagging, retake detection, shot classification, emphasis scoring — you derive at decision time.\n2. **Audio is primary, visuals follow.** Cut candidates come from speech boundaries and silence gaps. Drill into visuals only at decision points.\n3. **Ask → confirm → execute → iterate → persist.** Never touch the cut until the user has confirmed the strategy in plain English.\n4. **Generalize.** Do not assume what kind of video this is. Look at the material, ask the user, then edit.\n5. **Artistic freedom is the default.** Every specific value, preset, font, color, duration, pitch structure, and technique in this document is a *worked example* from one proven video — not a mandate. Read them to understand what's possible and why each worked. Then make your own taste calls based on what the material actually is and what the user actually wants. **The only things you MUST do are in the Hard Rules section below.** Everything else is yours.\n6. **Invent freely.** If the material calls for a technique not described here — split-screen, picture-in-picture, lower-third identity cards, reaction cuts, speed ramps, freeze frames, crossfades, match cuts, L-cuts, J-cuts, speed ramps over breath, whatever — build it. The helpers are ffmpeg and PIL. They can do anything the format supports. Do not wait for permission.\n7. **Verify your own output before showing it to the user.** If you wouldn't ship it, don't present it.\n\n## Hard Rules (production correctness — non-negotiable)\n\nThese are the things where deviation produces silent failures or broken output. They are not taste, they are correctness. Memorize them.\n\n1. **Subtitles are applied LAST in the filter chain**, after every overlay. Otherwise overlays hide captions. Silent failure.\n2. **Per-segment extract → lossless `-c copy` concat**, not single-pass filtergraph. Otherwise you double-encode every segment when overlays are added.\n3. **30ms audio fades at every segment boundary** (`afade=t=in:st=0:d=0.03,afade=t=out:st={dur-0.03}:d=0.03`). Otherwise audible pops at every cut.\n4. **Overlays use `setpts=PTS-STARTPTS+T/TB`** to shift the overlay's frame 0 to its window start. Otherwise you see the middle of the animation during the overlay window.\n5. **Master SRT uses output-timeline offsets**: `output_time = word.start - segment_start + segment_offset`. Otherwise captions misalign after segment concat.\n6. **Never cut inside a word.** Snap every cut edge to a word boundary from the Scribe transcript.\n7. **Pad every cut edge.** Working window: 30–200ms. Scribe timestamps drift 50–100ms — padding absorbs the drift. Tighter for fast-paced, looser for cinematic.\n8. **Word-level verbatim ASR only.** Never SRT/phrase mode (loses sub-second gap data). Never normalized fillers (loses editorial signal).\n9. **Cache transcripts per source.** Never re-transcribe unless the source file itself changed.\n10. **Parallel sub-agents for multiple animations.** Never sequential. Spawn N at once via the `Agent` tool; total wall time ≈ slowest one.\n11. **Strategy confirmation before execution.** Never touch the cut until the user has approved the plain-English plan.\n12. **All session outputs in `<videos_dir>/edit/`.** Never write inside the `video-use/` project directory.\n\nEverything else in this document is a worked example. Deviate whenever the material calls for it.\n\n## Directory layout\n\nThe skill lives in `video-use/`. User footage lives wherever they put it. All session outputs go into `<videos_dir>/edit/`.\n\n```\n<videos_dir>/\n├── <source files, untouched>\n└── edit/\n    ├── project.md               ← memory; appended every session\n    ├── takes_packed.md          ← phrase-level transcripts, the LLM's primary reading view\n    ├── edl.json                 ← cut decisions\n    ├── transcripts/<name>.json  ← cached raw Scribe JSON\n    ├── animations/slot_<id>/    ← per-animation source + render + reasoning\n    ├── clips_graded/            ← per-segment extracts with grade + fades\n    ├── master.srt               ← output-timeline subtitles\n    ├── downloads/               ← yt-dlp outputs\n    ├── verify/                  ← debug frames / timeline PNGs\n    ├── preview.mp4\n    └── final.mp4\n```\n\n## Setup\n\nFirst-time install lives in `install.md` (clone, deps, ffmpeg, skill registration, API key). Don't re-run it every session; on cold start just verify:\n\n- `ELEVENLABS_API_KEY` resolves — either in the environment or in `.env` at the video-use repo root. If missing, ask the user to paste one and write it to `.env` (never to the user's `<videos_dir>`).\n- `ffmpeg` + `ffprobe` on PATH.\n- Python deps installed (`uv sync` or `pip install -e .` inside the repo).\n- Node.js + npm available if the session needs HyperFrames or Remotion slots. HyperFrames currently requires Node.js 22+.\n- `yt-dlp`, HyperFrames, Remotion, Manim installed only on first use.\n- First-use animation setup happens inside the slot directory, never at the video-use repo root. HyperFrames can be invoked with `npx --yes hyperframes ...`; Remotion can be scaffolded with `npx create-video@latest` or installed as a project-local dependency before using its `remotion render` command.\n- This skill vendors `skills/manim-video/`. Read its SKILL.md when building a Manim slot.\n\nHelpers (`helpers/transcribe.py`, `helpers/render.py`, etc.) live alongside this SKILL.md. Resolve their paths relative to the directory containing this file — the skill is typically symlinked at `~/.claude/skills/video-use/` or `~/.codex/skills/video-use/`.\n\n## Helpers\n\n- **`transcribe.py <video>`** — single-file Scribe call. `--num-speakers N` optional. Cached.\n- **`transcribe_batch.py <videos_dir>`** — 4-worker parallel transcription. Use for multi-take.\n- **`pack_transcripts.py --edit-dir <dir>`** — `transcripts/*.json` → `takes_packed.md` (phrase-level, break on silence ≥ 0.5s).\n- **`timeline_view.py <video> <start> <end>`** — filmstrip + waveform PNG. On-demand visual drill-down. **Not a scan tool** — use it at decision points, not constantly.\n- **`render.py <edl.json> -o <out>`** — per-segment extract → concat → overlays (PTS-shifted) → subtitles LAST. `--preview` for 720p fast. `--build-subtitles` to generate master.srt inline.\n- **`grade.py <in> -o <out>`** — ffmpeg filter chain grade. Presets + `--filter '<raw>'` for custom.\n\nFor animations, create `<edit>/animations/slot_<id>/` with `Bash` and spawn a sub-agent via the `Agent` tool.\n\n## The process\n\n1. **Inventory.** `ffprobe` every source. `transcribe_batch.py` on the directory. `pack_transcripts.py` to produce `takes_packed.md`. Sample one or two `timeline_view`s for a visual first impression.\n2. **Pre-scan for problems.** One pass over `takes_packed.md` to note verbal slips, obvious mis-speaks, or phrasings to avoid. Plain list, feed into the editor brief.\n3. **Converse.** Describe what you see in plain English. Ask questions *shaped by the material*. Collect: content type, target length/aspect, aesthetic/brand direction, pacing feel, must-preserve moments, must-cut moments, animation and grade preferences, subtitle needs. Do not use a fixed checklist — the right questions are different every time.\n4. **Propose strategy.** 4–8 sentences: shape, take choices, cut direction, animation plan, grade direction, subtitle style, length estimate. **Wait for confirmation.**\n5. **Execute.** Produce `edl.json` via the editor sub-agent brief. Drill into `timeline_view` at ambiguous moments. Build animations in parallel sub-agents. Apply grade per-segment. Compose via `render.py`.\n6. **Preview.** `render.py --preview`.\n7. **Self-eval (before showing the user).** Run `timeline_view` on the **rendered output** (not the sources) at every cut boundary (±1.5s window). Check each image for:\n   - Visual discontinuity / flash / jump at the cut\n   - Waveform spike at the boundary (audio pop that slipped past the 30ms fade)\n   - Subtitle hidden behind an overlay (Rule 1 violation)\n   - Overlay misaligned or showing wrong frames (Rule 4 violation)\n\n   Also sample: first 2s, last 2s, and 2–3 mid-points — check grade consistency, subtitle readability, overall coherence. Run `ffprobe` on the output to verify duration matches the EDL expectation.\n\n   Measure the audio, don't assume it: `ffmpeg -i out.mp4 -af ebur128=peak=true -f null -` for integrated loudness and true peak, plus RMS per section (dialogue, music-only, end card). An end card 15 dB under the dialogue, or effects louder than speech, is a bug. You cannot listen: say so, and report the numbers.\n\n   For anything the user will publish (launch, promo, ad), also spawn one **critic sub-agent** with the rendered file, the EDL, and any reference videos the user gave. Brief it to roast, not to praise: a verdict, ranked problems with timecodes and evidence (frames, levels), and the 5 fixes to do first. Fresh eyes catch what the author stopped seeing — cut-off payoff lines, 0.5s memes, unreadable 28px text at phone size.\n\n   If anything fails: fix → re-render → re-eval. **Cap at 3 self-eval passes** — if issues remain after 3, flag them to the user rather than looping forever. Only present the preview once the self-eval passes.\n8. **Iterate + persist.** Natural-language feedback, re-plan, re-render. Never re-transcribe. Final render on confirmation. Append to `project.md`.\n\n## Cut craft (techniques)\n\n- **Audio-first.** Candidate cuts from word boundaries and silence gaps.\n- **Preserve peaks.** Laughs, punchlines, emphasis beats. Extend past punchlines to include reactions — the laugh IS the beat.\n- **Speaker handoffs** benefit from air between utterances. Common values: 400–600ms. Less for fast-paced, more for cinematic. Taste call.\n- **Audio events as signals.** `(laughs)`, `(sighs)`, `(applause)` mark beats. Extend past them.\n- **Silence gaps are cut candidates.** Silences ≥400ms are usually the cleanest. 150–400ms phrase boundaries are usable with a visual check. <150ms is unsafe (mid-phrase).\n- **Example cut padding** (the launch video shipped with this): 50ms before the first kept word, 80ms after the last. Tighter for montage energy, looser for documentary. Stay in the 30–200ms working window (Hard Rule 7).\n- **Never reason audio and video independently.** Every cut must work on both tracks.\n\n## The packed transcript (primary reading view)\n\n`pack_transcripts.py` reads all `transcripts/*.json` and produces one markdown file where each take is a list of phrase-level lines, each prefixed with its `[start-end]` time range. Phrases break on any silence ≥ 0.5s OR speaker change. This is the artifact the editor sub-agent reads to pick cuts — it gives word-boundary precision from text alone at 1/10 the tokens of raw JSON.\n\nExample line:\n```\n## C0103  (duration: 43.0s, 8 phrases)\n  [002.52-005.36] S0 Ninety percent of what a web agent does is completely wasted.\n  [006.08-006.74] S0 We fixed this.\n```\n\n## Editor sub-agent brief (for multi-take selection)\n\nWhen the task is \"pick the best take of each beat across many clips,\" spawn a dedicated sub-agent with a brief shaped like this. The structure is load-bearing; the pitch-shape example is not.\n\n```\nYou are editing a <type> video. Pick the best take of each beat and \nassemble them chronologically by beat, not by source clip order.\n\nINPUTS:\n  - takes_packed.md (time-annotated phrase-level transcripts of all takes)\n  - Product/narrative context: <2 sentences from the user>\n  - Speaker(s): <name, role, delivery style note>\n  - Expected structure: <pick an archetype or invent one>\n  - Verbal slips to avoid: <list from the pre-scan pass>\n  - Target runtime: <seconds>\n\nCommon structural archetypes (pick, adapt, or invent):\n  - Tech launch / demo:   HOOK → PROBLEM → SOLUTION → BENEFIT → EXAMPLE → CTA\n  - Tutorial:             INTRO → SETUP → STEPS → GOTCHAS → RECAP\n  - Interview:            (QUESTION → ANSWER → FOLLOWUP) repeat\n  - Travel / event:       ARRIVAL → HIGHLIGHTS → QUIET MOMENTS → DEPARTURE\n  - Documentary:          THESIS → EVIDENCE → COUNTERPOINT → CONCLUSION\n  - Music / performance:  INTRO → VERSE → CHORUS → BRIDGE → OUTRO\n  - Or invent your own.\n\nRULES:\n  - Start/end times must fall on word boundaries from the transcript.\n  - Pad cut boundaries (working window 30–200ms).\n  - Prefer silences ≥ 400ms as cut targets.\n  - Unavoidable slips are kept if no better take exists. Note them in \"reason\".\n  - If over budget, revise: drop a beat or trim tails. Report total and self-correct.\n\nOUTPUT (JSON array, no prose):\n  [{\"source\": \"C0103\", \"start\": 2.42, \"end\": 6.85, \"beat\": \"HOOK\",\n    \"quote\": \"...\", \"reason\": \"...\"}, ...]\n\nReturn the final EDL and a one-line total runtime check.\n```\n\n## Color grade (when requested)\n\nYour job is to **reason about the image**, not apply a preset. Look at a frame (via `timeline_view`), decide what's wrong, adjust one thing, look again.\n\nMental model is ASC CDL. Per channel: `out = (in * slope + offset) ** power`, then global saturation. `slope` → highlights, `offset` → shadows, `power` → midtones.\n\n**Example filter chains** (`grade.py` has `--list-presets`; use them as starting points or mix your own):\n\n- **`warm_cinematic`** — retro/technical, subtle teal/orange split, desaturated. Shipped in a real launch video. Safe for talking heads.\n- **`neutral_punch`** — minimal corrective: contrast bump + gentle S-curve. No hue shifts.\n- **`none`** — straight copy. Default when the user hasn't asked.\n\nFor anything else — portraiture, nature, product, music video, documentary — invent your own chain. `grade.py --filter '<raw ffmpeg>'` accepts any filter string.\n\nHard rules: apply **per-segment during extraction** (not post-concat, which re-encodes twice). Never go aggressive without testing skin tones.\n\n## Subtitles (when requested)\n\nSubtitles have three dimensions worth reasoning about: **chunking** (1/2/3/sentence per line), **case** (UPPER/Title/Natural), and **placement** (margin from bottom). The right combo depends on content.\n\n**Worked styles** — pick, adapt, or invent:\n\n**`bold-overlay`** — short-form tech launch, fast-paced social. ~2-word chunks, UPPERCASE, break on punctuation and pauses ≥ 0.3s, grow to 3 words rather than flash a cue < 0.35s (`chunk_words` in `render.py`), Helvetica 18 Bold, white-on-outline, `MarginV=35`. `render.py` ships with this as `SUB_FORCE_STYLE`.\n\n```\nFontName=Helvetica,FontSize=18,Bold=1,\nPrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BackColour=&H00000000,\nBorderStyle=1,Outline=2,Shadow=0,\nAlignment=2,MarginV=35\n```\n\n**`natural-sentence`** (if you invent this mode) — narrative, documentary, education. 4–7 word chunks, sentence case, break on natural pauses, `MarginV=60–80`, larger font for readability, slightly wider max-width. No shipped force_style — design one if you need it.\n\nInvent a third style if neither fits. Hard rules: subtitles LAST (Rule 1), output-timeline offsets (Rule 5).\n\n## Animations (when requested)\n\nAnimations match the content and the brand. **Get the palette, font, and visual language from the conversation** — never assume a default. If the user hasn't told you, propose a palette in the strategy phase and wait for confirmation before building anything.\n\n**Tool options:**\n\nPick the engine per animation slot. Do not default to Remotion just because the animation is web-adjacent.\n\n- **HyperFrames** — Browser-native HTML/CSS/GSAP video compositions: product UI motion, website-to-video or mockup-to-video captures, kinetic typography, landing-page/storyboard promos, data-driven UI states, transparent WebM overlays, and clips that need deterministic frame capture plus HyperFrames lint/validate/render checks. Best when the animation should be authored and verified like a web composition instead of a React component tree.\n- **Remotion** — React/CSS compositions with component state, reusable React primitives, or an existing Remotion brand system. Best when the user specifically asks for React/Remotion or when React composition is the simpler authoring model.\n- **Manim** — formal diagrams, state machines, equation derivations, graph morphs. Read `skills/manim-video/SKILL.md` and its references for depth.\n- **PIL + PNG sequence + ffmpeg** — simple overlay cards: counters, typewriter text, single bar reveals, progressive draws. Fast to iterate, any aesthetic you want. The launch video used this.\n\nFor HyperFrames slots, scaffold the slot inside `edit/animations/slot_<id>/` with `npx --yes hyperframes init . --example blank --non-interactive --skip-skills`, build the HTML composition there, run the HyperFrames checks that fit the slot (`lint`, `validate`, and a draft render when practical), then produce the final overlay video with `npx --yes hyperframes render . -o render.mp4` or `--format webm -o render.webm` when alpha is required. Point the EDL overlay `file` at the actual rendered path.\n\nFor Remotion slots, keep the Remotion project isolated inside the same slot directory, scaffold with `npx create-video@latest` or install Remotion locally there, render the composition to `render.mp4` with the project-local `remotion render` command, and verify duration and dimensions with `ffprobe`.\n\nNone is mandatory. Invent hybrids if useful (e.g., PIL background with a HyperFrames or Remotion layer on top).\n\n**Duration rules of thumb, context-dependent:**\n\n- **Sync-to-narration explanations.** A viewer needs to parse the content at 1×. Rough floor 3s, typical 5–7s for simple cards, 8–14s for complex diagrams. The launch video shipped at 5–7s per simple card.\n- **Beat-synced accents** (music video, fast montage). 0.5–2s is fine — they're visual accents, not information. The \"readable at 1×\" rule becomes *\"recognizable at 1×\"*, not *\"fully parseable.\"*\n- **Hold the final frame ≥ 1s** before the cut (universal).\n- **Over voiceover:** total duration ≥ `narration_length + 1s` (universal).\n- **Never parallel-reveal independent elements** — the eye can't track two new things at once. One thing, pause, next thing.\n\n**Animation payoff timing (rule for sync-to-narration):** get the payoff word's timestamp. Start the overlay `reveal_duration` seconds earlier so the landing frame coincides with the spoken payoff word. Without this sync the animation feels disconnected.\n\n**Easing** (universal — never `linear`, it looks robotic):\n\n```python\ndef ease_out_cubic(t):    return 1 - (1 - t) ** 3\ndef ease_in_out_cubic(t):\n    if t < 0.5: return 4 * t ** 3\n    return 1 - (-2 * t + 2) ** 3 / 2\n```\n\n`ease_out_cubic` for single reveals (slow landing). `ease_in_out_cubic` for continuous draws.\n\n**Typing text anchor trick:** center on the FULL string's width, not the partial-string width — otherwise text slides left during reveal.\n\n**Example palette** (the launch video — one aesthetic among infinite):\n- Background `(10, 10, 10)` near-black\n- Accent `#FF5A00` / `(255, 90, 0)` orange\n- Labels `(110, 110, 110)` dim gray\n- Font: Menlo Bold at `/System/Library/Fonts/Menlo.ttc` (index 1)\n- ≤ 2 accent colors, ~40% empty space, minimal chrome\n- Result: terminal / retro tech feel\n\nThis is one style. If the brand is warm and serif, use that. If it's colorful and playful, use that. If the user handed you a style guide, follow it. If they didn't, propose one and confirm.\n\n**Fonts fail silently.** A web font that didn't load renders in a fallback face with no error — the video ships in \"almost Arial\". In HyperFrames/Remotion, await the font load and then assert it: `if (!document.fonts.check('700 76px \"Inter\"')) throw new Error(...)`. In PIL, pass an explicit font path; never rely on the default.\n\n**Worked example — \"show the edit\" hook** (a launch video for this tool). Instead of a title card, the first 3s visualize the editing itself: each transcript word pops in on its Scribe timestamp, with bars under it drawn from the real audio envelope; a filler (\"ummm\") grows letter by letter while it is spoken, turns orange and is cut out on screen at the same frame the audio cuts, and the next line lands immediately. It works because the picture is *driven by the same data as the sound* — one composition (Remotion) reads frame-exact word/envelope JSON produced in Python, so nothing can drift. Use the idea whenever the story is \"we removed something\": make the removal visible.\n\n**Parallel sub-agent brief** — each animation is one sub-agent spawned via the `Agent` tool. Each prompt is self-contained (sub-agents have no parent context). Include:\n\n1. One-sentence goal: *\"Build ONE animation: [spec]. Nothing else.\"*\n2. Absolute output path (`<edit>/animations/slot_<id>/render.mp4`)\n3. Exact technical spec: resolution, fps, codec, pix_fmt, CRF, duration\n4. Style palette as concrete values (RGB tuples, hex, or reference to a design system)\n5. Font path with index\n6. Frame-by-frame timeline (what happens when, with easing)\n7. Anti-list (\"no chrome, no extras, no titles unless specified\")\n8. Code pattern reference (copy helpers inline, don't import across slots)\n9. Deliverable checklist (script, render, verify duration via ffprobe, report)\n10. **\"Do not ask questions. If anything is ambiguous, pick the most obvious interpretation and proceed.\"**\n\nOne sub-agent = one file (unique filenames, parallel agents don't overwrite each other).\n\n## Music and sound effects (when requested)\n\nSound is where generated videos sound cheap. Worked rules from launch edits:\n\n- **Fewer effects.** Every effect is tied to something visible (a cut, a landing, a click). ~20 stock whooshes/risers/impacts in 18s reads as generic; ~8 reads as designed.\n- **Hit on the frame.** Most effects have an attack (silence or a build before the transient). Measure it (first sample above ~-30 dBFS of the peak) and start the file `attack` seconds *before* the visible contact frame.\n- **Duck music under speech** (roughly -12 to -15 dB relative to its music-only level), and ramp it out before a stinger or end card instead of letting its own tail decay under your CTA.\n- **Master once:** mix to PCM, then two-pass loudnorm (-14 LUFS, true peak ≤ -1 dBTP) on the final mix. Then measure per section (see Self-eval).\n- **Music taste is the user's call.** Generated music defaults to \"hype\"; offer two contrasting beds and let the user listen. Don't claim a mix sounds good — you can only measure it.\n\n## Output spec\n\nMatch the source unless the user asked for something specific. Common targets: `1920×1080@24` cinematic, `1920×1080@30` screen content, `1080×1920@30` vertical social, `3840×2160@24` 4K cinema, `1080×1080@30` square. `render.py` defaults the scale to 1080p from any source; pass `--filter` or edit the extract command for other targets. Worth asking the user which delivery format matters.\n\n## EDL format\n\n```json\n{\n  \"version\": 1,\n  \"sources\": {\"C0103\": \"/abs/path/C0103.MP4\", \"C0108\": \"/abs/path/C0108.MP4\"},\n  \"ranges\": [\n    {\"source\": \"C0103\", \"start\": 2.42, \"end\": 6.85,\n     \"beat\": \"HOOK\", \"quote\": \"...\", \"reason\": \"Cleanest delivery, stops before slip at 38.46.\"},\n    {\"source\": \"C0108\", \"start\": 14.30, \"end\": 28.90,\n     \"beat\": \"SOLUTION\", \"quote\": \"...\", \"reason\": \"Only take without the false start.\"}\n  ],\n  \"grade\": \"warm_cinematic\",\n  \"overlays\": [\n    {\"file\": \"edit/animations/slot_1/render.mp4\", \"start_in_output\": 0.0, \"duration\": 5.0}\n  ],\n  \"subtitles\": \"edit/master.srt\",\n  \"total_duration_s\": 87.4\n}\n```\n\n`grade` is a preset name or raw ffmpeg filter. `overlays` are rendered animation clips. `subtitles` is optional and applied LAST.\n\n## Memory — `project.md`\n\nAppend one section per session at `<edit>/project.md`:\n\n```markdown\n## Session N — YYYY-MM-DD\n\n**Strategy:** one paragraph describing the approach\n**Decisions:** take choices, cuts, grades, animations + why\n**Reasoning log:** one-line rationale for non-obvious decisions\n**Outstanding:** deferred items\n```\n\nOn startup, read `project.md` if it exists and summarize the last session in one sentence before asking whether to continue.\n\n## Anti-patterns\n\nThings that consistently fail regardless of style:\n\n- **Hierarchical pre-computed codec formats** with USABILITY / tone tags / shot layers. Over-engineering. Derive from the transcript at decision time.\n- **Hand-tuned moment-scoring functions.** The LLM picks better than any heuristic you'll write.\n- **Whisper SRT / phrase-level output.** Loses sub-second gap data. Always word-level verbatim.\n- **Running Whisper locally on CPU.** Slow and it normalizes fillers. Use hosted Scribe.\n- **Burning subtitles into base before compositing overlays.** Overlays hide them. (Hard Rule 1.)\n- **Single-pass filtergraph when you have overlays.** Double re-encodes. Use per-segment extract → concat.\n- **Linear animation easing.** Looks robotic. Always cubic.\n- **Unverified web fonts.** A failed load silently falls back to a system face. Assert the font loaded before rendering.\n- **Stock SFX on every transition.** Tie each effect to a visible event; cap the count.\n- **Hard audio cuts at segment boundaries.** Audible pops. (Hard Rule 3.)\n- **Typing text centered on the partial string.** Text slides left as it grows.\n- **Sequential sub-agents for multiple animations.** Always parallel.\n- **Editing before confirming the strategy.** Never.\n- **Re-transcribing cached sources.** Immutable outputs of immutable inputs.\n- **Assuming what kind of video it is.** Look first, ask second, edit last.\n\n<!-- File: skills/manim-video/README.md (Tokens: ~194 | Category: anthropic-skill) -->\n# Manim Video Skill\n\nProduction pipeline for mathematical and technical animations using [Manim Community Edition](https://www.manim.community/).\n\n## What it does\n\nCreates 3Blue1Brown-style animated videos from text prompts. The agent handles the full pipeline: creative planning, Python code generation, rendering, scene stitching, and iterative refinement.\n\n## Use cases\n\n- **Concept explainers** — \"Explain how neural networks learn\"\n- **Equation derivations** — \"Animate the proof of the Pythagorean theorem\"\n- **Algorithm visualizations** — \"Show how quicksort works step by step\"\n- **Data stories** — \"Animate our before/after performance metrics\"\n- **Architecture diagrams** — \"Show our microservice architecture building up\"\n\n## Prerequisites\n\nPython 3.10+, Manim CE (`pip install manim`), LaTeX, ffmpeg.\n\n```bash\nbash skills/creative/manim-video/scripts/setup.sh\n```\n\n<!-- File: skills/manim-video/references/animation-design-thinking.md (Tokens: ~1663 | Category: anthropic-skill) -->\n# Animation Design Thinking\n\nHow to decide WHAT to animate and HOW to structure it — before writing any code.\n\n## Should I animate this?\n\nNot everything benefits from animation. Motion adds cognitive load. Bad animation is worse than a good static diagram.\n\n**Animate when:**\n- A sequence unfolds over time (algorithm steps, derivation, pipeline stages)\n- Spatial relationships change (transformation, deformation, rotation)\n- Something is built from parts (construction, assembly, accumulation)\n- You're comparing states (before/after, method A vs method B)\n- Temporal evolution is the point (training curves, wave propagation, gradient descent)\n\n**Show static when:**\n- The concept is a single labeled diagram (circuit, anatomy, architecture overview)\n- Motion would distract from spatial layout\n- The viewer needs to study it carefully (dense table, reference chart)\n- The concept is already intuitive from a well-labeled figure\n\n**Rule of thumb:** If you'd explain it with \"first X, then Y, then Z\" — animate it. If you'd explain it by pointing at parts of one picture — show it static.\n\n## Decomposing a concept into animation\n\n### Step 1: Write the narration first\n\nBefore any code, write what the narrator would say. This determines:\n- **Order** — what concept comes first\n- **Duration** — how long each idea gets\n- **Visuals** — what the viewer must SEE when they HEAR each sentence\n\nA scene where the narration says \"the gradient points uphill\" must show a gradient arrow at that moment. If the visual doesn't match the audio, the viewer's brain splits attention and both tracks are lost.\n\n### Step 2: Identify visual beats\n\nA \"beat\" is a moment where something changes on screen. Mark each beat in your narration:\n\n```\n\"Consider a function f of x.\"         → [BEAT: axes + curve appear]\n\"At this point...\"                     → [BEAT: dot appears on curve]\n\"...the slope is positive.\"            → [BEAT: tangent line drawn]\n\"So the gradient tells us to go left.\" → [BEAT: arrow points left, dot moves]\n```\n\nEach beat is one `self.play()` call or a small group of simultaneous animations.\n\n### Step 3: Choose the right tool per beat\n\n| Visual need | Manim approach |\n|-------------|----------------|\n| Object appears for first time | `Create`, `Write`, `FadeIn`, `GrowFromCenter` |\n| Object transforms into another | `Transform`, `ReplacementTransform`, `FadeTransform` |\n| Attention drawn to existing object | `Indicate`, `Circumscribe`, `Flash`, `ShowPassingFlash` |\n| Continuous relationship maintained | `add_updater`, `always_redraw`, `ValueTracker` |\n| Object leaves the scene | `FadeOut`, `Uncreate`, `ShrinkToCenter` |\n| Static context that stays visible | `self.add()` (no animation) |\n\n## Pacing: the universal mistake is too fast\n\n### Timing rules\n\n| Content type | Minimum on-screen time |\n|-------------|----------------------|\n| New equation appearing | 2.0s animation + 2.0s pause |\n| New concept label | 1.0s animation + 1.0s pause |\n| Key insight (\"aha moment\") | 2.5s animation + 3.0s pause |\n| Supporting annotation | 0.8s animation + 0.5s pause |\n| Scene transition (FadeOut all) | 0.5s animation + 0.3s pause |\n\n### Breathing room\n\nAfter every reveal, add `self.wait()`. The viewer needs time to:\n1. Read the new text\n2. Connect it to what's already on screen\n3. Form an expectation about what comes next\n\n**No wait = the viewer is always behind you.** They're still reading the equation when you've already started transforming it.\n\n### Tempo variation\n\nMonotonous pacing feels like a lecture. Vary the tempo:\n- **Slow build** for core concepts (long run_time, long pauses)\n- **Quick succession** for supporting details (short run_time, minimal pauses)\n- **Dramatic pause** before the key reveal (extra `self.wait(2.0)` before the \"aha\")\n- **Rapid montage** for \"and this applies to X, Y, Z...\" sequences (`LaggedStart` with tight lag_ratio)\n\n## Narration synchronization\n\n### The \"see then hear\" principle\n\nThe visual should appear slightly BEFORE the narration describes it. When the viewer sees a circle appear and THEN hears \"consider a circle,\" the visual primes their brain for the concept. The reverse — hearing first, seeing second — creates confusion because they're searching the screen for something that isn't there yet.\n\n### Practical timing\n\n```python\n# Scene duration should match narration duration.\n# If narration for this scene is 8 seconds:\n# Total animation run_times + total self.wait() times = ~8 seconds.\n\n# Use manim-voiceover for automatic sync:\nwith self.voiceover(text=\"The gradient points downhill\") as tracker:\n    self.play(GrowArrow(gradient_arrow), run_time=tracker.duration)\n```\n\n## Equation decomposition strategy\n\n### The \"dim and reveal\" pattern\n\nWhen building a complex equation step by step:\n1. Show the full equation dimmed at `opacity=0.2` (sets expectation for where you're going)\n2. Highlight the first term at full opacity\n3. Explain it\n4. Highlight the next term, dim the first to `0.5` (it's now context)\n5. Repeat until the full equation is bright\n\nThis is better than building left-to-right because the viewer always sees the destination.\n\n### Term ordering\n\nAnimate terms in the order the viewer needs to understand them, not in the order they appear in the equation. For `E = mc²`:\n- Show `E` (the thing we want to know)\n- Then `m` (the input)\n- Then `c²` (the constant that makes it work)\n- Then the `=` (connecting them)\n\n## Architecture and pipeline diagrams\n\n### Box granularity\n\nThe most common mistake: too many boxes. Each box is a concept the viewer must track. Five boxes with clear labels beats twelve boxes with abbreviations.\n\n**Rule:** If two consecutive boxes could be labeled \"X\" and \"process X output,\" merge them into one box.\n\n### Animation strategy\n\nBuild pipelines left-to-right (or top-to-bottom) with arrows connecting them:\n1. First box appears alone → explain it\n2. Arrow grows from first to second → \"the output feeds into...\"\n3. Second box appears → explain it\n4. Repeat\n\nThen show data flowing through: `ShowPassingFlash` along the arrows, or a colored dot traversing the path.\n\n### The zoom-and-return pattern\n\nFor complex systems:\n1. Show the full overview (all boxes, small)\n2. Zoom into one box (`MovingCameraScene.camera.frame.animate`)\n3. Expand that box into its internal components\n4. Zoom back out to the overview\n5. Zoom into the next box\n\n## Common design mistakes\n\n1. **Animating everything at once.** The viewer can track 1-2 simultaneous animations. More than that and nothing registers.\n2. **No visual hierarchy.** Everything at the same opacity/size/color means nothing stands out. Use opacity layering.\n3. **Equations without context.** An equation appearing alone means nothing. Always show the geometric/visual interpretation first or simultaneously.\n4. **Skipping the \"why.\"** Showing HOW a transformation works without WHY it matters. Add a sentence/label explaining the purpose.\n5. **Identical pacing throughout.** Every animation at run_time=1.5, every wait at 1.0. Vary it.\n6. **Forgetting the audience.** A video for high schoolers needs different pacing and complexity than one for PhD students. Decide the audience in the planning phase.\n\n<!-- File: skills/manim-video/references/animations.md (Tokens: ~2342 | Category: anthropic-skill) -->\n# Animations Reference\n\n## Core Concept\n\nAn animation is a Python object that computes intermediate visual states of a mobject over time. Animations are objects passed to `self.play()`, not functions.\n\n`run_time` controls seconds (default: 1). Always specify it explicitly for important animations.\n\n## Creation Animations\n\n```python\nself.play(Create(circle))          # traces outline\nself.play(Write(equation))         # simulates handwriting (for Text/MathTex)\nself.play(FadeIn(group))           # opacity 0 -> 1\nself.play(GrowFromCenter(dot))     # scale 0 -> 1 from center\nself.play(DrawBorderThenFill(sq))  # outline first, then fill\n```\n\n## Removal Animations\n\n```python\nself.play(FadeOut(mobject))         # opacity 1 -> 0\nself.play(Uncreate(circle))        # reverse of Create\nself.play(ShrinkToCenter(group))   # scale 1 -> 0\n```\n\n## Transform Animations\n\n```python\n# Transform -- modifies the original in place\nself.play(Transform(circle, square))\n# After: circle IS the square (same object, new appearance)\n\n# ReplacementTransform -- replaces old with new\nself.play(ReplacementTransform(circle, square))\n# After: circle removed, square on screen\n\n# TransformMatchingTex -- smart equation morphing\neq1 = MathTex(r\"a^2 + b^2\")\neq2 = MathTex(r\"a^2 + b^2 = c^2\")\nself.play(TransformMatchingTex(eq1, eq2))\n```\n\n**Critical**: After `Transform(A, B)`, variable `A` references the on-screen mobject. Variable `B` is NOT on screen. Use `ReplacementTransform` when you want to work with `B` afterwards.\n\n## The .animate Syntax\n\n```python\nself.play(circle.animate.set_color(RED))\nself.play(circle.animate.shift(RIGHT * 2).scale(0.5))  # chain multiple\n```\n\n## Additional Creation Animations\n\n```python\nself.play(GrowFromPoint(circle, LEFT * 3))     # scale 0 -> 1 from a specific point\nself.play(GrowFromEdge(rect, DOWN))             # grow from one edge\nself.play(SpinInFromNothing(square))            # scale up while rotating (default PI/2)\nself.play(GrowArrow(arrow))                     # grows arrow from start to tip\n```\n\n## Movement Animations\n\n```python\n# Move a mobject along an arbitrary path\npath = Arc(radius=2, angle=PI)\nself.play(MoveAlongPath(dot, path), run_time=2)\n\n# Rotate (as a Transform, not .animate — supports about_point)\nself.play(Rotate(square, angle=PI / 2, about_point=ORIGIN), run_time=1.5)\n\n# Rotating (continuous rotation, updater-style — good for spinning objects)\nself.play(Rotating(gear, angle=TAU, run_time=4, rate_func=linear))\n```\n\n`MoveAlongPath` takes any `VMobject` as the path — use `Arc`, `CubicBezier`, `Line`, or a custom `VMobject`. Position is computed via `path.point_from_proportion()`.\n\n## Emphasis Animations\n\n```python\nself.play(Indicate(mobject))             # brief yellow flash + scale\nself.play(Circumscribe(mobject))         # draw rectangle around it\nself.play(Flash(point))                  # radial flash\nself.play(Wiggle(mobject))               # shake side to side\n```\n\n## Rate Functions\n\n```python\nself.play(FadeIn(mob), rate_func=smooth)          # default: ease in/out\nself.play(FadeIn(mob), rate_func=linear)           # constant speed\nself.play(FadeIn(mob), rate_func=rush_into)        # start slow, end fast\nself.play(FadeIn(mob), rate_func=rush_from)        # start fast, end slow\nself.play(FadeIn(mob), rate_func=there_and_back)   # animate then reverse\n```\n\n## Composition\n\n```python\n# Simultaneous\nself.play(FadeIn(title), Create(circle), run_time=2)\n\n# AnimationGroup with lag\nself.play(AnimationGroup(*[FadeIn(i) for i in items], lag_ratio=0.2))\n\n# LaggedStart\nself.play(LaggedStart(*[Write(l) for l in lines], lag_ratio=0.3, run_time=3))\n\n# Succession (sequential in one play call)\nself.play(Succession(FadeIn(title), Wait(0.5), Write(subtitle)))\n```\n\n## Updaters\n\n```python\ntracker = ValueTracker(0)\ndot = Dot().add_updater(lambda m: m.move_to(axes.c2p(tracker.get_value(), 0)))\nself.play(tracker.animate.set_value(5), run_time=3)\n```\n\n## Subtitles\n\n```python\n# Method 1: standalone\nself.add_subcaption(\"Key insight\", duration=2)\nself.play(Write(equation), run_time=2.0)\n\n# Method 2: inline\nself.play(Write(equation), subcaption=\"Key insight\", subcaption_duration=2)\n```\n\nManim auto-generates `.srt` subtitle files. Always add subcaptions for accessibility.\n\n## Timing Patterns\n\n```python\n# Pause-after-reveal\nself.play(Write(key_equation), run_time=2.0)\nself.wait(2.0)\n\n# Dim-and-focus\nself.play(old_content.animate.set_opacity(0.3), FadeIn(new_content))\n\n# Clean exit\nself.play(FadeOut(Group(*self.mobjects)), run_time=0.5)\nself.wait(0.3)\n```\n\n## Reactive Mobjects: always_redraw()\n\nRebuild a mobject from scratch every frame — essential when its geometry depends on other animated objects:\n\n```python\n# Brace that follows a resizing square\nbrace = always_redraw(Brace, square, UP)\nself.add(brace)\nself.play(square.animate.scale(2))  # brace auto-adjusts\n\n# Horizontal line that tracks a moving dot\nh_line = always_redraw(lambda: axes.get_h_line(dot.get_left()))\n\n# Label that always stays next to another mobject\nlabel = always_redraw(lambda: Text(\"here\", font_size=20).next_to(dot, UP, buff=0.2))\n```\n\nNote: `always_redraw` recreates the mobject every frame. For simple property tracking, use `add_updater` instead (cheaper):\n```python\nlabel.add_updater(lambda m: m.next_to(dot, UP))\n```\n\n## TracedPath — Trajectory Tracing\n\nDraw the path a point has traveled:\n\n```python\ndot = Dot(color=YELLOW)\npath = TracedPath(dot.get_center, stroke_color=YELLOW, stroke_width=2)\nself.add(dot, path)\nself.play(dot.animate.shift(RIGHT * 3 + UP * 2), run_time=2)\n# path shows the trail the dot left behind\n\n# Fading trail (dissipates over time):\npath = TracedPath(dot.get_center, dissipating_time=0.5, stroke_opacity=[0, 1])\n```\n\nUse cases: gradient descent paths, planetary orbits, function tracing, particle trajectories.\n\n## FadeTransform — Smoother Cross-Fades\n\n`Transform` morphs shapes through ugly intermediate warping. `FadeTransform` cross-fades with position matching — use it when source and target look different:\n\n```python\n# UGLY: Transform warps circle into square through a blob\nself.play(Transform(circle, square))\n\n# SMOOTH: FadeTransform cross-fades cleanly\nself.play(FadeTransform(circle, square))\n\n# FadeTransformPieces: per-submobject FadeTransform\nself.play(FadeTransformPieces(group1, group2))\n\n# TransformFromCopy: animate a COPY while keeping the original visible\nself.play(TransformFromCopy(source, target))\n# source stays on screen, a copy morphs into target\n```\n\n**Recommendation:** Use `FadeTransform` as default for dissimilar shapes. Use `Transform`/`ReplacementTransform` only for similar shapes (circle→ellipse, equation→equation).\n\n## ApplyMatrix — Linear Transformation Visualization\n\nAnimate a matrix transformation on mobjects:\n\n```python\n# Apply a 2x2 matrix to a grid\nmatrix = [[2, 1], [1, 1]]\nself.play(ApplyMatrix(matrix, number_plane), run_time=2)\n\n# Also works on individual mobjects\nself.play(ApplyMatrix([[0, -1], [1, 0]], square))  # 90-degree rotation\n```\n\nPairs with `LinearTransformationScene` — see `camera-and-3d.md`.\n\n## squish_rate_func — Time-Window Staggering\n\nCompress any rate function into a time window within an animation. Enables overlapping stagger without `LaggedStart`:\n\n```python\nself.play(\n    FadeIn(a, rate_func=squish_rate_func(smooth, 0, 0.5)),    # 0% to 50%\n    FadeIn(b, rate_func=squish_rate_func(smooth, 0.25, 0.75)), # 25% to 75%\n    FadeIn(c, rate_func=squish_rate_func(smooth, 0.5, 1.0)),  # 50% to 100%\n    run_time=2\n)\n```\n\nMore precise than `LaggedStart` when you need exact overlap control.\n\n## Additional Rate Functions\n\n```python\nfrom manim import (\n    smooth, linear, rush_into, rush_from,\n    there_and_back, there_and_back_with_pause,\n    running_start, double_smooth, wiggle,\n    lingering, exponential_decay, not_quite_there,\n    squish_rate_func\n)\n\n# running_start: pulls back before going forward (anticipation)\nself.play(FadeIn(mob, rate_func=running_start))\n\n# there_and_back_with_pause: goes there, holds, comes back\nself.play(mob.animate.shift(UP), rate_func=there_and_back_with_pause)\n\n# not_quite_there: stops at a fraction of the full animation\nself.play(FadeIn(mob, rate_func=not_quite_there(0.7)))\n```\n\n## ShowIncreasingSubsets / ShowSubmobjectsOneByOne\n\nReveal group members progressively — ideal for algorithm visualization:\n\n```python\n# Reveal array elements one at a time\narray = Group(*[Square() for _ in range(8)]).arrange(RIGHT)\nself.play(ShowIncreasingSubsets(array), run_time=3)\n\n# Show submobjects with staggered appearance\nself.play(ShowSubmobjectsOneByOne(code_lines), run_time=4)\n```\n\n## ShowPassingFlash\n\nA flash of light travels along a path:\n\n```python\n# Flash traveling along a curve\nself.play(ShowPassingFlash(curve.copy().set_color(YELLOW), time_width=0.3))\n\n# Great for: data flow, electrical signals, network traffic\n```\n\n<!-- File: skills/manim-video/references/camera-and-3d.md (Tokens: ~1042 | Category: anthropic-skill) -->\n# Camera and 3D Reference\n\n## MovingCameraScene (2D Camera Control)\n\n```python\nclass ZoomExample(MovingCameraScene):\n    def construct(self):\n        circle = Circle(radius=2, color=BLUE)\n        self.play(Create(circle))\n        # Zoom in\n        self.play(self.camera.frame.animate.set(width=4).move_to(circle.get_top()), run_time=2)\n        self.wait(2)\n        # Zoom back out\n        self.play(self.camera.frame.animate.set(width=14.222).move_to(ORIGIN), run_time=2)\n```\n\n### Camera Operations\n\n```python\nself.camera.frame.animate.set(width=6)     # zoom in\nself.camera.frame.animate.set(width=20)    # zoom out\nself.camera.frame.animate.move_to(target)  # pan\nself.camera.frame.save_state()             # save\nself.play(Restore(self.camera.frame))      # restore\n```\n\n## ThreeDScene\n\n```python\nclass ThreeDExample(ThreeDScene):\n    def construct(self):\n        self.set_camera_orientation(phi=60*DEGREES, theta=-45*DEGREES)\n        axes = ThreeDAxes()\n        surface = Surface(\n            lambda u, v: axes.c2p(u, v, np.sin(u) * np.cos(v)),\n            u_range=[-PI, PI], v_range=[-PI, PI], resolution=(30, 30)\n        )\n        surface.set_color_by_gradient(BLUE, GREEN, YELLOW)\n        self.play(Create(axes), Create(surface))\n        self.begin_ambient_camera_rotation(rate=0.2)\n        self.wait(5)\n        self.stop_ambient_camera_rotation()\n```\n\n### Camera Control in 3D\n\n```python\nself.set_camera_orientation(phi=70*DEGREES, theta=-45*DEGREES)\nself.move_camera(phi=45*DEGREES, theta=30*DEGREES, run_time=2)\nself.begin_ambient_camera_rotation(rate=0.2)\n```\n\n### 3D Mobjects\n\n```python\nsphere = Sphere(radius=1).set_color(BLUE).set_opacity(0.7)\ncube = Cube(side_length=2, fill_color=GREEN, fill_opacity=0.5)\narrow = Arrow3D(start=ORIGIN, end=[2, 1, 1], color=RED)\n# 2D text facing camera:\nlabel = Text(\"Label\", font_size=30)\nself.add_fixed_in_frame_mobjects(label)\n```\n\n### Parametric Curves\n\n```python\nhelix = ParametricFunction(\n    lambda t: [np.cos(t), np.sin(t), t / (2*PI)],\n    t_range=[0, 4*PI], color=YELLOW\n)\n```\n\n## When to Use 3D\n- Surfaces, vector fields, spatial geometry, 3D transforms\n## When NOT to Use 3D\n- 2D concepts, text-heavy scenes, flat data (bar charts, time series)\n\n## ZoomedScene — Inset Zoom\n\nShow a magnified inset of a detail while keeping the full view visible:\n\n```python\nclass ZoomExample(ZoomedScene):\n    def __init__(self, **kwargs):\n        super().__init__(\n            zoom_factor=0.3,           # how much of the scene the zoom box covers\n            zoomed_display_height=3,   # size of the inset\n            zoomed_display_width=3,\n            zoomed_camera_frame_starting_position=ORIGIN,\n            **kwargs\n        )\n\n    def construct(self):\n        self.camera.background_color = BG\n        # ... create your scene content ...\n\n        # Activate the zoom\n        self.activate_zooming()\n\n        # Move the zoom frame to a point of interest\n        self.play(self.zoomed_camera.frame.animate.move_to(detail_point))\n        self.wait(2)\n\n        # Deactivate\n        self.play(self.get_zoomed_display_pop_out_animation(), rate_func=lambda t: smooth(1-t))\n```\n\nUse cases: zooming into a specific term in an equation, showing fine detail in a diagram, magnifying a region of a plot.\n\n## LinearTransformationScene — Linear Algebra\n\nPre-built scene with basis vectors and grid for visualizing matrix transformations:\n\n```python\nclass LinearTransformExample(LinearTransformationScene):\n    def __init__(self, **kwargs):\n        super().__init__(\n            show_coordinates=True,\n            show_basis_vectors=True,\n            **kwargs\n        )\n\n    def construct(self):\n        matrix = [[2, 1], [1, 1]]\n\n        # Add a vector before applying the transform\n        vector = self.get_vector([1, 2], color=YELLOW)\n        self.add_vector(vector)\n\n        # Apply the transformation — grid, basis vectors, and your vector all transform\n        self.apply_matrix(matrix)\n        self.wait(2)\n```\n\nThis produces the signature 3Blue1Brown \"Essence of Linear Algebra\" look — grid lines deforming, basis vectors stretching, determinant visualized through area change.\n\n<!-- File: skills/manim-video/references/decorations.md (Tokens: ~1384 | Category: anthropic-skill) -->\n# Decorations and Visual Polish\n\nDecorations are mobjects that annotate, highlight, or frame other mobjects. They turn a technically correct animation into a visually polished one.\n\n## SurroundingRectangle\n\nDraws a rectangle around any mobject. The go-to for highlighting:\n\n```python\nhighlight = SurroundingRectangle(\n    equation[2],            # the term to highlight\n    color=YELLOW,\n    buff=0.15,              # padding between content and border\n    corner_radius=0.1,      # rounded corners\n    stroke_width=2\n)\nself.play(Create(highlight))\nself.wait(1)\nself.play(FadeOut(highlight))\n```\n\n### Around part of an equation\n\n```python\neq = MathTex(r\"E\", r\"=\", r\"m\", r\"c^2\")\nbox = SurroundingRectangle(eq[2:], color=YELLOW, buff=0.1)  # highlight \"mc²\"\nlabel = Text(\"mass-energy\", font_size=18, font=\"Menlo\", color=YELLOW)\nlabel.next_to(box, DOWN, buff=0.2)\nself.play(Create(box), FadeIn(label))\n```\n\n## BackgroundRectangle\n\nSemi-transparent background behind text for readability over complex scenes:\n\n```python\nbg = BackgroundRectangle(equation, fill_opacity=0.7, buff=0.2, color=BLACK)\nself.play(FadeIn(bg), Write(equation))\n\n# Or using set_stroke for a \"backdrop\" effect on the text itself:\nlabel.set_stroke(BLACK, width=5, background=True)\n```\n\nThe `set_stroke(background=True)` approach is cleaner for text labels over graphs/diagrams.\n\n## Brace and BraceLabel\n\nCurly braces that annotate sections of a diagram or equation:\n\n```python\nbrace = Brace(equation[2:4], DOWN, color=YELLOW)\nbrace_label = brace.get_text(\"these terms\", font_size=20)\nself.play(GrowFromCenter(brace), FadeIn(brace_label))\n\n# Between two specific points\nbrace = BraceBetweenPoints(point_a, point_b, direction=UP)\n```\n\n### Brace placement\n\n```python\n# Below a group\nBrace(group, DOWN)\n# Above a group\nBrace(group, UP)\n# Left of a group\nBrace(group, LEFT)\n# Right of a group\nBrace(group, RIGHT)\n```\n\n## Arrows for Annotation\n\n### Straight arrows pointing to mobjects\n\n```python\narrow = Arrow(\n    start=label.get_bottom(),\n    end=target.get_top(),\n    color=YELLOW,\n    stroke_width=2,\n    buff=0.1,                    # gap between arrow tip and target\n    max_tip_length_to_length_ratio=0.15  # small arrowhead\n)\nself.play(GrowArrow(arrow), FadeIn(label))\n```\n\n### Curved arrows\n\n```python\narrow = CurvedArrow(\n    start_point=source.get_right(),\n    end_point=target.get_left(),\n    angle=PI/4,                  # curve angle\n    color=PRIMARY\n)\n```\n\n### Labeling with arrows\n\n```python\n# LabeledArrow: arrow with built-in text label\narr = LabeledArrow(\n    Text(\"gradient\", font_size=16, font=\"Menlo\"),\n    start=point_a, end=point_b, color=RED\n)\n```\n\n## DashedLine and DashedVMobject\n\n```python\n# Dashed line (for asymptotes, construction lines, implied connections)\nasymptote = DashedLine(\n    axes.c2p(2, -3), axes.c2p(2, 3),\n    color=YELLOW, dash_length=0.15\n)\n\n# Make any VMobject dashed\ndashed_circle = DashedVMobject(Circle(radius=2, color=BLUE), num_dashes=30)\n```\n\n## Angle and RightAngle Markers\n\n```python\nline1 = Line(ORIGIN, RIGHT * 2)\nline2 = Line(ORIGIN, UP * 2 + RIGHT)\n\n# Angle arc between two lines\nangle = Angle(line1, line2, radius=0.5, color=YELLOW)\nangle_value = angle.get_value()  # radians\n\n# Right angle marker (the small square)\nright_angle = RightAngle(line1, Line(ORIGIN, UP * 2), length=0.3, color=WHITE)\n```\n\n## Cross (strikethrough)\n\nMark something as wrong or deprecated:\n\n```python\ncross = Cross(old_equation, color=RED, stroke_width=4)\nself.play(Create(cross))\n# Then show the correct version\n```\n\n## Underline\n\n```python\nunderline = Underline(important_text, color=ACCENT, stroke_width=3)\nself.play(Create(underline))\n```\n\n## Color Highlighting Workflow\n\n### Method 1: At creation with t2c\n\n```python\ntext = Text(\"The gradient is negative here\", t2c={\"gradient\": BLUE, \"negative\": RED})\n```\n\n### Method 2: set_color_by_tex after creation\n\n```python\neq = MathTex(r\"\\nabla L = -\\frac{\\partial L}{\\partial w}\")\neq.set_color_by_tex(r\"\\nabla\", BLUE)\neq.set_color_by_tex(r\"\\partial\", RED)\n```\n\n### Method 3: Index into submobjects\n\n```python\neq = MathTex(r\"a\", r\"+\", r\"b\", r\"=\", r\"c\")\neq[0].set_color(RED)    # \"a\"\neq[2].set_color(BLUE)   # \"b\"\neq[4].set_color(GREEN)  # \"c\"\n```\n\n## Combining Annotations\n\nLayer multiple annotations for emphasis:\n\n```python\n# Highlight a term, add a brace, and an arrow — in sequence\nbox = SurroundingRectangle(eq[2], color=YELLOW, buff=0.1)\nbrace = Brace(eq[2], DOWN, color=YELLOW)\nlabel = brace.get_text(\"learning rate\", font_size=18)\n\nself.play(Create(box))\nself.wait(0.5)\nself.play(FadeOut(box), GrowFromCenter(brace), FadeIn(label))\nself.wait(1.5)\nself.play(FadeOut(brace), FadeOut(label))\n```\n\n### The annotation lifecycle\n\nAnnotations should follow a rhythm:\n1. **Appear** — draw attention (Create, GrowFromCenter)\n2. **Hold** — viewer reads and understands (self.wait)\n3. **Disappear** — clear the stage for the next thing (FadeOut)\n\nNever leave annotations on screen indefinitely — they become visual noise once their purpose is served.\n\n<!-- File: skills/manim-video/references/equations.md (Tokens: ~1956 | Category: anthropic-skill) -->\n# Equations and LaTeX Reference\n\n## Basic LaTeX\n\n```python\neq = MathTex(r\"E = mc^2\")\neq = MathTex(r\"f(x) &= x^2 + 2x + 1 \\\\ &= (x + 1)^2\")  # multi-line aligned\n```\n\n**Always use raw strings (`r\"\"`).**\n\n## Step-by-Step Derivations\n\n```python\nstep1 = MathTex(r\"a^2 + b^2 = c^2\")\nstep2 = MathTex(r\"a^2 = c^2 - b^2\")\nself.play(Write(step1), run_time=1.5)\nself.wait(1.5)\nself.play(TransformMatchingTex(step1, step2), run_time=1.5)\n```\n\n## Selective Color\n\n```python\neq = MathTex(r\"a^2\", r\"+\", r\"b^2\", r\"=\", r\"c^2\")\neq[0].set_color(RED)\neq[4].set_color(GREEN)\n```\n\n## Building Incrementally\n\n```python\nparts = MathTex(r\"f(x)\", r\"=\", r\"\\sum_{n=0}^{\\infty}\", r\"\\frac{f^{(n)}(a)}{n!}\", r\"(x-a)^n\")\nself.play(Write(parts[0:2]))\nself.wait(0.5)\nself.play(Write(parts[2]))\nself.wait(0.5)\nself.play(Write(parts[3:]))\n```\n\n## Highlighting\n\n```python\nhighlight = SurroundingRectangle(eq[2], color=YELLOW, buff=0.1)\nself.play(Create(highlight))\nself.play(Indicate(eq[4], color=YELLOW))\n```\n\n## Annotation\n\n```python\nbrace = Brace(eq, DOWN, color=YELLOW)\nlabel = brace.get_text(\"Fundamental Theorem\", font_size=24)\nself.play(GrowFromCenter(brace), Write(label))\n```\n\n## Common LaTeX\n\n```python\nMathTex(r\"\\frac{a}{b}\")                  # fraction\nMathTex(r\"\\alpha, \\beta, \\gamma\")         # Greek\nMathTex(r\"\\sum_{i=1}^{n} x_i\")           # summation\nMathTex(r\"\\int_{0}^{\\infty} e^{-x} dx\")  # integral\nMathTex(r\"\\vec{v}\")                       # vector\nMathTex(r\"\\lim_{x \\to \\infty} f(x)\")    # limit\n```\n\n## Matrices\n\n`MathTex` supports standard LaTeX matrix environments via `amsmath` (loaded by default):\n\n```python\n# Bracketed matrix\nMathTex(r\"\\begin{bmatrix} 1 & 0 \\\\ 0 & 1 \\end{bmatrix}\")\n\n# Parenthesized matrix\nMathTex(r\"\\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix}\")\n\n# Determinant (vertical bars)\nMathTex(r\"\\begin{vmatrix} a & b \\\\ c & d \\end{vmatrix}\")\n\n# Plain (no delimiters)\nMathTex(r\"\\begin{matrix} x_1 \\\\ x_2 \\\\ x_3 \\end{matrix}\")\n```\n\nFor matrices you need to animate element-by-element or color individual entries, use the `IntegerMatrix`, `DecimalMatrix`, or `MobjectMatrix` mobjects instead — see `mobjects.md`.\n\n## Cases and Piecewise Functions\n\n```python\nMathTex(r\"\"\"\n    f(x) = \\begin{cases}\n        x^2    & \\text{if } x \\geq 0 \\\\\n        -x^2   & \\text{if } x < 0\n    \\end{cases}\n\"\"\")\n```\n\n## Aligned Environments\n\nFor multi-line derivations with alignment, use `aligned` inside `MathTex`:\n\n```python\nMathTex(r\"\"\"\n    \\begin{aligned}\n        \\nabla \\cdot \\mathbf{E} &= \\frac{\\rho}{\\epsilon_0} \\\\\n        \\nabla \\cdot \\mathbf{B} &= 0 \\\\\n        \\nabla \\times \\mathbf{E} &= -\\frac{\\partial \\mathbf{B}}{\\partial t} \\\\\n        \\nabla \\times \\mathbf{B} &= \\mu_0 \\mathbf{J} + \\mu_0 \\epsilon_0 \\frac{\\partial \\mathbf{E}}{\\partial t}\n    \\end{aligned}\n\"\"\")\n```\n\nNote: `MathTex` wraps content in `align*` by default. Override with `tex_environment` if needed:\n```python\nMathTex(r\"...\", tex_environment=\"gather*\")\n```\n\n## Derivation Pattern\n\n```python\nclass DerivationScene(Scene):\n    def construct(self):\n        self.camera.background_color = BG\n        s1 = MathTex(r\"ax^2 + bx + c = 0\")\n        self.play(Write(s1))\n        self.wait(1.5)\n        s2 = MathTex(r\"x^2 + \\frac{b}{a}x + \\frac{c}{a} = 0\")\n        s2.next_to(s1, DOWN, buff=0.8)\n        self.play(s1.animate.set_opacity(0.4), TransformMatchingTex(s1.copy(), s2))\n```\n\n## substrings_to_isolate for Complex Equations\n\nFor dense equations where manually splitting into parts is impractical, use `substrings_to_isolate` to tell Manim which substrings to track as individual elements:\n\n```python\n# Without isolation — the whole expression is one blob\nlagrangian = MathTex(\n    r\"\\mathcal{L} = \\bar{\\psi}(i \\gamma^\\mu D_\\mu - m)\\psi - \\tfrac{1}{4}F_{\\mu\\nu}F^{\\mu\\nu}\"\n)\n\n# With isolation — each named substring is a separate submobject\nlagrangian = MathTex(\n    r\"\\mathcal{L} = \\bar{\\psi}(i \\gamma^\\mu D_\\mu - m)\\psi - \\tfrac{1}{4}F_{\\mu\\nu}F^{\\mu\\nu}\",\n    substrings_to_isolate=[r\"\\psi\", r\"D_\\mu\", r\"\\gamma^\\mu\", r\"F_{\\mu\\nu}\"]\n)\n# Now you can color individual terms\nlagrangian.set_color_by_tex(r\"\\psi\", BLUE)\nlagrangian.set_color_by_tex(r\"F_{\\mu\\nu}\", YELLOW)\n```\n\nEssential for `TransformMatchingTex` on complex equations — without isolation, matching fails on dense expressions.\n\n## Multi-Line Complex Equations\n\nFor equations with multiple related lines, pass each line as a separate argument:\n\n```python\nmaxwell = MathTex(\n    r\"\\nabla \\cdot \\mathbf{E} = \\frac{\\rho}{\\epsilon_0}\",\n    r\"\\nabla \\times \\mathbf{B} = \\mu_0\\mathbf{J} + \\mu_0\\epsilon_0\\frac{\\partial \\mathbf{E}}{\\partial t}\"\n).arrange(DOWN)\n\n# Each line is a separate submobject — animate independently\nself.play(Write(maxwell[0]))\nself.wait(1)\nself.play(Write(maxwell[1]))\n```\n\n## TransformMatchingTex with key_map\n\nMap specific substrings between source and target equations during transformation:\n\n```python\neq1 = MathTex(r\"A^2 + B^2 = C^2\")\neq2 = MathTex(r\"A^2 = C^2 - B^2\")\n\nself.play(TransformMatchingTex(\n    eq1, eq2,\n    key_map={\"+\": \"-\"},   # map \"+\" in source to \"-\" in target\n    path_arc=PI / 2,      # arc the pieces into position\n))\n```\n\n## set_color_by_tex — Color by Substring\n\n```python\neq = MathTex(r\"E = mc^2\")\neq.set_color_by_tex(\"E\", BLUE)\neq.set_color_by_tex(\"m\", RED)\neq.set_color_by_tex(\"c\", GREEN)\n```\n\n## TransformMatchingTex with matched_keys\n\nWhen matching substrings are ambiguous, specify which to align explicitly:\n\n```python\nkw = dict(font_size=72, t2c={\"A\": BLUE, \"B\": TEAL, \"C\": GREEN})\nlines = [\n    MathTex(r\"A^2 + B^2 = C^2\", **kw),\n    MathTex(r\"A^2 = C^2 - B^2\", **kw),\n    MathTex(r\"A^2 = (C + B)(C - B)\", **kw),\n    MathTex(r\"A = \\sqrt{(C + B)(C - B)}\", **kw),\n]\n\nself.play(TransformMatchingTex(\n    lines[0].copy(), lines[1],\n    matched_keys=[\"A^2\", \"B^2\", \"C^2\"],  # explicitly match these\n    key_map={\"+\": \"-\"},                    # map + to -\n    path_arc=PI / 2,                       # arc pieces into position\n))\n```\n\nWithout `matched_keys`, the animation matches the longest common substrings, which can produce unexpected results on complex equations (e.g., \"^2 = C^2\" matching across terms).\n\n<!-- File: skills/manim-video/references/graphs-and-data.md (Tokens: ~1449 | Category: anthropic-skill) -->\n# Graphs, Plots, and Data Visualization\n\n## Axes\n\n```python\naxes = Axes(\n    x_range=[-3, 3, 1], y_range=[-2, 2, 1],\n    x_length=8, y_length=5,\n    axis_config={\"include_numbers\": True, \"font_size\": 24}\n)\naxes.set_opacity(0.15)  # structural element\nx_label = axes.get_x_axis_label(r\"x\")\n```\n\n## Plotting\n\n```python\ngraph = axes.plot(lambda x: x**2, color=BLUE)\ngraph_label = axes.get_graph_label(graph, label=r\"x^2\", x_val=2)\narea = axes.get_area(graph, x_range=[0, 2], color=BLUE, opacity=0.3)\n```\n\n## Animated Plotting\n\n```python\nself.play(Create(graph), run_time=3)  # trace the graph\n\n# Moving dot along curve\ndot = Dot(color=YELLOW).move_to(axes.c2p(0, 0))\nself.play(MoveAlongPath(dot, graph), run_time=3)\n\n# Dynamic parameter\ntracker = ValueTracker(1)\ndynamic = always_redraw(lambda: axes.plot(lambda x: tracker.get_value() * x**2, color=BLUE))\nself.add(dynamic)\nself.play(tracker.animate.set_value(3), run_time=2)\n```\n\n## Bar Charts\n\n```python\nchart = BarChart(\n    values=[4, 6, 2, 8, 5], bar_names=[\"A\", \"B\", \"C\", \"D\", \"E\"],\n    y_range=[0, 10, 2], bar_colors=[RED, GREEN, BLUE, YELLOW, PURPLE]\n)\nself.play(Create(chart), run_time=2)\nself.play(chart.animate.change_bar_values([6, 3, 7, 4, 9]))\n```\n\n## Number Lines\n\n```python\nnl = NumberLine(x_range=[0, 10, 1], length=10, include_numbers=True)\npointer = Arrow(nl.n2p(3) + UP * 0.5, nl.n2p(3), color=RED, buff=0)\ntracker = ValueTracker(3)\npointer.add_updater(lambda m: m.put_start_and_end_on(\n    nl.n2p(tracker.get_value()) + UP * 0.5, nl.n2p(tracker.get_value())))\nself.play(tracker.animate.set_value(8), run_time=2)\n```\n\n## Animated Counters\n\n```python\ncounter = DecimalNumber(0, font_size=72, num_decimal_places=0)\nself.play(counter.animate.set_value(1000), run_time=3, rate_func=rush_from)\n```\n\n## Algorithm Visualization Pattern\n\n```python\nvalues = [5, 2, 8, 1, 9, 3]\nbars = VGroup(*[\n    Rectangle(width=0.6, height=v * 0.4, color=BLUE, fill_opacity=0.7)\n    for v in values\n]).arrange(RIGHT, buff=0.2, aligned_edge=DOWN).move_to(ORIGIN)\nself.play(LaggedStart(*[GrowFromEdge(b, DOWN) for b in bars], lag_ratio=0.1))\n# Highlight, swap, etc.\n```\n\n## Data Story Pattern\n\n```python\n# Before/After comparison\nbefore = BarChart(values=[3, 5, 2], bar_colors=[RED]*3).shift(LEFT * 3)\nafter = BarChart(values=[8, 9, 7], bar_colors=[GREEN]*3).shift(RIGHT * 3)\nself.play(Create(before)); self.wait(1)\nself.play(Create(after)); self.wait(1)\narrow = Arrow(before.get_right(), after.get_left(), color=YELLOW)\nlabel = Text(\"+167%\", font_size=36, color=YELLOW).next_to(arrow, UP)\nself.play(GrowArrow(arrow), Write(label))\n```\n\n## Graph / DiGraph — Graph Theory Visualization\n\nBuilt-in graph mobjects with automatic layout:\n\n```python\n# Undirected graph\ng = Graph(\n    vertices=[1, 2, 3, 4, 5],\n    edges=[(1, 2), (2, 3), (3, 4), (4, 5), (5, 1), (1, 3)],\n    layout=\"spring\",  # or \"circular\", \"kamada_kawai\", \"planar\", \"tree\"\n    labels=True,\n    vertex_config={\"fill_color\": PRIMARY},\n    edge_config={\"stroke_color\": SUBTLE},\n)\nself.play(Create(g))\n\n# Directed graph\ndg = DiGraph(\n    vertices=[\"A\", \"B\", \"C\"],\n    edges=[(\"A\", \"B\"), (\"B\", \"C\"), (\"C\", \"A\")],\n    layout=\"circular\",\n    labels=True,\n    edge_config={(\"A\", \"B\"): {\"stroke_color\": RED}},\n)\n\n# Add/remove vertices and edges dynamically\nself.play(g.animate.add_vertices(6, positions={6: RIGHT * 2}))\nself.play(g.animate.add_edges((1, 6)))\nself.play(g.animate.remove_vertices(3))\n```\n\nLayout algorithms: `\"spring\"`, `\"circular\"`, `\"kamada_kawai\"`, `\"planar\"`, `\"spectral\"`, `\"tree\"` (for rooted trees, specify `root=`).\n\n## ArrowVectorField / StreamLines — Vector Fields\n\n```python\n# Arrow field: arrows showing direction at each point\nfield = ArrowVectorField(\n    lambda pos: np.array([-pos[1], pos[0], 0]),  # rotation field\n    x_range=[-3, 3], y_range=[-3, 3],\n    colors=[BLUE, GREEN, YELLOW, RED]\n)\nself.play(Create(field))\n\n# StreamLines: flowing particle traces through the field\nstream = StreamLines(\n    lambda pos: np.array([-pos[1], pos[0], 0]),\n    stroke_width=2, max_anchors_per_line=30\n)\nself.add(stream)\nstream.start_animation(warm_up=True, flow_speed=1.5)\nself.wait(3)\nstream.end_animation()\n```\n\nUse cases: electromagnetic fields, fluid flow, gradient fields, ODE phase portraits.\n\n## ComplexPlane / PolarPlane\n\n```python\n# Complex plane with Re/Im labels\ncplane = ComplexPlane().add_coordinates()\ndot = Dot(cplane.n2p(2 + 1j), color=YELLOW)\nlabel = Text(\"2+i\", font_size=20).next_to(dot, UR, buff=0.1)\n\n# Apply complex function to the plane\nself.play(cplane.animate.apply_complex_function(lambda z: z**2), run_time=3)\n\n# Polar plane\npolar = PolarPlane(radius_max=3).add_coordinates()\n```\n\n<!-- File: skills/manim-video/references/mobjects.md (Tokens: ~2847 | Category: anthropic-skill) -->\n# Mobjects Reference\n\nEverything visible on screen is a Mobject. They have position, color, opacity, and can be animated.\n\n## Text\n\n```python\ntitle = Text(\"Hello World\", font_size=48, color=BLUE)\neq = MathTex(r\"E = mc^2\", font_size=40)\n\n# Multi-part (for selective coloring)\neq = MathTex(r\"a^2\", r\"+\", r\"b^2\", r\"=\", r\"c^2\")\neq[0].set_color(RED)\neq[4].set_color(BLUE)\n\n# Mixed text and math\nt = Tex(r\"The area is $\\pi r^2$\", font_size=36)\n\n# Styled markup\nt = MarkupText('<span foreground=\"#58C4DD\">Blue</span> text', font_size=30)\n```\n\n**Always use raw strings (`r\"\"`) for any string with backslashes.**\n\n## Shapes\n\n```python\ncircle = Circle(radius=1, color=BLUE, fill_opacity=0.5)\nsquare = Square(side_length=2, color=RED)\nrect = Rectangle(width=4, height=2, color=GREEN)\ndot = Dot(point=ORIGIN, radius=0.08, color=YELLOW)\nline = Line(LEFT * 2, RIGHT * 2, color=WHITE)\narrow = Arrow(LEFT, RIGHT, color=ORANGE)\nrrect = RoundedRectangle(corner_radius=0.3, width=4, height=2)\nbrace = Brace(rect, DOWN, color=YELLOW)\n```\n\n## Polygons and Arcs\n\n```python\n# Arbitrary polygon from vertices\npoly = Polygon(LEFT, UP * 2, RIGHT, color=GREEN, fill_opacity=0.3)\n\n# Regular n-sided polygon\nhexagon = RegularPolygon(n=6, color=TEAL, fill_opacity=0.4)\n\n# Triangle (shorthand for RegularPolygon(n=3))\ntri = Triangle(color=YELLOW, fill_opacity=0.5)\n\n# Arc (portion of a circle)\narc = Arc(radius=2, start_angle=0, angle=PI / 2, color=BLUE)\n\n# Arc between two points\narc_between = ArcBetweenPoints(LEFT * 2, RIGHT * 2, angle=TAU / 4, color=RED)\n\n# Curved arrow (arc with tip)\ncurved_arrow = CurvedArrow(LEFT * 2, RIGHT * 2, color=ORANGE)\n```\n\n## Sectors and Annuli\n\n```python\n# Sector (pie slice)\nsector = Sector(outer_radius=2, start_angle=0, angle=PI / 3, fill_opacity=0.7, color=BLUE)\n\n# Annulus (ring)\nring = Annulus(inner_radius=1, outer_radius=2, fill_opacity=0.5, color=GREEN)\n\n# Annular sector (partial ring)\npartial_ring = AnnularSector(\n    inner_radius=1, outer_radius=2,\n    angle=PI / 2, start_angle=0,\n    fill_opacity=0.7, color=TEAL\n)\n\n# Cutout (punch holes in a shape)\nbackground = Square(side_length=4, fill_opacity=1, color=BLUE)\nhole = Circle(radius=0.5)\ncutout = Cutout(background, hole, fill_opacity=1, color=BLUE)\n```\n\nUse cases: pie charts, ring progress indicators, Venn diagrams with arcs, geometric proofs.\n\n## Positioning\n\n```python\nmob.move_to(ORIGIN)                        # center\nmob.move_to(UP * 2 + RIGHT)               # relative\nlabel.next_to(circle, DOWN, buff=0.3)     # next to another\ntitle.to_edge(UP, buff=0.5)               # screen edge (buff >= 0.5!)\nmob.to_corner(UL, buff=0.5)               # corner\n```\n\n## VGroup vs Group\n\n**VGroup** is for collections of shapes (VMobjects only — Circle, Square, Arrow, Line, MathTex):\n```python\nshapes = VGroup(circle, square, arrow)\nshapes.arrange(DOWN, buff=0.5)\nshapes.set_color(BLUE)\n```\n\n**Group** is for mixed collections (Text + shapes, or any Mobject types):\n```python\n# Text objects are Mobjects, not VMobjects — use Group when mixing\nlabeled_shape = Group(circle, Text(\"Label\").next_to(circle, DOWN))\nlabeled_shape.move_to(ORIGIN)\n\n# FadeOut everything on screen (may contain mixed types)\nself.play(FadeOut(Group(*self.mobjects)))\n```\n\n**Rule: if your group contains any `Text()` objects, use `Group`, not `VGroup`.** VGroup will raise a TypeError on Manim CE v0.20+. MathTex and Tex are VMobjects and work with VGroup.\n\nBoth support `arrange()`, `arrange_in_grid()`, `set_opacity()`, `shift()`, `scale()`, `move_to()`.\n\n## Styling\n\n```python\nmob.set_color(BLUE)\nmob.set_fill(RED, opacity=0.5)\nmob.set_stroke(WHITE, width=2)\nmob.set_opacity(0.4)\nmob.set_z_index(1)                         # layering\n```\n\n## Specialized Mobjects\n\n```python\nnl = NumberLine(x_range=[-3, 3, 1], length=8, include_numbers=True)\ntable = Table([[\"A\", \"B\"], [\"C\", \"D\"]], row_labels=[Text(\"R1\"), Text(\"R2\")])\ncode = Code(\"example.py\", tab_width=4, font_size=20, language=\"python\")\nhighlight = SurroundingRectangle(target, color=YELLOW, buff=0.2)\nbg = BackgroundRectangle(equation, fill_opacity=0.7, buff=0.2)\n```\n\n## Custom Mobjects\n\n```python\nclass NetworkNode(Group):\n    def __init__(self, label_text, color=BLUE, **kwargs):\n        super().__init__(**kwargs)\n        self.circle = Circle(radius=0.4, color=color, fill_opacity=0.3)\n        self.label = Text(label_text, font_size=20).move_to(self.circle)\n        self.add(self.circle, self.label)\n```\n\n## Matrix Mobjects\n\nDisplay matrices as grids of numbers or mobjects:\n\n```python\n# Integer matrix\nm = IntegerMatrix([[1, 2], [3, 4]])\n\n# Decimal matrix (control decimal places)\nm = DecimalMatrix([[1.5, 2.7], [3.1, 4.9]], element_to_mobject_config={\"num_decimal_places\": 2})\n\n# Mobject matrix (any mobject in each cell)\nm = MobjectMatrix([\n    [MathTex(r\"\\pi\"), MathTex(r\"e\")],\n    [MathTex(r\"\\phi\"), MathTex(r\"\\tau\")]\n])\n\n# Bracket types: \"(\" \"[\" \"|\" or \"\\\\{\"\nm = IntegerMatrix([[1, 0], [0, 1]], left_bracket=\"[\", right_bracket=\"]\")\n```\n\nUse cases: linear algebra, transformation matrices, system-of-equations coefficient display.\n\n## Constants\n\nDirections: `UP, DOWN, LEFT, RIGHT, ORIGIN, UL, UR, DL, DR`\nColors: `RED, BLUE, GREEN, YELLOW, WHITE, GRAY, ORANGE, PINK, PURPLE, TEAL, GOLD`\nFrame: `config.frame_width = 14.222, config.frame_height = 8.0`\n\n## SVGMobject — Import SVG Files\n\n```python\nlogo = SVGMobject(\"path/to/logo.svg\")\nlogo.set_color(WHITE).scale(0.5).to_corner(UR)\nself.play(FadeIn(logo))\n\n# SVG submobjects are individually animatable\nfor part in logo.submobjects:\n    self.play(part.animate.set_color(random_color()))\n```\n\n## ImageMobject — Display Images\n\n```python\nimg = ImageMobject(\"screenshot.png\")\nimg.set_height(3).to_edge(RIGHT)\nself.play(FadeIn(img))\n```\n\nNote: images cannot be animated with `.animate` (they're raster, not vector). Use `FadeIn`/`FadeOut` and `shift`/`scale` only.\n\n## Variable — Auto-Updating Display\n\n```python\nvar = Variable(0, Text(\"x\"), num_decimal_places=2)\nvar.move_to(ORIGIN)\nself.add(var)\n\n# Animate the value\nself.play(var.tracker.animate.set_value(5), run_time=2)\n# Display auto-updates: \"x = 5.00\"\n```\n\nCleaner than manual `DecimalNumber` + `add_updater` for simple labeled-value displays.\n\n## BulletedList\n\n```python\nbullets = BulletedList(\n    \"First key point\",\n    \"Second important fact\",\n    \"Third conclusion\",\n    font_size=28\n)\nbullets.to_edge(LEFT, buff=1.0)\nself.play(Write(bullets))\n\n# Highlight individual items\nself.play(bullets[1].animate.set_color(YELLOW))\n```\n\n## DashedLine and Angle Markers\n\n```python\n# Dashed line (asymptotes, construction lines)\ndashed = DashedLine(LEFT * 3, RIGHT * 3, color=SUBTLE, dash_length=0.15)\n\n# Angle marker between two lines\nline1 = Line(ORIGIN, RIGHT * 2)\nline2 = Line(ORIGIN, UP * 2 + RIGHT)\nangle = Angle(line1, line2, radius=0.5, color=YELLOW)\nangle_label = angle.get_value()  # returns the angle in radians\n\n# Right angle marker\nright_angle = RightAngle(line1, Line(ORIGIN, UP * 2), length=0.3, color=WHITE)\n```\n\n## Boolean Operations (CSG)\n\nCombine, subtract, or intersect 2D shapes:\n\n```python\ncircle = Circle(radius=1.5, color=BLUE, fill_opacity=0.5).shift(LEFT * 0.5)\nsquare = Square(side_length=2, color=RED, fill_opacity=0.5).shift(RIGHT * 0.5)\n\n# Union, Intersection, Difference, Exclusion\nunion = Union(circle, square, color=GREEN, fill_opacity=0.5)\nintersect = Intersection(circle, square, color=YELLOW, fill_opacity=0.5)\ndiff = Difference(circle, square, color=PURPLE, fill_opacity=0.5)\nexclude = Exclusion(circle, square, color=ORANGE, fill_opacity=0.5)\n```\n\nUse cases: Venn diagrams, set theory, geometric proofs, area calculations.\n\n## LabeledArrow / LabeledLine\n\n```python\n# Arrow with built-in label (auto-positioned)\narr = LabeledArrow(Text(\"force\", font_size=18), start=LEFT, end=RIGHT, color=RED)\n\n# Line with label\nline = LabeledLine(Text(\"d = 5m\", font_size=18), start=LEFT * 2, end=RIGHT * 2)\n```\n\nAuto-handles label positioning — cleaner than manual `Arrow` + `Text().next_to()`.\n\n## Text Color/Font/Style Per-Substring (t2c, t2f, t2s, t2w)\n\n```python\n# Color specific words (t2c = text-to-color)\ntext = Text(\n    \"Gradient descent minimizes the loss function\",\n    t2c={\"Gradient descent\": BLUE, \"loss function\": RED}\n)\n\n# Different fonts per word (t2f = text-to-font)\ntext = Text(\n    \"Use Menlo for code and Inter for prose\",\n    t2f={\"Menlo\": \"Menlo\", \"Inter\": \"Inter\"}\n)\n\n# Italic/slant per word (t2s = text-to-slant)\ntext = Text(\"Normal and italic text\", t2s={\"italic\": ITALIC})\n\n# Bold per word (t2w = text-to-weight)\ntext = Text(\"Normal and bold text\", t2w={\"bold\": BOLD})\n```\n\nThese are much cleaner than creating separate Text objects and grouping them.\n\n## Backstroke for Readability Over Backgrounds\n\nWhen text overlaps other content (graphs, diagrams, images), add a dark stroke behind it:\n\n```python\n# CE syntax:\nlabel.set_stroke(BLACK, width=5, background=True)\n\n# Apply to a group\nfor mob in labels:\n    mob.set_stroke(BLACK, width=4, background=True)\n```\n\nThis is how 3Blue1Brown keeps text readable over complex backgrounds without using BackgroundRectangle.\n\n## Complex Function Transforms\n\nApply complex functions to entire mobjects — transforms the plane:\n\n```python\nc_grid = ComplexPlane()\nmoving_grid = c_grid.copy()\nmoving_grid.prepare_for_nonlinear_transform()  # adds more sample points for smooth deformation\n\nself.play(\n    moving_grid.animate.apply_complex_function(lambda z: z**2),\n    run_time=5,\n)\n\n# Also works with R3->R3 functions:\nself.play(grid.animate.apply_function(\n    lambda p: [p[0] + 0.5 * math.sin(p[1]), p[1] + 0.5 * math.sin(p[0]), p[2]]\n), run_time=5)\n```\n\n**Critical:** Call `prepare_for_nonlinear_transform()` before applying nonlinear functions — without it, the grid has too few sample points and the deformation looks jagged.\n\n<!-- File: skills/manim-video/references/paper-explainer.md (Tokens: ~2375 | Category: anthropic-skill) -->\n# Paper Explainer Workflow\n\nHow to turn a research paper into an animated explainer video.\n\n## Why animate a paper?\n\nA research paper is optimized for precision and completeness. A video is optimized for understanding and retention. The translation is NOT \"read the paper aloud with pictures\" — it's \"extract the core insight and make it feel obvious through visual storytelling.\"\n\nThe paper has one job: prove the claim is true. The video has a different job: make the viewer understand WHY the claim is true, and WHY it matters.\n\n## Who is watching?\n\nBefore anything, decide the audience:\n\n| Audience | Prerequisites | Pacing | Depth |\n|----------|--------------|--------|-------|\n| General public | None | Slow, many analogies | Intuition only, skip proofs |\n| Undergrad students | Basic math/CS | Medium, some formalism | Key equations, skip derivations |\n| Grad students / researchers | Domain knowledge | Faster, more notation | Full equations, sketch proofs |\n\nThis determines everything: vocabulary, pacing, which sections to animate, how much math to show.\n\n## The 5-minute template\n\nMost paper explainers fit this structure (scale times proportionally for longer videos):\n\n| Section | Duration | Purpose |\n|---------|----------|---------|\n| **Hook** | 0:00-0:30 | Surprising result or provocative question |\n| **Problem** | 0:30-1:30 | What was broken/missing before this paper |\n| **Key insight** | 1:30-3:00 | The core idea, explained visually |\n| **How it works** | 3:00-4:00 | Method/algorithm, simplified |\n| **Evidence** | 4:00-4:30 | Key result that proves it works |\n| **Implications** | 4:30-5:00 | Why it matters, what it enables |\n\n### What to skip\n\n- Related work survey → one sentence: \"Previous approaches did X, which had problem Y\"\n- Implementation details → skip unless they're the contribution\n- Ablation studies → show one chart at most\n- Proofs → show the key step, not the full proof\n- Hyperparameter tuning → skip entirely\n\n### What to expand\n\n- The core insight → this gets the most screen time\n- Geometric/visual intuition → if the paper has math, show what it MEANS\n- Before/after comparison → the most compelling evidence\n\n## Pre-code workflow\n\n### Gate 1: Narration script\n\nWrite the full narration before any code. Every sentence maps to a visual beat. If you can't write the narration, you don't understand the paper well enough to animate it.\n\n```markdown\n## Hook (30s)\n\"What if I told you that a model with 7 billion parameters can outperform\none with 70 billion — if you train it on the right data?\"\n\n## Problem (60s)\n\"The standard approach is to scale up. More parameters, more compute.\n[VISUAL: bar chart showing model sizes growing exponentially]\nBut Chinchilla showed us that most models are undertrained...\"\n```\n\n### Gate 2: Scene list\n\nAfter the narration, break it into scenes. Each scene is one Manim class.\n\n```markdown\nScene 1: Hook — surprising stat with animated counter\nScene 2: Problem — model size bar chart growing\nScene 3: Key insight — training data vs parameters, animated 2D plot\nScene 4: Method — pipeline diagram building left to right\nScene 5: Results — before/after comparison with animated bars\nScene 6: Closing — implications text\n```\n\n### Gate 3: Style constants\n\nBefore coding scenes, define the visual language:\n\n```python\n# style.py — import in every scene file\nBG = \"#0D1117\"\nPRIMARY = \"#58C4DD\"\nSECONDARY = \"#83C167\"\nACCENT = \"#FFFF00\"\nHIGHLIGHT = \"#FF6B6B\"\nMONO = \"Menlo\"\n\n# Color meanings for THIS paper\nMODEL_COLOR = PRIMARY      # \"the model\"\nDATA_COLOR = SECONDARY     # \"training data\"\nBASELINE_COLOR = HIGHLIGHT # \"previous approach\"\nRESULT_COLOR = ACCENT      # \"our result\"\n```\n\n## First-principles equation explanation\n\nWhen the paper has a key equation, don't just show it — build it from intuition:\n\n### The \"what would you do?\" pattern\n\n1. Pose the problem in plain language\n2. Ask what the simplest solution would be\n3. Show why it doesn't work (animate the failure)\n4. Introduce the paper's solution as the fix\n5. THEN show the equation — it now feels earned\n\n```python\n# Scene: Why we need attention (for a Transformer paper)\n# Step 1: \"How do we let each word look at every other word?\"\n# Step 2: Show naive approach (fully connected = O(n²) everything)\n# Step 3: Show it breaks (information overload, no selectivity)\n# Step 4: \"What if each word could CHOOSE which words to attend to?\"\n# Step 5: Show attention equation — Q, K, V now mean something\n```\n\n### Equation reveal strategy\n\n```python\n# Show equation dimmed first (full destination)\neq = MathTex(r\"Attention(Q,K,V) = softmax\\left(\\frac{QK^T}{\\sqrt{d_k}}\\right)V\")\neq.set_opacity(0.15)\nself.play(FadeIn(eq))\n\n# Highlight Q, K, V one at a time with color + label\nfor part, color, label_text in [\n    (r\"Q\", PRIMARY, \"Query: what am I looking for?\"),\n    (r\"K\", SECONDARY, \"Key: what do I contain?\"),\n    (r\"V\", ACCENT, \"Value: what do I output?\"),\n]:\n    eq.set_color_by_tex(part, color)\n    label = Text(label_text, font_size=18, color=color, font=MONO)\n    # position label, animate it, wait, then dim it\n```\n\n## Building architecture diagrams\n\n### The progressive build pattern\n\nDon't show the full architecture at once. Build it:\n\n1. First component appears alone → explain\n2. Arrow grows → \"this feeds into...\"\n3. Second component appears → explain\n4. Repeat until complete\n\n```python\n# Component factory\ndef make_box(label, color, width=2.0, height=0.8):\n    box = RoundedRectangle(corner_radius=0.1, width=width, height=height,\n                           color=color, fill_opacity=0.1, stroke_width=1.5)\n    text = Text(label, font_size=18, font=MONO, color=color).move_to(box)\n    return Group(box, text)\n\nencoder = make_box(\"Encoder\", PRIMARY)\ndecoder = make_box(\"Decoder\", SECONDARY).next_to(encoder, RIGHT, buff=1.5)\narrow = Arrow(encoder.get_right(), decoder.get_left(), color=DIM, stroke_width=1.5)\n\nself.play(FadeIn(encoder))\nself.wait(1)  # explain encoder\nself.play(GrowArrow(arrow))\nself.play(FadeIn(decoder))\nself.wait(1)  # explain decoder\n```\n\n### Data flow animation\n\nAfter building the diagram, show data moving through it:\n\n```python\n# Dot traveling along the pipeline\ndata_dot = Dot(color=ACCENT, radius=0.1).move_to(encoder)\nself.play(FadeIn(data_dot))\nself.play(MoveAlongPath(data_dot, arrow), run_time=1)\nself.play(data_dot.animate.move_to(decoder), run_time=0.5)\nself.play(Flash(data_dot.get_center(), color=ACCENT), run_time=0.3)\n```\n\n## Animating results\n\n### Bar chart comparison (most common)\n\n```python\n# Before/after bars\nbefore_data = [45, 52, 38, 61]\nafter_data = [78, 85, 72, 91]\nlabels = [\"Task A\", \"Task B\", \"Task C\", \"Task D\"]\n\nbefore_chart = BarChart(before_data, bar_names=labels,\n    y_range=[0, 100, 20], bar_colors=[HIGHLIGHT]*4).scale(0.6).shift(LEFT*3)\nafter_chart = BarChart(after_data, bar_names=labels,\n    y_range=[0, 100, 20], bar_colors=[SECONDARY]*4).scale(0.6).shift(RIGHT*3)\n\nbefore_label = Text(\"Baseline\", font_size=20, color=HIGHLIGHT, font=MONO)\nafter_label = Text(\"Ours\", font_size=20, color=SECONDARY, font=MONO)\n\n# Reveal baseline first, then ours (dramatic comparison)\nself.play(Create(before_chart), FadeIn(before_label))\nself.wait(1.5)\nself.play(Create(after_chart), FadeIn(after_label))\nself.wait(0.5)\n\n# Highlight the improvement\nimprovement = Text(\"+35% avg\", font_size=24, color=ACCENT, font=MONO)\nself.play(FadeIn(improvement))\n```\n\n### Training curve (for ML papers)\n\n```python\ntracker = ValueTracker(0)\ncurve = always_redraw(lambda: axes.plot(\n    lambda x: 1 - 0.8 * np.exp(-x / 3),\n    x_range=[0, tracker.get_value()], color=PRIMARY\n))\nepoch_label = always_redraw(lambda: Text(\n    f\"Epoch {int(tracker.get_value())}\", font_size=18, font=MONO\n).to_corner(UR))\n\nself.add(curve, epoch_label)\nself.play(tracker.animate.set_value(10), run_time=5, rate_func=linear)\n```\n\n## Domain-specific patterns\n\n### ML papers\n- Show data flow through the model (animated pipeline)\n- Training curves with `ValueTracker`\n- Attention heatmaps as colored grids\n- Embedding space as 2D scatter (PCA/t-SNE visualization)\n- Loss landscape as 3D surface with gradient descent dot\n\n### Physics/math papers\n- Use `LinearTransformationScene` for linear algebra\n- Vector fields with `ArrowVectorField` / `StreamLines`\n- Phase spaces with `NumberPlane` + trajectories\n- Wave equations with time-parameterized plots\n\n### Systems/architecture papers\n- Pipeline diagrams built progressively\n- `ShowPassingFlash` for data flow along arrows\n- `ZoomedScene` for zooming into components\n- Before/after latency/throughput comparisons\n\n## Common mistakes\n\n1. **Trying to cover the whole paper.** A 5-minute video can explain ONE core insight well. Covering everything means explaining nothing.\n2. **Reading the abstract as narration.** Academic writing is designed for readers, not listeners. Rewrite in conversational language.\n3. **Showing notation without meaning.** Never show a symbol without first showing what it represents visually.\n4. **Skipping the motivation.** Jumping straight to \"here's our method\" without showing why the problem matters. The Problem section is what makes the viewer care.\n5. **Identical pacing throughout.** The hook and key insight need the most visual energy. The method section can be faster. Evidence should land with impact (pause after showing the big number).\n\n<!-- File: skills/manim-video/references/production-quality.md (Tokens: ~1561 | Category: anthropic-skill) -->\n# Production Quality Checklist\n\nStandards and checks for ensuring animation output is publication-ready.\n\n## Pre-Code Checklist\n\nBefore writing any Manim code:\n\n- [ ] Narration script written with visual beats marked\n- [ ] Scene list with purpose, duration, and layout for each\n- [ ] Color palette defined with meaning assignments (`PRIMARY` = main concept, etc.)\n- [ ] `MONO = \"Menlo\"` set as the font constant\n- [ ] Target resolution and aspect ratio decided\n\n## Text Quality\n\n### Overlap prevention\n\n```python\n# RULE: buff >= 0.5 for edge text\nlabel.to_edge(DOWN, buff=0.5)     # GOOD\nlabel.to_edge(DOWN, buff=0.3)     # BAD — may clip\n\n# RULE: FadeOut previous before adding new at same position\nself.play(ReplacementTransform(note1, note2))  # GOOD\nself.play(Write(note2))                          # BAD — overlaps note1\n\n# RULE: Reduce font size for dense scenes\n# When > 4 text elements visible, use font_size=20 not 28\n```\n\n### Width enforcement\n\nLong text strings overflow the frame:\n\n```python\n# RULE: Set max width for any text that might be long\ntext = Text(\"This is a potentially long description\", font_size=22, font=MONO)\nif text.width > config.frame_width - 1.0:\n    text.set_width(config.frame_width - 1.0)\n```\n\n### Font consistency\n\n```python\n# RULE: Define MONO once, use everywhere\nMONO = \"Menlo\"\n\n# WRONG: mixing fonts\nText(\"Title\", font=\"Helvetica\")\nText(\"Label\", font=\"Arial\")\nText(\"Code\", font=\"Courier\")\n\n# RIGHT: one font\nText(\"Title\", font=MONO, weight=BOLD, font_size=48)\nText(\"Label\", font=MONO, font_size=20)\nText(\"Code\", font=MONO, font_size=18)\n```\n\n## Spatial Layout\n\n### The coordinate budget\n\nThe visible frame is approximately 14.2 wide × 8.0 tall (default 16:9). With mandatory margins:\n\n```\nUsable area: x ∈ [-6.5, 6.5], y ∈ [-3.5, 3.5]\nTop title zone: y ∈ [2.5, 3.5]\nBottom note zone: y ∈ [-3.5, -2.5]\nMain content: y ∈ [-2.5, 2.5], x ∈ [-6.0, 6.0]\n```\n\n### Fill the frame\n\nEmpty scenes look unfinished. If the main content is small, add context:\n- A dimmed grid/axes behind the content\n- A title/subtitle at the top\n- A source citation at the bottom\n- Decorative geometry at low opacity\n\n### Maximum simultaneous elements\n\n**Hard limit: 6 actively visible elements.** Beyond that, the viewer can't track everything. If you need more:\n- Dim old elements to opacity 0.3\n- Remove elements that have served their purpose\n- Split into two scenes\n\n## Animation Quality\n\n### Variety audit\n\nCheck that no two consecutive scenes use the exact same:\n- Animation type (if Scene 3 uses Write for everything, Scene 4 should use FadeIn or Create)\n- Color emphasis (rotate through palette colors)\n- Layout (center, left-right, grid — alternate)\n- Pacing (if Scene 2 was slow and deliberate, Scene 3 can be faster)\n\n### Tempo curve\n\nA good video follows a tempo curve:\n\n```\nSlow ──→ Medium ──→ FAST (climax) ──→ Slow (conclusion)\n\nScene 1: Slow (introduction, setup)\nScene 2: Medium (building understanding)\nScene 3: Medium-Fast (core content, lots of animation)\nScene 4: FAST (montage of applications/results)\nScene 5: Slow (conclusion, key takeaway)\n```\n\n### Transition quality\n\nBetween scenes:\n- **Clean exit**: `self.play(FadeOut(Group(*self.mobjects)), run_time=0.5)`\n- **Brief pause**: `self.wait(0.3)` after fadeout, before next scene's first animation\n- **Never hard-cut**: always animate the transition\n\n## Color Quality\n\n### Dimming on dark backgrounds\n\nColors that look vibrant on white look muddy on dark backgrounds (#0D1117, #1C1C1C). Test your palette:\n\n```python\n# Colors that work well on dark backgrounds:\n# Bright and saturated: #58C4DD, #83C167, #FFFF00, #FF6B6B\n# Colors that DON'T work: #666666 (invisible), #2244AA (too dark)\n\n# RULE: Structural elements (axes, grids) at opacity 0.15\n# Context elements at 0.3-0.4\n# Primary elements at 1.0\n```\n\n### Color meaning consistency\n\nOnce a color is assigned a meaning, it keeps that meaning for the entire video:\n\n```python\n# If PRIMARY (#58C4DD) means \"the model\" in Scene 1,\n# it means \"the model\" in every scene.\n# Never reuse PRIMARY for a different concept later.\n```\n\n## Data Visualization Quality\n\n### Minimum requirements for charts\n\n- Axis labels on every axis\n- Y-axis range starts at 0 (or has a clear break indicator)\n- Bar/line colors match the legend\n- Numbers on notable data points (at least the maximum and the comparison point)\n\n### Animated counters\n\nWhen showing a number changing:\n```python\n# GOOD: DecimalNumber with smooth animation\ncounter = DecimalNumber(0, font_size=48, num_decimal_places=0, font=\"Menlo\")\nself.play(counter.animate.set_value(1000), run_time=3, rate_func=rush_from)\n\n# BAD: Text that jumps between values\n```\n\n## Pre-Render Checklist\n\nBefore running `manim -qh`:\n\n- [ ] All scenes render without errors at `-ql`\n- [ ] Preview stills at `-qm` for text-heavy scenes (check kerning)\n- [ ] Background color set in every scene (`self.camera.background_color = BG`)\n- [ ] `add_subcaption()` or `subcaption=` on every significant animation\n- [ ] No text smaller than font_size=18\n- [ ] No text using proportional fonts (use monospace)\n- [ ] buff >= 0.5 on all `.to_edge()` calls\n- [ ] Clean exit (FadeOut all) at end of every scene\n- [ ] `self.wait()` after every reveal\n- [ ] Color constants used (no hardcoded hex strings in scene code)\n- [ ] All scenes use the same quality flag (don't mix `-ql` and `-qh`)\n\n## Post-Render Checklist\n\nAfter stitching the final video:\n\n- [ ] Watch the complete video at 1x speed — does it feel rushed anywhere?\n- [ ] Is there a moment where two things animate simultaneously and it's confusing?\n- [ ] Does every text label have enough time to be read?\n- [ ] Are transitions between scenes smooth (no black frames, no jarring cuts)?\n- [ ] Is the audio in sync with the visuals (if using voiceover)?\n- [ ] Is the Gibbs-like \"first impression\" good? The first 5 seconds determine if someone keeps watching\n\n<!-- File: skills/manim-video/references/rendering.md (Tokens: ~1430 | Category: anthropic-skill) -->\n# Rendering Reference\n\n## Prerequisites\n\n```bash\nmanim --version       # Manim CE\npdflatex --version    # LaTeX\nffmpeg -version       # ffmpeg\n```\n\n## CLI Reference\n\n```bash\nmanim -ql script.py Scene1 Scene2    # draft (480p 15fps)\nmanim -qm script.py Scene1           # medium (720p 30fps)\nmanim -qh script.py Scene1           # production (1080p 60fps)\nmanim -ql --format=png -s script.py Scene1  # preview still (last frame)\nmanim -ql --format=gif script.py Scene1     # GIF output\n```\n\n## Quality Presets\n\n| Flag | Resolution | FPS | Use case |\n|------|-----------|-----|----------|\n| `-ql` | 854x480 | 15 | Draft iteration (layout, timing) |\n| `-qm` | 1280x720 | 30 | Preview (use for text-heavy scenes) |\n| `-qh` | 1920x1080 | 60 | Production |\n\n**Text rendering quality:** `-ql` (480p15) produces noticeably poor text kerning and readability. For scenes with significant text, preview stills at `-qm` to catch issues invisible at 480p. Use `-ql` only for testing layout and animation timing.\n\n## Output Structure\n\n```\nmedia/videos/script/480p15/Scene1_Intro.mp4\nmedia/images/script/Scene1_Intro.png  (from -s flag)\n```\n\n## Stitching with ffmpeg\n\n```bash\ncat > concat.txt << 'EOF'\nfile 'media/videos/script/480p15/Scene1_Intro.mp4'\nfile 'media/videos/script/480p15/Scene2_Core.mp4'\nEOF\nffmpeg -y -f concat -safe 0 -i concat.txt -c copy final.mp4\n```\n\n## Add Voiceover\n\n```bash\n# Mux narration\nffmpeg -y -i final.mp4 -i narration.mp3 -c:v copy -c:a aac -b:a 192k -shortest final_narrated.mp4\n\n# Concat per-scene audio first\ncat > audio_concat.txt << 'EOF'\nfile 'audio/scene1.mp3'\nfile 'audio/scene2.mp3'\nEOF\nffmpeg -y -f concat -safe 0 -i audio_concat.txt -c copy full_narration.mp3\n```\n\n## Add Background Music\n\n```bash\nffmpeg -y -i final.mp4 -i music.mp3 \\\n  -filter_complex \"[1:a]volume=0.15[bg];[0:a][bg]amix=inputs=2:duration=shortest\" \\\n  -c:v copy final_with_music.mp4\n```\n\n## GIF Export\n\n```bash\nffmpeg -y -i scene.mp4 \\\n  -vf \"fps=15,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse\" \\\n  output.gif\n```\n\n## Aspect Ratios\n\n```bash\nmanim -ql --resolution 1080,1920 script.py Scene  # 9:16 vertical\nmanim -ql --resolution 1080,1080 script.py Scene  # 1:1 square\n```\n\n## Render Workflow\n\n1. Draft render all scenes at `-ql`\n2. Preview stills at key moments (`-s`)\n3. Fix and re-render only broken scenes\n4. Stitch with ffmpeg\n5. Review stitched output\n6. Production render at `-qh`\n7. Re-stitch + add audio\n\n## manim.cfg — Project Configuration\n\nCreate `manim.cfg` in the project directory for per-project defaults:\n\n```ini\n[CLI]\nquality = low_quality\npreview = True\nmedia_dir = ./media\n\n[renderer]\nbackground_color = #0D1117\n\n[tex]\ntex_template_file = custom_template.tex\n```\n\nThis eliminates repetitive CLI flags and `self.camera.background_color` in every scene.\n\n## Sections — Chapter Markers\n\nMark sections within a scene for organized output:\n\n```python\nclass LongVideo(Scene):\n    def construct(self):\n        self.next_section(\"Introduction\")\n        # ... intro content ...\n\n        self.next_section(\"Main Concept\")\n        # ... main content ...\n\n        self.next_section(\"Conclusion\")\n        # ... closing ...\n```\n\nRender individual sections: `manim --save_sections script.py LongVideo`\nThis outputs separate video files per section — useful for long videos where you want to re-render only one part.\n\n## manim-voiceover Plugin (Recommended for Narrated Videos)\n\nThe official `manim-voiceover` plugin integrates TTS directly into scene code, auto-syncing animation duration to voiceover length. This is significantly cleaner than the manual ffmpeg muxing approach above.\n\n### Installation\n\n```bash\npip install \"manim-voiceover[elevenlabs]\"\n# Or for free/local TTS:\npip install \"manim-voiceover[gtts]\"    # Google TTS (free, lower quality)\npip install \"manim-voiceover[azure]\"   # Azure Cognitive Services\n```\n\n### Usage\n\n```python\nfrom manim import *\nfrom manim_voiceover import VoiceoverScene\nfrom manim_voiceover.services.elevenlabs import ElevenLabsService\n\nclass NarratedScene(VoiceoverScene):\n    def construct(self):\n        self.set_speech_service(ElevenLabsService(\n            voice_name=\"Alice\",\n            model_id=\"eleven_multilingual_v2\"\n        ))\n\n        # Voiceover auto-controls scene duration\n        with self.voiceover(text=\"Here is a circle being drawn.\") as tracker:\n            self.play(Create(Circle()), run_time=tracker.duration)\n\n        with self.voiceover(text=\"Now let's transform it into a square.\") as tracker:\n            self.play(Transform(circle, Square()), run_time=tracker.duration)\n```\n\n### Key Features\n\n- `tracker.duration` — total voiceover duration in seconds\n- `tracker.time_until_bookmark(\"mark1\")` — sync specific animations to specific words\n- Auto-generates subtitle `.srt` files\n- Caches audio locally — re-renders don't re-generate TTS\n- Works with: ElevenLabs, Azure, Google TTS, pyttsx3 (offline), and custom services\n\n### Bookmarks for Precise Sync\n\n```python\nwith self.voiceover(text='This is a <bookmark mark=\"circle\"/>circle.') as tracker:\n    self.wait_until_bookmark(\"circle\")\n    self.play(Create(Circle()), run_time=tracker.time_until_bookmark(\"circle\", limit=1))\n```\n\nThis is the recommended approach for any video with narration. The manual ffmpeg muxing workflow above is still useful for adding background music or post-production audio mixing.\n\n<!-- File: skills/manim-video/references/scene-planning.md (Tokens: ~779 | Category: anthropic-skill) -->\n# Scene Planning Reference\n\n## Narrative Arc Structures\n\n### Discovery Arc (most common)\n1. Hook -- pose a question or surprising result\n2. Intuition -- build visual understanding\n3. Formalize -- introduce the equation/algorithm\n4. Reveal -- the \"aha moment\"\n5. Extend -- implications or generalizations\n\n### Problem-Solution Arc\n1. Problem -- what's broken\n2. Failed attempt -- obvious approach fails\n3. Key insight -- the idea that works\n4. Solution -- implement it\n5. Result -- show improvement\n\n### Comparison Arc\n1. Setup -- introduce two approaches\n2. Approach A -- how it works\n3. Approach B -- how it works\n4. Contrast -- differences\n5. Verdict -- which is better\n\n### Build-Up Arc (architecture/systems)\n1. Component A -- first piece\n2. Component B -- second piece\n3. Connection -- how they interact\n4. Scale -- add more pieces\n5. Full picture -- zoom out\n\n## Scene Transitions\n\n### Clean Break (default)\n```python\nself.play(FadeOut(Group(*self.mobjects)), run_time=0.5)\nself.wait(0.3)\n```\n\n### Carry-Forward\nKeep one element, fade the rest. Next scene starts with it still on screen.\n\n### Transform Bridge\nEnd scene with a shape, start next scene by transforming it.\n\n## Cross-Scene Consistency\n\n```python\n# Shared constants at file top\nBG = \"#1C1C1C\"\nPRIMARY = \"#58C4DD\"\nSECONDARY = \"#83C167\"\nACCENT = \"#FFFF00\"\nTITLE_SIZE = 48\nBODY_SIZE = 30\nLABEL_SIZE = 24\nFAST = 0.8; NORMAL = 1.5; SLOW = 2.5\n```\n\n## Scene Checklist\n\n- [ ] Background color set\n- [ ] Subcaptions on every animation\n- [ ] `self.wait()` after every reveal\n- [ ] Text buff >= 0.5 for edge positioning\n- [ ] No text overlap\n- [ ] Color constants used (not hardcoded)\n- [ ] Opacity layering applied\n- [ ] Clean exit at scene end\n- [ ] No more than 5-6 elements visible at once\n\n## Duration Estimation\n\n| Content | Duration |\n|---------|----------|\n| Title card | 3-5s |\n| Concept introduction | 10-20s |\n| Equation reveal | 15-25s |\n| Algorithm step | 5-10s |\n| Data comparison | 10-15s |\n| \"Aha moment\" | 15-30s |\n| Conclusion | 5-10s |\n\n## Planning Template\n\n```markdown\n# [Video Title]\n\n## Overview\n- **Topic**: [Core concept]\n- **Hook**: [Opening question]\n- **Aha moment**: [Key insight]\n- **Target audience**: [Prerequisites]\n- **Length**: [seconds/minutes]\n- **Resolution**: 480p (draft) / 1080p (final)\n\n## Color Palette\n- Background: #1C1C1C\n- Primary: #58C4DD -- [purpose]\n- Secondary: #83C167 -- [purpose]\n- Accent: #FFFF00 -- [purpose]\n\n## Arc: [Discovery / Problem-Solution / Comparison / Build-Up]\n\n## Scene 1: [Name] (~Ns)\n**Purpose**: [one sentence]\n**Layout**: [FULL_CENTER / LEFT_RIGHT / GRID / PROGRESSIVE]\n\n### Visual elements\n- [Mobject: type, position, color]\n\n### Animation sequence\n1. [Animation] -- [what it reveals] (~Ns)\n\n### Subtitle\n\"[text]\"\n```\n\n<!-- File: skills/manim-video/references/troubleshooting.md (Tokens: ~1139 | Category: anthropic-skill) -->\n# Troubleshooting\n\n## LaTeX Errors\n\n**Missing raw string** (the #1 error):\n```python\n# WRONG: MathTex(\"\\\\frac{1}{2}\")  -- \\\\f is form-feed\n# RIGHT: MathTex(r\"\\frac{1}{2}\")\n```\n\n**Unbalanced braces**: `MathTex(r\"\\frac{1}{2\")` -- missing closing brace.\n\n**LaTeX not installed**: `which pdflatex` -- install texlive-full or mactex.\n\n**Missing package**: Add to preamble:\n```python\ntex_template = TexTemplate()\ntex_template.add_to_preamble(r\"\\usepackage{mathrsfs}\")\nMathTex(r\"\\mathscr{L}\", tex_template=tex_template)\n```\n\n## VGroup TypeError\n\n**Error:** `TypeError: Only values of type VMobject can be added as submobjects of VGroup`\n\n**Cause:** `Text()` objects are `Mobject`, not `VMobject`. Mixing `Text` with shapes in a `VGroup` fails on Manim CE v0.20+.\n\n```python\n# WRONG: Text is not a VMobject\ngroup = VGroup(circle, Text(\"Label\"))\n\n# RIGHT: use Group for mixed types\ngroup = Group(circle, Text(\"Label\"))\n\n# RIGHT: VGroup is fine for shapes-only\nshapes = VGroup(circle, square, arrow)\n\n# RIGHT: MathTex IS a VMobject — VGroup works\nequations = VGroup(MathTex(r\"a\"), MathTex(r\"b\"))\n```\n\n**Rule:** If the group contains any `Text()`, use `Group`. If it's all shapes or all `MathTex`, `VGroup` is fine.\n\n**FadeOut everything:** Always use `Group(*self.mobjects)`, not `VGroup(*self.mobjects)`:\n```python\nself.play(FadeOut(Group(*self.mobjects)))  # safe for mixed types\n```\n\n## Group save_state() / restore() Not Supported\n\n**Error:** `NotImplementedError: Please override in a child class.`\n\n**Cause:** `Group.save_state()` and `Group.restore()` are not implemented in Manim CE v0.20+. Only `VGroup` and individual `Mobject` subclasses support save/restore.\n\n```python\n# WRONG: Group doesn't support save_state\ngroup = Group(circle, Text(\"label\"))\ngroup.save_state()  # NotImplementedError!\n\n# RIGHT: use FadeIn with shift/scale instead of save_state/restore\nself.play(FadeIn(group, shift=UP * 0.3, scale=0.8))\n\n# RIGHT: or save/restore on individual VMobjects\ncircle.save_state()\nself.play(circle.animate.shift(RIGHT))\nself.play(Restore(circle))\n```\n\n## letter_spacing Is Not a Valid Parameter\n\n**Error:** `TypeError: Mobject.__init__() got an unexpected keyword argument 'letter_spacing'`\n\n**Cause:** `Text()` does not accept `letter_spacing`. Manim uses Pango for text rendering and does not expose kerning controls on `Text()`.\n\n```python\n# WRONG\nText(\"HERMES\", letter_spacing=6)\n\n# RIGHT: use MarkupText with Pango attributes for spacing control\nMarkupText('<span letter_spacing=\"6000\">HERMES</span>', font_size=18)\n# Note: Pango letter_spacing is in 1/1024 of a point\n```\n\n## Animation Errors\n\n**Invisible animation** -- mobject never added:\n```python\n# WRONG: circle = Circle(); self.play(circle.animate.set_color(RED))\n# RIGHT: self.play(Create(circle)); self.play(circle.animate.set_color(RED))\n```\n\n**Transform confusion** -- after Transform(A, B), A is on screen, B is not. Use ReplacementTransform if you want B.\n\n**Duplicate animation** -- same mobject twice in one play():\n```python\n# WRONG: self.play(c.animate.shift(RIGHT), c.animate.set_color(RED))\n# RIGHT: self.play(c.animate.shift(RIGHT).set_color(RED))\n```\n\n**Updater fights animation**:\n```python\nmob.suspend_updating()\nself.play(mob.animate.shift(RIGHT))\nmob.resume_updating()\n```\n\n## Rendering Issues\n\n**Blurry output**: Using -ql (480p). Switch to -qm/-qh for final.\n\n**Slow render**: Use -ql during development. Reduce Surface resolution. Shorter self.wait().\n\n**Stale output**: `manim -ql --disable_caching script.py Scene`\n\n**ffmpeg concat fails**: All clips must match resolution/FPS/codec.\n\n## Common Mistakes\n\n**Text clips at edge**: `buff >= 0.5` for `.to_edge()`\n\n**Overlapping text**: Use `ReplacementTransform(old, new)`, not `Write(new)` on top.\n\n**Too crowded**: Max 5-6 elements visible. Split into scenes or use opacity layering.\n\n**No breathing room**: `self.wait(1.5)` minimum after reveals, `self.wait(2.0)` for key moments.\n\n**Missing background color**: Set `self.camera.background_color = BG` in every scene.\n\n## Debugging Strategy\n\n1. Render a still: `manim -ql -s script.py Scene` -- instant layout check\n2. Isolate the broken scene -- render only that one\n3. Replace `self.play()` with `self.add()` to see final state instantly\n4. Print positions: `print(mob.get_center())`\n5. Clear cache: delete `media/` directory\n\n<!-- File: skills/manim-video/references/updaters-and-trackers.md (Tokens: ~2224 | Category: anthropic-skill) -->\n# Updaters and Value Trackers\n\n## The problem updaters solve\n\nNormal animations are discrete: `self.play()` goes from state A to state B. But what if you need continuous relationships — a label that always hovers above a moving dot, or a line that always connects two points?\n\nWithout updaters, you'd manually reposition every dependent object before every `self.play()`. Five animations that move a dot means five manual repositioning calls for the label. Miss one and it freezes in the wrong spot.\n\nUpdaters let you declare a relationship ONCE. Manim calls the updater function EVERY FRAME (15-60 fps depending on quality) to enforce that relationship, no matter what else is happening.\n\n## ValueTracker: an invisible steering wheel\n\nA ValueTracker is an invisible Mobject that holds a single float. It never appears on screen. It exists so you can ANIMATE it while other objects REACT to its value.\n\nThink of it as a slider: drag the slider from 0 to 5, and every object wired to it responds in real time.\n\n```python\ntracker = ValueTracker(0)        # invisible, stores 0.0\ntracker.get_value()              # read: 0.0\ntracker.set_value(5)             # write: jump to 5.0 instantly\ntracker.animate.set_value(5)     # animate: smoothly interpolate to 5.0\n```\n\n### The three-step pattern\n\nEvery ValueTracker usage follows this:\n\n1. **Create the tracker** (the invisible slider)\n2. **Create visible objects that READ the tracker** via updaters\n3. **Animate the tracker** — all dependents update automatically\n\n```python\n# Step 1: Create tracker\nx_tracker = ValueTracker(1)\n\n# Step 2: Create dependent objects\ndot = always_redraw(lambda: Dot(axes.c2p(x_tracker.get_value(), 0), color=YELLOW))\nv_line = always_redraw(lambda: axes.get_vertical_line(\n    axes.c2p(x_tracker.get_value(), func(x_tracker.get_value())), color=BLUE\n))\nlabel = always_redraw(lambda: DecimalNumber(x_tracker.get_value(), font_size=24)\n    .next_to(dot, UP))\n\nself.add(dot, v_line, label)\n\n# Step 3: Animate the tracker — everything follows\nself.play(x_tracker.animate.set_value(5), run_time=3)\n```\n\n## Types of updaters\n\n### Lambda updater (most common)\n\nRuns a function every frame, passing the mobject itself:\n\n```python\n# Label always stays above the dot\nlabel.add_updater(lambda m: m.next_to(dot, UP, buff=0.2))\n\n# Line always connects two points\nline.add_updater(lambda m: m.put_start_and_end_on(\n    point_a.get_center(), point_b.get_center()\n))\n```\n\n### Time-based updater (with dt)\n\nThe second argument `dt` is the time since the last frame (~0.017s at 60fps):\n\n```python\n# Continuous rotation\nsquare.add_updater(lambda m, dt: m.rotate(0.5 * dt))\n\n# Continuous rightward drift\ndot.add_updater(lambda m, dt: m.shift(RIGHT * 0.3 * dt))\n\n# Oscillation\ndot.add_updater(lambda m, dt: m.move_to(\n    axes.c2p(m.get_center()[0], np.sin(self.time))\n))\n```\n\nUse `dt` updaters for physics simulations, continuous motion, and time-dependent effects.\n\n### always_redraw: full rebuild every frame\n\nCreates a new mobject from scratch each frame. More expensive than `add_updater` but handles cases where the mobject's structure changes (not just position/color):\n\n```python\n# Brace that follows a resizing square\nbrace = always_redraw(Brace, square, UP)\n\n# Area under curve that updates as function changes\narea = always_redraw(lambda: axes.get_area(\n    graph, x_range=[0, x_tracker.get_value()], color=BLUE, opacity=0.3\n))\n\n# Label that reconstructs its text\ncounter = always_redraw(lambda: Text(\n    f\"n = {int(x_tracker.get_value())}\", font_size=24, font=\"Menlo\"\n).to_corner(UR))\n```\n\n**When to use which:**\n- `add_updater` — position, color, opacity changes (cheap, preferred)\n- `always_redraw` — when the shape/structure itself changes (expensive, use sparingly)\n\n## DecimalNumber: showing live values\n\n```python\n# Counter that tracks a ValueTracker\ntracker = ValueTracker(0)\nnumber = DecimalNumber(0, font_size=48, num_decimal_places=1, color=PRIMARY)\nnumber.add_updater(lambda m: m.set_value(tracker.get_value()))\nnumber.add_updater(lambda m: m.next_to(dot, RIGHT, buff=0.3))\n\nself.add(number)\nself.play(tracker.animate.set_value(100), run_time=3)\n```\n\n### Variable: the labeled version\n\n```python\nvar = Variable(0, Text(\"x\", font_size=24, font=\"Menlo\"), num_decimal_places=2)\nself.add(var)\nself.play(var.tracker.animate.set_value(PI), run_time=2)\n# Displays: x = 3.14\n```\n\n## Removing updaters\n\n```python\n# Remove all updaters\nmobject.clear_updaters()\n\n# Suspend temporarily (during an animation that would fight the updater)\nmobject.suspend_updating()\nself.play(mobject.animate.shift(RIGHT))\nmobject.resume_updating()\n\n# Remove specific updater (if you stored a reference)\ndef my_updater(m):\n    m.next_to(dot, UP)\nlabel.add_updater(my_updater)\n# ... later ...\nlabel.remove_updater(my_updater)\n```\n\n## Animation-based updaters\n\n### UpdateFromFunc / UpdateFromAlphaFunc\n\nThese are ANIMATIONS (passed to `self.play`), not persistent updaters:\n\n```python\n# Call a function on each frame of the animation\nself.play(UpdateFromFunc(mobject, lambda m: m.next_to(moving_target, UP)), run_time=3)\n\n# With alpha (0 to 1) — useful for custom interpolation\nself.play(UpdateFromAlphaFunc(circle, lambda m, a: m.set_fill(opacity=a)), run_time=2)\n```\n\n### turn_animation_into_updater\n\nConvert a one-shot animation into a continuous updater:\n\n```python\nfrom manim import turn_animation_into_updater\n\n# This would normally play once — now it loops forever\nturn_animation_into_updater(Rotating(gear, rate=PI/4))\nself.add(gear)\nself.wait(5)  # gear rotates for 5 seconds\n```\n\n## Practical patterns\n\n### Pattern 1: Dot tracing a function\n\n```python\ntracker = ValueTracker(0)\ngraph = axes.plot(np.sin, x_range=[0, 2*PI], color=PRIMARY)\ndot = always_redraw(lambda: Dot(\n    axes.c2p(tracker.get_value(), np.sin(tracker.get_value())),\n    color=YELLOW\n))\ntangent = always_redraw(lambda: axes.get_secant_slope_group(\n    x=tracker.get_value(), graph=graph, dx=0.01,\n    secant_line_color=HIGHLIGHT, secant_line_length=3\n))\n\nself.add(graph, dot, tangent)\nself.play(tracker.animate.set_value(2*PI), run_time=6, rate_func=linear)\n```\n\n### Pattern 2: Live area under curve\n\n```python\ntracker = ValueTracker(0.5)\narea = always_redraw(lambda: axes.get_area(\n    graph, x_range=[0, tracker.get_value()],\n    color=PRIMARY, opacity=0.3\n))\narea_label = always_redraw(lambda: DecimalNumber(\n    # Numerical integration\n    sum(func(x) * 0.01 for x in np.arange(0, tracker.get_value(), 0.01)),\n    font_size=24\n).next_to(axes, RIGHT))\n\nself.add(area, area_label)\nself.play(tracker.animate.set_value(4), run_time=5)\n```\n\n### Pattern 3: Connected diagram\n\n```python\n# Nodes that can be moved, with edges that auto-follow\nnode_a = Dot(LEFT * 2, color=PRIMARY)\nnode_b = Dot(RIGHT * 2, color=SECONDARY)\nedge = Line().add_updater(lambda m: m.put_start_and_end_on(\n    node_a.get_center(), node_b.get_center()\n))\nlabel = Text(\"edge\", font_size=18, font=\"Menlo\").add_updater(\n    lambda m: m.move_to(edge.get_center() + UP * 0.3)\n)\n\nself.add(node_a, node_b, edge, label)\nself.play(node_a.animate.shift(UP * 2), run_time=2)\nself.play(node_b.animate.shift(DOWN + RIGHT), run_time=2)\n# Edge and label follow automatically\n```\n\n### Pattern 4: Parameter exploration\n\n```python\n# Explore how a parameter changes a curve\na_tracker = ValueTracker(1)\ncurve = always_redraw(lambda: axes.plot(\n    lambda x: a_tracker.get_value() * np.sin(x),\n    x_range=[0, 2*PI], color=PRIMARY\n))\nparam_label = always_redraw(lambda: Text(\n    f\"a = {a_tracker.get_value():.1f}\", font_size=24, font=\"Menlo\"\n).to_corner(UR))\n\nself.add(curve, param_label)\nself.play(a_tracker.animate.set_value(3), run_time=3)\nself.play(a_tracker.animate.set_value(0.5), run_time=2)\nself.play(a_tracker.animate.set_value(1), run_time=1)\n```\n\n## Common mistakes\n\n1. **Updater fights animation:** If a mobject has an updater that sets its position, and you try to animate it elsewhere, the updater wins every frame. Suspend updating first.\n\n2. **always_redraw for simple moves:** If you only need to reposition, use `add_updater`. `always_redraw` reconstructs the entire mobject every frame — expensive and unnecessary for position tracking.\n\n3. **Forgetting to add to scene:** Updaters only run on mobjects that are in the scene. `always_redraw` creates the mobject but you still need `self.add()`.\n\n4. **Updater creates new mobjects without cleanup:** If your updater creates Text objects every frame, they accumulate. Use `always_redraw` (which handles cleanup) or update properties in-place.\n\n<!-- File: skills/manim-video/references/visual-design.md (Tokens: ~1269 | Category: anthropic-skill) -->\n# Visual Design Principles\n\n## 12 Core Principles\n\n1. **Geometry Before Algebra** — Show the shape first, the equation second.\n2. **Opacity Layering** — PRIMARY=1.0, CONTEXT=0.4, GRID=0.15. Direct attention through brightness.\n3. **One New Idea Per Scene** — Each scene introduces exactly one concept.\n4. **Spatial Consistency** — Same concept occupies the same screen region throughout.\n5. **Color = Meaning** — Assign colors to concepts, not mobjects. If velocity is blue, it stays blue.\n6. **Progressive Disclosure** — Show simplest version first, add complexity incrementally.\n7. **Transform, Don't Replace** — Use Transform/ReplacementTransform to show connections.\n8. **Breathing Room** — `self.wait(1.5)` minimum after showing something new.\n9. **Visual Weight Balance** — Don't cluster everything on one side.\n10. **Consistent Motion Vocabulary** — Pick a small set of animation types and reuse them.\n11. **Dark Background, Light Content** — #1C1C1C to #2D2B55 backgrounds maximize contrast.\n12. **Intentional Empty Space** — Leave at least 15% of the frame empty.\n\n## Layout Templates\n\n### FULL_CENTER\nOne main element centered, title above, note below.\nBest for: single equations, single diagrams, title cards.\n\n### LEFT_RIGHT\nTwo elements side by side at x=-3.5 and x=3.5.\nBest for: equation + visual, before/after, comparison.\n\n### TOP_BOTTOM\nMain element at y=1.5, supporting content at y=-1.5.\nBest for: concept + examples, theorem + cases.\n\n### GRID\nMultiple elements via `arrange_in_grid()`.\nBest for: comparison matrices, multi-step processes.\n\n### PROGRESSIVE\nElements appear one at a time, arranged DOWN with aligned_edge=LEFT.\nBest for: algorithms, proofs, step-by-step processes.\n\n### ANNOTATED_DIAGRAM\nCentral diagram with floating labels connected by arrows.\nBest for: architecture diagrams, annotated figures.\n\n## Color Palettes\n\n### Classic 3B1B\n```python\nBG=\"#1C1C1C\"; PRIMARY=BLUE; SECONDARY=GREEN; ACCENT=YELLOW; HIGHLIGHT=RED\n```\n\n### Warm Academic\n```python\nBG=\"#2D2B55\"; PRIMARY=\"#FF6B6B\"; SECONDARY=\"#FFD93D\"; ACCENT=\"#6BCB77\"\n```\n\n### Neon Tech\n```python\nBG=\"#0A0A0A\"; PRIMARY=\"#00F5FF\"; SECONDARY=\"#FF00FF\"; ACCENT=\"#39FF14\"\n```\n\n## Font Selection\n\n**Use monospace fonts for all text.** Manim's Pango text renderer produces broken kerning with proportional fonts (Helvetica, Inter, SF Pro, Arial) at all sizes and resolutions. Characters overlap and spacing is inconsistent. This is a fundamental Pango limitation, not a Manim bug.\n\nMonospace fonts have fixed character widths — zero kerning issues by design.\n\n### Recommended Fonts\n\n| Use case | Font | Fallback |\n|----------|------|----------|\n| **All text (default)** | `\"Menlo\"` | `\"Courier New\"`, `\"DejaVu Sans Mono\"` |\n| Code, labels | `\"JetBrains Mono\"`, `\"SF Mono\"` | `\"Menlo\"` |\n| Math | Use `MathTex` (renders via LaTeX, not Pango) | — |\n\n```python\nMONO = \"Menlo\"  # define once at top of file\n\ntitle = Text(\"Fourier Series\", font_size=48, color=PRIMARY, weight=BOLD, font=MONO)\nlabel = Text(\"n=1: (4/pi) sin(x)\", font_size=20, color=BLUE, font=MONO)\nnote = Text(\"Convergence at discontinuities\", font_size=18, color=DIM, font=MONO)\n\n# Math — always use MathTex, not Text\nequation = MathTex(r\"\\nabla L = \\frac{\\partial L}{\\partial w}\")\n```\n\n### When Proportional Fonts Are Acceptable\n\nLarge title text (font_size >= 48) with short strings (1-3 words) can use proportional fonts without visible kerning issues. For anything else — labels, descriptions, multi-word text, small sizes — use monospace.\n\n### Font Availability\n\n- **macOS**: Menlo (pre-installed), SF Mono\n- **Linux**: DejaVu Sans Mono (pre-installed), Liberation Mono\n- **Cross-platform**: JetBrains Mono (install from jetbrains.com)\n\n`\"Menlo\"` is the safest default — pre-installed on macOS, and Linux systems fall back to DejaVu Sans Mono.\n\n### Fine-Grained Text Control\n\n`Text()` does not support `letter_spacing` or kerning parameters. For fine control, use `MarkupText` with Pango attributes:\n\n```python\n# Letter spacing (Pango units: 1/1024 of a point)\nMarkupText('<span letter_spacing=\"6000\">HERMES</span>', font_size=18, font=\"Menlo\")\n\n# Bold specific words\nMarkupText('This is <b>important</b>', font_size=24, font=\"Menlo\")\n\n# Color specific words\nMarkupText('Red <span foreground=\"#FF6B6B\">warning</span>', font_size=24, font=\"Menlo\")\n```\n\n### Minimum Font Size\n\n`font_size=18` is the minimum for readable text at any resolution. Below 18, characters become blurry at `-ql` and barely readable even at `-qh`.\n\n## Visual Hierarchy Checklist\n\nFor every frame:\n1. What is the ONE thing to look at? (brightest/largest)\n2. What is context? (dimmed to 0.3-0.4)\n3. What is structural? (dimmed to 0.15)\n4. Enough empty space? (>15%)\n5. All text readable at phone size?\n\n<!-- File: skills/manim-video/SKILL.md (Tokens: ~3028 | Category: anthropic-skill) -->\n---\nname: manim-video\ndescription: \"Production pipeline for mathematical and technical animations using Manim Community Edition. Creates 3Blue1Brown-style explainer videos, algorithm visualizations, equation derivations, architecture diagrams, and data stories. Use when users request: animated explanations, math animations, concept visualizations, algorithm walkthroughs, technical explainers, 3Blue1Brown style videos, or any programmatic animation with geometric/mathematical content.\"\nversion: 1.0.0\n---\n\n# Manim Video Production Pipeline\n\n## Creative Standard\n\nThis is educational cinema. Every frame teaches. Every animation reveals structure.\n\n**Before writing a single line of code**, articulate the narrative arc. What misconception does this correct? What is the \"aha moment\"? What visual story takes the viewer from confusion to understanding? The user's prompt is a starting point — interpret it with pedagogical ambition.\n\n**Geometry before algebra.** Show the shape first, the equation second. Visual memory encodes faster than symbolic memory. When the viewer sees the geometric pattern before the formula, the equation feels earned.\n\n**First-render excellence is non-negotiable.** The output must be visually clear and aesthetically cohesive without revision rounds. If something looks cluttered, poorly timed, or like \"AI-generated slides,\" it is wrong.\n\n**Opacity layering directs attention.** Never show everything at full brightness. Primary elements at 1.0, contextual elements at 0.4, structural elements (axes, grids) at 0.15. The brain processes visual salience in layers.\n\n**Breathing room.** Every animation needs `self.wait()` after it. The viewer needs time to absorb what just appeared. Never rush from one animation to the next. A 2-second pause after a key reveal is never wasted.\n\n**Cohesive visual language.** All scenes share a color palette, consistent typography sizing, matching animation speeds. A technically correct video where every scene uses random different colors is an aesthetic failure.\n\n## Prerequisites\n\nRun `scripts/setup.sh` to verify all dependencies. Requires: Python 3.10+, Manim Community Edition v0.20+ (`pip install manim`), LaTeX (`texlive-full` on Linux, `mactex` on macOS), and ffmpeg. Reference docs tested against Manim CE v0.20.1.\n\n## Modes\n\n| Mode | Input | Output | Reference |\n|------|-------|--------|-----------|\n| **Concept explainer** | Topic/concept | Animated explanation with geometric intuition | `references/scene-planning.md` |\n| **Equation derivation** | Math expressions | Step-by-step animated proof | `references/equations.md` |\n| **Algorithm visualization** | Algorithm description | Step-by-step execution with data structures | `references/graphs-and-data.md` |\n| **Data story** | Data/metrics | Animated charts, comparisons, counters | `references/graphs-and-data.md` |\n| **Architecture diagram** | System description | Components building up with connections | `references/mobjects.md` |\n| **Paper explainer** | Research paper | Key findings and methods animated | `references/scene-planning.md` |\n| **3D visualization** | 3D concept | Rotating surfaces, parametric curves, spatial geometry | `references/camera-and-3d.md` |\n\n## Stack\n\nSingle Python script per project. No browser, no Node.js, no GPU required.\n\n| Layer | Tool | Purpose |\n|-------|------|---------|\n| Core | Manim Community Edition | Scene rendering, animation engine |\n| Math | LaTeX (texlive/MiKTeX) | Equation rendering via `MathTex` |\n| Video I/O | ffmpeg | Scene stitching, format conversion, audio muxing |\n| TTS | ElevenLabs / Qwen3-TTS (optional) | Narration voiceover |\n\n## Pipeline\n\n```\nPLAN --> CODE --> RENDER --> STITCH --> AUDIO (optional) --> REVIEW\n```\n\n1. **PLAN** — Write `plan.md` with narrative arc, scene list, visual elements, color palette, voiceover script\n2. **CODE** — Write `script.py` with one class per scene, each independently renderable\n3. **RENDER** — `manim -ql script.py Scene1 Scene2 ...` for draft, `-qh` for production\n4. **STITCH** — ffmpeg concat of scene clips into `final.mp4`\n5. **AUDIO** (optional) — Add voiceover and/or background music via ffmpeg. See `references/rendering.md`\n6. **REVIEW** — Render preview stills, verify against plan, adjust\n\n## Project Structure\n\n```\nproject-name/\n  plan.md                # Narrative arc, scene breakdown\n  script.py              # All scenes in one file\n  concat.txt             # ffmpeg scene list\n  final.mp4              # Stitched output\n  media/                 # Auto-generated by Manim\n    videos/script/480p15/\n```\n\n## Creative Direction\n\n### Color Palettes\n\n| Palette | Background | Primary | Secondary | Accent | Use case |\n|---------|-----------|---------|-----------|--------|----------|\n| **Classic 3B1B** | `#1C1C1C` | `#58C4DD` (BLUE) | `#83C167` (GREEN) | `#FFFF00` (YELLOW) | General math/CS |\n| **Warm academic** | `#2D2B55` | `#FF6B6B` | `#FFD93D` | `#6BCB77` | Approachable |\n| **Neon tech** | `#0A0A0A` | `#00F5FF` | `#FF00FF` | `#39FF14` | Systems, architecture |\n| **Monochrome** | `#1A1A2E` | `#EAEAEA` | `#888888` | `#FFFFFF` | Minimalist |\n\n### Animation Speed\n\n| Context | run_time | self.wait() after |\n|---------|----------|-------------------|\n| Title/intro appear | 1.5s | 1.0s |\n| Key equation reveal | 2.0s | 2.0s |\n| Transform/morph | 1.5s | 1.5s |\n| Supporting label | 0.8s | 0.5s |\n| FadeOut cleanup | 0.5s | 0.3s |\n| \"Aha moment\" reveal | 2.5s | 3.0s |\n\n### Typography Scale\n\n| Role | Font size | Usage |\n|------|-----------|-------|\n| Title | 48 | Scene titles, opening text |\n| Heading | 36 | Section headers within a scene |\n| Body | 30 | Explanatory text |\n| Label | 24 | Annotations, axis labels |\n| Caption | 20 | Subtitles, fine print |\n\n### Fonts\n\n**Use monospace fonts for all text.** Manim's Pango renderer produces broken kerning with proportional fonts at all sizes. See `references/visual-design.md` for full recommendations.\n\n```python\nMONO = \"Menlo\"  # define once at top of file\n\nText(\"Fourier Series\", font_size=48, font=MONO, weight=BOLD)  # titles\nText(\"n=1: sin(x)\", font_size=20, font=MONO)                  # labels\nMathTex(r\"\\nabla L\")                                            # math (uses LaTeX)\n```\n\nMinimum `font_size=18` for readability.\n\n### Per-Scene Variation\n\nNever use identical config for all scenes. For each scene:\n- **Different dominant color** from the palette\n- **Different layout** — don't always center everything\n- **Different animation entry** — vary between Write, FadeIn, GrowFromCenter, Create\n- **Different visual weight** — some scenes dense, others sparse\n\n## Workflow\n\n### Step 1: Plan (plan.md)\n\nBefore any code, write `plan.md`. See `references/scene-planning.md` for the comprehensive template.\n\n### Step 2: Code (script.py)\n\nOne class per scene. Every scene is independently renderable.\n\n```python\nfrom manim import *\n\nBG = \"#1C1C1C\"\nPRIMARY = \"#58C4DD\"\nSECONDARY = \"#83C167\"\nACCENT = \"#FFFF00\"\nMONO = \"Menlo\"\n\nclass Scene1_Introduction(Scene):\n    def construct(self):\n        self.camera.background_color = BG\n        title = Text(\"Why Does This Work?\", font_size=48, color=PRIMARY, weight=BOLD, font=MONO)\n        self.add_subcaption(\"Why does this work?\", duration=2)\n        self.play(Write(title), run_time=1.5)\n        self.wait(1.0)\n        self.play(FadeOut(title), run_time=0.5)\n```\n\nKey patterns:\n- **Subtitles** on every animation: `self.add_subcaption(\"text\", duration=N)` or `subcaption=\"text\"` on `self.play()`\n- **Shared color constants** at file top for cross-scene consistency\n- **`self.camera.background_color`** set in every scene\n- **Clean exits** — FadeOut all mobjects at scene end: `self.play(FadeOut(Group(*self.mobjects)))`\n\n### Step 3: Render\n\n```bash\nmanim -ql script.py Scene1_Introduction Scene2_CoreConcept  # draft\nmanim -qh script.py Scene1_Introduction Scene2_CoreConcept  # production\n```\n\n### Step 4: Stitch\n\n```bash\ncat > concat.txt << 'EOF'\nfile 'media/videos/script/480p15/Scene1_Introduction.mp4'\nfile 'media/videos/script/480p15/Scene2_CoreConcept.mp4'\nEOF\nffmpeg -y -f concat -safe 0 -i concat.txt -c copy final.mp4\n```\n\n### Step 5: Review\n\n```bash\nmanim -ql --format=png -s script.py Scene2_CoreConcept  # preview still\n```\n\n## Critical Implementation Notes\n\n### Raw Strings for LaTeX\n```python\n# WRONG: MathTex(\"\\frac{1}{2}\")\n# RIGHT:\nMathTex(r\"\\frac{1}{2}\")\n```\n\n### buff >= 0.5 for Edge Text\n```python\nlabel.to_edge(DOWN, buff=0.5)  # never < 0.5\n```\n\n### FadeOut Before Replacing Text\n```python\nself.play(ReplacementTransform(note1, note2))  # not Write(note2) on top\n```\n\n### Never Animate Non-Added Mobjects\n```python\nself.play(Create(circle))  # must add first\nself.play(circle.animate.set_color(RED))  # then animate\n```\n\n## Performance Targets\n\n| Quality | Resolution | FPS | Speed |\n|---------|-----------|-----|-------|\n| `-ql` (draft) | 854x480 | 15 | 5-15s/scene |\n| `-qm` (medium) | 1280x720 | 30 | 15-60s/scene |\n| `-qh` (production) | 1920x1080 | 60 | 30-120s/scene |\n\nAlways iterate at `-ql`. Only render `-qh` for final output.\n\n## References\n\n| File | Contents |\n|------|----------|\n| `references/animations.md` | Core animations, rate functions, composition, `.animate` syntax, timing patterns |\n| `references/mobjects.md` | Text, shapes, VGroup/Group, positioning, styling, custom mobjects |\n| `references/visual-design.md` | 12 design principles, opacity layering, layout templates, color palettes |\n| `references/equations.md` | LaTeX in Manim, TransformMatchingTex, derivation patterns |\n| `references/graphs-and-data.md` | Axes, plotting, BarChart, animated data, algorithm visualization |\n| `references/camera-and-3d.md` | MovingCameraScene, ThreeDScene, 3D surfaces, camera control |\n| `references/scene-planning.md` | Narrative arcs, layout templates, scene transitions, planning template |\n| `references/rendering.md` | CLI reference, quality presets, ffmpeg, voiceover workflow, GIF export |\n| `references/troubleshooting.md` | LaTeX errors, animation errors, common mistakes, debugging |\n| `references/animation-design-thinking.md` | When to animate vs show static, decomposition, pacing, narration sync |\n| `references/updaters-and-trackers.md` | ValueTracker, add_updater, always_redraw, time-based updaters, patterns |\n| `references/paper-explainer.md` | Turning research papers into animations — workflow, templates, domain patterns |\n| `references/decorations.md` | SurroundingRectangle, Brace, arrows, DashedLine, Angle, annotation lifecycle |\n| `references/production-quality.md` | Pre-code, pre-render, post-render checklists, spatial layout, color, tempo |\n\n---\n\n## Creative Divergence (use only when user requests experimental/creative/unique output)\n\nIf the user asks for creative, experimental, or unconventional explanatory approaches, select a strategy and reason through it BEFORE designing the animation.\n\n- **SCAMPER** — when the user wants a fresh take on a standard explanation\n- **Assumption Reversal** — when the user wants to challenge how something is typically taught\n\n### SCAMPER Transformation\nTake a standard mathematical/technical visualization and transform it:\n- **Substitute**: replace the standard visual metaphor (number line → winding path, matrix → city grid)\n- **Combine**: merge two explanation approaches (algebraic + geometric simultaneously)\n- **Reverse**: derive backward — start from the result and deconstruct to axioms\n- **Modify**: exaggerate a parameter to show why it matters (10x the learning rate, 1000x the sample size)\n- **Eliminate**: remove all notation — explain purely through animation and spatial relationships\n\n### Assumption Reversal\n1. List what's \"standard\" about how this topic is visualized (left-to-right, 2D, discrete steps, formal notation)\n2. Pick the most fundamental assumption\n3. Reverse it (right-to-left derivation, 3D embedding of a 2D concept, continuous morphing instead of steps, zero notation)\n4. Explore what the reversal reveals that the standard approach hides\n</yakaai_skills>"}