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 builderMotion 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.
How to read the catalog
Four parts in every card, and one example that runs through the whole page.
All the elements at a glance
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.
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".
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.
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.
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.
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:
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).
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:
The same icon, two ways to store it
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.
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":
What it costs to draw N points
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.
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:
Wall clock vs. seek(t)
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.
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:
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 1How 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.
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.
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.
An area chart that draws itself
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
ReferenceLinewhose thickness grows on a spring.
Full prompt
<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>Bars that land on the beat
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.
Full prompt
<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>Counters that count up
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.
Full prompt
<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>Progress ring
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.
Full prompt
<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>
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.
An app tour, built from real components
- Card entersA card with a title, rising on a spring.
- Input gets typed into"Morning workout", one letter every 0.1 seconds, with a caret placed by measuring the text width.
- Button gets pressedThe cursor arrives and clicks, and the button shrinks and changes color.
- Progress fillsFrom 0 to 72% on a slow spring.
- Tabs switchToday → Week → Month, and the big number updates.
- Switch turns onThe little knob jumps to the other side.
- Slider movesIntensity from 30 to 80.
- Alert drops inA message: weekly goal reached.
- 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
transitionclass 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.
Full prompt
<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>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.
Self-drawing icons
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.
Full prompt
<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>
Official logos
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.
Full prompt
<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>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.
Four techniques for moving text
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".
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.Full prompt
<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>
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.
Phone and laptop with live screens
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 = truetells 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.
Full prompt
<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>
Breathing glass orb
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).
Full prompt
<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>
20,000 particles that morph
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
Pointsis 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.
Full prompt
<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>
Ocean and sky: from day to sunset
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
Skyis 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.Wateris 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).
Full prompt
<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>
Card carousel that snaps to the beat
What you see
Six cards on a ring in 3D. The ring turns one step on every beat, overshoots a little and settles. This is the moment in the launch film where the click bursts into a carousel.
How it works: a sum of springs
The ring's angle is a sum: every beat has its own spring, which starts at that beat's time and adds one step (60°, because 360° divided by 6 cards is 60°). angle = Σ step × spring(t − beat_i). The symbol Σ (sigma) means "add them all up": for every beat i, which starts at time beat_i, we add its spring. Each spring overshoots the target a little and comes back, which is why every step "lands". And since this is a sum of closed-form formulas, we can compute the angle at any moment, even if we jump straight to the middle.
Each card also rotates around its own axis so it always faces the camera. Otherwise the text on the cards at the back would read reversed, as in a mirror.
Full prompt
<inputs> Ask me for the card titles (6 to 8). If I skip them, use the six categories of this catalog: Charts, UI, Icons, Type, 3D, Effects. </inputs> <direction> A 6.6-second 3D clip, 1920x1080, 60fps. Six cards stand on a ring in 3D, facing outward. The ring snaps one card forward on every beat with a small overshoot. The front card is slightly larger and fully lit; the others dim with distance. Cards: white with a 28px radius, a navy uppercase title in Anton and a fire-gradient icon chip; background the sky gradient #5EA9F2 → #93CBFF → #DDF0FF. Banned: continuous spinning, motion blur streaks, cards that show mirrored text. </direction> <structure> 120 BPM: a snap every 0.5s for 12 beats. </structure> <build> One HTML file with three.js exposing window.seek(t). Each card is a plane with a CanvasTexture drawn once. Ring rotation = sum over beats of step × spring(t − beatTime_i), with step = 2π / cardCount and this closed-form spring, ω = 14 and ζ = 0.6: spring(t) = 1 − e^(−ζωt)·(cos(ω_d·t) + (ζω/ω_d)·sin(ω_d·t)), ω_d = ω·√(1 − ζ²). Each card's own rotation counter-rotates so it always faces the camera. Capture with Playwright at 60fps, assemble with ffmpeg. </build> <gotchas> - If the cards face the ring's centre instead of the camera, the far ones read mirrored. - Use sRGB colour space on the textures, or the brand colours look washed out. </gotchas> <start> Show me stills at beats 1, 4 and 8 before rendering. </start>
3D equalizer
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.
Full prompt
<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.
Effects and transitions
The "AI is thinking" neon, glass, an aurora glow, and transitions without a fade.
Neon edge: "the AI is thinking"
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.
Full prompt
<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>
Glass card
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.
Full prompt
<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>
Aurora background
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.
Full prompt
<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>
Four transitions without a fade
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.
Full prompt
<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>
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.
| Tool | What it is | When to pick it | License and price |
|---|---|---|---|
| HTML + seek(t)This page's method | One 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.dev | A 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.8 | An 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.com | An 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.io | A 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-web | An 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.app | An 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.design | A 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 Fiberpmndrs | three.js as React components. | A large 3D scene inside a React project. | Open source, MIT |
How to choose, in four questions
- One film or a hundred? One: HTML and seek(t). A hundred from the same template: Remotion or HyperFrames.
- Is there a ready-made animation from a designer? Lottie fits into any of these approaches.
- Does it live inside an app and react to the user? Rive. At that point it's no longer a video question.
- Need a custom 3D model? Blender or Spline, export to GLTF, and bring it into three.js.
Prompt builder
Pick a format, a length, branding and elements from the catalog, and get one complete prompt for the whole film.
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: every element adds a line to each of the six blocks, for example how to build it in <build> and which bug to warn about in <gotchas>. The music tempo and the length are converted into a number of beats, and the elements are spread across the film between a four-beat opening and a four-beat ending, in story order: first the product, then the proof (charts and numbers), and finally the trust (logos). Transitions and the background don't get a beat range of their own, because they aren't scenes: they're woven through the whole film. Write field 1 in English, since it goes into the prompt word for word.
Your prompt
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.)
Sources
All links were checked and live on October 17, 2026.
- Recharts: the charting library, MIT.
- shadcn/ui · Introduction: "This is not a component library", and how to add components.
- Radix Primitives: unstyled components that handle behavior and accessibility.
- Tailwind CSS
- Lucide: the icon library, ISC.
- Simple Icons and their legal disclaimer about trademarks.
- three.js and MeshPhysicalMaterial.
- MDN · font-variant-numeric (tabular-nums).
- MDN · stroke-dashoffset
- MDN · backdrop-filter
- MDN · conic-gradient()
- Playwright and FFmpeg
- Remotion · LICENSE: free for individuals and companies of up to 3 employees.
- HyperFrames (HeyGen), Apache 2.0, and my review on yuv.ai.
- 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.
- Motion Canvas, MIT.
- lottie-web, MIT, and Lottie on Wikipedia (the Bodymovin plugin dates from 2015, Airbnb's libraries from 2017).
- Rive · the new $9/mo plan (October 20, 2025): the runtimes are MIT, exporting needs a paid plan.
- Spline
- React Three Fiber, MIT.
- Chart.js, Apache ECharts, D3
- Method page sources: zero's original article and prompts (@twoclipping).