# How I make motion videos with Claude Opus 5.5 (the full setup, prompts and numbers)

> Source: <https://dev.to/arqamwd/how-i-make-motion-videos-with-claude-opus-55-the-full-setup-prompts-and-numbers-2g09>
> Published: 2026-10-26 10:34:57+00:00

**TL;DR.** Opus 5.5 never touches a pixel. It writes one HTML file where every frame is a pure function of time, Playwright screenshots each frame, ffmpeg stitches them. Swap every adjective in your prompt for a number, ban the defaults by name, review stills before the full render. My 3D Slabwatch film rendered on a laptop RTX 2050 at about 0.3 s a frame.

I'm not a motion designer. I've never opened After Effects. Last week I still needed a product film for Slabwatch, so I set up a motion design agent in Claude Code on Opus 5.5 and made one.

It went through a flat 2D version that I honestly didn't like, then a full 3D rebuild with a modelled electricity meter, real lighting, depth of field, a mirror floor and a synthesised soundtrack, all rendered on my laptop's RTX 2050.

Every "one prompt" motion video on your feed skips the middle part. The second prompt, the review, the "frame 84 is late" note. This article is that middle part. The exact setup, the render rules, the prompt template I use now, the numbers that make motion look expensive, and the checks that keep it sharp. Then, because my way isn't the only way, every other route I found: Blender, video models, Remotion, Manim and the rest, and when each one beats mine.

Opus 5.5 outputs text. It never touches a pixel. Every motion clip you've seen from it is a program, almost always one HTML file, where every frame is a **pure function of time**. You give it a time t, it draws exactly that frame.

Then a headless browser (Playwright) opens the page, freezes it at frame 0, 1, 2 and so on, screenshots each one, and ffmpeg stitches the PNGs into an MP4.

That sounds boring but it's the whole trick, and it gives you three things:

For that to work the page has to follow a few rules. I call it the render contract and it gets pasted under every motion prompt. Each line exists because something breaks without it:

```
RENDER CONTRACT (paste under any motion prompt)
- One self-contained HTML file. Only the libraries I name, pinned to an exact version.
- No network requests except fonts and those pinned libraries.
- Every frame is a pure function of t (seconds). Expose window.renderAt(t).
- No Date.now, no setTimeout / setInterval, no CSS transitions,
  no requestAnimationFrame in render mode.
- Seeded noise only (mulberry32). Never Math.random.
- No state carried from one frame to the next.
- Wait for document.fonts.ready before frame 0.
- Loops: render frames 0 to N-1. Frame N-1 must flow into frame 0.
- PNG frames into the final encode. Never JPEG.
```

Why each line matters, quickly:

`Math.random` makes shapes jitter between frames.
One thing Movez points out that surprised me: Opus defaults to this plain "one HTML file + Playwright + ffmpeg" route even when Remotion or HyperFrames are installed. If you want a framework, say it in the prompt.

My route is plain HTML, plus three.js when it's 3D. It's not the only one. Section 14 has the full table of every route (Remotion, HyperFrames, Blender, Manim, Lottie, Rive, video models and more), with licenses and when to pick each.

And where code loses: photoreal scenes, complex characters, organic movement. seeco makes this point well. Code wins on product films, UI motion, data stories, typography and launch explainers, which is basically everything a builder needs. For the rest, section 14 covers Blender and the video models, and how to mix them with code.

**No Claude Code? Start here.** Florence's article has a no-install path. Open the Claude app, pick Opus 5.5, ask for the animation as one HTML file, and it plays in the preview panel. Then screen-record it (Cmd+Shift+5 on a Mac, the Snipping Tool's video mode on Windows). While you iterate, ask for a frame counter on screen and a space bar pause. It's not frame-exact, a screen recording can drop frames, but it's fine for a first post.

For the real thing: I run everything in Claude Code on Linux, but this works the same on a Mac. You need Node, ffmpeg and a headless Chromium.

```
# one folder for every piece you make
mkdir motion-studio && cd motion-studio
npm init -y
npm i -D playwright
npx playwright install chromium
# ffmpeg: sudo apt install ffmpeg (Linux) or brew install ffmpeg (Mac)
claude   # then /model and pick Opus 5.5
```

Then a renderer. Mine is `scripts/render.mjs`, Claude wrote it, and the core is about this big:

