YUV.AIMOTION CATALOG עבריתעב
YUV.AI · MOTION CATALOG

What you can
ask for.

A live catalog of building blocks for AI motion graphics, explained from zero

Recharts · shadcn/ui · Lucide · Simple Icons · three.js

▶Start with the foundationsStraight to the prompt builder
Drag to rotate

Motion graphics are videos in which everything is drawn and animated, not filmed: text, charts, icons and shapes. They're usually made in Adobe's After Effects; here we write them as code. Everything that moves on this page is code running right now in your browser. There isn't a single video file here. Every element is something we can ask Claude to put into a film, and next to each one there are three things: a live demo where you can drag time back and forth, an explanation from zero (what it is, how it works under the hood, when to use it and when not to), and a full prompt to copy. No background in After Effects or programming needed: every term is explained where it first appears. On this page, an element is a whole building block you can put in a film, like a chart or a 3D phone.

24live elements9foundation concepts24full prompts1prompt builder
HOW

How to read the catalog

Four parts in every card, and one example that runs through the whole page.

LIVEDemoEvery demo is drawn from a single number: time. The bar under it, the scrubber, is the clock. Drag it to any moment and you'll see exactly the image (the frame) for that moment. That's exactly how a film gets captured.
WHAT · HOWWhat and howWhat you see, and how it works under the hood, step by step. Including what the library (ready-made code someone else wrote, explained in 0.3) does for us, so we know exactly what we're asking for.
FITWhen to use, when not toWhere the element serves the story, and where it only adds noise. A beautiful element in the wrong place weakens a film.
PROMPTPromptA complete prompt, ready to paste into Claude Code: the version of Claude that works directly on your computer, writing files, running commands and checking the result. Use the prompt on its own, or combine several elements in the prompt builder at the end.
The running example: KetzevKetzev (Hebrew for "rhythm") is a fictional fitness app. Every element in the catalog is shown as a possible scene in its launch film: the chart shows the user's progress, the interface (what we see and tap on screen: buttons, fields, menus) shows the app itself, and the 3D phone shows it on a device. That way we see not only what each element is, but where it fits in the story. The full Ketzev film, built with the same method, is on the method page.

All the elements at a glance

00

Nine concepts, and then it all makes sense

No After Effects and no programming background. Each concept builds on the one before it, so it pays to read them in order.

0.1

A video is a stack of pictures

And if we can draw any picture on demand, we can make a video.

A video is a fast sequence of still pictures. Each of these pictures is called a frame. When the pictures change fast enough, the eye stops seeing separate images and sees continuous motion. How fast? That's the fps, short for frames per second. Cinema uses 24, the web usually 30, and motion graphics of interfaces 60. Why isn't 24 enough? Because a camera smears fast movement (this is called motion blur), and the smear fills the gap between pictures. A picture drawn in code is perfectly sharp, so at 24 or 30 we see it jump. At 60 the jumps are small enough, and phone screens refresh at least 60 times per second too.

The price of 60: more pictures to compute. A 30-second video at 60fps is 30 × 60 = 1,800 pictures. Every picture is made of pixels, tiny squares of a single color, and the resolution says how many there are: 1920×1080 means 1,920 pixels across and 1,080 down. The ratio between width and height is the aspect ratio: 16:9 is a landscape screen (YouTube, presentations), and 9:16 is a portrait screen (Reels, TikTok, Shorts).

And here's the insight the whole catalog stands on: to make a video, all we need is a way to draw picture number N. If we can draw any frame on demand, we can ask for frame 0, then 1, then 2, all the way to 1,799, and stitch them together. That's the whole secret of "video that is code".

0.2

A web page: three languages, three layers

Why the browser is the best drawing engine for AI videos.

Where do the pictures come from? From a web page. The browser (Chrome, for example) is a free and remarkably powerful drawing engine: it can draw text in any language, including right-to-left languages like Hebrew and Arabic, in any font, plus shapes, images, video and even 3D. And Claude writes web page code fluently, because it's the most common kind of code on the internet.

A web page is built from three languages, and each one is responsible for a different layer. The demo below has one button from the Ketzev film. Toggle each layer off and on and see what it contributes:

  • HTML says what's there on screen: "there's a button, and it says Start workout". Each such item, a button or a heading or an image, is called an element.
  • CSS says how it looks: orange, fully rounded, in the Anton font. Without CSS we get the browser's plain gray default button.
  • JavaScript (JS for short) says what happens. In our case: how the button changes over time. Without JS the button freezes.
LAB 0.2

The three layers of one button

HTMLCSSJS

The JS lines in the demo use seek and spring. No need to understand them yet; they're explained in steps 0.7 and 0.8.

So how does a page like this become a video file? With two free tools. Playwright is a Microsoft tool that opens an invisible browser and drives it from code: "open the page, set the time to 0.5 seconds, capture", and again and again, 1,800 times. ffmpeg is a veteran program that stitches the pictures, together with the music, into a single MP4 file. It also compresses: 1,800 full pictures weigh several gigabytes, while an MP4 file mostly stores what changed from one picture to the next and gives up tiny differences, which is why it's hundreds of times smaller. The price: fine details, like a slow color gradient, can suffer.

0.3

Library, npm and license

Why we don't write everything from scratch, and why the exact name matters.

A library is code someone else wrote, tested and shared, so we don't have to write it ourselves. Drawing a chart correctly, with axes, ticks, labels and spacing, is weeks of work. A library like Recharts does it in ten lines. npm is the world's largest registry of JavaScript libraries, and it's free: the command npm install recharts downloads the library into the project. A library packaged for npm is called a package, and it has a version number like 3.2.1. The first number goes up when the new version is no longer compatible with code written for the old one, the middle one when a capability is added without breaking anything, and the last one when a bug is fixed. That's why we specify a version: if Claude knows version 2 and the project installs version 3, some of the code might not work. (A command like this is typed in the terminal, a text window where we give the computer instructions. In practice, Claude Code types it for us.)

Why does this matter in a prompt? Because Claude knows the popular libraries well, and the right way to use them. When we write "a Recharts area chart", it knows exactly what to build. When we write just "a chart", it may improvise a homemade version that looks average. An exact name turns a vague request into a clear target.

A license is the legal permission to use the code. Every element in the catalog is marked with its license, and three types keep coming up:

  • MIT and ISC: almost anything goes, including commercial work for a client. The only condition is keeping the copyright notice in the code.
  • Apache 2.0: similar, and adds protection against patent claims.
  • CC0: public domain. No conditions at all.
