← the field
S3 · runtime-proof
design the framesFigma
<VideoCanvas spec={launch}> <SceneTitle title="Runtime stays visible." /> <ProductFrame rows={…} /></VideoCanvas>
rebuild as componentsRemotion
motion passanime.js · the tokens
launch.mp445s · 1920×1080 · 30fps
renderevery export spec

How I make brand videos in code

A finished brand video in a day, without ever opening a video editor — designed in Figma, written as code, and held together by one small rulebook for how things move.
scroll
S3 · runtime-proof
design the framesFigma
<VideoCanvas spec={launch}> <SceneTitle title="Runtime stays visible." /> <ProductFrame rows={…} /></VideoCanvas>
rebuild as componentsRemotion
motion passanime.js · the tokens
launch.mp445s · 1920×1080 · 30fps
renderevery export spec

Why not just use a video editor

Why does every video start from nothing, when every screen starts from a system?

I wanted control over every frame. The exact moment a screen slides in, the weight of the voice, how long the logo holds before the tagline lands.

Video editors are built for a different job. On the stage is a 45-second launch cut the way an editor holds it: three tracks, eleven hand-placed clips, a file called cta_final_FINAL.mov. Nothing in that timeline knows about my brand, my type, or my design system. Every video starts from nothing, and consistency depends on me remembering what I did last time.

Insight: Design systems work the other way round — everything you decided once is written down and reused. I wanted my videos to work like that too.

Start with intent, not numbers

I already had brand guidelines and a design system — the written-down rules for how our product looks — sitting in the same place as the code. Colors, type, components, spacing. None of it said anything about how things should move. Motion is its own language, so it needed its own system — and the first page of that system has no numbers on it at all.

Before picking a single duration, name the mood — and a mood is easier to feel than to read. The stage is one particle field with three sliders under it. Drive them. Each slider changes a different property of the same motion: how fast everything moves, how many things are moving at once, and the path each one takes.

Three dials, because those are the three things that vary independently — how fast something moves says nothing about how many things move, and neither says anything about the shape of the movement. Each dial turns into a different chapter of rules later: the first picks your durations (how long movements take), the second your entry rules (how things arrive on screen), the third your curves (the shape of the speed itself). If two of your dials always move together, you have one dial and a synonym.

NoInfra sits at 0.28 · 0.24 · 0.68 — restrained, sparse, more organic than mechanical. Set the sliders there and you are looking at the mood every number in the rest of this post answers to.

Timing: three roles, not three sizes

The names matter more than the values. shortMs, mediumMs and longMs are not small, medium and large — they are roles: a state changing, an element entering or leaving, a scene changing. When a new piece of motion appears, the question is never “how many milliseconds” — it is “which of the three things is happening”. A value ticking over is not news, so it gets the state-change role and you barely notice it. A scene changing is news, so it gets the longest one and you feel the room change. For scale: all three are in milliseconds, and a blink of your eye takes about 300.

You do not need to read code to follow this — the three numbers below are the entire vocabulary:

theme.ts — the vocabulary, verbatim
motion: {
  shortMs: 160,   // a state changes: a row activates, a value ticks
  mediumMs: 360,  // an element enters or leaves
  longMs: 720,    // a scene changes

  easeOut:   "cubic-bezier(0.22, 1, 0.36, 1)",
  easeInOut: "cubic-bezier(0.65, 0, 0.35, 1)",
}

The three dots on the stage start together and run the same distance; the gap between them is the entire vocabulary being spoken at once.

One more layer down: a video is just still images shown quickly — thirty of them every second, here. So the tool that renders mine, Remotion, thinks in frames, and the milliseconds compile into them: 160ms is 5 frames, 360ms is 11, 720ms is 22. The named value is the decision; the frame count is arithmetic. This is what “a design system for motion” means in practice — no scene ever hardcodes a duration:

how a scene consumes it
import { interpolate, Easing } from "remotion";

// ms → frames, at whatever fps this spec declares
const f = (ms: number) =>
  Math.round((ms / 1000) * spec.fps);

// an element entering: 16px offset → 0, over mediumMs
const y = interpolate(
  frame,
  [enterAt, enterAt + f(motion.mediumMs)],
  [16, 0],
  { easing: Easing.bezier(0.22, 1, 0.36, 1),
    extrapolateRight: "clamp" },
);

Three roles is not a limitation I worked around. The moment there is a fourth duration, there are nine, and then every timing is a fresh decision made under deadline.

Easing: the part that reads as craft

Easing is how speed changes inside a single movement — the difference between a train easing into a station and a toy car hitting a wall. It is the hardest thing to communicate in writing, so on the stage it is running: same ball, same distance, same 720ms, only the curve changes. Watch the ends, which is where the difference lives.

linear arrives at full speed and stops dead, which is why it reads as mechanical; nothing in the physical world does that. easeOut covers most of the distance early and then settles, so it feels like it arrived somewhere. easeInOut is symmetrical — it gathers, travels, and eases off — right for something moving between two places, wrong for something appearing.

That gives three rules, and they are the whole policy: anything arriving uses easeOut, anything moving between two known states uses easeInOut, and nothing is linear except progress rails and counters, where linear is honest.

A curve is only four numbers, and nobody reads them — you drag until the movement feels right, then keep whatever numbers that turned out to be. So .

Entry and exit: one grammar

With timing and easing settled, the conventions are almost free. The two cards on the stage hold identical content — the left is what happens without a convention, the right is the rule: a 16px offset, opacity, mediumMs, easeOut, and no scaling of text ever.

