html-animation

Motion graphics as code. An agent skill for building frame-exact animated logos, kinetic typography and brand videos as a single HTML file, then exporting them to MP4 in every format social platforms ask for.

No After Effects. No timeline GUI. No plugins. The animation is code, so it is diffable, reviewable, re-renderable at any size — and, the part that matters most, measurable.

demo

Install

It is a folder of Markdown and scripts, so it drops into whichever directory your agent reads skills from.

# Claude Code
git clone https://github.com/acelera-agency/html-animation.git ~/.claude/skills/motion-graphics
# Codex
git clone https://github.com/acelera-agency/html-animation.git ~/.codex/skills/motion-graphics
# Cursor
git clone https://github.com/acelera-agency/html-animation.git ~/.cursor/skills/motion-graphics

Drop the ~/ for a project-local copy scoped to one repo. If your tool has no skill directory at all, point the agent straight at SKILL.md — it is written to be read cold and says which other file to open and when. Then ask for what you want animated.

"animate our wordmark for the launch reel — it's just the word Vértice, black on bone, tight sans. I want it to arrive with energy and brake hard. Square and vertical MP4."

Nothing here is tied to a particular model or vendor. You need ffmpeg on your PATH and npm i playwright for the exporter; the animation itself has no dependencies at all.

Why it exists

Getting an LLM to produce motion graphics that do not look like an LLM produced them turns out to be less about prompting than about a handful of things that stay invisible until they bite. This is the residue of building one film end to end and fixing everything that broke.

"It feels janky" was zero dropped frames. Median frame time 8.3 ms. The problem was scenes holding perfectly motionless for two seconds — a frozen frame reads as stuck at a flawless 60 fps.

Two animations on one element silently break the whole film. With fill: 'both' the later one wins from time zero, so something meant to appear at 14 s is on screen from the first frame. It presents as a content bug, which sends you looking in the wrong place entirely.

seek() without pausing corrupts every exported frame. The animation advances a few milliseconds before the screenshot lands. The video looks plausible, which is what makes it dangerous.

Playwright's animations: 'disabled' does not freeze anything. It fast-forwards to the end state and all 1,400 frames come out identical.

An easing curve is not a simulation. ease-out decelerates; a falling object accelerates.

Centering the layout box leaves text sitting low, because the box carries ascender and descender space nobody perceives. Measured before the fix: 28–41 px below centre in a 1080 frame. Nobody could name it; everybody felt it.

The habit it is really about

Motion work invites eyeballing, and eyeballing lies. Because the timeline is seekable you can put the piece at t = 7.2 and read a getBoundingClientRect() — which turns "I don't like it" into a number. scripts/verify.js ships those measurements as functions.

And the corollary that saved the most time: when a check suddenly fails, suspect the check. Most alarms were the measurement's fault — a stale window, a timestamp that drifted after a retime, a comparison of elements invisible at that moment.

Pick a motion language

The most common way a piece looks amateur is that different elements arrive in different ways. Choose one and hold to it. Which one is a tone decision, not a technical one.

Says
speed urgency, sport, news arrives fast with a directional smear and brakes
pop playful, product, consumer grows from nothing and overshoots
wipe editorial, architectural a hard edge uncovers it, nothing moves
drift premium, calm, serious barely moves, the fade does the work
rise cinematic titles comes up out of its own baseline
focus photographic, dreamy resolves from blurred

In assets/starter.html this is one constant. Change it and the whole film changes tone.

What is in here

SKILL.md              the workflow, the four failure modes, the measuring habit
scripts/timeline.js   the engine — paused WAAPI, tracks, motion languages, metrics
scripts/physics.js    projectile · bounce · topple · roll · departure
scripts/export.js     frame-by-frame capture → MP4, every aspect ratio
scripts/verify.js     measurements, and the traps in the measurements
references/           why each of the above is built the way it is
assets/starter.html   a complete working animation — copy this to begin

The agent drives all of that for you, but the scripts stand alone too.

npm i playwright

SRC=/abs/path/piece.html node scripts/export.js        # all three formats
SRC=/abs/path/piece.html node scripts/export.js 9x16   # one of them
SRC=/abs/path/piece.html node scripts/verify.js        # centring, fluidity, is it actually moving

A relative SRC resolves from scripts/, not from where you are standing, and the MP4s land next to the script.

Three flags decide whether a platform accepts your file. yuv420p or it will not decode, +faststart or playback waits for the whole download, and a silent audio track or some platforms reject a video-only upload. The exporter sets all three.


Built at acelera.agency and open-sourced, because the interesting parts are the mistakes. If you make something with it we would like to see it.

Website · LinkedIn · [email protected] · MIT