Skip to content

Instantly share code, notes, and snippets.

@lucataco
Created May 6, 2026 00:59
Show Gist options
  • Select an option

  • Save lucataco/d6289685b56400f6f22db9ff8b367e4d to your computer and use it in GitHub Desktop.

Select an option

Save lucataco/d6289685b56400f6f22db9ff8b367e4d to your computer and use it in GitHub Desktop.
CF-Explainer-Video

Skill: cf-explainer-video

Cloudflare-Themed Explainer Video Pipeline

When to use

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.

Creative Standard

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:

  1. 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.
  2. 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.
  3. 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.

Prerequisites

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

Pipeline

PLAN → SCRIPT → TTS → MANIM-CORE → REMOTION-SHELL → COMPOSITE → QA
  1. PLANplan.md with narrative arc, scene table, palette confirmation
  2. SCRIPTscript.md with one block per scene, Inworld bracketed steering
  3. TTS — generate all narration clips up front; measure durations
  4. MANIM-CORE — render technical/diagram scenes at 1080p30
  5. REMOTION-SHELL — render typographic scenes at 1080p30
  6. COMPOSITEcomposite.sh pads each scene's audio/video to scene target, concats, muxes
  7. QA — extract stills at every key beat; verify legibility, no clipping, no blank gaps. (see references/pitfalls.md for the 10 issues we know about — every one of them WILL bite if the QA pass is skipped.)

Project structure

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

Theme — extracted from let-it-slide.sid7002.workers.dev

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).

Workflow

Step 1 — plan.md

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).

Step 2 — script.md

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).

Step 3 — TTS generation

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"
done

This gives you the floor for each scene's video duration.

Step 4 — Manim scenes

Open templates/manim/scene-template.py and rename MMyScene. Every scene MUST:

  • Call add_frame(self) as the first line of construct() — draws the cream content card and dashed crop marks
  • Use t() for all sans-serif text and tm() 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's tpad can clone it. End with self.wait(0.5). The drop-in theme.py module ships t(), tm(), add_frame(), card(), pill(), section_label() and all color/font tokens.

Step 5 — Remotion scenes

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

Step 6 — Composite

Use pipeline/composite.sh. The script reads a SEGS array and for each line (name, video, audio, target_dur):

  1. Pads video to target with tpad=stop_mode=clone (freezes last frame)
  2. Pads audio to target with trailing silence (apad=whole_dur=...)
  3. Concats both streams
  4. Muxes into out/<title>.mp4 The target duration for each scene is max(video_dur, audio_dur) + small buffer.

Step 7 — QA pass (mandatory)

Extract stills at key beats and inspect them. Read every still. Check:

  • All section labels render with spaces (THE PROBLEM, not THEPROBLEM)
  • 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 #521000 on cream — readable on small screens See references/pitfalls.md for the full list of issues we hit on the first pass and how to spot them.

References

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

Critical conventions

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"

Performance

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.

Creative Divergence (use only when user requests experimental output)

Vertical / shorts (1080×1920)

The Frame component takes a vertical prop; flip the Manim frame width/height. Reduce font sizes ~30% to compensate for narrower horizontal real estate.

Multi-language

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.

Live-data 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment