返回 Skills
jezweb/claude-skills· MIT 内容可用

motion

Build React animations with Motion (Framer Motion) - gestures (drag, hover, tap), scroll effects, spring physics, layout animations, SVG. Bundle: 2.3 KB (mini) to 34 KB (full).

安装

与 skills.sh 相同的 Command / Prompt 安装方式


name: motion description: | Build React animations with Motion (Framer Motion) - gestures (drag, hover, tap), scroll effects, spring physics, layout animations, SVG. Bundle: 2.3 KB (mini) to 34 KB (full).

Use when: drag-and-drop, scroll animations, modals, carousels, parallax. Troubleshoot: AnimatePresence exit, list performance, Tailwind conflicts, Next.js "use client". user-invocable: true

Motion Animation Library

Overview

Motion (package: motion, formerly framer-motion) is the industry-standard React animation library used in production by thousands of applications. With 30,200+ GitHub stars and 300+ official examples, it provides a declarative API for creating sophisticated animations with minimal code.

Key Capabilities:

  • Gestures: drag, hover, tap, pan, focus with cross-device support
  • Scroll Animations: viewport-triggered, scroll-linked, parallax effects
  • Layout Animations: FLIP technique for smooth layout changes, shared element transitions
  • Spring Physics: Natural, customizable motion with physics-based easing
  • SVG: Path morphing, line drawing, attribute animation
  • Exit Animations: AnimatePresence for unmounting transitions
  • Performance: Hardware-accelerated, ScrollTimeline API, bundle optimization (2.3 KB - 34 KB)

Production Tested: React 19.2, Next.js 16.1, Vite 7.3, Tailwind v4


When to Use This Skill

✅ Use Motion When:

Complex Interactions:

  • Drag-and-drop interfaces (sortable lists, kanban boards, sliders)
  • Hover states with scale/rotation/color changes
  • Tap feedback with bounce/squeeze effects
  • Pan gestures for mobile-friendly controls

Scroll-Based Animations:

  • Hero sections with parallax layers
  • Scroll-triggered reveals (fade in as elements enter viewport)
  • Progress bars linked to scroll position
  • Sticky headers with scroll-dependent transforms

Layout Transitions:

  • Shared element transitions between routes (card → detail page)
  • Expand/collapse with automatic height animation
  • Grid/list view switching with smooth repositioning
  • Tab navigation with animated underline

Advanced Features:

  • SVG line drawing animations
  • Path morphing between shapes
  • Spring physics for natural bounce
  • Orchestrated sequences (staggered reveals)
  • Modal dialogs with backdrop blur

Bundle Optimization:

  • Need 2.3 KB animation library (useAnimate mini)
  • Want to reduce Motion from 34 KB to 4.6 KB (LazyMotion)

❌ Don't Use Motion When:

Simple List Animations → Use auto-animate skill instead:

  • Todo list add/remove (auto-animate: 3.28 KB vs motion: 34 KB)
  • Search results filtering
  • Shopping cart items
  • Notification toasts
  • Basic accordions without gestures

Static Content:

  • No user interaction or animations needed
  • Server-rendered content without client interactivity

