diff --git a/skills/motion-patterns/SKILL.md b/skills/motion-patterns/SKILL.md
new file mode 100644
index 00000000..eb542ad9
--- /dev/null
+++ b/skills/motion-patterns/SKILL.md
@@ -0,0 +1,420 @@
+---
+name: motion-patterns
+description: Production-ready animation patterns for React / Next.js — button, modal, toast, stagger, page transitions, exit animations, scroll, and layout — built on motion-foundations tokens and springs.
+version: 1.0
+tags: [motion, animation, ui-patterns]
+category: frontend
+author: jeff
+---
+
+# Motion Patterns
+
+Copy-paste patterns for the most common UI animation needs.
+Every pattern here is built on `motion-foundations` tokens and springs.
+Do not define new duration or easing values here — import them.
+
+## When to Activate
+
+- Animating a button, card, modal, or toast notification
+- Building list entrances with stagger
+- Setting up page transitions in Next.js App Router
+- Adding entrance or exit animations to conditional content
+- Implementing scroll-reveal, scroll-linked progress, or sticky story sections
+- Building expanding cards, accordions, or shared-element transitions
+
+## Outputs
+
+This skill produces:
+
+- Accessible, SSR-safe animation for all standard UI components
+- `AnimatePresence`-wrapped conditional renders with correct exit behavior
+- Page transition wrapper component for Next.js App Router
+- Scroll-reveal and scroll-linked patterns using `useScroll` + `useTransform`
+- Layout animation patterns (`layout`, `layoutId`) for expanding and crossfading elements
+
+## Principles
+
+- Every pattern imports from `motion-foundations`. No raw numbers.
+- Every conditional render is wrapped in `AnimatePresence` with a `key`.
+- Exit animations are always defined alongside enter animations — never as an afterthought.
+- `layout` is used only for small, isolated shifts. Large subtrees get explicit transforms.
+
+## Rules
+
+1. **Always wrap conditional renders in `AnimatePresence` with a `key`** on the direct child. Without a key, exit animations never fire.
+2. **Always define `exit` when defining `initial` + `animate`.** An animation without an exit is incomplete.
+3. **Use `mode="wait"` on page transitions.** Enter must not start until exit completes.
+4. **Never use `layout` on subtrees with more than ~5 children or deeply nested DOM.** Use explicit `x`/`y` transforms instead.
+5. **Stagger interval must stay between `0.05s` and `0.10s`.** Below feels mechanical; above feels sluggish.
+6. **Modals must always include:** focus trap, Escape-key close, scroll lock, `role="dialog"`, `aria-modal="true"`.
+7. **Scroll reveals use `viewport={{ once: true }}`.** Repeating on scroll-out is distracting, not informative.
+8. **All token values are imported from `motion-foundations`.** No inline numbers.
+
+## Decision Guidance
+
+### Choosing the right pattern
+
+| Situation | Pattern |
+| ---------------------------------------- | ---------------------- |
+| Element appears / disappears | `AnimatePresence` |
+| List of items loading in sequence | Stagger variants |
+| Navigating between routes | Page transition wrapper|
+| Element changes size in place | `layout` prop |
+| Same element moves across page contexts | `layoutId` |
+| Element enters when scrolled into view | `whileInView` |
+| Value tied to scroll position | `useScroll` + `useTransform` |
+
+### When to use `mode="wait"` vs `mode="sync"`
+
+| Mode | Use when |
+| ------- | --------------------------------------- |
+| `wait` | Page transitions, content swaps (one at a time) |
+| `sync` | Stacked notifications, list items (overlap is fine) |
+| `popLayout` | Items removed from a reflow list |
+
+## Core Concepts
+
+### AnimatePresence contract
+
+Three things must always be true:
+
+1. `AnimatePresence` wraps the conditional
+2. The direct child has a `key`
+3. The child has an `exit` prop
+
+Miss any one of these and the exit animation silently fails.
+
+### layout vs layoutId
+
+- `layout` — animates the element's own size/position change in place
+- `layoutId` — links two separate elements, crossfading between them across renders
+
+Use `layout="position"` on text inside an expanding container to prevent text reflow from animating.
+
+## Code Examples
+
+### Button feedback
+
+```tsx
+"use client"
+import { motion } from "motion/react"
+import { springs, motionTokens } from "@/lib/motion-tokens"
+
+
+```
+
+### Stagger list
+
+```tsx
+"use client"
+import { motion } from "motion/react"
+import { motionTokens, springs } from "@/lib/motion-tokens"
+
+const container = {
+ hidden: {},
+ visible: {
+ transition: {
+ staggerChildren: 0.08, // within the 0.05–0.10 rule
+ delayChildren: 0.1,
+ },
+ },
+}
+
+const item = {
+ hidden: { opacity: 0, y: motionTokens.distance.md },
+ visible: { opacity: 1, y: 0, transition: springs.gentle },
+}
+
+
+ {items.map((i) => (
+
+ ))}
+
+```
+
+### Modal
+
+```tsx
+"use client"
+import { motion, AnimatePresence } from "motion/react"
+import { springs } from "@/lib/motion-tokens"
+
+// Wrap at the call site:
+// {isOpen && }
+
+export function Modal({ onClose }: { onClose: () => void }) {
+ return (
+ <>
+ {/* Overlay */}
+
+
+ {/* Panel — accessibility requirements: focus trap, Escape close,
+ scroll lock, role="dialog", aria-modal="true" */}
+
+ >
+ )
+}
+```
+
+### Toast stack
+
+```tsx
+"use client"
+import { motion, AnimatePresence } from "motion/react"
+import { springs } from "@/lib/motion-tokens"
+
+
+ {toasts.map((t) => (
+
+ ))}
+
+```
+
+### Page transition (Next.js App Router)
+
+```tsx
+// components/page-transition.tsx
+"use client"
+import { motion, AnimatePresence } from "motion/react"
+import { usePathname } from "next/navigation"
+import { motionTokens } from "@/lib/motion-tokens"
+
+const variants = {
+ initial: { opacity: 0, y: motionTokens.distance.sm },
+ enter: { opacity: 1, y: 0 },
+ exit: { opacity: 0, y: -motionTokens.distance.sm },
+}
+
+export function PageTransition({ children }: { children: React.ReactNode }) {
+ const pathname = usePathname()
+ return (
+
+
+ {children}
+
+
+ )
+}
+```
+
+### Scroll reveal
+
+```tsx
+"use client"
+import { motion } from "motion/react"
+import { motionTokens, springs } from "@/lib/motion-tokens"
+
+
+```
+
+### Scroll progress bar
+
+```tsx
+"use client"
+import { motion, useScroll } from "motion/react"
+
+export function ScrollProgress() {
+ const { scrollYProgress } = useScroll()
+ return (
+
+ )
+}
+```
+
+### Expanding card
+
+```tsx
+"use client"
+import { useState } from "react"
+import { motion, AnimatePresence } from "motion/react"
+import { springs, motionTokens } from "@/lib/motion-tokens"
+
+export function ExpandingCard({ title, body }: { title: string; body: string }) {
+ const [expanded, setExpanded] = useState(false)
+
+ return (
+ setExpanded(!expanded)} className="cursor-pointer">
+ {/* layout="position" prevents text reflow from animating */}
+
+ {title}
+
+
+
+ {expanded && (
+
+ {body}
+
+ )}
+
+
+ )
+}
+```
+
+### Shared-element crossfade
+
+```tsx
+// Source context
+
+
+// Destination context (same layoutId — motion handles the transition)
+
+```
+
+### Accordion
+
+```tsx
+
+ {children}
+
+```
+
+## End-to-End Example
+
+A staggered list that enters on mount, handles conditional presence, and
+respects reduced motion — combining tokens, springs, AnimatePresence, and
+the accessibility hook from `motion-foundations`:
+
+```tsx
+"use client"
+import { useState } from "react"
+import { motion, AnimatePresence } from "motion/react"
+import { motionTokens, springs } from "@/lib/motion-tokens"
+import { useSafeMotion } from "@/hooks/use-reduced-motion"
+
+const containerVariants = {
+ hidden: {},
+ visible: {
+ transition: { staggerChildren: 0.08, delayChildren: 0.1 },
+ },
+}
+
+function ListItem({ label, onRemove }: { label: string; onRemove: () => void }) {
+ const safe = useSafeMotion(motionTokens.distance.sm)
+ return (
+
+ {label}
+
+
+ )
+}
+
+export function AnimatedList({ items, onRemove }: {
+ items: { id: string; label: string }[]
+ onRemove: (id: string) => void
+}) {
+ return (
+
+
+ {items.map((item) => (
+ onRemove(item.id)}
+ />
+ ))}
+
+
+ )
+}
+```
+
+## Constraints / Non-Goals
+
+This skill does **not** cover:
+
+- Token and spring definitions → see `motion-foundations`
+- Drag interactions, swipe gestures, reorderable lists → see `motion-advanced`
+- Text animations (word/character reveal, counters) → see `motion-advanced`
+- SVG path drawing or morphing → see `motion-advanced`
+- Custom animation hooks → see `motion-advanced`
+- CSS-only transitions not using `motion/react`
+
+## Anti-Patterns
+
+| Anti-pattern | Rule violated | Fix |
+| -------------------------------------------- | ------- | ------------------------------------------ |
+| `AnimatePresence` child missing `key` | Rule 1 | Add stable `key` to the direct child |
+| `initial` + `animate` without `exit` | Rule 2 | Always define all three together |
+| Page transition without `mode="wait"` | Rule 3 | Add `mode="wait"` to `AnimatePresence` |
+| `layout` on a 50-item list | Rule 4 | Use `mode="popLayout"` or explicit transforms |
+| `staggerChildren: 0.2` on a 10-item list | Rule 5 | Cap at `0.08–0.10` |
+| Modal without focus trap | Rule 6 | Add `focus-trap-react` or Radix Dialog |
+| `whileInView` without `viewport={{ once: true }}` | Rule 7 | Repeating entrances distract, not inform |
+| `transition={{ duration: 0.3 }}` inline | Rule 8 | Use `motionTokens.duration.normal` |
+
+## Related Skills
+
+- **`motion-foundations`** — defines all tokens, springs, the `useSafeMotion` hook, and SSR guards that every pattern here imports. Must be set up first.
+- **`motion-advanced`** — extends these patterns with drag, gestures, SVG, text, custom hooks, and imperative sequencing. Does not redefine any patterns from this skill.