``` js
// node render.mjs anim.html 6 30   (file, seconds, fps)
import { chromium } from 'playwright';
import { pathToFileURL } from 'node:url';
import { mkdirSync } from 'node:fs';

const [file = 'anim.html', secs = '6', fps = '30'] = process.argv.slice(2);
const FPS = Number(fps), N = Math.round(Number(secs) * FPS); // frames 0..N-1
mkdirSync('out/frames', { recursive: true });

const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1920, height: 1080 } });
await page.goto(pathToFileURL(file).href, { waitUntil: 'networkidle' });
await page.evaluate(() => document.fonts.ready);

for (let i = 0; i < N; i++) {
  await page.evaluate((t) => {
    if (window.renderAt) window.renderAt(t);
    document.getAnimations().forEach((a) => { a.pause(); a.currentTime = t * 1000; });
  }, i / FPS);
  await page.screenshot({ path: `out/frames/${String(i).padStart(4, '0')}.png` });
}
await browser.close();
```

Then frames to video:

```
ffmpeg -framerate 30 -i out/frames/%04d.png -c:v libx264 -profile:v high \
  -pix_fmt yuv420p -crf 18 -movflags +faststart out/anim.mp4
```

Every frame is exact because every PNG is the page frozen at one precise t, no matter how slow your machine is. A screen recording drops frames, this doesn't.

Next to the renderer I keep two more scripts:

`scripts/qa.sh`: makes a contact sheet, a frame strip, a phone-size test and a loop check from any MP4 (the ffmpeg lines are in section 10)`scripts/beats.py`: librosa beat detection, so cuts can land on the music. In the smoke test it found 120 BPM on a 120 BPM click track, so it at least knows what a beat is
And a `CLAUDE.md` in the studio folder that holds the render contract, the ban list and the QA rules. Every time something goes wrong, the fix goes in there as a rule, so the next piece doesn't hit it again. Honestly that file ends up mattering more than any single prompt.

Each piece gets its own folder: `pieces/<name>/` with the brief, style guide, shot list, assets, src, reviews and out.

From then on you barely type commands. You tell Claude Code "render this, open frames 0, 30 and 60, tell me what you see before you change anything" and it runs the loop itself.

Movez and seeco both say this and it's the rule I'd tattoo on the agent: **never let the model invent your product screens or numbers.** A fake UI looks fine for about two seconds, until someone opens the real app and nothing matches.

What I did for Slabwatch: the agent didn't redraw my UI at all. It loaded the site's actual HTML and CSS in an iframe inside the film, with live numbers. Same fonts, same layout, same everything, because it IS the page.

If you're doing a brand or launch reel, the instructions that work (mostly from Movez and seeco):

```
ASSETS
- Here is the URL: <your site>. Use the actual product screens, logo and assets.
- Capture them with Playwright into ./assets and list them before you start.
- Never redraw the UI from imagination.
- If a real asset is missing, stop and ask me. Never draw a convincing fake.
- Use my real numbers only. Never invent metrics.
```

Same for references. Naming a style beats describing it. If you give it a frame from a film you like, say what to take (palette, type, grain, shot length) and what NOT to take (the subject). Movez's line for this is basically "take the grammar, never the content".

This is the single most useful idea in all of this, and it's from Florence.

Florence ran the same 3D product shot twice. The vague prompt was three adjectives ("cool", "premium", "modern"). It came back as navy space, a glowing halo ring, bloom, particles, a spinning product and a tagline nobody asked for. That's the default look, and **adjectives are where the default lives**.

So you swap every adjective for a number:

| You'd say | Say this instead | 
|---|---|
| "premium" | materials: roughness 0.38, clearcoat 0.35; ease-out-quint, no overshoot on text | 
| "cool lighting" | key, fill and rim lights, each with a hex color and an intensity | 
| "modern camera" | fov 30, never parked, one push-in on ease-out-expo at 2.0 s | 
| "snappy" | durations in frames and a spring damping ratio | 
| "make it pop" | scale 1.0 to 1.08 to 1.0 over 6 frames, peak on the beat | 
| "it drags" | cut each hold by one beat | 
| "too busy" | one hero move, everything else 150 ms later at 60% of the travel | 

Here's the template I use now. It's Florence's structure with a couple of lines from Movez and seeco. Fill every line before you hit enter:

```
GOAL:    one sentence. What the viewer feels or does after watching.
OUTPUT:  engine + size. e.g. "A three.js scene in one HTML file, 1920x1080"
TIME:    seconds x fps = frames. Loop: yes / no.
LOOK:    hex palette, ONE accent color, typeface / weight / size / tracking.
MOTION:  cubic-beziers for enter / move / exit, springs as damping ratios,
         durations in frames.
SHOTS:   frame ranges, one hero move per shot, and the handoff
         (cut / match cut / mask wipe). Default to the cut.
MODEL / CAMERA (3D only): what the object is, fov, the one camera move.
IDENTITY LOCK: what may never change (logo proportions, mascot shape).
ASSETS:  real logo, real UI screens, real numbers. If something is
         missing, stop and ask.
BANNED:  see section 06.
QA:      stills at frames [..], a contact sheet, my OK, then the full render.
```