Cloudflare Workers Deployment → ✅ Fixed (Dec 2024):

  • Previous build compatibility issues resolved (GitHub issue #2918 closed as completed)
  • Motion now works directly with Wrangler - no workaround needed
  • Both motion and framer-motion v12.23.24 work correctly

3D Animations → Use dedicated 3D library:

  • Three.js for WebGL
  • React Three Fiber for React + Three.js

Installation

Latest Stable Version

# Using pnpm (recommended)
pnpm add motion

# Using npm
npm install motion

# Using yarn
yarn add motion

Current Version: 12.27.5 (verified 2026-01-21)

Note for Cloudflare Workers:

# Both packages work with Cloudflare Workers (issue #2918 fixed Dec 2024)
pnpm add motion
# OR
pnpm add framer-motion  # Same version, same API

Package Information

  • Bundle Size:
    • Full motion component: ~34 KB minified+gzipped
    • LazyMotion + m component: ~4.6 KB
    • useAnimate mini: 2.3 KB (smallest React animation library)
    • useAnimate hybrid: 17 KB
  • Dependencies: React 18+ or React 19+
  • TypeScript: Native support included (no @types package needed)

Core Concepts

1. AnimatePresence (Exit Animations)

Enables animations when components unmount:

import { AnimatePresence } from "motion/react"

<AnimatePresence>
  {isVisible && (
    <motion.div
      key="modal"
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
    >
      Modal content
    </motion.div>
  )}
</AnimatePresence>

Critical Rules:

  • AnimatePresence must stay mounted (don't wrap in conditional)
  • All children must have unique key props
  • AnimatePresence wraps the conditional, not the other way around

Common Mistake (exit animation won't play):

// ❌ Wrong - AnimatePresence unmounts with condition
{isVisible && (
  <AnimatePresence>
    <motion.div>Content</motion.div>
  </AnimatePresence>
)}

// ✅ Correct - AnimatePresence stays mounted
<AnimatePresence>
  {isVisible && <motion.div key="unique">Content</motion.div>}
</AnimatePresence>

2. Layout Animations

Special Props:

  • layout: Enable FLIP layout animations
  • layoutId: Connect separate elements for shared transitions
  • layoutScroll: Fix animations in scrollable containers (see Issue #5)
  • layoutRoot: Fix animations in fixed-position elements (see Issue #7)
<motion.div layout>
  {isExpanded ? <FullContent /> : <Summary />}
</motion.div>

3. Scroll Animations

Viewport-Triggered (whileInView)

<motion.div
  initial={{ opacity: 0, y: 50 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: "-100px" }}
>
  Fades in when 100px from entering viewport
</motion.div>

Scroll-Linked (useScroll)

import { useScroll, useTransform } from "motion/react"

const { scrollYProgress } = useScroll()
const y = useTransform(scrollYProgress, [0, 1], [0, -300])

<motion.div style={{ y }}>
  Moves up 300px as user scrolls page
</motion.div>

Performance: Uses native ScrollTimeline API when available for hardware acceleration.


Integration Guides

Vite + React + TypeScript

pnpm add motion

Import: import { motion } from "motion/react"

No Vite configuration needed - works out of the box.

Next.js App Router (Recommended Pattern)

Key Requirement: Motion only works in Client Components (not Server Components).

Step 1: Create Client Component Wrapper

src/components/motion-client.tsx:

"use client"

// Optimized import for Next.js (reduces client JS)
import * as motion from "motion/react-client"

export { motion }

Step 2: Use in Server Components

src/app/page.tsx:

import { motion } from "@/components/motion-client"

export default function Page() {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
    >
      This works in Server Component (wrapper is client)
    </motion.div>
  )
}

Alternative: Direct Client Component

"use client"

import { motion } from "motion/react"

export function AnimatedCard() {
  return <motion.div>...</motion.div>
}

Known Issues (Next.js 15+ + React 19):

  • React 19 fully supported as of December 2025 (see Issue #11 for one StrictMode edge case)
  • Most compatibility issues resolved in Motion 12.27.5
  • AnimatePresence may fail with soft navigation
  • Reorder component incompatible with Next.js routing and page-level scrolling (see Issue #10)

Next.js Pages Router

Works without modifications:

import { motion } from "motion/react"

export default function Page() {
  return <motion.div>No "use client" needed</motion.div>
}

Tailwind CSS Integration

Best Practice: Let each library do what it does best.

  • Tailwind: Static and responsive styling via className
  • Motion: Animations via motion props
<motion.button
  className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
  whileHover={{ scale: 1.1 }}
  whileTap={{ scale: 0.95 }}
>
  Tailwind styles + Motion animations
</motion.button>

⚠️ Remove Tailwind Transitions: Causes stuttering/conflicts.

// ❌ Wrong - Tailwind transition conflicts with Motion
<motion.div className="transition-all duration-300" animate={{ x: 100 }} />

// ✅ Correct - Remove Tailwind transition
<motion.div animate={{ x: 100 }} />

Why: Motion uses inline styles or native browser animations, both override Tailwind's CSS transitions.

Cloudflare Workers (✅ Now Supported)

Status: ✅ Fixed as of December 2024 (GitHub issue #2918 closed as completed)

Installation:

# Motion now works directly with Cloudflare Workers
pnpm add motion

Import:

import { motion } from "motion/react"

Historical Note: Prior to December 2024, there was a Wrangler ESM resolution issue requiring use of framer-motion as a workaround. This has been resolved, and both packages now work correctly with Cloudflare Workers.


Performance Optimization

1. Reduce Bundle Size with LazyMotion

Problem: Full motion component is ~34 KB minified+gzipped.

Solution: Use LazyMotion + m component for 4.6 KB:

import { LazyMotion, domAnimation, m } from "motion/react"

function App() {
  return (
    <LazyMotion features={domAnimation}>
      {/* Use 'm' instead of 'motion' */}
      <m.div
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
      >
        Only 4.6 KB!
      </m.div>
    </LazyMotion>
  )
}

How it works: Loads animation features on-demand instead of bundling everything.

Alternative (Smallest): useAnimate mini (2.3 KB):

import { useAnimate } from "motion/react"

function Component() {
  const [scope, animate] = useAnimate()

  return <div ref={scope}>Smallest possible React animation</div>
}

2. Hardware Acceleration

Add willChange for transforms:

<motion.div
  style={{ willChange: "transform" }}
  animate={{ x: 100, rotate: 45 }}
/>

Also add for: opacity, backgroundColor, clipPath, filter

How it works: Tells browser to optimize for animation, uses GPU compositing.

3. Large Lists → Use Virtualization

Problem: Animating 50-100+ items causes severe slowdown.

Solutions:

pnpm add react-window
# or
pnpm add react-virtuoso
# or
pnpm add @tanstack/react-virtual

Pattern:

import { FixedSizeList } from 'react-window'
import { motion } from 'motion/react'

<FixedSizeList
  height={600}
  itemCount={1000}
  itemSize={50}
>
  {({ index, style }) => (
    <motion.div style={style} layout>
      Item {index}
    </motion.div>
  )}
</FixedSizeList>

Why: Only renders visible items, reduces DOM updates and memory usage.

4. Use layout Prop for FLIP Animations

Automatically animates layout changes without JavaScript calculation:

<motion.div layout>
  {isExpanded ? <LargeContent /> : <SmallContent />}
</motion.div>

Performance: Hardware-accelerated via transforms, no reflow/repaint.


Accessibility

Respect prefers-reduced-motion

import { MotionConfig } from "motion/react"

<MotionConfig reducedMotion="user">
  <App />
</MotionConfig>

Options:

  • "user": Respects OS setting (recommended)
  • "always": Force instant transitions
  • "never": Ignore user preference

Note: ✅ Fixed in Jan 2023 (GitHub #1567) - MotionConfig now works correctly with AnimatePresence.


Common Patterns

5 Production-Ready Patterns:

  1. Modal Dialog - AnimatePresence with backdrop + dialog exit animations
  2. Accordion - Animate height with height: "auto"
  3. Drag Carousel - drag="x" with dragConstraints
  4. Scroll Reveal - whileInView with viewport margin
  5. Parallax Hero - useScroll + useTransform for layered effects

See references/common-patterns.md for full code (15+ patterns).


Known Issues & Solutions

Issue 1: AnimatePresence Exit Not Working

Error: Exit animations don't play, components disappear instantly Source: GitHub Issue #3078

Why It Happens: AnimatePresence wrapped in conditional or missing key props. Defining exit props on staggered children inside modals can also prevent modal from unmounting (backdrop remains visible).

Solution:

// ❌ Wrong - AnimatePresence wrapped in conditional
{isVisible && (
  <AnimatePresence>
    <motion.div>Content</motion.div>
  </AnimatePresence>
)}

// ✅ Correct - AnimatePresence stays mounted
<AnimatePresence>
  {isVisible && <motion.div key="unique">Content</motion.div>}
</AnimatePresence>

// ❌ Wrong - Staggered children with exit prevent modal removal
<AnimatePresence>
  {isOpen && (
    <Modal>
      <motion.ul>
        {items.map(item => (
          <motion.li
            key={item.id}
            exit={{ opacity: 1, scale: 1 }}  // ← Prevents modal unmount
          >
            {item.content}
          </motion.li>
        ))}
      </motion.ul>
    </Modal>
  )}
</AnimatePresence>

// ✅ Fix for modal - Remove exit from children or set duration: 0
<motion.li
  key={item.id}
  exit={{ opacity: 0, scale: 0.5, transition: { duration: 0 } }}
>
  {item.content}
</motion.li>

Issue 2: Large List Performance

Symptom: 50-100+ animated items cause severe slowdown, browser freezes.

Solution: Use virtualization:

pnpm add react-window

See references/performance-optimization.md for full guide.

Issue 3: Tailwind Transitions Conflict

Symptom: Animations stutter or don't work.

Solution: Remove transition-* classes:

// ❌ Wrong
<motion.div className="transition-all" animate={{ x: 100 }} />

// ✅ Correct
<motion.div animate={{ x: 100 }} />

Issue 4: Next.js "use client" Missing

Symptom: Build fails with "motion is not defined" or SSR errors.

Solution: Add "use client" directive:

"use client"

import { motion } from "motion/react"

See references/nextjs-integration.md for App Router patterns.

Issue 5: Scrollable Container Layout Animations

Symptom: Incomplete transitions when removing items from scrolled containers.

Solution: Add layoutScroll prop:

<motion.div layoutScroll className="overflow-auto">
  {items.map(item => (
    <motion.div key={item.id} layout>
      {item.content}
    </motion.div>
  ))}
</motion.div>

Issue 6: Cloudflare Workers Build Errors (✅ RESOLVED)

Status: ✅ Fixed in December 2024 (GitHub issue #2918 closed as completed)

Previous Symptom: Wrangler build failed with React import errors when using motion package.

Current State: Motion now works correctly with Cloudflare Workers. No workaround needed.

If you encounter build issues: Ensure you're using Motion v12.23.24 or later and Wrangler v3+.

GitHub issue: #2918 (closed as completed Dec 13, 2024)

Issue 7: Fixed Position Layout Animations

Symptom: Layout animations in fixed elements have incorrect positioning.

Solution: Add layoutRoot prop:

<motion.div layoutRoot className="fixed top-0 left-0">
  <motion.div layout>Content</motion.div>
</motion.div>

Issue 8: layoutId + AnimatePresence Unmounting

Symptom: Elements with layoutId inside AnimatePresence fail to unmount.

Solution: Wrap in LayoutGroup or avoid mixing exit + layout animations:

import { LayoutGroup } from "motion/react"

<LayoutGroup>
  <AnimatePresence>
    {items.map(item => (
      <motion.div key={item.id} layoutId={item.id}>
        {item.content}
      </motion.div>
    ))}
  </AnimatePresence>
</LayoutGroup>

Issue 9: Reduced Motion with AnimatePresence (✅ RESOLVED)

Status: ✅ Fixed in January 2023 (GitHub issue #1567 closed via PR #1891)

Previous Symptom: MotionConfig reducedMotion setting didn't affect AnimatePresence animations.

Current State: MotionConfig now correctly applies reducedMotion to AnimatePresence components. The setting works as documented.

Optional Manual Control: If you need custom behavior beyond the built-in support:

const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches

<motion.div
  initial={{ opacity: prefersReducedMotion ? 1 : 0 }}
  animate={{ opacity: 1 }}
  transition={{ duration: prefersReducedMotion ? 0 : 0.3 }}
/>

GitHub issue: #1567 (closed as completed Jan 13, 2023)

Issue 10: Reorder Component Limitations

Error: Reorder auto-scroll fails, doesn't work with Next.js routing Source: GitHub Issue #3469, #2183, #2101

Why It Happens:

  • Page-level scrolling: Reorder auto-scroll only works when Reorder.Group is inside element with overflow: auto/scroll, NOT when document itself is scrollable
  • Next.js routing: Incompatible with Next.js routing system, causes random stuck states

Prevention:

// ❌ Wrong - Page-level scrolling (auto-scroll fails)
<body style={{ height: "200vh" }}>
  <Reorder.Group values={items} onReorder={setItems}>
    {/* Auto-scroll doesn't trigger at viewport edges */}
  </Reorder.Group>
</body>

// ✅ Correct - Container with overflow
<div style={{ height: "300px", overflow: "auto" }}>
  <Reorder.Group values={items} onReorder={setItems}>
    {items.map(item => (
      <Reorder.Item key={item.id} value={item}>
        {item.content}
      </Reorder.Item>
    ))}
  </Reorder.Group>
</div>

// ✅ Alternative - Use DnD Kit for complex cases
// Motion docs officially recommend DnD Kit for:
// - Multi-row reordering
// - Dragging between columns
// - Page-level scrollable containers
// - Complex drag-and-drop interactions

// Install: pnpm add @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities

See references/nextjs-integration.md for full Next.js troubleshooting guide.

Issue 11: React 19 StrictMode Drag Bug

Error: Drag gestures break when dragging from top to bottom in file trees Source: GitHub Issue #3169

Why It Happens: Only occurs with React 19 + StrictMode enabled + Ant Design components. Dragged element position breaks and appears offset. Does NOT occur in React 18 or React 19 without StrictMode. Only affects top-to-bottom drag (bottom-to-top works fine).

Prevention: Temporarily disable StrictMode for React 19 projects using drag gestures, or use React 18 if StrictMode is critical. Awaiting official fix from Motion team.

Issue 12: Layout Animations in Scaled Containers

Error: Layout animations start from incorrect positions in scaled parent containers Source: GitHub Issue #3356

Why It Happens: Layout animation system uses scaled coordinates as if they were unscaled. Motion's layout animations work in pixels, while parent scale affects visual coordinates. The mismatch causes position calculation errors.

Prevention (Community Workaround):

// Use transformTemplate to correct for parent scale
const scale = 2; // Parent's transform scale value

<div style={{ transform: `scale(${scale})` }}>
  <motion.div
    layout
    transformTemplate={(latest, generated) => {
      const match = /translate3d\((.+)px,\s?(.+)px,\s?(.+)px\)/.exec(generated);
      if (match) {
        const [, x, y, z] = match;
        return `translate3d(${Number(x) / scale}px, ${Number(y) / scale}px, ${Number(z) / scale}px)`;
      }
      return generated;
    }}
  >
    Content
  </motion.div>
</div>

Limitations: Only works for layout animations only, doesn't fix other transforms, requires knowing parent scale value.

Issue 13: AnimatePresence Exit Gets Stuck on Unmount

Error: Exit state stuck when child unmounts during exit animation Source: GitHub Issue #3243

Why It Happens: When child component inside AnimatePresence unmounts immediately after exit animation triggers, exit state gets stuck. Component incorrectly remains in "exit" state.

Prevention: Don't unmount motion components while AnimatePresence is handling their exit. Ensure motion.div stays mounted until exit completes. Use conditional rendering only on parent AnimatePresence children.

Issue 14: Percentage Values Break Layout Animations in Flex Containers

Error: Layout animations teleport instantly instead of animating smoothly Source: GitHub Issue #3401

Why It Happens: Using percentage-based x values in initial prop breaks layout animations when container uses display flex with justify-content center. Motion's layout animations work in pixels, while CSS percentage transforms are resolved relative to element/parent. The coordinate system mismatch causes position recalculation mid-frame.

Prevention: Convert percentage to pixels before animation. Calculate container width and use pixel values instead of percentage strings.

Issue 15: Sub-Pixel Precision Loss in popLayout Mode

Error: 1px layout shift just before exit transition starts Source: GitHub Issue #3260

Why It Happens: When using AnimatePresence with mode popLayout, exiting element dimensions are captured and reapplied as inline styles. Sub-pixel values from getBoundingClientRect are rounded to nearest integer, causing visible layout shift. Can cause text wrapping changes.

Prevention: Use whole pixel values only for dimensions, or avoid popLayout for sub-pixel-sensitive layouts. No perfect workaround exists.


Templates

This skill includes 5 production-ready templates in the templates/ directory:

  1. motion-vite-basic.tsx - Basic Vite + React + TypeScript setup with common animations
  2. motion-nextjs-client.tsx - Next.js App Router pattern with client component wrapper
  3. scroll-parallax.tsx - Scroll animations, parallax, and viewport triggers
  4. ui-components.tsx - Modal, accordion, carousel, tabs with shared underline
  5. layout-transitions.tsx - FLIP layout animations and shared element transitions

Copy templates into your project and customize as needed.


References

This skill includes 4 comprehensive reference guides:

  • motion-vs-auto-animate.md - Decision guide: when to use Motion vs AutoAnimate
  • performance-optimization.md - Bundle size, LazyMotion, virtualization, hardware acceleration
  • nextjs-integration.md - App Router vs Pages Router, "use client", known issues
  • common-patterns.md - Top 15 patterns with full code examples

See references/ directory for detailed guides.


Scripts

This skill includes 2 automation scripts:

  • init-motion.sh - One-command setup with framework detection (Vite, Next.js, Cloudflare Workers)
  • optimize-bundle.sh - Convert existing Motion code to LazyMotion for smaller bundle

See scripts/ directory for automation tools.


Official Documentation


Related Skills

  • auto-animate - For simple list add/remove/sort animations (3.28 KB vs 34 KB)
  • tailwind-v4-shadcn - Styling integration
  • nextjs - Next.js App Router patterns
  • cloudflare-worker-base - Deployment (Motion now fully compatible)

Comparison: Motion vs AutoAnimate

AspectAutoAnimateMotion
Bundle Size3.28 KB2.3 KB (mini) - 34 KB (full)
Use CaseSimple list animationsComplex gestures, scroll, layout
APIZero-config, 1 lineDeclarative props, verbose
SetupSingle refMotion components + props
Gestures❌ Not supported✅ Drag, hover, tap, pan
Scroll Animations❌ Not supported✅ Parallax, scroll-linked
Layout Animations❌ Not supported✅ FLIP, shared elements
SVG❌ Not supported✅ Path morphing, line drawing
Cloudflare Workers✅ Full support✅ Full support (fixed Dec 2024)
Accessibility✅ Auto prefers-reduced-motion✅ Manual MotionConfig

Rule of Thumb: Use AutoAnimate for 90% of cases (list animations), Motion for 10% (complex interactions).

See references/motion-vs-auto-animate.md for detailed comparison.


Token Efficiency Metrics

ApproachTokens UsedErrors EncounteredTime to Complete
Manual Setup~30,0003-5 (AnimatePresence, Next.js, performance)~2-3 hours
With This Skill~5,0000 ✅~20-30 min
Savings~83%100%~85%

Errors Prevented: 35 documented errors = 100% prevention rate


Package Versions (Verified 2026-01-21)

PackageVersionStatus
motion12.27.5✅ Latest stable
framer-motion12.27.5✅ Same version as motion
react19.2.3✅ Latest stable
next16.1.1✅ Latest stable
vite7.3.1✅ Latest stable

Contributing

Found an issue or have a suggestion?


Production Tested: ✅ React 19.2 + Next.js 16.1 + Vite 7.3 + Tailwind v4 Token Savings: ~83% Error Prevention: 100% (35 documented errors prevented) Bundle Size: 2.3 KB (mini) - 34 KB (full), optimizable to 4.6 KB with LazyMotion Accessibility: MotionConfig reducedMotion support Last verified: 2026-01-21 | Skill version: 3.1.0 | Changes: Added 5 new React 19/layout animation issues, updated to Motion 12.27.5

附带文件

.claude-plugin/plugin.json
{
  "name": "motion",
  "description": "Build React animations with Motion (Framer Motion) - gestures (drag, hover, tap), scroll effects, spring physics, layout animations, SVG. Bundle: 2.3 KB (mini) to 34 KB (full). Use when: drag-and-drop, scroll animations, modals, carousels, parallax. Troubleshoot: AnimatePresence exit, list performance, Tailwind conflicts, Next.js use client.",
  "version": "1.0.0",
  "author": {
    "name": "Jeremy Dawes",
    "email": "jeremy@jezweb.net"
  },
  "license": "MIT",
  "repository": "https://github.com/jezweb/claude-skills",
  "keywords": []
}
README.md
# Motion (Framer Motion)

**Status**: Production Ready ✅
**Last Updated**: 2025-11-09
**Production Tested**: React 19 + Next.js 16 + Vite 7 + Tailwind v4

---

## Auto-Trigger Keywords

Claude Code automatically discovers this skill when you mention:

### Primary Keywords
- motion
- framer-motion
- framer motion
- react animation
- react animations
- javascript animation
- vite react animation
- nextjs animation
- animation library
- declarative animations

### Feature Keywords (Gestures)
- gestures
- drag
- drag and drop
- draggable
- hover animation
- hover state
- tap animation
- tap gesture
- pan gesture
- whileHover
- whileTap
- whileDrag
- drag constraints

### Feature Keywords (Scroll)
- scroll animation
- scroll animations
- scroll-based animation
- scroll-linked animation
- parallax
- parallax effect
- scroll progress
- scroll trigger
- scroll-triggered
- whileInView
- useScroll
- scroll indicator

### Feature Keywords (Layout)
- layout animation
- layout animations
- FLIP animation
- shared element
- shared element transition
- card expansion
- card expand
- expand collapse
- magic move
- layoutId
- LayoutGroup

### Feature Keywords (SVG)
- SVG animation
- SVG path
- path morphing
- line drawing
- path animation
- pathLength
- svg line
- draw SVG

### Feature Keywords (Physics)
- spring physics
- spring animation
- bounce animation
- natural motion
- physics-based
- stiffness
- damping

### Component Keywords
- modal animation
- dialog animation
- accordion animation
- carousel animation
- slider animation
- tabs animation
- dropdown animation
- toast animation
- notification animation
- hero section animation
- page transition
- route transition

### Integration Keywords
- tailwind animation
- shadcn animation
- radix animation
- next.js app router animation
- server components animation
- vite plugin animation
- typescript animation

### Problem Keywords
- AnimatePresence not working
- AnimatePresence exit not working
- layout animation bug
- scroll animation performance
- reduced motion
- prefers-reduced-motion
- bundle size optimization
- large bundle size
- animation performance
- janky animations
- stuttering animations

### Error-Based Keywords
- "motion is not defined"
- "Cannot find module motion"
- "AnimatePresence children must have key"
- "window is not defined motion"
- "useEffect is not defined"
- "Next.js motion error"
- "Cloudflare Workers motion"
- "build error motion"

### Use Case Keywords
- hero parallax
- landing page animation
- scroll reveal
- fade in on scroll
- stagger animation
- staggered list
- drag to reorder
- sortable list
- image gallery animation
- product carousel
- modal popup
- animated menu
- smooth scroll

### Comparison Keywords
- motion vs auto-animate
- framer motion alternative
- motion vs framer
- animation library comparison
- best react animation
- lightweight animation
- motion bundle size

### Migration Keywords
- migrate to motion
- upgrade framer motion
- replace framer motion
- motion migration
- animation library switch

---

## What This Skill Does

Production-ready setup for Motion (formerly Framer Motion) - the industry-standard React animation library with 30,200+ GitHub stars. Motion provides declarative animations, gesture controls, scroll effects, spring physics, layout animations, and SVG manipulation.

### Core Capabilities

✅ **Gesture Controls** - drag, hover, tap, pan, focus with cross-device support
✅ **Scroll Animations** - viewport-triggered, scroll-linked, parallax with ScrollTimeline API
✅ **Layout Animations** - FLIP technique, shared element transitions with layoutId
✅ **Spring Physics** - Natural, customizable physics-based motion
✅ **SVG Animations** - Path morphing, line drawing, attribute animation
✅ **Exit Animations** - AnimatePresence for smooth unmounting transitions
✅ **Bundle Optimization** - 2.3 KB (mini) to 34 KB (full), optimizable to 4.6 KB with LazyMotion
✅ **29+ Documented Issues Prevented** - AnimatePresence, layout, performance, Next.js, Cloudflare
✅ **5 Production Templates** - Vite, Next.js, scroll, UI components, layout transitions
✅ **4 Reference Guides** - vs AutoAnimate, performance, Next.js, common patterns
✅ **2 Automation Scripts** - One-command setup, bundle optimizer

---

## When to Use This Skill

### ✅ Use Motion When:

**Complex Interactions**:
- Drag-and-drop interfaces (sortable lists, kanban boards)
- Hover states with scale/rotation/color changes
- Tap feedback with bounce/squeeze effects

**Scroll-Based Animations**:
- Hero sections with parallax layers
- Scroll-triggered reveals (fade in as elements enter viewport)
- Progress bars linked to scroll position

**Layout Transitions**:
- Shared element transitions between routes (card → detail page)
- Expand/collapse with automatic height animation
- Grid/list view switching

**Advanced Features**:
- SVG line drawing, path morphing
- Spring physics for natural bounce
- Orchestrated sequences (staggered reveals)

### ❌ Don't Use Motion When:

**Simple List Animations** → Use `auto-animate` skill instead:
- Todo list add/remove (Motion: 34 KB vs AutoAnimate: 3.28 KB)
- Search results filtering
- Basic accordions without gestures

**Cloudflare Workers Deployment** → ✅ **Fixed (Dec 2024)**:
- Previous build issues resolved (GitHub issue #2918 closed)
- Motion now works directly with Wrangler

---

## Known Issues This Skill Prevents

| Issue | Why It Happens | Source | How Skill Fixes It |
|-------|---------------|---------|-------------------|
| AnimatePresence Exit Not Working | AnimatePresence wrapped in conditional or missing keys | Common mistake | Template shows correct placement pattern |
| Large List Performance | 50-100+ items cause browser freeze | GitHub issues | Virtualization guide with react-window examples |
| Tailwind Transitions Conflict | CSS transitions conflict with Motion | Official docs | Template shows correct pattern (remove transition classes) |
| Next.js "use client" Missing | Motion uses DOM APIs unavailable in Server Components | Next.js App Router | Next.js template with "use client" directive |
| Scrollable Container Broken | Layout animations incomplete in scrolled containers | Issue #1471 | layoutScroll prop documented |
| Fixed Element Positioning | Layout animations incorrect in fixed elements | Official docs | layoutRoot prop documented |
| layoutId + AnimatePresence Unmounting | Elements fail to unmount | Issue #1619 | LayoutGroup wrapper pattern |
| Reduced Motion with AnimatePresence (RESOLVED Jan 2023) | MotionConfig didn't apply to AnimatePresence | Issue #1567 (closed) | Fixed via PR #1891 - works correctly now |
| Reorder Component in Next.js | Incompatible with Next.js routing | Issues #2183, #2101 | Alternative drag implementations |
| Cloudflare Workers Build Errors (RESOLVED Dec 2024) | Wrangler ESM resolution issue | Issue #2918 (closed) | Fixed - Motion now works directly |
| Non-Accelerated Animations | Animating width/height causes reflow | Best practices | layout prop for FLIP technique |
| Missing willChange Hint | No GPU optimization | Performance guide | Template examples include willChange |
| Full Bundle for Simple Use | 34 KB for fade animation | Bundle size | LazyMotion setup guide (34 KB → 4.6 KB) |
| No Unique Keys on List | React can't track changes | React docs | Template shows key prop pattern |
| Stagger Without Variants | Manual delay calculation complex | Best practices | Variants pattern with staggerChildren |

**Total**: 29+ documented errors prevented

---

## Token Efficiency Metrics

| Approach | Tokens Used | Errors Encountered | Time to Complete |
|----------|------------|-------------------|------------------|
| **Manual Setup** | ~30,000 | 3-5 (AnimatePresence, Next.js, performance) | ~2-3 hours |
| **With This Skill** | ~5,000 | 0 ✅ | ~20-30 min |
| **Savings** | **~83%** | **100%** | **~85%** |

---

## Package Versions (Verified 2025-11-09)

| Package | Version | Status |
|---------|---------|--------|
| motion | 12.23.24 | ✅ Latest stable |
| framer-motion | 12.23.24 | ✅ Same version as motion |
| react | 19.2.0 | ✅ Latest stable |
| next | 16.0.1 | ✅ Latest stable |
| vite | 7.2.2 | ✅ Latest stable |

---

## Dependencies

**Prerequisites**: React 18+ or React 19+

**Integrates With**:
- tailwind-v4-shadcn (styling)
- nextjs (if using Next.js)
- auto-animate (complementary for simple animations)
- cloudflare-worker-base (deployment, use framer-motion variant)

---

## File Structure

```
motion/
├── SKILL.md                          # Complete documentation (~500 lines)
├── README.md                         # This file (auto-trigger keywords)
├── templates/                        # 5 production-ready examples
│   ├── motion-vite-basic.tsx        # Vite + React setup with 9 examples
│   ├── motion-nextjs-client.tsx     # Next.js App Router patterns
│   ├── scroll-parallax.tsx          # Scroll animations & parallax
│   ├── ui-components.tsx            # Modal, accordion, carousel, tabs, dropdown, toast
│   └── layout-transitions.tsx       # FLIP, shared elements, drag-to-reorder
├── references/                       # 4 comprehensive guides
│   ├── motion-vs-auto-animate.md    # Decision guide: when to use which
│   ├── performance-optimization.md  # Bundle size, LazyMotion, virtualization
│   ├── nextjs-integration.md        # App Router vs Pages Router patterns
│   └── common-patterns.md           # Top 15 patterns with code
└── scripts/                          # 2 automation scripts
    ├── init-motion.sh               # One-command setup (detects Vite/Next.js/Cloudflare)
    └── optimize-bundle.sh           # Convert to LazyMotion (34 KB → 4.6 KB)
```

---

## Official Documentation

- **Official Site**: https://motion.dev
- **React Docs**: https://motion.dev/docs/react
- **GitHub Repository**: https://github.com/motiondivision/motion (30,200+ stars)
- **Examples**: https://motion.dev/examples (300+ examples with source code)
- **npm Package**: https://www.npmjs.com/package/motion

---

## Related Skills

- **auto-animate** - For simple list add/remove/sort animations (use 90% of the time)
- **tailwind-v4-shadcn** - Styling integration (avoid transition classes with Motion)
- **nextjs** - Next.js App Router patterns ("use client" required)
- **cloudflare-worker-base** - Deployment (Motion now fully compatible)

---

## Motion vs AutoAnimate Quick Comparison

| Aspect | AutoAnimate | Motion | Winner |
|--------|-------------|--------|--------|
| **Bundle Size** | 3.28 KB | 2.3-34 KB | AutoAnimate |
| **API Simplicity** | 3 lines | 12+ lines | AutoAnimate |
| **Gesture Controls** | ❌ | ✅ | Motion |
| **Scroll Animations** | ❌ | ✅ | Motion |
| **Layout Animations** | ❌ | ✅ | Motion |
| **SVG Animations** | ❌ | ✅ | Motion |
| **List Animations** | ✅ Auto | ✅ Manual | AutoAnimate |
| **Cloudflare Workers** | ✅ | ⚠️ | AutoAnimate |

**Rule of Thumb**: Use AutoAnimate for 90% of cases (list animations), Motion for 10% (gestures, scroll, layout).

See `references/motion-vs-auto-animate.md` for detailed comparison.

---

## Contributing

Found an issue or have a suggestion?
- Open an issue: https://github.com/jezweb/claude-skills/issues
- See templates and references for detailed examples

---

## License

MIT License - See main repo LICENSE file

---

**Production Tested**: ✅ React 19 + Next.js 16 + Vite 7 + Tailwind v4
**Token Savings**: ~83%
**Error Prevention**: 100% (29+ documented errors prevented)
**Bundle Size**: 2.3 KB (mini) - 34 KB (full), optimizable to 4.6 KB
**Community**: 30,200+ GitHub stars, 300+ official examples
**Ready to use!** See [SKILL.md](SKILL.md) for complete setup.
references/common-patterns.md
# Motion Common Patterns - Quick Reference

Production-tested animation patterns with code examples. Copy-paste ready.

---

## 1. Modal Dialog

```tsx
import { motion, AnimatePresence } from "motion/react"

<AnimatePresence>
  {isOpen && (
    <>
      <motion.div
        key="backdrop"
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        onClick={onClose}
        className="fixed inset-0 bg-black/50 z-40"
      />
      <motion.dialog
        key="dialog"
        initial={{ opacity: 0, scale: 0.9 }}
        animate={{ opacity: 1, scale: 1 }}
        exit={{ opacity: 0, scale: 0.9 }}
        className="fixed inset-0 m-auto w-96 bg-white rounded-lg shadow-xl z-50"
      >
        Content
      </motion.dialog>
    </>
  )}
</AnimatePresence>
```

---

## 2. Accordion

```tsx
<motion.div
  animate={{ height: isOpen ? "auto" : 0 }}
  style={{ overflow: "hidden" }}
  transition={{ duration: 0.3 }}
>
  <div className="p-4">Content</div>
</motion.div>
```

---

## 3. Tabs with Shared Underline

```tsx
<div className="flex gap-4 border-b">
  {tabs.map(tab => (
    <button key={tab.id} onClick={() => setActive(tab.id)}>
      {tab.label}
      {active === tab.id && (
        <motion.div
          layoutId="underline"
          className="absolute bottom-0 h-0.5 bg-blue-600"
        />
      )}
    </button>
  ))}
</div>
```

---

## 4. Staggered List

```tsx
const container = {
  hidden: {},
  show: { transition: { staggerChildren: 0.1 } }
}

const item = {
  hidden: { opacity: 0, y: 20 },
  show: { opacity: 1, y: 0 }
}

<motion.ul variants={container} initial="hidden" animate="show">
  {items.map(item => (
    <motion.li key={item.id} variants={item}>
      {item.text}
    </motion.li>
  ))}
</motion.ul>
```

---

## 5. Parallax Hero

```tsx
const { scrollY } = useScroll()
const y = useTransform(scrollY, [0, 1000], [0, -300])

<motion.div style={{ y }}>
  <img src="/background.jpg" />
</motion.div>
```

---

## 6. Scroll Progress Bar

```tsx
const { scrollYProgress } = useScroll()

<motion.div
  style={{ scaleX: scrollYProgress }}
  className="fixed top-0 h-1 bg-blue-600 origin-left"
/>
```

---

## 7. Fade In on Scroll

```tsx
<motion.div
  initial={{ opacity: 0, y: 50 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: "-100px" }}
>
  Content
</motion.div>
```

---

## 8. Drag to Reorder

```tsx
<motion.div
  drag="y"
  dragConstraints={{ top: 0, bottom: 0 }}
  dragElastic={0.1}
  whileDrag={{ scale: 1.05 }}
>
  Drag me
</motion.div>
```

---

## 9. Card Expand

```tsx
<motion.div layout onClick={toggle} className={isExpanded ? "w-full" : "w-64"}>
  {isExpanded && <p>Extra content</p>}
</motion.div>
```

---

## 10. Shared Element Transition

```tsx
// Grid view
<motion.div layoutId={card.id} onClick={() => expand(card)}>
  Preview
</motion.div>

// Detail view
<motion.div layoutId={card.id}>
  Full details
</motion.div>
```

---

## 11. Carousel

```tsx
<motion.div
  drag="x"
  dragConstraints={{ left: -width, right: 0 }}
  className="flex"
>
  {images.map(img => <img key={img.id} src={img.url} />)}
</motion.div>
```

---

## 12. Hover & Tap Button

```tsx
<motion.button
  whileHover={{ scale: 1.1 }}
  whileTap={{ scale: 0.95 }}
>
  Click me
</motion.button>
```

---

## 13. Toast Notification

```tsx
<AnimatePresence>
  {isVisible && (
    <motion.div
      initial={{ opacity: 0, x: 300 }}
      animate={{ opacity: 1, x: 0 }}
      exit={{ opacity: 0, x: 300 }}
      className="fixed top-4 right-4 bg-blue-600 text-white p-4 rounded"
    >
      Message
    </motion.div>
  )}
</AnimatePresence>
```

---

## 14. SVG Line Drawing

```tsx
<motion.path
  d="M 0 50 Q 50 0 100 50"
  initial={{ pathLength: 0 }}
  animate={{ pathLength: 1 }}
  transition={{ duration: 2 }}
  stroke="black"
  strokeWidth={2}
  fill="none"
/>
```

---

## 15. Spring Physics

```tsx
<motion.div
  animate={{ x: 100 }}
  transition={{
    type: "spring",
    stiffness: 300,
    damping: 10
  }}
/>
```

---

See templates/ for full component examples.
references/motion-vs-auto-animate.md
# Motion vs AutoAnimate - Decision Guide

This document helps you choose between Motion (Framer Motion) and AutoAnimate for your animation needs.

---

## TL;DR - Quick Decision

**Use AutoAnimate when:**
- ✅ Animating list add/remove/sort operations
- ✅ Simple accordion expand/collapse
- ✅ Toast notifications fade in/out
- ✅ Form validation error messages appearing/disappearing
- ✅ Bundle size is critical (3.28 KB)
- ✅ Want zero configuration

**Use Motion when:**
- ✅ Need gesture controls (drag, hover, tap with fine control)
- ✅ Need scroll-based animations or parallax
- ✅ Need layout/shared element transitions
- ✅ Need SVG path morphing or line drawing
- ✅ Need spring physics customization
- ✅ Need complex orchestrated animations

**Rule of Thumb**: AutoAnimate for 90% of cases, Motion for 10%

---

## Bundle Size Comparison

| Package | Minified + Gzipped | Use Case |
|---------|-------------------|----------|
| **AutoAnimate** | 3.28 KB | Simple list animations |
| **Motion useAnimate mini** | 2.3 KB | Smallest React animation |
| **Motion useAnimate hybrid** | 17 KB | Imperative animations |
| **Motion with LazyMotion** | 4.6 KB | Optimized declarative |
| **Motion full component** | 34 KB | Full feature set |

**Winner for bundle size**: Motion useAnimate mini (2.3 KB) or AutoAnimate (3.28 KB)

**Winner for features**: Motion full (34 KB)

**Sweet spot**: LazyMotion (4.6 KB) for most Motion use cases

---

## API Complexity Comparison

### AutoAnimate (Zero Config)

```tsx
import { useAutoAnimate } from '@formkit/auto-animate/react'

const [parent] = useAutoAnimate()

return (
  <ul ref={parent}>
    {items.map(item => <li key={item.id}>{item.text}</li>)}
  </ul>
)
```

**Lines of code**: 3
**Configuration**: 0
**Learning curve**: Minutes

### Motion (Declarative)

```tsx
import { motion } from 'motion/react'

return (
  <ul>
    {items.map(item => (
      <motion.li
        key={item.id}
        initial={{ opacity: 0, y: 20 }}
        animate={{ opacity: 1, y: 0 }}
        exit={{ opacity: 0, y: -20 }}
        layout
      >
        {item.text}
      </motion.li>
    ))}
  </ul>
)
```

**Lines of code**: 12
**Configuration**: 4 props
**Learning curve**: Hours

**Winner for simplicity**: AutoAnimate

**Winner for control**: Motion

---

## Feature Comparison

| Feature | AutoAnimate | Motion |
|---------|-------------|--------|
| **List add/remove** | ✅ Automatic | ✅ Manual setup |
| **List reorder** | ✅ Automatic | ✅ Manual setup |
| **Accordion** | ✅ Automatic | ✅ Manual setup |
| **Drag gestures** | ❌ Not supported | ✅ Full control |
| **Hover states** | ❌ Not supported | ✅ whileHover prop |
| **Tap states** | ❌ Not supported | ✅ whileTap prop |
| **Scroll animations** | ❌ Not supported | ✅ whileInView, useScroll |
| **Parallax** | ❌ Not supported | ✅ useTransform |
| **Layout animations** | ❌ Not supported | ✅ layout prop, FLIP |
| **Shared elements** | ❌ Not supported | ✅ layoutId |
| **SVG animations** | ❌ Not supported | ✅ path, line drawing |
| **Spring physics** | ❌ Not customizable | ✅ Full control |
| **Variants/orchestration** | ❌ Not supported | ✅ Stagger, delay, sequence |
| **Exit animations** | ✅ Automatic | ✅ AnimatePresence |
| **TypeScript** | ✅ Native support | ✅ Native support |
| **SSR/Next.js** | ✅ Full support | ✅ Full support (use client) |
| **Cloudflare Workers** | ✅ Full support | ⚠️ Use framer-motion instead |
| **Accessibility** | ✅ Auto prefers-reduced-motion | ✅ Manual MotionConfig |

---

## Use Case Breakdown

### Simple List Animations (90% of use cases)

**Scenario**: Todo list, shopping cart, search results, notification list

**AutoAnimate**:
```tsx
const [parent] = useAutoAnimate()
return <ul ref={parent}>{items.map(...)}</ul>
```

**Motion**:
```tsx
<AnimatePresence>
  {items.map(item => (
    <motion.li
      key={item.id}
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      layout
    >
      {item.text}
    </motion.li>
  ))}
</AnimatePresence>
```

**Recommendation**: **AutoAnimate** (simpler, smaller)

---

### Accordion Components

**Scenario**: FAQ, collapsible sections, navigation menus

**AutoAnimate**:
```tsx
const [parent] = useAutoAnimate()

return (
  <div ref={parent}>
    {isOpen && <div>Content</div>}
  </div>
)
```

**Motion**:
```tsx
<motion.div
  animate={{ height: isOpen ? "auto" : 0 }}
  style={{ overflow: "hidden" }}
>
  <div>Content</div>
</motion.div>
```

**Recommendation**: **AutoAnimate** for simple accordions, **Motion** if you need:
- Custom spring physics
- Staggered child animations
- Scroll-triggered expand/collapse

---

### Modal Dialogs

**Scenario**: Popup dialogs, overlays, lightboxes

**AutoAnimate**:
- Not ideal (doesn't handle backdrop animations well)

**Motion**:
```tsx
<AnimatePresence>
  {isOpen && (
    <>
      <motion.div
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        exit={{ opacity: 0 }}
        className="backdrop"
      />
      <motion.dialog
        initial={{ opacity: 0, scale: 0.9 }}
        animate={{ opacity: 1, scale: 1 }}
        exit={{ opacity: 0, scale: 0.9 }}
      >
        Content
      </motion.dialog>
    </>
  )}
</AnimatePresence>
```

**Recommendation**: **Motion** (more control over backdrop + dialog)

---

### Hero Sections & Landing Pages

**Scenario**: Marketing sites with parallax, scroll effects, complex animations

**AutoAnimate**:
- Not suitable

**Motion**:
```tsx
const { scrollYProgress } = useScroll()
const y = useTransform(scrollYProgress, [0, 1], [0, -300])

return <motion.div style={{ y }}>Hero content</motion.div>
```

**Recommendation**: **Motion** (only option)

---

### Carousels & Sliders

**Scenario**: Image galleries, product showcases

**AutoAnimate**:
- Not suitable

**Motion**:
```tsx
<motion.div
  drag="x"
  dragConstraints={{ left: -width, right: 0 }}
>
  {images.map(img => <img src={img.url} />)}
</motion.div>
```

**Recommendation**: **Motion** (gesture controls required)

---

### Drag-and-Drop Interfaces

**Scenario**: Kanban boards, sortable lists, reorderable items

**AutoAnimate**:
- Not supported (no drag gestures)

**Motion**:
```tsx
<motion.div
  drag
  dragConstraints={constraints}
  onDragEnd={handleDragEnd}
>
  Draggable item
</motion.div>
```

**Recommendation**: **Motion** (only option)

---

### Page/Route Transitions

**Scenario**: Animating between pages or routes

**AutoAnimate**:
- Not ideal (designed for element-level, not page-level)

**Motion**:
```tsx
<AnimatePresence mode="wait">
  <motion.div
    key={pathname}
    initial={{ opacity: 0, x: 20 }}
    animate={{ opacity: 1, x: 0 }}
    exit={{ opacity: 0, x: -20 }}
  >
    {page content}
  </motion.div>
</AnimatePresence>
```

**Recommendation**: **Motion** (better control)

**Note**: Next.js App Router has issues with route transitions. Consider alternatives.

---

### Card Expand to Detail View

**Scenario**: Grid of cards → click to expand → detail page

**AutoAnimate**:
- Not supported (no shared element transitions)

**Motion**:
```tsx
// Grid view
<motion.div layoutId={card.id} onClick={expand}>
  Card preview
</motion.div>

// Detail view
<motion.div layoutId={card.id}>
  Full card details
</motion.div>
```

**Recommendation**: **Motion** (shared element transition with layoutId)

---

## Performance Comparison

| Metric | AutoAnimate | Motion |
|--------|-------------|--------|
| **First paint** | Fastest (3.28 KB) | Slow (34 KB) or Fast (2.3-4.6 KB with optimization) |
| **Runtime performance** | Fast (minimal JS) | Fast (GPU-accelerated when possible) |
| **Large lists (50+ items)** | Good (auto-optimized) | Poor (needs virtualization) |
| **Scroll animations** | N/A | Excellent (ScrollTimeline API) |
| **Hardware acceleration** | Yes | Yes |

**Winner for first paint**: AutoAnimate

**Winner for runtime**: Tie (both use GPU when possible)

**Winner for large lists**: AutoAnimate (no setup needed)

---

## Accessibility Comparison

| Feature | AutoAnimate | Motion |
|---------|-------------|--------|
| **prefers-reduced-motion** | ✅ Automatic | ✅ Manual (MotionConfig) |
| **Keyboard support** | ✅ Inherits from elements | ✅ whileFocus prop |
| **Screen reader friendly** | ✅ Yes | ✅ Yes |

**Winner**: AutoAnimate (automatic reduced motion support)

**Note**: Motion requires manual setup:
```tsx
<MotionConfig reducedMotion="user">
  <App />
</MotionConfig>
```

---

## Framework Compatibility

| Framework | AutoAnimate | Motion |
|-----------|-------------|--------|
| **React 18/19** | ✅ Full support | ✅ Full support |
| **Next.js Pages Router** | ✅ Works out of the box | ✅ Works out of the box |
| **Next.js App Router** | ✅ Works out of the box | ✅ Requires "use client" |
| **Vite** | ✅ Works out of the box | ✅ Works out of the box |
| **Cloudflare Workers** | ✅ Full support | ⚠️ Use framer-motion v12 |
| **Remix** | ✅ Works out of the box | ✅ Works out of the box |
| **Astro** | ✅ Via React component | ✅ Via React component |
| **SvelteKit** | ✅ Via Svelte plugin | ❌ React only |
| **Vue/Nuxt** | ✅ Via Vue plugin | ❌ React only |

**Winner for compatibility**: AutoAnimate (supports Vue, Svelte, vanilla JS)

**Note**: Motion is React-only, but AutoAnimate has official plugins for:
- Vue: `@formkit/auto-animate/vue`
- Svelte: `@formkit/auto-animate/svelte`
- Vanilla JS: `@formkit/auto-animate`

---

## Migration Guide

### From AutoAnimate to Motion

**When to migrate**: You outgrow AutoAnimate and need gestures, scroll, or layout animations.

**Example - Animated List**:

Before (AutoAnimate):
```tsx
const [parent] = useAutoAnimate()
return <ul ref={parent}>{items.map(...)}</ul>
```

After (Motion):
```tsx
<AnimatePresence>
  {items.map(item => (
    <motion.li
      key={item.id}
      initial={{ opacity: 0, x: -20 }}
      animate={{ opacity: 1, x: 0 }}
      exit={{ opacity: 0, x: 20 }}
      layout
    >
      {item.text}
    </motion.li>
  ))}
</AnimatePresence>
```

**Migration steps**:
1. Install Motion: `pnpm add motion`
2. Replace `<div ref={parent}>` with `<motion.div>`
3. Add animation props: `initial`, `animate`, `exit`
4. Wrap in `<AnimatePresence>` for exit animations
5. Add `layout` prop for reordering

---

### From Motion to AutoAnimate

**When to migrate**: Simplifying codebase, reducing bundle size for simple animations.

**Example - Animated List**:

Before (Motion):
```tsx
<AnimatePresence>
  {items.map(item => (
    <motion.li
      key={item.id}
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
      layout
    >
      {item.text}
    </motion.li>
  ))}
</AnimatePresence>
```

After (AutoAnimate):
```tsx
const [parent] = useAutoAnimate()
return <ul ref={parent}>{items.map(...)}</ul>
```

**Migration steps**:
1. Install AutoAnimate: `pnpm add @formkit/auto-animate`
2. Remove Motion props (initial, animate, exit, layout)
3. Remove AnimatePresence wrapper
4. Add `useAutoAnimate` hook and attach ref to parent

**Bundle size savings**: 34 KB → 3.28 KB (~90% reduction)

---

## Real-World Recommendations

### E-commerce Site

**Use AutoAnimate for**:
- Shopping cart item add/remove
- Product filter results
- Notification toasts

**Use Motion for**:
- Product image carousel (drag gestures)
- Hero section parallax
- Product detail page transitions (shared elements)

### Blog / Content Site

**Use AutoAnimate for**:
- Article list filtering
- Comment threads expand/collapse
- Tag selection

**Use Motion for**:
- Hero parallax on homepage
- Scroll-triggered section reveals
- Image lightbox modals

### Dashboard / SaaS App

**Use AutoAnimate for**:
- Sidebar navigation accordion
- Data table row add/remove
- Toast notifications

**Use Motion for**:
- Drag-to-reorder kanban cards
- Chart animations
- Modal dialogs with complex transitions

### Landing Page / Marketing Site

**Use AutoAnimate for**:
- FAQ accordion
- Feature comparison table filtering

**Use Motion for**:
- Hero section (parallax, scroll effects)
- Scroll-triggered reveals throughout page
- Interactive demos (gestures, drag)

---

## Cost-Benefit Analysis

### AutoAnimate

**Benefits**:
- ✅ Smallest bundle (3.28 KB)
- ✅ Zero configuration
- ✅ Works with any framework (React, Vue, Svelte, vanilla)
- ✅ Automatic prefers-reduced-motion
- ✅ Perfect for 90% of animation needs

**Costs**:
- ❌ No gesture controls
- ❌ No scroll animations
- ❌ No layout/shared element transitions
- ❌ No SVG path morphing
- ❌ Less control over animation timing/easing

### Motion

**Benefits**:
- ✅ Complete animation toolkit
- ✅ Gesture controls (drag, hover, tap, pan)
- ✅ Scroll-based animations
- ✅ Layout/shared element transitions
- ✅ SVG path morphing and line drawing
- ✅ Spring physics customization
- ✅ Hardware-accelerated (ScrollTimeline API)

**Costs**:
- ❌ Larger bundle (34 KB, optimizable to 2.3-4.6 KB)
- ❌ More complex API
- ❌ React-only
- ❌ Requires manual prefers-reduced-motion setup
- ❌ Cloudflare Workers compatibility issues

---

## Decision Flowchart

```
Start
  ↓
Do you need gestures (drag, hover with fine control)?
  ├─ Yes → Motion
  └─ No → ↓
Do you need scroll-based animations or parallax?
  ├─ Yes → Motion
  └─ No → ↓
Do you need shared element transitions (card → detail)?
  ├─ Yes → Motion
  └─ No → ↓
Do you need SVG path morphing or line drawing?
  ├─ Yes → Motion
  └─ No → ↓
Is it just list add/remove/sort animations?
  ├─ Yes → AutoAnimate
  └─ No → ↓
Is it accordion/collapse/expand?
  ├─ Yes → AutoAnimate (unless you need custom physics)
  └─ No → ↓
Do you want zero configuration?
  ├─ Yes → AutoAnimate
  └─ No → Motion
```

---

## Can You Use Both?

**Yes!** They complement each other well.

**Pattern**:
- Use AutoAnimate for simple list animations
- Use Motion for complex gestures and scroll effects

**Example**:
```tsx
// Simple list with AutoAnimate
const [listRef] = useAutoAnimate()

return (
  <>
    {/* Motion for hero parallax */}
    <motion.div style={{ y: parallaxY }}>
      Hero section
    </motion.div>

    {/* AutoAnimate for product list */}
    <ul ref={listRef}>
      {products.map(product => (
        <li key={product.id}>{product.name}</li>
      ))}
    </ul>

    {/* Motion for carousel */}
    <motion.div drag="x">
      {images.map(img => <img src={img.url} />)}
    </motion.div>
  </>
)
```

**Bundle size**: 3.28 KB (AutoAnimate) + 2.3-34 KB (Motion) = 5.58-37.28 KB total

**Recommendation**: Use AutoAnimate for 90% of cases, add Motion only for features AutoAnimate can't handle.

---

## Summary Table

| Criteria | AutoAnimate | Motion | Winner |
|----------|-------------|--------|--------|
| **Bundle Size** | 3.28 KB | 2.3-34 KB | AutoAnimate |
| **API Simplicity** | 3 lines of code | 12+ lines | AutoAnimate |
| **Feature Set** | Limited | Comprehensive | Motion |
| **Gesture Controls** | ❌ | ✅ | Motion |
| **Scroll Animations** | ❌ | ✅ | Motion |
| **Layout Animations** | ❌ | ✅ | Motion |
| **SVG Animations** | ❌ | ✅ | Motion |
| **List Animations** | ✅ Automatic | ✅ Manual | AutoAnimate |
| **Accessibility** | ✅ Automatic | ✅ Manual | AutoAnimate |
| **Framework Support** | React, Vue, Svelte, JS | React only | AutoAnimate |
| **Cloudflare Workers** | ✅ | ⚠️ | AutoAnimate |
| **Learning Curve** | Minutes | Hours | AutoAnimate |
| **Performance** | Excellent | Excellent | Tie |

---

## Final Recommendation

**Default to AutoAnimate** for:
- List animations (add/remove/sort)
- Simple accordions
- Toast notifications
- Form validation errors

**Upgrade to Motion** when you need:
- Gestures (drag, hover with fine control)
- Scroll animations or parallax
- Shared element transitions
- SVG path morphing
- Complex choreographed animations

**Use both** when building complex apps with diverse animation needs.

**80/20 Rule**: 80% of your animations can be handled by AutoAnimate, 20% require Motion.

---

## Getting Help

- **AutoAnimate**: https://auto-animate.formkit.com
- **Motion**: https://motion.dev
- **AutoAnimate Skill**: See `../SKILL.md` in this repo
- **Motion Skill**: See `../SKILL.md` in this repo
references/nextjs-integration.md
# Motion + Next.js Integration Guide

This guide covers how to use Motion (Framer Motion) with Next.js, including App Router patterns, Pages Router setup, known issues, and performance optimization.

---

## TL;DR - Quick Start

**Pages Router** (Next.js 12):
```tsx
// Works out of the box, no special setup needed
import { motion } from "motion/react"

export default function Page() {
  return <motion.div animate={{ opacity: 1 }}>Content</motion.div>
}
```

**App Router** (Next.js 13+):
```tsx
// MUST add "use client" directive
"use client"

import { motion } from "motion/react-client" // Optimized import

export default function Page() {
  return <motion.div animate={{ opacity: 1 }}>Content</motion.div>
}
```

---

## App Router (Next.js 13, 14, 15)

### Key Requirement: Client Components Only

Motion uses browser APIs (DOM, window, events) that **don't exist on the server**. Therefore, Motion only works in **Client Components**, not Server Components.

---

### Pattern 1: Direct Client Component (Simplest)

Add `"use client"` directive at the top of any file using Motion:

**File**: `src/app/page.tsx`
```tsx
"use client"

import { motion } from "motion/react-client" // Optimized for Next.js

export default function Page() {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
    >
      Welcome to Next.js + Motion
    </motion.div>
  )
}
```

**Pros**:
- ✅ Simple, straightforward
- ✅ Works immediately

**Cons**:
- ❌ Entire page becomes client-rendered
- ❌ Loses Server Component benefits (streaming, server-side data fetching)

---

### Pattern 2: Wrapper Component (Recommended)

Create a reusable Client Component wrapper to avoid repeating `"use client"`:

**File**: `src/components/motion-client.tsx`
```tsx
"use client"

// Optimized import for Next.js (reduces client JS)
import * as motion from "motion/react-client"

export { motion }

// Also export commonly used components/hooks
export {
  AnimatePresence,
  MotionConfig,
  LazyMotion,
  LayoutGroup,
  useMotionValue,
  useTransform,
  useScroll,
  useSpring,
  useAnimate,
  useInView,
} from "motion/react-client"
```

**File**: `src/app/page.tsx` (Server Component)
```tsx
import { motion } from "@/components/motion-client"

export default function Page() {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
    >
      This page is a Server Component!
      Motion wrapper is a Client Component.
    </motion.div>
  )
}
```

**Pros**:
- ✅ Server Components can use Motion via wrapper
- ✅ Only Motion components are client-rendered
- ✅ Cleaner imports (no need to repeat `"use client"`)

**Cons**:
- ❌ Slight indirection (one extra file)

---

### Pattern 3: Server Data + Client Animation

Fetch data in Server Component, animate in Client Component:

**File**: `src/components/AnimatedCard.tsx` (Client Component)
```tsx
"use client"

import { motion } from "motion/react-client"

interface Product {
  id: number
  name: string
  price: number
}

export function AnimatedCard({ product, index }: { product: Product; index: number }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ delay: index * 0.1 }} // Stagger
      whileHover={{ scale: 1.05 }}
      className="p-4 bg-white border rounded-lg"
    >
      <h3 className="font-bold">{product.name}</h3>
      <p className="text-gray-600">${product.price}</p>
    </motion.div>
  )
}
```

**File**: `src/app/products/page.tsx` (Server Component)
```tsx
import { AnimatedCard } from "@/components/AnimatedCard"

async function getProducts() {
  const res = await fetch('https://api.example.com/products', {
    cache: 'force-cache' // Server-side caching
  })
  return res.json()
}

export default async function ProductsPage() {
  const products = await getProducts() // Server-side fetch

  return (
    <div className="grid grid-cols-3 gap-4">
      {products.map((product, index) => (
        <AnimatedCard key={product.id} product={product} index={index} />
      ))}
    </div>
  )
}
```

**Pros**:
- ✅ Data fetched on server (SEO, performance, security)
- ✅ Animations run on client (interactivity)
- ✅ Best of both worlds

**Cons**:
- ❌ Requires splitting into two components

---

### Pattern 4: MotionConfig Provider

Wrap app in MotionConfig for global settings (reduced motion, transitions):

**File**: `src/components/MotionProvider.tsx` (Client Component)
```tsx
"use client"

import { MotionConfig } from "motion/react-client"
import { ReactNode } from "react"

export function MotionProvider({ children }: { children: ReactNode }) {
  return (
    <MotionConfig reducedMotion="user">
      {children}
    </MotionConfig>
  )
}
```

**File**: `src/app/layout.tsx` (Root Layout)
```tsx
import { MotionProvider } from "@/components/MotionProvider"

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en">
      <body>
        <MotionProvider>
          {children}
        </MotionProvider>
      </body>
    </html>
  )
}
```

**Respects**: macOS/Windows/iOS/Android "Reduce Motion" accessibility setting

---

## Pages Router (Next.js 12)

### No Special Setup Required

Motion works out of the box with Pages Router:

**File**: `pages/index.tsx`
```tsx
import { motion } from "motion/react"

export default function HomePage() {
  return (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
    >
      Welcome
    </motion.div>
  )
}
```

**No `"use client"` needed** - Pages Router renders on client by default.

---

### Server-Side Rendering (SSR)

Motion is **client-only**, so you may see hydration warnings in development. This is expected and can be ignored.

**If you see hydration errors**:

**Option 1: Dynamic Import**
```tsx
import dynamic from 'next/dynamic'

const AnimatedComponent = dynamic(
  () => import('@/components/AnimatedComponent'),
  { ssr: false }
)

export default function Page() {
  return <AnimatedComponent />
}
```

**Option 2: Conditional Rendering**
```tsx
import { useState, useEffect } from 'react'
import { motion } from 'motion/react'

export default function Page() {
  const [isClient, setIsClient] = useState(false)

  useEffect(() => {
    setIsClient(true)
  }, [])

  if (!isClient) {
    return <div>Loading...</div>
  }

  return <motion.div animate={{ opacity: 1 }}>Content</motion.div>
}
```

---

## Known Issues

### Issue 1: Next.js 15 + React 19 Compatibility

**Status**: Most issues resolved in latest Motion version (12.23.24)

**Symptoms**:
- Build errors: "unsupported to use 'export *' in a client boundary"
- Runtime errors with Server Components

**Solution**: Update to latest versions:
```bash
pnpm add motion@latest react@latest next@latest
```

**If issues persist**: Check GitHub for updates: https://github.com/motiondivision/motion/issues

---

### Issue 2: AnimatePresence with Soft Navigation

**Problem**: Exit animations don't work when navigating between pages in App Router.

**Why**: Next.js soft navigation doesn't trigger React unmount, so AnimatePresence doesn't detect exit.

**Solutions**:

**Option 1: Component-level AnimatePresence** (Recommended)
```tsx
// Use AnimatePresence for modals, dropdowns, tooltips
<AnimatePresence>
  {isOpen && (
    <motion.div
      initial={{ opacity: 0 }}
      animate={{ opacity: 1 }}
      exit={{ opacity: 0 }}
    >
      Modal content
    </motion.div>
  )}
</AnimatePresence>
```

**Option 2: template.tsx** (Experimental)
```tsx
// src/app/template.tsx
"use client"

import { motion } from "motion/react-client"

export default function Template({ children }: { children: ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, x: 20 }}
      animate={{ opacity: 1, x: 0 }}
      transition={{ duration: 0.3 }}
    >
      {children}
    </motion.div>
  )
}
```

**Note**: `template.tsx` creates new instances on navigation, enabling enter animations but not exit animations.

**Option 3: Middleware approach**
Use Next.js middleware to detect route changes and trigger animations manually. Complex, not recommended.

**Recommendation**: Accept that page-level exit animations don't work reliably in App Router. Use AnimatePresence for component-level animations (modals, dropdowns, etc.) where it works perfectly.

---

### Issue 3: Reorder Component Incompatibility

**Problem**: Motion's `<Reorder>` component doesn't work with Next.js routing.

**Symptoms**:
- Random stuck states
- Items don't reorder
- Console errors

**GitHub Issues**: #2183, #2101

**Solution**: Use alternative drag-to-reorder implementations:
- `@dnd-kit/core` (recommended)
- `react-beautiful-dnd`
- Manual implementation with `drag` prop

**Example with `drag` prop**:
```tsx
<motion.div
  drag="y"
  dragConstraints={{ top: 0, bottom: 0 }}
  whileDrag={{ scale: 1.05 }}
>
  Draggable item
</motion.div>
```

---

### Issue 4: Large Bundle Size

**Problem**: Full Motion component adds ~34 KB to client bundle.

**Solution**: Use optimized import and LazyMotion:

**File**: `src/components/motion-client.tsx`
```tsx
"use client"

import { LazyMotion, domAnimation } from "motion/react-client"
import { ReactNode } from "react"

export function MotionProvider({ children }: { children: ReactNode }) {
  return (
    <LazyMotion features={domAnimation}>
      {children}
    </LazyMotion>
  )
}

// Export 'm' component instead of 'motion'
export { m as motion } from "motion/react-client"
```

**Reduces bundle from 34 KB → 4.6 KB**

See `performance-optimization.md` for full guide.

---

### Issue 5: Reduced Motion Not Affecting AnimatePresence

**Problem**: MotionConfig reducedMotion prop doesn't disable AnimatePresence animations.

**GitHub Issue**: #1567

**Workaround**: Manual check:
```tsx
"use client"

import { useState, useEffect } from "react"
import { motion, AnimatePresence } from "motion/react-client"

export function Modal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) {
  const [prefersReducedMotion, setPrefersReducedMotion] = useState(false)

  useEffect(() => {
    const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)")
    setPrefersReducedMotion(mediaQuery.matches)

    const handleChange = () => setPrefersReducedMotion(mediaQuery.matches)
    mediaQuery.addEventListener("change", handleChange)
    return () => mediaQuery.removeEventListener("change", handleChange)
  }, [])

  return (
    <AnimatePresence>
      {isOpen && (
        <motion.div
          initial={{ opacity: prefersReducedMotion ? 1 : 0 }}
          animate={{ opacity: 1 }}
          exit={{ opacity: prefersReducedMotion ? 1 : 0 }}
          transition={{ duration: prefersReducedMotion ? 0 : 0.3 }}
        >
          Modal content
        </motion.div>
      )}
    </AnimatePresence>
  )
}
```

---

## Performance Optimization for Next.js

### 1. Use `motion/react-client` Import

**Regular import** (full bundle):
```tsx
import { motion } from "motion/react"
```

**Optimized import** (smaller bundle):
```tsx
import { motion } from "motion/react-client"
```

**Difference**: `react-client` variant excludes server-side code, reducing client JavaScript.

---

### 2. Code Splitting with Dynamic Imports

For animations not needed on initial load:

```tsx
import dynamic from 'next/dynamic'

const AnimatedHero = dynamic(
  () => import('@/components/AnimatedHero'),
  { ssr: false }
)

export default function HomePage() {
  return <AnimatedHero />
}
```

**Benefits**:
- ✅ Reduces initial JavaScript bundle
- ✅ Loads animation code only when needed

---

### 3. LazyMotion for Smaller Bundle

**Setup**:
```tsx
"use client"

import { LazyMotion, domAnimation, m } from "motion/react-client"

export default function Layout({ children }: { children: ReactNode }) {
  return (
    <LazyMotion features={domAnimation}>
      {children}
    </LazyMotion>
  )
}
```

**Then use `m` instead of `motion`**:
```tsx
"use client"

import { m } from "motion/react-client"

export function Component() {
  return <m.div animate={{ opacity: 1 }}>Content</m.div>
}
```

**Bundle size**: 34 KB → 4.6 KB

---

### 4. Optimize Images with Next/Image

Combine Motion with Next.js Image optimization:

```tsx
"use client"

import { motion } from "motion/react-client"
import Image from "next/image"

export function AnimatedImage() {
  return (
    <motion.div whileHover={{ scale: 1.05 }}>
      <Image
        src="/hero.jpg"
        width={1200}
        height={600}
        alt="Hero"
        priority
      />
    </motion.div>
  )
}
```

**Benefits**:
- ✅ Automatic image optimization
- ✅ Smooth animations

---

## Testing & Debugging

### 1. Verify Client Component Boundary

**Problem**: Accidentally using Motion in Server Component.

**Check**:
1. Look for `"use client"` directive at top of file
2. If using wrapper, verify wrapper has `"use client"`

**Error message**:
```
Error: motion is not defined
```

**Fix**: Add `"use client"` to the file.

---

### 2. Check Bundle Size

**Analyze**:
```bash
pnpm build

# Then check .next/analyze or use @next/bundle-analyzer
```

**Target**: Motion should be <5 KB (with LazyMotion)

**If larger**: Switch to LazyMotion or useAnimate mini.

---

### 3. Test Reduced Motion

**Enable in OS**:
- **macOS**: System Settings → Accessibility → Display → Reduce motion
- **Windows**: Settings → Ease of Access → Display → Show animations
- **iOS**: Settings → Accessibility → Motion
- **Android 9+**: Settings → Accessibility → Remove animations

**Verify**: Animations should be instant (no transitions).

---

### 4. Lighthouse Performance

**Run**:
```bash
pnpm build
pnpm start
# Open Chrome DevTools → Lighthouse → Run analysis
```

**Target Scores**:
- Performance: >90
- Accessibility: 100

**If low performance**: Check for:
- Large bundle size (optimize with LazyMotion)
- Too many animated elements (use virtualization)
- Non-accelerated animations (use transform, not width/height)

---

## Deployment Checklist

Before deploying Next.js + Motion:

- [ ] All Motion files have `"use client"` directive (App Router)
- [ ] Using `motion/react-client` import (not `motion/react`)
- [ ] LazyMotion enabled (if bundle size matters)
- [ ] MotionConfig with reducedMotion set up
- [ ] No Motion usage in Server Components
- [ ] AnimatePresence only for component-level animations (not routes)
- [ ] Images optimized with next/image
- [ ] Tested with prefers-reduced-motion enabled
- [ ] Bundle analyzed (<5 KB for Motion recommended)
- [ ] Lighthouse performance score >90

---

## Quick Reference

### App Router

| Task | Solution |
|------|----------|
| **Use Motion** | Add `"use client"` to file |
| **Optimize import** | `import from "motion/react-client"` |
| **Reduce bundle** | Use LazyMotion |
| **Global config** | Wrap in MotionProvider |
| **Server data + animation** | Fetch in Server Component, animate in Client Component |
| **Route transitions** | Not reliable, use component-level only |

### Pages Router

| Task | Solution |
|------|----------|
| **Use Motion** | Just import and use (no setup) |
| **Avoid hydration errors** | Use dynamic import with `ssr: false` |
| **Optimize bundle** | Use LazyMotion |

---

## Getting Help

- **Next.js Docs**: https://nextjs.org/docs
- **Motion Docs**: https://motion.dev/docs/react
- **Motion + Next.js Issues**: https://github.com/motiondivision/motion/issues
- **Stack Overflow**: Tag: `framer-motion` + `next.js`

---

**Key Takeaway**: For App Router, always use `"use client"` and `motion/react-client` import. For Pages Router, it just works. Use LazyMotion to keep bundle size small.
references/performance-optimization.md
# Motion Performance Optimization

This guide covers techniques to optimize Motion animations for production, including bundle size reduction, runtime performance improvements, and best practices.

---

## Bundle Size Optimization

### Problem: Full Motion Component is 34 KB

The full `motion` component includes all animation features, resulting in ~34 KB minified+gzipped. For many use cases, this is overkill.

---

### Solution 1: LazyMotion (Recommended)

**Reduces bundle from 34 KB → 4.6 KB**

LazyMotion loads animation features on-demand instead of bundling everything upfront.

**Setup**:
```tsx
import { LazyMotion, domAnimation, m } from "motion/react"

function App() {
  return (
    <LazyMotion features={domAnimation}>
      {/* Use 'm' instead of 'motion' */}
      <m.div
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        whileHover={{ scale: 1.1 }}
      >
        Content
      </m.div>
    </LazyMotion>
  )
}
```

**Key changes**:
1. Wrap app in `<LazyMotion features={domAnimation}>`
2. Use `m` component instead of `motion`
3. Import `m` from `motion/react` (not `motion`)

**Features included with `domAnimation`**:
- ✅ Transform animations (x, y, scale, rotate, etc.)
- ✅ Opacity animations
- ✅ Gestures (hover, tap, drag, pan)
- ✅ Layout animations
- ✅ useScroll, useTransform hooks
- ❌ SVG path animations (use `domMax` instead)
- ❌ Custom value types

**When to use `domMax` instead of `domAnimation`**:
```tsx
import { LazyMotion, domMax, m } from "motion/react"

<LazyMotion features={domMax}>
  {/* Now includes SVG path animations */}
  <m.path d="..." animate={{ pathLength: 1 }} />
</LazyMotion>
```

**Bundle size**: domAnimation (4.6 KB), domMax (~6 KB)

---

### Solution 2: useAnimate Mini (Smallest)

**Reduces bundle from 34 KB → 2.3 KB**

The `useAnimate` mini variant is the smallest React animation library available. Use for imperative animations.

**Setup**:
```tsx
import { useAnimate } from "motion/react"
import { useEffect } from "react"

function Component() {
  const [scope, animate] = useAnimate()

  useEffect(() => {
    animate(scope.current, { opacity: 1, x: 0 })
  }, [])

  return (
    <div ref={scope} style={{ opacity: 0, transform: "translateX(-20px)" }}>
      Content
    </div>
  )
}
```

**Pros**:
- ✅ Smallest bundle size (2.3 KB)
- ✅ Imperative API (full control)
- ✅ No component overhead

**Cons**:
- ❌ More verbose than declarative API
- ❌ Less ergonomic for complex animations

**When to use**:
- Bundle size is absolutely critical
- You prefer imperative animations
- Simple animations (fade in, slide in, etc.)

---

### Solution 3: useAnimate Hybrid

**Reduces bundle from 34 KB → 17 KB**

The hybrid version includes more features than mini but less than full.

**Setup**:
```tsx
import { useAnimate, stagger } from "motion/react"

function Component() {
  const [scope, animate] = useAnimate()

  const handleAnimate = () => {
    animate("li", { opacity: 1, x: 0 }, { delay: stagger(0.1) })
  }

  return (
    <ul ref={scope}>
      {items.map(item => <li key={item.id}>{item.text}</li>)}
    </ul>
  )
}
```

**Bundle size**: 17 KB

---

### Solution 4: Remove Unused Features

If you only use Motion for specific features, import only what you need:

**Example - Only scroll animations**:
```tsx
import { useScroll, useTransform } from "motion/react"
import { useRef } from "react"

function Component() {
  const ref = useRef(null)
  const { scrollYProgress } = useScroll({ container: ref })
  const y = useTransform(scrollYProgress, [0, 1], [0, -100])

  return (
    <div ref={ref} style={{ transform: `translateY(${y}px)` }}>
      Content
    </div>
  )
}
```

**No `motion` component imported** → smaller bundle

---

### Bundle Size Comparison

| Approach | Bundle Size | Features | Best For |
|----------|-------------|----------|----------|
| **Full motion** | 34 KB | All features | Kitchen sink approach |
| **LazyMotion + domAnimation** | 4.6 KB | Most features | **Recommended default** |
| **LazyMotion + domMax** | ~6 KB | All features except custom | SVG animations |
| **useAnimate hybrid** | 17 KB | Imperative + stagger | Imperative animations |
| **useAnimate mini** | 2.3 KB | Basic imperative | **Smallest bundle** |
| **Hooks only** | <5 KB | Specific features | Scroll/transform only |

**Recommendation**: Start with LazyMotion + domAnimation (4.6 KB) for 90% of use cases.

---

## Runtime Performance Optimization

### 1. Add `willChange` for Transforms

**Problem**: Browser doesn't know which properties will animate, causing reflows.

**Solution**: Tell browser to optimize for animation.

```tsx
<motion.div
  style={{ willChange: "transform" }}
  animate={{ x: 100, rotate: 45 }}
/>
```

**Also add for**:
- `opacity`
- `backgroundColor`
- `clipPath`
- `filter`

**How it works**:
- Browser promotes element to its own layer
- Uses GPU compositing
- Avoids reflow/repaint during animation

**Warning**: Don't overuse! Only add to elements that actually animate.

---

### 2. Use Hardware-Accelerated Properties

**Good properties** (GPU-accelerated):
- ✅ `transform` (x, y, scale, rotate, skew)
- ✅ `opacity`
- ✅ `filter` (blur, brightness, etc.)

**Bad properties** (causes reflow):
- ❌ `width`, `height`
- ❌ `top`, `left`, `right`, `bottom`
- ❌ `padding`, `margin`

**Example - Wrong**:
```tsx
<motion.div
  animate={{ width: 300, height: 200 }} // ❌ Causes layout reflow
/>
```

**Example - Correct**:
```tsx
<motion.div
  animate={{ scale: 1.5 }} // ✅ GPU-accelerated transform
/>
```

**Rule**: Prefer `transform` over layout properties.

---

### 3. Use `layout` Prop for FLIP Animations

**Problem**: Animating width/height directly causes reflow.

**Solution**: Use `layout` prop for FLIP technique (First, Last, Invert, Play).

```tsx
<motion.div layout>
  {isExpanded ? <LargeContent /> : <SmallContent />}
</motion.div>
```

**How it works**:
1. Measures element before change (First)
2. Applies change immediately (Last)
3. Inverts with transform to match first position
4. Animates to last position (Play)

**Result**: Smooth animation without reflow, all via GPU-accelerated transforms.

---

### 4. Optimize Scroll Animations

Motion uses native ScrollTimeline API when available for hardware-accelerated scroll animations.

**Setup**:
```tsx
import { useScroll, useTransform } from "motion/react"

const { scrollYProgress } = useScroll()
const y = useTransform(scrollYProgress, [0, 1], [0, -300])

<motion.div style={{ y }}>
  Content
</motion.div>
```

**Performance**:
- ✅ Runs on compositor thread (not main thread)
- ✅ 120fps on capable devices
- ✅ No JavaScript execution on scroll

**Fallback**: On browsers without ScrollTimeline API, Motion falls back to JavaScript `requestAnimationFrame`.

---

### 5. Debounce Complex Calculations

For expensive calculations in scroll/transform hooks, debounce updates:

**Problem**:
```tsx
const { scrollY } = useScroll()

// Recalculates on every pixel scrolled
const complexValue = useTransform(scrollY, (value) => {
  return expensiveCalculation(value) // ❌ Too frequent
})
```

**Solution**:
```tsx
const { scrollY } = useScroll()

// Only update when scroll changes by 10px
const complexValue = useTransform(scrollY, (value) => {
  return Math.floor(value / 10) * 10
})
```

---

### 6. Use `transition` Type Wisely

Different transition types have different performance characteristics:

**Spring** (default):
```tsx
transition={{ type: "spring", stiffness: 100, damping: 10 }}
```
- ✅ Natural, physics-based motion
- ❌ More JavaScript calculation

**Tween**:
```tsx
transition={{ type: "tween", duration: 0.3, ease: "easeOut" }}
```
- ✅ Predictable timing
- ✅ Less JavaScript calculation
- ❌ Less natural than spring

**Recommendation**: Use tween for simple animations, spring for interactive gestures.

---

## Large Lists Optimization

### Problem: 50-100+ Animated Items Cause Severe Slowdown

Animating many items simultaneously overwhelms the browser.

---

### Solution 1: Virtualization (Recommended)

Only render visible items.

**Install**:
```bash
pnpm add react-window
# or
pnpm add react-virtuoso
# or
pnpm add @tanstack/react-virtual
```

**Example with react-window**:
```tsx
import { FixedSizeList } from 'react-window'
import { motion } from 'motion/react'

function VirtualizedList({ items }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {({ index, style }) => (
        <motion.div
          style={style}
          layout
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
        >
          {items[index].text}
        </motion.div>
      )}
    </FixedSizeList>
  )
}
```

**Benefits**:
- ✅ Only renders ~20 items at a time (instead of 1000+)
- ✅ Dramatically reduces DOM nodes
- ✅ Maintains smooth 60fps

**Drawback**: Slightly more complex setup

---

### Solution 2: Stagger with `delayChildren`

For moderately-sized lists (10-30 items), use stagger to spread out animations:

```tsx
const container = {
  hidden: { opacity: 0 },
  show: {
    opacity: 1,
    transition: {
      staggerChildren: 0.05, // 50ms delay between each child
      delayChildren: 0.1,    // 100ms delay before first child
    }
  }
}

const item = {
  hidden: { opacity: 0, y: 20 },
  show: { opacity: 1, y: 0 }
}

<motion.ul variants={container} initial="hidden" animate="show">
  {items.map(item => (
    <motion.li key={item.id} variants={item}>
      {item.text}
    </motion.li>
  ))}
</motion.ul>
```

**Benefits**:
- ✅ Avoids animating all items simultaneously
- ✅ Creates pleasing wave effect
- ✅ Reduces performance spike

**Limit**: Still struggles above ~50 items

---

### Solution 3: Lazy Load with `whileInView`

Only animate items when they enter viewport:

```tsx
{items.map(item => (
  <motion.div
    key={item.id}
    initial={{ opacity: 0, y: 20 }}
    whileInView={{ opacity: 1, y: 0 }}
    viewport={{ once: true, margin: "-100px" }}
  >
    {item.content}
  </motion.div>
))}
```

**Benefits**:
- ✅ Only animates visible items
- ✅ Spreads performance cost over time
- ✅ No virtualization library needed

**Drawback**: Doesn't help with initial render if all items visible

---

### Solution 4: Simplify Animations

For very large lists, simplify or remove animations:

```tsx
const useReducedAnimations = items.length > 50

<motion.div
  initial={{ opacity: useReducedAnimations ? 1 : 0 }}
  animate={{ opacity: 1 }}
  transition={{ duration: useReducedAnimations ? 0 : 0.3 }}
>
  {item.content}
</motion.div>
```

---

### Performance Comparison (1000 Items)

| Approach | FPS | DOM Nodes | User Experience |
|----------|-----|-----------|-----------------|
| **No optimization** | 5-10 fps | 1000+ | Unusable, browser hangs |
| **Stagger only** | 15-20 fps | 1000+ | Laggy, still poor |
| **Virtualization** | 60 fps | ~20 | Smooth, production-ready |
| **whileInView** | 40-50 fps | 1000+ | Acceptable for long lists |
| **Simplified animations** | 50-60 fps | 1000+ | Smooth but less polished |

**Recommendation**: Use virtualization for lists with 50+ items.

---

## AnimatePresence Optimization

### 1. Use `mode` Prop

**Wait mode** (sequential):
```tsx
<AnimatePresence mode="wait">
  {isVisible && <motion.div key="content">Content</motion.div>}
</AnimatePresence>
```

- Waits for exit animation to complete before entering
- Prevents both elements from being in DOM simultaneously
- Better performance

**Sync mode** (simultaneous):
```tsx
<AnimatePresence mode="sync">
  {isVisible && <motion.div key="content">Content</motion.div>}
</AnimatePresence>
```

- Enter and exit happen simultaneously
- More DOM nodes temporarily
- Higher performance cost

**Recommendation**: Use `mode="wait"` for modals/dialogs, `mode="sync"` for crossfades.

---

### 2. Limit AnimatePresence Usage

**Problem**: Wrapping entire app in AnimatePresence adds overhead.

**Solution**: Only wrap components that actually exit:

**Bad**:
```tsx
<AnimatePresence>
  <Layout>
    <StaticHeader />
    <DynamicContent />
    <StaticFooter />
  </Layout>
</AnimatePresence>
```

**Good**:
```tsx
<Layout>
  <StaticHeader />
  <AnimatePresence>
    <DynamicContent />
  </AnimatePresence>
  <StaticFooter />
</Layout>
```

---

## Layout Animation Performance

### 1. Use `layoutId` for Shared Elements

**Problem**: Multiple separate layout animations calculated independently.

**Solution**: Use `layoutId` to connect related elements:

```tsx
// Card view
<motion.div layoutId={card.id}>
  <CardPreview />
</motion.div>

// Detail view
<motion.div layoutId={card.id}>
  <CardDetail />
</motion.div>
```

**Performance**: Motion knows these are the same element, optimizes FLIP calculation.

---

### 2. Add `layoutRoot` for Fixed Elements

**Problem**: Fixed-position elements cause expensive layout calculations.

**Solution**: Mark as layout root to isolate calculations:

```tsx
<motion.div
  layoutRoot
  layout
  className="fixed top-0 left-0"
>
  Fixed content
</motion.div>
```

---

### 3. Add `layoutScroll` for Scrollable Containers

**Problem**: Layout animations in scrolled containers are incomplete/broken.

**Solution**: Add `layoutScroll` to account for scroll offset:

```tsx
<motion.div
  layoutScroll
  className="overflow-auto h-96"
>
  {items.map(item => (
    <motion.div key={item.id} layout>
      {item.content}
    </motion.div>
  ))}
</motion.div>
```

---

## Gesture Performance

### 1. Use `dragMomentum={false}` When Not Needed

**Problem**: Momentum calculations add overhead.

**Solution**: Disable if you don't need inertia:

```tsx
<motion.div
  drag
  dragMomentum={false} // Disable inertia
>
  Drag me
</motion.div>
```

**Use momentum** for: Carousels, swiping
**Disable momentum** for: Precise positioning, drag-to-reorder

---

### 2. Set `dragElastic` Lower

**Problem**: Higher elasticity = more calculations.

**Solution**: Use 0-0.2 for most cases:

```tsx
<motion.div
  drag
  dragElastic={0.1} // Low elasticity
>
  Drag me
</motion.div>
```

**Default**: 0.5 (high)
**Recommended**: 0.1-0.2 (medium)
**Performance mode**: 0 (none)

---

## Measuring Performance

### 1. React DevTools Profiler

**Enable**:
1. Install React DevTools
2. Open "Profiler" tab
3. Click record
4. Trigger animations
5. Stop recording

**Look for**:
- Flame graph spikes during animations
- Components re-rendering unnecessarily
- Long commit times

---

### 2. Browser Performance Tab

**Enable**:
1. Open Chrome DevTools
2. "Performance" tab
3. Click record
4. Trigger animations
5. Stop recording

**Look for**:
- Frame rate (should be 60fps or 120fps)
- Long JavaScript tasks (should be <16ms)
- Layout reflows (should be minimal)
- Paint operations (should be green, not red)

---

### 3. Frame Rate Monitor

Add visual FPS counter during development:

```tsx
import { useState, useEffect } from "react"

function FPSMonitor() {
  const [fps, setFps] = useState(0)

  useEffect(() => {
    let lastTime = performance.now()
    let frames = 0

    function loop() {
      frames++
      const now = performance.now()
      if (now >= lastTime + 1000) {
        setFps(Math.round((frames * 1000) / (now - lastTime)))
        lastTime = now
        frames = 0
      }
      requestAnimationFrame(loop)
    }

    loop()
  }, [])

  return (
    <div className="fixed top-4 right-4 bg-black text-white p-2 rounded text-sm">
      {fps} FPS
    </div>
  )
}
```

**Target**: 60 FPS (or 120 FPS on high-refresh displays)

---

## Production Checklist

Before deploying Motion animations, verify:

- [ ] Bundle size optimized (LazyMotion or useAnimate)
- [ ] `willChange` added for animated transforms
- [ ] Only GPU-accelerated properties used (transform, opacity)
- [ ] `layout` prop used instead of animating width/height directly
- [ ] Large lists use virtualization (50+ items)
- [ ] Scroll animations use `whileInView` or `useScroll`
- [ ] AnimatePresence only wraps necessary components
- [ ] `mode="wait"` used for modals (if applicable)
- [ ] `layoutScroll` added to scrollable containers
- [ ] `layoutRoot` added to fixed elements
- [ ] Tested on low-end devices (throttle CPU in DevTools)
- [ ] Tested with `prefers-reduced-motion` enabled
- [ ] Frame rate verified (60fps minimum)
- [ ] No console warnings from Motion

---

## Performance Budget

Recommended limits for production:

| Metric | Target | Maximum |
|--------|--------|---------|
| **Bundle size (Motion)** | <5 KB | 10 KB |
| **Total JavaScript** | <200 KB | 300 KB |
| **Frame rate** | 60 FPS | 40 FPS minimum |
| **Animated elements (simultaneous)** | <20 | <50 |
| **AnimatePresence wrappers** | <5 | <10 |
| **Layout animations (simultaneous)** | <10 | <20 |

---

## Common Performance Anti-Patterns

### ❌ Anti-Pattern 1: Animating All List Items on Mount

```tsx
{items.map(item => (
  <motion.div
    key={item.id}
    initial={{ opacity: 0 }}
    animate={{ opacity: 1 }} // ❌ All 100 items animate at once
  >
    {item.content}
  </motion.div>
))}
```

**Fix**: Use stagger or `whileInView`:
```tsx
const container = {
  hidden: {},
  show: {
    transition: { staggerChildren: 0.05 }
  }
}

<motion.div variants={container} initial="hidden" animate="show">
  {items.map(item => (
    <motion.div key={item.id} variants={item}>
      {item.content}
    </motion.div>
  ))}
</motion.div>
```

---

### ❌ Anti-Pattern 2: No `key` Props with AnimatePresence

```tsx
<AnimatePresence>
  {items.map(item => (
    <motion.div> {/* ❌ Missing key */}
      {item.content}
    </motion.div>
  ))}
</AnimatePresence>
```

**Fix**: Always add unique `key` props:
```tsx
<AnimatePresence>
  {items.map(item => (
    <motion.div key={item.id}> {/* ✅ Unique key */}
      {item.content}
    </motion.div>
  ))}
</AnimatePresence>
```

---

### ❌ Anti-Pattern 3: Animating Non-Accelerated Properties

```tsx
<motion.div
  animate={{ width: 300, top: 100 }} // ❌ Causes reflow
/>
```

**Fix**: Use transforms:
```tsx
<motion.div
  animate={{ scale: 1.5, y: 100 }} // ✅ GPU-accelerated
/>
```

---

### ❌ Anti-Pattern 4: Full Motion Bundle for Simple Use Case

```tsx
import { motion } from "motion/react" // ❌ 34 KB for simple fade

<motion.div animate={{ opacity: 1 }}>Content</motion.div>
```

**Fix**: Use LazyMotion or useAnimate:
```tsx
import { LazyMotion, domAnimation, m } from "motion/react"

<LazyMotion features={domAnimation}>
  <m.div animate={{ opacity: 1 }}>Content</m.div>
</LazyMotion>
```

---

## Getting Help

- **Performance Issues**: https://github.com/motiondivision/motion/issues
- **Bundle Size Analysis**: https://bundlephobia.com/package/motion
- **Official Optimization Guide**: https://motion.dev/docs/react-reduce-bundle-size

---

**Key Takeaway**: For 90% of use cases, use **LazyMotion + domAnimation** (4.6 KB) and follow the optimization checklist above. This provides excellent performance while maintaining a small bundle size.
rules/motion.md
---
paths: "**/*.ts", "**/*.tsx", "**/motion*.ts", "**/animate*.ts"
---

# Motion (formerly Framer Motion) Corrections

Claude's training may reference Framer Motion. The library was **renamed to Motion** in late 2024.

## Package Name Change

```bash
# ❌ Old package name
npm install framer-motion

# ✅ New package name (2024+)
npm install motion
```

## Import Changes

```typescript
/* ❌ Old imports */
import { motion, AnimatePresence } from 'framer-motion'

/* ✅ New imports */
import { motion, AnimatePresence } from 'motion/react'
```

## React 19 Compatibility

```typescript
/* ❌ May cause issues with React 19 */
import { motion } from 'framer-motion'

/* ✅ motion/react is React 19 compatible */
import { motion } from 'motion/react'
```

## Variants Pattern (Unchanged)

```typescript
/* ✅ Same API, just new import */
import { motion } from 'motion/react'

const variants = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0 },
}

<motion.div
  initial="hidden"
  animate="visible"
  variants={variants}
/>
```

## useAnimation Hook

```typescript
/* ❌ Old import */
import { useAnimation } from 'framer-motion'

/* ✅ New import */
import { useAnimation } from 'motion/react'

const controls = useAnimation()
await controls.start({ opacity: 1 })
```

## Layout Animations

```typescript
/* ❌ Old import */
import { LayoutGroup } from 'framer-motion'

/* ✅ New import */
import { LayoutGroup } from 'motion/react'

<LayoutGroup>
  <motion.div layout />
</LayoutGroup>
```

## Quick Fixes

| If Claude suggests... | Use instead... |
|----------------------|----------------|
| `npm install framer-motion` | `npm install motion` |
| `from 'framer-motion'` | `from 'motion/react'` |
| `import { motion }` from framer-motion | `import { motion } from 'motion/react'` |
| `motion` package without `/react` | Use `motion/react` for React components |
scripts/init-motion.sh
#!/bin/bash
# Motion Setup Script
# Automates installation and initial setup for React + Vite + Next.js projects

set -e

echo "🎬 Motion Setup"
echo "==============="
echo ""

# Check if package.json exists
if [ ! -f "package.json" ]; then
  echo "❌ Error: package.json not found"
  echo "   Run this script from your project root"
  exit 1
fi

# Detect framework
FRAMEWORK="unknown"
USING_NEXTJS=false
USING_VITE=false
USING_CLOUDFLARE=false

if grep -q '"next"' package.json; then
  FRAMEWORK="Next.js"
  USING_NEXTJS=true
  echo "✅ Detected: Next.js project"
elif grep -q '"vite"' package.json; then
  FRAMEWORK="Vite"
  USING_VITE=true
  echo "✅ Detected: Vite project"
else
  echo "⚠️  Could not detect framework (Next.js or Vite)"
  echo "   Will proceed with generic setup"
fi

# Check for Cloudflare Workers
if grep -q '@cloudflare/vite-plugin\|wrangler' package.json; then
  USING_CLOUDFLARE=true
  echo "🔍 Detected: Cloudflare Workers project"
  echo ""
  echo "⚠️  WARNING: Motion has build compatibility issues with Wrangler"
  echo "   Recommendation: Use framer-motion v12.23.24 instead"
  echo ""
  read -p "Continue with Motion (may cause build errors) or use framer-motion? [motion/framer]: " choice
  if [ "$choice" = "framer" ]; then
    PACKAGE="framer-motion"
  else
    PACKAGE="motion"
  fi
else
  PACKAGE="motion"
fi

echo ""

# Detect package manager
if command -v pnpm &> /dev/null; then
  PKG_MANAGER="pnpm"
elif command -v yarn &> /dev/null; then
  PKG_MANAGER="yarn"
else
  PKG_MANAGER="npm"
fi

# Install Motion
echo "📦 Installing $PACKAGE using $PKG_MANAGER..."
if [ "$PKG_MANAGER" = "pnpm" ]; then
  pnpm add $PACKAGE
elif [ "$PKG_MANAGER" = "yarn" ]; then
  yarn add $PACKAGE
else
  npm install $PACKAGE
fi

echo "✅ Package installed"
echo ""

# Create directories
mkdir -p src/components
mkdir -p src/hooks

# Next.js specific setup
if [ "$USING_NEXTJS" = true ]; then
  echo "📝 Creating Next.js App Router Client Component wrapper..."

  # Create motion-client.tsx wrapper
  cat > src/components/motion-client.tsx << 'EOF'
"use client"

// Optimized import for Next.js (reduces client JS bundle)
import * as motion from "motion/react-client"

export { motion }

// Also export commonly used components
export {
  AnimatePresence,
  MotionConfig,
  LazyMotion,
  LayoutGroup,
  useMotionValue,
  useTransform,
  useScroll,
  useSpring,
  useAnimate,
  useInView,
  useDragControls,
} from "motion/react-client"
EOF

  echo "✅ Created: src/components/motion-client.tsx"
  echo ""

  # Create MotionProvider
  cat > src/components/MotionProvider.tsx << 'EOF'
"use client"

import { MotionConfig } from "motion/react-client"
import { ReactNode } from "react"

export function MotionProvider({ children }: { children: ReactNode }) {
  return (
    <MotionConfig reducedMotion="user">
      {children}
    </MotionConfig>
  )
}
EOF

  echo "✅ Created: src/components/MotionProvider.tsx"
  echo ""

  # Create example component
  cat > src/components/AnimatedButton.tsx << 'EOF'
"use client"

import { motion } from "motion/react-client"

export function AnimatedButton() {
  return (
    <motion.button
      whileHover={{ scale: 1.1 }}
      whileTap={{ scale: 0.95 }}
      className="px-6 py-3 bg-blue-600 text-white rounded-lg font-semibold"
    >
      Hover and Click Me
    </motion.button>
  )
}
EOF

  echo "✅ Created: src/components/AnimatedButton.tsx"
  echo ""
fi

# Vite specific setup
if [ "$USING_VITE" = true ]; then
  echo "📝 Creating Vite example component..."

  cat > src/components/AnimatedExample.tsx << 'EOF'
import { motion } from "motion/react"

export function AnimatedExample() {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.5 }}
      className="p-6 bg-blue-100 rounded-lg"
    >
      <h2 className="text-xl font-bold">Motion Animation</h2>
      <p>This component fades in and slides up on mount.</p>
    </motion.div>
  )
}
EOF

  echo "✅ Created: src/components/AnimatedExample.tsx"
  echo ""
fi

# Summary
echo "✨ Setup complete!"
echo ""
echo "Next steps:"
echo ""

if [ "$USING_NEXTJS" = true ]; then
  echo "1. Add MotionProvider to your root layout:"
  echo "   // src/app/layout.tsx"
  echo "   import { MotionProvider } from '@/components/MotionProvider'"
  echo "   "
  echo "   export default function RootLayout({ children }) {"
  echo "     return ("
  echo "       <html>"
  echo "         <body>"
  echo "           <MotionProvider>"
  echo "             {children}"
  echo "           </MotionProvider>"
  echo "         </body>"
  echo "       </html>"
  echo "     )"
  echo "   }"
  echo ""
  echo "2. Use Motion in any component:"
  echo "   import { motion } from '@/components/motion-client'"
  echo "   "
  echo "   <motion.div animate={{ opacity: 1 }}>Content</motion.div>"
  echo ""
  echo "3. See example:"
  echo "   import { AnimatedButton } from '@/components/AnimatedButton'"
  echo ""
elif [ "$USING_VITE" = true ]; then
  echo "1. Import the example component:"
  echo "   import { AnimatedExample } from '@/components/AnimatedExample'"
  echo ""
  echo "2. Use it in your app:"
  echo "   <AnimatedExample />"
  echo ""
fi

echo "4. Check templates/ folder for more examples:"
echo "   - Modal dialogs"
echo "   - Accordions"
echo "   - Carousels"
echo "   - Scroll animations"
echo "   - Layout transitions"
echo ""

if [ "$USING_CLOUDFLARE" = true ]; then
  echo "⚠️  IMPORTANT (Cloudflare Workers):"
  echo "   Monitor GitHub issue #2918 for Motion + Wrangler compatibility"
  echo "   Consider using framer-motion v12.23.24 if build errors occur"
  echo ""
fi

echo "📚 Documentation:"
echo "   - Official: https://motion.dev/docs/react"
echo "   - Skill docs: ../SKILL.md"
echo ""
scripts/optimize-bundle.sh
#!/bin/bash
# Motion Bundle Optimizer
# Converts full motion component to LazyMotion for smaller bundle (34 KB → 4.6 KB)

set -e

echo "📦 Motion Bundle Optimizer"
echo "=========================="
echo ""

# Check if we're in a project with Motion
if [ ! -f "package.json" ]; then
  echo "❌ Error: package.json not found"
  exit 1
fi

if ! grep -q '"motion"\|"framer-motion"' package.json; then
  echo "❌ Error: Motion or Framer Motion not found in package.json"
  exit 1
fi

echo "✅ Motion detected"
echo ""

# Estimate current bundle impact
echo "📊 Current bundle impact: ~34 KB (full motion component)"
echo "📊 After optimization: ~4.6 KB (LazyMotion + domAnimation)"
echo "📊 Savings: ~29.4 KB (~86% reduction)"
echo ""

echo "⚠️  This script will:"
echo "   1. Show you how to set up LazyMotion"
echo "   2. Provide conversion examples"
echo "   3. NOT automatically modify your code (manual conversion required)"
echo ""

read -p "Continue? [y/N]: " -n 1 -r
echo ""
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
  exit 0
fi

echo ""
echo "===================="
echo "STEP 1: Add LazyMotion Provider"
echo "===================="
echo ""
echo "Create a LazyMotion wrapper component:"
echo ""

cat << 'EOF'
// src/components/MotionProvider.tsx (or app/providers.tsx for Next.js)
"use client" // Add this for Next.js App Router

import { LazyMotion, domAnimation } from "motion/react"
import { ReactNode } from "react"

export function MotionProvider({ children }: { children: ReactNode }) {
  return (
    <LazyMotion features={domAnimation}>
      {children}
    </LazyMotion>
  )
}
EOF

echo ""
echo "Then wrap your app:"
echo ""

cat << 'EOF'
// For Vite: src/main.tsx or src/App.tsx
import { MotionProvider } from "@/components/MotionProvider"

function App() {
  return (
    <MotionProvider>
      <YourApp />
    </MotionProvider>
  )
}

// For Next.js: app/layout.tsx
import { MotionProvider } from "@/components/MotionProvider"

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <MotionProvider>
          {children}
        </MotionProvider>
      </body>
    </html>
  )
}
EOF

echo ""
echo "===================="
echo "STEP 2: Convert Components"
echo "===================="
echo ""
echo "Change all 'motion' imports to 'm':"
echo ""

cat << 'EOF'
// BEFORE:
import { motion } from "motion/react"

<motion.div animate={{ x: 100 }} />

// AFTER:
import { m } from "motion/react"

<m.div animate={{ x: 100 }} />
EOF

echo ""
echo "===================="
echo "STEP 3: Find and Replace"
echo "===================="
echo ""
echo "Use find-and-replace in your editor:"
echo ""
echo "1. Find:    import { motion"
echo "   Replace: import { m"
echo ""
echo "2. Find:    from \"motion/react\""
echo "   Keep as is (or use motion/react-client for Next.js)"
echo ""
echo "3. Find:    <motion."
echo "   Replace: <m."
echo ""
echo "4. Find:    </motion."
echo "   Replace: </m."
echo ""

echo "===================="
echo "STEP 4: Verify Bundle Size"
echo "===================="
echo ""
echo "After converting, build your project and check bundle size:"
echo ""

cat << 'EOF'
# Vite
pnpm build
# Check dist/ folder size

# Next.js
pnpm build
# Check .next/ folder size or use @next/bundle-analyzer
EOF

echo ""
echo "Expected results:"
echo "  Before: ~34 KB for motion"
echo "  After:  ~4.6 KB for LazyMotion + domAnimation"
echo ""

echo "===================="
echo "STEP 5: Features Included"
echo "===================="
echo ""
echo "domAnimation includes:"
echo "  ✅ Transform animations (x, y, scale, rotate)"
echo "  ✅ Opacity animations"
echo "  ✅ Gestures (hover, tap, drag, pan)"
echo "  ✅ Layout animations"
echo "  ✅ useScroll, useTransform hooks"
echo "  ❌ SVG path animations (use domMax instead)"
echo ""
echo "If you need SVG animations, use domMax:"
echo ""

cat << 'EOF'
import { LazyMotion, domMax } from "motion/react"

<LazyMotion features={domMax}>
  {/* Now includes SVG */}
</LazyMotion>
EOF

echo ""
echo "Bundle size with domMax: ~6 KB (still better than 34 KB)"
echo ""

echo "===================="
echo "TROUBLESHOOTING"
echo "===================="
echo ""
echo "Issue: Animations not working after conversion"
echo "Solution: Verify LazyMotion wrapper is at root of component tree"
echo ""
echo "Issue: SVG animations not working"
echo "Solution: Use domMax instead of domAnimation"
echo ""
echo "Issue: Still seeing large bundle"
echo "Solution: Clear cache and rebuild"
echo ""

echo "===================="
echo "OPTIMIZATION COMPLETE"
echo "===================="
echo ""
echo "Summary:"
echo "  1. ✅ Wrap app in <LazyMotion features={domAnimation}>"
echo "  2. ✅ Change all motion.* to m.*"
echo "  3. ✅ Rebuild and verify bundle size"
echo ""
echo "Expected bundle reduction: ~86% (34 KB → 4.6 KB)"
echo ""
echo "📚 Full guide: ../references/performance-optimization.md"
echo ""
templates/layout-transitions.tsx
// Motion Layout Animations & Shared Element Transitions
// Production-tested with Motion v12.23.24, React 19

/**
 * INSTALLATION
 *
 * pnpm add motion
 *
 * USAGE
 *
 * Copy components below into your project.
 * Layout animations use the FLIP technique for smooth, hardware-accelerated transitions.
 *
 * For Next.js App Router, add "use client" directive at top of file.
 */

import { motion, LayoutGroup } from "motion/react"
import { useState, ReactNode } from "react"

// ============================================================================
// PATTERN 1: Basic Layout Animation
// ============================================================================

/**
 * Automatically animates layout changes (position, size, etc.)
 * Uses FLIP technique: First, Last, Invert, Play
 */
export function ExpandableCard() {
  const [isExpanded, setIsExpanded] = useState(false)

  return (
    <motion.div
      layout // Enable FLIP layout animations
      onClick={() => setIsExpanded(!isExpanded)}
      className={`cursor-pointer bg-white border rounded-lg shadow-md p-6 ${
        isExpanded ? "w-full" : "w-64"
      }`}
      transition={{ layout: { duration: 0.3, type: "spring" } }}
    >
      <motion.h3 layout className="text-xl font-bold mb-2">
        Click to Expand
      </motion.h3>

      {isExpanded && (
        <motion.div
          initial={{ opacity: 0 }}
          animate={{ opacity: 1 }}
          transition={{ delay: 0.2 }}
        >
          <p>This content appears when expanded.</p>
          <p className="mt-2">Layout automatically animates the size change.</p>
        </motion.div>
      )}
    </motion.div>
  )
}

/**
 * KEY CONCEPT: The `layout` prop tells Motion to automatically animate
 * any layout changes (width, height, position) using GPU-accelerated transforms.
 * No JavaScript calculation needed - it's all done via FLIP technique.
 */

// ============================================================================
// PATTERN 2: Shared Element Transition (layoutId)
// ============================================================================

/**
 * Animate the same element between different states or routes.
 * Perfect for card → detail page transitions.
 */
interface Card {
  id: string
  title: string
  description: string
  color: string
}

export function SharedElementExample() {
  const [selectedCard, setSelectedCard] = useState<Card | null>(null)

  const cards: Card[] = [
    { id: "1", title: "Card 1", description: "Full description for card 1", color: "bg-red-500" },
    { id: "2", title: "Card 2", description: "Full description for card 2", color: "bg-blue-500" },
    { id: "3", title: "Card 3", description: "Full description for card 3", color: "bg-green-500" },
  ]

  return (
    <div>
      {/* Grid view */}
      {!selectedCard && (
        <div className="grid grid-cols-3 gap-4">
          {cards.map((card) => (
            <motion.div
              key={card.id}
              layoutId={card.id} // Connect this element to detail view
              onClick={() => setSelectedCard(card)}
              className={`${card.color} text-white p-6 rounded-lg cursor-pointer h-32 flex items-center justify-center`}
            >
              <motion.h3 layoutId={`title-${card.id}`} className="text-xl font-bold">
                {card.title}
              </motion.h3>
            </motion.div>
          ))}
        </div>
      )}

      {/* Detail view */}
      {selectedCard && (
        <motion.div
          layoutId={selectedCard.id} // Same layoutId = smooth transition
          onClick={() => setSelectedCard(null)}
          className={`${selectedCard.color} text-white p-12 rounded-lg cursor-pointer`}
        >
          <motion.h3 layoutId={`title-${selectedCard.id}`} className="text-4xl font-bold mb-4">
            {selectedCard.title}
          </motion.h3>
          <motion.p
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            transition={{ delay: 0.2 }}
            className="text-xl"
          >
            {selectedCard.description}
          </motion.p>
        </motion.div>
      )}
    </div>
  )
}

/**
 * KEY CONCEPT: Elements with the same `layoutId` are treated as the same element
 * across different states/routes. Motion automatically morphs between them.
 */

// ============================================================================
// PATTERN 3: List Reordering with Layout
// ============================================================================

/**
 * Automatically animate items when list order changes.
 * Perfect for drag-to-reorder, filtering, sorting.
 */
interface Item {
  id: number
  text: string
}

export function AnimatedList() {
  const [items, setItems] = useState<Item[]>([
    { id: 1, text: "Item 1" },
    { id: 2, text: "Item 2" },
    { id: 3, text: "Item 3" },
    { id: 4, text: "Item 4" },
  ])

  const shuffle = () => {
    setItems([...items].sort(() => Math.random() - 0.5))
  }

  const reverse = () => {
    setItems([...items].reverse())
  }

  return (
    <div>
      <div className="flex gap-2 mb-4">
        <button onClick={shuffle} className="px-4 py-2 bg-blue-600 text-white rounded">
          Shuffle
        </button>
        <button onClick={reverse} className="px-4 py-2 bg-purple-600 text-white rounded">
          Reverse
        </button>
      </div>

      <div className="space-y-2">
        {items.map((item) => (
          <motion.div
            key={item.id}
            layout // Animate position changes automatically
            className="p-4 bg-white border rounded-lg shadow-sm"
            transition={{ type: "spring", stiffness: 300, damping: 30 }}
          >
            {item.text}
          </motion.div>
        ))}
      </div>
    </div>
  )
}

/**
 * KEY CONCEPT: When items reorder, the `layout` prop automatically animates
 * each item to its new position. No manual position calculation needed.
 */

// ============================================================================
// PATTERN 4: LayoutGroup (Namespacing)
// ============================================================================

/**
 * Use LayoutGroup to namespace layoutIds for reusable components.
 * Prevents conflicts when using multiple instances.
 */
interface TabPanelProps {
  tabs: Array<{ id: string; label: string; content: ReactNode }>
}

export function TabPanel({ tabs }: TabPanelProps) {
  const [activeTab, setActiveTab] = useState(tabs[0].id)

  return (
    <LayoutGroup> {/* Namespace layoutIds within this group */}
      <div className="space-y-4">
        {/* Tab buttons */}
        <div className="flex gap-4 border-b border-gray-200">
          {tabs.map((tab) => (
            <button
              key={tab.id}
              onClick={() => setActiveTab(tab.id)}
              className="relative px-4 py-2"
            >
              {tab.label}

              {/* Shared underline */}
              {activeTab === tab.id && (
                <motion.div
                  layoutId="underline" // Shared within LayoutGroup
                  className="absolute bottom-0 left-0 right-0 h-0.5 bg-blue-600"
                  transition={{ type: "spring", stiffness: 300, damping: 30 }}
                />
              )}
            </button>
          ))}
        </div>

        {/* Tab content */}
        <div className="p-4">
          {tabs.find((tab) => tab.id === activeTab)?.content}
        </div>
      </div>
    </LayoutGroup>
  )
}

/**
 * USAGE
 *
 * <div className="grid grid-cols-2 gap-4">
 *   <TabPanel tabs={[...]} />
 *   <TabPanel tabs={[...]} />  {/* No layoutId conflicts! */}
 * </div>
 */

// ============================================================================
// PATTERN 5: Scrollable Container (layoutScroll)
// ============================================================================

/**
 * Fix layout animations in scrollable containers.
 * Without layoutScroll, animations break when container is scrolled.
 */
export function ScrollableList() {
  const [items, setItems] = useState<Item[]>(
    Array.from({ length: 20 }, (_, i) => ({
      id: i + 1,
      text: `Item ${i + 1}`,
    }))
  )

  const removeItem = (id: number) => {
    setItems(items.filter((item) => item.id !== id))
  }

  return (
    <motion.div
      layoutScroll // CRITICAL: Fixes animations in scrolled containers
      className="h-96 overflow-auto border border-gray-200 rounded-lg p-4 space-y-2"
    >
      {items.map((item) => (
        <motion.div
          key={item.id}
          layout
          initial={{ opacity: 0, scale: 0.8 }}
          animate={{ opacity: 1, scale: 1 }}
          exit={{ opacity: 0, scale: 0.8 }}
          className="p-4 bg-white border rounded-lg shadow-sm flex justify-between items-center"
        >
          <span>{item.text}</span>
          <button
            onClick={() => removeItem(item.id)}
            className="px-3 py-1 bg-red-500 text-white rounded text-sm"
          >
            Remove
          </button>
        </motion.div>
      ))}
    </motion.div>
  )
}

/**
 * KEY CONCEPT: Without `layoutScroll`, removing items from a scrolled container
 * causes incomplete/broken animations. layoutScroll fixes this by accounting
 * for scroll offset in FLIP calculations.
 */

// ============================================================================
// PATTERN 6: Fixed Position (layoutRoot)
// ============================================================================

/**
 * Fix layout animations in fixed-position elements.
 * Without layoutRoot, fixed elements animate incorrectly.
 */
export function FixedModal() {
  const [isOpen, setIsOpen] = useState(false)

  return (
    <>
      <button
        onClick={() => setIsOpen(!isOpen)}
        className="px-4 py-2 bg-blue-600 text-white rounded"
      >
        Toggle Fixed Modal
      </button>

      {isOpen && (
        <motion.div
          layoutRoot // CRITICAL: Fixes animations in fixed elements
          layout
          className="fixed bottom-4 right-4 w-64 bg-white border rounded-lg shadow-2xl p-4 z-50"
        >
          <h3 className="font-bold mb-2">Fixed Modal</h3>
          <p className="text-sm text-gray-700">
            This is a fixed-position element with layout animations.
          </p>
        </motion.div>
      )}
    </>
  )
}

/**
 * KEY CONCEPT: Fixed-position elements need `layoutRoot` to correctly
 * calculate scroll offset during FLIP animations.
 */

// ============================================================================
// PATTERN 7: Grid → List View Switching
// ============================================================================

/**
 * Smooth transition between grid and list layouts.
 */
export function ViewSwitcher() {
  const [view, setView] = useState<"grid" | "list">("grid")

  const items = Array.from({ length: 9 }, (_, i) => ({
    id: i + 1,
    title: `Item ${i + 1}`,
  }))

  return (
    <div>
      <div className="flex gap-2 mb-4">
        <button
          onClick={() => setView("grid")}
          className={`px-4 py-2 rounded ${
            view === "grid" ? "bg-blue-600 text-white" : "bg-gray-200"
          }`}
        >
          Grid
        </button>
        <button
          onClick={() => setView("list")}
          className={`px-4 py-2 rounded ${
            view === "list" ? "bg-blue-600 text-white" : "bg-gray-200"
          }`}
        >
          List
        </button>
      </div>

      <motion.div
        layout
        className={view === "grid" ? "grid grid-cols-3 gap-4" : "space-y-2"}
      >
        {items.map((item) => (
          <motion.div
            key={item.id}
            layout
            className="p-6 bg-white border rounded-lg shadow-sm"
            transition={{ type: "spring", stiffness: 300, damping: 30 }}
          >
            <h3 className="font-bold">{item.title}</h3>
          </motion.div>
        ))}
      </motion.div>
    </div>
  )
}

// ============================================================================
// PATTERN 8: Drag to Reorder (Manual Implementation)
// ============================================================================

/**
 * Drag items to reorder with visual feedback.
 * Note: Motion's Reorder component has Next.js compatibility issues.
 * This is a manual implementation that works everywhere.
 */
export function DragToReorder() {
  const [items, setItems] = useState<Item[]>([
    { id: 1, text: "Drag me" },
    { id: 2, text: "Reorder me" },
    { id: 3, text: "Move me around" },
    { id: 4, text: "Drop me anywhere" },
  ])

  return (
    <div className="space-y-2">
      {items.map((item, index) => (
        <motion.div
          key={item.id}
          layout
          drag="y"
          dragConstraints={{ top: 0, bottom: 0 }}
          dragElastic={0.1}
          whileDrag={{ scale: 1.05, boxShadow: "0 10px 40px rgba(0,0,0,0.2)" }}
          className="p-4 bg-white border rounded-lg shadow-sm cursor-grab active:cursor-grabbing"
          transition={{ type: "spring", stiffness: 300, damping: 30 }}
        >
          <div className="flex items-center gap-3">
            <span className="text-gray-400">☰</span>
            <span>{item.text}</span>
          </div>
        </motion.div>
      ))}
    </div>
  )
}

/**
 * Note: Full drag-to-reorder with position updates requires tracking
 * drag position and updating array order. For production, consider:
 * - @dnd-kit/core (recommended for Next.js)
 * - react-beautiful-dnd
 * - Motion's Reorder component (has Next.js issues, see GitHub #2183)
 */

// ============================================================================
// PATTERN 9: Masonry Layout Animation
// ============================================================================

/**
 * Animate items in masonry/Pinterest-style layout.
 */
export function MasonryLayout() {
  const [items, setItems] = useState(
    Array.from({ length: 12 }, (_, i) => ({
      id: i + 1,
      height: Math.floor(Math.random() * 200) + 100,
    }))
  )

  const shuffle = () => {
    setItems([...items].sort(() => Math.random() - 0.5))
  }

  return (
    <div>
      <button onClick={shuffle} className="px-4 py-2 bg-blue-600 text-white rounded mb-4">
        Shuffle
      </button>

      <div className="columns-3 gap-4">
        {items.map((item) => (
          <motion.div
            key={item.id}
            layout
            className="mb-4 bg-gradient-to-br from-purple-500 to-pink-500 rounded-lg p-4 text-white font-bold break-inside-avoid"
            style={{ height: item.height }}
            transition={{ type: "spring", stiffness: 300, damping: 30 }}
          >
            Item {item.id}
          </motion.div>
        ))}
      </div>
    </div>
  )
}

// ============================================================================
// ALL EXAMPLES DEMO
// ============================================================================

export function LayoutAnimationsDemo() {
  return (
    <div className="max-w-4xl mx-auto p-8 space-y-12">
      <h1 className="text-4xl font-bold mb-8">Layout Animations & Shared Elements</h1>

      <section>
        <h2 className="text-2xl font-bold mb-4">1. Basic Layout Animation</h2>
        <ExpandableCard />
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">2. Shared Element Transition</h2>
        <SharedElementExample />
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">3. List Reordering</h2>
        <AnimatedList />
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">4. Tab Panel with LayoutGroup</h2>
        <TabPanel
          tabs={[
            { id: "1", label: "Tab 1", content: <p>Content for tab 1</p> },
            { id: "2", label: "Tab 2", content: <p>Content for tab 2</p> },
            { id: "3", label: "Tab 3", content: <p>Content for tab 3</p> },
          ]}
        />
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">5. Scrollable Container</h2>
        <ScrollableList />
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">6. Fixed Position Modal</h2>
        <FixedModal />
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">7. Grid/List View Switcher</h2>
        <ViewSwitcher />
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">8. Drag to Reorder</h2>
        <DragToReorder />
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">9. Masonry Layout</h2>
        <MasonryLayout />
      </section>
    </div>
  )
}

/**
 * PERFORMANCE TIPS
 *
 * 1. Add willChange for layout animations:
 *    <motion.div layout style={{ willChange: "transform" }} />
 *
 * 2. Use layout prop only when needed:
 *    - If element size/position doesn't change, don't use layout
 *
 * 3. Combine with AnimatePresence for enter/exit:
 *    <AnimatePresence>
 *      {items.map(item => (
 *        <motion.div key={item.id} layout exit={{ opacity: 0 }} />
 *      ))}
 *    </AnimatePresence>
 *
 * 4. Use LayoutGroup to isolate layout calculations:
 *    - Prevents unnecessary recalculations across unrelated components
 *
 * See ../references/performance-optimization.md for full guide
 */

/**
 * COMMON ISSUES
 *
 * 1. Broken animations in scrollable containers
 *    Solution: Add layoutScroll prop
 *
 * 2. Incorrect animations in fixed elements
 *    Solution: Add layoutRoot prop
 *
 * 3. layoutId conflicts in reusable components
 *    Solution: Wrap in LayoutGroup
 *
 * 4. Elements not unmounting with layoutId + AnimatePresence
 *    Solution: Use LayoutGroup or avoid mixing exit + layout animations
 *
 * See ../SKILL.md for full troubleshooting guide
 */

/**
 * NEXT.JS USAGE
 *
 * Add "use client" directive at top of file:
 *
 * "use client"
 *
 * import { motion, LayoutGroup } from "motion/react-client"
 *
 * See ../references/nextjs-integration.md for comprehensive guide
 */
templates/motion-nextjs-client.tsx
// Motion + Next.js App Router - Client Component Pattern
// Production-tested with Motion v12.23.24, Next.js 15, React 19

/**
 * INSTALLATION
 *
 * 1. Install Motion:
 *    pnpm add motion
 *
 * 2. Create this file structure:
 *    src/components/motion-client.tsx  ← This file (wrapper)
 *    src/components/AnimatedModal.tsx  ← Example usage
 *    src/app/page.tsx                  ← Server Component can import
 *
 * 3. CRITICAL: Motion only works in Client Components, NOT Server Components
 *
 * NO NEXT.JS CONFIGURATION NEEDED - just use "use client" directive
 */

// ============================================================================
// PATTERN 1: Client Component Wrapper (Recommended for Next.js App Router)
// ============================================================================

/**
 * File: src/components/motion-client.tsx
 *
 * Create a wrapper that re-exports Motion as a Client Component.
 * This allows you to import { motion } in any file without repeating "use client".
 */

"use client"

// Optimized import for Next.js (reduces client JS bundle)
import * as motion from "motion/react-client"

// Re-export everything from motion
export { motion }

// Also export commonly used components
export {
  AnimatePresence,
  MotionConfig,
  LazyMotion,
  LayoutGroup,
  useMotionValue,
  useTransform,
  useScroll,
  useSpring,
  useAnimate,
  useInView,
  useDragControls,
} from "motion/react-client"

/**
 * USAGE IN SERVER COMPONENTS
 *
 * // src/app/page.tsx (Server Component)
 * import { motion } from "@/components/motion-client"
 *
 * export default function Page() {
 *   return (
 *     <motion.div
 *       initial={{ opacity: 0 }}
 *       animate={{ opacity: 1 }}
 *     >
 *       This works! The wrapper is a Client Component.
 *     </motion.div>
 *   )
 * }
 */

// ============================================================================
// PATTERN 2: Direct Client Component
// ============================================================================

/**
 * File: src/components/AnimatedModal.tsx
 *
 * For complex components, create dedicated Client Components.
 */

"use client"

import { motion, AnimatePresence } from "motion/react-client"
import { useState, ReactNode } from "react"

interface AnimatedModalProps {
  trigger: ReactNode
  title: string
  children: ReactNode
}

export function AnimatedModal({ trigger, title, children }: AnimatedModalProps) {
  const [isOpen, setIsOpen] = useState(false)

  return (
    <>
      {/* Trigger button */}
      <div onClick={() => setIsOpen(true)}>
        {trigger}
      </div>

      {/* Modal with AnimatePresence */}
      <AnimatePresence>
        {isOpen && (
          <>
            {/* Backdrop */}
            <motion.div
              key="backdrop"
              initial={{ opacity: 0 }}
              animate={{ opacity: 1 }}
              exit={{ opacity: 0 }}
              onClick={() => setIsOpen(false)}
              className="fixed inset-0 bg-black/50 z-40"
            />

            {/* Dialog */}
            <motion.dialog
              key="dialog"
              initial={{ opacity: 0, scale: 0.9, y: 20 }}
              animate={{ opacity: 1, scale: 1, y: 0 }}
              exit={{ opacity: 0, scale: 0.9, y: 20 }}
              transition={{ type: "spring", damping: 20, stiffness: 300 }}
              className="fixed inset-0 m-auto w-full max-w-md h-fit bg-white rounded-lg shadow-xl z-50 p-6"
            >
              <div className="flex justify-between items-center mb-4">
                <h2 className="text-2xl font-bold">{title}</h2>
                <button
                  onClick={() => setIsOpen(false)}
                  className="text-gray-500 hover:text-gray-700"
                >
                  ✕
                </button>
              </div>
              <div>{children}</div>
            </motion.dialog>
          </>
        )}
      </AnimatePresence>
    </>
  )
}

/**
 * USAGE IN SERVER COMPONENT
 *
 * // src/app/page.tsx (Server Component)
 * import { AnimatedModal } from "@/components/AnimatedModal"
 *
 * export default function Page() {
 *   return (
 *     <AnimatedModal
 *       trigger={<button>Open Modal</button>}
 *       title="Hello World"
 *     >
 *       <p>Modal content here</p>
 *     </AnimatedModal>
 *   )
 * }
 */

// ============================================================================
// PATTERN 3: Reduced Motion for Accessibility
// ============================================================================

/**
 * File: src/components/MotionProvider.tsx
 *
 * Wrap your app to respect user's prefers-reduced-motion setting.
 */

"use client"

import { MotionConfig } from "motion/react-client"
import { ReactNode } from "react"

interface MotionProviderProps {
  children: ReactNode
}

export function MotionProvider({ children }: MotionProviderProps) {
  return (
    <MotionConfig reducedMotion="user">
      {children}
    </MotionConfig>
  )
}

/**
 * USAGE IN ROOT LAYOUT
 *
 * // src/app/layout.tsx (Server Component)
 * import { MotionProvider } from "@/components/MotionProvider"
 *
 * export default function RootLayout({ children }) {
 *   return (
 *     <html>
 *       <body>
 *         <MotionProvider>
 *           {children}
 *         </MotionProvider>
 *       </body>
 *     </html>
 *   )
 * }
 *
 * This respects OS-level accessibility settings:
 * - macOS: System Settings → Accessibility → Display → Reduce motion
 * - Windows: Settings → Ease of Access → Display → Show animations
 * - iOS: Settings → Accessibility → Motion
 * - Android 9+: Settings → Accessibility → Remove animations
 */

// ============================================================================
// PATTERN 4: Page Transitions with App Router
// ============================================================================

/**
 * File: src/components/PageTransition.tsx
 *
 * Animate route changes in App Router.
 */

"use client"

import { motion } from "motion/react-client"
import { ReactNode } from "react"

interface PageTransitionProps {
  children: ReactNode
}

export function PageTransition({ children }: PageTransitionProps) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      exit={{ opacity: 0, y: -20 }}
      transition={{ duration: 0.3 }}
    >
      {children}
    </motion.div>
  )
}

/**
 * USAGE IN PAGE
 *
 * // src/app/about/page.tsx (Server Component)
 * import { PageTransition } from "@/components/PageTransition"
 *
 * export default function AboutPage() {
 *   return (
 *     <PageTransition>
 *       <h1>About Page</h1>
 *       <p>Content animates in on route change</p>
 *     </PageTransition>
 *   )
 * }
 *
 * Note: Exit animations require AnimatePresence, which may not work
 * reliably with Next.js soft navigation. For full page transitions,
 * consider using template.tsx file or middleware approach.
 */

// ============================================================================
// PATTERN 5: Server Data with Client Animation
// ============================================================================

/**
 * File: src/components/AnimatedProductCard.tsx
 *
 * Fetch data in Server Component, animate in Client Component.
 */

"use client"

import { motion } from "motion/react-client"

interface Product {
  id: number
  name: string
  price: number
  image: string
}

interface AnimatedProductCardProps {
  product: Product
  index: number
}

export function AnimatedProductCard({ product, index }: AnimatedProductCardProps) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ delay: index * 0.1 }} // Stagger
      whileHover={{ scale: 1.05 }}
      className="p-4 bg-white border rounded-lg shadow-sm cursor-pointer"
    >
      <img
        src={product.image}
        alt={product.name}
        className="w-full h-48 object-cover rounded mb-4"
      />
      <h3 className="text-lg font-bold">{product.name}</h3>
      <p className="text-gray-600">${product.price}</p>
    </motion.div>
  )
}

