VERSEVA Design.

Motion.

enter decelerating · exit accelerating transitions over keyframes: interruptible attention is finite · ambient never on data React kit: npx verseva add motion

Doctrine

Synthesized from the portfolio's shipped motion (Spectre, the NEMIXO console, this site) and the systems worth studying: Apple's springs, Material's easing pairs, Carbon's two registers, the sonner and vaul playbooks.

RuleIt means
motion shows behaviorEvery animation answers "what just happened"; a heading earns nothing
enter ≠ exitArrivals decelerate (--ease-entrance); permanent departures accelerate (--ease-exit); panels that will return leave on --ease-standard
two registersTask UI is fast and quiet (--dur-1/2); brand moments get --dur-3/4, and one page gets ONE of them
interruptibleState motion is a transition flipped by class, never a one-shot keyframe; mid-flight reversals animate from where they are
attention is finitePulse twice, hold, fade. Only progress may loop forever, at the 1.4s house period
compositor firsttransform + opacity; the earned exceptions are outline (.attn), fr tracks (.grow-*), and alpha-only masks
JS writes numbersHandlers set --mx/--my or a transform; every visual decision stays in CSS where the theme owns it
frequency discounts delightNever decorate an action performed a hundred times a day; hover may delight, focus never

Tokens

Four beats, six curves. Fire them: the difference between the curves IS the doctrine. --ease-exit leaves for good; --ease-back settles direct manipulation.

--ease-standard
--ease-entrance
--ease-emph
--ease-exit
--ease-back
TokenValueJob
--dur-1120msPress, hover, color shifts
--dur-2200msToggles, switches, chips
--dur-3320msEntrances, sheets, disclosure
--dur-4480msOrchestrated moments, rare
--ease-standardcubic-bezier(.2, 0, 0, 1)The default; press and state
--ease-entrancecubic-bezier(.16, 1, .3, 1)Things arriving
--ease-emphcubic-bezier(.3, 0, .1, 1)Emphasis, attention
--ease-exitcubic-bezier(.3, 0, 1, 1)Leaving for good
--ease-backcubic-bezier(.34, 1.56, .64, 1)Snap-back settle after a drag or magnet
--ease-springcubic-bezier(.22, 1, .36, 1)Playful settle, sparing

Press

Acknowledged within 120ms, always. Bar and inline controls sit down 1px; floating and icon controls compress to .94-.97; a disabled control never animates. Amended 2026-08-31.

Entrances

Fade + 10px rise on --ease-entrance, children 60ms apart, capped at six. Travel scales with the surface: 10px for cards, 24px for drawers and sheets, and the responsive trick is swapping animation-name, not the class.

First0ms
Second+60ms
Third+120ms
Fourth+180ms

Line reveal

Each line travels up out of its own mask, 70ms apart, on a transition so it stays interruptible. For display headings only: one per page.

Design is the signature.

Spotlight

The light follows the pointer; the group tracks on the container so neighbors warm before arrival. One handler writes --mx/--my, the CSS owns every visual decision. Touch gets no dead effect.

Move across thesethe halo tracks the pointer
Neighbors warm earlytracking is on the group
Accent at 10%never a new color

Magnetic

Follows at a third of the pointer's offset with no transition, then settles home on --ease-back. Direct manipulation is the only place overshoot is honest.

Numbers

Digits are columns travelling on transform behind an edge mask; tabular-nums so the width never jitters; every column rolls the SAME direction so the trend stays legible. The live number keeps its meaning in a sr-only twin.

/wk

Text swap

Two strings cross-dissolved read as garbage. The house idiom sequences them by flipping transition-delay: the outgoing string fully leaves, THEN the incoming one lands. Fenced behind hover-capable pointers.

v1.0.0 · stable

Attention

The guide ring: pulse twice, hold five seconds, fade to nothing. Attention that persists is nagging; the reduced-motion cut keeps the ring and the fade, drops the pulse. From the console's error guide.

Batch sizethe field the error points at

Loading

Skeletons mirror the real layout and sweep at the 1.4s house period, an overlay on transform, never background-position. Content replaces them with one rise beat, and the swap happens once: loading never flickers back.

Height + width

Intrinsic size animates through fr tracks: 0fr to 1fr, the one sanctioned route. Disclosure chrome still snaps, chevron-only. Width adds opacity so the string never squishes visibly.

Auto height, travelling. The wrapper animates grid-template-rows; the child clips. No measured pixels, no max-height guesses.

Ambient

The beam and the marquee live on marketing surfaces only, never near data. Ambient loops randomize or pause; a uniform loop reads cheap in a minute. Hover pauses the marquee.

Border beamconic accent on a ring mask · 4s

Theme wipe

Every page's theme toggle does it: the new theme sweeps out of the control as a circle, one View Transition, 480ms. The one orchestrated moment a page spends. No support, no motion preference: instant flip, same result.

Anti-patterns

If a surface matches a row, the motion is wrong.

NEVERAnimating height, width, margin, or top directly. fr tracks or a transform proxy, always.
NEVERA one-shot keyframe on state that can reverse. Mid-flight reversal jump-cuts; transitions don't.
NEVERExit slower than enter. Leaving is not an event; it accelerates and gets out of the way.
NEVERAttention that loops until clicked. Pulse twice, hold, fade; nagging is not urgency.
NEVERAmbient motion on a data surface. A beam next to a number is a lie about which one changed.
NEVERMeaning carried by motion alone. Kill every animation and the interface must still say everything.
NEVERA literal ms in a surface. Durations come from the tokens; only the contract itself holds the named above-the-ladder beats (the 1.4s loop, the attention choreography, the ambient periods).
NEVERStaggering past six children or 600ms total. A queue is choreography's failure mode.