@anubis609/astroanimate-core 0.1.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.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +212 -0
  3. package/dist/components/AnimatedBorderButton/AnimatedBorderButton.astro +129 -0
  4. package/dist/components/AnimatedBorderButton/index.js +3 -0
  5. package/dist/components/AnimatedBorderButton/index.js.map +1 -0
  6. package/dist/components/AnimatedButton/AnimatedButton.astro +299 -0
  7. package/dist/components/AnimatedButton/index.js +3 -0
  8. package/dist/components/AnimatedButton/index.js.map +1 -0
  9. package/dist/components/AnimatedCard/AnimatedCard.astro +832 -0
  10. package/dist/components/AnimatedCard/index.js +3 -0
  11. package/dist/components/AnimatedCard/index.js.map +1 -0
  12. package/dist/components/AnimatedTabs/AnimatedTabs.astro +348 -0
  13. package/dist/components/AnimatedTabs/index.js +3 -0
  14. package/dist/components/AnimatedTabs/index.js.map +1 -0
  15. package/dist/components/ArrowCTAButton/ArrowCTAButton.astro +159 -0
  16. package/dist/components/ArticleCard/ArticleCard.astro +208 -0
  17. package/dist/components/CardStack/CardStack.astro +444 -0
  18. package/dist/components/CardStack/index.js +3 -0
  19. package/dist/components/CardStack/index.js.map +1 -0
  20. package/dist/components/CountUp/CountUp.astro +89 -0
  21. package/dist/components/CountUp/index.js +3 -0
  22. package/dist/components/CountUp/index.js.map +1 -0
  23. package/dist/components/Dock/Dock.astro +567 -0
  24. package/dist/components/Dock/DockItem.astro +135 -0
  25. package/dist/components/Dropdown/Dropdown.astro +264 -0
  26. package/dist/components/ExpandableCard/ExpandableCard.astro +402 -0
  27. package/dist/components/ExpandableCard/index.js +3 -0
  28. package/dist/components/ExpandableCard/index.js.map +1 -0
  29. package/dist/components/FadeInText/FadeInText.astro +314 -0
  30. package/dist/components/FadeInText/index.js +3 -0
  31. package/dist/components/FadeInText/index.js.map +1 -0
  32. package/dist/components/FillHoverButton/FillHoverButton.astro +125 -0
  33. package/dist/components/GitHubShineButton/GitHubShineButton.astro +208 -0
  34. package/dist/components/GlassCard/GlassCard.astro +245 -0
  35. package/dist/components/GlassCard/index.js +3 -0
  36. package/dist/components/GlassCard/index.js.map +1 -0
  37. package/dist/components/GridDotsBackground/GridDotsBackground.astro +144 -0
  38. package/dist/components/HighlightText/HighlightText.astro +106 -0
  39. package/dist/components/InfiniteMarquee/InfiniteMarquee.astro +339 -0
  40. package/dist/components/JobCard/JobCard.astro +230 -0
  41. package/dist/components/LiquidGlassCard/LiquidGlassCard.astro +569 -0
  42. package/dist/components/Loader/Loader.astro +156 -0
  43. package/dist/components/Loader/index.js +3 -0
  44. package/dist/components/Loader/index.js.map +1 -0
  45. package/dist/components/NewsletterPopupCard/NewsletterPopupCard.astro +331 -0
  46. package/dist/components/ProductReviewCard/ProductReviewCard.astro +188 -0
  47. package/dist/components/ProgressBar/ProgressBar.astro +137 -0
  48. package/dist/components/ProgressBar/index.js +3 -0
  49. package/dist/components/ProgressBar/index.js.map +1 -0
  50. package/dist/components/RevealImage/RevealImage.astro +160 -0
  51. package/dist/components/RevealImage/index.js +3 -0
  52. package/dist/components/RevealImage/index.js.map +1 -0
  53. package/dist/components/ScaleIn/ScaleIn.astro +231 -0
  54. package/dist/components/ScaleIn/index.js +3 -0
  55. package/dist/components/ScaleIn/index.js.map +1 -0
  56. package/dist/components/SlidingOverlayButton/SlidingOverlayButton.astro +126 -0
  57. package/dist/components/StaggerTextButton/StaggerTextButton.astro +132 -0
  58. package/dist/components/Tooltip/Tooltip.astro +255 -0
  59. package/dist/components/Tooltip/index.js +3 -0
  60. package/dist/components/Tooltip/index.js.map +1 -0
  61. package/dist/components/TypewriterText/TypewriterText.astro +380 -0
  62. package/dist/components/TypewriterText/index.js +3 -0
  63. package/dist/components/TypewriterText/index.js.map +1 -0
  64. package/dist/components/index.js +33 -0
  65. package/dist/components/index.js.map +1 -0
  66. package/dist/index.js +31 -0
  67. package/dist/index.js.map +1 -0
  68. package/dist/internal/countup.js +90 -0
  69. package/dist/internal/countup.js.map +1 -0
  70. package/dist/internal/dropdown.js +166 -0
  71. package/dist/internal/dropdown.js.map +1 -0
  72. package/dist/internal/fadein.js +116 -0
  73. package/dist/internal/fadein.js.map +1 -0
  74. package/dist/internal/guards.js +12 -0
  75. package/dist/internal/guards.js.map +1 -0
  76. package/dist/internal/tabs.js +140 -0
  77. package/dist/internal/tabs.js.map +1 -0
  78. package/package.json +229 -0
