tribunal-kit 3.0.0 → 3.1.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.
Files changed (226) hide show
  1. package/.agent/ARCHITECTURE.md +99 -99
  2. package/.agent/GEMINI.md +52 -52
  3. package/.agent/agents/accessibility-reviewer.md +187 -220
  4. package/.agent/agents/ai-code-reviewer.md +199 -233
  5. package/.agent/agents/backend-specialist.md +215 -238
  6. package/.agent/agents/code-archaeologist.md +161 -181
  7. package/.agent/agents/database-architect.md +184 -207
  8. package/.agent/agents/debugger.md +191 -218
  9. package/.agent/agents/dependency-reviewer.md +103 -136
  10. package/.agent/agents/devops-engineer.md +218 -238
  11. package/.agent/agents/documentation-writer.md +201 -221
  12. package/.agent/agents/explorer-agent.md +160 -180
  13. package/.agent/agents/frontend-reviewer.md +160 -194
  14. package/.agent/agents/frontend-specialist.md +248 -237
  15. package/.agent/agents/game-developer.md +48 -52
  16. package/.agent/agents/logic-reviewer.md +116 -149
  17. package/.agent/agents/mobile-developer.md +200 -223
  18. package/.agent/agents/mobile-reviewer.md +162 -195
  19. package/.agent/agents/orchestrator.md +181 -211
  20. package/.agent/agents/penetration-tester.md +157 -174
  21. package/.agent/agents/performance-optimizer.md +183 -203
  22. package/.agent/agents/performance-reviewer.md +178 -211
  23. package/.agent/agents/product-manager.md +142 -162
  24. package/.agent/agents/product-owner.md +6 -25
  25. package/.agent/agents/project-planner.md +142 -162
  26. package/.agent/agents/qa-automation-engineer.md +225 -242
  27. package/.agent/agents/security-auditor.md +174 -194
  28. package/.agent/agents/seo-specialist.md +193 -213
  29. package/.agent/agents/sql-reviewer.md +161 -194
  30. package/.agent/agents/supervisor-agent.md +184 -203
  31. package/.agent/agents/swarm-worker-contracts.md +17 -17
  32. package/.agent/agents/swarm-worker-registry.md +46 -46
  33. package/.agent/agents/test-coverage-reviewer.md +160 -193
  34. package/.agent/agents/test-engineer.md +0 -21
  35. package/.agent/agents/type-safety-reviewer.md +175 -208
  36. package/.agent/patterns/generator.md +9 -9
  37. package/.agent/patterns/inversion.md +12 -12
  38. package/.agent/patterns/pipeline.md +9 -9
  39. package/.agent/patterns/reviewer.md +13 -13
  40. package/.agent/patterns/tool-wrapper.md +9 -9
  41. package/.agent/rules/GEMINI.md +63 -63
  42. package/.agent/scripts/compress_skills.py +167 -0
  43. package/.agent/scripts/consolidate_skills.py +173 -0
  44. package/.agent/scripts/deep_compress.py +202 -0
  45. package/.agent/scripts/minify_context.py +80 -0
  46. package/.agent/scripts/security_scan.py +1 -1
  47. package/.agent/scripts/strip_tribunal.py +41 -0
  48. package/.agent/skills/agent-organizer/SKILL.md +92 -126
  49. package/.agent/skills/agentic-patterns/SKILL.md +0 -70
  50. package/.agent/skills/ai-prompt-injection-defense/SKILL.md +126 -160
  51. package/.agent/skills/api-patterns/SKILL.md +123 -215
  52. package/.agent/skills/api-security-auditor/SKILL.md +143 -177
  53. package/.agent/skills/app-builder/SKILL.md +326 -50
  54. package/.agent/skills/app-builder/templates/SKILL.md +13 -15
  55. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +16 -16
  56. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +22 -22
  57. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +18 -18
  58. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +20 -20
  59. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +17 -17
  60. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +18 -18
  61. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +21 -21
  62. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +19 -19
  63. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +26 -26
  64. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +26 -26
  65. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +19 -19
  66. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +18 -18
  67. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +20 -20
  68. package/.agent/skills/appflow-wireframe/SKILL.md +87 -121
  69. package/.agent/skills/architecture/SKILL.md +82 -252
  70. package/.agent/skills/authentication-best-practices/SKILL.md +139 -173
  71. package/.agent/skills/bash-linux/SKILL.md +120 -154
  72. package/.agent/skills/behavioral-modes/SKILL.md +8 -69
  73. package/.agent/skills/brainstorming/SKILL.md +428 -104
  74. package/.agent/skills/building-native-ui/SKILL.md +143 -174
  75. package/.agent/skills/clean-code/SKILL.md +323 -360
  76. package/.agent/skills/code-review-checklist/SKILL.md +0 -62
  77. package/.agent/skills/config-validator/SKILL.md +107 -141
  78. package/.agent/skills/csharp-developer/SKILL.md +468 -528
  79. package/.agent/skills/database-design/SKILL.md +104 -369
  80. package/.agent/skills/deployment-procedures/SKILL.md +111 -145
  81. package/.agent/skills/devops-engineer/SKILL.md +295 -332
  82. package/.agent/skills/devops-incident-responder/SKILL.md +79 -113
  83. package/.agent/skills/doc.md +5 -5
  84. package/.agent/skills/documentation-templates/SKILL.md +19 -63
  85. package/.agent/skills/edge-computing/SKILL.md +123 -157
  86. package/.agent/skills/extract-design-system/SKILL.md +100 -134
  87. package/.agent/skills/framer-motion-expert/SKILL.md +111 -855
  88. package/.agent/skills/frontend-design/SKILL.md +151 -499
  89. package/.agent/skills/game-design-expert/SKILL.md +71 -105
  90. package/.agent/skills/game-engineering-expert/SKILL.md +88 -122
  91. package/.agent/skills/geo-fundamentals/SKILL.md +89 -124
  92. package/.agent/skills/github-operations/SKILL.md +279 -314
  93. package/.agent/skills/gsap-expert/SKILL.md +119 -826
  94. package/.agent/skills/i18n-localization/SKILL.md +104 -138
  95. package/.agent/skills/intelligent-routing/SKILL.md +159 -127
  96. package/.agent/skills/lint-and-validate/SKILL.md +8 -52
  97. package/.agent/skills/llm-engineering/SKILL.md +344 -357
  98. package/.agent/skills/local-first/SKILL.md +120 -154
  99. package/.agent/skills/mcp-builder/SKILL.md +84 -118
  100. package/.agent/skills/mobile-design/SKILL.md +213 -219
  101. package/.agent/skills/motion-engineering/SKILL.md +184 -0
  102. package/.agent/skills/nextjs-react-expert/SKILL.md +99 -698
  103. package/.agent/skills/nodejs-best-practices/SKILL.md +498 -559
  104. package/.agent/skills/observability/SKILL.md +293 -330
  105. package/.agent/skills/parallel-agents/SKILL.md +88 -122
  106. package/.agent/skills/performance-profiling/SKILL.md +217 -254
  107. package/.agent/skills/plan-writing/SKILL.md +84 -118
  108. package/.agent/skills/platform-engineer/SKILL.md +89 -123
  109. package/.agent/skills/playwright-best-practices/SKILL.md +128 -162
  110. package/.agent/skills/powershell-windows/SKILL.md +112 -146
  111. package/.agent/skills/python-patterns/SKILL.md +7 -35
  112. package/.agent/skills/python-pro/SKILL.md +148 -754
  113. package/.agent/skills/react-specialist/SKILL.md +123 -827
  114. package/.agent/skills/readme-builder/SKILL.md +15 -85
  115. package/.agent/skills/realtime-patterns/SKILL.md +269 -304
  116. package/.agent/skills/red-team-tactics/SKILL.md +10 -51
  117. package/.agent/skills/rust-pro/SKILL.md +623 -701
  118. package/.agent/skills/seo-fundamentals/SKILL.md +120 -154
  119. package/.agent/skills/server-management/SKILL.md +156 -190
  120. package/.agent/skills/shadcn-ui-expert/SKILL.md +172 -206
  121. package/.agent/skills/skill-creator/SKILL.md +18 -58
  122. package/.agent/skills/sql-pro/SKILL.md +579 -633
  123. package/.agent/skills/supabase-postgres-best-practices/SKILL.md +28 -68
  124. package/.agent/skills/swiftui-expert/SKILL.md +142 -176
  125. package/.agent/skills/systematic-debugging/SKILL.md +84 -118
  126. package/.agent/skills/tailwind-patterns/SKILL.md +516 -576
  127. package/.agent/skills/tdd-workflow/SKILL.md +103 -137
  128. package/.agent/skills/test-result-analyzer/SKILL.md +33 -73
  129. package/.agent/skills/testing-patterns/SKILL.md +512 -573
  130. package/.agent/skills/trend-researcher/SKILL.md +30 -71
  131. package/.agent/skills/ui-ux-pro-max/SKILL.md +0 -41
  132. package/.agent/skills/ui-ux-researcher/SKILL.md +51 -91
  133. package/.agent/skills/vue-expert/SKILL.md +127 -866
  134. package/.agent/skills/vulnerability-scanner/SKILL.md +354 -269
  135. package/.agent/skills/web-accessibility-auditor/SKILL.md +159 -193
  136. package/.agent/skills/web-design-guidelines/SKILL.md +17 -61
  137. package/.agent/skills/webapp-testing/SKILL.md +111 -145
  138. package/.agent/skills/whimsy-injector/SKILL.md +58 -132
  139. package/.agent/skills/workflow-optimizer/SKILL.md +28 -68
  140. package/.agent/workflows/api-tester.md +151 -151
  141. package/.agent/workflows/audit.md +127 -138
  142. package/.agent/workflows/brainstorm.md +110 -110
  143. package/.agent/workflows/changelog.md +112 -112
  144. package/.agent/workflows/create.md +124 -124
  145. package/.agent/workflows/debug.md +165 -189
  146. package/.agent/workflows/deploy.md +180 -189
  147. package/.agent/workflows/enhance.md +128 -151
  148. package/.agent/workflows/fix.md +114 -135
  149. package/.agent/workflows/generate.md +12 -4
  150. package/.agent/workflows/migrate.md +160 -160
  151. package/.agent/workflows/orchestrate.md +168 -168
  152. package/.agent/workflows/performance-benchmarker.md +114 -123
  153. package/.agent/workflows/plan.md +173 -173
  154. package/.agent/workflows/preview.md +80 -80
  155. package/.agent/workflows/refactor.md +161 -183
  156. package/.agent/workflows/review-ai.md +101 -129
  157. package/.agent/workflows/review.md +116 -116
  158. package/.agent/workflows/session.md +94 -94
  159. package/.agent/workflows/status.md +79 -79
  160. package/.agent/workflows/strengthen-skills.md +138 -139
  161. package/.agent/workflows/swarm.md +179 -179
  162. package/.agent/workflows/test.md +189 -211
  163. package/.agent/workflows/tribunal-backend.md +93 -113
  164. package/.agent/workflows/tribunal-database.md +94 -115
  165. package/.agent/workflows/tribunal-frontend.md +95 -118
  166. package/.agent/workflows/tribunal-full.md +92 -133
  167. package/.agent/workflows/tribunal-mobile.md +94 -119
  168. package/.agent/workflows/tribunal-performance.md +109 -133
  169. package/.agent/workflows/ui-ux-pro-max.md +122 -143
  170. package/package.json +1 -1
  171. package/.agent/skills/api-patterns/api-style.md +0 -42
  172. package/.agent/skills/api-patterns/auth.md +0 -24
  173. package/.agent/skills/api-patterns/documentation.md +0 -26
  174. package/.agent/skills/api-patterns/graphql.md +0 -41
  175. package/.agent/skills/api-patterns/rate-limiting.md +0 -31
  176. package/.agent/skills/api-patterns/response.md +0 -37
  177. package/.agent/skills/api-patterns/rest.md +0 -40
  178. package/.agent/skills/api-patterns/security-testing.md +0 -122
  179. package/.agent/skills/api-patterns/trpc.md +0 -41
  180. package/.agent/skills/api-patterns/versioning.md +0 -22
  181. package/.agent/skills/app-builder/agent-coordination.md +0 -71
  182. package/.agent/skills/app-builder/feature-building.md +0 -53
  183. package/.agent/skills/app-builder/project-detection.md +0 -34
  184. package/.agent/skills/app-builder/scaffolding.md +0 -118
  185. package/.agent/skills/app-builder/tech-stack.md +0 -40
  186. package/.agent/skills/architecture/context-discovery.md +0 -43
  187. package/.agent/skills/architecture/examples.md +0 -94
  188. package/.agent/skills/architecture/pattern-selection.md +0 -68
  189. package/.agent/skills/architecture/patterns-reference.md +0 -50
  190. package/.agent/skills/architecture/trade-off-analysis.md +0 -77
  191. package/.agent/skills/brainstorming/dynamic-questioning.md +0 -360
  192. package/.agent/skills/database-design/database-selection.md +0 -43
  193. package/.agent/skills/database-design/indexing.md +0 -39
  194. package/.agent/skills/database-design/migrations.md +0 -48
  195. package/.agent/skills/database-design/optimization.md +0 -36
  196. package/.agent/skills/database-design/orm-selection.md +0 -30
  197. package/.agent/skills/database-design/schema-design.md +0 -56
  198. package/.agent/skills/frontend-design/animation-guide.md +0 -331
  199. package/.agent/skills/frontend-design/color-system.md +0 -329
  200. package/.agent/skills/frontend-design/decision-trees.md +0 -418
  201. package/.agent/skills/frontend-design/motion-graphics.md +0 -306
  202. package/.agent/skills/frontend-design/typography-system.md +0 -363
  203. package/.agent/skills/frontend-design/ux-psychology.md +0 -1116
  204. package/.agent/skills/frontend-design/visual-effects.md +0 -383
  205. package/.agent/skills/intelligent-routing/router-manifest.md +0 -65
  206. package/.agent/skills/mobile-design/decision-trees.md +0 -516
  207. package/.agent/skills/mobile-design/mobile-backend.md +0 -491
  208. package/.agent/skills/mobile-design/mobile-color-system.md +0 -420
  209. package/.agent/skills/mobile-design/mobile-debugging.md +0 -122
  210. package/.agent/skills/mobile-design/mobile-design-thinking.md +0 -357
  211. package/.agent/skills/mobile-design/mobile-navigation.md +0 -458
  212. package/.agent/skills/mobile-design/mobile-performance.md +0 -767
  213. package/.agent/skills/mobile-design/mobile-testing.md +0 -356
  214. package/.agent/skills/mobile-design/mobile-typography.md +0 -433
  215. package/.agent/skills/mobile-design/platform-android.md +0 -666
  216. package/.agent/skills/mobile-design/platform-ios.md +0 -561
  217. package/.agent/skills/mobile-design/touch-psychology.md +0 -537
  218. package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +0 -312
  219. package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +0 -240
  220. package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +0 -490
  221. package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +0 -264
  222. package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +0 -581
  223. package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +0 -432
  224. package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +0 -684
  225. package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +0 -150
  226. 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+ mastery for React. Declarative animations, layout transitions, gestures, scroll-linked motion, AnimatePresence, useAnimate, useMotionValue, LazyMotion bundle optimization, and accessibility (prefers-reduced-motion). Use when building React component animations, page transitions, shared layout animations, or gesture-driven UI.
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.0
6
- last-updated: 2026-03-30
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 ExpertReact Animation Library
9
+ # Framer Motion 12+Dense Reference
11
10
 
