# Motion Timeline

A stage of child atoms choreographed on ONE clock, the way a product or launch video is: everything on screen is a pure function of time (seek(t)), so it plays, loops, scrubs, and can be frozen on any frame (open the page with #t=3.2). Children are any atoms, placed in percent of a fixed-size stage (so the composition is identical at every width) and addressed by id; tracks animate them by opacity, x/y, scale, rotate, 3D tilt, blur, wipe, and two custom properties the demo kit listens to (p = progress, step = which caption/nav item); a camera track pans, zooms and tilts the world while layer:"hud" children (captions) stay put. Each key is a DESTINATION: {t, x, ease} means arrive at x at time t using ease, like GSAP .to(); before a property's first key it holds that first value, so an element can be hidden until its first key. Ease is a token name from motion_tokens (expo-out, standard, quart-in-out, overshoot, hold...) or four cubic-bezier numbers; with bpm set, keys take beat instead of t so the piece sits on a grid. Style that reads as designed rather than generated: one orchestrated moment per scene, entrances 600 ms or less on expo-out, on-screen moves on standard, the camera on quart-in-out, a shared motion vector between scenes, restraint over noise by default (no particle bursts, neon glows or bouncy easing on UI; the opt-in expressive options, glow, shine, trails and ripples, are for a film that wants them). Honours prefers-reduced-motion (holds the poster frame; controls still work).

## Surfaces

web, google-meet-stage, google-apps-script-web, mcp-apps, android

## Fields

| Field | Type |
|---|---|
| title | string (optional, max 80). Accessible name of the stage. Default "Motion sequence". |
| aspect | "16:9" | "4:3" | "1:1" | "9:16" (optional, default "16:9"). The stage is laid out at 1280x720, 1200x900, 1000x1000 or 720x1280 logical px and scaled to the container. |
| duration | number (optional). Seconds, clamped 2-120. Default 12. |
| bpm | integer (optional). 0-240. When set, keys may use `beat` (t = beat * 60 / bpm) instead of `t`. |
| ease | string | [x1,y1,x2,y2] (optional). Default ease for keys that do not name one. A token from motion_tokens (not "hold"). Default "standard". |
| loop | bool (optional). Default true. |
| autoplay | bool (optional). Plays when scrolled into view, pauses when it leaves. Default true. Off, or under prefers-reduced-motion, it shows the poster frame. |
| drive | "time" | "scroll" (optional, default "time"). "scroll" turns the film into a scroll-driven piece: the stage is pinned (position:sticky) inside a tall runway and the film clock is the page's scroll progress through that runway (seeked on requestAnimationFrame from a passive scroll listener), so scrolling plays the film and scrolling back rewinds it. No autoplay and no play/pause button: the scrubber stays as a progress indicator and a keyboard control (moving it scrolls the page to that point), and the page's own keys scroll as usual. Under prefers-reduced-motion there is no runway: the poster frame is shown in the normal flow. The export buttons are not offered. Absent or any other value: unchanged. |
| scroll_length | number (optional). With drive "scroll": the runway height in percent of the viewport height, clamped 150-1000, default 300 (the film plays over the runway minus one viewport of travel). |
| controls | bool (optional). Play/pause button, scrubber and clock. Default true. |
| poster | number (optional). Seconds; the frame shown when not autoplaying. Default 60% of duration. |
| transcript | bool (optional). Default false (nothing is added). True adds, under the stage, a "Transcript" disclosure (a details element, works without script) holding a table of every text and value the film shows, in reading order: time on screen (from the same keyframes as the player, the cue timing of film_to_vtt), scene (the scene layer it sits in), and the text. Covers motion_text, demo_caption lines, motion_counter, motion_pill, motion_checklist, demo_kpis, demo_chart (title, labels and values), demo_progress and demo_toggle_grid, nested or not. Up to 200 rows. |
| theme | "dark" | "light"  (optional, default "dark"). Stage background and control colours. |
| accent | "#rrggbb" (optional). Glow and control accent. Default "#38bdf8". |
| background | "#rrggbb" (optional). Stage background override. |
| backdrop | "glow" | "grid" | "flat" (optional, default "glow") |
| blocks | array (max 24) of any atom blocks. Each may also carry id (^[a-z][a-z0-9_-]{0,31}$, makes it addressable by a track), place {x, y, w, h, z, origin} (x/y/w/h in percent of the stage; z 0-99; origin "c"|"tl"|"t"|"b"|"l"|"r" is the transform origin), and layer "world" (default, moves with the camera) or "hud" (stays on screen). |
| tracks | array (max 40) of {target: id, keys: [...]} (max 48 keys each). A key is {t | beat, ease?, x?, y?, opacity?, scale?, rotate?, rx?, ry?, blur?, clip?, p?, step?, a?}. x,y are the element's top-left in percent of the stage (for an element nested in another, an offset from its natural place); opacity 0-1; scale 0-6; rotate, rx, ry in degrees (tilt -80..80); blur 0-40 px; the filter keys hue (degrees, -360..360), saturation, brightness, contrast (multipliers 0-4, 1 = unchanged) and invert (0-1) are composed with blur into one CSS filter on the element, clamped to those ranges, and the settled frame is the value at the last key (a brightness or invert track that swings quickly is checked as a possible flash by scripts/motion_lint.py); clip 0-1 is a left-to-right wipe; p 0-1 drives a demo atom's progress (chart draw, bar growth, toggles, fills, click ripple, orb level) and recounts its numbers; step is a float that crossfades captions and selects the active nav item. ease on a key is the ease of the segment ARRIVING at that key: a token name, "hold" (jump at the key), or four numbers. PATH FOLLOWING: a track may also carry path (path data in stage PERCENT, 0-100 on both axes: the motion_path grammar, M L H V C S Q T A Z, 700 characters), and keys with a (0-1, how far along it): the element's top-left rides the path, like x and y, and a track with a keys ignores its x and y. orient: true also turns the element to the path's direction (rotate keys on the same track are then ignored). trail: {n 1-6, lag 0.02-0.5 s} adds n fading copies of a top-level block (it needs a numeric place.w) that follow the same path a little later, each fainter and blurrier than the one before. SPRING: a key ease (which governs the segment arriving at that key) may also be {spring: {stiffness 1-1000 (default 170), damping 0-100 (default 26), mass 0.1-10 (default 1)}} or {spring: true} for the defaults: a damped spring, computed when the page is built (semi-implicit Euler, 1 ms step, time in seconds), so the player needs no extra script. The spring replaces the segment arriving at the key that carries it (previous key value to this one), written as 24 linear steps; the key is always exact and every other segment keeps its own ease. Pick values that settle within the segment, otherwise it snaps at this key. Under-damped springs overshoot (clamped to the property range). A first key with no previous key, or any other ease object, keeps the normal ease. |
| scenes | array (max 12) of {layer: id, t | beat, transition?}, optional. STITCHES scenes together: each layer (usually a motion_layer) is hidden until its start and hands over to the next scene instead of cutting. Within `overlap` seconds of the next scene's start the old one leaves while the new one arrives. transition is one of cut, dissolve, push, zoom-through, blur, rise, whip, wipe, iris, clock, slice, flip, spin, portal (with `portal: {x, y, w}`), ribbon, fade-through, shared-x, shared-y, shared-z, collapse and applies to the scene it names (the hand-over INTO it); without one the timeline's `stitch` is used. Your own tracks on the same layer are applied after these and win, so a scene can still do its own entrance. |
| morphs | array (max 6) of {from, to, t | beat, dur?, ease?} (optional). Match cut: a copy of the `from` child flies from its box to the `to` child's box (resizing on the way) starting at t, over dur seconds (0.1-4, default 0.8), with ease (a motion token, default quart-in-out). from hides when the flight starts and to appears when it lands, so the morph owns their opacity: do not also give them opacity tracks. Both must have a numeric place.w and sit at the top level or directly inside a motion_layer; others are dropped and reported. |
| stitch | "cut" | "dissolve" | "push" | "zoom-through" | "blur" | "rise" | "whip" | "wipe" | "iris" | "clock" | "slice" | "flip" | "spin" | "portal" | "ribbon" | "fade-through" | "shared-x" | "shared-y" | "shared-z" | "collapse" (optional, default "dissolve"). The four Material patterns fade the old scene out in the first 30% of the overlap and the new one in after it, so the two are never both half visible: fade-through brings the new scene up from 92% size, shared-x and shared-y slide it in along the axis (8% and 6% of the stage), shared-z scales it from 80% while the old one grows to 110%; set overlap to about 0.3 for the 300 ms Material uses. whip is a fast push with heavy motion blur; wipe reveals the new scene left to right over the old one; iris opens a circle from the centre; clock sweeps round like a clock hand; slice reveals through vertical bars; flip turns the old scene edge-on and the new one in; spin rotates and scales through; portal dives into a box in the old scene (give that scene entry `portal: {x, y, w}` in stage percent, the box keeps the stage shape) and the box becomes the new scene; without a box it falls back to dissolve; ribbon sweeps a slanted band in the accent colour across the frame and reveals the new scene behind it. The hand-over used by scenes that do not name one. collapse squeezes the OLD scene sideways to a thin vertical band that narrows to nothing (content clipped with it, over the overlap), and the new scene appears when that is done: a closing move, best before a blank beat or a lone object. Pick one per film: a shared motion vector between scenes is what reads as designed. |
| overlap | number (optional). Seconds a hand-over takes, 0-3. Default 0.6. Ignored by cut. |
| camera | {keys: [{t | beat, ease?, x?, y?, zoom?, rx?, ry?, rz?, z?}]} (max 48 keys), optional. z (-1500 to 1500 px) dollies the camera through depth: top-level blocks with place.depth (0-1) sit further back and, with a compensating scale, look unchanged at z 0, so moving z gives real parallax. x,y are the focus point in percent of the stage (default 50,50); zoom 0.25-6 (default 1); rx, ry, rz tilt in degrees. Moves only layer:"world" children. |

## Example payload

```json
{
  "type": "motion_timeline"
}
```

Live page: https://a2uicatalog.ai/atoms/motion_timeline/
Full field contract: https://a2uicatalog.ai/spec.json