/**
 * USAGE WITH SERVER DATA
 *
 * // src/app/products/page.tsx (Server Component)
 * import { AnimatedProductCard } from "@/components/AnimatedProductCard"
 *
 * async function getProducts() {
 *   const res = await fetch('https://api.example.com/products')
 *   return res.json()
 * }
 *
 * export default async function ProductsPage() {
 *   const products = await getProducts() // Server-side fetch
 *
 *   return (
 *     <div className="grid grid-cols-3 gap-4">
 *       {products.map((product, index) => (
 *         <AnimatedProductCard
 *           key={product.id}
 *           product={product}
 *           index={index}
 *         />
 *       ))}
 *     </div>
 *   )
 * }
 *
 * Benefits:
 * - Data fetched on server (SEO, performance)
 * - Animation runs on client (interactivity)
 * - Best of both worlds
 */

// ============================================================================
// KNOWN ISSUES & WORKAROUNDS
// ============================================================================

/**
 * ISSUE 1: "motion is not defined" or SSR Errors
 *
 * Cause: Forgot "use client" directive
 *
 * Solution: Add "use client" at top of file:
 *
 * "use client"
 * import { motion } from "motion/react"
 */

/**
 * ISSUE 2: AnimatePresence Exit Animations Not Working
 *
 * Cause: Next.js soft navigation doesn't trigger React unmount
 *
 * Solution: Use route-level AnimatePresence is unreliable in App Router.
 * For component-level modals/dropdowns, it works fine. For page transitions,
 * consider alternatives like view transitions API or middleware approach.
 *
 * GitHub issue: Check Next.js docs for latest recommendations
 */