Write GOAL first. If you can't say in one sentence what the viewer should feel, Opus can't animate it, and no easing curve saves it. Then TIME, because every other number hangs off the frame count.

The QA line matters too. It makes Opus show you stills BEFORE it renders the whole clip, which is where all the real work happens (section 10).

For a short product film, seeco's default story shape is a good starting SHOTS line:

```
0-2 s    HOOK      one thing that stops the scroll
2-5 s    PROBLEM   shown on screen, not told
5-9 s    REVEAL    the product, through a real interaction
9-13 s   PROOF     one screen or one number
13-16 s  CLOSE     logo + a CTA people can actually read
```

Every beat needs an entry state, an exit state and a reason. No reason, cut it.

For Slabwatch my hook was literally the problem: an LCD ticking past 200 units and a Rs 32,000 bill. If you're in Pakistan you already know why that's scary, the 200-unit slab is where your bill jumps.

Motion designers talk in curves and frames, so give Opus curves and frames. Screenshot this section, it's the one I go back to most. Almost all of it is from Florence's cheat sheet.

**Easing**

| Name | cubic-bezier | Use it for | 
|---|---|---|
| ease-out-expo | 0.16, 1, 0.3, 1 | text and logo reveals | 
| ease-out-quint | 0.22, 1, 0.36, 1 | premium entrances | 
| ease-in-out-quart | 0.76, 0, 0.24, 1 | moves and camera pushes | 
| ease-in-expo | 0.7, 0, 0.84, 0 | fast exits | 
| ease-out-back | 0.34, 1.56, 0.64, 1 | a playful pop with overshoot | 

Rules: enter with ease-out, exit with ease-in, and make exits 40 to 50% shorter than entrances (400 ms in, 200 ms out). Linear only for rotation, progress bars and the phase of a loop. Linear easing on a move is the fastest way to make a render look like a render.

**Springs**

Every library names its spring knobs differently. One number translates between all of them, the damping ratio:

``` js
// damping ratio from any library's numbers. 1 = no bounce
const dampingRatio = (stiffness, damping, mass = 1) =>
  damping / (2 * Math.sqrt(stiffness * mass));
```

Remotion's default spring works out to 0.5, react-spring's default to 1.0. Position and size may bounce, opacity and color NEVER do. Tiny overshoot on UI, none on type.

Ask for springs as a closed-form function of t (no simulation), so frame 84 can render on its own without computing frames 0 to 83. That keeps the render contract happy. Both Movez and Florence have the code for this, you can just tell Opus "write the spring as a pure function of t".

seeco adds a good rule on top: one spring personality per kind of object. Small UI snaps, panels settle, the camera glides almost invisibly, headlines land hard and hold, mascots bounce on purpose. If everything overshoots, nothing feels precise.

**Durations and stagger**

```
UI feedback        100-200 ms
UI moves           200-500 ms
hero reveals       600-900 ms
letters            1 frame apart
words              60-120 ms apart
list items         40-80 ms apart
next element starts when the previous one is 30-50% done
```

For video, say it in frames. At 30 fps a frame is about 33 ms, so a 400 ms move is 12 frames.

**Hierarchy**

One hero move per shot. Secondary moves travel 50 to 70% as far and start 100 to 200 ms later. Ambient motion stays under 5% of the hero's travel on a slow sine. Something new every 2 to 4 s, and the hook has to land in the first 2 s.

**Holds**

On-screen text stays up at least 0.3 s per word plus 0.5 s. A two-word title needs 1.1 s. Keep a slow 2% push going during the hold so the frame doesn't feel dead while people read.

**Beat grid**

Frames per beat = fps x 60 / BPM. At 120 BPM and 30 fps that's 15 frames. Compute each beat from time and round each one separately, because adding a rounded number again and again drifts off the music. Cut on the beat, change the layout on the bar (every 4 beats). Changing everything on every beat wears people out.

**Two accessibility numbers**

Text at 4.5:1 contrast on every frame, including over moving backgrounds. Never more than 3 flashes a second. And for phone size, push key poses 10 to 30% further than feels right, the feed shrinks everything.

The trick that ties this together: put all of it in one `motion.js` tokens file (EASE, durations, stagger) and tell Opus "use only these tokens, no inline numbers". Then the whole film moves like one thing.