A code license is not a trademark licenseThe logo file (in SVG format, which we'll meet in 0.5) can be completely free, and the logo itself is still a trademark: a name or symbol the law reserves for a company, so that nobody can present themselves as the company or as acting on its behalf. Showing a logo to say that a product works with that company's service is fine, but implying that the company endorses us is not, and most companies' brand guidelines forbid distorting or recoloring their logo. Some tools are also free only up to a certain size: Remotion, a tool for making videos from code that we'll meet in chapter 09, is free for individuals and for companies of up to three employees. Before any commercial work, check.
0.4

Components and React

Lego bricks with settings, and why they're perfect for video.

A component is a building block we can use again and again, with settings. Like a Lego brick that comes in several colors and sizes: the same button can be orange or transparent, large or small. These settings are called props. React is a library from Meta for building interfaces out of components. Instead of telling the browser "move this, color that", we describe to it what the screen should look like given the data. Every time the description changes, React compares it with the previous one and changes only what actually changed on the page.

And that's exactly what video needs. We compute the frame's data from the time ("at second 3.6, progress is 72%"), hand it to React, and it draws exactly that frame. The same input always gives the same picture. A function is a recipe in code: we put a value in (the input) and it returns a result (the output), for example time goes in and a progress percentage comes out. A function that has no hidden memory, and whose result depends only on its input, is called a pure function:

a React component that is a pure function of time
function Frame({ t }) {                       // t = the time in seconds
  const progress = Math.min(100, t * 20);     // 0% at t=0, 100% at t=5
  return <Progress value={progress} />;       // a ready-made progress-bar component
}

The progress is t times 20, but never more than 100 (Math.min picks the smaller of the two). The number goes to a ready-made component that draws a progress bar (Progress, which we'll meet in chapter 02).

0.5

Vector vs. raster

Why logos and icons need to be SVG.

There are two ways to store an image. Vector stores a description: "a circle, the center is here, the radius is this much, the color is orange". When we enlarge it, the computer redraws it from the description, so the edges stay sharp at any size. The web's vector format is called SVG. Raster stores a grid of pixels (PNG, JPG): when we enlarge it, each pixel becomes a big square, or smears if smoothing is turned on.

In motion this is critical, because the camera zooms: a 3× push-in on a logo is a 3× enlargement. In the demo, both sides show exactly the same icon. Turn up the zoom and see what happens to each one:

LAB 0.5

The same icon, two ways to store it

SVGPNG

The rule: logos, icons, shapes and text are always vector. Photos are raster by nature, and that's fine, as long as their resolution is higher than the size they'll appear at after the zoom.

0.6

Three ways to draw: DOM, Canvas, WebGL

And why particles and 3D need the graphics card.

  • DOM (short for Document Object Model) is the usual way. The browser keeps every element as an object in memory, with all its styles, works out what goes where, and draws. Excellent for text and interfaces, and slow when there are thousands of elements, because each one has to be stored and computed.
  • Canvas is a blank drawing board of pixels. We give it commands ("draw a circle here") every frame, and it doesn't remember what was drawn. Very fast for thousands of shapes, but everything is on us: no automatic layout, no line wrapping.
  • WebGL is a way to draw through the graphics card (GPU). A regular processor has a few powerful cores that each work through tasks one after another; a graphics card has thousands of small cores that compute thousands of points in parallel. That's why 3D and tens of thousands of particles go through it. The three.js library makes it comfortable to use.

In the demo, the same points are drawn in all three ways, and the time is measured right now on your device. Slide it to 30,000 and press "Measure now":

LAB 0.6

What it costs to draw N points

DOMCanvas 2DWebGL

Why is the number 16.7 marked? At 60fps each frame gets 1,000 ÷ 60 = 16.7 milliseconds. Anything that takes longer stutters in live playback. But here's the important nuance: when we produce a video file, there is no budget. Playwright waits as long as each frame needs, so a heavy scene that stutters in the preview (the live playback in the browser while we work, before we produce a file) will come out perfectly smooth in the video. On one condition, which is the subject of the next step: the page draws each frame from a time number it's given, not from the clock. The budget matters for the preview and for working speed, not for the result.

0.7

The clock t: why a library's animation breaks video

The rule that applies to every element in this catalog.

Most libraries animate by the wall clock: "move this over one and a half seconds of real time". In a browser that's perfect. But while capturing a video, each frame takes 100 to 500 milliseconds to capture, because for every capture the browser has to finish drawing, turn about 2 million pixels into an image file and hand it to the program doing the capturing. That's 6 to 30 times longer than the 16.7 milliseconds of live playback. Meanwhile, the library's animation keeps running in real time, and within a few frames it's already over. The video jumps.

The fix, applied in every demo on this page: turn off the library's animation and compute everything from t. Frame number i gets t = i ÷ 60, and draws whatever should be on screen at that moment. That way frame 37 is always the same picture, no matter how long it took to capture. The function that draws the picture for moment t is called seek(t). The demo captures 12 frames with both methods, with random capture times, just like in real life:

LAB 0.7

Wall clock vs. seek(t)

seek(t)Playwright

In the top row, the dot reaches the end after just a frame or two, and the rest are wasted. In the bottom row, each frame shows a small, equal step. Every prompt on this page includes the sentence that enforces this. Most libraries have a way to turn off their built-in animation: in the Recharts chart library (chapter 01) it's a single setting, isAnimationActive={false}. The interface components we'll meet in chapter 02 have no such switch, so we delete the instructions in their code that make the browser animate on its own.

0.8

Motion that looks real: easing and springs

Why motion at a constant speed looks robotic.

Nothing in the world moves at a constant speed: a ball speeds up as it falls, a door slows down before it closes. That's why motion at a constant speed (linear motion) looks mechanical. Easing is a curve that converts "how much of the time has passed" (from 0 to 1) into "how much of the way we've covered". It comes in three common shapes, and we'll see their names in the prompts: ease-in starts slow and speeds up, ease-out slows down at the end, and ease-in-out does both.

A spring is a curve that overshoots the target a little and comes back, like a door on a spring hinge. It has two settings: stiffness (how fast it gets there) and damping (how quickly the bounces die down: higher damping, fewer bounces). In the catalog we compute the spring with a closed-form formula: we plug in t and immediately get the position, without computing all the frames before it. That's why it obeys the t rule:

a closed-form spring: position at time t, straight from the formula
const spring = (t, w = 12, z = 0.8) => {    // w = frequency (higher = faster), z = damping, must stay below 1
  if (t <= 0) return 0;
  const wd = w * Math.sqrt(1 - z * z);
  return 1 - Math.exp(-z * w * t) * (Math.cos(wd * t) + (z * w / wd) * Math.sin(wd * t));
};
// spring(0.1) = 0.38, spring(0.2) = 0.79, spring(0.4) = 1.013 (just past the target), spring(0.6) = 1.005, then 1

How to read it: in the code, w is the stiffness (a higher number means a faster spring; technically it's the spring's frequency) and z is the damping. Math.exp(…) is the brake, a number that starts at 1 and shrinks over time, which is why the oscillation fades. cos and sin rise and fall in a smooth, repeating wave between -1 and 1, and they are the oscillation itself. (If a point's horizontal position is cos of t and its vertical position is sin of t, the point moves in a circle. That's how the circles move in several of the effects later on.) z must stay below 1, because at 1 the formula divides by zero; 0.99 means almost no bounce. When the prompts say "closed-form springs", this is what they mean. An interactive lab for playing with stiffness and damping is in the springs chapter on the method page.

0.9

How we ask: the six blocks

The skeleton every prompt in the catalog is built on.

Every prompt here is built on the same skeleton, developed by the creator zero (@twoclipping) for his launch films. A language model that doesn't get precise instructions gives us the average of what it has seen, and in motion the average is a "template": dark background, glow, particles and a fade (a transition where one picture slowly dissolves and the next one appears beneath it). The six blocks erase the average:

  • <inputs>: what Claude needs to ask us for before it starts (data, logo, music), and what to do if we don't provide it.
  • <direction>: the taste. Format, colors as exact codes, fonts by name, and a list of what's forbidden. An exact color is written like this: #FF8F3A. That's three pairs of characters: how much red, how much green and how much blue, from 00 (none) to FF (the most, 255). Lots of red, some green and a little blue make orange, and that way Claude gets exactly the shade we mean.
  • <structure>: what happens when, in beats of the music. A beat is a single pulse of the song, and tempo is measured in beats per minute (BPM). At 120 BPM: 60 seconds divided by 120 = half a second per beat. 120 is common in popular music, and the numbers come out round: one beat = 0.5 seconds = 30 frames at 60fps.
  • <build>: how to build it. This is where the t rule and the technical instructions for each library go.
  • <gotchas>: known bugs, listed up front, so Claude avoids them on the first pass.
  • <start>: a cheap approval gate. First a beat map (a list of what happens on each beat) and four stills (single pictures from the film), and only after we approve, the full film. It's cheap because four pictures are created in seconds, while rendering, producing all the frames into the final file, takes many minutes.

Library names, function names and technical terms are English anyway, so these prompts work as-is in any tool. The on-screen text inside them is whatever language the film uses: here it's English, and for a film in another language we just swap those strings. A full breakdown of each block, and why it works on a language model, is in the anatomy chapter on the method page.

01

Data and charts

The moment the film proves something: growth, results, progress.

A good launch film answers the question "Why should I believe you?", and most of the time the answer is a number. In the film for Ketzev (Hebrew for "rhythm"), a fictional fitness app, this is the moment the user sees how far they came this month. The four elements here are four ways to show a number: a line that unfolds over time, a comparison between days, one big number, and a percentage of a goal.

The library we use here is Recharts: a collection of React components for charts, which draws them as SVG (that is, vector graphics, sharp at any zoom). Under the hood it borrows parts of a library called D3 for the math: where each tick mark goes, how to smooth a curve. It's free under the MIT license, and the current version is 3. The main alternatives, and when to pick them:

  • Chart.js: draws on Canvas and doesn't need React. Fast and simple, less flexible in styling. MIT.
  • Apache ECharts: hundreds of chart types, maps included. Heavier. Apache 2.0.
  • D3: the low-level layer itself. Full control over every pixel, and a lot more code. ISC.

The rule for choosing: if the project is in React and the chart is a standard one, Recharts. If we need a shape that has no name, D3. Either way, the built-in animation is off and everything is computed from t.

01.1

An area chart that draws itself

RechartsReactSVGMIT

What you see

30 days of workout minutes in the Ketzev app. The line draws itself from left to right, an orange dot rides its tip, and at the end a dashed goal line lands: 60 minutes. The viewer watches the trend being built instead of receiving it ready-made.

Try it

Check "Let Recharts animate on its own". Now the library animates by the wall clock, and the scrubber no longer controls it. That's exactly the animation that would break during capture.

How it works

  • On every frame we compute from t how much of the line is already revealed, and pass Recharts only those points, plus one in-between point so the tip moves smoothly instead of jumping a whole day at a time.
  • isAnimationActive={false} turns off the library's own animation. The reveal is ours.
  • type="monotone" smooths the line without overshooting the data, so we don't invent peaks that never happened.
  • The fill is a gradient from orange to transparent, and the goal is a ReferenceLine whose thickness grows on a spring.
When to useThe proof moment: "look how far you've come". One clear trend, with an ending that lands on the beat.
When not toMore than three lines, or exact values the viewer needs to read in two seconds. In that case, one big number (01.3) works better.
Full prompt
area chart · Recharts · paste into Claude Code
<inputs>
Ask me for the data series (one value per day) and its label. If I skip it, invent a plausible 30-day series of workout minutes that trends upward with natural noise, and say so.
</inputs>

<direction>
A 7-second clip, 1920x1080, 60fps. A Recharts AreaChart draws itself from left to right as the data is revealed. A dot in the accent colour rides the tip of the line. At the end, a dashed goal line at 60 lands on a beat with the label "Goal: 60 min".
Look: light background #EEF3FB, navy text and grid #12163F, the line is a gradient #FFC74E → #FF8F3A → #F56B49, the area fill is the same orange fading to transparent. Axis labels in Inter; the title in Anton, uppercase. Swap the colours and fonts for your brand.
Banned: 3D charts, drop shadows on the line, legends that nobody needs, gridlines on both axes.
</direction>

<structure>
120 BPM, a beat every 0.5s. Beats 0-1: the empty axes. Beats 1-9: the line draws with an ease-in-out. Beat 10: the goal line springs in. Beats 10-13: hold, with the dot pulsing once per beat.
</structure>

<build>
One HTML file with React and Recharts that exposes window.seek(t). Every frame is computed from t alone: no CSS transitions, no timers, no state between frames.
Set isAnimationActive={false} on every series. For each frame, compute how far along the line we are, pass only the points revealed so far plus one interpolated point at the tip, and render with flushSync so the DOM is ready before the screenshot.
Use type="monotone" for the curve. The goal line's stroke width and label size come from a closed-form spring of t.
Render with Playwright at 60fps and assemble with ffmpeg.
</build>

<gotchas>
- Recharts animates on its own clock by default. If isAnimationActive is left on anywhere, captured frames jump.
- Fix the Y axis domain (for example 0 to 80), or the axis rescales every frame as points are added.
- Wait for document.fonts.ready before the first frame.
</gotchas>

<start>
Show me 3 stills (beat 1, beat 6, beat 12) before rendering the full clip.
</start>
01.2

Bars that land on the beat

RechartsBarChartMIT

What you see

A week of workouts. Each bar jumps up on its own beat, half a second after the one before it, overshoots its height a little and settles back. Today's bar is painted in the brand's "fire" gradient (yellow, orange, red). The days run left to right, Sunday to Saturday. (If your film is in Hebrew or Arabic, which read right to left, reverse the order so the week starts on the right.)

How it works

  • The height of each bar = its value × spring(t − beatTime), where beatTime is the moment of that bar's own beat. We subtract the beat time to give each bar its own clock that starts at zero: before its beat the result is negative, the spring returns 0, and the bar hasn't started yet. The spring overshoots 1 a little and comes back, which is why the bar "lands" instead of stopping dead.
  • The number above a bar appears only once the bar is 60% of the way up, so the number doesn't jump ahead of it.
  • Today's color comes from Cell, a Recharts component that lets each bar have its own color.
When to useComparing 4 to 12 items and highlighting one of them. Perfect for a musical rhythm, because every bar is a beat.
When not toMore than 12 bars: they get too thin to read on a phone screen, and the animation drags on too long.
Full prompt
bar chart on the beat · Recharts
<inputs>
Ask me for the 7 values and which bar to highlight. If I skip them, use workout minutes for a week (Sunday to Saturday) and highlight Friday.
</inputs>

<direction>
A 5.5-second clip, 1080x1920 for Reels, 60fps. A Recharts BarChart of 7 days where each bar springs up on its own beat, overshoots slightly and settles. The highlighted bar is filled with the gradient #FFC74E → #FF8F3A → #F56B49; the others are navy #12163F on a light #EEF3FB background. Day labels Sun to Sat, ordered left to right. The value appears above each bar once it is most of the way up.
Banned: bars growing all at once, bouncing more than once, 3D bars.
</direction>

<structure>
120 BPM. Bar i lands on beat i+1 (0.5s apart). The highlighted bar gets a small extra scale pop on its beat.
</structure>

<build>
One HTML file with React and Recharts that exposes window.seek(t); every frame is computed from t alone, no CSS transitions or timers.
BarChart with isAnimationActive={false}. Each bar's value = target × spring(t − beatTime), where spring is this closed-form damped spring with ω = 13 and ζ = 0.5: spring(t) = 1 − e^(−ζωt)·(cos(ω_d·t) + (ζω/ω_d)·sin(ω_d·t)), ω_d = ω·√(1 − ζ²). Use Cell for per-bar colours. Render each frame with flushSync. Capture with Playwright at 60fps and assemble with ffmpeg.
</build>

<gotchas>
- Clamp the spring at 0 so a bar never dips below the axis.
- Fix the Y domain so the axis does not rescale while bars grow.
- Pass all 7 bars from the first frame (bars that have not started at 0) and animate only their heights. Adding bars as they appear changes the category widths, and every bar slides sideways.
</gotchas>

<start>
Show me stills at beats 2, 5 and 10 before the full render.
</start>
01.3

Counters that count up

CSStabular-numsIntl.NumberFormat

What you see

Three numbers that run from zero to their final value, one after another, and land with a small hop past the value and back. No library at all: it's just a number computed from t and written into an element.

Try it

Uncheck "Fixed-width digits" and watch the numbers shake while they count.

Why they shake

In many fonts, for example Inter, the font the numbers here are set in, the digit 1 is narrower than the digit 8. These are called proportional digits. When the number changes every frame, its width changes, and everything around it moves. The fix is a font feature called tabular-nums: every digit gets the same width, like in a table. (This works only if the font includes such digits; Inter does.) One line of CSS: font-variant-numeric: tabular-nums.

And for the thousands separators (1,248) we use Intl.NumberFormat, the browser's built-in tool for formatting numbers the way each language writes them.

When to useOne number that is the story: "1,248 minutes". It reads instantly, even on a phone.
When not toMore than three counters at the same moment: the eye doesn't know where to look.
Full prompt
count-up KPI tiles
<inputs>
Ask me for up to three KPIs (value, label, optional suffix like % or +). If I skip them, use: 1,248 "Workout minutes", 30 "Day streak", +38% "This month".
</inputs>

<direction>
A 4.5-second clip, 1920x1080, 60fps. Three KPI tiles side by side, ordered left to right. Each number counts up from 0 to its value and lands with a small overshoot, one tile per beat. Numbers in Inter 800, labels in Inter 600. Light tiles #EEF3FB on white, navy numbers #12163F, the middle number in orange #B4501A.
Banned: slot-machine rolling digits, confetti, numbers that keep moving after they land.
</direction>

<structure>
120 BPM. Tile 1 starts on beat 1, tile 2 on beat 2, tile 3 on beat 3. Each count lasts 1.2s with an ease-out and a tiny spring at the end.
</structure>

<build>
One HTML file exposing window.seek(t); every value is computed from t alone, no timers or CSS transitions.
displayed = round(target × easeOut(progress(t))); format with Intl.NumberFormat('en-US'); keep the suffix outside the number in its own span. Use font-variant-numeric: tabular-nums on every number. Capture with Playwright at 60fps, assemble with ffmpeg.
</build>

<gotchas>
- Without tabular-nums the numbers change width every frame and the tiles shake.
- Round before you format. Given the raw value, Intl.NumberFormat shows decimals (1,247.6) on the in-between frames.
</gotchas>

<start>
Show me one still mid-count and one at the end before rendering.
</start>
01.4

Progress ring

SVGstroke-dasharray

What you see

A ring that fills to 78% of the weekly goal, with the number counting up in the center. The ring we know from fitness apps, drawn in code, with no library at all.

Try it

Check "Show the full outline" and see that the ring is really one long dashed line.

How it works

In SVG we can turn a circle's outline into a dashed line. stroke-dasharray sets the length of the dash and also the length of the gap after it, and stroke-dashoffset shifts where the pattern starts. The trick: make the dash and the gap both as long as the whole circumference (2πr: two times pi, about 6.28, times the radius r). When we shift the dash, the gap slides into its place, and the gap is nothing, so only part of the dash stays visible:

offset = circumference × (1 − progress). When progress is 0, the whole dash sits off the circle. At 1, it covers everything. The numbers update live in the dark box. And we rotate the circle by −90°, because in SVG a circle starts at 3 o'clock, and we want it to start at 12.

When to useA percentage of a goal, a countdown, loading. A shape everyone knows from their watch and their phone.
When not toComparing several values: nested rings are hard to read. Bars (01.2) are clearer.
Full prompt
progress ring · SVG
<inputs>
Ask me for the goal and the reached value. If I skip them, fill to 78% with the label "of weekly goal".
</inputs>

<direction>
A 4-second clip, 1080x1080, 60fps. A circular progress ring fills from 12 o'clock to its value with a spring that settles, while the percentage counts up in the centre. Track in navy at 10% opacity, the arc in the gradient #FFC74E → #FF8F3A → #F56B49 with round caps, 22px thick at a 92px radius (scale up for 1080). Number in Inter 800, label in Inter 600.
Banned: glow, multiple nested rings, a ring that spins after it lands.
</direction>

<structure>
Beat 1: the track appears. Beats 1-5: the arc fills with a closed-form spring, ω = 6 and ζ = 0.85: spring(t) = 1 − e^(−ζωt)·(cos(ω_d·t) + (ζω/ω_d)·sin(ω_d·t)), ω_d = ω·√(1 − ζ²). Beat 6: a small scale pop on the number.
</structure>

<build>
One HTML file exposing window.seek(t); every attribute is computed from t, no CSS transitions.
SVG circle with stroke-dasharray = 2πr and stroke-dashoffset = 2πr × (1 − progress(t)), rotated −90° around its centre so it starts at 12 o'clock. The number uses tabular-nums. Capture with Playwright at 60fps, assemble with ffmpeg.
</build>

<gotchas>
- With round caps, a 0% ring still shows a dot. Hide the arc until progress passes 1%.
- Set the dash values as attributes every frame; a CSS transition on stroke-dashoffset would run on the wall clock.
</gotchas>

<start>
Show me a still at 50% and at the end before rendering.
</start>
02

Real UI with shadcn/ui

A product film needs an app that looks real. Here's how to get one without designing it from scratch.

The most important scene in Ketzev's film is a tour of the app: someone types, clicks, switches a tab, and we see that the product works. For it to look like a real app, we build it from real interface components. Three concepts, each one stacked on the one before:

  • Tailwind CSS is a way to write styling with classes: a class is a label we stick on an element, and the CSS decides what each label does. In Tailwind every label does one small thing, so instead of writing a separate CSS file, we write rounded-full bg-primary px-4: fully rounded, in the primary color, with horizontal padding.
  • Radix is a library of components with no styling at all, which handles all the behavior: the keyboard, focus, and screen readers (software that reads the screen aloud for people with visual impairments). A tab that works properly with the Tab key, for example.
  • shadcn/ui combines the two. It describes itself as "not a component library": instead of installing a package, we copy each component's code into the project (npx shadcn add button), and it's ours to change. That's exactly what lets us adapt it for video.

shadcn/ui is very popular, which is why it looks "like a real app". For the same reason, it can also look generic. In the demo below, every component got the Phoenix colors, the YUV.AI brand palette (navy, orange, gold), and the brand fonts (Anton for headlines, Inter for text), and that's what makes them ours.

02.1

An app tour, built from real components

shadcn/uiRadixTailwindReactMIT
  1. Card entersA card with a title, rising on a spring.
  2. Input gets typed into"Morning workout", one letter every 0.1 seconds, with a caret placed by measuring the text width.
  3. Button gets pressedThe cursor arrives and clicks, and the button shrinks and changes color.
  4. Progress fillsFrom 0 to 72% on a slow spring.
  5. Tabs switchToday → Week → Month, and the big number updates.
  6. Switch turns onThe little knob jumps to the other side.
  7. Slider movesIntensity from 30 to 80.
  8. Alert drops inA message: weekly goal reached.
  9. Badge popsA small label that pops past its size and settles back.

What you see

A Ketzev screen built from nine real shadcn/ui components. The list on the side marks which component is acting right now. Drag the scrubber backward and watch every component return to exactly the state it had at that moment, the tab and the switch included.

Controlled components: the key idea here

A regular switch remembers by itself whether it's on. This is called internal state. In a film that's a problem: if the switch "remembers" it was turned on and we jump back in time, it stays on. That's why every component here is controlled: it decides nothing on its own. On every frame we tell it from the outside what its state is, for example checked={t > 6.6}.

How it works

  • The components were copied from shadcn/ui, and every transition class was deleted from them. A class like that makes the browser animate changes by itself, on the wall clock.
  • Every value is a function of t: the typed text, the active tab, the switch position, the slider value.
  • The mouse cursor computes where the real button sits on the screen and glides to it, slowing down as it arrives.
  • The colors are defined once as variables (--primary, --accent). A variable is a name for a value we store once, and every element "inherits" the variables of the element that wraps it. So we define them once at the top, and all the components get them. That's how we swap a brand in one line.
When to useA product film, a feature demo, "here's how it works". When the viewer needs to believe there's a real app.
When not toWhen there are real screenshots and a product that already exists: use the real screens, and rebuild only what moves.
Full prompt
app tour · shadcn/ui · paste into Claude Code
<inputs>
Ask me for my app's name, its main screen (screenshot or description) and the one action the tour should show. If I skip them, invent a believable fitness app called "Ketzev" with a workout card.
</inputs>

<direction>
An 11-second product tour, 1080x1920, 60fps, built from real shadcn/ui components: Card, Input, Button, Progress, Tabs, Switch, Slider, Alert and Badge. A cursor types "Morning workout" into the search input with a real keystroke rhythm, clicks the start button, the weekly progress fills to 72%, the tabs switch day → week → month, a reminders switch flips on, an intensity slider moves from 30 to 80, an alert drops in ("Weekly goal reached") and a badge pops.
The UI is left to right (dir="ltr"). Restyle the shadcn tokens to my brand: navy #12163F as --primary, orange #FF8F3A as --accent, #EEF3FB as --background, radius 28px. Headlines in Anton, uppercase; UI text in Inter.
Banned: default shadcn grey look, default Tailwind colours, generic lorem ipsum, a cursor that teleports.
</direction>

<structure>
Card enters 0-0.9s, typing 0.9-2.4s, click at 3.1s, progress 3.4-4.4s, tabs at 4.4s and 5.4s, switch at 6.6s, slider 7.2-8.8s, alert at 8.8s, badge at 9.6s. Every event lands on a beat at 120 BPM.
</structure>

<build>
One HTML file with React, exposing window.seek(t). Add the components with npx shadcn add (or copy their source), then:
- Delete every transition-* and animate-* class from them.
- Make every component controlled: value, checked, the active tab and the slider value are props computed from t. Render each frame with flushSync.
- The cursor's target is measured from the real button's bounding box; its path is an ease-in-out from off-screen.
- Measure the typed text width with canvas measureText so the caret sits exactly after the last letter.
Capture with Playwright at 60fps, assemble with ffmpeg.
</build>

<gotchas>
- Radix components keep internal state. If they are not controlled from t, scrubbing backwards leaves them in the wrong state.
- Some transitions hide inside the parts: the Progress indicator ships with transition-all and the Switch thumb with transition-transform. Miss one and that part lags behind t.
- Tailwind's preflight reset can break the rest of the page; scope it or disable it.
</gotchas>

<start>
Show me 4 stills (typing, click, tabs on month, alert) before the full render.
</start>
03

Icons and official logos

Thousands of free symbols, and what we're allowed to do with them.

Two kinds of symbols end up in almost every film. Icons are generic symbols: a dumbbell, a flame, a clock. Logos are company symbols: GitHub, React, Claude. Both have huge free libraries, and all of them are vector (SVG), so they stay sharp at any zoom.

03.1

Self-drawing icons

Lucidelucide-reactSVGISC

What you see

Eight fitness icons, each drawing itself stroke by stroke, a quarter of a second after the one before, and at the end they all light up in orange. Try the stroke width slider: same icons, a completely different character.

What Lucide is

An open-source icon library with nearly 2,000 icons, under the ISC license (everything is allowed, commercial use included). Every icon is drawn with strokes of the same thickness on a 24×24 grid, so they all look like one family. lucide-react gives us each icon as a React component: <Dumbbell />.

How it works

It's the same trick as the ring (01.4): we measure the length of each stroke (getTotalLength), turn it into a dashed stroke with a single dash of exactly that length, and slide that dash from "hidden" to "visible" based on t. An icon built from several strokes draws them one after another.

Shortcut: if we give each stroke pathLength="1", the browser treats every stroke as if its length were 1, so there's nothing to measure.

When to useFeature lists, steps, "what you get". Self-drawing pulls the eye to the next item.
When not toMixing two icon libraries in the same film: different stroke weights and styles look sloppy.
Full prompt
self-drawing icons · Lucide
<inputs>
Ask me for the features to show (a short label each). If I skip them, use 4 fitness features: Strength, Calories, Heart rate, Timer.
</inputs>

<direction>
A 5-second clip, 1920x1080, 60fps. Four Lucide icons (Dumbbell, Flame, HeartPulse, Timer) in a row, left to right. Each icon draws itself stroke by stroke on its own beat, then its label rises out of a mask line under it. When all are drawn, they light up in orange #FF8F3A together. Icons navy #12163F, stroke width 2, on #EEF3FB. Labels in Inter 700.
Banned: icons fading in, mixing icon libraries, filled icons.
</direction>

<structure>
120 BPM. Icon i starts drawing on beat i+1 and takes 1 beat. All light up on beat 7.
</structure>

<build>
One HTML file with React and lucide-react, exposing window.seek(t); everything is computed from t, no CSS transitions.
Give every path pathLength="1", set stroke-dasharray to 1, and animate stroke-dashoffset from 1 to 0 with an eased progress of t. Multi-path icons draw their paths in sequence. Capture with Playwright at 60fps, assemble with ffmpeg.
</build>

<gotchas>
- Without pathLength="1", paths of different lengths draw at different speeds.
- lucide-react sets stroke on the svg element; set the dash styles on each child path, circle and line.
</gotchas>

<start>
Show me a still at beat 2 and at beat 7 before rendering.
</start>
03.2

Official logos

Simple IconsSVGCC0 · trademarks apply

What you see

A wall of 16 logos from the tools that built this page, popping in one after another in their official brand colors. Try unchecking "Official brand colors" and checking "Dark background". The one-color version, in black or white, is usually the only version brands allow besides their official color, which is why the demo switches to it and not to some other color.

What Simple Icons is

A collection of more than 3,400 brand logos, each one a clean SVG, together with the brand's official color code. The simple-icons package on npm gives us both the shape and the color of every logo, so Claude doesn't have to draw a logo from memory, and won't invent a shape that's "roughly" right.

What's allowed and what isn't

The files themselves are CC0, public domain. But a logo is a trademark, and that's a different law entirely. Simple Icons itself asks everyone to respect each company's brand guidelines. In practice:

  • OK: showing a logo to say "works with" or "built with".
  • Not OK: changing it to an unofficial color, stretching it, cropping it or adding effects.
  • Not OK: implying that the company endorses us or is our partner.
  • The current official version of a logo lives on the company's Brand or Press page.
When to use"Works with your tools", "built with", clients who agreed to appear.
When not toClients who haven't agreed, or any situation where the logo looks like an endorsement we never got.
Full prompt
official logo wall · simple-icons
<inputs>
Ask me which brands to show and confirm that I have the right to show each of them. If I skip it, use the tools this project was built with: Claude, React, Three.js, Tailwind CSS, shadcn/ui, FFmpeg, Node.js, GitHub.
</inputs>

<direction>
A 4.5-second "built with" clip, 1920x1080, 60fps. A grid of official logos pops in on the beat, one after another from the left, each in its official brand colour on white tiles over #EEF3FB. A title above in Anton, uppercase: "Built with". Logos keep their exact proportions and colours.
Banned: recoloured logos, logos with glow or shadows, logos drawn from memory, distortion.
</direction>

<structure>
120 BPM, 16th-note stagger: a new logo every 0.125s, each with a short spring scale from 0.6 to 1.
</structure>

<build>
One HTML file exposing window.seek(t); everything is computed from t, no CSS transitions.
Import each logo from the simple-icons npm package (import { siReact } from 'simple-icons'), use its .path in a 24×24 viewBox and "#" + its .hex as the fill (.hex comes without the #). Do not redraw or modify any logo. Capture with Playwright at 60fps, assemble with ffmpeg.
</build>

<gotchas>
- Some brands (for example GitHub) have black logos; on a dark background, switch to the brand's official white version, never an invented colour.
- simple-icons occasionally removes logos at a brand's request; if one is missing, ask me for the official file instead of approximating it.
</gotchas>

<start>
List the logos you will use and their hex colours, then show me the final frame before rendering.
</start>
04

Kinetic typography

Text that moves with the music. Four techniques that replace almost every "fade".

Kinetic typography is text whose motion is part of the message: a word that lands hard feels important, a word that gets typed out feels like a conversation. In a launch video most of the screen is text, which makes this maybe the most important element in the catalog. The demo below runs four techniques back to back, two and a half seconds each.

04.1

Four techniques for moving text

Canvas 2DAntonRTL

SLAM: words that land

Each word appears on its own beat, starts too big and shrinks to its size with a spring, like a rubber stamp. Made for headlines of three or four words at the peak of the music.

MASK RISE: rising from a line

The words rise out of an invisible line, as if they were waiting just below it. The "line" is a mask: a rectangle that shows only what's inside it, like a stencil. The text moves and the mask doesn't, so the words look like they're born from the line.

POP TYPE: typing

Letter after letter, each one pops a little as it's typed, with a blinking cursor. The pace is uneven, like a real person typing, and every keystroke lands on a half beat or a quarter beat (what musicians call an "eighth note" and a "sixteenth note"; at 120 BPM those are 0.25 and 0.125 seconds), so the typing "plays" along with the music.

MARKER: highlighter

A strip of color stretches behind the important word, in the reading direction (left to right in English, right to left in Hebrew). The simplest way to tell the viewer "this is the word".

Right-to-left languages (Hebrew, Arabic): three things to ask forReveal words from right to left, in reading order. Wrap numbers and English words in unicode-bidi: isolate. Why? The browser lays out Hebrew right to left, and English and numbers left to right. Neutral characters like +, % and the space take their direction from their neighbors, so sometimes they wander to the other side. This setting tells the browser to lay out the wrapped piece as one sealed unit, without mixing it into the direction of the sentence. And give condensed display fonts more size: Anton is condensed (so is Karantina, our Hebrew headline font), so a headline in it can be set about 1.25× larger than one in a wider font and still carry the same visual weight.
When to useIn every launch video. SLAM for the peak, MASK RISE for sentences, POP TYPE for prompts and chats, MARKER for a key word.
When not toMore than two techniques in the same scene: the text starts competing for attention instead of telling a story.
Full prompt
kinetic typography · English
<inputs>
Ask me for the exact on-screen lines and which word is the key word. If I skip them, use: "BUILD. RENDER. SHIP." (slam), "Every word rises from a line" (mask rise), "Start morning workout" (typed), and mark "RENDER".
</inputs>

<direction>
A 10-second kinetic typography clip, 1920x1080, 60fps, English, left to right. Four moments, each on its own colour spread with a hard wipe between them: SLAM (words land one per beat from 1.6× scale with a spring), MASK RISE (words rise out of an invisible line and leave through it), POP TYPE (letters type on with a slight pop and a blinking caret), MARKER (an orange marker swipe from left to right behind the key word).
Type: Anton uppercase for headlines (it is condensed, so it can run about 1.25× the size you would give a wider font), Inter for small text. Colours: navy #12163F, orange #FF8F3A, gold #FFC74E, light #EEF3FB, denim #AFCBEF.
Banned: fades, blur-ins, letter-by-letter scramble effects, text that moves after it lands.
</direction>

<structure>
120 BPM. Slam: one word per beat. Mask rise: words 0.12s apart. Typing: one keystroke per 16th note (0.125s), with 2 short pauses between words. Marker: swipes in over half a beat on the downbeat.
</structure>

<build>
One HTML file exposing window.seek(t), drawing on a 2D canvas (or DOM) from t alone.
Lay out each line once (measure words with measureText, left to right), then move and clip each word as a function of t. The mask is a clip rectangle; the word's y offset goes from below the line to 0 with a spring. Capture with Playwright at 60fps, assemble with ffmpeg.
</build>

<gotchas>
- Measure with the real font loaded (await document.fonts.ready), never with the fallback font.
- If a line is in a right-to-left language (Hebrew, Arabic), set ctx.direction = 'rtl' and textAlign = 'right', and wrap numbers and English words with unicode-bidi: isolate (DOM) or draw them as separate runs (canvas), or they jump to the wrong side.
- Never split a word letter by letter across lines.
</gotchas>

<start>
Show me one still of each of the four moments before rendering.
</start>
05

3D with three.js

Phones, glass, particles, ocean. And what every word in the prompt means.

three.js is the most popular library for 3D on the web, free under the MIT license. It sits on top of WebGL (the browser's way of drawing with the graphics card) and spares us the thousands of lines of code WebGL demands. To understand a 3D prompt, it helps to think of a film set:

  • Scene: the stage, where we place everything.
  • Camera: where we look from, and how wide the view is (wide like a wide-angle lens, or narrow like a telephoto, zoom-style lens).
  • Mesh (an object): every object in the scene. It is built from two parts: Geometry, the shape itself, and Material, the surface: its color, how shiny it is, how rough it is, whether it is transparent. The graphics card can draw one thing very fast, triangles, so every shape, even a sphere, is broken down into lots of small triangles.
  • Light, and the Environment map: a 360° image of the surroundings that shows up as reflections in shiny surfaces. Without it, metal and glass look gray and dead, because metal has almost no color of its own: what we see in it is whatever it reflects, and with no surroundings it reflects nothing.
  • Renderer (the photographer): turns all of this into one 2D image, the frame.

Two more terms keep coming up in the prompts. PBR (Physically Based Rendering) means materials that respond to light according to the laws of physics, so they look real under any lighting. We control them mostly with two numbers between 0 and 1: metalness (how metallic it is) and roughness (how rough it is: 0 is a mirror, 1 is chalk). GLTF is the standard file format for 3D models, "the JPEG of 3D": a model we built in a 3D program (for example Blender, which is free and appears at the end of this chapter) or downloaded from a site comes into three.js as a GLTF file. Drag any demo here to rotate it. That works only in the preview: in the film, the angle is computed from t.

05.1

Phone and laptop with live screens

three.jsCanvasTexturePBRMIT

What you see

A laptop lid opens, a phone rises beside it, and the camera slowly circles around them. The screens show live content: on the phone, a push-up counter fills up; on the laptop, code is being typed. Everything builds itself from simple shapes in code, with no model file at all.

An honest answer about "iPhone" and "MacBook"

These are generic devices inspired by a modern phone and laptop, not Apple models. Apple's device designs and its logo are legally protected. If we need a recognizable device, we use a GLTF model whose license allows that use, or the official device templates Apple publishes for marketers, following Apple's guidelines.

How it works

  • Each device's body is a box with rounded corners in a metallic PBR material, and it reflects a virtual "studio room" (RoomEnvironment).
  • The live screen: every frame, we draw the user interface on a regular 2D Canvas and turn it into a texture (CanvasTexture), a "sticker" stretched over the 3D screen. needsUpdate = true tells three.js to copy the new drawing to the graphics card. By default it does not copy, because copying an image to the graphics card every frame is expensive, and in most textures the image never changes.
  • The lid is a hinge: its rotation is computed from t with a spring. The camera moves on a circular path around the devices.
When to use"It works on every device", showing an app inside a 3D world, a transition from a flat interface to a real product.
When not toWhen the viewer needs to read what's on the screen: at a 3D angle, small text gets lost. Show the screen flat first, and only then put it into the device.
Full prompt
3D phone + laptop with live screens · three.js
<inputs>
Ask me what each screen should show. If I skip it: the phone shows a push-up counter ring filling to 12 reps with the title "Push-ups", the laptop shows code typing line by line.
</inputs>

<direction>
A 9-second 3D clip, 1920x1080, 60fps, three.js. A generic modern laptop opens its lid while a generic phone rises beside it; the camera orbits slowly around both. Both screens are live and animate in sync with the film.
Devices: rounded aluminium-like bodies (PBR, metalness 0.8, roughness 0.3), black glass screens with a thin bezel, no brand logos. Background: the clear sky gradient #5EA9F2 → #93CBFF → #DDF0FF, a soft contact shadow under the devices. Accent colours on screen: #FFC74E, #FF8F3A, #F56B49.
Banned: Apple logos or exact Apple product shapes, floating particles, lens flares, a camera that reverses direction.
</direction>

<structure>
120 BPM. Beats 0-3: lid opens on a spring. Beats 2-5: the phone rises. Beats 4-17: slow orbit, the counter ring fills on screen, the code types on the laptop at one line per beat.
</structure>

<build>
One HTML file with three.js that exposes window.seek(t); every transform and every screen pixel is computed from t, no clocks.
Build the devices from rounded boxes. For each screen, draw its UI on an offscreen 2D canvas every frame, wrap it in a CanvasTexture with colorSpace = SRGBColorSpace, and set needsUpdate = true after drawing. Use RoomEnvironment through PMREMGenerator for reflections and ACES tone mapping. The lid's rotation and the phone's height come from closed-form springs of t; the camera orbit angle is a smooth function of t.
Capture with Playwright at 60fps (preserveDrawingBuffer on the renderer), assemble with ffmpeg.
</build>

<gotchas>
- Forgetting needsUpdate = true leaves the screen frozen on its first frame.
- Screen text drawn on the canvas must be large; on a tilted 3D screen anything under about 28px becomes unreadable.
- Never call renderer.setAnimationLoop; render once inside seek(t).
</gotchas>

<start>
Show me 3 stills (lid half open, phone up, mid orbit) before rendering.
</start>
05.2

Breathing glass orb

three.jsMeshPhysicalMaterialshader

What you see

A glass orb that slowly changes shape, like a droplet, with a ring and small spheres circling it. Behind it sits the word YUV.AI, and the glass bends the text, exactly the way a glass of water distorts whatever is behind it.

The material: real glass

MeshPhysicalMaterial is the advanced material in three.js. Three settings turn it into glass: transmission (how much light passes through it), thickness (how thick it is, because thick glass bends light more), and ior, the index of refraction: how much light bends when it enters the material. For glass it's about 1.5, for water 1.33.

How it "breathes"

Every point on the orb's surface is pushed outward or inward according to noise: a function that gives random but smooth values, so neighboring points get similar values, and the result looks organic rather than jittery. The calculation is done by a shader: a small program that runs on the graphics card, on all the points in parallel. We inject a few lines into the built-in three.js shader with onBeforeCompile, and pass t to it as a variable (uTime).

When to useA "premium" moment, a logo or product name behind glass, a transition between chapters.
When not toOn a bright, flat background: the glass has nothing to refract, and it looks like gray plastic.
Full prompt
liquid glass orb · three.js
<inputs>
Ask me for the word that sits behind the glass. If I skip it, use "YUV.AI".
</inputs>

<direction>
A 12-second loopable 3D clip, 1920x1080, 60fps. A liquid glass orb slowly breathes (its surface swells and settles organically) in front of a large word set in Anton in the fire gradient #FFC74E → #FF8F3A → #F56B49, which it refracts. An orange torus ring tilts slowly behind it and five small glossy spheres in brand colours (#FFC74E, #12163F, #F56B49, #5EA9F2, #D74270) orbit it. Background: the sky gradient #2C78D2 → #93CBFF → #F4FBFF, one white key light from the upper right.
Banned: rainbow chromatic aberration, bloom everywhere, particles, a glass that looks grey.
</direction>

<structure>
One breath cycle every 4 beats at 120 BPM (2s). The orbiting spheres complete one orbit over the full 12s so the clip loops seamlessly.
</structure>

<build>
One HTML file with three.js that exposes window.seek(t); every uniform and transform is computed from t.
Material: MeshPhysicalMaterial with transmission 1, thickness 1.2, ior 1.45, roughness 0.05, an environment map from RoomEnvironment via PMREMGenerator. Displace vertices along their normals with 3D simplex noise injected through onBeforeCompile; the shader's uTime uniform is set from t every frame. Use a high-segment sphere (at least 128×128) so the displacement is smooth.
Capture with Playwright at 60fps, assemble with ffmpeg.
</build>

<gotchas>
- Transmission needs an environment map and something behind the object, or the glass reads as grey.
- After displacing vertices, recompute normals in the shader (or use a normal-from-noise approximation), or the lighting looks flat.
- For a seamless loop, make every periodic motion an integer number of cycles over the clip's length.
</gotchas>

<start>
Show me 2 stills (inhale and exhale) before rendering.
</start>
05.3

20,000 particles that morph

three.jsPointsGPU

What you see

Twenty thousand points moving between four shapes: a sphere, a knot, the word AI, and a galaxy. The same technique runs at the top of this page. Wait: aren't "particles" on zero's banned list? They are, because aimless particles drifting in the background are a cliché. Particles that turn into a word and tell us something are a technique.

How it works

  • Points is a three.js object that draws every point in a list as a dot on screen, all of them at once on the graphics card. 20,000 dots as regular page elements (the DOM) would choke the browser (see lab 0.6).
  • For each shape we precompute 20,000 target positions. The word is sampled from a drawing of it: we write it on a 2D Canvas and read which pixels got painted.
  • Each point's position = a blend between the previous shape and the next one, following the progress p with easing: mix(A, B, ease(p)). Every point gets a small delay of its own, so the transition looks like a swarm and not like a solid block.
When to useAn opening or a closing, "from chaos to order", a logo built from thousands of points.
When not toAs a moving background behind text: that's exactly the cliché, and it competes with reading.
Full prompt
particle morph · three.js Points
<inputs>
Ask me for the word or logo the particles should form. If I skip it, use "AI".
</inputs>

<direction>
An 11-second 3D clip, 1920x1080, 60fps. 20,000 particles morph from a sphere into a torus knot, into a word, into a spiral galaxy, holding each shape for about 1.6s. Particles are small soft dots in #FFC74E, #FF8F3A, #F56B49 and a little #5DE4E9, additive blending on #0B0E2A. The camera drifts slowly.
Banned: particles drifting randomly in the background, bloom halos around everything, a linear morph.
</direction>

<structure>
120 BPM. Each morph takes 3 beats (1.5s) and starts on a downbeat; each hold is just over 3 beats.
</structure>

<build>
One HTML file with three.js that exposes window.seek(t); positions are computed from t every frame, no simulation state.
THREE.Points with a fixed count of 20,000. Precompute a Float32Array of target positions per shape: sphere (Fibonacci sphere), torus knot (TorusKnotGeometry vertices, resampled), word (draw it on a 2D canvas, sample lit pixels, add a little depth jitter), galaxy (logarithmic spiral arms with noise). position = mix(A, B, easeInOut(progress)), with a small per-particle delay. Custom point shader for round soft dots.
Capture with Playwright at 60fps, assemble with ffmpeg.
</build>

<gotchas>
- Every shape must have exactly the same particle count; resample, never truncate.
- Seed the random jitter (a fixed seed), so frame 300 is identical on every render.
- Additive blending with depthWrite on causes black squares; set depthWrite: false.
</gotchas>

<start>
Show me one still of each shape before rendering.
</start>
05.4

Ocean and sky: from day to sunset

three.jsSkyWater

What you see

A calm sea and a sky that moves from midday to sunset and back, in twelve seconds. This is the time-lapse from the YUV.AI MOTION launch film, where the devices float above the water.

How it works

  • Sky is a three.js addon that computes the color of the sky from the sun's position, using a physical model of how light scatters in the atmosphere. Why orange? Air scatters blue light far more than red light. When the sun is low, its light passes through much more air on the way to us, most of the blue scatters off to the sides, and what reaches us is mostly orange and red. We don't paint a sunset: we lower the sun.
  • Water is an addon that draws the scene one more time, as if in a mirror under the surface of the water, and distorts that reflection with a wave pattern that moves according to t.
  • The time of day is a single number computed from t, and from it we derive the sun's height, the color of the light and the exposure (the overall brightness of the image).
When to use"Time passing", atmosphere, a backdrop for a 3D product scene, an emotional ending.
When not toA pure interface film: a whole world distracts from the app.
Full prompt
ocean time-lapse · three.js Sky + Water
<inputs>
Ask me whether something should float above the water (a product, a logo). If I skip it, keep the scene empty.
</inputs>

<direction>
A 12-second 3D clip, 1920x1080, 60fps. A calm open ocean under a physically based sky. Time of day goes from midday to golden hour to sunset and back to day, so the clip loops. The camera is low over the water and drifts forward slowly. Warm sunset tones should land near #FF8F3A and #F56B49, the day sky near #5EA9F2.
Banned: lens flares, birds, a sun that jumps, choppy stormy waves.
</direction>

<structure>
120 BPM. The sun reaches its lowest point on beat 12 (6s), exactly mid-clip.
</structure>

<build>
One HTML file with three.js and its Sky and Water addons, exposing window.seek(t).
One number tod(t) from 0 (midday) to 1 (sunset) drives the sun's elevation, the Sky's turbidity and rayleigh, the directional light's colour and the renderer's exposure. The Water shader's time uniform is set from t every frame.
Capture with Playwright at 60fps, assemble with ffmpeg.
</build>

<gotchas>
- The Water addon advances its own time if you let it. Set material.uniforms.time.value from t every frame.
- Update the sun direction on both the Sky and the Water, or the reflection points at a different sun.
- Lower exposure at sunset, or the sky blows out to white.
</gotchas>

<start>
Show me stills at midday, golden hour and sunset before rendering.
</start>
05.6

3D equalizer

three.jsInstancedMesh

What you see

A 20×20 grid of bars with a wave running through it, and every beat gives it a "kick" from the center outward. The color depends on height: low bars are blue, medium ones orange, tall ones gold. Here the wave comes from a formula; in a real film the heights come from the music itself.

From the music, without breaking the clock

A live audio analyzer in the browser runs on the wall clock (real time, not the film's t), so it doesn't work for frame-by-frame capture. Instead, we analyze the song in advance: for every frame we compute the spectrum, how much energy there is in each frequency range (bass, mids, highs), and store it in a table. During capture we just read the row for t.

How it works: one shape, 400 instances

Every separate object in three.js is a separate drawing instruction that the processor (CPU) sends to the graphics card (a draw call). Thousands of these every frame slow the processor down, even when the graphics card has capacity to spare. This matters mostly for the live preview (lab 0.6); the film comes out smooth either way. InstancedMesh sends one instruction: "draw this box 400 times, here are the position and color of each one". Same image, a fraction of the work.

When to useA music video, "the music is the clock", an energetic build-up before the drop.
When not toA film without prominent music: an equalizer moving over silence looks disconnected.
Full prompt
3D audio equalizer · InstancedMesh
<inputs>
Ask me for the music track. If I skip it, use a royalty-free 120 BPM track with a clear kick (Mixkit, licensed for commercial use) and tell me which one.
</inputs>

<direction>
A 12-second 3D clip, 1920x1080, 60fps. A 20×20 grid of bars seen from a three-quarter angle above. Bar heights follow the music: bass in the centre, highs at the edges, and every kick sends a ring-shaped pulse from the centre outward. Colour by height from blue #2C78D2 through orange #FF8F3A to gold #FFC74E. Background #0B0E2A, a soft rim light.
Banned: random heights not tied to the audio, bloom on everything, a camera that shakes.
</direction>

<structure>
The track's own beat grid drives everything: measure the BPM and the kick times first.
</structure>

<build>
Analyse the track offline first (Python with librosa, or ffmpeg plus an FFT): for every frame at 60fps, store the energy of 20 frequency bands and the kick onsets in a JSON file.
Then one HTML file with three.js exposing window.seek(t): one InstancedMesh of 400 boxes; for each frame, read row round(t × 60) of the JSON, set each instance's scale.y and colour with setMatrixAt and setColorAt, then set instanceMatrix.needsUpdate and instanceColor.needsUpdate. The pulse is a ring whose radius grows from each kick time.
Capture with Playwright at 60fps and mux the same audio with ffmpeg.
</build>

<gotchas>
- A live Web Audio analyser runs on the wall clock. Precompute the spectrum, or picture and sound drift apart.
- Scale boxes from their base, not their centre (translate the geometry up by half its height once).
- Smooth the band energies over 2-3 frames, or the bars flicker.
</gotchas>

<start>
Show me the detected BPM, the kick times and 2 stills before rendering.
</start>

When you need more than this

  • React Three Fiber: three.js in the form of React components (React is a popular library for building web interfaces). Useful when the scene is large and the project is already built in React. MIT. Version 9 is made for React 19.
  • Spline: a visual 3D editor in the browser, no code. We design by hand and export to GLTF, three.js or React. There is a free plan.
  • Blender: a full 3D program, free, under the GPL license: we may use it for any purpose, commercial included, and what we create with it is ours; the GPL terms apply only to someone who modifies the program itself and distributes it. We build custom models in it and export them as GLTF. A steep learning curve, results without limits.
06

Under the hood: WebGL, WebGPU and shaders

Who actually paints the pixels, and why it matters when we capture a film.

In the previous chapter three.js did the work for us. Now we go one layer down, to two questions: who actually draws, and in which language we talk to it.

  • Graphics card (GPU): a processor made of thousands of small cores that do the same operation on lots of data at once. The regular processor (CPU) is a few expert chefs; the graphics card is a thousand cooks who each chop one cucumber. Drawing an image is exactly that kind of work: millions of pixels, and the same calculation for each one.
  • WebGL: the browser's long-standing way to talk to the graphics card, since 2011 (and version 2 since 2017). It works in almost every browser and device, and three.js uses it by default.
  • WebGPU: the new way. Faster when there are many objects, and it can also do general computing (compute): using the graphics card not only for drawing but for any calculation that repeats, like a simulation of hundreds of thousands of particles. According to Google (November 2025) it is available in Chrome and Edge (on Windows, macOS, ChromeOS and Android), in Safari 26, and in Firefox on Windows and on Macs with ARM chips. On Linux it is still on its way, and that matters to us, because render machines usually run Linux.
  • Shader: a small program that runs on the graphics card. A fragment shader runs once for every pixel in every frame and returns a colour. A vertex shader runs once for every point of a shape and decides where it appears on screen. In WebGL shaders are written in GLSL, in WebGPU in WGSL.
  • TSL (Three Shading Language): three.js's shader language. We write it as ordinary JavaScript, and it turns into WGSL or GLSL by itself, depending on what the browser supports.

Why does this matter for a film? Because a shader is exactly a function of time: colour = f(pixel, t). It is the same idea as seek(t), except the graphics card runs it on a million pixels at once. A sunset, a sea, marble and film grain are a few lines of code, with no image file at all.

The video-render trapWhen Playwright captures the film, it runs the browser headless: with no window, usually on a server. In that mode Chrome turns the graphics card off by default. WebGL keeps working through SwiftShader, software that imitates a graphics card on the regular processor: slower, but it works and comes out the same every time. WebGPU needs special flags, and on some servers drivers too. We tested it: with the flags --enable-unsafe-webgpu --enable-features=Vulkan --use-vulkan=swiftshader --use-webgpu-adapter=swiftshader the WebGPU demo below runs even without a graphics card. Without the flags, three.js falls back to WebGL 2 by itself.

How to choose an engine, in four lines

  1. Interface, buttons and text: HTML and CSS. The browser already draws them very well.
  2. Illustrations, charts and 2D motion: Canvas 2D, a drawing surface we paint on with commands.
  3. 3D, materials and shaders: three.js with WebGLRenderer. The safe choice for rendering.
  4. Hundreds of thousands of particles or a simulation: three.js with WebGPURenderer, which falls back to WebGL 2 by itself when there is no WebGPU.
06.1

Shader: a colour for every pixel

WebGL 2GLSLno library

What you see

Five worlds drawn without a single image and without a library: a sunset, a sea, marble, a liquid gradient and paper with grain. Each one is a single function a few lines long. Open "The code that paints every pixel" to see it, and pick a world with the buttons.

How it works

  • The canvas is covered by one triangle bigger than the screen. The graphics card runs the shader once for every pixel the triangle covers, which means the whole canvas, all in parallel.
  • Every pixel gets its position (p) and the time (t) and returns a colour. There is no memory between frames, so any frame can be computed on its own, exactly like seek(t).
  • Marble and paper are made of noise: a function that returns random numbers that change gradually, like a landscape of hills rather than TV static. We add a few layers of noise at different sizes (fbm) and bend one noise with another (domain warping), and marble veins appear.
  • The grain in the gradient and the paper is not decoration: a smooth gradient breaks into colour "steps" after video compression (banding), and fine noise hides it.
When to useBackgrounds, skies, water, textures and atmosphere, with no heavy files and full quality at any resolution.
When not toAn object with a defined shape (a phone, a bottle). There, three.js with a model is better.
Full prompt
background · raw WebGL2 fragment shader
<inputs>
Ask me which look I want behind the film (sunset sky, sea, marble, a liquid brand gradient, paper with grain) and my brand colours. If I skip it, use a liquid gradient in my brand colours.
</inputs>

<direction>
A full-frame animated background drawn by a single fragment shader, 1920x1080, 60fps, under the rest of the film. It moves slowly and continuously and never competes with the text on top of it. Colours come only from my palette. Fine film grain on top so gradients never band after compression.
Banned: image files for the background, video loops, a background that moves faster than the foreground, visible banding.
</direction>

<structure>
The background runs for the whole film. Its slowest motion completes one cycle over the full length, so the first and last frames match.
</structure>

<build>
Raw WebGL2 (no library): one triangle that covers the canvas, and a GLSL fragment shader with uniforms u_t (seconds), u_res and u_aspect. color = f(gl_FragCoord, u_t) with no state between frames, so seek(t) can render any frame in any order.
Build textures from value noise and fbm (4 to 5 octaves); use domain warping for marble-like veins; add grain as hash(pixel + floor(t * 24)) at about 3% amplitude.
Set u_t from seek(t) every frame; create the context with preserveDrawingBuffer: true so Playwright can capture the canvas.
</build>

<gotchas>
- smoothstep(edge0, edge1, x) with edge0 > edge1 is undefined in GLSL. Write 1.0 - smoothstep(a, b, x) instead.
- Waves near the horizon get so dense that they flicker (aliasing). Fade their amplitude to zero near the horizon.
- Headless Chrome has no GPU by default; WebGL still runs through SwiftShader, only slower. Keep the shader cheap (loops of 5 steps at most).
- Use highp precision, or noise turns blocky on some mobile GPUs.
</gotchas>

<start>
Show me 3 stills of the background (start, middle, end) under a sample headline before rendering.
</start>
06.2

WebGPU: two hundred thousand particles

three.jsWebGPURendererTSL

What you see

200,000 particles that move from a sphere to a galaxy to a wave. The corner shows what your browser is actually running: WebGPU, or WebGL 2 when it has no WebGPU.

How it works

  • three.js comes in two versions: three with WebGLRenderer, and three/webgpu with WebGPURenderer. The second tries WebGPU and, if it is missing, falls back to WebGL 2 by itself. That takes a moment, so we wait for await renderer.init() before the first frame.
  • All the particles are copies of the same small square (instancing): the graphics card gets one shape and a number of copies, and draws them all in one command.
  • Each particle's position is computed on the graphics card, in TSL: from the particle's index we derive three fixed random numbers, from those three shapes, and one number that comes from t decides how far we are from one shape to the next. Each particle starts a little after its neighbour, which turns the morph into a wave.
  • WebGPU's great strength is general computing (compute), such as a simulation in which every frame is built from the previous one. In a film we prefer a pure function of t, so we can jump to any frame, which is why everything here is recomputed every frame directly from t.
When to useA "wow" moment of sheer quantity, morphing between shapes, a logo built from particles.
When not toParticles as decoration on a black background. That is exactly the "template" zero bans.
Full prompt
particles · three.js WebGPURenderer + TSL
<inputs>
Ask me for the word or logo the particles should end on, if any. If I skip it, morph sphere → galaxy → wave.
</inputs>

<direction>
A 12-second particle scene, 1920x1080, 60fps: 200,000 soft particles on deep navy that morph between three shapes, each particle starting slightly after its neighbour so the morph ripples. Colours: the phoenix fire gradient and the clear sky blues, additive, never pure white.
Banned: random jitter that changes every frame, particles that pop in or out, a wall of white from over-bright additive blending.
</direction>

<structure>
120 BPM. Beats 0-6: the first shape, turning slowly. Beats 6-10: the morph to the second. Beats 10-16: hold and turn. Beats 16-20: the morph to the third. Beats 20-24: hold.
</structure>

<build>
three.js WebGPURenderer from 'three/webgpu' (it falls back to WebGL 2 by itself); await renderer.init() before the first seek(t).
One instanced sprite mesh with SpriteNodeMaterial and mesh.count = 200000. Positions are computed in TSL from instanceIndex: three fixed hashes per particle, three target shapes, and one uniform morph value that seek(t) sets every frame. No compute pass and no state between frames, so any frame can be rendered in any order.
Alpha is a soft disc from uv(); colour mixes fire and sky by a per-particle hash.
</build>

<gotchas>
- WebGPURenderer does not support ShaderMaterial or onBeforeCompile; write custom shading in TSL.
- Headless Chrome has no WebGPU by default. Launch it with --enable-unsafe-webgpu --enable-features=Vulkan --use-vulkan=swiftshader --use-webgpu-adapter=swiftshader, or let it fall back to WebGL 2, and log which backend actually ran.
- Additive blending of 200,000 particles blows out to white. Keep each particle's colour around 25% brightness.
- Import everything in this file from 'three/webgpu'; mixing it with 'three' loads two copies of the library.
</gotchas>

<start>
Show me one still per shape and tell me which backend the render used.
</start>
07

Effects and transitions

The "AI is thinking" neon, glass, an aurora glow, and transitions without a fade.

07.1

Neon edge: "the AI is thinking"

CSSconic-gradientmask

What you see

A text field with a colored edge spinning around it and a soft halo that pulses on every beat, while a request types itself inside. This has become the standard visual cue for the moment an AI is processing a request.

How it works

  • conic-gradient is a color gradient that sweeps around a point, like the hands of a clock: every angle gets a different color. Change the starting angle, and the colors rotate.
  • mask cuts only a thin ring at the edge out of that gradient, so all that's left is a colored outline.
  • A second copy of the same gradient, blurred and placed behind, creates the halo. On every beat it briefly gets stronger.
  • The angle = t × 1.5 radians. A radian is a unit of angle, like a degree: a full turn is 2π, about 6.28 radians. At 1.5 radians per second we get a full turn about every 4.2 seconds, slow enough to feel like "thinking" rather than dizzying. On the page itself (the button at the top) the neon spins with a regular CSS animation, because nothing is being captured there. In the film, the angle comes from t.
When to useA prompt field, the main action button, one central card. One element per screen.
When not toOn everything: neon everywhere cancels itself out and turns into noise.
Full prompt
neon "AI is thinking" edge · CSS
<inputs>
Ask me for the request that types into the field. If I skip it, use "Launch film for a fitness app".
</inputs>

<direction>
A 6-second clip, 1920x1080, 60fps. A large white pill-shaped input field on the sky gradient #5EA9F2 → #93CBFF → #DDF0FF. Around it, an iridescent neon edge (a conic gradient of #5DE4E9, #4FA3FF, #8B6CFF, #FF66C4, #FF8F3A, #FFC74E) rotates slowly, with a soft blurred halo behind it that swells on every beat. The placeholder "What is the film about?" is replaced by the request typing in, with a blinking caret, and a round send button in the fire gradient with its arrow pointing right. Text in Inter, left to right.
Banned: a glow that covers the text, neon on more than this one element, flicker.
</direction>

<structure>
120 BPM. The halo pulses on every beat. Typing starts on beat 2 at one letter per 16th note.
</structure>

<build>
One HTML file exposing window.seek(t); every style is computed from t.
The ring is a pseudo-element with background: conic-gradient(from var(--ang), …), masked to a 4px ring (-webkit-mask-composite: xor plus the standard mask-composite: exclude); a second copy behind it has filter: blur(22px). Set --ang = t × 1.5rad and the halo opacity from a beat-synced spring on every frame. Do not use a CSS keyframe animation.
Capture with Playwright at 60fps, assemble with ffmpeg.
</build>

<gotchas>
- A CSS @keyframes rotation runs on the wall clock and breaks the capture. Set the angle from t.
- If you register --ang with @property and inherits: false, a value set on the element never reaches its ::before. Leave it unregistered (plain custom properties inherit) or register it with inherits: true.
- Keep the mask on both -webkit-mask (with -webkit-mask-composite: xor) and mask (with mask-composite: exclude).
</gotchas>

<start>
Show me a still before typing and one mid-typing before rendering.
</start>
07.2

Glass card

CSSbackdrop-filter

What you see

A milky data card floating over moving blobs of color. The card itself is almost transparent; what gives it its look is the blur of whatever is behind it. Move the blur slider, and switch the glass off to see the difference.

How it works

backdrop-filter: blur() blurs what's behind the element, not the element itself. saturate() boosts the colors of that blurred background, and that's what gives it the "liquid" feel. Add a thin, semi-transparent white border, and the glass seems to have thickness. The blobs move by the sin and cos of t.

When to useAn info card over a colorful moving background, a notification over a photo, an interface in the "glass" style of modern operating systems.
When not toOver a flat, solid background: there's nothing to blur, and the card just looks gray.
Full prompt
frosted glass card · CSS backdrop-filter
<inputs>
Ask me for the number and label on the card. If I skip them, use 268 "minutes this week" with a "+12%" chip.
</inputs>

<direction>
A 10-second loopable clip, 1080x1920, 60fps. Four large soft colour blobs (#FF8F3A, #5EA9F2, #FFC74E, #D74270) drift slowly on navy #12163F. A frosted glass card floats in the centre: background white at 16% opacity, a 1.5px white border at 40%, strong backdrop blur and saturation. On it, a big number in Inter 800, an uppercase label in Anton, and a small fire-gradient chip. The card springs in at the start.
Banned: a glass card over a flat background, heavy drop shadows, noise textures on the card.
</direction>

<structure>
The card springs in on beat 1 at 120 BPM; the blobs complete whole cycles over 10s so the clip loops.
</structure>

<build>
One HTML file exposing window.seek(t); blob positions and the card's scale are computed from t, no CSS animations.
The card uses backdrop-filter: blur(22px) saturate(1.6) (plus -webkit-backdrop-filter). Blob positions = sin/cos of t with different speeds, each completing an integer number of cycles in 10s.
Capture with Playwright at 60fps, assemble with ffmpeg.
</build>

<gotchas>
- backdrop-filter only shows when something moves behind the card; keep the blobs passing under it.
- An ancestor with opacity below 1, filter, mask, clip-path, mix-blend-mode or will-change creates a new backdrop root and the card stops seeing the blobs; keep those off the card's ancestors.
</gotchas>

<start>
Show me one still before rendering.
</start>
07.3

Aurora background

Canvas 2Dblurgrain

What you see

A background of huge, soft blobs of color drifting slowly, with a fine grain like film. Check "No blur" and the secret is out: these are five plain circles.

How it works

We draw five large circles in the brand colors, each moving along a path made of the sin and cos of t at its own speed, and blur them all with a very heavy blur (70 pixels). On top goes a faint layer of random noise.

Why the grain matters

A video stores each color channel (red, green, blue) in 8 bits, which means 256 levels. A subtle gradient, say from navy to a slightly lighter navy, uses maybe 20 of them. Across 1,000 pixels that's 20 steps, each 50 pixels wide, and the eye sees stripes (banding). The video file's compression makes it even worse. Subtle noise breaks up the stripes, because the eye averages it into one smooth tone. This technique is called dithering.

When to useA background for a headline or an end scene, a change of mood between chapters.
When not toBehind small text or a busy interface: it lowers the contrast.
Full prompt
aurora background with grain · Canvas
<inputs>
Ask me for the headline that sits on top. If I skip it, use "FLY HIGH" in Anton.
</inputs>

<direction>
A 14-second loopable background clip, 1920x1080, 60fps. Five huge soft colour blobs (#5EA9F2, #FF8F3A, #FFC74E, #D74270, #93CBFF) drift slowly on navy #12163F and blend into an aurora, with fine film grain on top. The headline sits centred in white.
Banned: hard edges, fast movement, a rainbow, grain so strong it looks dirty.
</direction>

<structure>
No beat sync: the motion is slow and continuous. Every blob completes a whole number of cycles in 14s so the clip loops.
</structure>

<build>
One HTML file exposing window.seek(t), drawing on a 2D canvas.
Draw five large circles with ctx.filter = 'blur(70px)' at positions from sin/cos of t with different speeds. Overlay a tiled noise texture generated once from a seeded random function, at about 9% opacity.
Capture with Playwright at 60fps. Encode with ffmpeg at a high bitrate (or -tune grain) so the compression keeps the grain.
</build>

<gotchas>
- Smooth gradients band after video compression. The grain hides it, but only if the encoder keeps it.
- Generate the noise from a fixed seed so every frame uses the same pattern (a changing pattern looks like TV static).
</gotchas>

<start>
Show me one still with and one without grain before rendering.
</start>
07.4

Four transitions without a fade

Canvas 2Dfloodmorphwipeiris

Why no fade

A fade (crossfade) is when one image slowly disappears and the next one appears underneath it. It's the first transition in every editing app, which is exactly why it looks like a "template". zero's rule: every scene is born from an object that's already on screen. That way the eye has something to follow instead of being cut off.

FLOOD: fill the frame

A button is pressed, and its color spreads out as a circle until it fills the whole screen. The final radius = the distance from the button to the farthest corner, so not a single sliver is left behind.

MORPH: change shape

A small pill widens, then grows taller and changes its corner rounding until it becomes a card. Width, height and corner radius each ride a spring of their own.

WIPE: sweep across

A new page enters from the right, led by a fire-gradient band. That band is what turns a plain wipe into something with energy.

IRIS: close to a dot

The screen closes into a shrinking circle until only a dot is left, and that dot becomes the dot in the YUV.AI logo. The radius shrinks on a logarithmic scale: in every equal stretch of time it shrinks by the same factor. In a 1920×1080 film it starts at 1,100 pixels, the distance from the center of the screen to a corner, so at first the circle covers everything, and it ends at 14, the size of the dot in the logo. Four equal steps: 1,100, 370, 125, 42, 14, each one 3 times smaller than the one before. Why not linear? A linear shrink removes the same number of pixels every frame: 18 pixels out of 1,100 are barely noticeable, but 18 out of 30 is more than half. So the circle hardly moves at first and then slams shut all at once, because the eye perceives changes in size as ratios, not as pixels.

When to useIn every transition between scenes. Pick by the object on screen: a button floods, a pill becomes a card, a dot becomes a logo.
When not toA transition that doesn't grow out of an object on screen looks like a cut, however polished it is.
Full prompt
four transitions without a crossfade · Canvas
<inputs>
Ask me for the scenes on each side of each transition. If I skip them, use simple title cards in my brand.
</inputs>

<direction>
An 11-second clip, 1920x1080, 60fps, showing four transitions in a row. FLOOD: a "Start" button is pressed and its fire gradient floods the frame as a growing circle. MORPH: a navy pill widens, then grows into a card with a progress bar. WIPE: a new page slides in from the right led by a fire-gradient band. IRIS: the frame closes into a shrinking circle that becomes the dot of the YUV.AI logo.
Colours: navy #12163F, denim #AFCBEF, light #EEF3FB and #DDF0FF, fire #FFC74E → #FF8F3A → #F56B49. Titles in Anton, uppercase.
Banned: crossfades, blur transitions, any transition that does not start from an object already on screen.
</direction>

<structure>
120 BPM. Each transition gets 2.8s; the main motion of each starts on a downbeat and takes about one beat.
</structure>

<build>
One HTML file exposing window.seek(t), drawing on a 2D canvas.
Flood: radius = distance from the button to the farthest corner × 1.03 × easeOut(progress). Morph: width, height and corner radius each follow a sum of springs. Wipe: clip the new page to a rectangle whose edge follows an eased progress, and draw a fire band at the edge. Iris: radius interpolated in log space, exp(lerp(log(rStart), log(rEnd), easeInOut(p))).
Capture with Playwright at 60fps, assemble with ffmpeg.
</build>

<gotchas>
- A flood radius smaller than the farthest corner leaves a sliver of the old scene.
- Linear iris shrinking crawls at the start and snaps shut at the end; use log space.
- Give each layer an explicit z-order so the flood covers the old scene but not the label it came from.
</gotchas>

<start>
Show me one still in the middle of each transition before rendering.
</start>
08

The real world: maps, photos and materials

What makes a code film feel shot, not computer-made.

When everything is flat vector shapes, the eye recognises a computer. Good product films bring in pieces of the real world: a map with a courier riding it, a real photo, materials that reflect light like real ones, and a 3D model of a real product. The four elements here do exactly that, and all of them are still a function of t.

Real-world material comes with a licence, so here are three terms worth knowing:

  • CC0: a full waiver of copyright. We may use, change and sell, even without credit. Poly Haven (HDRI environments, textures and models) and Khronos (sample models) publish much of their material this way. Logos and trademarks inside such an asset are usually not covered by the waiver.
  • The Pexels licence: photos and videos are free to use, commercially too and without credit. It is not allowed to show identifiable people in a bad light, to suggest that someone in the photo endorses the product, or to sell the photo as it is.
  • OpenStreetMap: a free map of the world. Anyone using its data or map tiles must show a credit and say the data is under the ODbL licence. A stylised map drawn in code, like the demo below, needs nothing.
08.1

Map and route: a courier on the way

Canvas 2Darc length

What you see

A stylised map, a route that draws itself from the restaurant to home, a courier who rides it while the camera follows, an arrival clock counting down, and a pin that turns into the brand mark. This is the moment from zero's food film and print film.

How it works

  • The map is drawn once into a hidden canvas, and every frame only the "camera" moves over it: moving and scaling the same image. That is very cheap.
  • The courier moves by arc length: we measure the length of each segment of the route and move it by distance, not by segment number. That way it travels at the same speed even when one segment is long and the next is short.
  • The part we have already travelled is painted in the fire gradient, and what remains stays dashed. Both are drawn from the same function of distance.
  • The zoom moves in log space: every moment adds the same percentage, not the same amount, so the eye feels an even motion.
  • The pin does not vanish: its head grows into a circle with a check. The new scene is born from an object already on screen.
When to useDeliveries, rides, "where is my order", a user's journey.
When not toWhen the real geography matters, like an address or a real route. Then we need a real map, with its credit.
Full prompt
delivery map · Canvas 2D, arc length
<inputs>
Ask me for: the start and end of the route (names only, like "the restaurant" and "home"), my brand mark, and whether the map must show a real place. If it must be real, ask for the area and use OpenStreetMap data with its attribution on screen; otherwise draw a stylised map.
</inputs>

<direction>
A 10-second delivery scene, 1920x1080, 60fps. A stylised 2D city map in my palette: light streets on a pale ground, a strip of sea, a few parks, a handful of street names. A route draws itself from the start pin to the destination pin, a courier chip rides it while the camera follows, an ETA chip counts down, and on arrival the destination pin's head grows into my brand mark with a check. One continuous take, no cuts.
Banned: satellite imagery, 3D buildings, a camera that zooms in and out more than once, a courier that moves at uneven speed.
</direction>

<structure>
120 BPM. Beats 0-2: the map and both pins pop in. Beats 2-5: the route draws itself. Beats 5-7: the camera zooms in on the courier. Beats 7-16: the ride, with the camera following. Beats 16-18: the camera pulls back to the destination. Beats 18-20: the pin becomes the brand mark.
</structure>

<build>
Canvas 2D. Draw the static map once into an offscreen canvas at 2x, then every frame only translate and scale it (that is the camera).
Move the courier by arc length: precompute each segment's length and place it at distance s(t) = eased(t) x total length, with its heading from the current segment.
Draw the travelled part of the route in the accent gradient and the rest dashed, both from the same arc-length function.
Interpolate the zoom in log space. Pins and chips pop on closed-form springs evaluated at t.
</build>

<gotchas>
- Moving by segment index instead of arc length makes the courier speed up on short segments.
- Make the map larger than the widest camera view, or its edge shows when the camera pulls back.
- Real map tiles need their attribution on screen and the tile provider's usage policy.
- Hebrew street names: set ctx.direction = 'rtl' before fillText.
</gotchas>

<start>
Show me stills of the overview, mid-ride and the arrival before rendering.
</start>
08.2

A real photo: Ken Burns and a relight

Canvas 2Dwipe2 shots

What you see

A photo card that lands on a spring, with a slow camera push inwards. Then a slider appears, the cursor drags it, and the photo moves from midday to golden hour following the knob. Try dragging the knob yourself.

How it works

  • The Ken Burns effect is a slow zoom and pan over a still photo, named after the documentary filmmaker who used it a lot. We start slightly above zoom 1 so the pan never shows the edge of the photo.
  • The change is not a fade: there are two aligned photos of the same place, and the second is revealed from left to right up to the knob, with a lit edge. It is a wipe the viewer controls.
  • For this to work the two photos must line up pixel for pixel: shoot from a tripod, or export two versions of the same image. Here the two "photos" are two renders of our sea scene from the same camera.
  • The knob grows while it is held, a small piece of feedback that says "got it".
When to useBefore and after, day and night, a product in two colours, colour grades.
When not toWhen there are no two aligned photos. A wipe between photos that don't match looks like a mistake.
Full prompt
photo relight · Ken Burns + wipe
<inputs>
Ask me for two aligned photos of the same scene in two states (day and golden hour, before and after, two product colours), shot from a tripod or exported as two versions of one image. If I don't have them, stop and tell me: this effect needs aligned shots.
</inputs>

<direction>
A 10-second scene, 1920x1080, 60fps. The first photo lands in a rounded card (radius 28, a soft navy shadow) on a spring and gets a slow Ken Burns push. Then a glass slider grows out of the card's bottom edge, a cursor drags its knob with two small pauses, and the second photo is revealed by a wipe that follows the knob, with a lit leading edge. Labels for the two states sit on the card.
Banned: crossfading the two photos, brightness or filter "developing", a knob that moves at constant speed.
</direction>

<structure>
120 BPM. Beats 0-6: the card and the Ken Burns push. Beat 6: the slider appears. Beats 8-14: the drag, pausing on beats 10 and 11. Beats 14-20: hold on the second state while the push continues.
</structure>

<build>
Canvas 2D. Draw each photo to cover the card (scale = max(cardW / imgW, cardH / imgH) x zoom), clipped to a rounded rect.
Ken Burns: zoom from 1.07 to 1.2 in log space plus a small pan, all functions of t; starting above 1 means the pan never shows an edge.
The wipe: clip the second photo to x < knob position, and draw a 3px white edge with a soft accent glow just before it.
The knob position is an eased function of t with two pauses; the knob grows while it is held.
</build>

<gotchas>
- If the two photos are not aligned pixel for pixel, the wipe exposes the mismatch. Align them first.
- Decode the images before the first seek() (await img.decode()), or the first frames are empty.
- Use photos you are licensed to use; every stock site has its own licence.
</gotchas>

<start>
Show me stills at beats 4, 11 and 18 before rendering.
</start>
08.3

Materials and textures: the same sphere six times

three.jsPBRnormal map

What you see

Six identical spheres in the same light: chrome, brushed metal, glass, glazed ceramic, velvet and marble. Only a few numbers and one texture change between them, and the corner says which.

How it works

  • PBR materials compute light by the laws of physics. The two main numbers: metalness (0 plastic, 1 metal) and roughness (0 mirror, 1 chalk).
  • transmission makes a material see-through like glass, and ior, the index of refraction (1.45 for glass), sets how much it bends what is behind it. That is why there are stripes behind the spheres: with nothing behind, there is nothing to bend.
  • clearcoat adds a clear lacquer layer over the colour, as on ceramics or car paint. sheen adds a soft shine at the edges, as on fabric.
  • A texture is an image wrapped around the object. map sets the colour (the marble veins). normalMap "lies" to the light about which way each pixel faces, so a smooth surface looks brushed or woven without adding a single triangle. Every texture here is generated in code from noise, with no files.
  • Real textures, like wood, concrete and fabric, are free to download from Poly Haven under CC0.
When to useA physical product, packaging, a brand's "material", switching between finishes.
When not toA flat interface film. Shiny materials pull the eye away from the screen.
Full prompt
materials · three.js PBR
<inputs>
Ask me which finishes my product comes in (for example aluminium, glass, matte ceramic, fabric) and my brand colours. If I skip it, show chrome, brushed metal, glass, glazed ceramic, velvet and marble.
</inputs>

<direction>
A 12-second material showcase, 1920x1080, 60fps, in three.js. Identical spheres in a row on a pale floor, lit by one studio environment, with a striped backdrop so transparent materials have something to bend. Every 2 seconds the camera glides to the next sphere, it lifts on a spring, and a label shows the material's name and its key PBR numbers.
Banned: different lighting per material, coloured lights, bloom, orbiting the camera around the row.
</direction>

<structure>
120 BPM. One material per 4 beats; the lift lands on the first beat of each group.
</structure>

<build>
three.js WebGLRenderer, ACES tone mapping, sRGB output, RoomEnvironment through PMREMGenerator for reflections.
One MeshPhysicalMaterial per finish: chrome (metalness 1, roughness 0.05), brushed metal (metalness 1, roughness 0.35, a stretched-noise normalMap), glass (transmission 1, ior 1.45, thickness 0.8), glazed ceramic (roughness 0.4, clearcoat 1), velvet (roughness 0.9, sheen 1), marble (a generated colour map, roughness 0.18).
Generate the textures in code: value noise → height field → normal map on a canvas, used as a CanvasTexture with RepeatWrapping.
The camera's x and the lift are functions of t (an eased glide and a closed-form spring).
</build>

<gotchas>
- Metal without an environment map renders almost black: metal shows what it reflects.
- Transmission needs something behind the object, or glass reads as grey plastic.
- Colour textures need colorSpace = SRGBColorSpace; normal maps must stay linear.
</gotchas>

<start>
Show me one still per material before rendering.
</start>
08.4

A real model and HDRI light: a bottle on a turntable

three.jsglTFHDRICC0

What you see

A real bottle from a glTF file turns a quarter turn at a time. In the first half it is lit by a studio, in the second by a real 360° photo of a sunset in Venice, and the reflections move across it. Its label is redrawn from t, and the second label wipes across the first. The buttons also let us pick the light.

How it works

  • glTF is the standard file format for 3D models: shape, materials and textures in one file (.glb). We download a model, build it in Blender, or get it from the client, and three.js's GLTFLoader loads it.
  • An HDRI is a 360° panoramic photo saved with the full range of brightness, including the sun itself, which is far brighter than everything else. Used as the environment, the light on the product comes from the photo and its reflections are real. Rotating the environment with t (environmentRotation) moves the reflections across the product.
  • The model was prepared for the web: the textures were resized to 1024 pixels and converted to WebP with the glTF-Transform tool, and the file went from 9 megabytes to 230 kilobytes.
  • The label is drawn on a canvas over the original colour texture and updated every frame. That is how we put our brand on a product without touching the model.
  • The model (Khronos's Water Bottle) and the environment (Poly Haven's Venice Sunset) are CC0. A model of another company's real product, like an iPhone, needs a licence.
When to useA product packshot, packaging, the launch of a physical product.
When not toWhen there is no model of the product. A generic device or a real photo beats a model that imitates a product.
Full prompt
packshot · glTF + HDRI
<inputs>
Ask me for my product as a glTF/GLB model (from my designer, from Blender, or a model whose licence allows this use), my label artwork or brand name, and an HDRI environment. If I have no model, ask whether to use a generic shape instead; never imitate someone else's real product. If I skip the HDRI, use a CC0 one from Poly Haven.
</inputs>

<direction>
A 12-second packshot, 1920x1080, 60fps, in three.js. The product stands on a soft contact shadow and turns a quarter turn on every third beat, settling with a slight overshoot. The first half is lit by a clean studio environment on a light gradient; the second half by the HDRI, shown blurred behind the product, with the environment slowly rotating so reflections sweep across the surface. Mid-film, a new label wipes across the old one. The camera pushes in slowly, once.
Banned: spinning at constant speed, a lighting change that is not on a beat, floating particles, lens flares.
</direction>

<structure>
120 BPM. Quarter turns on beats 3, 6, 9, 12, 16 and 20. Beat 12: the light changes and the label wipe starts. The push-in runs from beat 0 to beat 22.
</structure>

<build>
three.js WebGLRenderer, ACES tone mapping. Load the model with GLTFLoader and the HDRI with RGBELoader; pre-filter both environments once with PMREMGenerator (fromScene(new RoomEnvironment()) and fromEquirectangular(hdr)).
Normalise the model: centre it, stand it on y = 0, scale it to a fixed height.
The label: draw the model's base colour image onto a canvas, paint the label artwork over its label area (find it in the UV layout), and use the canvas as a CanvasTexture with flipY = false and SRGBColorSpace; redraw it every frame so the wipe is a function of t.
Rotation is a sum of springs keyed on the beats; scene.environmentRotation.y and scene.backgroundRotation.y are functions of t.
Optimise the model for the web first: gltf-transform resize --width 1024 --height 1024, then gltf-transform webp.
</build>

<gotchas>
- glTF textures use flipY = false. A CanvasTexture defaults to flipY = true, so the label comes out upside down unless you set it.
- A label wider than about a third of the circumference disappears around the back. Keep words short, or stack them.
- CC0 model licences usually exclude logos and trademarks. Paint out any brand mark that isn't yours.
- Await both loaders before the first seek(), or the first frames render an empty scene.
</gotchas>

<start>
Show me stills at beats 4, 12 and 20 before rendering.
</start>
09

Tools map: what else is out there

The catalog is built on HTML and seek(t). Here are the tools that do the same job differently, and when each one wins.

All license and pricing details were checked on October 17, 2026. Prices change, so before any commercial work it's worth checking again through the link in the sources.

ToolWhat it isWhen to pick itLicense and price
HTML + seek(t)This page's methodOne HTML file that Claude writes from scratch, with a function that draws any moment. Playwright captures, ffmpeg assembles.One film under full control, without installing a framework (a ready-made structure for a whole project, with its own rules and commands). Every library in the catalog fits inside it.Free
Remotionremotion.devA framework where every video is a React component that receives the frame number. Renders to MP4, in the cloud too.Lots of similar videos from one template (for example, a personal video for every customer), or a team that already works in React.Free for individuals and companies of up to 3 employees; paid company license above that
HyperFramesHeyGen · v0.8An HTML file where every element has data-start and data-duration, plus deterministic capture to MP4: every frame always comes out the same, exactly like seek(t). Built for AI agents (AI that carries out actions on its own, like Claude Code), and it ships with skills: instruction files that teach an agent how to work with the tool.When we want the seek(t) method with a ready-made tool that runs from terminal commands (init, preview, render). It's still before version 1.0, so things may still change.Open source, Apache 2.0
GSAPgsap.comAn animation library built on timelines. A timeline can be paused and jumped to any moment, which makes it a good fit for frame-by-frame capture.Complex choreography of many elements. HyperFrames uses it by default.Completely free, commercial use included, with all plugins. Webflow's license (not open source) only forbids using it to build a no-code animation tool that competes with Webflow
Motion Canvasmotioncanvas.ioA library in TypeScript (a version of JavaScript where every value is labeled with its type, so mistakes get caught early), plus an editor with a live preview, for vector animations timed to a voiceover.Explainer videos with a voiceover, diagrams, code that changes on screen.Open source, MIT
Lottielottie-webAn animation format based on JSON (a simple text format for storing data). An animator builds the animation in After Effects and exports it with the Bodymovin plugin, and it plays in the browser and in apps. It can jump to any frame (goToAndStop).When there's a ready-made animation from a designer, or from the LottieFiles library, and we want to drop it into a film.The player is open source (MIT); each animation has its own license
Riverive.appAn editor for interactive animations that react to the user, using a "state machine": a list of states, such as "idle", "hover" and "pressed", plus rules for when to move between them.Animation inside an app or a website that reacts to clicks. Less so for a linear film.The players (runtimes) are open source (MIT); exporting requires a paid plan, from $9 a month billed annually
Splinespline.designA visual 3D editor in the browser.Designing a 3D scene by hand, then exporting it to GLTF or to code.Free plan, plus paid plans
React Three Fiberpmndrsthree.js as React components.A large 3D scene inside a React project.Open source, MIT

How to choose, in four questions

  1. One film or a hundred? One: HTML and seek(t). A hundred from the same template: Remotion or HyperFrames.
  2. Is there a ready-made animation from a designer? Lottie fits into any of these approaches.
  3. Does it live inside an app and react to the user? Rive. At that point it's no longer a video question.
  4. Need a custom 3D model? Blender or Spline, export to GLTF, and bring it into three.js.
10

Prompt builder

Start from a preset, lay the scenes out on the beat grid, and get one complete prompt for the whole film. Save and share it with a link.

There's no AI here. The builder assembles the prompt from fixed parts, so it's easy to see exactly which sentence each choice adds. It follows the six blocks:

  1. Start from a preset: an app launch, zero-style food ordering, a physical product or a data film. Each preset fills every field, and everything stays editable.
  2. The story, format, tempo and branding go into <direction>. The sentence about the film goes into the prompt word for word.
  3. The elements: each catalog element adds a line to every block: what to see, how to build it in <build>, and which bug to warn about in <gotchas>. The builder also sums up which engines the film needs (Canvas, three.js, WebGPU) and adds the WebGPU flags when they're needed.
  4. The scenes are <structure>: each scene has a name, a number of beats, the on-screen text, an element, and how it is born from the one before. We mark the scene where the drop lands, and the film's length is the sum of the beats. Moving the length slider stretches all the scenes together.
  5. How things become each other: the motion verbs and camera moves from the interface dictionary, one signature trick, and the banned list.
  6. Saving: the builder remembers what we built in this browser, "Copy a link" stores every choice inside the address so we can come back to it or send it to someone, and "Download as a file" saves the prompt as text.
10.1

Your prompt

6 blockslive

Next step: paste it into Claude Code (or any coding agent), answer the questions it asks in <inputs>, and approve the beat map and the stills before the full render. The whole process, from capture to music, is in the pipeline chapter on the method page. (A coding agent is an AI that writes and runs code by itself, on your own computer.)

11

Sources

All links were checked and live on October 17, 2026.

  1. Recharts: the charting library, MIT.
  2. shadcn/ui · Introduction: "This is not a component library", and how to add components.
  3. Radix Primitives: unstyled components that handle behavior and accessibility.
  4. Tailwind CSS
  5. Lucide: the icon library, ISC.
  6. Simple Icons and their legal disclaimer about trademarks.
  7. three.js and MeshPhysicalMaterial.
  8. MDN · font-variant-numeric (tabular-nums).
  9. MDN · stroke-dashoffset
  10. MDN · backdrop-filter
  11. MDN · conic-gradient()
  12. Playwright and FFmpeg
  13. Remotion · LICENSE: free for individuals and companies of up to 3 employees.
  14. HyperFrames (HeyGen), Apache 2.0, and my review on yuv.ai.
  15. GSAP · Pricing: completely free, plugins included, and the standard license: commercial use is allowed, a no-code animation tool that competes with Webflow is not.
  16. Motion Canvas, MIT.
  17. lottie-web, MIT, and Lottie on Wikipedia (the Bodymovin plugin dates from 2015, Airbnb's libraries from 2017).
  18. Rive · the new $9/mo plan (October 20, 2025): the runtimes are MIT, exporting needs a paid plan.
  19. Spline
  20. React Three Fiber, MIT.
  21. Chart.js, Apache ECharts, D3
  22. Method page sources: zero's original article and prompts (@twoclipping).