/**
 * ISSUE 3: Large Bundle Size
 *
 * Cause: Full motion component is ~34 KB minified+gzipped
 *
 * Solution: Use LazyMotion for 4.6 KB:
 *
 * "use client"
 *
 * import { LazyMotion, domAnimation, m } from "motion/react-client"
 *
 * export function App() {
 *   return (
 *     <LazyMotion features={domAnimation}>
 *       <m.div animate={{ x: 100 }} />
 *     </LazyMotion>
 *   )
 * }
 *
 * See ../references/performance-optimization.md for full guide
 */

/**
 * ISSUE 4: Next.js 15 + React 19 Compatibility
 *
 * Status: Most issues resolved in latest Motion version
 *
 * Solution: Update to latest:
 * pnpm add motion@latest react@latest next@latest
 *
 * If issues persist, check GitHub: https://github.com/motiondivision/motion/issues
 */

/**
 * ISSUE 5: Reorder Component Doesn't Work
 *
 * Cause: Reorder component incompatible with Next.js routing
 *
 * Solution: Use alternative drag-to-reorder implementations or avoid Reorder
 *
 * GitHub issues: #2183, #2101
 */

// ============================================================================
// PERFORMANCE OPTIMIZATION
// ============================================================================

/**
 * 1. Use "motion/react-client" import (not "motion/react")
 *    - Reduces client JS bundle
 *    - Optimized for Next.js App Router
 *
 * 2. Add willChange for transforms:
 *    <motion.div style={{ willChange: "transform" }} animate={{ x: 100 }} />
 *
 * 3. Use LazyMotion for smaller bundle (see Issue 3 above)
 *
 * 4. For large lists (50+ items), use virtualization:
 *    pnpm add @tanstack/react-virtual
 *
 * 5. Memoize expensive components:
 *    import { memo } from "react"
 *    export const AnimatedCard = memo(AnimatedCardComponent)
 */