A generic "avoid the AI look" doesn't work. It just swaps one default for another. What works is banning the defaults BY NAME. This is the list I start from, merged from Movez and Florence:

```
BANNED
- centered title on a gradient
- everything fading in at once
- fade-and-slide-up on every element
- centered-everything layouts
- purple or blue gradients
- glow (incl. on UI), glow orbs, bloom, neon, lens flares
- decorative particles, particle bursts
- corner labels, frame borders
- generic spinning turntables
- text that moves while it's being read
- dead time
```

Then keep it alive. After every first render, look at what defaults it used anyway, and add them to the list. My list is longer than this now, and it gets longer every piece.

OK, here's what actually happened when I used all of this.

v1 of the Slabwatch film was 60 seconds, 1920x1080, 30 fps, all flat 2D. A seekable HTML page rendered frame by frame with Playwright. The beats:

Sound was a fallback. The Openverse API timed out, so the agent built a calm sound kit with earshot (more on that in section 11) and a synthesised pad in C, mixed to -14 LUFS.

On X I said it one shotted my demo video. And the first render did come out in one go. It just wasn't the one I ended up using.

My honest verdict on v1: it was flat and not that amusing. Everything was correct and nothing made you want to keep watching.

The lesson I gave my agent from this: for a product film, aim high by default. Real 3D, camera moves, light, depth. And show me the style in stills before rendering anything long. So I told it to go all out and make it 3D.

v2 was three.js 0.186 scenes rendered in headless Chromium on my laptop's RTX 2050. The scenes:

PBR lighting, depth of field, grain. The agent checked stills over 4 rounds before it rendered the full film.

Things that will cost you time if nobody tells you:

**Headless Chromium renders on the CPU by default.** It uses SwiftShader, which is software rendering and MUCH slower. These flags put it on your actual GPU through Vulkan:

``` js
const browser = await chromium.launch({
  args: ['--use-angle=vulkan', '--enable-gpu', '--ignore-gpu-blocklist'],
});
```

With them, my RTX 2050 rendered about 0.3 s per frame at 1080p with depth of field and shadows. That's a laptop GPU, so you don't need a 4090 for this.

**ES modules need a local http server.** `file://` won't load them, and three.js examples are all modules now. Something like `npx http-server` and point the renderer at localhost.

**Pin your three.js version and know what changed.** `PCFSoftShadowMap` is gone in three 0.186, so older examples (and older model knowledge) break on it. Florence hit a similar trap in r160 where TextGeometry reads `height` and ignores `depth`. Pin the version, name the right parameter in the prompt, and the trap is gone.

**Motion blur comes from subframes.** A real camera's shutter stays open while things move, a frame from `renderAt(t)` is frozen at one instant, so fast moves look like they strobe. The fix Florence's 3D pieces and Movez's renderer both use: render 4 subframes per frame and blend them. Spread them over half a frame, which is how a 180-degree film shutter records blur. In the renderer:

``` js
// 4 subframes per output frame, spread over half a frame (180° shutter)
const SUB = 4, SHUTTER = 0.5;
let n = 0;
for (let i = 0; i < N; i++) {
  for (let k = 0; k < SUB; k++) {
    const t = (i + (k / SUB) * SHUTTER) / FPS;
    await page.evaluate((t) => window.renderAt(t), t);
    await page.screenshot({ path: `out/frames/${String(n++).padStart(5, '0')}.png` });
  }
}
```

Then blend each group of 4 into one frame:

```
ffmpeg -framerate 120 -i out/frames/%05d.png \
  -vf "tmix=frames=4,select='eq(mod(n\,4)\,3)',setpts=N/30/TB" -r 30 \
  -c:v libx264 -pix_fmt yuv420p -crf 16 out/anim.mp4
```

It costs 4x the render time, so turn it on for the final, not while iterating. Anything that isn't moving stays exactly as sharp, since 4 identical frames blend into the same frame.

Sound for v2 was earshot again, this time with generated risers, whooshes and sub hits over a synth bed, mixed to -14.6 LUFS. Earshot's critique scored the mix 19 out of 21, "launch-grade".

3D gives you depth of field, grain and close-ups, and every one of those eats text if you let it. A product film lives or dies on whether people can read the screen and the end card. So these four go in the render contract BEFORE the first frame:

**1. Text and UI never go through depth of field or any blur.** Draw them on their own layer after the DOF pass. Otherwise the bokeh drags ink out of the letters and you get grey halos, and the end card is the one frame everybody actually reads.

**2. At least 1 texel per output pixel at the closest shot.** If the camera pushes into your UI, capture the page at 2x or more (3x for real close-ups). You can't zoom into pixels that aren't there.

