atlas-pipeline-mcp 1.0.25 → 1.0.27
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 +50 -4
- package/dist/mcp.js +182 -0
- package/dist/mcp.js.map +1 -1
- package/dist/tools/animation-studio.d.ts +83 -0
- package/dist/tools/animation-studio.d.ts.map +1 -0
- package/dist/tools/animation-studio.js +1064 -0
- package/dist/tools/animation-studio.js.map +1 -0
- package/dist/tools/api-integration-helper.d.ts +141 -0
- package/dist/tools/api-integration-helper.d.ts.map +1 -0
- package/dist/tools/api-integration-helper.js +907 -0
- package/dist/tools/api-integration-helper.js.map +1 -0
- package/dist/tools/css-architecture-wizard.d.ts +86 -0
- package/dist/tools/css-architecture-wizard.d.ts.map +1 -0
- package/dist/tools/css-architecture-wizard.js +790 -0
- package/dist/tools/css-architecture-wizard.js.map +1 -0
- package/dist/tools/frontend-performance-doctor.d.ts +108 -0
- package/dist/tools/frontend-performance-doctor.d.ts.map +1 -0
- package/dist/tools/frontend-performance-doctor.js +731 -0
- package/dist/tools/frontend-performance-doctor.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,1064 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation Studio
|
|
3
|
+
*
|
|
4
|
+
* Professional animation generator for frontend developers:
|
|
5
|
+
* - Creates smooth CSS animations and keyframes
|
|
6
|
+
* - Generates Framer Motion code for React
|
|
7
|
+
* - Creates GSAP animation timelines
|
|
8
|
+
* - Provides Lottie integration guidance
|
|
9
|
+
* - Micro-interactions library
|
|
10
|
+
* - Performance-optimized animations
|
|
11
|
+
* - Accessibility-friendly (respects prefers-reduced-motion)
|
|
12
|
+
*/
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
import { logger } from '../utils.js';
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Validation Schema
|
|
17
|
+
// ============================================================================
|
|
18
|
+
export const AnimationRequestSchema = z.object({
|
|
19
|
+
type: z.enum(['entrance', 'exit', 'hover', 'loading', 'scroll', 'gesture', 'transition', 'micro-interaction']),
|
|
20
|
+
element: z.string().min(1, 'Element description required'),
|
|
21
|
+
style: z.enum(['smooth', 'bouncy', 'elastic', 'sharp', 'natural', 'playful']).optional().default('smooth'),
|
|
22
|
+
duration: z.number().min(100).max(5000).optional().default(300),
|
|
23
|
+
library: z.enum(['css', 'framer-motion', 'gsap', 'react-spring', 'anime-js']),
|
|
24
|
+
framework: z.enum(['react', 'vue', 'svelte', 'vanilla']).optional().default('react'),
|
|
25
|
+
includeReducedMotion: z.boolean().optional().default(true),
|
|
26
|
+
customParams: z.record(z.any()).optional()
|
|
27
|
+
});
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Animation Presets Database
|
|
30
|
+
// ============================================================================
|
|
31
|
+
const EASING_FUNCTIONS = {
|
|
32
|
+
smooth: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
33
|
+
bouncy: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
|
|
34
|
+
elastic: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
|
|
35
|
+
sharp: 'cubic-bezier(0.4, 0, 0.6, 1)',
|
|
36
|
+
natural: 'cubic-bezier(0.25, 0.1, 0.25, 1)',
|
|
37
|
+
playful: 'cubic-bezier(0.68, -0.6, 0.32, 1.6)'
|
|
38
|
+
};
|
|
39
|
+
// Default fallback animation preset
|
|
40
|
+
const DEFAULT_ANIMATION_PRESET = {
|
|
41
|
+
name: 'Fade In',
|
|
42
|
+
category: 'entrance',
|
|
43
|
+
description: 'Simple opacity fade in',
|
|
44
|
+
preview: '0% opacity → 100% opacity',
|
|
45
|
+
css: `@keyframes fadeIn {
|
|
46
|
+
from { opacity: 0; }
|
|
47
|
+
to { opacity: 1; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.fade-in {
|
|
51
|
+
animation: fadeIn 0.3s ease-out forwards;
|
|
52
|
+
}`,
|
|
53
|
+
framerMotion: `const fadeIn = {
|
|
54
|
+
initial: { opacity: 0 },
|
|
55
|
+
animate: { opacity: 1 },
|
|
56
|
+
transition: { duration: 0.3, ease: "easeOut" }
|
|
57
|
+
};`,
|
|
58
|
+
gsap: `gsap.from(element, {
|
|
59
|
+
opacity: 0,
|
|
60
|
+
duration: 0.3,
|
|
61
|
+
ease: "power2.out"
|
|
62
|
+
});`
|
|
63
|
+
};
|
|
64
|
+
const ENTRANCE_ANIMATIONS = {
|
|
65
|
+
fadeIn: {
|
|
66
|
+
name: 'Fade In',
|
|
67
|
+
category: 'entrance',
|
|
68
|
+
description: 'Simple opacity fade in',
|
|
69
|
+
preview: '0% opacity → 100% opacity',
|
|
70
|
+
css: `@keyframes fadeIn {
|
|
71
|
+
from { opacity: 0; }
|
|
72
|
+
to { opacity: 1; }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.fade-in {
|
|
76
|
+
animation: fadeIn 0.3s ease-out forwards;
|
|
77
|
+
}`,
|
|
78
|
+
framerMotion: `const fadeIn = {
|
|
79
|
+
initial: { opacity: 0 },
|
|
80
|
+
animate: { opacity: 1 },
|
|
81
|
+
transition: { duration: 0.3, ease: "easeOut" }
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Usage: <motion.div {...fadeIn}>Content</motion.div>`,
|
|
85
|
+
gsap: `gsap.from(element, {
|
|
86
|
+
opacity: 0,
|
|
87
|
+
duration: 0.3,
|
|
88
|
+
ease: "power2.out"
|
|
89
|
+
});`
|
|
90
|
+
},
|
|
91
|
+
slideUp: {
|
|
92
|
+
name: 'Slide Up',
|
|
93
|
+
category: 'entrance',
|
|
94
|
+
description: 'Slide in from bottom with fade',
|
|
95
|
+
preview: 'Translate Y 20px → 0 with opacity',
|
|
96
|
+
css: `@keyframes slideUp {
|
|
97
|
+
from {
|
|
98
|
+
opacity: 0;
|
|
99
|
+
transform: translateY(20px);
|
|
100
|
+
}
|
|
101
|
+
to {
|
|
102
|
+
opacity: 1;
|
|
103
|
+
transform: translateY(0);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.slide-up {
|
|
108
|
+
animation: slideUp 0.4s ease-out forwards;
|
|
109
|
+
}`,
|
|
110
|
+
framerMotion: `const slideUp = {
|
|
111
|
+
initial: { opacity: 0, y: 20 },
|
|
112
|
+
animate: { opacity: 1, y: 0 },
|
|
113
|
+
transition: { duration: 0.4, ease: [0.4, 0, 0.2, 1] }
|
|
114
|
+
};`,
|
|
115
|
+
gsap: `gsap.from(element, {
|
|
116
|
+
opacity: 0,
|
|
117
|
+
y: 20,
|
|
118
|
+
duration: 0.4,
|
|
119
|
+
ease: "power2.out"
|
|
120
|
+
});`
|
|
121
|
+
},
|
|
122
|
+
scaleIn: {
|
|
123
|
+
name: 'Scale In',
|
|
124
|
+
category: 'entrance',
|
|
125
|
+
description: 'Scale from small to full size',
|
|
126
|
+
preview: 'Scale 0.9 → 1 with opacity',
|
|
127
|
+
css: `@keyframes scaleIn {
|
|
128
|
+
from {
|
|
129
|
+
opacity: 0;
|
|
130
|
+
transform: scale(0.9);
|
|
131
|
+
}
|
|
132
|
+
to {
|
|
133
|
+
opacity: 1;
|
|
134
|
+
transform: scale(1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.scale-in {
|
|
139
|
+
animation: scaleIn 0.3s ease-out forwards;
|
|
140
|
+
}`,
|
|
141
|
+
framerMotion: `const scaleIn = {
|
|
142
|
+
initial: { opacity: 0, scale: 0.9 },
|
|
143
|
+
animate: { opacity: 1, scale: 1 },
|
|
144
|
+
transition: { duration: 0.3, ease: "easeOut" }
|
|
145
|
+
};`,
|
|
146
|
+
gsap: `gsap.from(element, {
|
|
147
|
+
opacity: 0,
|
|
148
|
+
scale: 0.9,
|
|
149
|
+
duration: 0.3,
|
|
150
|
+
ease: "back.out(1.7)"
|
|
151
|
+
});`
|
|
152
|
+
},
|
|
153
|
+
bounceIn: {
|
|
154
|
+
name: 'Bounce In',
|
|
155
|
+
category: 'entrance',
|
|
156
|
+
description: 'Playful bounce entrance',
|
|
157
|
+
preview: 'Scale with overshoot bounce',
|
|
158
|
+
css: `@keyframes bounceIn {
|
|
159
|
+
0% {
|
|
160
|
+
opacity: 0;
|
|
161
|
+
transform: scale(0.3);
|
|
162
|
+
}
|
|
163
|
+
50% {
|
|
164
|
+
transform: scale(1.05);
|
|
165
|
+
}
|
|
166
|
+
70% {
|
|
167
|
+
transform: scale(0.9);
|
|
168
|
+
}
|
|
169
|
+
100% {
|
|
170
|
+
opacity: 1;
|
|
171
|
+
transform: scale(1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.bounce-in {
|
|
176
|
+
animation: bounceIn 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) forwards;
|
|
177
|
+
}`,
|
|
178
|
+
framerMotion: `const bounceIn = {
|
|
179
|
+
initial: { opacity: 0, scale: 0.3 },
|
|
180
|
+
animate: { opacity: 1, scale: 1 },
|
|
181
|
+
transition: {
|
|
182
|
+
type: "spring",
|
|
183
|
+
stiffness: 260,
|
|
184
|
+
damping: 20
|
|
185
|
+
}
|
|
186
|
+
};`,
|
|
187
|
+
gsap: `gsap.from(element, {
|
|
188
|
+
opacity: 0,
|
|
189
|
+
scale: 0.3,
|
|
190
|
+
duration: 0.5,
|
|
191
|
+
ease: "elastic.out(1, 0.5)"
|
|
192
|
+
});`
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
const HOVER_ANIMATIONS = {
|
|
196
|
+
lift: {
|
|
197
|
+
name: 'Lift',
|
|
198
|
+
category: 'hover',
|
|
199
|
+
description: 'Subtle lift with shadow',
|
|
200
|
+
preview: 'Translate Y -4px with shadow increase',
|
|
201
|
+
css: `.lift-hover {
|
|
202
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.lift-hover:hover {
|
|
206
|
+
transform: translateY(-4px);
|
|
207
|
+
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
|
|
208
|
+
}`,
|
|
209
|
+
framerMotion: `const liftHover = {
|
|
210
|
+
whileHover: {
|
|
211
|
+
y: -4,
|
|
212
|
+
boxShadow: "0 12px 24px rgba(0, 0, 0, 0.15)",
|
|
213
|
+
transition: { duration: 0.2 }
|
|
214
|
+
}
|
|
215
|
+
};`,
|
|
216
|
+
gsap: `element.addEventListener('mouseenter', () => {
|
|
217
|
+
gsap.to(element, {
|
|
218
|
+
y: -4,
|
|
219
|
+
boxShadow: "0 12px 24px rgba(0, 0, 0, 0.15)",
|
|
220
|
+
duration: 0.2
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
element.addEventListener('mouseleave', () => {
|
|
224
|
+
gsap.to(element, {
|
|
225
|
+
y: 0,
|
|
226
|
+
boxShadow: "0 4px 6px rgba(0, 0, 0, 0.1)",
|
|
227
|
+
duration: 0.2
|
|
228
|
+
});
|
|
229
|
+
});`
|
|
230
|
+
},
|
|
231
|
+
glow: {
|
|
232
|
+
name: 'Glow',
|
|
233
|
+
category: 'hover',
|
|
234
|
+
description: 'Glowing effect on hover',
|
|
235
|
+
preview: 'Box shadow glow animation',
|
|
236
|
+
css: `.glow-hover {
|
|
237
|
+
transition: box-shadow 0.3s ease;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.glow-hover:hover {
|
|
241
|
+
box-shadow: 0 0 20px rgba(59, 130, 246, 0.5),
|
|
242
|
+
0 0 40px rgba(59, 130, 246, 0.3);
|
|
243
|
+
}`,
|
|
244
|
+
framerMotion: `const glowHover = {
|
|
245
|
+
whileHover: {
|
|
246
|
+
boxShadow: [
|
|
247
|
+
"0 0 0px rgba(59, 130, 246, 0)",
|
|
248
|
+
"0 0 20px rgba(59, 130, 246, 0.5), 0 0 40px rgba(59, 130, 246, 0.3)"
|
|
249
|
+
],
|
|
250
|
+
transition: { duration: 0.3 }
|
|
251
|
+
}
|
|
252
|
+
};`,
|
|
253
|
+
},
|
|
254
|
+
scale: {
|
|
255
|
+
name: 'Scale',
|
|
256
|
+
category: 'hover',
|
|
257
|
+
description: 'Subtle scale up on hover',
|
|
258
|
+
preview: 'Scale 1 → 1.05',
|
|
259
|
+
css: `.scale-hover {
|
|
260
|
+
transition: transform 0.2s ease;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.scale-hover:hover {
|
|
264
|
+
transform: scale(1.05);
|
|
265
|
+
}`,
|
|
266
|
+
framerMotion: `const scaleHover = {
|
|
267
|
+
whileHover: { scale: 1.05 },
|
|
268
|
+
whileTap: { scale: 0.95 },
|
|
269
|
+
transition: { type: "spring", stiffness: 400, damping: 17 }
|
|
270
|
+
};`,
|
|
271
|
+
gsap: `element.addEventListener('mouseenter', () => {
|
|
272
|
+
gsap.to(element, { scale: 1.05, duration: 0.2 });
|
|
273
|
+
});
|
|
274
|
+
element.addEventListener('mouseleave', () => {
|
|
275
|
+
gsap.to(element, { scale: 1, duration: 0.2 });
|
|
276
|
+
});`
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
const LOADING_ANIMATIONS = {
|
|
280
|
+
spinner: {
|
|
281
|
+
name: 'Spinner',
|
|
282
|
+
category: 'loading',
|
|
283
|
+
description: 'Classic rotating spinner',
|
|
284
|
+
preview: 'Infinite rotation',
|
|
285
|
+
css: `@keyframes spin {
|
|
286
|
+
to { transform: rotate(360deg); }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.spinner {
|
|
290
|
+
width: 24px;
|
|
291
|
+
height: 24px;
|
|
292
|
+
border: 2px solid #E5E7EB;
|
|
293
|
+
border-top-color: #3B82F6;
|
|
294
|
+
border-radius: 50%;
|
|
295
|
+
animation: spin 0.8s linear infinite;
|
|
296
|
+
}`,
|
|
297
|
+
framerMotion: `const Spinner = () => (
|
|
298
|
+
<motion.div
|
|
299
|
+
style={{
|
|
300
|
+
width: 24,
|
|
301
|
+
height: 24,
|
|
302
|
+
border: "2px solid #E5E7EB",
|
|
303
|
+
borderTopColor: "#3B82F6",
|
|
304
|
+
borderRadius: "50%"
|
|
305
|
+
}}
|
|
306
|
+
animate={{ rotate: 360 }}
|
|
307
|
+
transition={{ duration: 0.8, repeat: Infinity, ease: "linear" }}
|
|
308
|
+
/>
|
|
309
|
+
);`,
|
|
310
|
+
},
|
|
311
|
+
pulse: {
|
|
312
|
+
name: 'Pulse',
|
|
313
|
+
category: 'loading',
|
|
314
|
+
description: 'Pulsing opacity effect',
|
|
315
|
+
preview: 'Opacity oscillation',
|
|
316
|
+
css: `@keyframes pulse {
|
|
317
|
+
0%, 100% { opacity: 1; }
|
|
318
|
+
50% { opacity: 0.5; }
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.pulse {
|
|
322
|
+
animation: pulse 1.5s ease-in-out infinite;
|
|
323
|
+
}`,
|
|
324
|
+
framerMotion: `const pulse = {
|
|
325
|
+
animate: {
|
|
326
|
+
opacity: [1, 0.5, 1],
|
|
327
|
+
transition: {
|
|
328
|
+
duration: 1.5,
|
|
329
|
+
repeat: Infinity,
|
|
330
|
+
ease: "easeInOut"
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
};`,
|
|
334
|
+
},
|
|
335
|
+
skeleton: {
|
|
336
|
+
name: 'Skeleton',
|
|
337
|
+
category: 'loading',
|
|
338
|
+
description: 'Skeleton loading shimmer',
|
|
339
|
+
preview: 'Gradient shimmer effect',
|
|
340
|
+
css: `@keyframes shimmer {
|
|
341
|
+
0% { background-position: -200% 0; }
|
|
342
|
+
100% { background-position: 200% 0; }
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.skeleton {
|
|
346
|
+
background: linear-gradient(
|
|
347
|
+
90deg,
|
|
348
|
+
#f0f0f0 25%,
|
|
349
|
+
#e0e0e0 50%,
|
|
350
|
+
#f0f0f0 75%
|
|
351
|
+
);
|
|
352
|
+
background-size: 200% 100%;
|
|
353
|
+
animation: shimmer 1.5s infinite;
|
|
354
|
+
border-radius: 4px;
|
|
355
|
+
}`,
|
|
356
|
+
framerMotion: `const Skeleton = ({ width, height }) => (
|
|
357
|
+
<motion.div
|
|
358
|
+
style={{
|
|
359
|
+
width,
|
|
360
|
+
height,
|
|
361
|
+
background: "linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%)",
|
|
362
|
+
backgroundSize: "200% 100%",
|
|
363
|
+
borderRadius: 4
|
|
364
|
+
}}
|
|
365
|
+
animate={{ backgroundPosition: ["200% 0", "-200% 0"] }}
|
|
366
|
+
transition={{ duration: 1.5, repeat: Infinity, ease: "linear" }}
|
|
367
|
+
/>
|
|
368
|
+
);`,
|
|
369
|
+
},
|
|
370
|
+
dots: {
|
|
371
|
+
name: 'Bouncing Dots',
|
|
372
|
+
category: 'loading',
|
|
373
|
+
description: 'Three bouncing dots',
|
|
374
|
+
preview: 'Staggered dot animation',
|
|
375
|
+
css: `@keyframes bounce {
|
|
376
|
+
0%, 80%, 100% { transform: translateY(0); }
|
|
377
|
+
40% { transform: translateY(-8px); }
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.dots {
|
|
381
|
+
display: flex;
|
|
382
|
+
gap: 4px;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.dots span {
|
|
386
|
+
width: 8px;
|
|
387
|
+
height: 8px;
|
|
388
|
+
background: #3B82F6;
|
|
389
|
+
border-radius: 50%;
|
|
390
|
+
animation: bounce 1.4s infinite ease-in-out;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.dots span:nth-child(1) { animation-delay: 0s; }
|
|
394
|
+
.dots span:nth-child(2) { animation-delay: 0.16s; }
|
|
395
|
+
.dots span:nth-child(3) { animation-delay: 0.32s; }`,
|
|
396
|
+
framerMotion: `const BouncingDots = () => (
|
|
397
|
+
<div style={{ display: "flex", gap: 4 }}>
|
|
398
|
+
{[0, 1, 2].map((i) => (
|
|
399
|
+
<motion.span
|
|
400
|
+
key={i}
|
|
401
|
+
style={{
|
|
402
|
+
width: 8,
|
|
403
|
+
height: 8,
|
|
404
|
+
background: "#3B82F6",
|
|
405
|
+
borderRadius: "50%"
|
|
406
|
+
}}
|
|
407
|
+
animate={{ y: [0, -8, 0] }}
|
|
408
|
+
transition={{
|
|
409
|
+
duration: 0.6,
|
|
410
|
+
repeat: Infinity,
|
|
411
|
+
delay: i * 0.1,
|
|
412
|
+
ease: "easeInOut"
|
|
413
|
+
}}
|
|
414
|
+
/>
|
|
415
|
+
))}
|
|
416
|
+
</div>
|
|
417
|
+
);`,
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
const SCROLL_ANIMATIONS = {
|
|
421
|
+
fadeInOnScroll: {
|
|
422
|
+
name: 'Fade In On Scroll',
|
|
423
|
+
category: 'scroll',
|
|
424
|
+
description: 'Fade in when element enters viewport',
|
|
425
|
+
preview: 'Intersection Observer trigger',
|
|
426
|
+
css: `.fade-in-scroll {
|
|
427
|
+
opacity: 0;
|
|
428
|
+
transform: translateY(30px);
|
|
429
|
+
transition: opacity 0.6s ease, transform 0.6s ease;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
.fade-in-scroll.visible {
|
|
433
|
+
opacity: 1;
|
|
434
|
+
transform: translateY(0);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/* JavaScript needed for Intersection Observer */`,
|
|
438
|
+
framerMotion: `import { motion, useInView } from 'framer-motion';
|
|
439
|
+
import { useRef } from 'react';
|
|
440
|
+
|
|
441
|
+
const FadeInOnScroll = ({ children }) => {
|
|
442
|
+
const ref = useRef(null);
|
|
443
|
+
const isInView = useInView(ref, { once: true, margin: "-100px" });
|
|
444
|
+
|
|
445
|
+
return (
|
|
446
|
+
<motion.div
|
|
447
|
+
ref={ref}
|
|
448
|
+
initial={{ opacity: 0, y: 30 }}
|
|
449
|
+
animate={isInView ? { opacity: 1, y: 0 } : { opacity: 0, y: 30 }}
|
|
450
|
+
transition={{ duration: 0.6, ease: "easeOut" }}
|
|
451
|
+
>
|
|
452
|
+
{children}
|
|
453
|
+
</motion.div>
|
|
454
|
+
);
|
|
455
|
+
};`,
|
|
456
|
+
gsap: `import { gsap } from 'gsap';
|
|
457
|
+
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
|
458
|
+
|
|
459
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
460
|
+
|
|
461
|
+
gsap.from('.fade-in-scroll', {
|
|
462
|
+
opacity: 0,
|
|
463
|
+
y: 30,
|
|
464
|
+
duration: 0.6,
|
|
465
|
+
ease: 'power2.out',
|
|
466
|
+
scrollTrigger: {
|
|
467
|
+
trigger: '.fade-in-scroll',
|
|
468
|
+
start: 'top 80%',
|
|
469
|
+
toggleActions: 'play none none reverse'
|
|
470
|
+
}
|
|
471
|
+
});`
|
|
472
|
+
},
|
|
473
|
+
parallax: {
|
|
474
|
+
name: 'Parallax',
|
|
475
|
+
category: 'scroll',
|
|
476
|
+
description: 'Parallax scrolling effect',
|
|
477
|
+
preview: 'Different scroll speeds',
|
|
478
|
+
css: `/* CSS-only parallax (limited) */
|
|
479
|
+
.parallax-container {
|
|
480
|
+
perspective: 1px;
|
|
481
|
+
height: 100vh;
|
|
482
|
+
overflow-x: hidden;
|
|
483
|
+
overflow-y: auto;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
.parallax-layer {
|
|
487
|
+
position: absolute;
|
|
488
|
+
inset: 0;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.parallax-back {
|
|
492
|
+
transform: translateZ(-1px) scale(2);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.parallax-front {
|
|
496
|
+
transform: translateZ(0);
|
|
497
|
+
}`,
|
|
498
|
+
framerMotion: `import { motion, useScroll, useTransform } from 'framer-motion';
|
|
499
|
+
|
|
500
|
+
const Parallax = ({ children, speed = 0.5 }) => {
|
|
501
|
+
const { scrollY } = useScroll();
|
|
502
|
+
const y = useTransform(scrollY, [0, 500], [0, 500 * speed]);
|
|
503
|
+
|
|
504
|
+
return (
|
|
505
|
+
<motion.div style={{ y }}>
|
|
506
|
+
{children}
|
|
507
|
+
</motion.div>
|
|
508
|
+
);
|
|
509
|
+
};`,
|
|
510
|
+
gsap: `import { gsap } from 'gsap';
|
|
511
|
+
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
|
512
|
+
|
|
513
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
514
|
+
|
|
515
|
+
gsap.to('.parallax-element', {
|
|
516
|
+
yPercent: -50,
|
|
517
|
+
ease: 'none',
|
|
518
|
+
scrollTrigger: {
|
|
519
|
+
trigger: '.parallax-container',
|
|
520
|
+
start: 'top top',
|
|
521
|
+
end: 'bottom top',
|
|
522
|
+
scrub: true
|
|
523
|
+
}
|
|
524
|
+
});`
|
|
525
|
+
}
|
|
526
|
+
};
|
|
527
|
+
const MICRO_INTERACTIONS = {
|
|
528
|
+
buttonPress: {
|
|
529
|
+
name: 'Button Press',
|
|
530
|
+
category: 'micro-interaction',
|
|
531
|
+
description: 'Satisfying button press feedback',
|
|
532
|
+
preview: 'Scale down on click',
|
|
533
|
+
css: `.button-press {
|
|
534
|
+
transition: transform 0.1s ease;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
.button-press:active {
|
|
538
|
+
transform: scale(0.95);
|
|
539
|
+
}`,
|
|
540
|
+
framerMotion: `const buttonPress = {
|
|
541
|
+
whileHover: { scale: 1.02 },
|
|
542
|
+
whileTap: { scale: 0.95 },
|
|
543
|
+
transition: { type: "spring", stiffness: 400, damping: 17 }
|
|
544
|
+
};`,
|
|
545
|
+
},
|
|
546
|
+
checkbox: {
|
|
547
|
+
name: 'Checkbox Check',
|
|
548
|
+
category: 'micro-interaction',
|
|
549
|
+
description: 'Animated checkmark',
|
|
550
|
+
preview: 'SVG path animation',
|
|
551
|
+
css: `@keyframes check {
|
|
552
|
+
0% { stroke-dashoffset: 24; }
|
|
553
|
+
100% { stroke-dashoffset: 0; }
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
.checkbox-check {
|
|
557
|
+
stroke-dasharray: 24;
|
|
558
|
+
stroke-dashoffset: 24;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
.checkbox:checked + .checkbox-check {
|
|
562
|
+
animation: check 0.3s ease forwards;
|
|
563
|
+
}`,
|
|
564
|
+
framerMotion: `const CheckIcon = ({ isChecked }) => (
|
|
565
|
+
<svg viewBox="0 0 24 24" width={24} height={24}>
|
|
566
|
+
<motion.path
|
|
567
|
+
d="M5 12l5 5L20 7"
|
|
568
|
+
fill="none"
|
|
569
|
+
stroke="currentColor"
|
|
570
|
+
strokeWidth={2}
|
|
571
|
+
initial={{ pathLength: 0 }}
|
|
572
|
+
animate={{ pathLength: isChecked ? 1 : 0 }}
|
|
573
|
+
transition={{ duration: 0.3, ease: "easeOut" }}
|
|
574
|
+
/>
|
|
575
|
+
</svg>
|
|
576
|
+
);`,
|
|
577
|
+
},
|
|
578
|
+
toggle: {
|
|
579
|
+
name: 'Toggle Switch',
|
|
580
|
+
category: 'micro-interaction',
|
|
581
|
+
description: 'Smooth toggle animation',
|
|
582
|
+
preview: 'Sliding knob with color change',
|
|
583
|
+
css: `.toggle {
|
|
584
|
+
width: 48px;
|
|
585
|
+
height: 24px;
|
|
586
|
+
background: #E5E7EB;
|
|
587
|
+
border-radius: 12px;
|
|
588
|
+
position: relative;
|
|
589
|
+
transition: background 0.3s ease;
|
|
590
|
+
cursor: pointer;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.toggle.active {
|
|
594
|
+
background: #3B82F6;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.toggle-knob {
|
|
598
|
+
width: 20px;
|
|
599
|
+
height: 20px;
|
|
600
|
+
background: white;
|
|
601
|
+
border-radius: 50%;
|
|
602
|
+
position: absolute;
|
|
603
|
+
top: 2px;
|
|
604
|
+
left: 2px;
|
|
605
|
+
transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
|
606
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.toggle.active .toggle-knob {
|
|
610
|
+
transform: translateX(24px);
|
|
611
|
+
}`,
|
|
612
|
+
framerMotion: `const Toggle = ({ isOn, onToggle }) => (
|
|
613
|
+
<motion.div
|
|
614
|
+
onClick={onToggle}
|
|
615
|
+
animate={{ background: isOn ? "#3B82F6" : "#E5E7EB" }}
|
|
616
|
+
style={{
|
|
617
|
+
width: 48,
|
|
618
|
+
height: 24,
|
|
619
|
+
borderRadius: 12,
|
|
620
|
+
display: "flex",
|
|
621
|
+
alignItems: "center",
|
|
622
|
+
padding: 2,
|
|
623
|
+
cursor: "pointer"
|
|
624
|
+
}}
|
|
625
|
+
>
|
|
626
|
+
<motion.div
|
|
627
|
+
animate={{ x: isOn ? 24 : 0 }}
|
|
628
|
+
transition={{ type: "spring", stiffness: 500, damping: 30 }}
|
|
629
|
+
style={{
|
|
630
|
+
width: 20,
|
|
631
|
+
height: 20,
|
|
632
|
+
background: "white",
|
|
633
|
+
borderRadius: "50%",
|
|
634
|
+
boxShadow: "0 2px 4px rgba(0,0,0,0.2)"
|
|
635
|
+
}}
|
|
636
|
+
/>
|
|
637
|
+
</motion.div>
|
|
638
|
+
);`,
|
|
639
|
+
},
|
|
640
|
+
ripple: {
|
|
641
|
+
name: 'Ripple Effect',
|
|
642
|
+
category: 'micro-interaction',
|
|
643
|
+
description: 'Material Design ripple',
|
|
644
|
+
preview: 'Expanding circle from click point',
|
|
645
|
+
css: `.ripple-container {
|
|
646
|
+
position: relative;
|
|
647
|
+
overflow: hidden;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.ripple {
|
|
651
|
+
position: absolute;
|
|
652
|
+
border-radius: 50%;
|
|
653
|
+
background: rgba(255, 255, 255, 0.4);
|
|
654
|
+
transform: scale(0);
|
|
655
|
+
animation: ripple 0.6s linear;
|
|
656
|
+
pointer-events: none;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
@keyframes ripple {
|
|
660
|
+
to {
|
|
661
|
+
transform: scale(4);
|
|
662
|
+
opacity: 0;
|
|
663
|
+
}
|
|
664
|
+
}`,
|
|
665
|
+
framerMotion: `const Ripple = ({ x, y }) => (
|
|
666
|
+
<motion.span
|
|
667
|
+
style={{
|
|
668
|
+
position: "absolute",
|
|
669
|
+
left: x,
|
|
670
|
+
top: y,
|
|
671
|
+
width: 20,
|
|
672
|
+
height: 20,
|
|
673
|
+
marginLeft: -10,
|
|
674
|
+
marginTop: -10,
|
|
675
|
+
borderRadius: "50%",
|
|
676
|
+
background: "rgba(255, 255, 255, 0.4)",
|
|
677
|
+
pointerEvents: "none"
|
|
678
|
+
}}
|
|
679
|
+
initial={{ scale: 0, opacity: 1 }}
|
|
680
|
+
animate={{ scale: 4, opacity: 0 }}
|
|
681
|
+
transition={{ duration: 0.6 }}
|
|
682
|
+
onAnimationComplete={onComplete}
|
|
683
|
+
/>
|
|
684
|
+
);`,
|
|
685
|
+
}
|
|
686
|
+
};
|
|
687
|
+
// ============================================================================
|
|
688
|
+
// Main Functions
|
|
689
|
+
// ============================================================================
|
|
690
|
+
export async function generateAnimation(request) {
|
|
691
|
+
const validated = AnimationRequestSchema.parse(request);
|
|
692
|
+
logger.info(`Generating ${validated.type} animation for ${validated.element}`);
|
|
693
|
+
// Select appropriate preset based on type
|
|
694
|
+
const presets = getPresetsForType(validated.type);
|
|
695
|
+
const bestPreset = selectBestPreset(presets, validated);
|
|
696
|
+
// Generate code for the requested library
|
|
697
|
+
const code = generateLibraryCode(validated, bestPreset);
|
|
698
|
+
const cssKeyframes = generateCSSKeyframes(validated, bestPreset);
|
|
699
|
+
const usageExample = generateUsageExample(validated, code);
|
|
700
|
+
// Generate alternatives
|
|
701
|
+
const alternatives = generateAlternatives(validated, bestPreset);
|
|
702
|
+
// Performance tips
|
|
703
|
+
const performanceTips = generatePerformanceTips(validated);
|
|
704
|
+
// Accessibility
|
|
705
|
+
const accessibilityNote = validated.includeReducedMotion
|
|
706
|
+
? generateAccessibilityCode(validated)
|
|
707
|
+
: 'Consider adding prefers-reduced-motion support';
|
|
708
|
+
return {
|
|
709
|
+
name: `${validated.element}-${validated.type}-animation`,
|
|
710
|
+
description: `${validated.style} ${validated.type} animation for ${validated.element}`,
|
|
711
|
+
code,
|
|
712
|
+
usageExample,
|
|
713
|
+
cssKeyframes,
|
|
714
|
+
performanceTips,
|
|
715
|
+
accessibilityNote,
|
|
716
|
+
browserSupport: 'All modern browsers (Chrome 64+, Firefox 63+, Safari 12+, Edge 79+)',
|
|
717
|
+
alternatives
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function getPresetsForType(type) {
|
|
721
|
+
switch (type) {
|
|
722
|
+
case 'entrance':
|
|
723
|
+
case 'exit':
|
|
724
|
+
return ENTRANCE_ANIMATIONS;
|
|
725
|
+
case 'hover':
|
|
726
|
+
return HOVER_ANIMATIONS;
|
|
727
|
+
case 'loading':
|
|
728
|
+
return LOADING_ANIMATIONS;
|
|
729
|
+
case 'scroll':
|
|
730
|
+
return SCROLL_ANIMATIONS;
|
|
731
|
+
case 'micro-interaction':
|
|
732
|
+
case 'gesture':
|
|
733
|
+
return MICRO_INTERACTIONS;
|
|
734
|
+
default:
|
|
735
|
+
return ENTRANCE_ANIMATIONS;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function selectBestPreset(presets, request) {
|
|
739
|
+
// Use DEFAULT_ANIMATION_PRESET as the ultimate fallback
|
|
740
|
+
const presetValues = Object.values(presets);
|
|
741
|
+
let fallback = DEFAULT_ANIMATION_PRESET;
|
|
742
|
+
if (presetValues.length > 0 && presetValues[0]) {
|
|
743
|
+
fallback = presetValues[0];
|
|
744
|
+
}
|
|
745
|
+
// Select based on style preference
|
|
746
|
+
if (request.style === 'bouncy' || request.style === 'playful') {
|
|
747
|
+
if (presets.bounceIn)
|
|
748
|
+
return presets.bounceIn;
|
|
749
|
+
if (presets.scale)
|
|
750
|
+
return presets.scale;
|
|
751
|
+
return fallback;
|
|
752
|
+
}
|
|
753
|
+
if (request.style === 'smooth' || request.style === 'natural') {
|
|
754
|
+
if (presets.fadeIn)
|
|
755
|
+
return presets.fadeIn;
|
|
756
|
+
if (presets.slideUp)
|
|
757
|
+
return presets.slideUp;
|
|
758
|
+
return fallback;
|
|
759
|
+
}
|
|
760
|
+
return fallback;
|
|
761
|
+
}
|
|
762
|
+
function generateLibraryCode(request, preset) {
|
|
763
|
+
const easing = EASING_FUNCTIONS[request.style || 'smooth'];
|
|
764
|
+
const duration = request.duration || 300;
|
|
765
|
+
switch (request.library) {
|
|
766
|
+
case 'framer-motion':
|
|
767
|
+
return generateFramerMotionCode(request, preset, easing, duration);
|
|
768
|
+
case 'gsap':
|
|
769
|
+
return generateGSAPCode(request, preset, easing, duration);
|
|
770
|
+
case 'react-spring':
|
|
771
|
+
return generateReactSpringCode(request, preset, duration);
|
|
772
|
+
case 'anime-js':
|
|
773
|
+
return generateAnimeJSCode(request, preset, easing, duration);
|
|
774
|
+
default:
|
|
775
|
+
return generateCSSCode(request, preset, easing, duration);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
function generateFramerMotionCode(request, preset, easing, duration) {
|
|
779
|
+
const durationSec = duration / 1000;
|
|
780
|
+
if (request.type === 'entrance') {
|
|
781
|
+
return `import { motion } from 'framer-motion';
|
|
782
|
+
|
|
783
|
+
const ${toPascalCase(request.element)}Animation = {
|
|
784
|
+
initial: { opacity: 0, y: 20 },
|
|
785
|
+
animate: { opacity: 1, y: 0 },
|
|
786
|
+
exit: { opacity: 0, y: -20 },
|
|
787
|
+
transition: {
|
|
788
|
+
duration: ${durationSec},
|
|
789
|
+
ease: [0.4, 0, 0.2, 1]
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
export const Animated${toPascalCase(request.element)} = ({ children }) => (
|
|
794
|
+
<motion.div
|
|
795
|
+
initial="initial"
|
|
796
|
+
animate="animate"
|
|
797
|
+
exit="exit"
|
|
798
|
+
variants={${toPascalCase(request.element)}Animation}
|
|
799
|
+
>
|
|
800
|
+
{children}
|
|
801
|
+
</motion.div>
|
|
802
|
+
);`;
|
|
803
|
+
}
|
|
804
|
+
if (request.type === 'hover') {
|
|
805
|
+
return `import { motion } from 'framer-motion';
|
|
806
|
+
|
|
807
|
+
export const Hoverable${toPascalCase(request.element)} = ({ children, onClick }) => (
|
|
808
|
+
<motion.div
|
|
809
|
+
whileHover={{ scale: 1.02, y: -2 }}
|
|
810
|
+
whileTap={{ scale: 0.98 }}
|
|
811
|
+
transition={{ type: "spring", stiffness: 400, damping: 17 }}
|
|
812
|
+
onClick={onClick}
|
|
813
|
+
>
|
|
814
|
+
{children}
|
|
815
|
+
</motion.div>
|
|
816
|
+
);`;
|
|
817
|
+
}
|
|
818
|
+
if (request.type === 'loading') {
|
|
819
|
+
return `import { motion } from 'framer-motion';
|
|
820
|
+
|
|
821
|
+
export const Loading${toPascalCase(request.element)} = () => (
|
|
822
|
+
<motion.div
|
|
823
|
+
animate={{
|
|
824
|
+
rotate: 360,
|
|
825
|
+
}}
|
|
826
|
+
transition={{
|
|
827
|
+
duration: 1,
|
|
828
|
+
repeat: Infinity,
|
|
829
|
+
ease: "linear"
|
|
830
|
+
}}
|
|
831
|
+
style={{
|
|
832
|
+
width: 24,
|
|
833
|
+
height: 24,
|
|
834
|
+
border: "2px solid #E5E7EB",
|
|
835
|
+
borderTopColor: "#3B82F6",
|
|
836
|
+
borderRadius: "50%"
|
|
837
|
+
}}
|
|
838
|
+
/>
|
|
839
|
+
);`;
|
|
840
|
+
}
|
|
841
|
+
return preset.framerMotion || preset.css;
|
|
842
|
+
}
|
|
843
|
+
function generateGSAPCode(request, preset, easing, duration) {
|
|
844
|
+
const durationSec = duration / 1000;
|
|
845
|
+
if (request.type === 'entrance') {
|
|
846
|
+
return `import { gsap } from 'gsap';
|
|
847
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
848
|
+
|
|
849
|
+
export const Animated${toPascalCase(request.element)} = ({ children }) => {
|
|
850
|
+
const ref = useRef(null);
|
|
851
|
+
|
|
852
|
+
useLayoutEffect(() => {
|
|
853
|
+
const ctx = gsap.context(() => {
|
|
854
|
+
gsap.from(ref.current, {
|
|
855
|
+
opacity: 0,
|
|
856
|
+
y: 20,
|
|
857
|
+
duration: ${durationSec},
|
|
858
|
+
ease: "power2.out"
|
|
859
|
+
});
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
return () => ctx.revert();
|
|
863
|
+
}, []);
|
|
864
|
+
|
|
865
|
+
return <div ref={ref}>{children}</div>;
|
|
866
|
+
};`;
|
|
867
|
+
}
|
|
868
|
+
if (request.type === 'scroll') {
|
|
869
|
+
return `import { gsap } from 'gsap';
|
|
870
|
+
import { ScrollTrigger } from 'gsap/ScrollTrigger';
|
|
871
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
872
|
+
|
|
873
|
+
gsap.registerPlugin(ScrollTrigger);
|
|
874
|
+
|
|
875
|
+
export const ScrollAnimated${toPascalCase(request.element)} = ({ children }) => {
|
|
876
|
+
const ref = useRef(null);
|
|
877
|
+
|
|
878
|
+
useLayoutEffect(() => {
|
|
879
|
+
const ctx = gsap.context(() => {
|
|
880
|
+
gsap.from(ref.current, {
|
|
881
|
+
opacity: 0,
|
|
882
|
+
y: 50,
|
|
883
|
+
duration: ${durationSec},
|
|
884
|
+
ease: "power2.out",
|
|
885
|
+
scrollTrigger: {
|
|
886
|
+
trigger: ref.current,
|
|
887
|
+
start: "top 80%",
|
|
888
|
+
toggleActions: "play none none reverse"
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
return () => ctx.revert();
|
|
894
|
+
}, []);
|
|
895
|
+
|
|
896
|
+
return <div ref={ref}>{children}</div>;
|
|
897
|
+
};`;
|
|
898
|
+
}
|
|
899
|
+
return preset.gsap || preset.css;
|
|
900
|
+
}
|
|
901
|
+
function generateReactSpringCode(request, preset, duration) {
|
|
902
|
+
return `import { useSpring, animated } from '@react-spring/web';
|
|
903
|
+
|
|
904
|
+
export const Animated${toPascalCase(request.element)} = ({ children }) => {
|
|
905
|
+
const springs = useSpring({
|
|
906
|
+
from: { opacity: 0, transform: 'translateY(20px)' },
|
|
907
|
+
to: { opacity: 1, transform: 'translateY(0px)' },
|
|
908
|
+
config: { tension: 280, friction: 20 }
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
return (
|
|
912
|
+
<animated.div style={springs}>
|
|
913
|
+
{children}
|
|
914
|
+
</animated.div>
|
|
915
|
+
);
|
|
916
|
+
};`;
|
|
917
|
+
}
|
|
918
|
+
function generateAnimeJSCode(request, preset, easing, duration) {
|
|
919
|
+
return `import anime from 'animejs';
|
|
920
|
+
import { useEffect, useRef } from 'react';
|
|
921
|
+
|
|
922
|
+
export const Animated${toPascalCase(request.element)} = ({ children }) => {
|
|
923
|
+
const ref = useRef(null);
|
|
924
|
+
|
|
925
|
+
useEffect(() => {
|
|
926
|
+
anime({
|
|
927
|
+
targets: ref.current,
|
|
928
|
+
opacity: [0, 1],
|
|
929
|
+
translateY: [20, 0],
|
|
930
|
+
duration: ${duration},
|
|
931
|
+
easing: 'easeOutCubic'
|
|
932
|
+
});
|
|
933
|
+
}, []);
|
|
934
|
+
|
|
935
|
+
return <div ref={ref}>{children}</div>;
|
|
936
|
+
};`;
|
|
937
|
+
}
|
|
938
|
+
function generateCSSCode(request, preset, easing, duration) {
|
|
939
|
+
return `/* ${request.element} ${request.type} Animation */
|
|
940
|
+
|
|
941
|
+
@keyframes ${request.element}${toPascalCase(request.type)} {
|
|
942
|
+
from {
|
|
943
|
+
opacity: 0;
|
|
944
|
+
transform: translateY(20px);
|
|
945
|
+
}
|
|
946
|
+
to {
|
|
947
|
+
opacity: 1;
|
|
948
|
+
transform: translateY(0);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
.${request.element}-${request.type} {
|
|
953
|
+
animation: ${request.element}${toPascalCase(request.type)} ${duration}ms ${easing} forwards;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/* Reduced motion support */
|
|
957
|
+
@media (prefers-reduced-motion: reduce) {
|
|
958
|
+
.${request.element}-${request.type} {
|
|
959
|
+
animation: none;
|
|
960
|
+
opacity: 1;
|
|
961
|
+
transform: none;
|
|
962
|
+
}
|
|
963
|
+
}`;
|
|
964
|
+
}
|
|
965
|
+
function generateCSSKeyframes(request, preset) {
|
|
966
|
+
return preset.css;
|
|
967
|
+
}
|
|
968
|
+
function generateUsageExample(request, code) {
|
|
969
|
+
if (request.library === 'framer-motion') {
|
|
970
|
+
return `// Import the animated component
|
|
971
|
+
import { Animated${toPascalCase(request.element)} } from './animations';
|
|
972
|
+
|
|
973
|
+
// Use in your component
|
|
974
|
+
function MyComponent() {
|
|
975
|
+
return (
|
|
976
|
+
<Animated${toPascalCase(request.element)}>
|
|
977
|
+
<div className="${request.element}">
|
|
978
|
+
Your content here
|
|
979
|
+
</div>
|
|
980
|
+
</Animated${toPascalCase(request.element)}>
|
|
981
|
+
);
|
|
982
|
+
}`;
|
|
983
|
+
}
|
|
984
|
+
if (request.library === 'css') {
|
|
985
|
+
return `<!-- Add the CSS class to your element -->
|
|
986
|
+
<div class="${request.element}-${request.type}">
|
|
987
|
+
Your content here
|
|
988
|
+
</div>
|
|
989
|
+
|
|
990
|
+
<!-- Or trigger with JavaScript -->
|
|
991
|
+
<script>
|
|
992
|
+
element.classList.add('${request.element}-${request.type}');
|
|
993
|
+
</script>`;
|
|
994
|
+
}
|
|
995
|
+
return `// See the generated code above for usage`;
|
|
996
|
+
}
|
|
997
|
+
function generateAlternatives(request, preset) {
|
|
998
|
+
const alternatives = [];
|
|
999
|
+
if (request.library !== 'css' && preset.css) {
|
|
1000
|
+
alternatives.push({ library: 'CSS', code: preset.css });
|
|
1001
|
+
}
|
|
1002
|
+
if (request.library !== 'framer-motion' && preset.framerMotion) {
|
|
1003
|
+
alternatives.push({ library: 'Framer Motion', code: preset.framerMotion });
|
|
1004
|
+
}
|
|
1005
|
+
if (request.library !== 'gsap' && preset.gsap) {
|
|
1006
|
+
alternatives.push({ library: 'GSAP', code: preset.gsap });
|
|
1007
|
+
}
|
|
1008
|
+
return alternatives;
|
|
1009
|
+
}
|
|
1010
|
+
function generatePerformanceTips(request) {
|
|
1011
|
+
const tips = [];
|
|
1012
|
+
tips.push('Use transform and opacity for smooth 60fps animations');
|
|
1013
|
+
tips.push('Avoid animating width, height, top, left (triggers layout)');
|
|
1014
|
+
tips.push('Use will-change sparingly on elements that will animate');
|
|
1015
|
+
if (request.type === 'scroll') {
|
|
1016
|
+
tips.push('Throttle scroll handlers or use Intersection Observer');
|
|
1017
|
+
tips.push('Consider using CSS scroll-timeline for better performance');
|
|
1018
|
+
}
|
|
1019
|
+
if (request.type === 'loading') {
|
|
1020
|
+
tips.push('Use CSS animations for looping effects (lower CPU)');
|
|
1021
|
+
tips.push('Avoid JS-based infinite loops');
|
|
1022
|
+
}
|
|
1023
|
+
tips.push('Test on low-end devices for real performance');
|
|
1024
|
+
return tips;
|
|
1025
|
+
}
|
|
1026
|
+
function generateAccessibilityCode(request) {
|
|
1027
|
+
if (request.library === 'framer-motion') {
|
|
1028
|
+
return `// Framer Motion respects reduced motion automatically, or:
|
|
1029
|
+
import { useReducedMotion } from 'framer-motion';
|
|
1030
|
+
|
|
1031
|
+
const shouldReduceMotion = useReducedMotion();
|
|
1032
|
+
const animation = shouldReduceMotion ? {} : { y: [20, 0], opacity: [0, 1] };`;
|
|
1033
|
+
}
|
|
1034
|
+
return `@media (prefers-reduced-motion: reduce) {
|
|
1035
|
+
.${request.element}-${request.type} {
|
|
1036
|
+
animation: none;
|
|
1037
|
+
transition: none;
|
|
1038
|
+
}
|
|
1039
|
+
}`;
|
|
1040
|
+
}
|
|
1041
|
+
function toPascalCase(str) {
|
|
1042
|
+
return str
|
|
1043
|
+
.split(/[-_\s]+/)
|
|
1044
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
1045
|
+
.join('');
|
|
1046
|
+
}
|
|
1047
|
+
// ============================================================================
|
|
1048
|
+
// Get Animation Presets
|
|
1049
|
+
// ============================================================================
|
|
1050
|
+
export function getAnimationPresets(category) {
|
|
1051
|
+
const all = [
|
|
1052
|
+
...Object.values(ENTRANCE_ANIMATIONS),
|
|
1053
|
+
...Object.values(HOVER_ANIMATIONS),
|
|
1054
|
+
...Object.values(LOADING_ANIMATIONS),
|
|
1055
|
+
...Object.values(SCROLL_ANIMATIONS),
|
|
1056
|
+
...Object.values(MICRO_INTERACTIONS)
|
|
1057
|
+
];
|
|
1058
|
+
if (category) {
|
|
1059
|
+
return all.filter(p => p.category === category);
|
|
1060
|
+
}
|
|
1061
|
+
return all;
|
|
1062
|
+
}
|
|
1063
|
+
export default { generateAnimation, getAnimationPresets };
|
|
1064
|
+
//# sourceMappingURL=animation-studio.js.map
|