// ============================================================================
// TYPESCRIPT TYPES
// ============================================================================

/**
 * Motion includes full TypeScript support out of the box.
 * No @types package needed.
 *
 * Common types:
 */

import type {
  HTMLMotionProps,
  SVGMotionProps,
  Variants,
  Target,
  Transition,
  MotionValue,
  AnimationControls,
  DragControls,
} from "motion/react-client"

// Example: Typed motion component props
interface AnimatedBoxProps extends HTMLMotionProps<"div"> {
  title: string
}

export function AnimatedBox({ title, ...motionProps }: AnimatedBoxProps) {
  return (
    <motion.div {...motionProps}>
      <h3>{title}</h3>
    </motion.div>
  )
}

// Example: Typed variants
const typedVariants: Variants = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0 },
}

/**
 * QUICK REFERENCE
 *
 * App Router Requirements:
 * ✅ Add "use client" to files using Motion
 * ✅ Use "motion/react-client" import for optimized bundle
 * ✅ Server Components can import Client Components
 * ✅ Wrap app in MotionProvider for reduced motion support
 * ❌ Don't use Motion in Server Components
 * ❌ Don't rely on AnimatePresence for route transitions (unreliable)
 *
 * See ../references/nextjs-integration.md for comprehensive guide
 */
templates/motion-vite-basic.tsx
// Motion + Vite + React + TypeScript - Basic Setup
// Production-tested with Motion v12.23.24, React 19, Vite 6

