// motion
Motion
Every duration and easing below is a live var() from globals.css, not a copy-pasted number — read docs/motion.md for the full policy this page demonstrates.
01 · tokens
Duration & easing
Hover a card to replay it. No component is allowed to invent its own timing or curve — everything ships from these four durations and two easings.
150ms
--motion-fast · hover, press, color
200ms
--motion-base · tooltips, dropdowns, modal enter/exit
300ms
--motion-slow · larger reveals, sheet exit
500ms
--motion-enter · sheet open only
ease-quiet · cubic-bezier(.16, 1, .3, 1) — strong ease-out, every enter/exit
ease-quiet-in-out · cubic-bezier(.65, 0, .35, 1) — on-screen moves
// hover this card to replay both at once
02 · reduced motion
The kill switch
Decorative motion is skipped entirely under prefers-reduced-motion, never just played shorter — functional loading indicators (the spinner) are the one carve-out.
click ++ with the switch off, then on — the pop and micro-burst should disappear entirely, not just play shorter.
03 · adopted transitions
Every shipped motion, in one place
The six overlay primitives below are adapted from transitions.dev's free-tier catalog — re-implemented on our own tokens and class names, never vendored (their terms forbid redistributing the library). Everything past the overlays is original to this system.
dialog
200ms · ease-quiet · centered, scale .95→1 + fade
dropdown-menu
200ms · ease-quiet · scales from trigger origin
tooltip
200ms · ease-quiet · scales from trigger origin
select
200ms · ease-quiet · scales from trigger origin
hover-card
200ms · ease-quiet · scales from trigger origin
sheet
500ms in / 300ms out · ease-quiet
collapsible
300ms · ease-quiet · grid-rows 0fr→1fr
Never tweens height directly — a grid track sized 0fr/1fr does the reveal, so it's transform/layout-cheap.
stat-button like-pop
150ms ease-quiet pop · 400ms ease-quiet burst
Scale 1→1.15→1, and only on the ++ (like) action going active — never on save, never on un-liking. The 5-particle burst is primary-tinted and restrained on purpose.
copy-button
200ms ease-quiet · icon crossfade + stroke-draw
git clone github.com/mollybuilds/tinysynththeme-toggle
150ms ease-quiet · label crossfade (scale + blur)
avatar-stack
150ms ease-quiet in · 200ms overshoot out
The overshoot return uses an explicitly-approved custom back-ease (cubic-bezier(.34,1.56,.64,1)) — the one place this system allows a curve outside the two named tokens.
skeleton-card shimmer
1.6s linear, infinite — respects the kill switch
number pop-in
200ms ease-quiet · RepoStatsRow + ProjectCard counts
switch
150ms ease-quiet-in-out · thumb + track color
project-card hover
150ms ease-quiet · border sharpen + lift 1px
Retuned from a hardcoded ease-out to the token — same behavior, correct curve.
command palette
no animation — ever
Command Palette
Search for a command to run...
⌘K is opened 100+ times a day by the people who use it most. Per docs/motion.md that frequency gets zero animation, full stop — contrast this against every other entry above.
04 · rejected
Considered, not shipped
Every one of these was a real idea before it wasn't — the reason is the point.
- card stack hover— no surface to stack — ProjectCard doesn't layer.
- 3D tilt— contradicts the locked hover treatment (sharpen + lift 1px).
- literal confetti— bg-bloom pulse instead — same celebratory beat, on-brand.
- Pro gradient text— no gradient type in this system; also Pro-gated on source.