Brief. Honest. Out of the way.
Every animation earns its place by telling the user something a static frame can't —
state changed, something entered, something's still working. No bounces, no elastic,
no decorative drift. prefers-reduced-motion
silences everything.
ease-out. Border shift, bg shift, or translate-y 1px. One effect, not three.
cubic-bezier(.25,1,.5,1). Fade + 8px rise. Staggered 30ms per sibling up to 12 items.
Background + foreground cross-fade. Accents switch instantly — they're meaning, not decoration.
Slow sweep across hero. Ambient, low-opacity. A heartbeat, not a siren.
Bind the rhythm to variables.
Every transition and keyframe pulls from this set. Two easing curves — one for most
things (quart), one for decisive arrivals (expo) — and four
durations from instant to stage. No bespoke cubic-beziers sprinkled across components.
--ease-out-quart
cubic-bezier(0.25, 1, 0.5, 1)
Hover states, small transitions, most UI work. Natural deceleration.
--ease-out-expo
cubic-bezier(0.16, 1, 0.3, 1)
Menu reveals, sidebar swaps. Starts fast, settles decisively.
--dur-instant
90ms
Pressure feedback: border, color, tiny translate on hover.
--dur-quick
160ms
Panel swaps, dropdown reveals, collapse/restore.
--dur-base
220ms
Larger state changes: stagger intervals, layered entrances.
--dur-stage
320ms
Full-stage transitions. Page-sized cross-fades, theme swaps.
prefers-reduced-motion short-circuits every transition-duration and
animation-duration globally to 0.001ms. No per-component opt-in needed.