The grammar
The vocabulary the scenes are built from. A transition is something you pick between two finished clips; a movement carries the film from one state to the next with no join to hide. Every entry here runs a live demo, states the numbers it starts from, and names the bench that can fail it. Twenty-six of them run on the slab; the last ten have no interface on screen at all, because in those the sentence is the film.
Thirty-one of the thirty-six name a bench that exists, runs in CI and can fail. The other five are printed in grey: four name a bench nobody has written yet, and one names a bench that exists but cannot run in CI. A measurement nobody runs is a proposal, and this page would be doing the thing it warns against if it let the two look alike.
Forty-five seconds, one shot
This is the film the vocabulary is for. Twelve of the thirteen beats
render and join end to end. The one still in grey is the truck, and it
is not grey for lack of time: any clipping — overflow: hidden,
overflow: clip, clip-path — flattens
preserve-3d, and the slab has to clip, so per-layer
parallax costs a restructure of the DOM in every scene rather than a
few translateZ. It stays printed the way the missing
benches are printed: a plan and a thing are not allowed to look alike.
UI_MOCKUP_START_POSE to UI_MOCKUP_END_POSEUIMockupCardFocus, and it stays sharp because it is DOMCardFocus
What renders today is six scenes joined end to end, every join
measured by seam.sh: UIMockup, CardHandoff, CardFocus,
CardRelease, PromptInput, BoardOrbit. 1460 frames, 48.7 seconds, no cut
anywhere in them, and the five joins read 557, 64, 55, 29 and 15
pixels. They get cleaner as they go, which is what happens when both
sides take the same geometry from the same module instead of agreeing
by hand. CardRelease is not a beat in this list because this list is
the plan and not the inventory.
Speed is a number
Motion design is dense: the references behind this vocabulary run 13 to
42 seconds and fit more into them than there is in these 48. The tempo
of a scene is now its duration — each scene writes its beats against a
reference length and durationInFrames scales all of them,
so halving it halves every beat inside rather than cutting the tail
off. What does not scale is the perceptual thresholds:
the four frames between a click and its consequence are the window in
which the eye ties a gesture to its effect, not a rhythm decision, and
at double speed they would be two. Same for the three frames the card
lags behind the hand, which are the weight of the object, and for the
caret's blink, which is a frequency. Scaling those along with the rest
is how a faster scene becomes a broken one.
The benches, and what they fail
| Bench | The failure it catches | Reading on the repo |
|---|---|---|
| seam.sh | A join that is really a cut in costume | 29 px against 59,895, 2065× |
| handoff-travel.py | A card that does not actually cross. A freeze frame passes the other checks with full marks, and for months this one did not run at all on macOS, printing a diagnosis about the scene for a fault in itself | 170-195 px on the slab, three ratios |
| beats.py | A promised beat that is not on screen. The answer was assembling under the composer | 2 → 7 → 10 words, three ratios |
| fill-measure.sh | A frame that lets its edges die, which it cannot catch. The scenes sit on a background as bright as its threshold, so a slab shrunk to 60 per cent with the real background around it reads the same as a full frame. Kept as a readout; fill-geom.py does the job in CI, in geometry | 20 of 20, on the defect too |
| legibility.sh | Text that does not survive being scaled down. It reads the reference films beside the render, and those are not in git, so it runs on a laptop and not in CI | holds under 640 px wide |
| framelocked-verdict.sh | A scene that is not reproducible: it still renders, with different pixels. For months it printed its verdict and exited 0 either way, so it now has to fail a probe with a Math.random inside before its green counts | same pixels every render, the probe fails |
| focus-sharpness.sh | A slab that stopped being DOM: the text falls apart once the camera is on top of it | 2.09× against 1.03× on the fixture |
| rest-point.sh | A scene still moving where the next one has to attach. Its thresholds were absolute to begin with, tuned on this machine, and every one of them failed the move to Linux | edges under 30% of mid-scene |
| click-gap.py | A press and its consequence fused onto one frame, or so far apart that it stops reading as a consequence and starts reading as lag | f271 and f276, five frames, three ratios |
| loop-close.py | A demo whose loop tears every pass, or whose second half restarts from scratch instead of continuing the first. Hunting for the mid-loop cut did not work — a cut and a fade look alike to every metric tried — so each two-part demo declares the frame it changes on and the bench looks there, which is the bargain seamAfter already strikes for the scenes | 11 torn, now 0 |
| type-check.py | Type that covers other type or the readout, leaves the frame, snaps while it is visible, or goes away altogether. The first fix of the typography family made sure the frame never went black, and the sentences kept leaving and coming back anyway, in waves — in TYP-01 a fifth of the sentence was on screen for half a second, twice a loop. What reads as the sentence being gone is having nothing whole to read, so that is the rule: on every frame at least one word is on screen in full. And with reduced motion, where a demo shows a single frame, that frame has to be at rest | 10 of 10 failing on the previous page, 0 now, at four widths |
| tempo.py | A scene that was shortened rather than sped up. If the beats scale, frame f of the short render is frame f/k of the long one; compared that way the two differ by 287 px against 5379 without normalising. The residual is not slop — it is the signature of the thresholds that deliberately did not scale | 18.7× advantage, 4 required |
| fixture-trim.sh | Measures nothing. Truncates a scene to the short duration instead of retiming it, which is what lowering the duration produced before any of this existed | 120 frames, beats left in place |
| contrast-floor.py | Content attenuated so far that it stops being a plane behind and becomes dirt on the background. Two wrong versions before this one: the first measured the composer placeholder, which is deliberately faint and sits at 2.93:1 before any attenuation at all; the second measured the right content in a place whose position depends on how the messages wrap, and on Linux the crop landed on empty background | 4.17:1, 3.84 on Linux, against 1.71 |
| drift.py | A descent that slides past its subject, measured away from Topics: two slabs, one of them light and vertical, in three formats. Its second broken case starts exactly in place and only drifts while the camera pushes in, and the first version of the bench failed the correct case too, because the white of the probe slab ended up in the magenta mask | 2.03 px at worst of six, broken cases 39.5 to 99.8 |
| chain-check.py | A camera that reverses while it is moving, which reads as a cut even when the two frames match. It reads the tracks the scenes render from, so it needs no render, and the version of the chain with the easing taken out is its broken case | 7 reversals, all at rest |
| fill-geom.py | A slab that leaves an edge of the frame uncovered, checked on every frame from the camera tracks and the kit projection instead of from pixels. Its first run said BoardOrbit fails, and it does, on purpose: the orbit shows the edge | 5.4 px of cover at worst, all five fail pushed back |
| project-check.py | A projection that gets a rotation sign wrong. The kit works out where a point of the slab lands at any pose, tilted included, and every bench that watches a moving camera will crop by it, so it is checked against the engine that draws: Chromium, two slabs, three stages, the six poses of the chain | 0.040 px at worst, 103.7 with the origin wrong |
| no-product-literals.sh | A bench that works out the geometry of one product by itself instead of asking the manifest. Four benches did, which is how a bench ends up measuring Topics and nothing else without ever failing | 4 on the previous commit, 0 now |
| demo-check.py | A demonstration on this page that has stopped demonstrating its own entry. The difference between showing a thesis and not showing it is almost always temporal, and a screenshot cannot see it, so this one scrubs | 25 entries, frame by frame |
| fixture-screenshot.sh | Measures nothing. It builds the scene the bench above has to fail, and without it that bench is a promise | first frame magnified 2.26× |
The negative control gets written first. A bench that cannot fail measures nothing, so it is made to exit non-zero on a broken input before its green is worth anything.
What stays out
| Excluded | Why |
|---|---|
| The nine ways to reveal a logo | One is enough, and it goes at the end. Nine is a symptom of what the ecosystem thinks it is for |
| The twelve template transitions | They ship with Remotion already. Rewriting them is wasted work, and they are cuts anyway |
| YouTube furniture | Subscribe reminders, end cards, credits rolls, countdowns. A different trade |
| Decorative backgrounds | Bokeh, matrix rain, particle explosions, starfields. Handsome and mute: they say nothing about the product |
| Anything that needs a cut | If it only works thanks to the edit, it does not belong in one unbroken shot |