# Motion

Every animation in pixilui uses the same small set of presets from `motion-tokens.ts`, and the same rules about when to use each one. Items never contain a literal duration, easing or spring number, so a change to a preset reaches the whole library.

Animations run on [motion](https://motion.dev) (`motion/react`) for springs and layout, and on CSS transitions for colour and opacity. Every animation has a reduced-motion branch.

## The three springs

| Spring | Feel | Used for |
| --- | --- | --- |
| `snappy` | quick, a small settle (visual duration 0.26s, bounce 0.12) | presses, toggles, switch thumbs, selection indicators |
| `smooth` | calm, never overshoots (0.40s, bounce 0) | panels, heights, anything shrinking |
| `morph` | a little give (0.42s, bounce 0.16) | shapes growing, widths following their content |

The rule: **growing uses morph, shrinking uses smooth.** A narrowing width must never dip below its target and bounce back, so shrinking never overshoots. `resizeSpring(previous, next)` picks the right one.

The springs are defined by stiffness and damping, not a fixed duration. If a value changes again mid-flight (a label changes twice quickly), the animation keeps its velocity and heads for the new target instead of restarting.

```ts
import { motionTokens, resizeSpring } from "@/components/pixilui/motion-tokens";

<motion.div animate={{ scale: pressed ? 0.97 : 1 }} transition={motionTokens.spring.snappy} />
```

## Durations and easings

For transitions that are not springs:

| Duration | ms | For |
| --- | --- | --- |
| `instant` | 120 | hover tints, an old face fading out |
| `quick` | 150 | press, close, text swap, tooltip appear |
| `fast` | 250 | open, icon swap, tabs |
| `standard` | 240 | the default UI transition |
| `medium` | 350 | panel close, toast close |
| `slow` | 400 | panel open, content reveal |
| `considered` | 480 | transitions that carry meaning |
| `verySlow` | 500 | emphasis, a success check, an entrance |

| Easing | For |
| --- | --- |
| `enter` | things arriving |
| `smoothOut` | surfaces opening, closing, sliding, resizing |
| `exit` | things leaving |
| `move` | travel across the screen |
| `swap` | icon and label swaps; never bounces |
| `linear` | progress, shimmer, spinners |
| `bounce` | entrances only |

The same values exist as CSS variables and Tailwind utilities: `duration-fast`, `ease-enter`, `ease-swap`.

Staggers keep a sequence short: 16ms between characters, 35ms between items, 40ms between words, 80ms between lines, and the whole sequence under about 400ms.

## The rules

These are the behaviours you will see across the library, and the ones to keep if you change an item.

- **One material.** When something changes shape (a button whose label changes, a card folding into a receipt), width, height and radius animate together. The content never scales. The old content fades out quickly with a 2px blur, and the new content arrives in reading order, 35ms apart.
- **Numbers roll, they do not swap.** Digits are keyed by place and roll in the direction the value moved. A new digit column opens its width on the morph spring.
- **Only changed words move.** When text updates, the words that changed rise 0.3em out of a 4px blur; the rest stay still.
- **Limits push back.** Dragging past an end stretches at most 10px and returns. A note rises from the side it guards.
- **Results do not snap in.** Anything that took work shows a short sequence first.
- **Agent runs read at human speed.** Steps advance every 0.7 to 1s, text streams at about 50ms per word, and rows stagger 150 to 200ms.
- **Quiet success.** A check scales in. No glow, no confetti, no looping decoration.
- **Things that move between containers travel.** They animate from where they were to where they land, instead of disappearing and reappearing.

## Reduced motion

When the user asks for reduced motion, items swap movement for a short fade (`reducedTransition`, 150ms linear) or an instant change (`instantTransition`). Nothing travels, scales or bounces, and spinners hold still. Items read the setting with motion's `useReducedMotion()`; you do not need to wire anything.

## Using the presets in your own code

Import from the installed file and stay inside the presets, so your additions feel like the rest of the app:

```tsx
import { motion, useReducedMotion } from "motion/react";
import { motionTokens, reducedTransition } from "@/components/pixilui/motion-tokens";

export function Reveal({ children }: { children: React.ReactNode }) {
  const reduce = useReducedMotion();
  return (
    <motion.div
      initial={{ opacity: 0, y: reduce ? 0 : motionTokens.distance.base }}
      animate={{ opacity: 1, y: 0 }}
      transition={reduce ? reducedTransition : motionTokens.spring.smooth}
    >
      {children}
    </motion.div>
  );
}
```
