Motion
Presets in lib/motion.ts, built on Motion for React. Every animation in the system uses them.
Principles
- Feedback under 250ms. Anything answering a click or keypress finishes fast.
- Springs for things that move, tweens for things that fade. Springs keep momentum when interrupted, which happens constantly with streaming content.
- Motion shows state change. A send button turning into stop, a step completing, the orb following your voice. Nothing moves just to move.
- Respect reduced motion. Looping animations stop under
prefers-reduced-motion.
Springs
spring.snappyButtons, toggles, icon swapsspring.gentleMessages, panels, layoutspring.softVoice orb, ambient elementslib/motion.ts
export const spring = {
snappy: { type: "spring", stiffness: 520, damping: 34, mass: 0.6 },
gentle: { type: "spring", stiffness: 260, damping: 30 },
soft: { type: "spring", stiffness: 120, damping: 18 },
}Durations and easing
For tweens: instant 100ms, fast 160ms, base 240ms, slow 400ms. ease.out (quint) for entrances. The same curves exist in CSS as ease-out-quint and ease-in-out-quart for Tailwind transitions.
Variants
rise is the entrance for streamed content: 6px up, a short blur, fade in. stagger() cascades children, as in Suggestions.
import { rise, stagger } from "@/lib/motion"
<motion.div variants={stagger(0.05)} initial="hidden" animate="visible">
{items.map((item) => (
<motion.div key={item.id} variants={rise}>{item.label}</motion.div>
))}
</motion.div>