Motion

signature · blur-at-rest
default curve · ease-out-cubic
Motion should read as confident restraint. Fast, quiet, purposeful. Nothing bounces. Nothing overshoots. The one exception is the signature link hover, a blur-to-focus that reads as clarity arriving.

Duration scale

6 tokens · use by role
TokenWhere it livesDurationRelative speed · shared 3s cycle --dur-xsMicrofeedback · button press, tap state, focus ring120ms
--dur-smHover states · tooltip, link underline, small transforms200ms
--dur-mdUI transitions · panel open, tab switch, accordion300ms
--dur-lgPage-level · crossfade, modal, route transition600ms
--dur-xlNarrative · section reveal, stat count-up, hero entrance900ms

Easing curves

3 total · never spring, never bounce
--ease-outcubic-bezier(0.25, 0.8, 0.25, 1)
default · UI arrives, then settles
--ease-in-outcubic-bezier(0.4, 0, 0.2, 1)
crossfades · A-to-B, no focal point
--ease-readcubic-bezier(0.2, 0.8, 0.2, 1)
text reveals · settles quickly, reads calm

Signature · recessive-at-rest link

site-wide identity · do not rename
At rest · 1px dotted border at 35% currentColor · 0.78 opacity · no glyph filter
Build with conviction
hover to see the resolve
Transition · --dur-base · --ease-causal · opacity + border-color (glyphs untouched)
.link { border-bottom: 1px dotted color-mix(in srgb, currentColor 35%, transparent); opacity: 0.78; transition: opacity var(--dur-base) var(--ease-causal), border-bottom-color var(--dur-base) var(--ease-causal); } .link:hover, .link:focus-visible { opacity: 1; border-bottom-style: solid; border-bottom-color: currentColor; }

Primitives

hover any to trigger
Button sweep · 380ms · ease-causal · arrow + slab
Stat count-up · 900ms · ease-out · tabular
0%
Text reveal · 500ms · ease-read · 60ms stagger
Predict the choice, not the word.
Page crossfade · 400ms · ease-in-out · opacity only
Before
After

Rules

enforced
01Never spring. Never bounce. Never overshoot.
02Default curve is ease-out. Only deviate with reason.
03If you can't justify motion > 600ms, cut it.
04Scroll-triggered reveals fire once. No replay on scroll-back.
05Respect prefers-reduced-motion; drop all non-essential motion.
06Color never animates alone. Pair with transform or opacity.
07Loading is not motion. Use a static mono "…" or a 1px progress rule.
08Stagger in groups of ≤6, ≤60ms apart. Beyond that, use a single reveal.

Rule 07 · Simulation running

1px rule · cursor blink · no spinner
SIMULATION · RUNNING
Stated · intent
"73% would buy"
Revealed · pending
——
Loading is not motion. One 1px ink rule fills left-to-right over 8s. Cursor blinks at 1s step-start. The revealed column resolves when the simulation completes — not before.