Motion Code turns a one-line brief into an editable, on-brand promo video, rendered entirely as code (an HTML document with a GSAP timeline), driven by a single clock, and exported to a byte-identical MP4. Because the video is code, you can click any element and change it, or ask a model to rewrite just that element, and fixing one word never re-renders the whole minute.
▶ Live case study (interactive) · Open the app · LinkedIn
video-as-code · gsap · typescript · deterministic-rendering · next.js
The one-line version: most AI video tools generate pixels you cannot edit. Motion Code generates an editable program, drives it from one clock, and draws a hard line between what the model decides and what code guarantees. That line is the product.
An LLM is, at its core, a code engine. Pixel generation fights that: it is expensive, non-deterministic, and the output is a flat frame you cannot touch. So Motion Code does the opposite. Every scene is a self-contained HTML document with a GSAP timeline. The code on the left literally is the video on the right. Nothing is streamed.
That single reframe changes what an edit is. With pixels, an edit means re-render. Here, an edit is a text change to a live program: click an element and change its text, color or size, drag it, or say "make this calmer" and have the model rewrite only that element. None of that is possible with pixels.
This is the idea the whole architecture leans on. GSAP automatically aggregates every tween created anywhere on the page into a single globalTimeline. Motion Code makes that one object the source of truth for everything that needs a sense of time.
flowchart TB
G(("globalTimeline<br/>the one clock<br/>paused for render"))
G --- P["Preview<br/>pauses and resumes it"]
G --- E["Editor / drag<br/>freezes it so the<br/>animation cannot fight your cursor"]
G --- C["Captions<br/>their tweens auto-join it<br/>the caption code never calls the renderer"]
G --- R["Renderer<br/>registers it, pauses it,<br/>and drives time by seeking"]
And here is what the one clock buys: determinism, for free. The renderer pauses the timeline and drives time by seeking to each frame. Nothing runs on Date.now() or requestAnimationFrame. Seek to t = 2.0s, screenshot, repeat. The same scene renders byte-for-byte identically on any machine. A stochastic, model-generated artifact becomes a deterministic function of time.
From a line of intent to a deterministic MP4. Each stage sees the whole story: scenes are generated with the full ordered script in context, like an artist who reads the script before drawing a frame.
flowchart LR
A["brief<br/>one line"] --> B["split<br/>ordered scenes"]
B --> C["generate<br/>scene = code"]
C --> D["edit<br/>click, or ask the model"]
D --> E["voice + captions"]
E --> F["render<br/>seek then screenshot"]
F --> G[("MP4")]
The scene's timeline overwrites each element's transform sixty times a second. So when you drag an element, you cannot write to transform: the next frame stomps it. The fix is to move elements through channels the animation never touches, and let the browser compose them:
| Channel | Owned by | Purpose |
|---|---|---|
transform |
the animation | the scene's own motion, untouched |
left / top (layout) |
your drag | move the layer |
scale / rotate (individual CSS props) |
your resize | size and turn it, live |
SVG elements get a dedicated <g> wrapper carrying the offset as data-attributes. The result is the Figma editing model, rebuilt on an adversarial substrate: the float never stops while you edit, because your edit and the animation write to orthogonal channels. The AI-edit path is just as disciplined: a hand-written balanced-tag scanner finds the exact character span of the selected element and replaces only that span, so the GSAP <script> that drives everything stays byte-for-byte intact. Regenerating would be the easy path; it would also sever the animation wiring, so it is refused, by both a parser and a prompt.
The single most important lesson in the codebase is a boundary, found the hard way and written into a comment:
"A mechanical per-item timing job belongs in a pure, unit-tested function, not a prompt."
Models are unreliable at "apply this to every one of N items." So that work was moved across the boundary into deterministic code. The pattern recurs:
| The model is unreliable at... | ...so a pure function guarantees it |
|---|---|
| Per-word caption timing across N words | Character timing to words to lines, unit-tested, one shared source for preview and export so they cannot drift |
| Leaving no element accidentally static | An orphan-revival pass grafts ambient motion onto any element the model forgot to animate, no regeneration needed |
| Avoiding render-breaking infinite loops | Every "forever" loop is mechanically rewritten to a large-but-finite one before render |
The model brings the judgment (what the scene should look like, which style to call). Code brings the guarantees (timing, determinism, safety). Knowing exactly where that line sits, from experiment rather than theory, is most of what makes this reliable.
Frame-seeking a browser sounds simple until the scene refuses to hold still. AI scenes love "forever" loops for ambient motion. They come in two forms, one of which is a genuine trap:
| Loop form | The trap |
|---|---|
GSAP repeat:-1 |
On the global clock, so pausing the clock stops it. Rewritten to a finite repeat anyway, so a seek never lands on an infinite tween. |
Native SVG SMIL repeatCount="indefinite" |
Pausing the GSAP clock does NOT stop it. SMIL runs on its own native clock, so the page never settles and the render hangs. Confirmed empirically: every scene with this pattern hung. |
The counter-measure is belt-and-suspenders: the craft prompt teaches the model to avoid these, and the renderer mechanically rewrites both forms to finite loops regardless. You defend a determinism boundary at the layer where the mistake is made and at the layer where it would bite.
Every look (clean, bold, cinematic, neon, hand-drawn, cartoon, and more) is a skill.md: a page of expert direction on palette, line weight, lighting and motion personality, injected into generation. A new style is not a retrain, it is a new file. Hand Motion Code a reference image or a link, and it reads that into a fresh skill.md that drives generation exactly like the built-in styles. Your brand becomes a skill, and every future video inherits it.
Because the craft skill does the quality work, the model's own hidden thinking budget is set to zero. That roughly halves the cost at full quality: move the intelligence into a curated, versioned prompt asset, and stop paying the model to re-derive it.
The render pipeline runs on one machine, but it is engineered like a small distributed system, because the failure modes are the same:
| What goes wrong | What the system does |
|---|---|
| Laptop dies mid-render | A content-hashed cache lives beside the script, never in temp. Already-rendered scenes are reused on restart. Publish is atomic: render to a temp file, then same-directory rename, so a crash cannot leave a half-written clip. |
| A render logic change ships | CACHE_VERSION bumps and invalidates every entry at once, so a code change can never silently reuse a stale clip. |
| One scene hangs forever | A timeout race: the render is raced against a timer, because a cancellation signal alone cannot force a stuck await to return. After one retry, a simple placeholder card renders for that scene only (never cached), so one bad scene cannot sink a 52-scene render. |
| An interrupted render orphans a headless Chrome | A reaper garbage-collects leftover processes on startup and before each export, gated on live-PID probes, so it can never kill a render that is actually running. |
Photoreal models bill by the second, and you pay the whole thing again on every re-render. Motion Code produces the same minute for about ₹70, a little over ₹1 a second, because it draws code instead of sampling pixels. An edit is a text change, not a new render, so fixing one word never buys the minute twice.
| Tool | Per second | Per minute |
|---|---|---|
| Motion Code | ₹1.2 | ₹70 |
| Kling (cheapest photoreal) | ₹6.7 | ₹399 |
| Sora 2 (720p) | ₹9.5 | ₹570 |
| Veo 3 (quality tier) | ₹38 | ₹2,280 |
Roughly 8x cheaper than Sora, up to 33x under Veo. Providers' own per-second rates, verified Jul 2026.
Cheap and editable comes from one constraint: everything is drawn in code. That is also the honest limit.
| ✓ Draws it in code | ✕ Out of scope, by design |
|---|---|
| UI, product and dashboard mockups | Photoreal human faces |
| Diagrams, flows and state machines | Live-action or real camera footage |
| Kinetic type and data-viz | Literal photography of a place or product |
| Hand-drawn, isometric, cinematic, neon looks | Hyper-real 3D and heavy VFX |
If a brief needs a real camera shot, those photoreal tools are the right call. Motion Code is for editable, on-brand motion.
promo-studio/ the Next.js app: prompt → generate → click-to-edit → export
render-worker/ the deterministic renderer (local seek-and-screenshot → MP4)
design/ the design system and theme explorations
audit/ architecture audits (editing model, daemon, render, infra)
tracker/ roadmap + build tracker
docs/ this project's public case-study site (GitHub Pages)
Good architecture means naming its edges:
- Export is deliberately local. The cloud render worker was built and measured. It was not faster at 2 vCPU and it lost the durable cache, so rendering stays on the machine, on purpose. A measured choice, not a missing feature.
- Captions "for free" is an ElevenLabs property, not a universal one. ElevenLabs returns character-level timing with the audio, so captions need no transcription. The Google voice path does transcribe: it runs speech-to-text on its own synthesized audio, then rebuilds a character grid to match ElevenLabs' shape, so the caption code downstream never knows the difference. Same interface, a round-trip behind it.
The interactive case study runs real Motion Code scenes live in your browser: the code-is-the-video panel, a draggable element that keeps animating while you edit it, the one-clock diagram, and the full style showcase. It is the best way to see the architecture actually move.
Built by Khizer Hussain. Motion Code is a working product; the app is live at getmotioncode.vercel.app.