Use when the user wants a short (~1-3 min) explainer or marketing video about a Cloudflare product, primitive, or developer-platform concept (Workers, Durable Objects, R2, Queues, AI Gateway, KV, Pages, Browser Rendering, Sandbox SDK, Access, etc.). Produces a polished MP4 that visually matches the Cloudflare "Let It Slide" slide-deck aesthetic at let-it-slide.sid7002.workers.dev: warm cream background, deep-brown body text, Cloudflare orange accent, rounded cards with dashed crop marks. This skill composes two underlying tools:
- Remotion — TSX shells for typographic hero scenes (intros, titles, code reveals, outros). Strong typography, kinetic text, reliable font rendering.
- Manim — Python diagrams for technical / algorithmic scenes (data flow, routing, race conditions, state diagrams, architecture). Strong vector math. Plus an Inworld TTS narration track via Replicate, and an ffmpeg compositor that stitches everything into one synced MP4.
This is Cloudflare-quality work. The output should look like it could ship on a developer-blog post, a Connect talk, or a product launch reel. Three non-negotiables:
- Visual continuity between Manim and Remotion scenes. Both must use the same warm cream background, the same dashed crop marks, the same deep-brown body text, the same orange accent. The viewer should never feel a tool change — only a content change.
- Two fonts maximum across the entire video. One sans (Helvetica/Inter class, depending on tool), one mono (Menlo/JetBrains Mono class). Never introduce a third.
- Audio drives everything. Generate narration first, measure each clip's
duration, then design every visual scene to fit. Do not design visuals
first and force narration to match. The compositor pads short audio with
silence and short video with held final frames so each scene plays for
max(audio_dur, video_dur) + small buffer.
| Tool | Version | Purpose |
|---|---|---|
| Python 3.10+ | — | Manim runtime |
| Manim CE | 0.20+ | Vector animation |
| LaTeX (mactex/texlive-full) | — | MathTex (rarely used here) |
| Node.js | 18+ LTS | Remotion runtime |
| ffmpeg | recent | Stitching, padding, mux |
REPLICATE_API_TOKEN env var |
— | Inworld TTS via Replicate |
PLAN → SCRIPT → TTS → MANIM-CORE → REMOTION-SHELL → COMPOSITE → QA
- PLAN —
plan.mdwith narrative arc, scene table, palette confirmation - SCRIPT —
script.mdwith one block per scene, Inworld bracketed steering - TTS — generate all narration clips up front; measure durations
- MANIM-CORE — render technical/diagram scenes at
1080p30 - REMOTION-SHELL — render typographic scenes at
1080p30 - COMPOSITE —
composite.shpads each scene's audio/video to scene target, concats, muxes - QA — extract stills at every key beat; verify legibility, no clipping,
no blank gaps. (see
references/pitfalls.mdfor the 10 issues we know about — every one of them WILL bite if the QA pass is skipped.)
my-explainer/
├── plan.md
├── script.md
├── composite.sh
├── audio/
│ ├── 01-intro.mp3 ... 07-outro.mp3
├── manim/
│ └── script.py # all manim scenes in one file
├── remotion/
│ ├── package.json
│ ├── tsconfig.json
│ ├── remotion.config.ts
│ └── src/
│ ├── index.ts # registerRoot only
│ ├── Root.tsx # composition registrations
│ ├── theme.ts
│ ├── components/Frame.tsx
│ └── scenes/*.tsx
└── out/durable-objects.mp4 # final
bg-page #F5F1EB (outer page background)
bg-content #FFFBF5 (content card — Manim background ALSO uses this)
bg-inset #FEF7ED (callouts, code blocks)
text #521000 (deep brown body text — never pure black)
text-muted #52100099 (60% opacity)
border #EBD5C1
orange #FF6633 (primary accent)
blue #0A95FF (secondary)
green #19B006 (success)
purple #9333EA (tertiary)
red #D9342B (errors / lost-update)
Full theme tokens: theme/theme.md. Drop-in modules: theme/theme.py
(Manim) and theme/theme.ts (Remotion).
Write the narrative arc first. Use this template:
Hook: one stateless / one limitation / one gap → user feels the pain. Pivot: name the Cloudflare primitive that fixes it. Mechanism: 1 diagram showing how it works (Manim). Proof: 1 code reveal showing how to use it (Remotion). Use cases: grid of 3-4 typical applications (Manim). Outro: 3-word distillation + call to action (Remotion). Then a scene table with: | # | Tool | id | dur(s) | Purpose | |---|------|----|--------|---------| Aim for ~95-115s total (Cloudflare audience attention span).
Write the narration. Use Inworld's bracketed steering at the start of each
block: [say warmly with gentle pacing], [say like a friendly engineer],
[say with rising energy]. Use <break time="500ms" /> for pacing pauses.
Capitalize words for emphasis (STATELESS, ATOMIC).
Use Replicate's Inworld realtime-tts-2 model. Voice Ashley for warm female
narrator (recommended for tech content), Dennis for warm male. Request mp3
output. See pipeline/tts-replicate.md.
After generating, MEASURE each clip:
for f in audio/*.mp3; do
ffprobe -v error -show_entries format=duration -of default=nw=1:nk=1 "$f"
doneThis gives you the floor for each scene's video duration.
Open templates/manim/scene-template.py and rename MMyScene. Every scene
MUST:
- Call
add_frame(self)as the first line ofconstruct()— draws the cream content card and dashed crop marks - Use
t()for all sans-serif text andtm()for all mono — these helpers fix the Pango space-stripping bug (NBSP substitution +disable_ligatures=True) - NEVER end with
FadeOut(*self.mobjects)— leave the final composition on screen so the compositor'stpadcan clone it. End withself.wait(0.5). The drop-intheme.pymodule shipst(),tm(),add_frame(),card(),pill(),section_label()and all color/font tokens.
Copy the templates/remotion/ tree wholesale. Each scene component:
- Wraps in
<Frame>(provides cream content card + crop marks) - Drives all motion from
useCurrentFrame()(no CSS transitions, no setState animations) - Uses theme tokens from
./theme— never inline hex - Targets 1920×1080 @ 30fps
Use pipeline/composite.sh. The script reads a SEGS array and for each line
(name, video, audio, target_dur):
- Pads video to target with
tpad=stop_mode=clone(freezes last frame) - Pads audio to target with trailing silence (
apad=whole_dur=...) - Concats both streams
- Muxes into
out/<title>.mp4The target duration for each scene ismax(video_dur, audio_dur) + small buffer.
Extract stills at key beats and inspect them. Read every still. Check:
- All section labels render with spaces (
THE PROBLEM, notTHEPROBLEM) - All cards/boxes contain their text — nothing bleeds outside
- No element clips at the frame edges
- Two fonts only — no surprise third font in any scene
- No blank gray gaps mid-video (Manim scenes don't fade-out at end)
- Background is consistent warm cream across BOTH Manim and Remotion
- Text contrast: deep brown
#521000on cream — readable on small screens Seereferences/pitfalls.mdfor the full list of issues we hit on the first pass and how to spot them.
| File | Contents |
|---|---|
theme/theme.md |
Full color palette, typography scale, visual signature |
theme/theme.py |
Drop-in Manim theme module: tokens, t(), tm(), add_frame(), card(), pill(), section_label() |
theme/theme.ts |
Drop-in Remotion theme module: tokens + font fallback chains |
templates/manim/scene-template.py |
Reference Manim scene with all helpers |
templates/remotion/ |
Reference Remotion app skeleton (package.json, Root.tsx, Frame component, scene examples) |
pipeline/composite.sh |
Stitching pipeline: per-scene padding, concat, mux |
pipeline/tts-replicate.md |
How to invoke Inworld realtime-tts-2 with bracketed steering |
references/pitfalls.md |
10 known issues + fixes (Pango spaces, FadeOut blanks, z-order, edge clipping, code-card overflow, font count, etc.) |
references/pacing.md |
Audio-first design, scene-duration math, breathing waits |
references/narrative.md |
Cloudflare-style narrative arc template + 3 worked examples |
These are non-negotiable. Violating any one of them produces visibly broken frames within the first render pass.
# Manim — ALWAYS use t() / tm() helpers, NEVER raw Text(...)
title = t("Two writers. One counter.", font_size=48, color=TEXT, weight=BOLD)
# Manim — ALWAYS add_frame at scene start, NEVER bare camera bg
def construct(self):
add_frame(self)
...
# Manim — NEVER FadeOut at scene end; let tpad clone the held composition
self.play(Write(closer), run_time=1.1)
self.wait(0.5) # ← that's the whole ending
# Remotion — derive everything from frame, never CSS transitions
const opacity = interpolate(frame, [0, 30], [0, 1], {extrapolateRight: 'clamp'});
# Remotion — wrap every scene in <Frame> from components/Frame.tsx
return <Frame>{...}</Frame>
# Composite — target_dur = max(video_dur, audio_dur) + 0.1s buffer
"01-intro out/r-intro.mp4 audio/01-intro.mp3 9.60"| Quality | Resolution | FPS | Render time per scene (M-series Mac) |
|---|---|---|---|
Draft (-ql / --scale=0.5) |
854×480 / 960×540 | 15 | 5-30s |
Production (-qh --fps 30) |
1920×1080 | 30 | 60-120s |
| Always iterate at draft, only render production for the final pass. | |||
| Total render budget for a 7-scene, ~95s video: ~6-10 minutes end-to-end on | |||
| a recent MacBook Pro. |
The Frame component takes a vertical prop; flip the Manim frame width/height.
Reduce font sizes ~30% to compensate for narrower horizontal real estate.
Inworld supports 15 production languages. Generate parallel narration tracks; re-run the composite per language. The Manim/Remotion scenes are language- agnostic if you keep on-screen text minimal — bias toward iconographic/ diagrammatic explanation in localized variants.
Use Remotion's calculateMetadata + getInputProps to template the same
shell across many products. The Cloudflare theme stays constant; product name,
metric, and color emphasis vary per render.