Animated video
Everything here renders in remotion/ for zero credits. Two levels — always try level 1 first:
- Compose built-in scenes in a spec (minutes, no code).
- Add a new scene type to the engine when an idea genuinely needs one (code, tested, then reusable by every agent).
Before rendering locally: if remotion/node_modules is missing, run cd remotion && npm ci
(the first render also downloads Remotion's headless Chrome, ~170 MB, once).
Level 1 — the scene kit
scene (shots[].type) | best for | key fields |
|---|---|---|
kinetic | the hook, a punchline | text, highlight, per_word (0.18) — words pop in one by one |
title | a single bold line | text ≤ 6 words, highlight |
number | one striking figure | value, prefix, suffix, decimals, caption, color — counts up |
bars | how things compare in size | title, unit, items[{label,value,color}] |
compare | them vs you, myth vs fact, before/after | title, left{label,items,color}, right{…} |
list | "3 things", do/don't | items, style tick/cross/number |
timeline | how it happened | items[{year,text}] |
chat | a story told in texts | title (contact), messages[{from: me/them, text, at}] |
quote | a sourced line | text, by |
media | b-roll, a photo, the AI clip | src, kenburns, headline, fit |
Every scene takes label (kicker), stamp (rubber-stamp number), bg (dimmed photo/video behind
it, with credit) and transition (fade default, cut, slide, zoom, wipe). Overlays
(headline, textover, lowerthird, stamp) sit on top of any scene. Full reference: remotion/SPEC.md.
A worked example of every new scene: remotion/examples/animated.json.
Recipes
- Faceless explainer (20–40 s):
kinetichook →number→bars/compare→list→titlepayoff that echoes the hook. Voice-over asaudio, captions fromwords, a quietmusicbed at 0.08–0.12. - Text-message story:
kinetichook ("She replied at 3am") → onechatshot per exchange (atspaced 0.8–1.4 s, timed to the narration) →titletwist. Keep each bubble ≤ 10 words. - Animated insert in a talking video: CLIP_1 …
numberorcomparefor 2–3 s on the beat that says it (transition: "zoom") … back to CLIP_1 withstartset to where the clip should resume. - Myth vs fact:
comparewithleft.color: "danger",right.color: "good", then astamp"FALSE" on the nexttitle.
Plan the beats with video-script (tools/beats.mjs times scenes to the voice), then:
cd remotion
node render.mjs job/spec.json --validate
node render.mjs job/spec.json job/final.mp4 --preview job/check.jpg --at 0.1,<each scene's middle>,<end-1>
Always look at the preview strip before delivering: hook fully drawn at 0.1 s, nothing clipped off the edges, no text overlapping, the accent colour readable, the AI-generated tag and watermark there.
Style rules
- Brand from
settings.brand(spectheme): one accent colour, used for the one thing that matters in each scene. - ≤ 7 words per headline, ≤ 12 per textover, ≤ 4 items per list/compare column (the phone screen is small).
- A scene stays up at least 1.5 s per line of text in it.
- Numbers must match
citations; never animate a figure you didn't source. - No brand logos, no real people's photos, no screenshots of real chats.
Level 2 — a new scene type
Only when no combination above can show the idea (e.g. a map route, a progress ring, a split-screen reaction).
src/types.ts: addXShot = ShotBase & { type: "x"; … }and add it to theShotunion.src/shots/Shots.tsx: write the component next to the others — animate withspring/interpolatefromuseCurrentFrame()(never CSS animations orMath.random: renders must be deterministic), uset.font,t.accent,tone(t, color), size for 1080×1920, then add oneif (s.type === "x") body = …line inShotView. If it's on screen at 0 s (base === 0) draw it fully with no entrance.render.mjs: add"x"totypesand validation for its required fields (the checker agent relies on these messages).SPEC.md+ the table above: document it. Add it toexamples/animated.json.- Test:
npx tsc --noEmit -p .,node render.mjs examples/animated.json out/animated.mp4 --preview out/animated.jpg --at …and look at it.