**3. Grain at .02 or less, PNG frames all the way to the encoder.** JPEG frames before x264 means every frame gets compressed twice, and heavy grain makes it worse. Encode the final at crf 18 or lower.

**4. Look at full-size frames before you call it done.** Pull the closest shot and the end card at full size and crop the text at 200%. A contact sheet won't show soft text and neither will a self-score. Only a full-size frame does. My agent's QA script also measures it: a sharpness score per text box (the strongest edges divided by the text's contrast), 0.50 or higher to pass, 0.60+ for big type. It's not done until that passes, whatever any self-score says.

Here's the block I paste:

```
SHARPNESS
- Text and UI on their own layer, drawn AFTER depth of field / any blur.
- Screens and textures captured at >= 1 texel per output pixel at the
  closest framing (2x+, 3x for close-ups).
- Grain <= .02. Chromatic aberration ~.0025. Max blur ~.006.
- PNG frames only, final encode crf <= 18 (16 by default, 14 for detailed 3D).
- Before "done": full-size closest shot + end card, crop text at 200%.
```

Two web-side extras from the sources: never put `will-change` on anything the camera scales (it rasterizes once and stays blurry), and snap resting text to whole pixels or render at 2x and scale down.

The first render is a draft. Every source says it and it matched what happened to me. The loop that finishes a film:

The scorecard I use, from Movez, each 1 to 10, keep going until everything is 8+:

```
SCORE EACH 1-10, THEN FIX THE 3 WORST WITH FRAME NUMBERS
- hook in the first 2 s
- readability at 360 px wide (phone)
- motion quality
- variety
- composition
- brand accuracy (real assets, right colors)
- sound sync
```

And the hunt list, things to look for on every pass: text overlapping during swaps, sliding where it should ease, corner labels and borders sneaking back in, centered-on-gradient, blurry scaled text, dead beats, a stutter at the loop seam.

Measure instead of eyeballing where you can. Florence caught a glint flooding 178,864 pixels and an anticipation pose landing ONE frame late, just by measuring. You can ask Opus for an onion skin (frames stacked at low opacity, even spacing means robotic motion) or a plot of position per frame (sharp corners mean dead starts and stops). It can draw both and read both.

Your notes should point at frames, not feelings. "The punch at frame 84 peaks late, move it 2 frames earlier" works. "Make it better" doesn't.

When a fix doesn't hold, render frames N-1 and N+1 side by side, have it list the differences, and fix only that line.

These ffmpeg one-liners do most of my reviewing:

```
# contact sheet: 2 frames a second, tiled
ffmpeg -i out/final.mp4 -vf "fps=2,scale=270:-1,tile=6x5" -frames:v 1 out/contact.png

# strip around a fast action (starting at 4.1 s)
ffmpeg -ss 4.1 -i out/final.mp4 -vf "scale=320:-1,tile=12x1" -frames:v 1 out/strip.png

# phone test: does it read at 360 px wide?
ffmpeg -i out/final.mp4 -vf "fps=1,scale=360:-1,tile=5x3" -frames:v 1 out/phone.png

# loop check: play it 3 times and watch the seam
ffmpeg -stream_loop 2 -i out/anim.mp4 -c copy out/loop_check.mp4
```

**Freeze any frame by URL** (from Florence). Put this before `</body>` and `anim.html?t=2.5` shows exactly that frame, frozen. Opus can screenshot it without rendering anything else, which makes every round of the loop cheap:

``` php
<script>
  // anim.html?t=2.5 -> render that exact frame and freeze everything
  const q = new URLSearchParams(location.search).get('t');
  if (q !== null) addEventListener('load', async () => {
    await document.fonts.ready;
    const t = Number(q);
    if (window.renderAt) window.renderAt(t);
    document.getAnimations().forEach((a) => { a.pause(); a.currentTime = t * 1000; });
  });
</script>
```

**Loops that actually loop.** "Render 0 to N-1" fixes the double frame, but the motion still has to come back to where it started. Drive everything from one phase, and only use whole cycles:

``` js
const N = 180;                        // loop length in frames
const phase = (frame % N) / N;        // 0..1, wraps exactly
const angle = phase * Math.PI * 2;    // whole turns only, never 1.5
// organic drift that comes back: walk seeded noise around a circle
const drift = noise2D(Math.cos(angle) * 0.8, Math.sin(angle) * 0.8);
```

A rotation of 2 full turns loops, 1.5 turns jumps. For organic wobble, sampling noise around a circle means it ends exactly where it began. And make frame 0 a finished composition, because it's what people see while a GIF loads.

One more trick while iterating: ask for a playhead and frame labels on screen, pinned to frames. A move that runs late shows up as a label that lights before its move. Remove them for the final.

And every fix that works goes into the studio's `CLAUDE.md` as a rule, so the next film starts with it.

Claude can't hear. So every "this sound fits" needs a number behind it, and that's what earshot by Leon Lin is for. It's an open source tool ("ears for coding agents") that ranks and describes sounds, measures loudness, generates sound effects in code and critiques a full mix against professional launch films. That's where my 19/21 came from. It only judges the audio, so it tells you nothing about the picture.

The order my agent sources sound in:

Rules that held up:

**Voice.** One of the examples in Movez's article gave a mascot a voice with ElevenLabs. If you do that, the key lives in `.env` and the prompt only names the variable. Never paste a real key into a prompt, you'll screenshot that prompt for a post one day. HyperFrames also has built-in TTS if you don't want another account.

```
VOICE
- The ElevenLabs key is in .env as ELEVENLABS_API_KEY. Read it from there,
  never print it, never write it into a file. .env stays in .gitignore.
- Generate the voice-over first, then time every beat to the words.
```

Licenses matter if you post this. Fine: CC0, public domain, CC BY 2.0 to 4.0, Mixkit free. Not fine: anything NC, ND or SA. And read the actual item page, the license field in an API is a lead, not proof. CC BY credit goes in the post.

Muxing it all, with loudness normalisation in the same pass:

```
ffmpeg -i out/anim.mp4 -i music.wav -i sfx.wav \
  -filter_complex "[1:a][2:a]amix=inputs=2:duration=first,loudnorm=I=-14:TP=-1:LRA=11[a]" \
  -map 0:v -map "[a]" -c:v copy -c:a aac -b:a 192k -shortest \
  -movflags +faststart out/final.mp4
```

For anything longer than a short loop, the agent works for hours, and you don't want it polishing 900 wrong frames. So you put gates in, places where it stops and waits for your OK. This structure is from seeco and Movez:

```
GATES (stop and wait for my OK at each)
1  references + assets approved
2  style guide + shot list approved
3  stills per shot, critiqued
4  animatic at 960x540 with placeholder audio
5  full pass, polish, sound
6  formats, loop check, poster, README

DELIVER
final.mp4, loop_check.mp4, poster.png, contact.png, README
```

The brief itself: the film in one line, references, tools and budget, an identity lock for any character or logo, a beat sheet with timestamps, text-on-screen rules, then the gates above and the critique loop.

If you split chapters across subagents, have it write an `ANIMATION_GUIDE.md` first so they all code in one style.

```
# X video: H.264 High, yuv420p, AAC, up to 60 fps
ffmpeg -framerate 30 -i out/frames/%04d.png -c:v libx264 -profile:v high \
  -pix_fmt yuv420p -crf 18 -movflags +faststart out/anim.mp4
```

Sizes: 1920x1080 (16:9), 1080x1080 (1:1), 1080x1350 (4:5), 1080x1920 (9:16). Write scenes against a layout function and render every format from one timeline, then reframe per format, never crop. Vertical gets bigger type and fewer elements.

Without Premium, X caps video at 140 s.

**GIFs.** X allows 15 MB, up to 1280x1080, and caps a GIF at 350 frames (about 14 s at 25 fps). Gradients band in GIFs, so avoid them there. The palette command for a clean one:

```
ffmpeg -i out/anim.mp4 -vf "fps=25,scale=1200:-1:flags=lanczos,split[a][b];[a]palettegen=stats_mode=diff[p];[b][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle" -loop 0 out/anim.gif
```

The timing trap: GIF frame delays are in hundredths of a second. 25 fps (4/100 s) and 20 fps (5/100 s) are exact. 24 fps isn't, every frame gets 40 ms and the loop runs 4% fast. If you need 24, the delays have to go five frames at 40 ms, then one at 50 ms. Easier to export the GIF at 25.

The three articles I learned from barely touch Blender. It shows up once, as a caption in Florence's article: Shikhar's 3D browser world (1.75M views) was built "from Blender assets, generated images and Three.js", on Opus 5.5 medium, third iteration. No Blender how-to anywhere, and nothing on Manim, Motion Canvas, Lottie, Rive, After Effects scripting or Veo either. So I looked into everything else too.

My way (HTML and three.js, rendered frame by frame) is one route. Here's every route I found and when to pick it. Versions and prices are as of 2026-10-26, they change fast, so check before you commit.

| You're making | Use | License / cost | 
|---|---|---|
| One idea under 10 s, product film, UI motion, type, data, launch explainer | HTML + Playwright + ffmpeg (my rig), or HyperFrames | free; HyperFrames is Apache 2.0 | 
| A templated series, data-driven videos, a React team | Remotion | free up to 3 employees, then $25/seat/month (Creators) or $0.01/render with a $100/month minimum (Automators) | 
| 3D in the browser, abstract or product shots | three.js | MIT, free | 
| Photoreal 3D, real light, physics, rigged characters | Blender (Claude writes Python, Blender renders with no window) | free, GPL; your renders are yours | 
| Math, equations, diagrams | Manim Community | MIT, free | 
| Vector explainers synced to a voice-over | Motion Canvas (or its fork Revideo) | MIT, free | 
| Motion inside an app: icons, loaders | Lottie (dotLottie) | MIT runtimes | 
| Interactive, state-driven app motion | Rive | editor free with a splash screen on exports, $9/seat to remove it; runtimes MIT | 
| Motion inside a live website | Motion or GSAP | free, GSAP plugins included | 
| You already live in After Effects | AE + ExtendScript + aerender | Adobe subscription | 
| People, photoreal scenes, organic movement | a video model (Veo 3.1 / Gemini Omni, Kling 4.0, Seedance 2.5, Runway Gen-4.5) | paid credits, check the commercial terms | 
| Hand-drawn, storybook style | the claude-animation skill (node canvas) | free | 

Blender does the stuff three.js fakes: real path-traced light (Cycles), physics sims (cloth, fluid, rigid body), rigged characters and geometry nodes. Current version is Blender 5.2 LTS, released 2026-07-14 and supported until July 2028.

There are two ways to get Claude driving it.

**Way 1, scripts.** This is the one I'd use, because it's the same mindset as the render contract. Claude writes a Python (bpy) file that builds the whole scene, Blender runs it with no window, then renders the frames. Re-runnable, so an edit is still a changed number and a re-render.

```
# build the scene from Claude's script and save it
blender -b -P build_scene.py

# render frames 1-240 with Cycles on the GPU, as PNGs
blender -b scene.blend -E CYCLES -o //out/frame_#### -F PNG -s 1 -e 240 -a -- --cycles-device OPTIX
```

The gotcha, straight from the Blender manual: arguments run in the order you write them. `-a` (or `-f 10` for one frame) goes LAST, after the output path and format, or your settings get ignored. Use CUDA instead of OPTIX on older NVIDIA cards, HIP on AMD, METAL on a Mac. `blender -E help` lists the engines.

**Way 2, MCP (Claude drives a live Blender).** There's an official one from Blender Lab, the Blender Foundation's experimental arm: add-on v1.0.3, needs Blender 5.1+. Its own page warns it "will execute LLM generated code in Blender without any guards" and recommends a VM, so take that seriously. The popular community one is ahujasid/blender-mcp (MIT, around 32k GitHub stars). It's a socket add-on that gives Claude viewport screenshots, runs Python, and pulls assets from Poly Haven, Sketchfab and 3D generators.

**Engines.** Cycles is the path tracer, accurate and slow, and it renders fine with no display. EEVEE is real-time and fast but needs a GPU context. Headless on Linux works through EGL and 5.2 added `gpu.init()` for GPU in background mode, but test it on your own machine first. Workbench is for quick previews.

**The version trap, same lesson as three.js.** Blender 5.0 renamed EEVEE's engine id from `BLENDER_EEVEE_NEXT` back to `BLENDER_EEVEE`, so older scripts (and older model knowledge) break on it. Put the Blender version in the prompt.

**What it costs you.** Cycles frames take seconds to minutes each, nowhere near the ~0.3 s my three.js frames took. A 4 GB laptop GPU like mine limits how big a scene can get. And Claude can't see the viewport in Way 1, so the loop from section 10 still applies: render stills, contact sheet, fix, then the full render.

**Assets.** Poly Haven is the safe default (CC0 models, textures and HDRIs). Sketchfab licenses vary per model, so read each one before it goes in a film you post.

**Hybrids are the strongest move.** Model and light it in Blender, export glTF (built in), and load it in three.js with GLTFLoader. That's literally how Shikhar's piece was made. Or render Blender plates as backgrounds and put the UI and text on top in HTML, so text never goes through Blender's blur at all (that's rule 1 from section 09 again).