12
- > Framer Motion is declarative-first. If you're writing imperative animation loops in React, you're fighting the framework.
13
- > Every `motion.div` must have a reason. Every `AnimatePresence` must have a `key`. Every `useMotionValue` must avoid re-renders.
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 API — Declarative Animations
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
- drag="x"
167
- dragTransition={{
168
- bounceStiffness: 600,
169
- bounceDamping: 20,
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
- // Parent controls when children animate
180
- const parent = {
181
- visible: {
182
- transition: {
183
- when: "beforeChildren", // "afterChildren", "beforeChildren"
184
- staggerChildren: 0.1,
185
- staggerDirection: 1, // 1 = forward, -1 = reverse
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
- ### Per-Property Transitions
193
-
54
+ ### Transitions
194
55
  ```tsx
195
- <motion.div
196
- animate={{ x: 100, opacity: 1 }}
197
- transition={{
198
- x: { type: "spring", stiffness: 300 },
199
- opacity: { duration: 0.2 },
200
- default: { duration: 0.5 }, // fallback for unlisted properties
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, backgroundColor: "#4338ca" }}
72
+ whileHover={{ scale: 1.05 }}
214
73
  whileTap={{ scale: 0.95 }}
215
- whileFocus={{ boxShadow: "0 0 0 3px rgba(66, 153, 225, 0.6)" }}
74
+ whileFocus={{ boxShadow: "0 0 0 3px rgba(66,153,225,0.6)" }}
216
75
  transition={{ type: "spring", stiffness: 400, damping: 15 }}
217
- >
218
- Click me
219
- </motion.button>
220
- ```
221
-
222
- ### Drag
223
-
224
- ```tsx
76
+ />
77
+ // Drag
225
78
  <motion.div
226
- drag // "x", "y", or true for both axes
227
- dragConstraints={{ left: -100, right: 100, top: -50, bottom: 50 }}
228
- dragElastic={0.2} // 0 = hard stop, 1 = free (default: 0.35)
229
- dragMomentum={true} // continue with inertia after release (default: true)
230
- dragSnapToOrigin // return to starting position on release
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
- once: true, // only animate once (do NOT re-trigger on scroll back)
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
- // The most powerful feature in Framer Motion
308
- // Elements with the same layoutId across renders morph into each other
309
-
310
- function ItemList({ selectedId, onSelect }) {
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
- // ❌ HALLUCINATION TRAP: layoutId elements MUST be in the same LayoutGroup
347
- // or be siblings under the same AnimatePresence. Cross-tree layoutId
348
- // requires wrapping in <LayoutGroup>.
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
- ### `LayoutGroup`
352
-
112
+ ### AnimatePresence
353
113
  ```tsx
354
- import { LayoutGroup } from "framer-motion";
355
-
356
- // Required when layoutId elements span different component trees
357
- <LayoutGroup>
358
- <Sidebar />
359
- <MainContent />
360
- </LayoutGroup>
361
-
362
- // Also useful to prevent layout animations from affecting siblings
363
- <LayoutGroup id="sidebar">
364
- {/* Layout animations here won't leak to the rest of the page */}
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
- // Chain multiple transforms
420
- const scale = useTransform(scrollYProgress, [0, 0.5, 1], [1, 1.5, 1]);
421
- const rotate = useTransform(scrollYProgress, [0, 1], [0, 360]);
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` (Imperative Control)
437
-
148
+ ### `useAnimate` Imperative sequences
438
149
  ```tsx
439
150
  import { useAnimate, stagger } from "framer-motion";
440
-
441
- function AnimatedList() {
442
- const [scope, animate] = useAnimate();
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
- ### `useVelocity`
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 xVelocity = useVelocity(x);
524
- const skewX = useTransform(xVelocity, [-1000, 0, 1000], [-15, 0, 15]);
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
- import { AnimatePresence, motion } from "framer-motion";
536
-
537
- function Notifications({ items }) {
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 & Accessibility
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
- // LazyMotion + domAnimation = ~5KB instead of ~30KB
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
- ### `prefers-reduced-motion` (Accessibility — Mandatory)
634
-
182
+ ### Accessibility
635
183
  ```tsx
636
184
  import { useReducedMotion } from "framer-motion";
637
-
638
- function AnimatedComponent() {
639
- const shouldReduceMotion = useReducedMotion();
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
- ### Cleanup on Unmount
706
-
707
- ```tsx
708
- // AnimatePresence handles unmount animations automatically
709
- // But if using useAnimate or manual animation controls:
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