@champpaba/claude-agent-kit 1.4.0 → 1.4.2

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,768 @@
1
+ # Animation & Micro-interaction Patterns
2
+
3
+ > **Purpose:** Add life and polish to UI through thoughtful animations and interactions
4
+ > **Audience:** uxui-frontend agent (primary), all frontend agents
5
+ > **Integration:** Used with `/pageplan` Section 2.6 (Animation Blueprint)
6
+ > **Philosophy:** Match Flow Engineer Step 3 - Design animations systematically
7
+
8
+ **Key Principles:**
9
+ - **Purpose:** Every animation should have a purpose (guide attention, provide feedback, show transition)
10
+ - **Performance:** Use `transform` and `opacity` only (GPU-accelerated)
11
+ - **Accessibility:** Respect `prefers-reduced-motion` for users sensitive to motion
12
+ - **Subtlety:** Animations should enhance, not distract
13
+ - **Consistency:** Same component type = same animation pattern
14
+
15
+ ---
16
+
17
+ ## 📘 How to Use This File
18
+
19
+ **Priority:**
20
+ 1. **Page-specific plan:** `.changes/{id}/page-plan.md` Section 2.6 (if exists)
21
+ 2. **Project tokens:** `design-system/STYLE_TOKENS.json` (animation tokens)
22
+ 3. **General guidelines:** This file (fallback when above don't exist)
23
+
24
+ **When to read:**
25
+ - uxui-frontend agent STEP 0 (after loading project context)
26
+ - When creating components with interactions
27
+ - When no page-plan.md Animation Blueprint exists
28
+
29
+ ---
30
+
31
+ ## ⚡ Animation Performance Rules
32
+
33
+ ### ✅ Fast Properties (GPU-accelerated)
34
+
35
+ **Use these properties for smooth 60fps animations:**
36
+
37
+ ```css
38
+ /* ✅ GOOD - GPU accelerated */
39
+ .element {
40
+ transform: translateX(100px); /* Position */
41
+ transform: scale(1.1); /* Size */
42
+ transform: rotate(45deg); /* Rotation */
43
+ opacity: 0.5; /* Transparency */
44
+ }
45
+ ```
46
+
47
+ ### ❌ Slow Properties (Avoid)
48
+
49
+ **These trigger layout recalculation - use sparingly:**
50
+
51
+ ```css
52
+ /* ❌ BAD - Causes reflow/repaint */
53
+ .element {
54
+ width: 200px; /* Triggers layout */
55
+ height: 100px; /* Triggers layout */
56
+ top: 50px; /* Triggers layout */
57
+ left: 100px; /* Triggers layout */
58
+ margin: 20px; /* Triggers layout */
59
+ }
60
+ ```
61
+
62
+ **Source:** FreeCodeCamp Performance Handbook
63
+ - `transform`/`opacity`: ~3ms render time
64
+ - Layout properties: ~14ms render time (4-5× slower)
65
+
66
+ ---
67
+
68
+ ## 🎯 Animation Duration Guidelines
69
+
70
+ **Standard Durations (from STYLE_TOKENS.json):**
71
+
72
+ | Token | Duration | Use Case | Example |
73
+ |-------|----------|----------|---------|
74
+ | **fast** | 150ms | Micro-interactions | Button hover, icon rotation |
75
+ | **normal** | 300ms | Transitions, reveals | Card elevation, modal entrance |
76
+ | **slow** | 500ms | Complex animations | Page transitions, carousel |
77
+
78
+ **❌ DON'T use random durations:** 200ms, 250ms, 400ms (inconsistent)
79
+ **✅ DO use token durations:** 150ms, 300ms, 500ms (consistent, predictable)
80
+
81
+ ```css
82
+ /* Fast (hover effects) */
83
+ .button:hover {
84
+ transform: translateY(-2px);
85
+ transition: transform 150ms ease-out;
86
+ }
87
+
88
+ /* Normal (modal entrance) */
89
+ .modal {
90
+ animation: fadeIn 300ms ease-out;
91
+ }
92
+
93
+ /* Slow (page transition) */
94
+ .page-transition {
95
+ animation: slideIn 500ms ease-in-out;
96
+ }
97
+ ```
98
+
99
+ ---
100
+
101
+ ## 🧩 Component Animation Patterns
102
+
103
+ > **Purpose:** Standard animation patterns for common components
104
+ > **Source:** Based on STYLE_TOKENS.json + `/pageplan` Section 2.6
105
+ > **Principle:** Same component type = same animation pattern (consistency)
106
+
107
+ ### Button Components
108
+
109
+ #### Pattern: Scale + Shadow (Primary/CTA)
110
+ ```tsx
111
+ <button className="
112
+ transition-all duration-150
113
+ hover:scale-105 hover:shadow-lg
114
+ active:scale-95
115
+ ">
116
+ Get Started
117
+ </button>
118
+ ```
119
+
120
+ **States:**
121
+ - **Hover:** Scale(1.05) + Shadow(md→lg) | 150ms
122
+ - **Active:** Scale(0.95) | 100ms (immediate feedback)
123
+ - **Disabled:** Opacity(70%) | static
124
+
125
+ **Rationale:** Creates depth, confirms interactivity, tactile press feedback
126
+
127
+ ---
128
+
129
+ #### Pattern: Background Shift (Secondary)
130
+ ```tsx
131
+ <button className="
132
+ transition-colors duration-150
133
+ hover:bg-secondary/80
134
+ ">
135
+ Learn More
136
+ </button>
137
+ ```
138
+
139
+ **Rationale:** Subtle, doesn't distract from primary CTA
140
+
141
+ ---
142
+
143
+ ### Card Components
144
+
145
+ #### Pattern: Shadow Elevation
146
+ ```tsx
147
+ <div className="
148
+ transition-shadow duration-300
149
+ hover:shadow-xl hover:border-primary/50
150
+ ">
151
+ {/* Card content */}
152
+ </div>
153
+ ```
154
+
155
+ **States:**
156
+ - **Hover:** Shadow(sm→xl) + Border glow | 300ms
157
+ - **Click (if interactive):** Scale(0.98) | 100ms
158
+
159
+ **Rationale:** Elegant, smooth, matches Material Design
160
+
161
+ ---
162
+
163
+ ### Input & Form Components
164
+
165
+ #### Pattern: Ring + Border Shift
166
+ ```tsx
167
+ <input className="
168
+ transition-all duration-200
169
+ focus:ring-2 focus:ring-primary focus:border-primary
170
+ " />
171
+ ```
172
+
173
+ **States:**
174
+ - **Focus:** Ring(2px primary) + Border(primary) | 200ms
175
+ - **Error:** Border(destructive) + optional shake | 300ms
176
+
177
+ **Rationale:** Clear focus indicator (accessibility), balanced duration
178
+
179
+ ---
180
+
181
+ ## 🎨 Entrance Animations
182
+
183
+ ### 1. Fade In
184
+
185
+ **Use for:** Images, cards, content sections
186
+
187
+ ```css
188
+ @keyframes fadeIn {
189
+ from {
190
+ opacity: 0;
191
+ }
192
+ to {
193
+ opacity: 1;
194
+ }
195
+ }
196
+
197
+ .fade-in {
198
+ animation: fadeIn 300ms ease-out;
199
+ }
200
+ ```
201
+
202
+ **React/Next.js:**
203
+ ```tsx
204
+ <div className="animate-fadeIn opacity-0">
205
+ Content appears smoothly
206
+ </div>
207
+
208
+ // Tailwind config
209
+ module.exports = {
210
+ theme: {
211
+ extend: {
212
+ keyframes: {
213
+ fadeIn: {
214
+ '0%': { opacity: '0' },
215
+ '100%': { opacity: '1' },
216
+ },
217
+ },
218
+ animation: {
219
+ fadeIn: 'fadeIn 300ms ease-out forwards',
220
+ },
221
+ },
222
+ },
223
+ }
224
+ ```
225
+
226
+ ---
227
+
228
+ ### 2. Slide In
229
+
230
+ **Use for:** Sidebars, notifications, modals
231
+
232
+ ```css
233
+ /* Slide from bottom */
234
+ @keyframes slideInBottom {
235
+ from {
236
+ opacity: 0;
237
+ transform: translateY(20px);
238
+ }
239
+ to {
240
+ opacity: 1;
241
+ transform: translateY(0);
242
+ }
243
+ }
244
+
245
+ .slide-in-bottom {
246
+ animation: slideInBottom 300ms ease-out;
247
+ }
248
+
249
+ /* Slide from right (sidebar) */
250
+ @keyframes slideInRight {
251
+ from {
252
+ transform: translateX(100%);
253
+ }
254
+ to {
255
+ transform: translateX(0);
256
+ }
257
+ }
258
+
259
+ .sidebar {
260
+ animation: slideInRight 250ms ease-out;
261
+ }
262
+ ```
263
+
264
+ ---
265
+
266
+ ### 3. Scale In
267
+
268
+ **Use for:** Modals, tooltips, popovers
269
+
270
+ ```css
271
+ @keyframes scaleIn {
272
+ from {
273
+ opacity: 0;
274
+ transform: scale(0.95);
275
+ }
276
+ to {
277
+ opacity: 1;
278
+ transform: scale(1);
279
+ }
280
+ }
281
+
282
+ .modal {
283
+ animation: scaleIn 200ms ease-out;
284
+ }
285
+ ```
286
+
287
+ ---
288
+
289
+ ### 4. Scroll-Triggered Animations
290
+
291
+ **Use for:** Elements that appear on scroll (lazy reveal)
292
+
293
+ **Using Intersection Observer:**
294
+
295
+ ```tsx
296
+ import { useEffect, useRef } from 'react'
297
+
298
+ export function ScrollReveal({ children }: { children: React.ReactNode }) {
299
+ const ref = useRef<HTMLDivElement>(null)
300
+
301
+ useEffect(() => {
302
+ const observer = new IntersectionObserver(
303
+ ([entry]) => {
304
+ if (entry.isIntersecting) {
305
+ entry.target.classList.add('animate-fadeInUp')
306
+ }
307
+ },
308
+ { threshold: 0.1 }
309
+ )
310
+
311
+ if (ref.current) observer.observe(ref.current)
312
+ return () => observer.disconnect()
313
+ }, [])
314
+
315
+ return (
316
+ <div ref={ref} className="opacity-0">
317
+ {children}
318
+ </div>
319
+ )
320
+ }
321
+ ```
322
+
323
+ **CSS:**
324
+ ```css
325
+ @keyframes fadeInUp {
326
+ from {
327
+ opacity: 0;
328
+ transform: translateY(30px);
329
+ }
330
+ to {
331
+ opacity: 1;
332
+ transform: translateY(0);
333
+ }
334
+ }
335
+
336
+ .animate-fadeInUp {
337
+ animation: fadeInUp 400ms ease-out forwards;
338
+ }
339
+ ```
340
+
341
+ ---
342
+
343
+ ## 🖱️ Micro-interactions
344
+
345
+ ### 1. Hover Effects
346
+
347
+ **Card Lift (common pattern):**
348
+
349
+ ```css
350
+ .card {
351
+ transition: transform 200ms ease-out,
352
+ box-shadow 200ms ease-out;
353
+ }
354
+
355
+ .card:hover {
356
+ transform: translateY(-4px);
357
+ box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
358
+ }
359
+ ```
360
+
361
+ **Button Scale:**
362
+
363
+ ```css
364
+ .button {
365
+ transition: transform 150ms ease-out;
366
+ }
367
+
368
+ .button:hover {
369
+ transform: scale(1.05);
370
+ }
371
+ ```
372
+
373
+ **Link Underline Animation:**
374
+
375
+ ```css
376
+ .link {
377
+ position: relative;
378
+ text-decoration: none;
379
+ }
380
+
381
+ .link::after {
382
+ content: '';
383
+ position: absolute;
384
+ bottom: -2px;
385
+ left: 0;
386
+ width: 0;
387
+ height: 2px;
388
+ background: currentColor;
389
+ transition: width 200ms ease-out;
390
+ }
391
+
392
+ .link:hover::after {
393
+ width: 100%;
394
+ }
395
+ ```
396
+
397
+ ---
398
+
399
+ ### 2. Click/Press Effects
400
+
401
+ **Scale Down (tactile feedback):**
402
+
403
+ ```css
404
+ .button {
405
+ transition: transform 100ms ease-out;
406
+ }
407
+
408
+ .button:active {
409
+ transform: scale(0.98);
410
+ }
411
+ ```
412
+
413
+ **Ripple Effect (Material Design):**
414
+
415
+ ```tsx
416
+ import { useState } from 'react'
417
+
418
+ export function RippleButton({ children, onClick }: any) {
419
+ const [ripples, setRipples] = useState<Array<{ x: number; y: number; id: number }>>([])
420
+
421
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
422
+ const rect = e.currentTarget.getBoundingClientRect()
423
+ const x = e.clientX - rect.left
424
+ const y = e.clientY - rect.top
425
+
426
+ setRipples([...ripples, { x, y, id: Date.now() }])
427
+ setTimeout(() => setRipples((prev) => prev.slice(1)), 600)
428
+
429
+ onClick?.(e)
430
+ }
431
+
432
+ return (
433
+ <button className="relative overflow-hidden" onClick={handleClick}>
434
+ {children}
435
+ {ripples.map((ripple) => (
436
+ <span
437
+ key={ripple.id}
438
+ className="absolute bg-white/30 rounded-full animate-ripple"
439
+ style={{
440
+ left: ripple.x,
441
+ top: ripple.y,
442
+ width: 10,
443
+ height: 10,
444
+ }}
445
+ />
446
+ ))}
447
+ </button>
448
+ )
449
+ }
450
+ ```
451
+
452
+ **CSS:**
453
+ ```css
454
+ @keyframes ripple {
455
+ to {
456
+ transform: translate(-50%, -50%) scale(10);
457
+ opacity: 0;
458
+ }
459
+ }
460
+
461
+ .animate-ripple {
462
+ animation: ripple 600ms ease-out forwards;
463
+ transform: translate(-50%, -50%);
464
+ }
465
+ ```
466
+
467
+ ---
468
+
469
+ ### 3. Focus States
470
+
471
+ **Ring Animation:**
472
+
473
+ ```css
474
+ .input:focus {
475
+ outline: none;
476
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5);
477
+ transition: box-shadow 150ms ease-out;
478
+ }
479
+ ```
480
+
481
+ **Border Glow:**
482
+
483
+ ```css
484
+ .input {
485
+ border: 2px solid transparent;
486
+ transition: border-color 200ms ease-out;
487
+ }
488
+
489
+ .input:focus {
490
+ border-color: var(--color-primary);
491
+ }
492
+ ```
493
+
494
+ ---
495
+
496
+ ## 🔄 Loading States
497
+
498
+ ### 1. Skeleton Loader
499
+
500
+ ```tsx
501
+ export function SkeletonCard() {
502
+ return (
503
+ <div className="border rounded-lg p-6 space-y-4">
504
+ <div className="h-4 bg-gray-200 rounded animate-pulse" />
505
+ <div className="h-4 bg-gray-200 rounded animate-pulse w-3/4" />
506
+ <div className="h-8 bg-gray-200 rounded animate-pulse" />
507
+ </div>
508
+ )
509
+ }
510
+ ```
511
+
512
+ **CSS:**
513
+ ```css
514
+ @keyframes pulse {
515
+ 0%, 100% {
516
+ opacity: 1;
517
+ }
518
+ 50% {
519
+ opacity: 0.5;
520
+ }
521
+ }
522
+
523
+ .animate-pulse {
524
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
525
+ }
526
+ ```
527
+
528
+ ---
529
+
530
+ ### 2. Shimmer Effect
531
+
532
+ ```css
533
+ @keyframes shimmer {
534
+ 0% {
535
+ background-position: -1000px 0;
536
+ }
537
+ 100% {
538
+ background-position: 1000px 0;
539
+ }
540
+ }
541
+
542
+ .skeleton {
543
+ background: linear-gradient(
544
+ 90deg,
545
+ #f0f0f0 25%,
546
+ #e0e0e0 50%,
547
+ #f0f0f0 75%
548
+ );
549
+ background-size: 1000px 100%;
550
+ animation: shimmer 2s infinite;
551
+ }
552
+ ```
553
+
554
+ ---
555
+
556
+ ### 3. Spinner (Button Loading)
557
+
558
+ ```tsx
559
+ export function ButtonWithSpinner({ loading, children, ...props }: any) {
560
+ return (
561
+ <button {...props} disabled={loading}>
562
+ {loading ? (
563
+ <div className="flex items-center gap-2">
564
+ <div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
565
+ Loading...
566
+ </div>
567
+ ) : (
568
+ children
569
+ )}
570
+ </button>
571
+ )
572
+ }
573
+ ```
574
+
575
+ **CSS:**
576
+ ```css
577
+ @keyframes spin {
578
+ to {
579
+ transform: rotate(360deg);
580
+ }
581
+ }
582
+
583
+ .animate-spin {
584
+ animation: spin 1s linear infinite;
585
+ }
586
+ ```
587
+
588
+ ---
589
+
590
+ ## 🎭 Transition Choreography
591
+
592
+ ### 1. Stagger Effect (List Items)
593
+
594
+ **Animate list items with delay:**
595
+
596
+ ```tsx
597
+ export function StaggerList({ items }: { items: string[] }) {
598
+ return (
599
+ <ul>
600
+ {items.map((item, index) => (
601
+ <li
602
+ key={item}
603
+ className="animate-fadeInUp"
604
+ style={{ animationDelay: `${index * 100}ms` }}
605
+ >
606
+ {item}
607
+ </li>
608
+ ))}
609
+ </ul>
610
+ )
611
+ }
612
+ ```
613
+
614
+ ---
615
+
616
+ ### 2. Page Transitions
617
+
618
+ ```tsx
619
+ import { AnimatePresence, motion } from 'framer-motion'
620
+
621
+ export function PageTransition({ children }: { children: React.ReactNode }) {
622
+ return (
623
+ <AnimatePresence mode="wait">
624
+ <motion.div
625
+ initial={{ opacity: 0, y: 20 }}
626
+ animate={{ opacity: 1, y: 0 }}
627
+ exit={{ opacity: 0, y: -20 }}
628
+ transition={{ duration: 0.3 }}
629
+ >
630
+ {children}
631
+ </motion.div>
632
+ </AnimatePresence>
633
+ )
634
+ }
635
+ ```
636
+
637
+ ---
638
+
639
+ ## ♿ Accessibility: Reduced Motion
640
+
641
+ **Always respect user preferences:**
642
+
643
+ ```css
644
+ /* Default: animations enabled */
645
+ .element {
646
+ animation: slideIn 300ms ease-out;
647
+ }
648
+
649
+ /* User prefers reduced motion */
650
+ @media (prefers-reduced-motion: reduce) {
651
+ .element {
652
+ animation: none;
653
+ transition: none;
654
+ }
655
+ }
656
+ ```
657
+
658
+ **React Hook:**
659
+
660
+ ```tsx
661
+ import { useEffect, useState } from 'react'
662
+
663
+ export function usePrefersReducedMotion() {
664
+ const [prefersReducedMotion, setPrefersReducedMotion] = useState(false)
665
+
666
+ useEffect(() => {
667
+ const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)')
668
+ setPrefersReducedMotion(mediaQuery.matches)
669
+
670
+ const handler = (e: MediaQueryListEvent) => setPrefersReducedMotion(e.matches)
671
+ mediaQuery.addEventListener('change', handler)
672
+ return () => mediaQuery.removeEventListener('change', handler)
673
+ }, [])
674
+
675
+ return prefersReducedMotion
676
+ }
677
+ ```
678
+
679
+ ---
680
+
681
+ ## 📋 Animation Checklist
682
+
683
+ **Before implementing animations:**
684
+
685
+ - [ ] **Performance:** Using `transform`/`opacity` only? ✓
686
+ - [ ] **Duration:** 150-600ms (appropriate for use case)? ✓
687
+ - [ ] **Easing:** Using `ease-out` (most cases) or `ease-in-out`? ✓
688
+ - [ ] **Purpose:** Animation has clear purpose (not decorative)? ✓
689
+ - [ ] **Accessibility:** Respects `prefers-reduced-motion`? ✓
690
+ - [ ] **Subtlety:** Animation enhances, doesn't distract? ✓
691
+
692
+ ---
693
+
694
+ ## 🚨 Common Mistakes
695
+
696
+ ### ❌ Mistake 1: Animating layout properties
697
+
698
+ ```css
699
+ /* ❌ BAD: Width triggers layout recalculation */
700
+ .element:hover {
701
+ width: 200px;
702
+ transition: width 300ms;
703
+ }
704
+
705
+ /* ✅ GOOD: Scale uses transform (GPU) */
706
+ .element:hover {
707
+ transform: scaleX(1.2);
708
+ transition: transform 300ms;
709
+ }
710
+ ```
711
+
712
+ ---
713
+
714
+ ### ❌ Mistake 2: Too long animations
715
+
716
+ ```css
717
+ /* ❌ BAD: Feels sluggish */
718
+ .button:hover {
719
+ transform: scale(1.05);
720
+ transition: transform 1000ms;
721
+ }
722
+
723
+ /* ✅ GOOD: Snappy and responsive */
724
+ .button:hover {
725
+ transform: scale(1.05);
726
+ transition: transform 150ms ease-out;
727
+ }
728
+ ```
729
+
730
+ ---
731
+
732
+ ### ❌ Mistake 3: Forgetting reduced motion
733
+
734
+ ```css
735
+ /* ❌ BAD: Ignores user preference */
736
+ .element {
737
+ animation: spinForever 2s infinite;
738
+ }
739
+
740
+ /* ✅ GOOD: Respects accessibility */
741
+ .element {
742
+ animation: spinForever 2s infinite;
743
+ }
744
+
745
+ @media (prefers-reduced-motion: reduce) {
746
+ .element {
747
+ animation: none;
748
+ }
749
+ }
750
+ ```
751
+
752
+ ---
753
+
754
+ ## 📚 Additional Resources
755
+
756
+ **Libraries:**
757
+ - **Framer Motion:** https://www.framer.com/motion/ (React animations)
758
+ - **GSAP:** https://greensock.com/gsap/ (High-performance animations)
759
+ - **Auto Animate:** https://auto-animate.formkit.com/ (Zero-config animations)
760
+
761
+ **Inspiration:**
762
+ - **UI Movement:** https://uimovement.com/
763
+ - **Lottie Files:** https://lottiefiles.com/
764
+ - **Codrops:** https://tympanus.net/codrops/
765
+
766
+ ---
767
+
768
+ **💡 Remember:** The best animations are the ones users don't consciously notice - they just make the UI feel better!