Elements enter from the direction they conceptually come from: something arriving from a server enters from the right, something the user did enters from below. Exits are faster than entries — shortMs — because leaving should not compete with whatever arrives next. Only one thing enters at a time, unless the group is a deliberate stagger.

Insight: The highest-leverage rule here is never scale text on entry. Scaling type makes it briefly unreadable and reads as a template. Offset and fade instead — one line different, and it is the difference between “made” and “generated”.

What a motion guide actually contains

That is the thinking. On the stage is the full table of contents — the seven chapters a complete motion guide needs, whatever your tooling. Intent, timing and choreography are the language; signature and sound are what make it a brand rather than a style; composition and governance — the written rules of what must and must never appear — are what let other people, and AI agents, use it without you in the room.

Mine happens to be implemented as five TypeScript files beside the product (theme.ts, specs.ts, storyboards.ts, brief.ts, components.tsx), and that order is also the build order — theme first because everything imports it, specs second because export dimensions decide layout and it is miserable to discover that late. But the taxonomy is the transferable part; the downloadable guide at the end follows it chapter by chapter.

Storyboards are data

The thing I did not expect: once the system existed, the storyboard stopped being a document and became structured data — a list a computer can check, not a PDF a person squints at. On the stage is the real launch storyboard as a filmstrip — four scenes, each drawn in proportion to its duration: the hook (4s), the product (8s), the proof (10s), the ask (6s). The proof gets the most time because it is the reason the video exists.

Each scene carries its kind, its duration, the headline, the caption, the visual direction, and the narration. That means a storyboard can be reviewed and compared line by line — the same way engineers review changes to code — and handed to a renderer, or to an AI agent, without a single ambiguous instruction.

And because it is data, it plays: . Nothing there is a video file — the frames, the narration and the captions are read from the same structured list the finished video is rendered from.

specs.ts
copyGuardrails: {
  requiredPublicBranding: ["NoInfra"],
  allowedLegalBranding: ["KriyAI, Inc."],
  unsupportedClaims: [
    "revenue claims",
    "customer count claims",
    "guaranteed uptime claims",
  ],
}

Video agents drift. They invent brand, motion, copy, and layout from scratch every time you ask. Writing the constraints down as types is what keeps a generated video attached to the same system as the product.

The stack

Remotion is the foundation. It lets you make video in React — the toolkit most modern web apps are built with — so every frame of the video is a component: a reusable, describable piece of interface. The video is those components played across a frame count. Change a word in the script and you have changed a variable — there is no clip to recut. And because it is code, the same file renders at any size with no re-export.

npx create-video@latest

The scene primitives are ordinary React components, which is what makes them composable:

a scene
import { VideoCanvas, SceneTitle } from "@noinfra/creative-system";

<VideoCanvas tone="light" width={1920} height={1080}>
  <SceneTitle
    eyebrow="Runtime"
    title="Runtime, tokens, and status stay visible."
  />
</VideoCanvas>

anime.js handles the fine motion — the stagger across a group, the exact easing on a slide. Figma is where frames are designed before they become video: fast to change by hand, and it exports clean vectors that Remotion rebuilds almost exactly.

Composing the frame

The spec — each platform’s declared size, length and frame rate — is the easy part. A frame is actually composed against four things at once, and the stage lets you toggle each one over the same scene: the safe area the spec reserves, the platform UI a phone draws over a vertical video, where attention lands, and — on the landscape master — the 9:16 column that survives a crop.

Turn on the platform UI and the vertical spec explains itself: the 220px at the bottom is the caption block and progress bar, the right edge is the like/comment/share rail, the 160px at the top is the platform’s own chrome. Design to the full frame and your caption sits under a Reels play button.

Turn on attention and you see why the focal block sits upper-left-of-centre: eyes land there first and fall to the caption zone second. Which is also the argument for whitespace — one focal point per frame, and most of the frame deliberately empty. A video frame is on screen for seconds; if two things compete, neither is read.

And the cut guide is the cheapest trick in the guide: keep the story inside the centre 9:16 column of the landscape master, and the vertical version is a crop instead of a re-layout.

The build, start to finish

Watch the pipeline assemble on the stage — seven levels, landing in the order the day actually runs.

Story. I brainstormed the beats with Claude: what the video needs to say, the voiceover, how it enters and exits. Worth doing before touching any tool, because the story decides everything downstream.

Frames. I connected Claude to Figma and had it lay out every state in one place, so I could see the transitions up front instead of discovering them once things were moving. Then I went in by hand and tuned until each frame was right — the step where being a designer matters and the tools stay out of the way.

Rebuild. Figma exports frames as vectors, and vectors rebuild cleanly in code, so getting the screens into Remotion through Claude was the easy part.

Motion — the hard part. Designing the entries, exits and transitions took real iteration. The first version was stiff. Elements arrived too fast or held too long, and the logo reveal did not land. The system makes the video possible; getting it to feel good still takes passes, and that is the part no pipeline removes.

Voice, then render. One consistent AI voice, one file, every export spec.

Take the motion design system

The same source file renders from 720p to 4K. I control the voiceover, the dialogue, the screens, the timing, and every transition, and I made all of it without knowing how to edit video — because the pipeline does the editing and I do the directing. I gave up the timeline and got a design system for motion in its place.

The system is the piece worth stealing. Here it is as a markdown file you can drop into your own repo and adapt — intent, timing, easing, entry and exit conventions, the end-frame sequence, voice, export specs, guardrails, and the review checklist.

motion-design-system.mdMarkdown · 10 KB

Written August 2026. The creative system it describes is the one behind the NoInfra launch video.