@@ -0,0 +1,832 @@
1
+ ---
2
+ /**
3
+ * AnimatedCard.astro
4
+ */
5
+
6
+ interface Props {
7
+ title: string;
8
+ description: string;
9
+ variant?: "lift" | "scale" | "flip" | "shine";
10
+ color?: "rose" | "emerald" | "violet" | "amber";
11
+ selected?: boolean;
12
+ selectedOnScroll?: boolean;
13
+ class?: string;
14
+ customClass?: string;
15
+ style?: string;
16
+ href?: string;
17
+ target?: string;
18
+ rel?: string;
19
+ backTitle?: string;
20
+ backDescription?: string;
21
+ backButtonText?: string;
22
+ flipHint?: string;
23
+ iconLabel?: string;
24
+ minHeight?: string;
25
+ ariaLabel?: string;
26
+ }
27
+
28
+ const {
29
+ title,
30
+ description,
31
+ variant = "lift",
32
+ color = "rose",
33
+ selected = false,
34
+ selectedOnScroll = false,
35
+ class: className = "",
36
+ customClass = "",
37
+ style: styleProp = "",
38
+ href,
39
+ target,
40
+ rel,
41
+ backTitle,
42
+ backDescription = "Tap or click the button below to flip this card back.",
43
+ backButtonText = "Flip back",
44
+ flipHint = "Flip to reveal more details",
45
+ iconLabel = "✦",
46
+ minHeight = "18rem",
47
+ ariaLabel,
48
+ } = Astro.props;
49
+
50
+ const themeMap = {
51
+ rose: {
52
+ accent: "#f43f5e",
53
+ accentText: "#fb7185",
54
+ border: "rgba(244, 63, 94, 0.28)",
55
+ soft: "rgba(244, 63, 94, 0.12)",
56
+ aura: "rgba(244, 63, 94, 0.2)",
57
+ surface:
58
+ "linear-gradient(180deg, rgba(30, 41, 59, 0.96), rgba(15, 23, 42, 1))",
59
+ backSurface:
60
+ "linear-gradient(180deg, rgba(38, 18, 31, 0.98), rgba(20, 11, 21, 1))",
61
+ inset: "rgba(251, 113, 133, 0.22)",
62
+ },
63
+ emerald: {
64
+ accent: "#10b981",
65
+ accentText: "#34d399",
66
+ border: "rgba(16, 185, 129, 0.28)",
67
+ soft: "rgba(16, 185, 129, 0.12)",
68
+ aura: "rgba(16, 185, 129, 0.18)",
69
+ surface:
70
+ "linear-gradient(180deg, rgba(15, 36, 34, 0.96), rgba(6, 22, 19, 1))",
71
+ backSurface:
72
+ "linear-gradient(180deg, rgba(8, 33, 28, 0.98), rgba(5, 21, 18, 1))",
73
+ inset: "rgba(52, 211, 153, 0.2)",
74
+ },
75
+ violet: {
76
+ accent: "#8b5cf6",
77
+ accentText: "#a78bfa",
78
+ border: "rgba(139, 92, 246, 0.28)",
79
+ soft: "rgba(139, 92, 246, 0.12)",
80
+ aura: "rgba(139, 92, 246, 0.2)",
81
+ surface:
82
+ "linear-gradient(180deg, rgba(27, 22, 46, 0.97), rgba(15, 14, 30, 1))",
83
+ backSurface:
84
+ "linear-gradient(180deg, rgba(22, 16, 41, 0.98), rgba(14, 10, 31, 1))",
85
+ inset: "rgba(167, 139, 250, 0.2)",
86
+ },
87
+ amber: {
88
+ accent: "#f59e0b",
89
+ accentText: "#fbbf24",
90
+ border: "rgba(245, 158, 11, 0.28)",
91
+ soft: "rgba(245, 158, 11, 0.12)",
92
+ aura: "rgba(245, 158, 11, 0.18)",
93
+ surface:
94
+ "linear-gradient(180deg, rgba(42, 29, 10, 0.97), rgba(28, 18, 8, 1))",
95
+ backSurface:
96
+ "linear-gradient(180deg, rgba(37, 24, 8, 0.98), rgba(24, 15, 5, 1))",
97
+ inset: "rgba(251, 191, 36, 0.2)",
98
+ },
99
+ } as const;
100
+
101
+ const theme = themeMap[color];
102
+ const mergedClass = [className, customClass].filter(Boolean).join(" ");
103
+ const CardTag = href ? "a" : "article";
104
+ const flipId = `animated-card-flip-${Math.random().toString(36).slice(2, 10)}`;
105
+ const mergedStyle = [
106
+ `--animated-card-accent: ${theme.accent}`,
107
+ `--animated-card-accent-text: ${theme.accentText}`,
108
+ `--animated-card-border: ${theme.border}`,
109
+ `--animated-card-soft: ${theme.soft}`,
110
+ `--animated-card-aura: ${theme.aura}`,
111
+ `--animated-card-surface: ${theme.surface}`,
112
+ `--animated-card-back-surface: ${theme.backSurface}`,
113
+ `--animated-card-inset: ${theme.inset}`,
114
+ `--animated-card-min-height: ${minHeight}`,
115
+ styleProp,
116
+ ]
117
+ .filter(Boolean)
118
+ .join("; ");
119
+
120
+ const cardLabel = ariaLabel ?? title;
121
+ const backHeading = backTitle ?? title;
122
+ const interactiveAttrs = href
123
+ ? {
124
+ href,
125
+ target,
126
+ rel,
127
+ }
128
+ : {};
129
+ ---
130
+
131
+ <style>
132
+ /* ✅ CRITERION 7: CSS-FIRST architecture with themeable custom properties */
133
+ [data-animated-card],
134
+ [data-animated-card] * {
135
+ box-sizing: border-box;
136
+ }
137
+
138
+ [data-animated-card] {
139
+ --animated-card-radius: 1.5rem;
140
+ --animated-card-padding: 1.5rem;
141
+ --animated-card-shadow:
142
+ 0 14px 30px rgba(2, 6, 23, 0.24),
143
+ 0 2px 10px rgba(2, 6, 23, 0.2);
144
+ --animated-card-shadow-active:
145
+ 0 28px 52px rgba(2, 6, 23, 0.42),
146
+ 0 0 0 1px rgba(255, 255, 255, 0.03);
147
+ --animated-card-title-size: 1.25rem;
148
+ --animated-card-icon-size: 3.5rem;
149
+
150
+ position: relative;
151
+ isolation: isolate;
152
+ display: flex;
153
+ min-height: var(--animated-card-min-height, 18rem);
154
+ border-radius: var(--animated-card-radius);
155
+ background: var(--animated-card-bg, var(--animated-card-surface));
156
+ border: 1px solid var(--animated-card-border);
157
+ box-shadow: var(--animated-card-shadow);
158
+ overflow: hidden;
159
+ color: #ffffff;
160
+ }
161
+
162
+ [data-animated-card][data-clickable="true"] {
163
+ text-decoration: none;
164
+ }
165
+
166
+ .animated-card__surface {
167
+ position: relative;
168
+ display: flex;
169
+ flex-direction: column;
170
+ width: 100%;
171
+ min-height: inherit;
172
+ padding: var(--animated-card-padding);
173
+ gap: 0.95rem;
174
+ z-index: 1;
175
+ }
176
+
177
+ .animated-card__ambient,
178
+ .animated-card__grid,
179
+ .animated-card__shine-overlay,
180
+ .animated-card__inner-glow,
181
+ .animated-card__indicator {
182
+ pointer-events: none;
183
+ position: absolute;
184
+ }
185
+
186
+ .animated-card__ambient {
187
+ inset: auto -15% -35% auto;
188
+ width: 12rem;
189
+ height: 12rem;
190
+ border-radius: 999px;
191
+ background: radial-gradient(circle, var(--animated-card-aura), transparent 70%);
192
+ opacity: 0.7;
193
+ transition:
194
+ opacity 260ms ease,
195
+ transform 260ms ease,
196
+ filter 260ms ease;
197
+ z-index: 0;
198
+ }
199
+
200
+ .animated-card__grid {
201
+ inset: 0;
202
+ background-image:
203
+ linear-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1px),
204
+ linear-gradient(90deg, rgba(255, 255, 255, 0.045) 1px, transparent 1px);
205
+ background-size: 28px 28px;
206
+ mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.4), transparent 90%);
207
+ opacity: 0.16;
208
+ z-index: 0;
209
+ }
210
+
211
+ .animated-card__shine-overlay {
212
+ inset: -35% auto -35% -120%;
213
+ width: 68%;
214
+ background: linear-gradient(
215
+ 115deg,
216
+ transparent 0%,
217
+ rgba(255, 255, 255, 0.04) 25%,
218
+ rgba(255, 255, 255, 0.2) 50%,
219
+ rgba(255, 255, 255, 0.04) 75%,
220
+ transparent 100%
221
+ );
222
+ transform: skewX(-22deg) translateX(0);
223
+ opacity: 0;
224
+ transition:
225
+ transform 600ms cubic-bezier(0.22, 1, 0.36, 1),
226
+ opacity 220ms ease;
227
+ z-index: 1;
228
+ }
229
+
230
+ .animated-card__inner-glow {
231
+ inset: 0;
232
+ border-radius: inherit;
233
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
234
+ opacity: 1;
235
+ transition:
236
+ box-shadow 280ms ease,
237
+ opacity 280ms ease;
238
+ z-index: 0;
239
+ }
240
+
241
+ .animated-card__indicator {
242
+ left: 1.25rem;
243
+ right: 1.25rem;
244
+ bottom: 0;
245
+ height: 0.2rem;
246
+ border-radius: 999px 999px 0 0;
247
+ background: linear-gradient(
248
+ 90deg,
249
+ transparent,
250
+ var(--animated-card-accent),
251
+ transparent
252
+ );
253
+ transform: scaleX(0.18);
254
+ transform-origin: center;
255
+ opacity: 0;
256
+ transition:
257
+ transform 280ms ease,
258
+ opacity 280ms ease;
259
+ z-index: 2;
260
+ }
261
+
262
+ .animated-card__topline {
263
+ display: flex;
264
+ align-items: flex-start;
265
+ justify-content: space-between;
266
+ gap: 1rem;
267
+ }
268
+
269
+ .animated-card__icon-shell {
270
+ width: var(--animated-card-icon-size);
271
+ height: var(--animated-card-icon-size);
272
+ border-radius: 1rem;
273
+ background: linear-gradient(
274
+ 180deg,
275
+ rgba(255, 255, 255, 0.06),
276
+ var(--animated-card-soft)
277
+ );
278
+ border: 1px solid var(--animated-card-border);
279
+ display: inline-flex;
280
+ align-items: center;
281
+ justify-content: center;
282
+ color: var(--animated-card-accent-text);
283
+ flex-shrink: 0;
284
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.06);
285
+ transition:
286
+ transform 280ms ease,
287
+ background 280ms ease,
288
+ border-color 280ms ease;
289
+ }
290
+
291
+ .animated-card__icon-fallback {
292
+ font-size: 1.75rem;
293
+ line-height: 1;
294
+ }
295
+
296
+ .animated-card__badge {
297
+ display: inline-flex;
298
+ align-items: center;
299
+ justify-content: center;
300
+ align-self: flex-start;
301
+ padding: 0.4rem 0.72rem;
302
+ border-radius: 999px;
303
+ font-size: 0.72rem;
304
+ font-weight: 700;
305
+ letter-spacing: 0.08em;
306
+ text-transform: uppercase;
307
+ color: var(--animated-card-accent-text);
308
+ background: color-mix(in srgb, var(--animated-card-accent) 18%, transparent);
309
+ border: 1px solid color-mix(in srgb, var(--animated-card-accent) 32%, transparent);
310
+ backdrop-filter: blur(8px);
311
+ }
312
+
313
+ .animated-card__title {
314
+ margin: 0;
315
+ font-size: var(--animated-card-title-size);
316
+ line-height: 1.2;
317
+ font-weight: 700;
318
+ letter-spacing: -0.02em;
319
+ }
320
+
321
+ .animated-card__description,
322
+ .animated-card__back-description,
323
+ .animated-card__hint {
324
+ margin: 0;
325
+ color: rgba(226, 232, 240, 0.78);
326
+ line-height: 1.65;
327
+ font-size: 0.95rem;
328
+ }
329
+
330
+ .animated-card__content {
331
+ display: flex;
332
+ flex-direction: column;
333
+ gap: 0.8rem;
334
+ position: relative;
335
+ z-index: 2;
336
+ flex: 1;
337
+ }
338
+
339
+ .animated-card__meta {
340
+ margin-top: auto;
341
+ display: flex;
342
+ align-items: center;
343
+ justify-content: space-between;
344
+ gap: 1rem;
345
+ color: rgba(255, 255, 255, 0.68);
346
+ font-size: 0.82rem;
347
+ }
348
+
349
+ .animated-card__meta-dot {
350
+ width: 0.55rem;
351
+ height: 0.55rem;
352
+ border-radius: 999px;
353
+ background: var(--animated-card-accent);
354
+ box-shadow: 0 0 0 0.3rem color-mix(in srgb, var(--animated-card-accent) 18%, transparent);
355
+ }
356
+
357
+ [data-animated-card]:focus-visible,
358
+ .animated-card__toggle:focus-visible + .animated-card__flip-scene {
359
+ outline: 2px solid color-mix(in srgb, var(--animated-card-accent) 55%, white);
360
+ outline-offset: 4px;
361
+ }
362
+
363
+ [data-animated-card][data-selected="true"]:not([data-selected-on-scroll="true"]) .animated-card__indicator,
364
+ [data-animated-card][data-indicator-visible="true"] .animated-card__indicator,
365
+ [data-animated-card]:is(:hover, :focus-visible) .animated-card__indicator {
366
+ opacity: 1;
367
+ transform: scaleX(1);
368
+ }
369
+
370
+ /* ✅ CRITERION 1: Meaningful card content visible server-side without JS */
371
+ .animated-card--lift,
372
+ .animated-card--scale,
373
+ .animated-card--shine {
374
+ transition:
375
+ transform 280ms cubic-bezier(0.22, 1, 0.36, 1),
376
+ box-shadow 280ms cubic-bezier(0.22, 1, 0.36, 1),
377
+ border-color 280ms ease,
378
+ filter 280ms ease;
379
+ }
380
+
381
+ .animated-card--lift:is(:hover, :focus-visible) {
382
+ transform: translateY(-0.55rem);
383
+ box-shadow:
384
+ 0 28px 54px rgba(2, 6, 23, 0.42),
385
+ 0 0 3rem color-mix(in srgb, var(--animated-card-accent) 18%, transparent);
386
+ border-color: color-mix(in srgb, var(--animated-card-accent) 40%, transparent);
387
+ }
388
+
389
+ .animated-card--lift:is(:hover, :focus-visible) .animated-card__ambient {
390
+ opacity: 1;
391
+ transform: translate3d(-0.5rem, -0.6rem, 0) scale(1.04);
392
+ filter: blur(2px);
393
+ }
394
+
395
+ .animated-card--lift:is(:hover, :focus-visible) .animated-card__icon-shell {
396
+ transform: translateY(-0.25rem);
397
+ }
398
+
399
+ .animated-card--scale:is(:hover, :focus-visible) {
400
+ transform: scale(1.04);
401
+ box-shadow:
402
+ 0 24px 44px rgba(2, 6, 23, 0.36),
403
+ 0 0 0 1px color-mix(in srgb, var(--animated-card-accent) 22%, transparent);
404
+ }
405
+
406
+ .animated-card--scale:is(:hover, :focus-visible) .animated-card__inner-glow {
407
+ box-shadow:
408
+ inset 0 0 0 1px color-mix(in srgb, var(--animated-card-accent) 30%, transparent),
409
+ inset 0 0 2.25rem var(--animated-card-inset);
410
+ }
411
+
412
+ .animated-card--scale:is(:hover, :focus-visible) .animated-card__icon-shell {
413
+ transform: scale(1.08);
414
+ background: linear-gradient(
415
+ 180deg,
416
+ rgba(255, 255, 255, 0.1),
417
+ color-mix(in srgb, var(--animated-card-accent) 18%, transparent)
418
+ );
419
+ }
420
+
421
+ .animated-card--shine:is(:hover, :focus-visible) {
422
+ box-shadow:
423
+ 0 20px 42px rgba(2, 6, 23, 0.34),
424
+ 0 0 0 1px rgba(255, 255, 255, 0.04);
425
+ border-color: color-mix(in srgb, var(--animated-card-accent) 32%, transparent);
426
+ }
427
+
428
+ .animated-card--shine:is(:hover, :focus-visible) .animated-card__shine-overlay {
429
+ opacity: 1;
430
+ transform: skewX(-22deg) translateX(430%);
431
+ }
432
+
433
+ .animated-card--shine:is(:hover, :focus-visible) .animated-card__ambient {
434
+ opacity: 0.92;
435
+ transform: scale(1.08);
436
+ }
437
+
438
+ .animated-card--shine:is(:hover, :focus-visible) .animated-card__icon-shell {
439
+ border-color: color-mix(in srgb, var(--animated-card-accent) 42%, transparent);
440
+ }
441
+
442
+ /* Flip variant */
443
+ .animated-card--flip {
444
+ padding: 0;
445
+ background: transparent;
446
+ border: 0;
447
+ box-shadow: none;
448
+ overflow: visible;
449
+ }
450
+
451
+ .animated-card__toggle {
452
+ position: absolute;
453
+ width: 1px;
454
+ height: 1px;
455
+ margin: -1px;
456
+ padding: 0;
457
+ overflow: hidden;
458
+ clip: rect(0, 0, 0, 0);
459
+ clip-path: inset(50%);
460
+ white-space: nowrap;
461
+ border: 0;
462
+ }
463
+
464
+ .animated-card__flip-scene {
465
+ position: relative;
466
+ width: 100%;
467
+ min-height: var(--animated-card-min-height, 18rem);
468
+ perspective: 1200px;
469
+ }
470
+
471
+ .animated-card__flip-inner {
472
+ position: relative;
473
+ width: 100%;
474
+ min-height: inherit;
475
+ transform-style: preserve-3d;
476
+ transition: transform 600ms cubic-bezier(0.22, 1, 0.36, 1);
477
+ }
478
+
479
+ .animated-card__toggle:checked + .animated-card__flip-scene .animated-card__flip-inner {
480
+ transform: rotateY(180deg);
481
+ }
482
+
483
+ .animated-card__face {
484
+ position: absolute;
485
+ inset: 0;
486
+ display: flex;
487
+ min-height: inherit;
488
+ border-radius: var(--animated-card-radius);
489
+ overflow: hidden;
490
+ backface-visibility: hidden;
491
+ -webkit-backface-visibility: hidden;
492
+ box-shadow: var(--animated-card-shadow);
493
+ }
494
+
495
+ .animated-card__face--front,
496
+ .animated-card__face--back {
497
+ background: var(--animated-card-bg, var(--animated-card-surface));
498
+ border: 1px solid var(--animated-card-border);
499
+ }
500
+
501
+ .animated-card__face--front {
502
+ cursor: pointer;
503
+ }
504
+
505
+ .animated-card__face--back {
506
+ background: var(--animated-card-back-surface);
507
+ transform: rotateY(180deg);
508
+ }
509
+
510
+ .animated-card__face--front .animated-card__surface,
511
+ .animated-card__face--back .animated-card__surface {
512
+ min-height: var(--animated-card-min-height, 18rem);
513
+ }
514
+
515
+ .animated-card__back-content {
516
+ display: flex;
517
+ flex-direction: column;
518
+ justify-content: center;
519
+ align-items: flex-start;
520
+ gap: 0.95rem;
521
+ width: 100%;
522
+ }
523
+
524
+ .animated-card__back-button {
525
+ display: inline-flex;
526
+ align-items: center;
527
+ justify-content: center;
528
+ align-self: flex-start;
529
+ margin-top: 0.2rem;
530
+ padding: 0.8rem 1rem;
531
+ border-radius: 0.9rem;
532
+ cursor: pointer;
533
+ color: #fff;
534
+ background: linear-gradient(
535
+ 180deg,
536
+ color-mix(in srgb, var(--animated-card-accent) 88%, white 6%),
537
+ var(--animated-card-accent)
538
+ );
539
+ border: 1px solid color-mix(in srgb, var(--animated-card-accent) 62%, transparent);
540
+ font-size: 0.9rem;
541
+ font-weight: 700;
542
+ box-shadow:
543
+ 0 10px 24px color-mix(in srgb, var(--animated-card-accent) 26%, transparent),
544
+ inset 0 1px 0 rgba(255, 255, 255, 0.18);
545
+ }
546
+
547
+ .animated-card__flip-instruction {
548
+ display: inline-flex;
549
+ align-items: center;
550
+ gap: 0.55rem;
551
+ margin-top: auto;
552
+ color: rgba(255, 255, 255, 0.68);
553
+ font-size: 0.82rem;
554
+ }
555
+
556
+ .animated-card__flip-instruction::before {
557
+ content: "";
558
+ width: 0.7rem;
559
+ height: 0.7rem;
560
+ border-radius: 999px;
561
+ border: 1px solid color-mix(in srgb, var(--animated-card-accent) 46%, transparent);
562
+ background: color-mix(in srgb, var(--animated-card-accent) 18%, transparent);
563
+ }
564
+
565
+ .animated-card__toggle:focus-visible + .animated-card__flip-scene .animated-card__face--front,
566
+ .animated-card__face--front:hover {
567
+ border-color: color-mix(in srgb, var(--animated-card-accent) 42%, transparent);
568
+ box-shadow:
569
+ 0 28px 54px rgba(2, 6, 23, 0.42),
570
+ 0 0 3rem color-mix(in srgb, var(--animated-card-accent) 18%, transparent);
571
+ }
572
+
573
+ .animated-card__face--front:hover .animated-card__icon-shell,
574
+ .animated-card__toggle:focus-visible
575
+ + .animated-card__flip-scene
576
+ .animated-card__face--front
577
+ .animated-card__icon-shell {
578
+ transform: translateY(-0.22rem);
579
+ }
580
+
581
+ /* ✅ CRITERION 6: Reduced motion disables transforms and sweep animations */
582
+ @media (prefers-reduced-motion: reduce) {
583
+ [data-animated-card],
584
+ [data-animated-card] * {
585
+ animation: none !important;
586
+ transition: none !important;
587
+ scroll-behavior: auto !important;
588
+ }
589
+
590
+ .animated-card--lift:is(:hover, :focus-visible),
591
+ .animated-card--scale:is(:hover, :focus-visible) {
592
+ transform: none !important;
593
+ }
594
+
595
+ .animated-card--shine .animated-card__shine-overlay {
596
+ opacity: 0 !important;
597
+ transform: none !important;
598
+ }
599
+
600
+ .animated-card__flip-inner {
601
+ transform: none !important;
602
+ }
603
+
604
+ .animated-card__toggle:checked + .animated-card__flip-scene .animated-card__face--front {
605
+ opacity: 0;
606
+ visibility: hidden;
607
+ }
608
+
609
+ .animated-card__toggle:checked + .animated-card__flip-scene .animated-card__face--back {
610
+ opacity: 1;
611
+ visibility: visible;
612
+ }
613
+
614
+ .animated-card__face--front {
615
+ opacity: 1;
616
+ visibility: visible;
617
+ }
618
+
619
+ .animated-card__face--back {
620
+ opacity: 0;
621
+ visibility: hidden;
622
+ transform: none !important;
623
+ }
624
+ }
625
+
626
+ @media (max-width: 640px) {
627
+ [data-animated-card] {
628
+ --animated-card-padding: 1.25rem;
629
+ --animated-card-radius: 1.25rem;
630
+ }
631
+
632
+ .animated-card__title {
633
+ font-size: 1.12rem;
634
+ }
635
+
636
+ .animated-card__description,
637
+ .animated-card__back-description,
638
+ .animated-card__hint {
639
+ font-size: 0.92rem;
640
+ }
641
+ }
642
+ </style>
643
+
644
+ {variant === "flip" ? (
645
+ <div
646
+ class:list={["animated-card--flip", mergedClass]}
647
+ data-animated-card
648
+ data-color={color}
649
+ data-variant={variant}
650
+ data-selected={selected ? "true" : "false"}
651
+ data-selected-on-scroll={selectedOnScroll ? "true" : "false"}
652
+ style={mergedStyle}
653
+ aria-label={cardLabel}
654
+ >
655
+ <input
656
+ id={flipId}
657
+ class="animated-card__toggle"
658
+ type="checkbox"
659
+ aria-label={`Flip ${cardLabel}`}
660
+ />
661
+ <div class="animated-card__flip-scene">
662
+ <div class="animated-card__flip-inner">
663
+ <label
664
+ for={flipId}
665
+ class="animated-card__face animated-card__face--front"
666
+ data-flip-trigger
667
+ role="button"
668
+ tabindex="0"
669
+ >
670
+ <span class="animated-card__ambient" aria-hidden="true"></span>
671
+ <span class="animated-card__grid" aria-hidden="true"></span>
672
+ <span class="animated-card__inner-glow" aria-hidden="true"></span>
673
+ <span class="animated-card__indicator" aria-hidden="true"></span>
674
+ <div class="animated-card__surface">
675
+ <div class="animated-card__topline">
676
+ <div class="animated-card__icon-shell">
677
+ <slot name="icon">
678
+ <span class="animated-card__icon-fallback" aria-hidden="true">
679
+ {iconLabel}
680
+ </span>
681
+ </slot>
682
+ </div>
683
+ {selected && <span class="animated-card__badge">Selected</span>}
684
+ </div>
685
+
686
+ <div class="animated-card__content">
687
+ <h3 class="animated-card__title">{title}</h3>
688
+ <p class="animated-card__description">{description}</p>
689
+ <p class="animated-card__flip-instruction">{flipHint}</p>
690
+ </div>
691
+ </div>
692
+ </label>
693
+
694
+ <div class="animated-card__face animated-card__face--back">
695
+ <span class="animated-card__ambient" aria-hidden="true"></span>
696
+ <span class="animated-card__inner-glow" aria-hidden="true"></span>
697
+ <div class="animated-card__surface">
698
+ <div class="animated-card__back-content">
699
+ <slot name="back-content">
700
+ <div class="animated-card__icon-shell">
701
+ <span class="animated-card__icon-fallback" aria-hidden="true">
702
+ {iconLabel}
703
+ </span>
704
+ </div>
705
+ <h3 class="animated-card__title">{backHeading}</h3>
706
+ <p class="animated-card__back-description">{backDescription}</p>
707
+ </slot>
708
+
709
+ <label
710
+ for={flipId}
711
+ class="animated-card__back-button"
712
+ data-flip-trigger
713
+ role="button"
714
+ tabindex="0"
715
+ >
716
+ {backButtonText}
717
+ </label>
718
+ </div>
719
+ </div>
720
+ </div>
721
+ </div>
722
+ </div>
723
+ </div>
724
+ ) : (
725
+ <CardTag
726
+ class:list={[
727
+ `animated-card--${variant}`,
728
+ { "animated-card--scale": variant === "scale" },
729
+ { "animated-card--shine": variant === "shine" },
730
+ { "animated-card--lift": variant === "lift" },
731
+ mergedClass,
732
+ ]}
733
+ data-animated-card
734
+ data-color={color}
735
+ data-variant={variant}
736
+ data-selected={selected ? "true" : "false"}
737
+ data-selected-on-scroll={selectedOnScroll ? "true" : "false"}
738
+ data-clickable={href ? "true" : "false"}
739
+ style={mergedStyle}
740
+ aria-label={cardLabel}
741
+ {...interactiveAttrs}
742
+ >
743
+ <span class="animated-card__ambient" aria-hidden="true"></span>
744
+ <span class="animated-card__grid" aria-hidden="true"></span>
745
+ <span class="animated-card__shine-overlay" aria-hidden="true"></span>
746
+ <span class="animated-card__inner-glow" aria-hidden="true"></span>
747
+ <span class="animated-card__indicator" aria-hidden="true"></span>
748
+
749
+ <div class="animated-card__surface">
750
+ <div class="animated-card__topline">
751
+ <div class="animated-card__icon-shell">
752
+ <slot name="icon">
753
+ <span class="animated-card__icon-fallback" aria-hidden="true">
754
+ {iconLabel}
755
+ </span>
756
+ </slot>
757
+ </div>
758
+ {selected && <span class="animated-card__badge">Selected</span>}
759
+ </div>
760
+
761
+ <div class="animated-card__content">
762
+ <h3 class="animated-card__title">{title}</h3>
763
+ <p class="animated-card__description">{description}</p>
764
+ </div>
765
+
766
+ <div class="animated-card__meta" aria-hidden="true">
767
+ <span class="animated-card__meta-dot"></span>
768
+ <span>{variant} interaction</span>
769
+ </div>
770
+ </div>
771
+ </CardTag>
772
+ )}
773
+
774
+ {variant === "flip" && (
775
+ <script>
776
+ document
777
+ .querySelectorAll("[data-flip-trigger]:not([data-flip-ready])")
778
+ .forEach((trigger) => {
779
+ if (!(trigger instanceof HTMLElement)) return;
780
+ trigger.dataset.flipReady = "true";
781
+
782
+ trigger.addEventListener("keydown", (event) => {
783
+ if (event.key === "Enter" || event.key === " ") {
784
+ event.preventDefault();
785
+ trigger.click();
786
+ }
787
+ });
788
+ });
789
+ </script>
790
+ )}
791
+
792
+ {selected && selectedOnScroll && (
793
+ <script>
794
+ const cards = document.querySelectorAll(
795
+ '[data-animated-card][data-selected="true"][data-selected-on-scroll="true"]:not([data-selected-scroll-ready])',
796
+ );
797
+
798
+ cards.forEach((card) => {
799
+ if (!(card instanceof HTMLElement)) return;
800
+ card.dataset.selectedScrollReady = "true";
801
+ });
802
+
803
+ if (cards.length > 0) {
804
+ const observer = new IntersectionObserver(
805
+ (entries, activeObserver) => {
806
+ entries.forEach((entry) => {
807
+ if (!(entry.target instanceof HTMLElement)) return;
808
+ if (!entry.isIntersecting) return;
809
+
810
+ entry.target.dataset.indicatorVisible = "true";
811
+ activeObserver.unobserve(entry.target);
812
+ });
813
+ },
814
+ {
815
+ threshold: 0.35,
816
+ rootMargin: "0px 0px -12% 0px",
817
+ },
818
+ );
819
+
820
+ const armOnScroll = () => {
821
+ cards.forEach((card) => {
822
+ if (card instanceof HTMLElement) observer.observe(card);
823
+ });
824
+ };
825
+
826
+ window.addEventListener("scroll", armOnScroll, {
827
+ passive: true,
828
+ once: true,
829
+ });
830
+ }
831
+ </script>
832
+ )}