/**
 * INSTALLATION
 *
 * 1. Install Motion:
 *    pnpm add motion
 *
 * 2. Install React + TypeScript (if new project):
 *    pnpm create vite my-app --template react-ts
 *    cd my-app
 *    pnpm install
 *
 * 3. Copy this file to src/components/AnimatedExamples.tsx
 *
 * 4. Import in your App.tsx:
 *    import { AnimatedExamples } from '@/components/AnimatedExamples'
 *
 * NO VITE CONFIGURATION NEEDED - works out of the box
 */

import { motion } from "motion/react"
import { useState } from "react"

/**
 * Example 1: Basic Animation
 * Fade in and slide up on mount
 */
export function FadeInBox() {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.5 }}
      className="p-6 bg-blue-100 rounded-lg"
    >
      <h2 className="text-xl font-bold">I fade in and slide up!</h2>
      <p className="text-gray-700">Basic animation example</p>
    </motion.div>
  )
}

/**
 * Example 2: Hover and Tap Gestures
 * Scale on hover, shrink on tap
 */
export function InteractiveButton() {
  return (
    <motion.button
      whileHover={{ scale: 1.1 }}
      whileTap={{ scale: 0.95 }}
      className="px-6 py-3 bg-blue-600 text-white rounded-lg font-semibold"
    >
      Hover and Click Me
    </motion.button>
  )
}