Where it fits: if the shot needs real materials, physics or a character, Blender. If it's UI, type or a product screen, stay in HTML.

seeco says code loses on photoreal scenes, characters and organic motion. Here's what to use instead:

Don't go looking for Sora. OpenAI shut the Sora app on 2026-04-26 and the API on 2026-09-24.

What you give up compared to code: you can't seek, you re-roll instead of editing, text and UI come out wrong, and you can't hit a beat on an exact frame. Check each tool's commercial terms before you use it for a paid launch.

The pattern that gets you both, from Movez and seeco, is **generate-then-trace**. Seedance renders the base shots for characters and physics, then Opus redraws the whole film in JavaScript on top of them, so the viewer only ever sees the code layer. Or keep it simpler: generated footage as background plates, with code-drawn type over it.

**Remotion.** v4 (4.0.538 as of 2026-10-22). Videos are React components, it has official agent skills and mature cloud rendering on Lambda. License in the table above. Remember Movez's point from section 01: Opus won't use it unless you say so.

**HyperFrames** (by HeyGen). Plain HTML and GSAP, no build step. It renders by seeking each frame in headless Chrome and encoding with FFmpeg, locally or on AWS Lambda. Apache 2.0, "no per-render fees or commercial-use thresholds". Needs Node 22+ and FFmpeg. It's basically my rig as a framework, and it's the default in my agent's studio now.

