@polymorphism-tech/morph-spec 7.0.0 → 7.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.
- package/README.md +367 -367
- package/framework/agents.json +132 -127
- package/framework/rubrics/backend.json +41 -36
- package/framework/rules/ai-agents-standards.md +45 -15
- package/framework/rules/neon-standards.md +25 -26
- package/framework/standards/STANDARDS.json +1729 -1133
- package/framework/standards/ai-agents/agent-session.md +160 -0
- package/framework/standards/ai-agents/batching.md +193 -0
- package/framework/standards/ai-agents/context-providers.md +188 -0
- package/framework/standards/ai-agents/mcp-server.md +250 -0
- package/framework/standards/ai-agents/mcp-tools.md +197 -0
- package/framework/standards/ai-agents/middleware-patterns.md +207 -0
- package/framework/standards/ai-agents/modalities-image-gen.md +173 -0
- package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -0
- package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -0
- package/framework/standards/ai-agents/modalities-vision.md +201 -0
- package/framework/standards/ai-agents/multi-agent-patterns.md +155 -0
- package/framework/standards/ai-agents/production.md +114 -258
- package/framework/standards/ai-agents/prompts-in-db.md +214 -0
- package/framework/standards/ai-agents/providers/model-registry.md +158 -0
- package/framework/standards/ai-agents/rag-custom-pgvector.md +243 -0
- package/framework/standards/ai-agents/service-tier-flex.md +135 -0
- package/framework/standards/ai-agents/setup.md +142 -316
- package/framework/standards/ai-agents/structured-output.md +249 -0
- package/framework/standards/ai-agents/sweet-spot.md +109 -0
- package/framework/standards/ai-agents/workflows.md +114 -284
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +433 -429
- package/framework/standards/backend/api/validation.md +92 -88
- package/framework/standards/backend/database/postgresql/database.md +336 -332
- package/framework/standards/backend/database/vector-search-rag.md +599 -541
- package/framework/standards/backend/dotnet/fluent-validation-vsa.md +4 -0
- package/framework/standards/backend/dotnet/result-pattern.md +4 -0
- package/framework/standards/backend/dotnet/scalar-ui.md +176 -172
- package/framework/standards/backend/dotnet/scrutor.md +4 -0
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +278 -274
- package/framework/standards/backend/integrations/asaas/asaas-api.md +260 -216
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +354 -350
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +30 -19
- package/framework/standards/backend/integrations/resend/resend-email.md +400 -385
- package/framework/standards/data/nosql/cache/redis.md +106 -97
- package/framework/standards/data/nosql/cosmos-db.md +127 -118
- package/framework/standards/data/vector-search/rag-chunking.md +113 -104
- package/framework/standards/frontend/design-system/ai-image-generation.md +4 -0
- package/framework/standards/frontend/design-system/animations.md +164 -160
- package/framework/standards/frontend/design-system/naming.md +68 -64
- package/framework/standards/frontend/gsap/gsap-core.md +14 -8
- package/framework/standards/frontend/gsap/gsap-plugins.md +26 -21
- package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +9 -7
- package/framework/standards/frontend/nextjs/app-router.md +127 -123
- package/framework/standards/frontend/nextjs/components.md +136 -132
- package/framework/standards/frontend/nextjs/data-fetching.md +130 -126
- package/framework/standards/frontend/nextjs/forms.md +134 -128
- package/framework/standards/frontend/nextjs/motion-patterns.md +328 -322
- package/framework/standards/frontend/nextjs/naming-conventions.md +71 -67
- package/framework/standards/frontend/nextjs/nextjs-patterns.md +229 -225
- package/framework/standards/frontend/nextjs/project-structure.md +106 -102
- package/framework/standards/frontend/nextjs/state-management.md +76 -72
- package/framework/standards/frontend/nextjs/testing.md +115 -111
- package/framework/standards/infrastructure/docker/easypanel-deploy.md +196 -192
- package/framework/standards/infrastructure/neon/neon-pgrag.md +6 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +4 -0
- package/framework/standards/infrastructure/neon/neon-setup.md +5 -1
- package/framework/standards/integration/mcp/mcp-tools.md +471 -462
- package/framework/templates/REGISTRY.json +1745 -1239
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -0
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -0
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -0
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -0
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -0
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -0
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -0
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -0
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -0
- package/package.json +89 -83
- package/src/core/templates/template-registry.js +289 -256
- package/src/lib/generators/context-generator.js +516 -518
- package/src/lib/generators/scaffold-generator.js +23 -26
- package/framework/standards/infrastructure/neon/neon-semantic-kernel.md +0 -310
- package/framework/templates/integrations/semantic-kernel-neon.cs +0 -125
|
@@ -1,322 +1,328 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
import {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
- `
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
import {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
<Skeleton className="h-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
|
296
|
-
|
|
297
|
-
|
|
|
298
|
-
|
|
|
299
|
-
|
|
|
300
|
-
|
|
|
301
|
-
|
|
|
302
|
-
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
1
|
+
# Motion Patterns — Motion for React
|
|
2
|
+
|
|
3
|
+
> **Scope:** frontend-nextjs-ui
|
|
4
|
+
> **Layer:** 3
|
|
5
|
+
> **Keywords:** motion, framer-motion, animation, transition, variants, gesture, scroll
|
|
6
|
+
> **Load When:** nextjs-expert active
|
|
7
|
+
|
|
8
|
+
**Verified against:** Motion for React 12 (`motion`, imports from `motion/react`). Last-verified: 2026-05-20.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
Reference guide for implementing animations and transitions in Next.js projects using **Motion for React** — the library formerly published as `framer-motion`. The package is now `motion` and components import from `motion/react`; the `framer-motion` name only survives inside the Framer design tool. Covers the canonical variants library, design token bridge, scroll and gesture patterns, loading states, and mandatory WCAG accessibility compliance.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Section 1: Core Variants Library
|
|
17
|
+
|
|
18
|
+
Place this file at `motion/variants.ts` in your Next.js project root.
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { Variants } from 'motion/react';
|
|
22
|
+
import { durations, easings } from '@/lib/design-tokens'; // maps to design-system.md motion tokens
|
|
23
|
+
|
|
24
|
+
// Fade
|
|
25
|
+
export const fadeIn: Variants = {
|
|
26
|
+
hidden: { opacity: 0 },
|
|
27
|
+
visible: { opacity: 1, transition: { duration: durations.base, ease: easings.standard } },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Slide variations
|
|
31
|
+
export const slideInUp: Variants = {
|
|
32
|
+
hidden: { opacity: 0, y: 24 },
|
|
33
|
+
visible: { opacity: 1, y: 0, transition: { duration: durations.base, ease: easings.decelerate } },
|
|
34
|
+
};
|
|
35
|
+
export const slideInDown: Variants = {
|
|
36
|
+
hidden: { opacity: 0, y: -24 },
|
|
37
|
+
visible: { opacity: 1, y: 0, transition: { duration: durations.base, ease: easings.decelerate } },
|
|
38
|
+
};
|
|
39
|
+
export const slideInLeft: Variants = {
|
|
40
|
+
hidden: { opacity: 0, x: -24 },
|
|
41
|
+
visible: { opacity: 1, x: 0, transition: { duration: durations.base, ease: easings.decelerate } },
|
|
42
|
+
};
|
|
43
|
+
export const slideInRight: Variants = {
|
|
44
|
+
hidden: { opacity: 0, x: 24 },
|
|
45
|
+
visible: { opacity: 1, x: 0, transition: { duration: durations.base, ease: easings.decelerate } },
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Scale
|
|
49
|
+
export const scaleIn: Variants = {
|
|
50
|
+
hidden: { opacity: 0, scale: 0.92 },
|
|
51
|
+
visible: { opacity: 1, scale: 1, transition: { duration: durations.fast, ease: easings.bounce } },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Stagger container + child
|
|
55
|
+
export const staggerContainer: Variants = {
|
|
56
|
+
hidden: {},
|
|
57
|
+
visible: {
|
|
58
|
+
transition: {
|
|
59
|
+
staggerChildren: 0.08,
|
|
60
|
+
delayChildren: 0.1,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
export const staggerItem: Variants = {
|
|
65
|
+
hidden: { opacity: 0, y: 16 },
|
|
66
|
+
visible: { opacity: 1, y: 0, transition: { duration: durations.base, ease: easings.decelerate } },
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Page transition (for AnimatePresence in layout.tsx)
|
|
70
|
+
export const pageTransition: Variants = {
|
|
71
|
+
hidden: { opacity: 0, y: 8 },
|
|
72
|
+
visible: { opacity: 1, y: 0, transition: { duration: durations.base, ease: easings.decelerate } },
|
|
73
|
+
exit: { opacity: 0, y: -8, transition: { duration: durations.fast, ease: easings.accelerate } },
|
|
74
|
+
};
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Section 2: Design Token Bridge
|
|
80
|
+
|
|
81
|
+
This file bridges the CSS variables defined in `.morph/context/design-system.md` to JavaScript values consumed by Framer Motion. Place at `lib/design-tokens.ts`.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// lib/design-tokens.ts
|
|
85
|
+
// Maps CSS variables from .morph/context/design-system.md to JS values for Framer Motion
|
|
86
|
+
export const durations = {
|
|
87
|
+
instant: 0.05,
|
|
88
|
+
fast: 0.15,
|
|
89
|
+
base: 0.3,
|
|
90
|
+
slow: 0.5,
|
|
91
|
+
slower: 0.8,
|
|
92
|
+
} as const;
|
|
93
|
+
|
|
94
|
+
export const easings = {
|
|
95
|
+
standard: [0.4, 0, 0.2, 1] as [number, number, number, number],
|
|
96
|
+
decelerate: [0, 0, 0.2, 1] as [number, number, number, number],
|
|
97
|
+
accelerate: [0.4, 0, 1, 1] as [number, number, number, number],
|
|
98
|
+
bounce: [0.34, 1.56, 0.64, 1] as [number, number, number, number],
|
|
99
|
+
spring: [0.68, -0.55, 0.265, 1.55] as [number, number, number, number],
|
|
100
|
+
};
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
> **Rule**: Never hardcode duration numbers or easing arrays in component files. Always import from `@/lib/design-tokens`.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Section 3: Scroll Animations
|
|
108
|
+
|
|
109
|
+
Two approaches for triggering animations when elements enter the viewport:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
'use client';
|
|
113
|
+
import { motion, useInView } from 'motion/react';
|
|
114
|
+
import { useRef } from 'react';
|
|
115
|
+
import { fadeIn, slideInUp } from '@/motion/variants';
|
|
116
|
+
|
|
117
|
+
// Option A: whileInView (simpler, one-shot)
|
|
118
|
+
<motion.div
|
|
119
|
+
variants={slideInUp}
|
|
120
|
+
initial="hidden"
|
|
121
|
+
whileInView="visible"
|
|
122
|
+
viewport={{ once: true, margin: '-50px' }}
|
|
123
|
+
>
|
|
124
|
+
|
|
125
|
+
// Option B: useInView hook (more control)
|
|
126
|
+
function AnimatedSection({ children }: { children: React.ReactNode }) {
|
|
127
|
+
const ref = useRef(null);
|
|
128
|
+
const isInView = useInView(ref, { once: true, margin: '-100px' });
|
|
129
|
+
return (
|
|
130
|
+
<motion.div ref={ref} variants={fadeIn} initial="hidden" animate={isInView ? 'visible' : 'hidden'}>
|
|
131
|
+
{children}
|
|
132
|
+
</motion.div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
- Use `once: true` for content sections — animations should not replay on scroll up.
|
|
138
|
+
- Adjust `margin` to fire slightly before the element fully enters the viewport.
|
|
139
|
+
- Prefer `whileInView` for simple cases; use `useInView` when you need imperative control or conditional logic.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Section 4: Gesture Interactions
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import { durations, easings } from '@/lib/design-tokens';
|
|
147
|
+
|
|
148
|
+
// Hover + tap on cards
|
|
149
|
+
<motion.div
|
|
150
|
+
whileHover={{ y: -4, boxShadow: 'var(--shadow-lift)' }}
|
|
151
|
+
whileTap={{ scale: 0.98 }}
|
|
152
|
+
transition={{ duration: durations.fast, ease: easings.standard }}
|
|
153
|
+
>
|
|
154
|
+
|
|
155
|
+
// Drag interaction
|
|
156
|
+
<motion.div
|
|
157
|
+
drag
|
|
158
|
+
dragConstraints={{ left: 0, right: 0, top: 0, bottom: 0 }}
|
|
159
|
+
dragElastic={0.1}
|
|
160
|
+
whileDrag={{ scale: 1.05, cursor: 'grabbing' }}
|
|
161
|
+
>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- `whileHover` and `whileTap` are inline — they do not need variant definitions for simple state changes.
|
|
165
|
+
- Use `var(--shadow-lift)` from the design system rather than hardcoded box-shadow values.
|
|
166
|
+
- `dragElastic={0.1}` gives a subtle rubber-band feel without being distracting.
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Section 5: AnimatePresence (Page Transitions)
|
|
171
|
+
|
|
172
|
+
Integrate `AnimatePresence` at the layout level to animate between routes:
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
// app/layout.tsx
|
|
176
|
+
'use client';
|
|
177
|
+
import { AnimatePresence, motion } from 'motion/react';
|
|
178
|
+
import { usePathname } from 'next/navigation';
|
|
179
|
+
import { pageTransition } from '@/motion/variants';
|
|
180
|
+
|
|
181
|
+
export default function Layout({ children }: { children: React.ReactNode }) {
|
|
182
|
+
const pathname = usePathname();
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<html>
|
|
186
|
+
<body>
|
|
187
|
+
<AnimatePresence mode="wait">
|
|
188
|
+
<motion.main
|
|
189
|
+
key={pathname}
|
|
190
|
+
variants={pageTransition}
|
|
191
|
+
initial="hidden"
|
|
192
|
+
animate="visible"
|
|
193
|
+
exit="exit"
|
|
194
|
+
>
|
|
195
|
+
{children}
|
|
196
|
+
</motion.main>
|
|
197
|
+
</AnimatePresence>
|
|
198
|
+
</body>
|
|
199
|
+
</html>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
- `mode="wait"` ensures the exit animation completes before the entering page renders.
|
|
205
|
+
- The `key` prop must change on navigation — use `usePathname()` from `next/navigation`.
|
|
206
|
+
- Keep page transitions subtle (`y: 8`, fast duration) — aggressive transitions hurt perceived performance.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Section 6: Loading Patterns
|
|
211
|
+
|
|
212
|
+
Prefer CSS `animate-pulse` for skeleton loaders over Framer Motion — CSS animations run off the main thread and have lower performance overhead.
|
|
213
|
+
|
|
214
|
+
```tsx
|
|
215
|
+
// components/skeleton.tsx
|
|
216
|
+
import { cn } from '@/lib/utils';
|
|
217
|
+
|
|
218
|
+
function Skeleton({ className }: { className?: string }) {
|
|
219
|
+
return (
|
|
220
|
+
<div
|
|
221
|
+
className={cn(
|
|
222
|
+
'animate-pulse rounded-[var(--radius-md)] bg-[var(--color-neutral-200)]',
|
|
223
|
+
className
|
|
224
|
+
)}
|
|
225
|
+
/>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Skeleton card example
|
|
230
|
+
function SkeletonCard() {
|
|
231
|
+
return (
|
|
232
|
+
<div className="p-[var(--space-6)] space-y-[var(--space-4)]">
|
|
233
|
+
<Skeleton className="h-4 w-3/4" />
|
|
234
|
+
<Skeleton className="h-4 w-1/2" />
|
|
235
|
+
<Skeleton className="h-20 w-full" />
|
|
236
|
+
</div>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
- Use design token CSS variables (`--radius-md`, `--color-neutral-200`, `--space-*`) — never hardcode dimensions or colors.
|
|
242
|
+
- Skeleton shapes should mirror the actual content layout to minimize layout shift on load.
|
|
243
|
+
- For spinner/progress indicators that need JS control, use a simple Framer Motion `animate={{ rotate: 360 }}` with `repeat: Infinity`.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Section 7: WCAG Accessibility (MANDATORY)
|
|
248
|
+
|
|
249
|
+
> **This section is non-negotiable.** All animated components must respect `prefers-reduced-motion`.
|
|
250
|
+
|
|
251
|
+
### Hook pattern — wrap all animated components
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
'use client';
|
|
255
|
+
import { motion, useReducedMotion } from 'motion/react';
|
|
256
|
+
import { slideInUp } from '@/motion/variants';
|
|
257
|
+
|
|
258
|
+
function AnimatedCard({ children }: { children: React.ReactNode }) {
|
|
259
|
+
const shouldReduceMotion = useReducedMotion();
|
|
260
|
+
|
|
261
|
+
const variants = shouldReduceMotion
|
|
262
|
+
? { hidden: { opacity: 0 }, visible: { opacity: 1 } } // instant, no movement
|
|
263
|
+
: slideInUp;
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<motion.div variants={variants} initial="hidden" animate="visible">
|
|
267
|
+
{children}
|
|
268
|
+
</motion.div>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
When `shouldReduceMotion` is `true`:
|
|
274
|
+
- Remove all `x`, `y`, `scale` transforms.
|
|
275
|
+
- Keep opacity transitions (they do not cause vestibular issues).
|
|
276
|
+
- Do not set `transition: { duration: 0 }` — a very short fade is acceptable and less jarring than an instant pop-in.
|
|
277
|
+
|
|
278
|
+
### CSS fallback (add to global stylesheet)
|
|
279
|
+
|
|
280
|
+
```css
|
|
281
|
+
@media (prefers-reduced-motion: reduce) {
|
|
282
|
+
*, *::before, *::after {
|
|
283
|
+
animation-duration: 0.01ms !important;
|
|
284
|
+
transition-duration: 0.01ms !important;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
This CSS fallback catches animations outside Framer Motion (Tailwind `animate-*`, third-party components, CSS keyframes).
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Section 8: Anti-patterns
|
|
294
|
+
|
|
295
|
+
| Anti-pattern | Correct approach |
|
|
296
|
+
|---|---|
|
|
297
|
+
| `duration: 0.8` for UI interactions | `duration: durations.fast` (0.15) for micro-interactions |
|
|
298
|
+
| Hardcoded easing `[0.4, 0, 0.2, 1]` | `ease: easings.standard` (from design-tokens) |
|
|
299
|
+
| No `useReducedMotion()` check | Always check or use CSS media query |
|
|
300
|
+
| Animating everything on the page | Reserve motion for 3-5 meaningful moments per page |
|
|
301
|
+
| Forgetting `animation-fill-mode: backwards` in stagger | Will cause a flash before the animation starts |
|
|
302
|
+
| `AnimatePresence` without `key` changes | Exit animations will never fire |
|
|
303
|
+
| Using `motion` for skeleton loaders | Use CSS `animate-pulse` — lower performance cost |
|
|
304
|
+
| `npm install framer-motion` / `import ... from 'framer-motion'` | `npm install motion` / `import ... from 'motion/react'` — `framer-motion` is the legacy name |
|
|
305
|
+
| Need ScrollTrigger, SVG morph, or complex timelines | Use GSAP — see `frontend/gsap/gsap-core.md` |
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Section 9: Installation
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
npm install motion
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
> The package is `motion` and components import from `motion/react`. The old
|
|
316
|
+
> `framer-motion` package still resolves as a deprecated alias, but new code
|
|
317
|
+
> should depend on `motion` directly. The `framer-motion` import name is only
|
|
318
|
+
> kept for projects running inside the Framer design tool.
|
|
319
|
+
|
|
320
|
+
Verify the installed version supports the features used here (requires motion >= 11):
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
npm list motion
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
*MORPH-SPEC by Polymorphism Tech*
|