tribunal-kit 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/ARCHITECTURE.md +99 -99
- package/.agent/GEMINI.md +52 -52
- package/.agent/agents/accessibility-reviewer.md +187 -220
- package/.agent/agents/ai-code-reviewer.md +199 -233
- package/.agent/agents/backend-specialist.md +215 -238
- package/.agent/agents/code-archaeologist.md +161 -181
- package/.agent/agents/database-architect.md +184 -207
- package/.agent/agents/debugger.md +191 -218
- package/.agent/agents/dependency-reviewer.md +103 -136
- package/.agent/agents/devops-engineer.md +218 -238
- package/.agent/agents/documentation-writer.md +201 -221
- package/.agent/agents/explorer-agent.md +160 -180
- package/.agent/agents/frontend-reviewer.md +160 -194
- package/.agent/agents/frontend-specialist.md +248 -237
- package/.agent/agents/game-developer.md +48 -52
- package/.agent/agents/logic-reviewer.md +116 -149
- package/.agent/agents/mobile-developer.md +200 -223
- package/.agent/agents/mobile-reviewer.md +162 -195
- package/.agent/agents/orchestrator.md +181 -211
- package/.agent/agents/penetration-tester.md +157 -174
- package/.agent/agents/performance-optimizer.md +183 -203
- package/.agent/agents/performance-reviewer.md +178 -211
- package/.agent/agents/precedence-reviewer.md +213 -0
- package/.agent/agents/product-manager.md +142 -162
- package/.agent/agents/product-owner.md +6 -25
- package/.agent/agents/project-planner.md +142 -162
- package/.agent/agents/qa-automation-engineer.md +225 -242
- package/.agent/agents/security-auditor.md +174 -194
- package/.agent/agents/seo-specialist.md +193 -213
- package/.agent/agents/sql-reviewer.md +161 -194
- package/.agent/agents/supervisor-agent.md +184 -203
- package/.agent/agents/swarm-worker-contracts.md +17 -17
- package/.agent/agents/swarm-worker-registry.md +46 -46
- package/.agent/agents/test-coverage-reviewer.md +160 -193
- package/.agent/agents/test-engineer.md +0 -21
- package/.agent/agents/type-safety-reviewer.md +175 -208
- package/.agent/patterns/generator.md +9 -9
- package/.agent/patterns/inversion.md +12 -12
- package/.agent/patterns/pipeline.md +9 -9
- package/.agent/patterns/reviewer.md +13 -13
- package/.agent/patterns/tool-wrapper.md +9 -9
- package/.agent/rules/GEMINI.md +63 -63
- package/.agent/scripts/append_flow.js +72 -0
- package/.agent/scripts/case_law_manager.py +525 -0
- package/.agent/scripts/compress_skills.py +167 -0
- package/.agent/scripts/consolidate_skills.py +173 -0
- package/.agent/scripts/deep_compress.py +202 -0
- package/.agent/scripts/minify_context.py +80 -0
- package/.agent/scripts/security_scan.py +1 -1
- package/.agent/scripts/skill_evolution.py +563 -0
- package/.agent/scripts/strip_tribunal.py +41 -0
- package/.agent/skills/agent-organizer/SKILL.md +100 -126
- package/.agent/skills/agentic-patterns/SKILL.md +0 -70
- package/.agent/skills/ai-prompt-injection-defense/SKILL.md +134 -160
- package/.agent/skills/api-patterns/SKILL.md +123 -215
- package/.agent/skills/api-security-auditor/SKILL.md +143 -177
- package/.agent/skills/app-builder/SKILL.md +334 -50
- package/.agent/skills/app-builder/templates/SKILL.md +13 -15
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +16 -16
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +22 -22
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +18 -18
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +20 -20
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +17 -17
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +18 -18
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +21 -21
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +19 -19
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +26 -26
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +26 -26
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +19 -19
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +18 -18
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +20 -20
- package/.agent/skills/appflow-wireframe/SKILL.md +95 -121
- package/.agent/skills/architecture/SKILL.md +169 -331
- package/.agent/skills/authentication-best-practices/SKILL.md +139 -173
- package/.agent/skills/bash-linux/SKILL.md +129 -154
- package/.agent/skills/behavioral-modes/SKILL.md +8 -69
- package/.agent/skills/brainstorming/SKILL.md +436 -104
- package/.agent/skills/building-native-ui/SKILL.md +152 -174
- package/.agent/skills/clean-code/SKILL.md +331 -360
- package/.agent/skills/code-review-checklist/SKILL.md +0 -62
- package/.agent/skills/config-validator/SKILL.md +115 -141
- package/.agent/skills/csharp-developer/SKILL.md +468 -528
- package/.agent/skills/database-design/SKILL.md +104 -369
- package/.agent/skills/deployment-procedures/SKILL.md +119 -145
- package/.agent/skills/devops-engineer/SKILL.md +295 -332
- package/.agent/skills/devops-incident-responder/SKILL.md +87 -113
- package/.agent/skills/doc.md +5 -5
- package/.agent/skills/documentation-templates/SKILL.md +27 -63
- package/.agent/skills/edge-computing/SKILL.md +131 -157
- package/.agent/skills/extract-design-system/SKILL.md +108 -134
- package/.agent/skills/framer-motion-expert/SKILL.md +111 -855
- package/.agent/skills/frontend-design/SKILL.md +151 -499
- package/.agent/skills/game-design-expert/SKILL.md +79 -105
- package/.agent/skills/game-engineering-expert/SKILL.md +96 -122
- package/.agent/skills/geo-fundamentals/SKILL.md +97 -124
- package/.agent/skills/github-operations/SKILL.md +279 -314
- package/.agent/skills/gsap-expert/SKILL.md +119 -826
- package/.agent/skills/i18n-localization/SKILL.md +113 -138
- package/.agent/skills/intelligent-routing/SKILL.md +167 -127
- package/.agent/skills/lint-and-validate/SKILL.md +16 -52
- package/.agent/skills/llm-engineering/SKILL.md +344 -357
- package/.agent/skills/local-first/SKILL.md +128 -154
- package/.agent/skills/mcp-builder/SKILL.md +92 -118
- package/.agent/skills/mobile-design/SKILL.md +213 -219
- package/.agent/skills/motion-engineering/SKILL.md +184 -0
- package/.agent/skills/nextjs-react-expert/SKILL.md +99 -698
- package/.agent/skills/nodejs-best-practices/SKILL.md +498 -559
- package/.agent/skills/observability/SKILL.md +293 -330
- package/.agent/skills/parallel-agents/SKILL.md +96 -122
- package/.agent/skills/performance-profiling/SKILL.md +217 -254
- package/.agent/skills/plan-writing/SKILL.md +92 -118
- package/.agent/skills/platform-engineer/SKILL.md +97 -123
- package/.agent/skills/playwright-best-practices/SKILL.md +137 -162
- package/.agent/skills/powershell-windows/SKILL.md +112 -146
- package/.agent/skills/project-idioms/SKILL.md +87 -0
- package/.agent/skills/python-patterns/SKILL.md +15 -35
- package/.agent/skills/python-pro/SKILL.md +148 -754
- package/.agent/skills/react-specialist/SKILL.md +123 -827
- package/.agent/skills/readme-builder/SKILL.md +23 -85
- package/.agent/skills/realtime-patterns/SKILL.md +269 -304
- package/.agent/skills/red-team-tactics/SKILL.md +18 -51
- package/.agent/skills/rust-pro/SKILL.md +623 -701
- package/.agent/skills/seo-fundamentals/SKILL.md +129 -154
- package/.agent/skills/server-management/SKILL.md +164 -190
- package/.agent/skills/shadcn-ui-expert/SKILL.md +181 -206
- package/.agent/skills/skill-creator/SKILL.md +24 -56
- package/.agent/skills/sql-pro/SKILL.md +579 -633
- package/.agent/skills/supabase-postgres-best-practices/SKILL.md +35 -66
- package/.agent/skills/swiftui-expert/SKILL.md +151 -176
- package/.agent/skills/systematic-debugging/SKILL.md +92 -118
- package/.agent/skills/tailwind-patterns/SKILL.md +516 -576
- package/.agent/skills/tdd-workflow/SKILL.md +111 -137
- package/.agent/skills/test-result-analyzer/SKILL.md +33 -73
- package/.agent/skills/testing-patterns/SKILL.md +512 -573
- package/.agent/skills/trend-researcher/SKILL.md +30 -71
- package/.agent/skills/ui-ux-pro-max/SKILL.md +8 -41
- package/.agent/skills/ui-ux-researcher/SKILL.md +51 -91
- package/.agent/skills/vue-expert/SKILL.md +127 -866
- package/.agent/skills/vulnerability-scanner/SKILL.md +354 -269
- package/.agent/skills/web-accessibility-auditor/SKILL.md +168 -193
- package/.agent/skills/web-design-guidelines/SKILL.md +25 -61
- package/.agent/skills/webapp-testing/SKILL.md +119 -145
- package/.agent/skills/whimsy-injector/SKILL.md +58 -132
- package/.agent/skills/workflow-optimizer/SKILL.md +28 -68
- package/.agent/workflows/api-tester.md +151 -151
- package/.agent/workflows/audit.md +127 -138
- package/.agent/workflows/brainstorm.md +110 -110
- package/.agent/workflows/changelog.md +112 -112
- package/.agent/workflows/create.md +124 -124
- package/.agent/workflows/debug.md +165 -189
- package/.agent/workflows/deploy.md +180 -189
- package/.agent/workflows/enhance.md +128 -151
- package/.agent/workflows/fix.md +114 -135
- package/.agent/workflows/generate.md +13 -4
- package/.agent/workflows/migrate.md +160 -160
- package/.agent/workflows/orchestrate.md +168 -168
- package/.agent/workflows/performance-benchmarker.md +114 -123
- package/.agent/workflows/plan.md +173 -173
- package/.agent/workflows/preview.md +80 -80
- package/.agent/workflows/refactor.md +161 -183
- package/.agent/workflows/review-ai.md +101 -129
- package/.agent/workflows/review.md +116 -116
- package/.agent/workflows/session.md +94 -94
- package/.agent/workflows/status.md +79 -79
- package/.agent/workflows/strengthen-skills.md +138 -139
- package/.agent/workflows/swarm.md +179 -179
- package/.agent/workflows/test.md +189 -211
- package/.agent/workflows/tribunal-backend.md +94 -113
- package/.agent/workflows/tribunal-database.md +95 -115
- package/.agent/workflows/tribunal-frontend.md +96 -118
- package/.agent/workflows/tribunal-full.md +93 -133
- package/.agent/workflows/tribunal-mobile.md +95 -119
- package/.agent/workflows/tribunal-performance.md +110 -133
- package/.agent/workflows/ui-ux-pro-max.md +122 -143
- package/README.md +30 -1
- package/bin/tribunal-kit.js +175 -12
- package/package.json +25 -4
- package/.agent/skills/api-patterns/api-style.md +0 -42
- package/.agent/skills/api-patterns/auth.md +0 -24
- package/.agent/skills/api-patterns/documentation.md +0 -26
- package/.agent/skills/api-patterns/graphql.md +0 -41
- package/.agent/skills/api-patterns/rate-limiting.md +0 -31
- package/.agent/skills/api-patterns/response.md +0 -37
- package/.agent/skills/api-patterns/rest.md +0 -40
- package/.agent/skills/api-patterns/security-testing.md +0 -122
- package/.agent/skills/api-patterns/trpc.md +0 -41
- package/.agent/skills/api-patterns/versioning.md +0 -22
- package/.agent/skills/app-builder/agent-coordination.md +0 -71
- package/.agent/skills/app-builder/feature-building.md +0 -53
- package/.agent/skills/app-builder/project-detection.md +0 -34
- package/.agent/skills/app-builder/scaffolding.md +0 -118
- package/.agent/skills/app-builder/tech-stack.md +0 -40
- package/.agent/skills/architecture/context-discovery.md +0 -43
- package/.agent/skills/architecture/examples.md +0 -94
- package/.agent/skills/architecture/pattern-selection.md +0 -68
- package/.agent/skills/architecture/patterns-reference.md +0 -50
- package/.agent/skills/architecture/trade-off-analysis.md +0 -77
- package/.agent/skills/brainstorming/dynamic-questioning.md +0 -360
- package/.agent/skills/database-design/database-selection.md +0 -43
- package/.agent/skills/database-design/indexing.md +0 -39
- package/.agent/skills/database-design/migrations.md +0 -48
- package/.agent/skills/database-design/optimization.md +0 -36
- package/.agent/skills/database-design/orm-selection.md +0 -30
- package/.agent/skills/database-design/schema-design.md +0 -56
- package/.agent/skills/frontend-design/animation-guide.md +0 -331
- package/.agent/skills/frontend-design/color-system.md +0 -329
- package/.agent/skills/frontend-design/decision-trees.md +0 -418
- package/.agent/skills/frontend-design/motion-graphics.md +0 -306
- package/.agent/skills/frontend-design/typography-system.md +0 -363
- package/.agent/skills/frontend-design/ux-psychology.md +0 -1116
- package/.agent/skills/frontend-design/visual-effects.md +0 -383
- package/.agent/skills/intelligent-routing/router-manifest.md +0 -65
- package/.agent/skills/mobile-design/decision-trees.md +0 -516
- package/.agent/skills/mobile-design/mobile-backend.md +0 -491
- package/.agent/skills/mobile-design/mobile-color-system.md +0 -420
- package/.agent/skills/mobile-design/mobile-debugging.md +0 -122
- package/.agent/skills/mobile-design/mobile-design-thinking.md +0 -357
- package/.agent/skills/mobile-design/mobile-navigation.md +0 -458
- package/.agent/skills/mobile-design/mobile-performance.md +0 -767
- package/.agent/skills/mobile-design/mobile-testing.md +0 -356
- package/.agent/skills/mobile-design/mobile-typography.md +0 -433
- package/.agent/skills/mobile-design/platform-android.md +0 -666
- package/.agent/skills/mobile-design/platform-ios.md +0 -561
- package/.agent/skills/mobile-design/touch-psychology.md +0 -537
- package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +0 -312
- package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +0 -240
- package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +0 -490
- package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +0 -264
- package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +0 -581
- package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +0 -432
- package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +0 -684
- package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +0 -150
- package/.agent/skills/vulnerability-scanner/checklists.md +0 -121
|
@@ -1,939 +1,195 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: framer-motion-expert
|
|
3
|
-
description: Framer Motion 12+
|
|
3
|
+
description: Framer Motion 12+ for React. Declarative animations, layout transitions, gestures, scroll-linked motion, AnimatePresence, useAnimate, LazyMotion. Use when building component animations, page transitions, shared layout animations, or gesture-driven UI.
|
|
4
4
|
allowed-tools: Read, Write, Edit, Glob, Grep
|
|
5
|
-
version: 1.0
|
|
6
|
-
last-updated: 2026-
|
|
7
|
-
applies-to-model: gemini-2.5-pro, claude-3-7-sonnet
|
|
5
|
+
version: 3.1.0
|
|
6
|
+
last-updated: 2026-04-06
|
|
8
7
|
---
|
|
9
8
|
|
|
10
|
-
# Framer Motion
|
|
9
|
+
# Framer Motion 12+ — Dense Reference
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
## Hallucination Traps (Read First)
|
|
12
|
+
- ❌ `<Motion>` (capital M) → ✅ `motion.div` (lowercase dot notation)
|
|
13
|
+
- ❌ `motion()` wrapper function → ✅ `motion.div`, `motion.span`, etc.
|
|
14
|
+
- ❌ `exitBeforeEnter` prop → ✅ `mode="wait"` on `<AnimatePresence>` (removed in FM7+)
|
|
15
|
+
- ❌ `exit` works without `<AnimatePresence>` → ✅ REQUIRES AnimatePresence wrapper
|
|
16
|
+
- ❌ `<AnimatePresence>` children without unique `key` → ✅ ALWAYS set `key`
|
|
17
|
+
- ❌ `stiffness + damping` AND `duration + bounce` together → ✅ pick ONE pair
|
|
18
|
+
- ❌ `m.div` without `<LazyMotion>` wrapper → ✅ REQUIRES LazyMotion parent
|
|
19
|
+
- ❌ `layout` animations with `domAnimation` feature set → ✅ requires `domMax`
|
|
20
|
+
- ❌ Force-animating `width`/`height`/`top`/`left` → ✅ use `x`,`y`,`scale`,`opacity` (GPU)
|
|
21
|
+
- ❌ `viewport.once` defaults to true → ✅ defaults to **false** — add `once: true` for entrance anims
|
|
14
22
|
|
|
15
23
|
---
|
|
16
24
|
|
|
17
|
-
## Core
|
|
18
|
-
|
|
19
|
-
### The `motion` Component
|
|
25
|
+
## Core Primitives
|
|
20
26
|
|
|
27
|
+
### `motion.X` / Declarative Animation
|
|
21
28
|
```tsx
|
|
22
29
|
import { motion } from "framer-motion";
|
|
23
|
-
|
|
24
|
-
// motion.div, motion.span, motion.button, motion.svg, motion.path, etc.
|
|
25
|
-
// Any HTML or SVG element can be prefixed with `motion.`
|
|
26
|
-
|
|
27
|
-
function FadeInBox() {
|
|
28
|
-
return (
|
|
29
|
-
<motion.div
|
|
30
|
-
initial={{ opacity: 0, y: 20 }}
|
|
31
|
-
animate={{ opacity: 1, y: 0 }}
|
|
32
|
-
transition={{ duration: 0.5, ease: "easeOut" }}
|
|
33
|
-
>
|
|
34
|
-
Hello, animated world
|
|
35
|
-
</motion.div>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ❌ HALLUCINATION TRAP: There is NO <Motion> component (capital M)
|
|
40
|
-
// ❌ HALLUCINATION TRAP: There is NO motion() function wrapper
|
|
41
|
-
// ✅ It's always motion.div, motion.span, etc. (lowercase dot notation)
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### The `animate` Prop
|
|
45
|
-
|
|
46
|
-
```tsx
|
|
47
|
-
// Object syntax (most common)
|
|
48
|
-
<motion.div animate={{ x: 100, opacity: 1 }} />
|
|
49
|
-
|
|
50
|
-
// Dynamic — responds to state changes automatically
|
|
51
|
-
const [isOpen, setIsOpen] = useState(false);
|
|
52
|
-
<motion.div animate={{ height: isOpen ? "auto" : 0 }} />
|
|
53
|
-
|
|
54
|
-
// ❌ HALLUCINATION TRAP: `animate={{ height: "auto" }}` only works
|
|
55
|
-
// in Framer Motion 11+ with the layout animation engine.
|
|
56
|
-
// For older versions, use explicit pixel values or the `layout` prop.
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### `initial`, `animate`, `exit`
|
|
60
|
-
|
|
61
|
-
```tsx
|
|
62
|
-
<AnimatePresence>
|
|
63
|
-
{isVisible && (
|
|
64
|
-
<motion.div
|
|
65
|
-
key="modal" // ← key is REQUIRED inside AnimatePresence
|
|
66
|
-
initial={{ opacity: 0, scale: 0.95 }}
|
|
67
|
-
animate={{ opacity: 1, scale: 1 }}
|
|
68
|
-
exit={{ opacity: 0, scale: 0.95 }}
|
|
69
|
-
transition={{ duration: 0.3 }}
|
|
70
|
-
>
|
|
71
|
-
Modal content
|
|
72
|
-
</motion.div>
|
|
73
|
-
)}
|
|
74
|
-
</AnimatePresence>
|
|
75
|
-
|
|
76
|
-
// ❌ HALLUCINATION TRAP: exit animations do NOT work without AnimatePresence
|
|
77
|
-
// ❌ HALLUCINATION TRAP: AnimatePresence children MUST have a unique `key`
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Variants (Declarative Animation Maps)
|
|
81
|
-
|
|
82
|
-
```tsx
|
|
83
|
-
const containerVariants = {
|
|
84
|
-
hidden: { opacity: 0 },
|
|
85
|
-
visible: {
|
|
86
|
-
opacity: 1,
|
|
87
|
-
transition: {
|
|
88
|
-
staggerChildren: 0.1, // stagger between children
|
|
89
|
-
delayChildren: 0.2, // delay before first child
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const itemVariants = {
|
|
95
|
-
hidden: { opacity: 0, y: 20 },
|
|
96
|
-
visible: { opacity: 1, y: 0 },
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
function List() {
|
|
100
|
-
return (
|
|
101
|
-
<motion.ul
|
|
102
|
-
variants={containerVariants}
|
|
103
|
-
initial="hidden"
|
|
104
|
-
animate="visible"
|
|
105
|
-
>
|
|
106
|
-
{items.map((item) => (
|
|
107
|
-
<motion.li key={item.id} variants={itemVariants}>
|
|
108
|
-
{item.name}
|
|
109
|
-
</motion.li>
|
|
110
|
-
))}
|
|
111
|
-
</motion.ul>
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Variant names propagate automatically to children
|
|
116
|
-
// Children inherit the current variant name from parent
|
|
117
|
-
// You do NOT need to set initial/animate on each child
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## Transitions
|
|
123
|
-
|
|
124
|
-
### Tween (Default)
|
|
125
|
-
|
|
126
|
-
```tsx
|
|
127
|
-
<motion.div
|
|
128
|
-
animate={{ x: 100 }}
|
|
129
|
-
transition={{
|
|
130
|
-
type: "tween", // default for most properties
|
|
131
|
-
duration: 0.5,
|
|
132
|
-
ease: "easeInOut", // or [0.42, 0, 0.58, 1] (cubic-bezier)
|
|
133
|
-
delay: 0.2,
|
|
134
|
-
repeat: 2, // number of repeats (Infinity for loop)
|
|
135
|
-
repeatType: "reverse", // "loop", "reverse", "mirror"
|
|
136
|
-
repeatDelay: 0.5,
|
|
137
|
-
}}
|
|
138
|
-
/>
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Spring (Physics-Based)
|
|
142
|
-
|
|
143
|
-
```tsx
|
|
144
|
-
<motion.div
|
|
145
|
-
animate={{ x: 100 }}
|
|
146
|
-
transition={{
|
|
147
|
-
type: "spring",
|
|
148
|
-
stiffness: 300, // spring tension (default: 100)
|
|
149
|
-
damping: 20, // resistance (default: 10)
|
|
150
|
-
mass: 1, // weight (default: 1)
|
|
151
|
-
bounce: 0.25, // shorthand: 0 = no bounce, 1 = max bounce
|
|
152
|
-
// duration + bounce is an alternative to stiffness + damping
|
|
153
|
-
duration: 0.8, // approximate duration (auto-calculates stiffness/damping)
|
|
154
|
-
}}
|
|
155
|
-
/>
|
|
156
|
-
|
|
157
|
-
// ❌ HALLUCINATION TRAP: You cannot use BOTH stiffness+damping AND duration+bounce
|
|
158
|
-
// Pick one pair. Using both causes unpredictable behavior.
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### Inertia
|
|
162
|
-
|
|
163
|
-
```tsx
|
|
164
|
-
// Inertia transitions are used after drag gestures
|
|
165
30
|
<motion.div
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
power: 0.8, // deceleration rate
|
|
171
|
-
timeConstant: 750, // ms to reach ~63% of projected distance
|
|
172
|
-
}}
|
|
31
|
+
initial={{ opacity: 0, y: 20 }}
|
|
32
|
+
animate={{ opacity: 1, y: 0 }}
|
|
33
|
+
exit={{ opacity: 0, y: -20 }}
|
|
34
|
+
transition={{ duration: 0.3, ease: "easeOut" }}
|
|
173
35
|
/>
|
|
174
36
|
```
|
|
175
37
|
|
|
176
|
-
### Orchestration
|
|
177
|
-
|
|
38
|
+
### Variants (Stagger / Orchestration)
|
|
178
39
|
```tsx
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
visible: {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
delayChildren: 0.3,
|
|
187
|
-
},
|
|
188
|
-
},
|
|
40
|
+
const container = {
|
|
41
|
+
hidden: {},
|
|
42
|
+
visible: { transition: { staggerChildren: 0.08, delayChildren: 0.1 } },
|
|
43
|
+
};
|
|
44
|
+
const item = {
|
|
45
|
+
hidden: { opacity: 0, y: 20, filter: "blur(4px)" },
|
|
46
|
+
visible: { opacity: 1, y: 0, filter: "blur(0px)", transition: { duration: 0.4 } },
|
|
189
47
|
};
|
|
48
|
+
<motion.ul variants={container} initial="hidden" animate="visible">
|
|
49
|
+
{list.map(e => <motion.li key={e.id} variants={item}>{e.name}</motion.li>)}
|
|
50
|
+
</motion.ul>
|
|
51
|
+
// Variant names propagate to children automatically — no need to set initial/animate on each child
|
|
190
52
|
```
|
|
191
53
|
|
|
192
|
-
###
|
|
193
|
-
|
|
54
|
+
### Transitions
|
|
194
55
|
```tsx
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
/>
|
|
56
|
+
// Tween (default)
|
|
57
|
+
transition={{ duration: 0.5, ease: "easeInOut", delay: 0.2, repeat: Infinity, repeatType: "reverse" }}
|
|
58
|
+
// Spring (physics)
|
|
59
|
+
transition={{ type: "spring", stiffness: 300, damping: 20 }} // OR use duration+bounce, not both
|
|
60
|
+
transition={{ type: "spring", duration: 0.8, bounce: 0.25 }}
|
|
61
|
+
// Per-property
|
|
62
|
+
transition={{ x: { type: "spring", stiffness: 300 }, opacity: { duration: 0.2 } }}
|
|
203
63
|
```
|
|
204
64
|
|
|
205
65
|
---
|
|
206
66
|
|
|
207
67
|
## Gestures
|
|
208
68
|
|
|
209
|
-
### Hover, Tap, Focus
|
|
210
|
-
|
|
211
69
|
```tsx
|
|
70
|
+
// Hover/Tap/Focus
|
|
212
71
|
<motion.button
|
|
213
|
-
whileHover={{ scale: 1.05
|
|
72
|
+
whileHover={{ scale: 1.05 }}
|
|
214
73
|
whileTap={{ scale: 0.95 }}
|
|
215
|
-
whileFocus={{ boxShadow: "0 0 0 3px rgba(66,
|
|
74
|
+
whileFocus={{ boxShadow: "0 0 0 3px rgba(66,153,225,0.6)" }}
|
|
216
75
|
transition={{ type: "spring", stiffness: 400, damping: 15 }}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
</motion.button>
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
### Drag
|
|
223
|
-
|
|
224
|
-
```tsx
|
|
76
|
+
/>
|
|
77
|
+
// Drag
|
|
225
78
|
<motion.div
|
|
226
|
-
drag
|
|
227
|
-
dragConstraints={{ left: -100, right: 100
|
|
228
|
-
dragElastic={0.2}
|
|
229
|
-
dragMomentum={true}
|
|
230
|
-
dragSnapToOrigin
|
|
231
|
-
onDragStart={(event, info) => console.log(info.point)}
|
|
232
|
-
onDrag={(event, info) => console.log(info.offset)}
|
|
233
|
-
onDragEnd={(event, info) => console.log(info.velocity)}
|
|
79
|
+
drag="x" // "x" | "y" | true
|
|
80
|
+
dragConstraints={{ left: -100, right: 100 }}
|
|
81
|
+
dragElastic={0.2} // 0=hard stop, 1=free
|
|
82
|
+
dragMomentum={true}
|
|
83
|
+
dragSnapToOrigin
|
|
234
84
|
/>
|
|
235
|
-
|
|
236
|
-
// Drag within a parent container
|
|
237
|
-
function DragInContainer() {
|
|
238
|
-
const constraintsRef = useRef(null);
|
|
239
|
-
return (
|
|
240
|
-
<motion.div ref={constraintsRef} style={{ width: 400, height: 400 }}>
|
|
241
|
-
<motion.div drag dragConstraints={constraintsRef} />
|
|
242
|
-
</motion.div>
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
### `whileInView` (Scroll-Triggered)
|
|
248
|
-
|
|
249
|
-
```tsx
|
|
85
|
+
// Scroll-triggered
|
|
250
86
|
<motion.div
|
|
251
87
|
initial={{ opacity: 0, y: 50 }}
|
|
252
88
|
whileInView={{ opacity: 1, y: 0 }}
|
|
253
|
-
viewport={{
|
|
254
|
-
|
|
255
|
-
amount: 0.3, // 30% of element must be visible
|
|
256
|
-
margin: "-100px", // shrink viewport detection area
|
|
257
|
-
}}
|
|
258
|
-
transition={{ duration: 0.6 }}
|
|
259
|
-
>
|
|
260
|
-
Appears on scroll
|
|
261
|
-
</motion.div>
|
|
262
|
-
|
|
263
|
-
// ❌ HALLUCINATION TRAP: `viewport.once` defaults to false
|
|
264
|
-
// This means the animation replays every time the element enters/exits
|
|
265
|
-
// Most designs want `once: true` for entrance animations
|
|
89
|
+
viewport={{ once: true, amount: 0.3 }} // ← once: true is almost always what you want
|
|
90
|
+
/>
|
|
266
91
|
```
|
|
267
92
|
|
|
268
93
|
---
|
|
269
94
|
|
|
270
95
|
## Layout Animations
|
|
271
96
|
|
|
272
|
-
### The `layout` Prop
|
|
273
|
-
|
|
274
|
-
```tsx
|
|
275
|
-
// The MAGIC of Framer Motion — automatic layout animations
|
|
276
|
-
// When a component's size or position changes,
|
|
277
|
-
// Framer Motion animates between the old and new layout automatically
|
|
278
|
-
|
|
279
|
-
function ExpandableCard({ isExpanded }) {
|
|
280
|
-
return (
|
|
281
|
-
<motion.div
|
|
282
|
-
layout // ← THIS is the magic prop
|
|
283
|
-
style={{
|
|
284
|
-
width: isExpanded ? 400 : 200,
|
|
285
|
-
height: isExpanded ? 300 : 100,
|
|
286
|
-
}}
|
|
287
|
-
transition={{ type: "spring", stiffness: 200, damping: 25 }}
|
|
288
|
-
>
|
|
289
|
-
<motion.p layout="position">
|
|
290
|
-
{/* layout="position" — only animate position, not size */}
|
|
291
|
-
Card content
|
|
292
|
-
</motion.p>
|
|
293
|
-
</motion.div>
|
|
294
|
-
);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// layout values:
|
|
298
|
-
// true — animate both position and size
|
|
299
|
-
// "position" — only animate position (prevents text reflow flicker)
|
|
300
|
-
// "size" — only animate size
|
|
301
|
-
// "preserve-aspect" — maintain aspect ratio during transition
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
### `layoutId` — Shared Element Transitions
|
|
305
|
-
|
|
306
97
|
```tsx
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
return (
|
|
312
|
-
<div className="grid">
|
|
313
|
-
{items.map((item) => (
|
|
314
|
-
<motion.div
|
|
315
|
-
key={item.id}
|
|
316
|
-
layoutId={`card-${item.id}`}
|
|
317
|
-
onClick={() => onSelect(item.id)}
|
|
318
|
-
>
|
|
319
|
-
<motion.h2 layoutId={`title-${item.id}`}>{item.title}</motion.h2>
|
|
320
|
-
</motion.div>
|
|
321
|
-
))}
|
|
322
|
-
|
|
323
|
-
<AnimatePresence>
|
|
324
|
-
{selectedId && (
|
|
325
|
-
<motion.div
|
|
326
|
-
layoutId={`card-${selectedId}`}
|
|
327
|
-
className="expanded-card"
|
|
328
|
-
>
|
|
329
|
-
<motion.h2 layoutId={`title-${selectedId}`}>
|
|
330
|
-
{items.find(i => i.id === selectedId).title}
|
|
331
|
-
</motion.h2>
|
|
332
|
-
<motion.p
|
|
333
|
-
initial={{ opacity: 0 }}
|
|
334
|
-
animate={{ opacity: 1 }}
|
|
335
|
-
exit={{ opacity: 0 }}
|
|
336
|
-
>
|
|
337
|
-
Expanded content...
|
|
338
|
-
</motion.p>
|
|
339
|
-
</motion.div>
|
|
340
|
-
)}
|
|
341
|
-
</AnimatePresence>
|
|
342
|
-
</div>
|
|
343
|
-
);
|
|
344
|
-
}
|
|
98
|
+
// layout prop — auto-animates position/size changes
|
|
99
|
+
<motion.div layout transition={{ type: "spring", stiffness: 200 }}>
|
|
100
|
+
{/* layout="position" = only position, layout="size" = only size */}
|
|
101
|
+
</motion.div>
|
|
345
102
|
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
|
|
103
|
+
// layoutId — shared element transition (morph between renders)
|
|
104
|
+
// List thumbnail → expanded modal:
|
|
105
|
+
<motion.div key={item.id} layoutId={`card-${item.id}`} /> // in list
|
|
106
|
+
<motion.div layoutId={`card-${selectedId}`} className="modal" /> // in modal
|
|
107
|
+
// ❌ TRAP: Cross-tree layoutId requires <LayoutGroup> wrapper
|
|
108
|
+
import { LayoutGroup } from "framer-motion";
|
|
109
|
+
<LayoutGroup><Sidebar /><MainContent /></LayoutGroup>
|
|
349
110
|
```
|
|
350
111
|
|
|
351
|
-
###
|
|
352
|
-
|
|
112
|
+
### AnimatePresence
|
|
353
113
|
```tsx
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
</LayoutGroup>
|
|
114
|
+
<AnimatePresence mode="sync"> {/* "sync"|"wait"|"popLayout" */}
|
|
115
|
+
{items.map(item => (
|
|
116
|
+
<motion.div key={item.id} /* ← REQUIRED */
|
|
117
|
+
initial={{ opacity: 0, height: 0 }}
|
|
118
|
+
animate={{ opacity: 1, height: "auto" }}
|
|
119
|
+
exit={{ opacity: 0, height: 0 }}
|
|
120
|
+
/>
|
|
121
|
+
))}
|
|
122
|
+
</AnimatePresence>
|
|
123
|
+
// mode="wait" — waits for exit before entering
|
|
124
|
+
// initial={false} on AnimatePresence — skip first-render animation
|
|
366
125
|
```
|
|
367
126
|
|
|
368
127
|
---
|
|
369
128
|
|
|
370
129
|
## Scroll Animations
|
|
371
130
|
|
|
372
|
-
### `useScroll`
|
|
373
|
-
|
|
374
|
-
```tsx
|
|
375
|
-
import { motion, useScroll, useTransform } from "framer-motion";
|
|
376
|
-
|
|
377
|
-
function ParallaxHero() {
|
|
378
|
-
const { scrollYProgress } = useScroll();
|
|
379
|
-
|
|
380
|
-
// Map scroll progress (0–1) to a y offset (-50 to 50)
|
|
381
|
-
const y = useTransform(scrollYProgress, [0, 1], [0, -200]);
|
|
382
|
-
const opacity = useTransform(scrollYProgress, [0, 0.5], [1, 0]);
|
|
383
|
-
|
|
384
|
-
return (
|
|
385
|
-
<motion.div style={{ y, opacity }}>
|
|
386
|
-
<h1>Parallax Hero</h1>
|
|
387
|
-
</motion.div>
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
```
|
|
391
|
-
|
|
392
|
-
### Element-Scoped Scroll Tracking
|
|
393
|
-
|
|
394
|
-
```tsx
|
|
395
|
-
function ProgressBar() {
|
|
396
|
-
const ref = useRef(null);
|
|
397
|
-
const { scrollYProgress } = useScroll({
|
|
398
|
-
target: ref, // track this element's scroll position
|
|
399
|
-
offset: ["start end", "end start"], // [trigger start, trigger end]
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
return (
|
|
403
|
-
<div ref={ref}>
|
|
404
|
-
<motion.div
|
|
405
|
-
style={{ scaleX: scrollYProgress, transformOrigin: "left" }}
|
|
406
|
-
className="progress-bar"
|
|
407
|
-
/>
|
|
408
|
-
Long content...
|
|
409
|
-
</div>
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
```
|
|
413
|
-
|
|
414
|
-
### `useTransform` Chains
|
|
415
|
-
|
|
416
131
|
```tsx
|
|
132
|
+
import { useScroll, useTransform } from "framer-motion";
|
|
133
|
+
// Page scroll progress (0–1)
|
|
417
134
|
const { scrollYProgress } = useScroll();
|
|
135
|
+
const y = useTransform(scrollYProgress, [0, 1], [0, -200]);
|
|
136
|
+
const opacity = useTransform(scrollYProgress, [0, 0.5], [1, 0]);
|
|
137
|
+
<motion.div style={{ y, opacity }} />
|
|
418
138
|
|
|
419
|
-
//
|
|
420
|
-
const
|
|
421
|
-
const
|
|
422
|
-
const color = useTransform(
|
|
423
|
-
scrollYProgress,
|
|
424
|
-
[0, 0.5, 1],
|
|
425
|
-
["#ff0000", "#00ff00", "#0000ff"]
|
|
426
|
-
);
|
|
427
|
-
|
|
428
|
-
// Derived transforms
|
|
429
|
-
const invertedY = useTransform(scrollYProgress, (v) => 1 - v);
|
|
139
|
+
// Element-scoped scroll
|
|
140
|
+
const ref = useRef(null);
|
|
141
|
+
const { scrollYProgress } = useScroll({ target: ref, offset: ["start end", "end start"] });
|
|
430
142
|
```
|
|
431
143
|
|
|
432
144
|
---
|
|
433
145
|
|
|
434
146
|
## Hooks
|
|
435
147
|
|
|
436
|
-
### `useAnimate`
|
|
437
|
-
|
|
148
|
+
### `useAnimate` — Imperative sequences
|
|
438
149
|
```tsx
|
|
439
150
|
import { useAnimate, stagger } from "framer-motion";
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
async function handleClick() {
|
|
445
|
-
// Imperative sequence
|
|
446
|
-
await animate(".list-item", { opacity: 1, y: 0 }, {
|
|
447
|
-
delay: stagger(0.1),
|
|
448
|
-
duration: 0.3,
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
await animate(".cta-button", { scale: [1, 1.1, 1] }, {
|
|
452
|
-
duration: 0.4,
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
return (
|
|
457
|
-
<div ref={scope}>
|
|
458
|
-
{items.map((item) => (
|
|
459
|
-
<div key={item.id} className="list-item" style={{ opacity: 0 }}>
|
|
460
|
-
{item.name}
|
|
461
|
-
</div>
|
|
462
|
-
))}
|
|
463
|
-
<button className="cta-button" onClick={handleClick}>
|
|
464
|
-
Show All
|
|
465
|
-
</button>
|
|
466
|
-
</div>
|
|
467
|
-
);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// ❌ HALLUCINATION TRAP: useAnimate returns [scope, animate]
|
|
471
|
-
// NOT [ref, controls] — that was the old useCycle/useAnimationControls API
|
|
472
|
-
```
|
|
473
|
-
|
|
474
|
-
### `useMotionValue`
|
|
475
|
-
|
|
476
|
-
```tsx
|
|
477
|
-
import { motion, useMotionValue, useTransform } from "framer-motion";
|
|
478
|
-
|
|
479
|
-
function RotatingCard() {
|
|
480
|
-
const x = useMotionValue(0);
|
|
481
|
-
const rotateY = useTransform(x, [-200, 200], [-45, 45]);
|
|
482
|
-
const background = useTransform(
|
|
483
|
-
x,
|
|
484
|
-
[-200, 0, 200],
|
|
485
|
-
["#ff008c", "#7700ff", "#00d4ff"]
|
|
486
|
-
);
|
|
487
|
-
|
|
488
|
-
return (
|
|
489
|
-
<motion.div
|
|
490
|
-
style={{ x, rotateY, background }}
|
|
491
|
-
drag="x"
|
|
492
|
-
dragConstraints={{ left: -200, right: 200 }}
|
|
493
|
-
/>
|
|
494
|
-
);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// ✅ useMotionValue does NOT trigger React re-renders
|
|
498
|
-
// This is the key performance advantage over useState for animations
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
### `useSpring`
|
|
502
|
-
|
|
503
|
-
```tsx
|
|
504
|
-
import { useSpring, useMotionValue } from "framer-motion";
|
|
505
|
-
|
|
506
|
-
const x = useMotionValue(0);
|
|
507
|
-
const springX = useSpring(x, {
|
|
508
|
-
stiffness: 300,
|
|
509
|
-
damping: 30,
|
|
510
|
-
restDelta: 0.001, // stop spring when movement is below this
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
// springX automatically follows x with spring physics
|
|
514
|
-
// Use springX in style={{ x: springX }} for smooth following
|
|
151
|
+
const [scope, animate] = useAnimate(); // ← returns [scope, animate] NOT [ref, controls]
|
|
152
|
+
await animate(".item", { opacity: 1 }, { delay: stagger(0.1) });
|
|
153
|
+
<div ref={scope}>...</div>
|
|
515
154
|
```
|
|
516
155
|
|
|
517
|
-
### `
|
|
518
|
-
|
|
156
|
+
### `useMotionValue` + `useTransform` — No re-renders
|
|
519
157
|
```tsx
|
|
520
|
-
import { useMotionValue, useVelocity, useTransform } from "framer-motion";
|
|
521
|
-
|
|
522
158
|
const x = useMotionValue(0);
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
// Skews element based on drag speed — creates a "rubber" feel
|
|
527
|
-
<motion.div style={{ x, skewX }} drag="x" />
|
|
159
|
+
const rotateY = useTransform(x, [-200, 200], [-45, 45]);
|
|
160
|
+
// ✅ useMotionValue does NOT trigger React re-renders — key perf advantage over useState
|
|
161
|
+
<motion.div style={{ x, rotateY }} drag="x" />
|
|
528
162
|
```
|
|
529
163
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
## AnimatePresence (Mount/Unmount Animations)
|
|
533
|
-
|
|
164
|
+
### `useSpring` / `useVelocity`
|
|
534
165
|
```tsx
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
return (
|
|
539
|
-
<AnimatePresence
|
|
540
|
-
mode="sync" // "sync" | "wait" | "popLayout"
|
|
541
|
-
initial={false} // skip initial animation on first render
|
|
542
|
-
>
|
|
543
|
-
{items.map((item) => (
|
|
544
|
-
<motion.div
|
|
545
|
-
key={item.id} // ← UNIQUE KEY IS MANDATORY
|
|
546
|
-
initial={{ opacity: 0, height: 0 }}
|
|
547
|
-
animate={{ opacity: 1, height: "auto" }}
|
|
548
|
-
exit={{ opacity: 0, height: 0 }}
|
|
549
|
-
transition={{ duration: 0.3 }}
|
|
550
|
-
>
|
|
551
|
-
{item.message}
|
|
552
|
-
</motion.div>
|
|
553
|
-
))}
|
|
554
|
-
</AnimatePresence>
|
|
555
|
-
);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// Modes:
|
|
559
|
-
// "sync" — new and old animate simultaneously (default)
|
|
560
|
-
// "wait" — wait for exit to finish before entering
|
|
561
|
-
// "popLayout" — uses FLIP to handle layout shifts during exit
|
|
562
|
-
|
|
563
|
-
// ❌ HALLUCINATION TRAP: `mode="wait"` used to be called `exitBeforeEnter`
|
|
564
|
-
// exitBeforeEnter was REMOVED in Framer Motion 7+
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
### Page Transitions (Next.js App Router)
|
|
568
|
-
|
|
569
|
-
```tsx
|
|
570
|
-
// layout.tsx
|
|
571
|
-
"use client";
|
|
572
|
-
import { AnimatePresence } from "framer-motion";
|
|
573
|
-
|
|
574
|
-
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
575
|
-
return (
|
|
576
|
-
<AnimatePresence mode="wait">
|
|
577
|
-
{children}
|
|
578
|
-
</AnimatePresence>
|
|
579
|
-
);
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// page.tsx
|
|
583
|
-
"use client";
|
|
584
|
-
import { motion } from "framer-motion";
|
|
585
|
-
|
|
586
|
-
export default function Page() {
|
|
587
|
-
return (
|
|
588
|
-
<motion.main
|
|
589
|
-
initial={{ opacity: 0, y: 20 }}
|
|
590
|
-
animate={{ opacity: 1, y: 0 }}
|
|
591
|
-
exit={{ opacity: 0, y: -20 }}
|
|
592
|
-
transition={{ duration: 0.3 }}
|
|
593
|
-
>
|
|
594
|
-
Page content
|
|
595
|
-
</motion.main>
|
|
596
|
-
);
|
|
597
|
-
}
|
|
166
|
+
const springX = useSpring(x, { stiffness: 300, damping: 30 });
|
|
167
|
+
const xVel = useVelocity(x);
|
|
168
|
+
const skewX = useTransform(xVel, [-1000, 0, 1000], [-15, 0, 15]);
|
|
598
169
|
```
|
|
599
170
|
|
|
600
171
|
---
|
|
601
172
|
|
|
602
|
-
## Performance &
|
|
603
|
-
|
|
604
|
-
### `m` vs `motion` — Bundle Optimization (LazyMotion)
|
|
173
|
+
## Performance & Bundle
|
|
605
174
|
|
|
606
175
|
```tsx
|
|
176
|
+
// LazyMotion — ~5KB vs ~30KB full bundle
|
|
607
177
|
import { LazyMotion, domAnimation, m } from "framer-motion";
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
// Use `m.div` instead of `motion.div` inside LazyMotion
|
|
611
|
-
|
|
612
|
-
function App() {
|
|
613
|
-
return (
|
|
614
|
-
<LazyMotion features={domAnimation}>
|
|
615
|
-
<m.div
|
|
616
|
-
initial={{ opacity: 0 }}
|
|
617
|
-
animate={{ opacity: 1 }}
|
|
618
|
-
>
|
|
619
|
-
Lightweight animation
|
|
620
|
-
</m.div>
|
|
621
|
-
</LazyMotion>
|
|
622
|
-
);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// For full feature set (layout animations, drag, etc.):
|
|
626
|
-
import { domMax } from "framer-motion";
|
|
627
|
-
<LazyMotion features={domMax}>
|
|
628
|
-
|
|
629
|
-
// ❌ HALLUCINATION TRAP: m.div does NOT work without LazyMotion wrapper
|
|
630
|
-
// ❌ HALLUCINATION TRAP: layout animations require domMax, not domAnimation
|
|
178
|
+
// domAnimation ≈ 5KB | domMax ≈ 20KB (needed for layout/drag)
|
|
179
|
+
<LazyMotion features={domAnimation}><m.div animate={{ opacity: 1 }} /></LazyMotion>
|
|
631
180
|
```
|
|
632
181
|
|
|
633
|
-
###
|
|
634
|
-
|
|
182
|
+
### Accessibility
|
|
635
183
|
```tsx
|
|
636
184
|
import { useReducedMotion } from "framer-motion";
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
return (
|
|
642
|
-
<motion.div
|
|
643
|
-
animate={{
|
|
644
|
-
x: shouldReduceMotion ? 0 : 100,
|
|
645
|
-
opacity: 1, // opacity changes are always safe
|
|
646
|
-
}}
|
|
647
|
-
transition={{
|
|
648
|
-
duration: shouldReduceMotion ? 0 : 0.5,
|
|
649
|
-
}}
|
|
650
|
-
>
|
|
651
|
-
Accessible animation
|
|
652
|
-
</motion.div>
|
|
653
|
-
);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
// ✅ RULE: Opacity and color transitions are acceptable for reduced-motion users
|
|
657
|
-
// ❌ RULE: Position, scale, and rotation animations must be disabled or minimized
|
|
658
|
-
// ❌ RULE: Auto-playing looping animations MUST stop under reduced motion
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
### Performance Rules
|
|
662
|
-
|
|
663
|
-
```
|
|
664
|
-
✅ Use useMotionValue instead of useState for animation-driven values
|
|
665
|
-
→ useMotionValue does NOT trigger React re-renders
|
|
666
|
-
|
|
667
|
-
✅ Use useTransform to derive values from motion values
|
|
668
|
-
→ Avoids recomputation in the React render cycle
|
|
669
|
-
|
|
670
|
-
✅ Animate transform properties (x, y, scale, rotate, opacity)
|
|
671
|
-
→ These are GPU-composited and do not trigger layout
|
|
672
|
-
|
|
673
|
-
✅ Use LazyMotion + m.div in production to reduce bundle size
|
|
674
|
-
→ Cuts Framer Motion from ~30KB to ~5KB
|
|
675
|
-
|
|
676
|
-
❌ Do NOT animate width, height, top, left, padding, margin
|
|
677
|
-
→ These trigger layout recalculation every frame
|
|
678
|
-
|
|
679
|
-
❌ Do NOT create new motion values inside render
|
|
680
|
-
→ Causes GC pressure and breaks animation continuity
|
|
681
|
-
|
|
682
|
-
❌ Do NOT nest AnimatePresence unnecessarily
|
|
683
|
-
→ Each instance adds overhead to the reconciler
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
---
|
|
687
|
-
|
|
688
|
-
## Framework Considerations
|
|
689
|
-
|
|
690
|
-
### Next.js (App Router)
|
|
691
|
-
|
|
692
|
-
```tsx
|
|
693
|
-
// Framer Motion components MUST be client components
|
|
694
|
-
"use client"; // ← required at top of file
|
|
695
|
-
|
|
696
|
-
import { motion } from "framer-motion";
|
|
697
|
-
|
|
698
|
-
// ❌ HALLUCINATION TRAP: motion.div CANNOT be used in Server Components
|
|
699
|
-
// The `motion` component requires browser APIs (DOM, window, RAF)
|
|
700
|
-
|
|
701
|
-
// For server-rendered pages with client animations,
|
|
702
|
-
// split into a server-rendered layout + client animation wrapper
|
|
185
|
+
const reduce = useReducedMotion();
|
|
186
|
+
// opacity/color: always safe | position/scale/rotation: must be disabled when reduce=true
|
|
187
|
+
<motion.div animate={{ x: reduce ? 0 : 100, opacity: 1 }} transition={{ duration: reduce ? 0 : 0.5 }} />
|
|
703
188
|
```
|
|
704
189
|
|
|
705
|
-
###
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
useEffect(() => {
|
|
712
|
-
const controls = animate(".element", { opacity: 1 });
|
|
713
|
-
|
|
714
|
-
return () => {
|
|
715
|
-
controls.stop(); // ← ALWAYS stop animations on cleanup
|
|
716
|
-
};
|
|
717
|
-
}, []);
|
|
718
|
-
```
|
|
719
|
-
|
|
720
|
-
### Vue / Non-React Usage
|
|
721
|
-
|
|
722
|
-
```
|
|
723
|
-
⚠️ Framer Motion is React-ONLY.
|
|
724
|
-
|
|
725
|
-
For Vue, use:
|
|
726
|
-
- @vueuse/motion (Vue 3 motion library, similar API)
|
|
727
|
-
- vue-kinesis (gesture-based)
|
|
728
|
-
|
|
729
|
-
For Svelte, use:
|
|
730
|
-
- svelte/animate and svelte/transition (built-in)
|
|
731
|
-
- Motion One (framework-agnostic, by Framer Motion creator)
|
|
732
|
-
|
|
733
|
-
For vanilla JS, use:
|
|
734
|
-
- Motion One (motion.dev) — from the same team, but framework-agnostic
|
|
735
|
-
- GSAP — use the gsap-expert skill instead
|
|
736
|
-
|
|
737
|
-
// ❌ HALLUCINATION TRAP: There is NO "framer-motion/vue" or "framer-motion/svelte"
|
|
738
|
-
// Framer Motion is exclusively a React library
|
|
739
|
-
```
|
|
740
|
-
|
|
741
|
-
---
|
|
742
|
-
|
|
743
|
-
## Common Animation Patterns
|
|
744
|
-
|
|
745
|
-
### Staggered List Entrance
|
|
746
|
-
|
|
747
|
-
```tsx
|
|
748
|
-
const container = {
|
|
749
|
-
hidden: {},
|
|
750
|
-
visible: {
|
|
751
|
-
transition: { staggerChildren: 0.08, delayChildren: 0.1 },
|
|
752
|
-
},
|
|
753
|
-
};
|
|
754
|
-
|
|
755
|
-
const item = {
|
|
756
|
-
hidden: { opacity: 0, y: 20, filter: "blur(4px)" },
|
|
757
|
-
visible: {
|
|
758
|
-
opacity: 1,
|
|
759
|
-
y: 0,
|
|
760
|
-
filter: "blur(0px)",
|
|
761
|
-
transition: { duration: 0.4, ease: [0.25, 0.46, 0.45, 0.94] },
|
|
762
|
-
},
|
|
763
|
-
};
|
|
764
|
-
|
|
765
|
-
<motion.ul variants={container} initial="hidden" animate="visible">
|
|
766
|
-
{list.map((entry) => (
|
|
767
|
-
<motion.li key={entry.id} variants={item}>
|
|
768
|
-
{entry.name}
|
|
769
|
-
</motion.li>
|
|
770
|
-
))}
|
|
771
|
-
</motion.ul>
|
|
772
|
-
```
|
|
773
|
-
|
|
774
|
-
### Smooth Tab Indicator
|
|
775
|
-
|
|
776
|
-
```tsx
|
|
777
|
-
function Tabs({ tabs, activeTab }) {
|
|
778
|
-
return (
|
|
779
|
-
<div className="tab-list">
|
|
780
|
-
{tabs.map((tab) => (
|
|
781
|
-
<button
|
|
782
|
-
key={tab.id}
|
|
783
|
-
onClick={() => setActiveTab(tab.id)}
|
|
784
|
-
className="tab"
|
|
785
|
-
>
|
|
786
|
-
{tab.label}
|
|
787
|
-
{activeTab === tab.id && (
|
|
788
|
-
<motion.div
|
|
789
|
-
layoutId="tab-indicator" // ← shared element
|
|
790
|
-
className="tab-underline"
|
|
791
|
-
transition={{ type: "spring", stiffness: 500, damping: 30 }}
|
|
792
|
-
/>
|
|
793
|
-
)}
|
|
794
|
-
</button>
|
|
795
|
-
))}
|
|
796
|
-
</div>
|
|
797
|
-
);
|
|
798
|
-
}
|
|
799
|
-
```
|
|
800
|
-
|
|
801
|
-
### Card Hover Effect
|
|
802
|
-
|
|
803
|
-
```tsx
|
|
804
|
-
<motion.div
|
|
805
|
-
whileHover={{ y: -4, boxShadow: "0 20px 40px rgba(0,0,0,0.15)" }}
|
|
806
|
-
transition={{ type: "spring", stiffness: 300, damping: 20 }}
|
|
807
|
-
className="card"
|
|
808
|
-
>
|
|
809
|
-
<motion.img
|
|
810
|
-
whileHover={{ scale: 1.03 }}
|
|
811
|
-
transition={{ duration: 0.3 }}
|
|
812
|
-
src={imageUrl}
|
|
813
|
-
/>
|
|
814
|
-
</motion.div>
|
|
815
|
-
```
|
|
816
|
-
|
|
817
|
-
### Expandable Accordion
|
|
818
|
-
|
|
819
|
-
```tsx
|
|
820
|
-
function Accordion({ title, children, isOpen, onToggle }) {
|
|
821
|
-
return (
|
|
822
|
-
<div>
|
|
823
|
-
<button onClick={onToggle}>{title}</button>
|
|
824
|
-
<AnimatePresence initial={false}>
|
|
825
|
-
{isOpen && (
|
|
826
|
-
<motion.div
|
|
827
|
-
key="content"
|
|
828
|
-
initial={{ height: 0, opacity: 0 }}
|
|
829
|
-
animate={{ height: "auto", opacity: 1 }}
|
|
830
|
-
exit={{ height: 0, opacity: 0 }}
|
|
831
|
-
transition={{ duration: 0.3, ease: [0.04, 0.62, 0.23, 0.98] }}
|
|
832
|
-
style={{ overflow: "hidden" }}
|
|
833
|
-
>
|
|
834
|
-
{children}
|
|
835
|
-
</motion.div>
|
|
836
|
-
)}
|
|
837
|
-
</AnimatePresence>
|
|
838
|
-
</div>
|
|
839
|
-
);
|
|
840
|
-
}
|
|
841
|
-
```
|
|
842
|
-
|
|
843
|
-
### Scroll Progress Bar
|
|
844
|
-
|
|
845
|
-
```tsx
|
|
846
|
-
function ScrollProgressBar() {
|
|
847
|
-
const { scrollYProgress } = useScroll();
|
|
848
|
-
|
|
849
|
-
return (
|
|
850
|
-
<motion.div
|
|
851
|
-
style={{
|
|
852
|
-
scaleX: scrollYProgress,
|
|
853
|
-
transformOrigin: "left",
|
|
854
|
-
position: "fixed",
|
|
855
|
-
top: 0,
|
|
856
|
-
left: 0,
|
|
857
|
-
right: 0,
|
|
858
|
-
height: 4,
|
|
859
|
-
background: "linear-gradient(90deg, #06b6d4, #8b5cf6)",
|
|
860
|
-
zIndex: 50,
|
|
861
|
-
}}
|
|
862
|
-
/>
|
|
863
|
-
);
|
|
864
|
-
}
|
|
865
|
-
```
|
|
866
|
-
|
|
867
|
-
---
|
|
868
|
-
|
|
869
|
-
## Output Format
|
|
870
|
-
|
|
871
|
-
When this skill produces or reviews code, structure your output as follows:
|
|
872
|
-
|
|
873
|
-
```
|
|
874
|
-
━━━ Framer Motion Expert Report ━━━━━━━━━━━━━━━━━━━━━━━━
|
|
875
|
-
Skill: Framer Motion Expert
|
|
876
|
-
FM Version: 12+
|
|
877
|
-
Scope: [N files · N components]
|
|
878
|
-
─────────────────────────────────────────────────
|
|
879
|
-
✅ Passed: [checks that passed, or "All clean"]
|
|
880
|
-
⚠️ Warnings: [non-blocking issues, or "None"]
|
|
881
|
-
❌ Blocked: [blocking issues requiring fix, or "None"]
|
|
882
|
-
─────────────────────────────────────────────────
|
|
883
|
-
VBC status: PENDING → VERIFIED
|
|
884
|
-
Evidence: [test output / lint pass / compile success]
|
|
885
|
-
```
|
|
886
|
-
|
|
887
|
-
**VBC (Verification-Before-Completion) is mandatory.**
|
|
888
|
-
Do not mark status as VERIFIED until concrete terminal evidence is provided.
|
|
889
|
-
|
|
890
|
-
---
|
|
891
|
-
|
|
892
|
-
## 🤖 LLM-Specific Traps
|
|
893
|
-
|
|
894
|
-
AI coding assistants often fall into specific bad habits when generating Framer Motion code. These are strictly forbidden:
|
|
895
|
-
|
|
896
|
-
1. **Inventing Component Names:** There is no `<Motion>`, `<MotionDiv>`, `<AnimatedDiv>`, or `motion()` wrapper function. The correct API is `motion.div`, `motion.span`, etc. (lowercase dot notation).
|
|
897
|
-
2. **Using Deprecated `exitBeforeEnter`:** This prop was removed in Framer Motion 7+. The correct replacement is `mode="wait"` on `AnimatePresence`.
|
|
898
|
-
3. **Missing `key` in AnimatePresence:** Every direct child of `AnimatePresence` MUST have a unique `key` prop for exit animations to work.
|
|
899
|
-
4. **Using `motion.div` in Server Components:** Framer Motion requires browser APIs. Components using `motion` MUST be marked `"use client"` in Next.js App Router.
|
|
900
|
-
5. **Confusing `useAnimation` (removed) with `useAnimate`:** The modern imperative API is `useAnimate()` which returns `[scope, animate]`, NOT `useAnimation()` which returned animation controls.
|
|
901
|
-
6. **Using `m.div` Without `LazyMotion`:** The `m` component is only valid inside a `<LazyMotion>` provider. Using it standalone produces no animations.
|
|
902
|
-
7. **Layout Animations Without `domMax`:** The `layout` prop and `layoutId` require `domMax` features in LazyMotion, not `domAnimation`.
|
|
903
|
-
8. **Over-Animating:** Adding spring animations to every element creates visual chaos. Be intentional — every animation must serve a UX purpose.
|
|
904
|
-
9. **Hallucinating Non-React Exports:** There is no `framer-motion/vue`, `framer-motion/svelte`, or `framer-motion/vanilla`. It is React-only.
|
|
905
|
-
10. **Animating Layout Properties:** Animating `width`, `height`, `top`, `left` directly instead of using `x`, `y`, `scale`, or the `layout` prop causes layout thrashing.
|
|
906
|
-
|
|
907
|
-
---
|
|
908
|
-
|
|
909
|
-
## 🏛️ Tribunal Integration (Anti-Hallucination)
|
|
910
|
-
|
|
911
|
-
**Slash command: `/review` or `/tribunal-full`**
|
|
912
|
-
**Active reviewers: `logic-reviewer` · `security-auditor` · `frontend-reviewer` · `performance-optimizer`**
|
|
913
|
-
|
|
914
|
-
### ❌ Forbidden AI Tropes
|
|
915
|
-
|
|
916
|
-
1. **Blind Assumptions:** Never make an assumption without documenting it clearly with `// VERIFY: [reason]`.
|
|
917
|
-
2. **Silent Degradation:** Catching and suppressing animation errors without logging.
|
|
918
|
-
3. **Context Amnesia:** Forgetting the user's constraints (e.g., generating `motion.div` in a Server Component).
|
|
919
|
-
4. **Accessibility Neglect:** Failing to implement `prefers-reduced-motion` support is a hard block.
|
|
920
|
-
|
|
921
|
-
### ✅ Pre-Flight Self-Audit
|
|
922
|
-
|
|
923
|
-
Review these questions before confirming output:
|
|
924
|
-
```
|
|
925
|
-
✅ Did I use "use client" for all components using motion.*?
|
|
926
|
-
✅ Did I add a unique `key` to every AnimatePresence child?
|
|
927
|
-
✅ Did I use mode="wait" instead of the removed exitBeforeEnter?
|
|
928
|
-
✅ Did I use useMotionValue (not useState) for animation values?
|
|
929
|
-
✅ Did I animate transforms (x, y, scale) instead of layout props?
|
|
930
|
-
✅ Did I handle prefers-reduced-motion with useReducedMotion()?
|
|
931
|
-
✅ Did I use LazyMotion + m.div for bundle optimization?
|
|
932
|
-
✅ Did I stop/cleanup animations on unmount?
|
|
933
|
-
```
|
|
934
|
-
|
|
935
|
-
### 🛑 Verification-Before-Completion (VBC) Protocol
|
|
936
|
-
|
|
937
|
-
**CRITICAL:** You must follow a strict "evidence-based closeout" state machine.
|
|
938
|
-
- ❌ **Forbidden:** Declaring a task complete because the output "looks correct."
|
|
939
|
-
- ✅ **Required:** You are explicitly forbidden from finalizing any task without providing **concrete evidence** (terminal output, passing tests, compile success, or equivalent proof) that your output works as intended.
|
|
190
|
+
### Rules
|
|
191
|
+
- ✅ Animate: `x`, `y`, `scale`, `rotation`, `opacity` (GPU composited)
|
|
192
|
+
- ❌ Never animate: `width`, `height`, `top`, `left`, `padding`, `margin` (causes layout thrashing)
|
|
193
|
+
- ✅ `useMotionValue` for animation-driven values — never `useState`
|
|
194
|
+
- ❌ Nest `AnimatePresence` only when necessary — each adds reconciler overhead
|
|
195
|
+
- `"use client"` required in Next.js — `motion.div` cannot run in Server Components
|