/**
 * Example 3: Variants (Named Animation States)
 * Propagates through component tree automatically
 */
interface Item {
  id: number
  text: string
}

const containerVariants = {
  hidden: { opacity: 0 },
  show: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1, // Delay between each child (in seconds)
    }
  }
}

const itemVariants = {
  hidden: { opacity: 0, x: -20 },
  show: { opacity: 1, x: 0 }
}

export function StaggeredList() {
  const [items] = useState<Item[]>([
    { id: 1, text: "First item" },
    { id: 2, text: "Second item" },
    { id: 3, text: "Third item" },
    { id: 4, text: "Fourth item" },
  ])

  return (
    <motion.ul
      variants={containerVariants}
      initial="hidden"
      animate="show"
      className="space-y-2"
    >
      {items.map((item) => (
        <motion.li
          key={item.id}
          variants={itemVariants}
          className="p-4 bg-white border rounded-lg shadow-sm"
        >
          {item.text}
        </motion.li>
      ))}
    </motion.ul>
  )
}

/**
 * Example 4: Spring Physics
 * Natural, physics-based motion
 */
export function SpringButton() {
  const [isActive, setIsActive] = useState(false)

  return (
    <motion.button
      animate={{
        scale: isActive ? 1.2 : 1,
        rotate: isActive ? 180 : 0,
      }}
      transition={{
        type: "spring",
        stiffness: 300,  // Higher = more sudden movement
        damping: 10,     // Higher = less oscillation
        mass: 1,         // Higher = more lethargic
      }}
      onClick={() => setIsActive(!isActive)}
      className="w-16 h-16 bg-purple-600 text-white rounded-full"
    >
      🎨
    </motion.button>
  )
}

/**
 * Example 5: Keyframes
 * Multiple intermediate states
 */
export function KeyframeAnimation() {
  return (
    <motion.div
      animate={{
        scale: [1, 1.2, 1.2, 1, 1],
        rotate: [0, 0, 180, 180, 0],
        borderRadius: ["10%", "10%", "50%", "50%", "10%"],
      }}
      transition={{
        duration: 2,
        ease: "easeInOut",
        times: [0, 0.2, 0.5, 0.8, 1], // When each keyframe happens (0-1)
        repeat: Infinity,
        repeatDelay: 1,
      }}
      className="w-20 h-20 bg-gradient-to-br from-pink-500 to-purple-600"
    />
  )
}

/**
 * Example 6: Drag Gesture
 * Drag with constraints and elastic edges
 */
export function DraggableBox() {
  return (
    <div className="relative w-full h-64 bg-gray-100 rounded-lg overflow-hidden">
      <motion.div
        drag
        dragConstraints={{ left: 0, right: 300, top: 0, bottom: 200 }}
        dragElastic={0.2} // Elasticity at constraints (0-1)
        dragMomentum={false} // Disable momentum/inertia
        className="absolute w-16 h-16 bg-red-500 rounded-lg cursor-grab active:cursor-grabbing flex items-center justify-center text-white font-bold"
      >
        DRAG
      </motion.div>
    </div>
  )
}

/**
 * Example 7: Hover Color Change
 * Animate any CSS property
 */
export function ColorChangeCard() {
  return (
    <motion.div
      whileHover={{
        backgroundColor: "#3b82f6", // Tailwind blue-600
        color: "#ffffff",
      }}
      transition={{ duration: 0.3 }}
      className="p-6 bg-gray-200 text-gray-900 rounded-lg cursor-pointer"
    >
      <h3 className="text-lg font-bold">Hover me!</h3>
      <p>Background and text color change on hover</p>
    </motion.div>
  )
}

/**
 * Example 8: TypeScript Types
 * Motion components are fully typed
 */
interface AnimatedCardProps {
  title: string
  description: string
  delay?: number
}

export function AnimatedCard({ title, description, delay = 0 }: AnimatedCardProps) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ delay, duration: 0.5 }}
      className="p-6 bg-white border rounded-lg shadow-sm"
    >
      <h3 className="text-xl font-bold mb-2">{title}</h3>
      <p className="text-gray-700">{description}</p>
    </motion.div>
  )
}

/**
 * Example 9: Tailwind Integration
 * ⚠️ IMPORTANT: Remove Tailwind transition classes to avoid conflicts
 */
export function TailwindIntegration() {
  return (
    <div className="space-y-4">
      {/* ❌ Wrong - Tailwind transition conflicts with Motion */}
      {/* <motion.div className="transition-all duration-300" animate={{ x: 100 }} /> */}

      {/* ✅ Correct - Let Motion handle animations, Tailwind for styles */}
      <motion.div
        whileHover={{ scale: 1.05 }}
        className="px-6 py-4 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-lg"
      >
        Tailwind styles + Motion animations (no transition classes)
      </motion.div>
    </div>
  )
}

/**
 * All Examples Component
 * Demo of all patterns above
 */
export function AnimatedExamples() {
  return (
    <div className="max-w-4xl mx-auto p-8 space-y-8">
      <h1 className="text-3xl font-bold mb-8">Motion Animation Examples</h1>

      <section>
        <h2 className="text-2xl font-bold mb-4">1. Basic Animation</h2>
        <FadeInBox />
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">2. Hover & Tap Gestures</h2>
        <InteractiveButton />
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">3. Staggered List (Variants)</h2>
        <StaggeredList />
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">4. Spring Physics</h2>
        <SpringButton />
        <p className="text-sm text-gray-600 mt-2">Click to toggle</p>
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">5. Keyframe Animation</h2>
        <KeyframeAnimation />
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">6. Drag Gesture</h2>
        <DraggableBox />
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">7. Color Change</h2>
        <ColorChangeCard />
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">8. TypeScript Example</h2>
        <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
          <AnimatedCard
            title="Card 1"
            description="First card with no delay"
            delay={0}
          />
          <AnimatedCard
            title="Card 2"
            description="Second card with 0.2s delay"
            delay={0.2}
          />
          <AnimatedCard
            title="Card 3"
            description="Third card with 0.4s delay"
            delay={0.4}
          />
        </div>
      </section>

      <section>
        <h2 className="text-2xl font-bold mb-4">9. Tailwind Integration</h2>
        <TailwindIntegration />
      </section>
    </div>
  )
}

/**
 * USAGE IN APP.TSX
 *
 * import { AnimatedExamples } from '@/components/AnimatedExamples'
 *
 * function App() {
 *   return <AnimatedExamples />
 * }
 */

/**
 * PERFORMANCE TIPS
 *
 * 1. Add willChange for transforms:
 *    <motion.div style={{ willChange: "transform" }} />
 *
 * 2. Use LazyMotion for smaller bundle (34 KB → 4.6 KB):
 *    import { LazyMotion, domAnimation, m } from "motion/react"
 *    <LazyMotion features={domAnimation}>
 *      <m.div animate={{ x: 100 }} />
 *    </LazyMotion>
 *
 * 3. For large lists (50+ items), use virtualization:
 *    pnpm add react-window
 *
 * See ../references/performance-optimization.md for full guide
 */

/**
 * ACCESSIBILITY
 *
 * Respect prefers-reduced-motion:
 *
 * import { MotionConfig } from "motion/react"
 *
 * <MotionConfig reducedMotion="user">
 *   <App />
 * </MotionConfig>
 *
 * User settings:
 * - macOS: System Settings → Accessibility → Display → Reduce motion
 * - Windows: Settings → Ease of Access → Display → Show animations
 * - iOS: Settings → Accessibility → Motion
 * - Android 9+: Settings → Accessibility → Remove animations
 */
templates/scroll-parallax.tsx
// Motion Scroll Animations & Parallax Effects
// Production-tested with Motion v12.23.24, React 19

/**
 * INSTALLATION
 *
 * pnpm add motion
 *
 * USAGE
 *
 * Copy examples below into your components.
 * All patterns use hardware-accelerated ScrollTimeline API when available.
 *
 * For Next.js App Router, add "use client" directive at top of file.
 */

import { motion, useScroll, useTransform, useInView, useMotionValue, useSpring } from "motion/react"
import { useRef } from "react"

// ============================================================================
// PATTERN 1: Scroll-Triggered Reveal (whileInView)
// ============================================================================

/**
 * Fade in and slide up when element enters viewport.
 * Perfect for revealing sections as user scrolls.
 */
export function FadeInSection({ children }: { children: React.ReactNode }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 50 }}
      whileInView={{ opacity: 1, y: 0 }}
      viewport={{
        once: true,       // Animate only once (don't re-animate on scroll up)
        margin: "-100px", // Start animation 100px before entering viewport
        amount: 0.3,      // Trigger when 30% of element is visible
      }}
      transition={{ duration: 0.5 }}
    >
      {children}
    </motion.div>
  )
}

/**
 * USAGE
 *
 * <FadeInSection>
 *   <h2>This section fades in when you scroll to it</h2>
 * </FadeInSection>
 */

// ============================================================================
// PATTERN 2: Parallax Hero Section
// ============================================================================

/**
 * Multi-layer parallax effect with different scroll speeds.
 * Perfect for hero sections and landing pages.
 */
export function ParallaxHero() {
  const { scrollY } = useScroll()

  // Transform scroll position to different speeds for each layer
  const y1 = useTransform(scrollY, [0, 1000], [0, -300])   // Fast layer (background)
  const y2 = useTransform(scrollY, [0, 1000], [0, -150])   // Medium layer (middle)
  const y3 = useTransform(scrollY, [0, 1000], [0, -50])    // Slow layer (foreground)
  const opacity = useTransform(scrollY, [0, 300], [1, 0])  // Fade out

  return (
    <div className="relative h-screen overflow-hidden">
      {/* Background layer (fastest) */}
      <motion.div
        style={{ y: y1 }}
        className="absolute inset-0 -z-10"
      >
        <img
          src="/images/background.jpg"
          alt="Background"
          className="w-full h-full object-cover"
        />
      </motion.div>

      {/* Middle layer (medium speed) */}
      <motion.div
        style={{ y: y2 }}
        className="absolute inset-0 -z-5"
      >
        <img
          src="/images/mountains.png"
          alt="Mountains"
          className="w-full h-full object-cover"
        />
      </motion.div>

      {/* Foreground content (slowest) + fade out */}
      <motion.div
        style={{ y: y3, opacity }}
        className="relative z-10 flex items-center justify-center h-full"
      >
        <div className="text-center text-white">
          <h1 className="text-6xl font-bold mb-4">Welcome</h1>
          <p className="text-2xl">Scroll down to see parallax effect</p>
        </div>
      </motion.div>
    </div>
  )
}

// ============================================================================
// PATTERN 3: Scroll-Linked Progress Bar
// ============================================================================

/**
 * Progress bar that fills as user scrolls through page.
 * Perfect for blog posts and long-form content.
 */
export function ScrollProgressBar() {
  const { scrollYProgress } = useScroll()

  return (
    <motion.div
      style={{ scaleX: scrollYProgress }}
      className="fixed top-0 left-0 right-0 h-1 bg-blue-600 origin-left z-50"
    />
  )
}

/**
 * USAGE
 *
 * // In your layout or page
 * <ScrollProgressBar />
 * <YourContent />
 */

// ============================================================================
// PATTERN 4: Section-Specific Scroll Progress
// ============================================================================

/**
 * Track scroll progress within a specific section (not entire page).
 * Perfect for multi-section landing pages.
 */
export function SectionScrollIndicator() {
  const ref = useRef<HTMLDivElement>(null)

  // Only track scroll within this section
  const { scrollYProgress } = useScroll({
    target: ref,
    offset: ["start end", "end start"], // Start when section enters, end when it leaves
  })

  // Convert 0-1 progress to 0-100 for display
  const percentage = useTransform(scrollYProgress, [0, 1], [0, 100])

  return (
    <section ref={ref} className="min-h-screen bg-gray-100 p-8">
      <motion.div
        style={{ scaleX: scrollYProgress }}
        className="h-2 bg-purple-600 mb-4 origin-left"
      />
      <motion.p className="text-2xl font-bold">
        Section Progress: {percentage.get().toFixed(0)}%
      </motion.p>
      <p className="mt-4 text-gray-700">
        Scroll through this section to see the progress bar fill.
      </p>
    </section>
  )
}

// ============================================================================
// PATTERN 5: Smooth Parallax with Spring Physics
// ============================================================================

/**
 * Parallax with spring physics for natural, smooth motion.
 * Adds inertia/lag effect like Apple's website.
 */
export function SmoothParallax() {
  const { scrollY } = useScroll()

  // Add spring physics to parallax for smooth, natural motion
  const y = useSpring(useTransform(scrollY, [0, 1000], [0, -200]), {
    stiffness: 100,
    damping: 30,
    restDelta: 0.001,
  })

  return (
    <div className="relative h-screen overflow-hidden">
      <motion.div
        style={{ y }}
        className="absolute inset-0"
      >
        <img
          src="/images/hero.jpg"
          alt="Hero"
          className="w-full h-full object-cover"
        />
      </motion.div>

      <div className="relative z-10 flex items-center justify-center h-full">
        <h1 className="text-6xl font-bold text-white">Smooth Parallax</h1>
      </div>
    </div>
  )
}

// ============================================================================
// PATTERN 6: Scale on Scroll
// ============================================================================

/**
 * Scale element based on scroll position.
 * Perfect for sticky headers that shrink on scroll.
 */
export function ScaleOnScroll({ children }: { children: React.ReactNode }) {
  const { scrollY } = useScroll()

  // Scale from 1 to 0.8 as user scrolls from 0 to 300px
  const scale = useTransform(scrollY, [0, 300], [1, 0.8])

  return (
    <motion.div style={{ scale }}>
      {children}
    </motion.div>
  )
}

// ============================================================================
// PATTERN 7: Horizontal Scroll Animation
// ============================================================================

/**
 * Horizontal scroll gallery triggered by vertical scroll.
 * Creates "scroll-jacking" effect like Apple product pages.
 */
export function HorizontalScrollGallery() {
  const targetRef = useRef<HTMLDivElement>(null)

  const { scrollYProgress } = useScroll({
    target: targetRef,
    offset: ["start start", "end end"],
  })

  // Convert vertical scroll to horizontal movement
  // 0 → 0%, 1 → -300% (moves 3 screens to the left)
  const x = useTransform(scrollYProgress, [0, 1], ["0%", "-300%"])

  return (
    <section ref={targetRef} className="relative h-[400vh]">
      <div className="sticky top-0 h-screen overflow-hidden">
        <motion.div style={{ x }} className="flex h-full">
          <div className="min-w-full h-full bg-red-500 flex items-center justify-center text-white text-4xl">
            Panel 1
          </div>
          <div className="min-w-full h-full bg-blue-500 flex items-center justify-center text-white text-4xl">
            Panel 2
          </div>
          <div className="min-w-full h-full bg-green-500 flex items-center justify-center text-white text-4xl">
            Panel 3
          </div>
          <div className="min-w-full h-full bg-purple-500 flex items-center justify-center text-white text-4xl">
            Panel 4
          </div>
        </motion.div>
      </div>
    </section>
  )
}

// ============================================================================
// PATTERN 8: Rotate on Scroll
// ============================================================================

/**
 * Rotate element based on scroll position.
 * Perfect for decorative elements and logos.
 */
export function RotateOnScroll() {
  const { scrollY } = useScroll()

  // Rotate 360 degrees as user scrolls 1000px
  const rotate = useTransform(scrollY, [0, 1000], [0, 360])

  return (
    <motion.div
      style={{ rotate }}
      className="w-32 h-32 bg-gradient-to-br from-pink-500 to-purple-600 rounded-full"
    />
  )
}

// ============================================================================
// PATTERN 9: Sticky Element with Scroll Effects
// ============================================================================

/**
 * Sticky element that scales and fades as user scrolls past.
 * Perfect for "scroll to continue" indicators.
 */
export function StickyScrollIndicator() {
  const { scrollY } = useScroll()

  // Fade out and scale down after scrolling 200px
  const opacity = useTransform(scrollY, [0, 200], [1, 0])
  const scale = useTransform(scrollY, [0, 200], [1, 0.5])

  return (
    <motion.div
      style={{ opacity, scale }}
      className="fixed bottom-8 left-1/2 -translate-x-1/2 z-50"
    >
      <div className="flex flex-col items-center text-gray-600">
        <p className="mb-2">Scroll Down</p>
        <svg
          className="w-6 h-6 animate-bounce"
          fill="none"
          stroke="currentColor"
          viewBox="0 0 24 24"
        >
          <path
            strokeLinecap="round"
            strokeLinejoin="round"
            strokeWidth={2}
            d="M19 14l-7 7m0 0l-7-7m7 7V3"
          />
        </svg>
      </div>
    </motion.div>
  )
}

// ============================================================================
// PATTERN 10: useInView Hook (Manual Control)
// ============================================================================

/**
 * More control than whileInView prop.
 * Trigger custom animations when element enters viewport.
 */
export function CustomInViewAnimation() {
  const ref = useRef<HTMLDivElement>(null)

  // Returns true when element is in viewport
  const isInView = useInView(ref, {
    once: true,
    margin: "-100px",
    amount: 0.5,
  })

  return (
    <div ref={ref} className="min-h-screen flex items-center justify-center">
      <motion.div
        initial={{ opacity: 0, scale: 0.5 }}
        animate={isInView ? { opacity: 1, scale: 1 } : { opacity: 0, scale: 0.5 }}
        transition={{ duration: 0.5 }}
        className="p-8 bg-blue-100 rounded-lg"
      >
        <h2 className="text-2xl font-bold">
          {isInView ? "I'm in view!" : "Scroll to me"}
        </h2>
      </motion.div>
    </div>
  )
}

// ============================================================================
// PATTERN 11: Scroll-Linked Color Change
// ============================================================================

/**
 * Change background color based on scroll position.
 * Perfect for section transitions.
 */
export function ScrollColorChange() {
  const { scrollYProgress } = useScroll()

  // Interpolate between colors
  const backgroundColor = useTransform(
    scrollYProgress,
    [0, 0.33, 0.66, 1],
    ["#ef4444", "#3b82f6", "#10b981", "#8b5cf6"] // red → blue → green → purple
  )

  return (
    <motion.div
      style={{ backgroundColor }}
      className="min-h-[200vh] flex items-center justify-center"
    >
      <div className="text-white text-4xl font-bold">
        Background changes as you scroll
      </div>
    </motion.div>
  )
}

