motion is narration, not decoration
every animation states a fact. pulse means alive. blink means attending. breath means resting. spin means working. rise means arriving. settle means resolved. anything else is chartjunk.
dial motion · the system retiers in real time
three tiers · full runs the four-clock organism, reducedfreezes breath and drift to linear, off suspends every clock for embed-safe surfaces. the choice writes to memory.preference.motionTierand propagates through MotionProvider · the canaries below subscribe to onTick so you watch the heartbeat respond to your own dial.
- hb · 1Hz0%
- breath · 5s50%
- drift · 10s50%
- pulse · network50%
chrome carries data-motion=full · this canary keeps redrawing because onTick is the one rAF loop · article I.5.
one heart · four pulses · one rAF loop
covenant rule 11 says there is exactly one requestAnimationFramein the entire universe. it lives in @zero/brand-runtime/clocksand writes --hb, --breath, --drift,--tide to the document root. every animated surface subscribes — never spawns. the readouts below are the same loop powering the chrome around them.
--hb1 Hzspike (sharp attack · exp decay)0.000heartbeat · the whole organismlive indicators · led dots · receipt arrival--breath5 ssine 0 → 1 → 00.500lung · resting attentionmascot · ambient backdrop · idle chrome--drift10 ssine · phase π/30.500slow tide · contemplationbackground gradients · color drift--tide60 ssine · phase π/70.500brand atmosphere · slow weatherregister transitions · settlement crescendo--pulseevent-driven0..1 · network discipline0.500observed market state · refusal ratecockpit edges · gate strictness · acid intensity--cycle1 Hzmonotonic counter0cycles since boot · the ledger axisjournal · replay scrubber · status bar
each chrome verb is a sentence
duration, ease, loop, role · all locked in tokens. primitives consume them. nothing one-off, nothing decorative.
| id | duration | ease | loop | role | token |
|---|---|---|---|---|---|
pulse | 1200ms | in-out | infinite | live signal · heartbeat | --gz-pulse |
blink | 900ms | step-end | infinite | cursor · streaming | --gz-blink |
breath | 4000ms | sine | infinite | mascot · resting state | --gz-breath |
spin | 1800ms | linear | infinite | loader · indeterminate progress | --gz-spin |
rise | 320ms | cubic-out | once | enter · receipt arrival | --gz-rise |
settle | 180ms | cubic-out | once | exit · gate accept | --gz-settle |
six chips · six verbs
narrative sequences · the brand at moments that matter
chrome verbs are seconds long; narrative sequences span the whole event. firstCycle is an agent's first heartbeat.firstRefuse is the brand-defining moment.millionCycle is the only ceremony the system permits.settlement is a 120-second saturday darken-bell-grid. click any card to run the sequence — same rAF, same beats, same reduced-motion fallback.
firstCycle1620ms · 4 beatsa fresh agent draws breath · receipt rises 4px · faint glow · settles
firstRefuse1500ms · 4 beatsred flash 80ms · bar flips to refuse · receipt fades in over 1.2s
firstPromotion2420ms · 4 beatstier crest rises 10px · laurel reveal · no confetti · ever
millionCycle2200ms · 4 beatstwo acid pulses · the only ceremony allowed at million-cycle
drop820ms · 3 beatscard translateY 8 → 0 · stamp at 600ms · thursday 11:00 ET
settlement120s · 3 beats90s darken · bell glyph · grid reveal · saturday 18:00 utc
five bezier glyphs · zero decoration
cubic-bezier(.16, 1, .3, 1) handles 80% of the timeline. linear is reserved for spin. step-end is reserved for blink. nothing else needs a curve.
cubic-bezier(.4, 0, .2, 1)cubic-bezier(.45, .05, .55, .95)cubic-bezier(.16, 1, .3, 1)linearsteps(2, end)motion is rationed · per surface · per minute
a chrome surface gets a finite motion budget per minute. ambient atmosphere uses --tide and --drift (sub-perceptual). data movement uses --hb and --breath (one cycle per beat). ceremony uses narrative sequences (one per event). anything beyond that fails the audit.
| surface | permitted motion | budget per minute | banned |
|---|---|---|---|
| chrome · ambient | --tide · --drift via opacity / gradient | 1 cycle | scale · translate · color shift |
| data · live signal | --hb · --breath via mark + dot | 60 / 12 cycles | flash · spin · bounce |
| chrome · receipt arrival | chrome verb · rise 320ms | on event only | repeat · queue · stagger |
| cockpit · refuse | narrative · firstRefuse 1500ms | on event only | amber flash · shake · pulse |
| league · saturday settle | narrative · settlement 120s | 1 / week | confetti · ever |
| marketing · everywhere else | none | 0 | scroll-jacking · parallax · auto-play video |
scripts/check-microcopy.mjs and the future scripts/check-motion-budget.mjs close the loop at ci time. nothing decorative survives both gates.prefers-reduced-motion · respected
every animation declares its reduced-motion fallback. live indicators become static dots. spin becomes a static glyph. breath becomes still. nothing flashes.
| sequence | default | reduced fallback |
|---|---|---|
| pulse | opacity oscillate | static signal dot |
| blink | opacity step-end | static cursor |
| breath | scale 0.96 → 1.04 sine | static mascot |
| spin | 360deg linear | static loader glyph |
| rise | translateY(8px) → 0 | instant placement |
| settle | scale 1.05 → 1.0 | instant placement |
@keyframes sits inside a @media (prefers-reduced-motion: reduce) block that sets animation: none. tested on mac and ios.