atlas-pipeline-mcp 1.0.25 → 1.0.26

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.
@@ -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