// ============================================================================
// ALL EXAMPLES COMPONENT
// ============================================================================

/**
 * Demo page showing all scroll patterns.
 */
export function ScrollAnimationExamples() {
  return (
    <div>
      <ScrollProgressBar />

      <ParallaxHero />

      <FadeInSection>
        <div className="container mx-auto p-8">
          <h2 className="text-4xl font-bold mb-4">Fade In Section</h2>
          <p className="text-gray-700">
            This section fades in when you scroll to it.
          </p>
        </div>
      </FadeInSection>

      <SectionScrollIndicator />

      <SmoothParallax />

      <div className="container mx-auto p-8">
        <ScaleOnScroll>
          <h2 className="text-4xl font-bold mb-4">Scale on Scroll</h2>
          <p className="text-gray-700">This scales down as you scroll.</p>
        </ScaleOnScroll>
      </div>

      <HorizontalScrollGallery />

      <div className="container mx-auto p-8 flex justify-center">
        <RotateOnScroll />
      </div>

      <StickyScrollIndicator />

      <CustomInViewAnimation />

      <ScrollColorChange />

      <div className="h-screen" /> {/* Spacer to allow scrolling */}
    </div>
  )
}

/**
 * PERFORMANCE TIPS
 *
 * 1. Add willChange for transforms:
 *    <motion.div style={{ willChange: "transform" }} />
 *
 * 2. Use ScrollTimeline API (automatic in Motion):
 *    - Hardware-accelerated
 *    - Runs on compositor thread (no JavaScript on scroll)
 *    - 120fps on capable devices
 *
 * 3. Avoid animating expensive properties:
 *    ✅ Good: transform, opacity
 *    ❌ Bad: width, height, top, left (causes layout reflow)
 *
 * 4. Use useSpring for smooth, natural motion:
 *    const y = useSpring(useTransform(scrollY, [0, 1000], [0, -200]))
 *
 * 5. Debounce complex calculations:
 *    Only update on significant scroll changes, not every pixel
 */

/**
 * ACCESSIBILITY
 *
 * 1. Respect prefers-reduced-motion:
 *
 *    import { MotionConfig } from "motion/react"
 *
 *    <MotionConfig reducedMotion="user">
 *      <ScrollAnimationExamples />
 *    </MotionConfig>
 *
 * 2. Don't rely on scroll animations for critical content:
 *    - Ensure content is visible without JavaScript
 *    - Provide alternative navigation
 *    - Don't hide important UI behind scroll triggers
 *
 * 3. Test with keyboard navigation:
 *    - Tab through page
 *    - Ensure animations don't block keyboard focus
 *
 * See ../references/performance-optimization.md for full guide
 */

/**
 * NEXT.JS USAGE
 *
 * Add "use client" directive at top of file:
 *
 * "use client"
 *
 * import { motion, useScroll, ... } from "motion/react-client"
 *
 * See ../references/nextjs-integration.md for comprehensive guide
 */
templates/ui-components.tsx
// Motion UI Components - Modal, Accordion, Carousel, Tabs
// Production-tested with Motion v12.23.24, React 19

/**
 * INSTALLATION
 *
 * pnpm add motion
 *
 * USAGE
 *
 * Copy components below into your project.
 * All components are production-ready and fully typed.
 *
 * For Next.js App Router, add "use client" directive at top of file.
 */

import { motion, AnimatePresence } from "motion/react"
import { useState, ReactNode, useRef } from "react"

// ============================================================================
// COMPONENT 1: Modal Dialog
// ============================================================================

interface ModalProps {
  isOpen: boolean
  onClose: () => void
  title: string
  children: ReactNode
}

export function Modal({ isOpen, onClose, title, children }: ModalProps) {
  return (
    <AnimatePresence>
      {isOpen && (
        <>
          {/* Backdrop */}
          <motion.div
            key="backdrop"
            initial={{ opacity: 0 }}
            animate={{ opacity: 1 }}
            exit={{ opacity: 0 }}
            onClick={onClose}
            className="fixed inset-0 bg-black/50 z-40 cursor-pointer"
          />

          {/* Dialog */}
          <motion.dialog
            key="dialog"
            initial={{ opacity: 0, scale: 0.9, y: 20 }}
            animate={{ opacity: 1, scale: 1, y: 0 }}
            exit={{ opacity: 0, scale: 0.9, y: 20 }}
            transition={{ type: "spring", damping: 20, stiffness: 300 }}
            className="fixed inset-0 m-auto w-full max-w-md h-fit bg-white dark:bg-gray-800 rounded-lg shadow-2xl z-50 p-6"
            style={{ willChange: "transform, opacity" }}
          >
            {/* Header */}
            <div className="flex justify-between items-center mb-4">
              <h2 className="text-2xl font-bold">{title}</h2>
              <motion.button
                whileHover={{ scale: 1.1, rotate: 90 }}
                whileTap={{ scale: 0.9 }}
                onClick={onClose}
                className="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 text-2xl"
              >
                ✕
              </motion.button>
            </div>

            {/* Content */}
            <div className="text-gray-700 dark:text-gray-300">
              {children}
            </div>

            {/* Footer */}
            <div className="mt-6 flex justify-end gap-2">
              <motion.button
                whileHover={{ scale: 1.05 }}
                whileTap={{ scale: 0.95 }}
                onClick={onClose}
                className="px-4 py-2 bg-gray-200 dark:bg-gray-700 rounded hover:bg-gray-300 dark:hover:bg-gray-600"
              >
                Close
              </motion.button>
            </div>
          </motion.dialog>
        </>
      )}
    </AnimatePresence>
  )
}

/**
 * USAGE
 *
 * function App() {
 *   const [isOpen, setIsOpen] = useState(false)
 *
 *   return (
 *     <>
 *       <button onClick={() => setIsOpen(true)}>Open Modal</button>
 *       <Modal
 *         isOpen={isOpen}
 *         onClose={() => setIsOpen(false)}
 *         title="Welcome"
 *       >
 *         <p>Modal content goes here.</p>
 *       </Modal>
 *     </>
 *   )
 * }
 */

// ============================================================================
// COMPONENT 2: Accordion
// ============================================================================

interface AccordionItemProps {
  title: string
  children: ReactNode
  isOpen: boolean
  onToggle: () => void
}

export function AccordionItem({ title, children, isOpen, onToggle }: AccordionItemProps) {
  return (
    <div className="border-b border-gray-200 dark:border-gray-700">
      {/* Trigger */}
      <motion.button
        onClick={onToggle}
        className="w-full flex justify-between items-center py-4 px-4 text-left hover:bg-gray-50 dark:hover:bg-gray-800"
        whileHover={{ backgroundColor: "rgba(0, 0, 0, 0.02)" }}
      >
        <span className="font-semibold">{title}</span>
        <motion.span
          animate={{ rotate: isOpen ? 180 : 0 }}
          transition={{ duration: 0.3 }}
        >
          ▼
        </motion.span>
      </motion.button>

      {/* Content */}
      <motion.div
        initial={false}
        animate={{
          height: isOpen ? "auto" : 0,
          opacity: isOpen ? 1 : 0,
        }}
        transition={{ duration: 0.3 }}
        style={{ overflow: "hidden" }}
      >
        <div className="p-4 text-gray-700 dark:text-gray-300">
          {children}
        </div>
      </motion.div>
    </div>
  )
}

interface AccordionProps {
  items: Array<{
    id: string
    title: string
    content: ReactNode
  }>
  allowMultiple?: boolean
}

export function Accordion({ items, allowMultiple = false }: AccordionProps) {
  const [openItems, setOpenItems] = useState<Set<string>>(new Set())

  const handleToggle = (id: string) => {
    setOpenItems((prev) => {
      const next = new Set(prev)
      if (next.has(id)) {
        next.delete(id)
      } else {
        if (!allowMultiple) {
          next.clear()
        }
        next.add(id)
      }
      return next
    })
  }

  return (
    <div className="border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden">
      {items.map((item) => (
        <AccordionItem
          key={item.id}
          title={item.title}
          isOpen={openItems.has(item.id)}
          onToggle={() => handleToggle(item.id)}
        >
          {item.content}
        </AccordionItem>
      ))}
    </div>
  )
}

/**
 * USAGE
 *
 * <Accordion
 *   allowMultiple={false}
 *   items={[
 *     { id: "1", title: "Section 1", content: <p>Content 1</p> },
 *     { id: "2", title: "Section 2", content: <p>Content 2</p> },
 *   ]}
 * />
 */

// ============================================================================
// COMPONENT 3: Carousel / Image Slider
// ============================================================================

interface CarouselProps {
  images: Array<{
    id: string
    url: string
    alt: string
  }>
}

export function Carousel({ images }: CarouselProps) {
  const [currentIndex, setCurrentIndex] = useState(0)
  const [direction, setDirection] = useState(0) // 1 = next, -1 = prev

  const handleNext = () => {
    setDirection(1)
    setCurrentIndex((prev) => (prev + 1) % images.length)
  }

  const handlePrev = () => {
    setDirection(-1)
    setCurrentIndex((prev) => (prev - 1 + images.length) % images.length)
  }

  const variants = {
    enter: (direction: number) => ({
      x: direction > 0 ? 1000 : -1000,
      opacity: 0,
    }),
    center: {
      x: 0,
      opacity: 1,
    },
    exit: (direction: number) => ({
      x: direction < 0 ? 1000 : -1000,
      opacity: 0,
    }),
  }

  return (
    <div className="relative w-full h-96 overflow-hidden bg-gray-100 dark:bg-gray-800 rounded-lg">
      {/* Image */}
      <AnimatePresence initial={false} custom={direction}>
        <motion.img
          key={currentIndex}
          src={images[currentIndex].url}
          alt={images[currentIndex].alt}
          custom={direction}
          variants={variants}
          initial="enter"
          animate="center"
          exit="exit"
          transition={{ type: "spring", stiffness: 300, damping: 30 }}
          className="absolute inset-0 w-full h-full object-cover"
        />
      </AnimatePresence>

      {/* Navigation buttons */}
      <button
        onClick={handlePrev}
        className="absolute left-4 top-1/2 -translate-y-1/2 bg-white/80 dark:bg-gray-800/80 p-3 rounded-full shadow-lg z-10"
      >
        ←
      </button>
      <button
        onClick={handleNext}
        className="absolute right-4 top-1/2 -translate-y-1/2 bg-white/80 dark:bg-gray-800/80 p-3 rounded-full shadow-lg z-10"
      >
        →
      </button>

      {/* Indicators */}
      <div className="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2 z-10">
        {images.map((_, index) => (
          <motion.button
            key={index}
            onClick={() => {
              setDirection(index > currentIndex ? 1 : -1)
              setCurrentIndex(index)
            }}
            animate={{
              scale: index === currentIndex ? 1.2 : 1,
              opacity: index === currentIndex ? 1 : 0.5,
            }}
            className="w-3 h-3 bg-white rounded-full"
          />
        ))}
      </div>
    </div>
  )
}

/**
 * USAGE
 *
 * <Carousel
 *   images={[
 *     { id: "1", url: "/image1.jpg", alt: "Image 1" },
 *     { id: "2", url: "/image2.jpg", alt: "Image 2" },
 *   ]}
 * />
 */

// ============================================================================
// COMPONENT 4: Drag-to-Scroll Carousel (Alternative)
// ============================================================================

interface DragCarouselProps {
  items: ReactNode[]
}

export function DragCarousel({ items }: DragCarouselProps) {
  const constraintsRef = useRef<HTMLDivElement>(null)

  return (
    <div
      ref={constraintsRef}
      className="overflow-hidden bg-gray-100 dark:bg-gray-800 rounded-lg p-4"
    >
      <motion.div
        drag="x"
        dragConstraints={constraintsRef}
        dragElastic={0.1}
        dragMomentum={false}
        className="flex gap-4 cursor-grab active:cursor-grabbing"
      >
        {items.map((item, index) => (
          <motion.div
            key={index}
            whileHover={{ scale: 1.05 }}
            className="min-w-[300px] h-64 bg-white dark:bg-gray-700 rounded-lg shadow-md flex items-center justify-center"
          >
            {item}
          </motion.div>
        ))}
      </motion.div>
    </div>
  )
}

/**
 * USAGE
 *
 * <DragCarousel
 *   items={[
 *     <div>Card 1</div>,
 *     <div>Card 2</div>,
 *     <div>Card 3</div>,
 *   ]}
 * />
 */

// ============================================================================
// COMPONENT 5: Tabs with Shared Underline (layoutId)
// ============================================================================

interface Tab {
  id: string
  label: string
  content: ReactNode
}

interface TabsProps {
  tabs: Tab[]
}

export function Tabs({ tabs }: TabsProps) {
  const [activeTab, setActiveTab] = useState(tabs[0].id)

  return (
    <div>
      {/* Tab buttons */}
      <div className="flex gap-4 border-b border-gray-200 dark:border-gray-700">
        {tabs.map((tab) => (
          <button
            key={tab.id}
            onClick={() => setActiveTab(tab.id)}
            className="relative px-4 py-2 text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"
          >
            {tab.label}

            {/* Shared underline */}
            {activeTab === tab.id && (
              <motion.div
                layoutId="activeTab"
                className="absolute bottom-0 left-0 right-0 h-0.5 bg-blue-600"
                transition={{ type: "spring", stiffness: 300, damping: 30 }}
              />
            )}
          </button>
        ))}
      </div>

      {/* Tab content */}
      <AnimatePresence mode="wait">
        <motion.div
          key={activeTab}
          initial={{ opacity: 0, y: 10 }}
          animate={{ opacity: 1, y: 0 }}
          exit={{ opacity: 0, y: -10 }}
          transition={{ duration: 0.2 }}
          className="p-4"
        >
          {tabs.find((tab) => tab.id === activeTab)?.content}
        </motion.div>
      </AnimatePresence>
    </div>
  )
}

/**
 * USAGE
 *
 * <Tabs
 *   tabs={[
 *     { id: "1", label: "Home", content: <p>Home content</p> },
 *     { id: "2", label: "Profile", content: <p>Profile content</p> },
 *     { id: "3", label: "Settings", content: <p>Settings content</p> },
 *   ]}
 * />
 */

// ============================================================================
// COMPONENT 6: Dropdown Menu
// ============================================================================

interface DropdownProps {
  trigger: ReactNode
  items: Array<{
    label: string
    onClick: () => void
  }>
}

export function Dropdown({ trigger, items }: DropdownProps) {
  const [isOpen, setIsOpen] = useState(false)

  return (
    <div className="relative">
      {/* Trigger */}
      <div onClick={() => setIsOpen(!isOpen)}>
        {trigger}
      </div>

      {/* Menu */}
      <AnimatePresence>
        {isOpen && (
          <>
            {/* Backdrop to close on click outside */}
            <div
              className="fixed inset-0 z-10"
              onClick={() => setIsOpen(false)}
            />

            {/* Dropdown */}
            <motion.div
              initial={{ opacity: 0, y: -10 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: -10 }}
              transition={{ duration: 0.2 }}
              className="absolute right-0 mt-2 w-48 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg z-20"
            >
              {items.map((item, index) => (
                <motion.button
                  key={index}
                  whileHover={{ backgroundColor: "rgba(0, 0, 0, 0.05)" }}
                  onClick={() => {
                    item.onClick()
                    setIsOpen(false)
                  }}
                  className="w-full text-left px-4 py-2 first:rounded-t-lg last:rounded-b-lg"
                >
                  {item.label}
                </motion.button>
              ))}
            </motion.div>
          </>
        )}
      </AnimatePresence>
    </div>
  )
}

/**
 * USAGE
 *
 * <Dropdown
 *   trigger={<button>Open Menu</button>}
 *   items={[
 *     { label: "Profile", onClick: () => console.log("Profile") },
 *     { label: "Settings", onClick: () => console.log("Settings") },
 *     { label: "Logout", onClick: () => console.log("Logout") },
 *   ]}
 * />
 */

// ============================================================================
// COMPONENT 7: Toast Notification
// ============================================================================

interface ToastProps {
  message: string
  type?: "success" | "error" | "info"
  isVisible: boolean
  onClose: () => void
}

export function Toast({ message, type = "info", isVisible, onClose }: ToastProps) {
  const colors = {
    success: "bg-green-500",
    error: "bg-red-500",
    info: "bg-blue-500",
  }

  return (
    <AnimatePresence>
      {isVisible && (
        <motion.div
          initial={{ opacity: 0, x: 300 }}
          animate={{ opacity: 1, x: 0 }}
          exit={{ opacity: 0, x: 300 }}
          transition={{ type: "spring", stiffness: 300, damping: 30 }}
          className={`fixed top-4 right-4 ${colors[type]} text-white px-6 py-4 rounded-lg shadow-lg z-50 flex items-center gap-4`}
        >
          <span>{message}</span>
          <motion.button
            whileHover={{ scale: 1.1 }}
            whileTap={{ scale: 0.9 }}
            onClick={onClose}
            className="text-white/80 hover:text-white"
          >
            ✕
          </motion.button>
        </motion.div>
      )}
    </AnimatePresence>
  )
}

/**
 * USAGE
 *
 * function App() {
 *   const [showToast, setShowToast] = useState(false)
 *
 *   return (
 *     <>
 *       <button onClick={() => setShowToast(true)}>Show Toast</button>
 *       <Toast
 *         message="Action completed!"
 *         type="success"
 *         isVisible={showToast}
 *         onClose={() => setShowToast(false)}
 *       />
 *     </>
 *   )
 * }
 */

// ============================================================================
// ALL COMPONENTS DEMO
// ============================================================================

export function UIComponentsDemo() {
  const [isModalOpen, setIsModalOpen] = useState(false)
  const [showToast, setShowToast] = useState(false)

  return (
    <div className="max-w-4xl mx-auto p-8 space-y-12">
      <h1 className="text-4xl font-bold mb-8">Motion UI Components</h1>

      {/* Modal */}
      <section>
        <h2 className="text-2xl font-bold mb-4">1. Modal Dialog</h2>
        <button
          onClick={() => setIsModalOpen(true)}
          className="px-4 py-2 bg-blue-600 text-white rounded"
        >
          Open Modal
        </button>
        <Modal
          isOpen={isModalOpen}
          onClose={() => setIsModalOpen(false)}
          title="Example Modal"
        >
          <p>This is a modal with smooth animations.</p>
        </Modal>
      </section>

      {/* Accordion */}
      <section>
        <h2 className="text-2xl font-bold mb-4">2. Accordion</h2>
        <Accordion
          items={[
            { id: "1", title: "Section 1", content: <p>Content for section 1</p> },
            { id: "2", title: "Section 2", content: <p>Content for section 2</p> },
            { id: "3", title: "Section 3", content: <p>Content for section 3</p> },
          ]}
        />
      </section>

      {/* Carousel */}
      <section>
        <h2 className="text-2xl font-bold mb-4">3. Carousel</h2>
        <Carousel
          images={[
            { id: "1", url: "https://via.placeholder.com/800x400/ff6b6b", alt: "Red" },
            { id: "2", url: "https://via.placeholder.com/800x400/4ecdc4", alt: "Teal" },
            { id: "3", url: "https://via.placeholder.com/800x400/ffe66d", alt: "Yellow" },
          ]}
        />
      </section>

      {/* Drag Carousel */}
      <section>
        <h2 className="text-2xl font-bold mb-4">4. Drag Carousel</h2>
        <DragCarousel
          items={[
            <div className="text-2xl font-bold">Card 1</div>,
            <div className="text-2xl font-bold">Card 2</div>,
            <div className="text-2xl font-bold">Card 3</div>,
            <div className="text-2xl font-bold">Card 4</div>,
          ]}
        />
      </section>

      {/* Tabs */}
      <section>
        <h2 className="text-2xl font-bold mb-4">5. Tabs</h2>
        <Tabs
          tabs={[
            { id: "home", label: "Home", content: <p>Home tab content</p> },
            { id: "profile", label: "Profile", content: <p>Profile tab content</p> },
            { id: "settings", label: "Settings", content: <p>Settings tab content</p> },
          ]}
        />
      </section>

      {/* Dropdown */}
      <section>
        <h2 className="text-2xl font-bold mb-4">6. Dropdown Menu</h2>
        <Dropdown
          trigger={
            <button className="px-4 py-2 bg-gray-600 text-white rounded">
              Open Menu
            </button>
          }
          items={[
            { label: "Profile", onClick: () => alert("Profile clicked") },
            { label: "Settings", onClick: () => alert("Settings clicked") },
            { label: "Logout", onClick: () => alert("Logout clicked") },
          ]}
        />
      </section>

      {/* Toast */}
      <section>
        <h2 className="text-2xl font-bold mb-4">7. Toast Notification</h2>
        <button
          onClick={() => setShowToast(true)}
          className="px-4 py-2 bg-green-600 text-white rounded"
        >
          Show Toast
        </button>
        <Toast
          message="Success! Action completed."
          type="success"
          isVisible={showToast}
          onClose={() => setShowToast(false)}
        />
      </section>
    </div>
  )
}

/**
 * PERFORMANCE TIPS
 *
 * 1. Add willChange for transforms:
 *    style={{ willChange: "transform, opacity" }}
 *
 * 2. Use AnimatePresence mode="wait" for sequential animations
 *
 * 3. For large lists in carousels, use virtualization:
 *    pnpm add react-window
 *
 * See ../references/performance-optimization.md for full guide
 */

/**
 * ACCESSIBILITY TIPS
 *
 * 1. Add keyboard support:
 *    - Escape to close modals
 *    - Arrow keys for carousels
 *    - Enter/Space for accordion triggers
 *
 * 2. Add ARIA attributes:
 *    - aria-expanded for accordions
 *    - aria-hidden for modals
 *    - role="dialog" for modals
 *
 * 3. Respect prefers-reduced-motion:
 *    <MotionConfig reducedMotion="user">
 *      <UIComponentsDemo />
 *    </MotionConfig>
 */