**Manim Community.** Python, the 3Blue1Brown-style engine, v0.22.0 (2026-10-08). Best for anything with equations or graphs.

```
manim -pqh scene.py SceneName   # render the scene in high quality and preview it
```

**Motion Canvas.** TypeScript generators with a live editor, made for explainers synced to a voice-over. The catch: the last tagged release (v3.17.2) was December 2024 and development has slowed. Revideo is an MIT fork that adds rendering from code or an API.

**Lottie.** Vector animation stored as JSON, usually exported from After Effects with Bodymovin, or written by code. Use it for motion inside apps. dotLottie is the maintained runtime (lottie-web hasn't moved since 2025-09).

**Rive.** Interactive animations with state machines, for apps and games. Pricing's in the table.

**After Effects.** If you already use it, Claude can write ExtendScript (`.jsx`) and render with `aerender`, AE's command-line renderer. There are community AE MCP servers but none are official and I haven't vetted any, so I'm not naming one.

**claude-animation skill** (from Movez's setup). Hand-drawn, storybook-style films drawn in node canvas, with synthesised sound.

The rule across all of it: pick the route by the shot, mix routes when one film needs two, and keep text in HTML wherever you can.

All three source articles say it in different ways, and it matches what happened to me: "one prompt" is mostly a myth.

seeco's numbers are the clearest. The best "one-prompt" films started from 9,500 to 19,000 character briefs and ran for hours. One watercolor short took 163 model calls and about 7 hours. Florence's own pieces took 4 to 15 follow-ups and around 2 hours each, plus 45 to 90 minutes for sound. The biggest piece in Florence's article, Vox's black-hole lab, took 5 h 28 min end to end, about $90 at API prices. `/fast` is up to 2.5x faster but double the price ($8/$40 instead of $4/$20 per million tokens, Florence's numbers).

On effort settings the sources disagree. Florence keeps Opus 5.5 on medium for everything and only raises it when a from-scratch piece misses twice. Movez and seeco go xhigh for new films and max when the first 3 seconds carry a launch. What I do: medium for fixes, xhigh for a new piece.

The sources also sell things (a Substack, a "sell this as a service" angle). The techniques hold up. The business stuff is anecdotes, skip it.

| Step | What you do | The one rule | 
|---|---|---|
| Pixels | Opus writes a program, Playwright renders, ffmpeg encodes | every frame is a pure function of t | 
| Rig | renderer + QA scripts + a studio CLAUDE.md | every fix becomes a rule in CLAUDE.md | 
| Assets | load the real site, real logo, real numbers | never let it draw your product | 
| Direction | fill the template, GOAL first, then TIME | swap every adjective for a number | 
| Numbers | easing, springs, durations from one tokens file | position may bounce, opacity never | 
| Bans | start from the list, extend after every render | ban defaults by name | 
| 3D | GPU flags, local server, pinned version | aim high, show style in stills first | 
| Sharpness | text after DOF, 2x+ textures, PNG, low grain | check full-size frames, crop at 200% | 
| Loop | stills, score, fix the 3 worst, 2 to 4 rounds | notes point at frames, not feelings | 
| Sound | earshot, measured, licensed | picture works muted, hits 1 frame late | 
| Ship | H.264 yuv420p, one timeline per format, palette GIF at 25 fps | reframe, never crop | 
| Alternatives | Blender for real 3D/physics, Manim for math, Remotion for templates, video models for organic shots | pick by the shot, mix routes, keep text in HTML | 

If you try this, send me what you make, I wanna see it.
