astro-component-kit 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3279 @@
1
+ // src/data/registry/buttons.ts
2
+ import type { ComponentDoc } from './types';
3
+
4
+ export const buttons: ComponentDoc[] = [
5
+ {
6
+ slug: 'glass-button',
7
+ name: 'Glass Button',
8
+ description: 'A premium button with glassmorphism effect, gradient border and smooth hover animations.',
9
+ category: 'buttons',
10
+ tags: ['button', 'glass', 'gradient', 'animated'],
11
+ featured: true,
12
+ code: `---
13
+ interface Props {
14
+ href?: string;
15
+ type?: 'button' | 'submit' | 'reset';
16
+ variant?: 'primary' | 'secondary' | 'ghost';
17
+ size?: 'sm' | 'md' | 'lg';
18
+ disabled?: boolean;
19
+ }
20
+
21
+ const { href, type = 'button', variant = 'primary', size = 'md', disabled = false } = Astro.props;
22
+ const Tag = href ? 'a' : 'button';
23
+ ---
24
+ <Tag
25
+ href={href}
26
+ type={href ? undefined : type}
27
+ disabled={disabled}
28
+ class={\`glass-btn glass-btn--\${variant} glass-btn--\${size}\`}
29
+ >
30
+ <span class="glass-btn__shimmer"></span>
31
+ <span class="glass-btn__content">
32
+ <slot />
33
+ </span>
34
+ </Tag>
35
+
36
+ <style>
37
+ .glass-btn {
38
+ --btn-bg: rgba(255, 255, 255, 0.05);
39
+ --btn-border: rgba(255, 255, 255, 0.1);
40
+ --btn-text: var(--c-text-1, #d4d4d4);
41
+ --btn-glow: rgba(184, 134, 11, 0);
42
+
43
+ display: inline-flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ gap: 0.6rem;
47
+ font-family: var(--font-sans, inherit);
48
+ font-weight: 600;
49
+ letter-spacing: 0.02em;
50
+ border-radius: var(--r-md, 12px);
51
+ cursor: pointer;
52
+ text-decoration: none;
53
+ border: 1px solid var(--btn-border);
54
+ background: var(--btn-bg);
55
+ backdrop-filter: blur(12px);
56
+ -webkit-backdrop-filter: blur(12px);
57
+ position: relative;
58
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
59
+ white-space: nowrap;
60
+ user-select: none;
61
+ overflow: hidden;
62
+ color: var(--btn-text);
63
+ }
64
+
65
+ /* Shimmer Effect */
66
+ .glass-btn__shimmer {
67
+ position: absolute;
68
+ inset: 0;
69
+ background: linear-gradient(
70
+ 90deg,
71
+ transparent,
72
+ rgba(255, 255, 255, 0.05),
73
+ rgba(255, 255, 255, 0.15),
74
+ rgba(255, 255, 255, 0.05),
75
+ transparent
76
+ );
77
+ transform: translateX(-100%);
78
+ transition: transform 0.6s ease;
79
+ pointer-events: none;
80
+ }
81
+
82
+ .glass-btn:hover:not(:disabled) .glass-btn__shimmer {
83
+ transform: translateX(100%);
84
+ }
85
+
86
+ /* Glass Border Glow */
87
+ .glass-btn::after {
88
+ content: '';
89
+ position: absolute;
90
+ inset: 0;
91
+ border-radius: inherit;
92
+ padding: 1px;
93
+ background: linear-gradient(135deg, rgba(255,255,255,0.2), transparent, rgba(255,255,255,0.05));
94
+ -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
95
+ -webkit-mask-composite: xor;
96
+ mask-composite: exclude;
97
+ pointer-events: none;
98
+ opacity: 0.5;
99
+ transition: opacity 0.3s ease;
100
+ }
101
+
102
+ .glass-btn:hover:not(:disabled)::after {
103
+ opacity: 1;
104
+ }
105
+
106
+ .glass-btn:hover:not(:disabled) {
107
+ transform: translateY(-2px);
108
+ border-color: rgba(255, 255, 255, 0.2);
109
+ box-shadow: 0 10px 30px -10px var(--btn-glow);
110
+ }
111
+
112
+ .glass-btn:active:not(:disabled) {
113
+ transform: translateY(0) scale(0.98);
114
+ }
115
+
116
+ .glass-btn:disabled {
117
+ opacity: 0.4;
118
+ cursor: not-allowed;
119
+ filter: grayscale(1);
120
+ }
121
+
122
+ /* Variants */
123
+ .glass-btn--primary {
124
+ --btn-bg: linear-gradient(135deg, rgba(184, 134, 11, 0.7), rgba(212, 160, 23, 0.7));
125
+ --btn-text: #0a0a0a;
126
+ --btn-glow: rgba(184, 134, 11, 0.4);
127
+ border-color: rgba(255, 255, 255, 0.1);
128
+ }
129
+
130
+ .glass-btn--secondary {
131
+ --btn-bg: rgba(255, 255, 255, 0.03);
132
+ --btn-glow: rgba(255, 255, 255, 0.1);
133
+ }
134
+
135
+ .glass-btn--ghost {
136
+ --btn-bg: transparent;
137
+ --btn-text: var(--c-primary-light, #d4a017);
138
+ --btn-border: rgba(184, 134, 11, 0.25);
139
+ --btn-glow: rgba(184, 134, 11, 0.15);
140
+ backdrop-filter: none;
141
+ }
142
+
143
+ .glass-btn--ghost:hover:not(:disabled) {
144
+ background: rgba(184, 134, 11, 0.08);
145
+ border-color: rgba(184, 134, 11, 0.4);
146
+ }
147
+
148
+ /* Sizes */
149
+ .glass-btn--sm { padding: 0.45rem 1.1rem; font-size: 0.85rem; }
150
+ .glass-btn--md { padding: 0.75rem 1.6rem; font-size: 0.95rem; }
151
+ .glass-btn--lg { padding: 1.1rem 2.4rem; font-size: 1.1rem; }
152
+ </style>
153
+ `,
154
+ usage: `---
155
+ import { GlassButton } from 'astro-component-kit';
156
+ ---
157
+
158
+ <div class="button-group">
159
+ <GlassButton variant="primary">Explore Now</GlassButton>
160
+ <GlassButton variant="secondary">View Docs</GlassButton>
161
+ <GlassButton variant="ghost">Learn More</GlassButton>
162
+ </div>`,
163
+ },
164
+ {
165
+ slug: 'glow-button',
166
+ name: 'Glow Button',
167
+ description: 'A button with an animated glow effect that pulsates on hover.',
168
+ category: 'buttons',
169
+ tags: ['button', 'glow', 'animated', 'neon'],
170
+ code: `---
171
+ interface Props {
172
+ color?: string;
173
+ href?: string;
174
+ type?: 'button' | 'submit' | 'reset';
175
+ size?: 'sm' | 'md' | 'lg';
176
+ disabled?: boolean;
177
+ }
178
+
179
+ const { color = '#6366f1', href, type = 'button', size = 'md', disabled = false } = Astro.props;
180
+ const Tag = href ? 'a' : 'button';
181
+ ---
182
+ <Tag
183
+ href={href}
184
+ type={href ? undefined : type}
185
+ disabled={disabled}
186
+ class={\`glow-btn glow-btn--\${size}\`}
187
+ style={\`--btn-color: \${color}\`}
188
+ >
189
+ <span class="glow-btn__content"><slot /></span>
190
+ </Tag>
191
+
192
+ <style>
193
+ .glow-btn {
194
+ --glow-color: var(--btn-color);
195
+ --glow-opacity: 0.4;
196
+
197
+ display: inline-flex;
198
+ align-items: center;
199
+ justify-content: center;
200
+ font-family: var(--font-sans, inherit);
201
+ font-weight: 700;
202
+ letter-spacing: 0.05em;
203
+ text-transform: uppercase;
204
+ color: #fff;
205
+ background: var(--btn-color);
206
+ border: none;
207
+ border-radius: 10px;
208
+ cursor: pointer;
209
+ text-decoration: none;
210
+ position: relative;
211
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
212
+ white-space: nowrap;
213
+ user-select: none;
214
+ z-index: 1;
215
+
216
+ /* Layered Glow */
217
+ box-shadow:
218
+ 0 0 10px color-mix(in srgb, var(--glow-color), transparent 60%),
219
+ 0 0 20px color-mix(in srgb, var(--glow-color), transparent 80%);
220
+ }
221
+
222
+ .glow-btn__content {
223
+ position: relative;
224
+ z-index: 2;
225
+ }
226
+
227
+ .glow-btn::before {
228
+ content: '';
229
+ position: absolute;
230
+ inset: -2px;
231
+ background: var(--btn-color);
232
+ border-radius: inherit;
233
+ z-index: -1;
234
+ opacity: 0;
235
+ filter: blur(8px);
236
+ transition: opacity 0.3s ease;
237
+ }
238
+
239
+ .glow-btn::after {
240
+ content: '';
241
+ position: absolute;
242
+ inset: -8px;
243
+ background: var(--btn-color);
244
+ border-radius: 14px;
245
+ opacity: 0;
246
+ filter: blur(25px);
247
+ transition: opacity 0.4s ease;
248
+ z-index: -2;
249
+ }
250
+
251
+ .glow-btn:hover:not(:disabled) {
252
+ transform: translateY(-3px) scale(1.03);
253
+ box-shadow:
254
+ 0 0 20px color-mix(in srgb, var(--glow-color), transparent 40%),
255
+ 0 0 40px color-mix(in srgb, var(--glow-color), transparent 70%);
256
+ }
257
+
258
+ .glow-btn:hover:not(:disabled)::before {
259
+ opacity: 0.4;
260
+ }
261
+
262
+ .glow-btn:hover:not(:disabled)::after {
263
+ opacity: 0.7;
264
+ animation: animate-glow 2.5s ease-in-out infinite;
265
+ }
266
+
267
+ .glow-btn:active:not(:disabled) {
268
+ transform: translateY(1px) scale(0.98);
269
+ }
270
+
271
+ .glow-btn:disabled {
272
+ opacity: 0.5;
273
+ cursor: not-allowed;
274
+ filter: grayscale(1);
275
+ box-shadow: none;
276
+ }
277
+
278
+ @keyframes animate-glow {
279
+ 0%, 100% { opacity: 0.5; filter: blur(20px); transform: scale(1); }
280
+ 50% { opacity: 0.8; filter: blur(30px); transform: scale(1.1); }
281
+ }
282
+
283
+ /* Sizes */
284
+ .glow-btn--sm { padding: 0.5rem 1.4rem; font-size: 0.75rem; }
285
+ .glow-btn--md { padding: 0.8rem 2.2rem; font-size: 0.875rem; }
286
+ .glow-btn--lg { padding: 1.1rem 3rem; font-size: 1rem; }
287
+ </style>
288
+
289
+ `,
290
+ usage: `---
291
+ import { GlowButton } from 'astro-component-kit';
292
+ ---
293
+
294
+ <GlowButton
295
+ color="#6366f1"
296
+ size="lg"
297
+ >
298
+ Activate System
299
+ </GlowButton>`,
300
+ },
301
+ {
302
+ slug: 'neumorphic-button',
303
+ name: 'Neumorphic Button',
304
+ description: 'A premium Soft UI button with beveled surface gradients and hue-tinted shadows.',
305
+ category: 'buttons',
306
+ tags: ['button', 'neumorphism', 'soft ui'],
307
+ code: `---
308
+ interface Props {
309
+ href?: string;
310
+ type?: 'button' | 'submit' | 'reset';
311
+ baseColor?: string;
312
+ variant?: 'convex' | 'concave';
313
+ size?: 'sm' | 'md' | 'lg';
314
+ disabled?: boolean;
315
+ }
316
+
317
+ const {
318
+ href,
319
+ type = 'button',
320
+ baseColor = 'var(--c-bg-2, #111827)',
321
+ variant = 'convex',
322
+ size = 'md',
323
+ disabled = false
324
+ } = Astro.props;
325
+
326
+ const Tag = href ? 'a' : 'button';
327
+ ---
328
+
329
+ <Tag
330
+ href={href}
331
+ type={href ? undefined : type}
332
+ disabled={disabled}
333
+ class={\`neu-btn neu-btn--\${size} neu-btn--\${variant}\`}
334
+ style={\`--base-color: \${baseColor}\`}
335
+ >
336
+ <span class="neu-btn__surface">
337
+ <slot />
338
+ </span>
339
+ </Tag>
340
+
341
+ <style>
342
+ .neu-btn {
343
+ /* Design Tokens */
344
+ --distance: 8px;
345
+ --blur: 16px;
346
+ --shadow-opacity: 0.4;
347
+ --highlight-opacity: 0.05;
348
+
349
+ /* Derived Colors */
350
+ --shadow-dark: rgba(0, 0, 0, var(--shadow-opacity));
351
+ --shadow-light: rgba(255, 255, 255, var(--highlight-opacity));
352
+ --surface-light: color-mix(in srgb, var(--base-color), white 4%);
353
+ --surface-dark: color-mix(in srgb, var(--base-color), black 4%);
354
+
355
+ position: relative;
356
+ display: inline-flex;
357
+ align-items: center;
358
+ justify-content: center;
359
+ padding: 0;
360
+ border: none;
361
+ background: transparent;
362
+ cursor: pointer;
363
+ text-decoration: none;
364
+ transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
365
+ font-family: var(--font-sans, inherit);
366
+ font-weight: 700;
367
+ letter-spacing: 0.3px;
368
+ color: var(--c-text-1, #f1f5f9);
369
+ border-radius: var(--r-xl, 20px);
370
+ user-select: none;
371
+ white-space: nowrap;
372
+ outline: none;
373
+ }
374
+
375
+ .neu-btn__surface {
376
+ position: relative;
377
+ display: flex;
378
+ align-items: center;
379
+ justify-content: center;
380
+ width: 100%;
381
+ height: 100%;
382
+ padding: inherit;
383
+ border-radius: inherit;
384
+ background: linear-gradient(145deg, var(--surface-light), var(--surface-dark));
385
+ z-index: 2;
386
+ transition: background 0.4s ease, box-shadow 0.4s ease;
387
+ }
388
+
389
+ /* Rim Light highlight */
390
+ .neu-btn__surface::before {
391
+ content: '';
392
+ position: absolute;
393
+ inset: 0.5px;
394
+ border-radius: inherit;
395
+ padding: 1px;
396
+ background: linear-gradient(135deg, rgba(255,255,255,0.08), transparent 50%, rgba(0,0,0,0.1));
397
+ -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
398
+ mask-composite: exclude;
399
+ pointer-events: none;
400
+ }
401
+
402
+ /* Convex Variant (Default) */
403
+ .neu-btn--convex {
404
+ box-shadow:
405
+ var(--distance) var(--distance) var(--blur) var(--shadow-dark),
406
+ calc(var(--distance) * -0.6) calc(var(--distance) * -0.6) var(--blur) var(--shadow-light);
407
+ }
408
+
409
+ /* Concave Variant */
410
+ .neu-btn--concave .neu-btn__surface {
411
+ background: var(--base-color);
412
+ box-shadow:
413
+ inset var(--distance) var(--distance) var(--blur) var(--shadow-dark),
414
+ inset calc(var(--distance) * -0.5) calc(var(--distance) * -0.5) var(--blur) var(--shadow-light);
415
+ }
416
+
417
+ /* Hover States */
418
+ .neu-btn:hover:not(:disabled) {
419
+ transform: translateY(-2px);
420
+ filter: brightness(1.08);
421
+ }
422
+
423
+ .neu-btn--convex:hover:not(:disabled) {
424
+ box-shadow:
425
+ calc(var(--distance) * 1.4) calc(var(--distance) * 1.4) calc(var(--blur) * 1.4) var(--shadow-dark),
426
+ calc(var(--distance) * -0.8) calc(var(--distance) * -0.8) calc(var(--blur) * 1.4) var(--shadow-light);
427
+ }
428
+
429
+ /* Active/Pressed States */
430
+ .neu-btn:active:not(:disabled) {
431
+ transform: translateY(0) scale(0.97);
432
+ }
433
+
434
+ .neu-btn--convex:active:not(:disabled) .neu-btn__surface {
435
+ background: var(--base-color);
436
+ box-shadow:
437
+ inset calc(var(--distance) * 0.5) calc(var(--distance) * 0.5) calc(var(--blur) * 0.5) var(--shadow-dark),
438
+ inset calc(var(--distance) * -0.3) calc(var(--distance) * -0.3) calc(var(--blur) * 0.5) var(--shadow-light);
439
+ }
440
+
441
+ .neu-btn:disabled {
442
+ opacity: 0.4;
443
+ cursor: not-allowed;
444
+ filter: grayscale(1);
445
+ box-shadow: none;
446
+ }
447
+
448
+ /* Sizes */
449
+ .neu-btn--sm { padding: 0.5rem 1.4rem; font-size: 0.85rem; --distance: 4px; --blur: 8px; }
450
+ .neu-btn--md { padding: 0.85rem 2.4rem; font-size: 1rem; }
451
+ .neu-btn--lg { padding: 1.2rem 3.5rem; font-size: 1.2rem; --distance: 12px; --blur: 24px; }
452
+
453
+ /* Color scheme adjustments */
454
+ @media (prefers-color-scheme: light) {
455
+ .neu-btn {
456
+ --shadow-opacity: 0.15;
457
+ --highlight-opacity: 0.8;
458
+ --shadow-dark: rgba(163, 177, 198, var(--shadow-opacity));
459
+ --shadow-light: rgba(255, 255, 255, var(--highlight-opacity));
460
+ color: #374151;
461
+ }
462
+ }
463
+ </style>
464
+
465
+ `,
466
+ usage: `---
467
+ import { NeumorphicButton } from 'astro-component-kit';
468
+ ---
469
+
470
+ <NeumorphicButton
471
+ baseColor="#6366f1"
472
+ variant="convex"
473
+ size="lg"
474
+ >
475
+ Confirm Action
476
+ </NeumorphicButton>`,
477
+ },
478
+ {
479
+ slug: 'cyberpunk-button',
480
+ name: 'Cyberpunk Button',
481
+ description: 'A glitchy, neon-styled button inspired by futuristic sci-fi aesthetics.',
482
+ category: 'buttons',
483
+ tags: ['button', 'cyberpunk', 'glitch', 'neon'],
484
+ code: `---
485
+ interface Props {
486
+ href?: string;
487
+ type?: 'button' | 'submit' | 'reset';
488
+ primaryColor?: string;
489
+ secondaryColor?: string;
490
+ size?: 'sm' | 'md' | 'lg';
491
+ label?: string; // Used for glitch effect text
492
+ }
493
+
494
+ const {
495
+ href,
496
+ type = 'button',
497
+ primaryColor = '#ff003c',
498
+ secondaryColor = '#00e0ff',
499
+ size = 'md',
500
+ label = "READY"
501
+ } = Astro.props;
502
+
503
+ const Tag = href ? 'a' : 'button';
504
+ ---
505
+
506
+ <Tag
507
+ href={href}
508
+ type={href ? undefined : type}
509
+ class={\`cyber-btn cyber-btn--\${size}\`}
510
+ style={\`--primary-color: \${primaryColor}; --secondary-color: \${secondaryColor};\`}
511
+ data-text={label}
512
+ >
513
+ <span class="cyber-btn__inner">
514
+ <slot>{label}</slot>
515
+ </span>
516
+ <span class="cyber-btn__glitch"></span>
517
+ <span class="cyber-btn__tag">R25</span>
518
+ </Tag>
519
+
520
+ <style>
521
+ .cyber-btn {
522
+ --glitch-width: 4px;
523
+ position: relative;
524
+ display: inline-flex;
525
+ align-items: center;
526
+ justify-content: center;
527
+ background: var(--primary-color);
528
+ color: #fff;
529
+ border: none;
530
+ font-family: var(--font-sans, 'Inter', sans-serif);
531
+ font-weight: 800;
532
+ text-transform: uppercase;
533
+ font-style: italic;
534
+ cursor: pointer;
535
+ text-decoration: none;
536
+ transition: all 0.2s;
537
+ clip-path: polygon(-10% 0, 100% 0, 100% 65%, 90% 103%, 0 103%, -10% 70%);
538
+ user-select: none;
539
+ white-space: nowrap;
540
+ }
541
+
542
+ .cyber-btn__inner {
543
+ position: relative;
544
+ z-index: 2;
545
+ padding: 0.8rem 2.2rem;
546
+ display: block;
547
+ }
548
+
549
+ /* Glitch Layers */
550
+ .cyber-btn::before,
551
+ .cyber-btn::after,
552
+ .cyber-btn__glitch {
553
+ content: attr(data-text);
554
+ position: absolute;
555
+ top: 0;
556
+ left: 0;
557
+ width: 100%;
558
+ height: 100%;
559
+ display: flex;
560
+ align-items: center;
561
+ justify-content: center;
562
+ opacity: 0;
563
+ z-index: 1;
564
+ }
565
+
566
+ .cyber-btn::before {
567
+ background: var(--secondary-color);
568
+ clip-path: polygon(0 0, 100% 0, 100% 5%, 0 5%);
569
+ transform: translate(-4px, -2px);
570
+ }
571
+
572
+ .cyber-btn::after {
573
+ background: #f3f3f3;
574
+ clip-path: polygon(0 95%, 100% 95%, 100% 100%, 0 100%);
575
+ transform: translate(4px, 2px);
576
+ color: #000;
577
+ }
578
+
579
+ .cyber-btn__tag {
580
+ position: absolute;
581
+ padding: 1px 4px;
582
+ letter-spacing: 1px;
583
+ line-height: 1;
584
+ bottom: -2px;
585
+ right: 5%;
586
+ font-weight: 400;
587
+ font-size: 0.6rem;
588
+ background: #000;
589
+ color: var(--secondary-color);
590
+ z-index: 3;
591
+ font-style: normal;
592
+ }
593
+
594
+ /* Hover Animations */
595
+ .cyber-btn:hover {
596
+ transform: scale(1.03);
597
+ background: var(--primary-color);
598
+ box-shadow: 10px 0px 0px var(--secondary-color);
599
+ }
600
+
601
+ .cyber-btn:hover::before,
602
+ .cyber-btn:hover::after,
603
+ .cyber-btn:hover .cyber-btn__glitch {
604
+ opacity: 1;
605
+ animation: glitch 0.3s infinite;
606
+ }
607
+
608
+ .cyber-btn:hover::before {
609
+ animation-direction: alternate-reverse;
610
+ }
611
+
612
+ .cyber-btn:active {
613
+ transform: scale(0.98);
614
+ filter: brightness(1.2);
615
+ }
616
+
617
+ @keyframes glitch {
618
+ 0% { clip-path: polygon(0 2%, 100% 2%, 100% 5%, 0 5%); transform: translate(0); }
619
+ 20% { clip-path: polygon(0 15%, 100% 15%, 100% 15%, 0 15%); transform: translate(-5px); }
620
+ 40% { clip-path: polygon(0 10%, 100% 10%, 100% 20%, 0 20%); transform: translate(5px); }
621
+ 60% { clip-path: polygon(0 80%, 100% 80%, 100% 90%, 0 90%); transform: translate(-5px); }
622
+ 80% { clip-path: polygon(0 50%, 100% 50%, 100% 55%, 0 55%); transform: translate(5px); }
623
+ 100% { clip-path: polygon(0 70%, 100% 70%, 100% 80%, 0 80%); transform: translate(0); }
624
+ }
625
+
626
+ /* Sizes */
627
+ .cyber-btn--sm .cyber-btn__inner { padding: 0.5rem 1.4rem; font-size: 0.85rem; }
628
+ .cyber-btn--md .cyber-btn__inner { padding: 0.8rem 2.2rem; font-size: 1.05rem; }
629
+ .cyber-btn--lg .cyber-btn__inner { padding: 1.1rem 3rem; font-size: 1.25rem; }
630
+ </style>
631
+
632
+ `,
633
+ usage: `---
634
+ import { CyberpunkButton } from 'astro-component-kit';
635
+ ---
636
+
637
+ <CyberpunkButton
638
+ label="READY"
639
+ primaryColor="#ff003c"
640
+ secondaryColor="#00e0ff"
641
+ size="lg"
642
+ >
643
+ LAUNCH SYSTEM
644
+ </CyberpunkButton>`,
645
+ },
646
+ {
647
+ slug: 'pulse-button',
648
+ name: 'Pulse Button',
649
+ description: 'A button with a persistent circular pulse effect to draw attention.',
650
+ category: 'buttons',
651
+ tags: ['button', 'animated', 'attention'],
652
+ code: `---
653
+ interface Props {
654
+ href?: string;
655
+ type?: 'button' | 'submit' | 'reset';
656
+ variant?: 'primary' | 'secondary' | 'ghost';
657
+ size?: 'sm' | 'md' | 'lg';
658
+ color?: string;
659
+ disabled?: boolean;
660
+ }
661
+
662
+ const {
663
+ href,
664
+ type = 'button',
665
+ variant = 'primary',
666
+ size = 'md',
667
+ color = '#8b5cf6',
668
+ disabled = false
669
+ } = Astro.props;
670
+
671
+ const Tag = href ? 'a' : 'button';
672
+ ---
673
+
674
+ <Tag
675
+ href={href}
676
+ type={href ? undefined : type}
677
+ disabled={disabled}
678
+ class={\`pulse-btn pulse-btn--\${variant} pulse-btn--\${size}\`}
679
+ style={\`--pulse-color: \${color}\`}
680
+ >
681
+ <slot />
682
+ </Tag>
683
+
684
+ <style>
685
+ .pulse-btn {
686
+ position: relative;
687
+ display: inline-flex;
688
+ align-items: center;
689
+ justify-content: center;
690
+ padding: 0.7rem 1.6rem;
691
+ background: var(--pulse-color);
692
+ color: white;
693
+ border: none;
694
+ border-radius: var(--r-full, 9999px);
695
+ cursor: pointer;
696
+ text-decoration: none;
697
+ font-weight: 600;
698
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
699
+ user-select: none;
700
+ white-space: nowrap;
701
+ font-family: var(--font-sans, inherit);
702
+ isolation: isolate;
703
+ box-shadow: 0 10px 26px -18px color-mix(in srgb, var(--pulse-color), black 10%);
704
+ }
705
+
706
+ .pulse-btn::after {
707
+ content: '';
708
+ position: absolute;
709
+ inset: 0;
710
+ border-radius: inherit;
711
+ box-shadow: 0 0 0 0 var(--pulse-color);
712
+ animation: pulse-ring 2s infinite;
713
+ z-index: -1;
714
+ pointer-events: none;
715
+ }
716
+
717
+ @keyframes pulse-ring {
718
+ 0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--pulse-color), transparent 30%); }
719
+ 70% { box-shadow: 0 0 0 15px color-mix(in srgb, var(--pulse-color), transparent 100%); }
720
+ 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--pulse-color), transparent 100%); }
721
+ }
722
+
723
+ .pulse-btn:hover:not(:disabled) {
724
+ transform: scale(1.05);
725
+ filter: brightness(110%);
726
+ }
727
+
728
+ .pulse-btn:active:not(:disabled) {
729
+ transform: scale(0.98);
730
+ }
731
+
732
+ .pulse-btn:disabled {
733
+ opacity: 0.5;
734
+ cursor: not-allowed;
735
+ animation: none;
736
+ filter: grayscale(1);
737
+ }
738
+
739
+ .pulse-btn:disabled::after {
740
+ animation: none;
741
+ box-shadow: none;
742
+ }
743
+
744
+ /* Variants */
745
+ .pulse-btn--secondary {
746
+ background: #0f172a;
747
+ border: 1px solid rgba(255, 255, 255, 0.1);
748
+ box-shadow: 0 10px 26px -20px rgba(15, 23, 42, 0.95);
749
+ }
750
+
751
+ .pulse-btn--ghost {
752
+ background: transparent;
753
+ border: 1px solid var(--pulse-color);
754
+ color: var(--pulse-color);
755
+ box-shadow: none;
756
+ }
757
+
758
+ /* Sizes */
759
+ .pulse-btn--sm { padding: 0.4rem 1.1rem; font-size: 0.8rem; }
760
+ .pulse-btn--md { padding: 0.7rem 1.6rem; font-size: 0.95rem; }
761
+ .pulse-btn--lg { padding: 1rem 2.2rem; font-size: 1.1rem; }
762
+ </style>
763
+ `,
764
+ usage: `---
765
+ import { PulseButton } from 'astro-component-kit';
766
+ ---
767
+
768
+ <PulseButton
769
+ color="#6366f1"
770
+ variant="primary"
771
+ size="lg"
772
+ >
773
+ Start Process
774
+ </PulseButton>`,
775
+ },
776
+ {
777
+ slug: 'shiny-button',
778
+ name: 'Shiny Button',
779
+ description: 'A button with a metallic shine effect that sweeps across on hover.',
780
+ category: 'buttons',
781
+ tags: ['button', 'shine', 'animated'],
782
+ code: `---
783
+ interface Props {
784
+ href?: string;
785
+ type?: 'button' | 'submit' | 'reset';
786
+ variant?: 'primary' | 'secondary' | 'ghost';
787
+ size?: 'sm' | 'md' | 'lg';
788
+ disabled?: boolean;
789
+ }
790
+
791
+ const {
792
+ href,
793
+ type = 'button',
794
+ variant = 'primary',
795
+ size = 'md',
796
+ disabled = false
797
+ } = Astro.props;
798
+
799
+ const Tag = href ? 'a' : 'button';
800
+ ---
801
+
802
+ <Tag
803
+ href={href}
804
+ type={href ? undefined : type}
805
+ disabled={disabled}
806
+ class={\`shiny-btn shiny-btn--\${variant} shiny-btn--\${size}\`}
807
+ >
808
+ <span class="shiny-btn__content"><slot /></span>
809
+ </Tag>
810
+
811
+ <style>
812
+ .shiny-btn {
813
+ position: relative;
814
+ display: inline-flex;
815
+ align-items: center;
816
+ justify-content: center;
817
+ background: #111;
818
+ color: #fff;
819
+ padding: 0;
820
+ border: none;
821
+ border-radius: var(--r-md, 12px);
822
+ cursor: pointer;
823
+ text-decoration: none;
824
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
825
+ font-weight: 600;
826
+ user-select: none;
827
+ white-space: nowrap;
828
+ overflow: hidden;
829
+ font-family: var(--font-sans, inherit);
830
+ }
831
+
832
+ .shiny-btn__content {
833
+ display: block;
834
+ padding: 0.7rem 1.8rem;
835
+ z-index: 2;
836
+ }
837
+
838
+ .shiny-btn::after {
839
+ content: '';
840
+ position: absolute;
841
+ top: -50%;
842
+ left: -150%;
843
+ width: 200%;
844
+ height: 200%;
845
+ background: linear-gradient(
846
+ 45deg,
847
+ transparent 0%,
848
+ rgba(255, 255, 255, 0) 30%,
849
+ rgba(255, 255, 255, 0.2) 50%,
850
+ rgba(255, 255, 255, 0) 70%,
851
+ transparent 100%
852
+ );
853
+ transform: rotate(45deg);
854
+ transition: left 0.6s ease;
855
+ z-index: 1;
856
+ }
857
+
858
+ .shiny-btn:hover:not(:disabled)::after {
859
+ left: 100%;
860
+ }
861
+
862
+ .shiny-btn:hover:not(:disabled) {
863
+ box-shadow: 0 10px 30px -10px rgba(255, 255, 255, 0.1);
864
+ transform: translateY(-2px);
865
+ }
866
+
867
+ .shiny-btn:active:not(:disabled) {
868
+ transform: scale(0.98);
869
+ }
870
+
871
+ .shiny-btn:disabled {
872
+ opacity: 0.5;
873
+ cursor: not-allowed;
874
+ filter: grayscale(1);
875
+ }
876
+
877
+ /* Variants */
878
+ .shiny-btn--primary {
879
+ background: linear-gradient(135deg, #1e293b, #0f172a);
880
+ border: 1px solid rgba(255, 255, 255, 0.1);
881
+ }
882
+
883
+ .shiny-btn--secondary {
884
+ background: var(--c-primary, #6366f1);
885
+ }
886
+
887
+ .shiny-btn--ghost {
888
+ background: transparent;
889
+ border: 1px solid rgba(255, 255, 255, 0.2);
890
+ color: var(--c-text-2, #94a3b8);
891
+ }
892
+ .shiny-btn--ghost:hover:not(:disabled) {
893
+ color: #fff;
894
+ border-color: #fff;
895
+ }
896
+
897
+ /* Sizes */
898
+ .shiny-btn--sm .shiny-btn__content { padding: 0.45rem 1.2rem; font-size: 0.85rem; }
899
+ .shiny-btn--md .shiny-btn__content { padding: 0.75rem 1.8rem; font-size: 1rem; }
900
+ .shiny-btn--lg .shiny-btn__content { padding: 1.1rem 2.6rem; font-size: 1.15rem; }
901
+ </style>
902
+
903
+ `,
904
+ usage: `---
905
+ import { ShinyButton } from 'astro-component-kit';
906
+ ---
907
+
908
+ <ShinyButton
909
+ href="#"
910
+ variant="primary"
911
+ size="lg"
912
+ >
913
+ Claim Special Offer
914
+ </ShinyButton>`,
915
+ },
916
+ {
917
+ slug: 'retro-arcade-button',
918
+ name: 'Retro Arcade Button',
919
+ description: 'Classic 8-bit style button with sharp pixelated edges and vibrant colors.',
920
+ category: 'buttons',
921
+ tags: ['button', 'retro', '8-bit', 'game'],
922
+ code: `---
923
+ interface Props {
924
+ href?: string;
925
+ type?: 'button' | 'submit' | 'reset';
926
+ variant?: 'yellow' | 'red' | 'blue' | 'green';
927
+ size?: 'sm' | 'md' | 'lg';
928
+ disabled?: boolean;
929
+ }
930
+
931
+ const {
932
+ href,
933
+ type = 'button',
934
+ variant = 'yellow',
935
+ size = 'md',
936
+ disabled = false
937
+ } = Astro.props;
938
+
939
+ const Tag = href ? 'a' : 'button';
940
+ ---
941
+
942
+ <Tag
943
+ href={href}
944
+ type={href ? undefined : type}
945
+ disabled={disabled}
946
+ class={\`retro-arcade-btn retro-arcade-btn--\${variant} retro-arcade-btn--\${size}\`}
947
+ >
948
+ <span class="retro-arcade-btn__inner">
949
+ <slot />
950
+ </span>
951
+ </Tag>
952
+
953
+ <style>
954
+ .retro-arcade-btn {
955
+ --pixel-size: 4px;
956
+ --main-color: #facc15;
957
+ --shadow-color: #a16207;
958
+
959
+ position: relative;
960
+ display: inline-flex;
961
+ padding: 0;
962
+ background: #000;
963
+ border: none;
964
+ cursor: pointer;
965
+ text-decoration: none;
966
+ box-shadow:
967
+ var(--pixel-size) 0 #000,
968
+ calc(var(--pixel-size) * -1) 0 #000,
969
+ 0 var(--pixel-size) #000,
970
+ 0 calc(var(--pixel-size) * -1) #000;
971
+ transition: transform 0.1s;
972
+ user-select: none;
973
+ margin: var(--pixel-size);
974
+ white-space: nowrap;
975
+ }
976
+
977
+ .retro-arcade-btn__inner {
978
+ display: block;
979
+ padding: 0.6rem 1.4rem;
980
+ background: var(--main-color);
981
+ color: #000;
982
+ font-family: 'Courier New', monospace;
983
+ font-weight: 900;
984
+ text-transform: uppercase;
985
+ box-shadow:
986
+ inset calc(var(--pixel-size) * -1) calc(var(--pixel-size) * -1) var(--shadow-color);
987
+ transition: all 0.1s;
988
+ }
989
+
990
+ /* Interaction */
991
+ .retro-arcade-btn:hover:not(:disabled) .retro-arcade-btn__inner {
992
+ filter: brightness(110%);
993
+ }
994
+
995
+ .retro-arcade-btn:active:not(:disabled) {
996
+ transform: translate(calc(var(--pixel-size) / 2), calc(var(--pixel-size) / 2));
997
+ }
998
+
999
+ .retro-arcade-btn:active:not(:disabled) .retro-arcade-btn__inner {
1000
+ box-shadow:
1001
+ inset var(--pixel-size) var(--pixel-size) var(--shadow-color);
1002
+ }
1003
+
1004
+ .retro-arcade-btn:disabled {
1005
+ opacity: 0.6;
1006
+ cursor: not-allowed;
1007
+ filter: grayscale(1);
1008
+ }
1009
+
1010
+ /* Variants */
1011
+ .retro-arcade-btn--yellow { --main-color: #facc15; --shadow-color: #a16207; }
1012
+ .retro-arcade-btn--red { --main-color: #ef4444; --shadow-color: #991b1b; }
1013
+ .retro-arcade-btn--blue { --main-color: #3b82f6; --shadow-color: #1e3a8a; }
1014
+ .retro-arcade-btn--green { --main-color: #22c55e; --shadow-color: #14532d; }
1015
+
1016
+ /* Sizes */
1017
+ .retro-arcade-btn--sm { --pixel-size: 2px; }
1018
+ .retro-arcade-btn--sm .retro-arcade-btn__inner { padding: 0.4rem 0.8rem; font-size: 0.75rem; }
1019
+
1020
+ .retro-arcade-btn--md { --pixel-size: 4px; }
1021
+ .retro-arcade-btn--md .retro-arcade-btn__inner { padding: 0.7rem 1.4rem; font-size: 0.9rem; }
1022
+
1023
+ .retro-arcade-btn--lg { --pixel-size: 6px; }
1024
+ .retro-arcade-btn--lg .retro-arcade-btn__inner { padding: 1rem 2rem; font-size: 1.1rem; }
1025
+ </style>
1026
+
1027
+ `,
1028
+ usage: `<RetroArcadeButton href="#" type="button" variant="primary" size="md" disabled={false}>Press Start</RetroArcadeButton>`,
1029
+ },
1030
+ {
1031
+ slug: '3d-push-button',
1032
+ name: '3D Push Button',
1033
+ description: 'A button that physically sinks into the interface when clicked.',
1034
+ category: 'buttons',
1035
+ tags: ['button', '3d', 'interactive'],
1036
+ code: `---
1037
+ interface Props {
1038
+ href?: string;
1039
+ type?: 'button' | 'submit' | 'reset';
1040
+ color?: string;
1041
+ size?: 'sm' | 'md' | 'lg';
1042
+ disabled?: boolean;
1043
+ }
1044
+
1045
+ const {
1046
+ href,
1047
+ type = 'button',
1048
+ color = '#6366f1',
1049
+ size = 'md',
1050
+ disabled = false
1051
+ } = Astro.props;
1052
+
1053
+ const Tag = href ? 'a' : 'button';
1054
+ ---
1055
+
1056
+ <Tag
1057
+ href={href}
1058
+ type={href ? undefined : type}
1059
+ disabled={disabled}
1060
+ class={\`push-btn push-btn--\${size}\`}
1061
+ style={\`--btn-color: \${color}\`}
1062
+ >
1063
+ <span class="push-btn__shadow"></span>
1064
+ <span class="push-btn__edge"></span>
1065
+ <span class="push-btn__front">
1066
+ <slot />
1067
+ </span>
1068
+ </Tag>
1069
+
1070
+ <style>
1071
+ .push-btn {
1072
+ --depth: 8px;
1073
+ position: relative;
1074
+ display: inline-flex;
1075
+ padding: 0;
1076
+ padding-top: var(--depth); /* Space for the elevated front */
1077
+ border: none;
1078
+ background: transparent;
1079
+ cursor: pointer;
1080
+ outline-offset: 4px;
1081
+ transition: filter 250ms;
1082
+ user-select: none;
1083
+ text-decoration: none;
1084
+ vertical-align: middle;
1085
+ }
1086
+
1087
+ .push-btn__shadow {
1088
+ position: absolute;
1089
+ top: var(--depth); /* Align with base */
1090
+ left: 0;
1091
+ width: 100%;
1092
+ height: calc(100% - var(--depth));
1093
+ border-radius: var(--r-md, 12px);
1094
+ background: rgba(0, 0, 0, 0.4);
1095
+ transform: translateY(2px);
1096
+ filter: blur(4px);
1097
+ transition: transform 600ms cubic-bezier(.3, .7, .4, 1);
1098
+ }
1099
+
1100
+ .push-btn__edge {
1101
+ position: absolute;
1102
+ top: var(--depth); /* Align with base */
1103
+ left: 0;
1104
+ width: 100%;
1105
+ height: calc(100% - var(--depth));
1106
+ border-radius: var(--r-md, 12px);
1107
+ background: linear-gradient(
1108
+ to left,
1109
+ color-mix(in srgb, var(--btn-color), black 30%) 0%,
1110
+ color-mix(in srgb, var(--btn-color), black 15%) 8%,
1111
+ color-mix(in srgb, var(--btn-color), black 15%) 92%,
1112
+ color-mix(in srgb, var(--btn-color), black 30%) 100%
1113
+ );
1114
+ }
1115
+
1116
+ .push-btn__front {
1117
+ display: block;
1118
+ position: relative;
1119
+ padding: 12px 32px;
1120
+ border-radius: var(--r-md, 12px);
1121
+ font-size: 1rem;
1122
+ font-weight: 700;
1123
+ color: white;
1124
+ background: var(--btn-color);
1125
+ transform: translateY(calc(var(--depth) * -1));
1126
+ transition: transform 600ms cubic-bezier(.3, .7, .4, 1);
1127
+ font-family: var(--font-sans, inherit);
1128
+ white-space: nowrap;
1129
+ border: 1px solid rgba(255, 255, 255, 0.1);
1130
+ }
1131
+
1132
+ .push-btn:hover:not(:disabled) {
1133
+ filter: brightness(110%);
1134
+ }
1135
+
1136
+ .push-btn:hover:not(:disabled) .push-btn__front {
1137
+ transform: translateY(calc(var(--depth) * -1.2));
1138
+ transition: transform 250ms cubic-bezier(.3, .7, .4, 1.5);
1139
+ }
1140
+
1141
+ .push-btn:hover:not(:disabled) .push-btn__shadow {
1142
+ transform: translateY(4px);
1143
+ transition: transform 250ms cubic-bezier(.3, .7, .4, 1.5);
1144
+ }
1145
+
1146
+ .push-btn:active:not(:disabled) .push-btn__front {
1147
+ transform: translateY(-2px);
1148
+ transition: transform 34ms;
1149
+ }
1150
+
1151
+ .push-btn:active:not(:disabled) .push-btn__shadow {
1152
+ transform: translateY(1px);
1153
+ transition: transform 34ms;
1154
+ }
1155
+
1156
+ .push-btn:disabled {
1157
+ cursor: not-allowed;
1158
+ filter: grayscale(1) opacity(0.5);
1159
+ box-shadow: none;
1160
+ }
1161
+
1162
+ /* Sizes */
1163
+ .push-btn--sm { --depth: 5px; }
1164
+ .push-btn--sm .push-btn__front { padding: 8px 18px; font-size: 0.85rem; }
1165
+
1166
+ .push-btn--md { --depth: 8px; }
1167
+ .push-btn--md .push-btn__front { padding: 12px 32px; font-size: 1rem; }
1168
+
1169
+ .push-btn--lg { --depth: 12px; }
1170
+ .push-btn--lg .push-btn__front { padding: 16px 42px; font-size: 1.15rem; }
1171
+ </style>
1172
+ `,
1173
+ usage: `<3dPushButton href="#" type="button" color="#6366f1" size="md" disabled={false}>Push Me</3dPushButton>`,
1174
+ },
1175
+ {
1176
+ slug: 'loading-button',
1177
+ name: 'Loading Spinner Button',
1178
+ description: 'A button that shows a built-in spinner when in a loading state.',
1179
+ category: 'buttons',
1180
+ tags: ['button', 'loading', 'state'],
1181
+ code: `---
1182
+ interface Props {
1183
+ isLoading?: boolean;
1184
+ href?: string;
1185
+ type?: 'button' | 'submit' | 'reset';
1186
+ variant?: 'primary' | 'secondary' | 'ghost';
1187
+ size?: 'sm' | 'md' | 'lg';
1188
+ disabled?: boolean;
1189
+ }
1190
+
1191
+ const {
1192
+ isLoading = false,
1193
+ href,
1194
+ type = 'button',
1195
+ variant = 'primary',
1196
+ size = 'md',
1197
+ disabled = false
1198
+ } = Astro.props;
1199
+
1200
+ const Tag = href ? 'a' : 'button';
1201
+ ---
1202
+
1203
+ <Tag
1204
+ href={href}
1205
+ type={href ? undefined : type}
1206
+ disabled={disabled || isLoading}
1207
+ class:list={[
1208
+ "load-btn",
1209
+ \`load-btn--\${variant}\`,
1210
+ \`load-btn--\${size}\`,
1211
+ { "is-loading": isLoading }
1212
+ ]}
1213
+ >
1214
+ <span class="load-btn__content">
1215
+ <slot />
1216
+ </span>
1217
+ <span class="load-btn__spinner">
1218
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
1219
+ <path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/>
1220
+ </svg>
1221
+ </span>
1222
+ </Tag>
1223
+
1224
+ <style>
1225
+ .load-btn {
1226
+ position: relative;
1227
+ display: inline-flex;
1228
+ align-items: center;
1229
+ justify-content: center;
1230
+ font-family: var(--font-sans, inherit);
1231
+ font-weight: 600;
1232
+ letter-spacing: 0.02em;
1233
+ border-radius: var(--r-md, 12px);
1234
+ cursor: pointer;
1235
+ text-decoration: none;
1236
+ border: 1px solid transparent;
1237
+ transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
1238
+ overflow: hidden;
1239
+ white-space: nowrap;
1240
+ user-select: none;
1241
+ }
1242
+
1243
+ .load-btn__content {
1244
+ display: flex;
1245
+ align-items: center;
1246
+ gap: 0.5rem;
1247
+ transition: opacity 0.2s ease, transform 0.2s ease;
1248
+ }
1249
+
1250
+ .load-btn__spinner {
1251
+ position: absolute;
1252
+ inset: 0;
1253
+ display: flex;
1254
+ align-items: center;
1255
+ justify-content: center;
1256
+ opacity: 0;
1257
+ transform: scale(0.5);
1258
+ transition: all 0.2s ease;
1259
+ pointer-events: none;
1260
+ }
1261
+
1262
+ .load-btn__spinner svg {
1263
+ width: 1.25em;
1264
+ height: 1.25em;
1265
+ animation: spin 1s linear infinite;
1266
+ }
1267
+
1268
+ /* Loading State */
1269
+ .load-btn.is-loading {
1270
+ cursor: wait;
1271
+ }
1272
+
1273
+ .load-btn.is-loading .load-btn__content {
1274
+ opacity: 0;
1275
+ transform: translateY(5px);
1276
+ }
1277
+
1278
+ .load-btn.is-loading .load-btn__spinner {
1279
+ opacity: 1;
1280
+ transform: scale(1);
1281
+ }
1282
+
1283
+ @keyframes spin {
1284
+ from { transform: rotate(0deg); }
1285
+ to { transform: rotate(360deg); }
1286
+ }
1287
+
1288
+ /* Variants */
1289
+ .load-btn--primary {
1290
+ background: var(--c-primary, #6366f1);
1291
+ color: #fff;
1292
+ box-shadow: 0 4px 15px rgba(99, 102, 241, 0.2);
1293
+ }
1294
+ .load-btn--primary:hover:not(:disabled) {
1295
+ background: var(--c-primary-light, #818cf8);
1296
+ transform: translateY(-1px);
1297
+ }
1298
+
1299
+ .load-btn--secondary {
1300
+ background: rgba(255, 255, 255, 0.05);
1301
+ color: var(--c-text-1, #f8fafc);
1302
+ border-color: var(--c-border, rgba(255, 255, 255, 0.1));
1303
+ }
1304
+ .load-btn--secondary:hover:not(:disabled) {
1305
+ background: rgba(255, 255, 255, 0.08);
1306
+ }
1307
+
1308
+ .load-btn--ghost {
1309
+ background: transparent;
1310
+ color: var(--c-primary-light, #818cf8);
1311
+ border-color: rgba(99, 102, 241, 0.3);
1312
+ }
1313
+ .load-btn--ghost:hover:not(:disabled) {
1314
+ background: rgba(99, 102, 241, 0.05);
1315
+ }
1316
+
1317
+ /* Sizes / Layout */
1318
+ .load-btn--sm { padding: 0.45rem 1rem; font-size: 0.825rem; min-width: 100px; }
1319
+ .load-btn--md { padding: 0.75rem 1.6rem; font-size: 0.95rem; min-width: 140px; }
1320
+ .load-btn--lg { padding: 1rem 2.2rem; font-size: 1.1rem; min-width: 180px; }
1321
+
1322
+ .load-btn:disabled {
1323
+ opacity: 0.5;
1324
+ cursor: not-allowed;
1325
+ filter: grayscale(1);
1326
+ }
1327
+ </style>
1328
+
1329
+ `,
1330
+ usage: `<LoadingButton isLoading={false} href="#" type="button" variant="primary" size="md">Submit Form</LoadingButton>`,
1331
+ },
1332
+ {
1333
+ slug: 'shimmer-button',
1334
+ name: 'Shimmer Button',
1335
+ description: 'A highly elegant button with a moving shimmer outline.',
1336
+ category: 'buttons',
1337
+ tags: ['button', 'shimmer', 'elegant'],
1338
+ code: `---
1339
+ interface Props {
1340
+ href?: string;
1341
+ type?: 'button' | 'submit' | 'reset';
1342
+ variant?: 'primary' | 'secondary' | 'ghost';
1343
+ size?: 'sm' | 'md' | 'lg';
1344
+ disabled?: boolean;
1345
+ }
1346
+
1347
+ const {
1348
+ href,
1349
+ type = 'button',
1350
+ variant = 'primary',
1351
+ size = 'md',
1352
+ disabled = false
1353
+ } = Astro.props;
1354
+
1355
+ const Tag = href ? 'a' : 'button';
1356
+ ---
1357
+
1358
+ <Tag
1359
+ href={href}
1360
+ type={href ? undefined : type}
1361
+ disabled={disabled}
1362
+ class={\`shimmer-btn shimmer-btn--\${variant} shimmer-btn--\${size}\`}
1363
+ >
1364
+ <span class="shimmer-btn__content"><slot /></span>
1365
+ </Tag>
1366
+
1367
+ <style>
1368
+ .shimmer-btn {
1369
+ --shimmer-speed: 3s;
1370
+ --shimmer-color: rgba(255, 255, 255, 0.3);
1371
+
1372
+ position: relative;
1373
+ display: inline-flex;
1374
+ align-items: center;
1375
+ justify-content: center;
1376
+ padding: 0;
1377
+ border: none;
1378
+ border-radius: var(--r-full, 9999px);
1379
+ cursor: pointer;
1380
+ text-decoration: none;
1381
+ overflow: hidden;
1382
+ transition: all 0.3s ease;
1383
+ user-select: none;
1384
+ font-family: var(--font-sans, inherit);
1385
+ font-weight: 600;
1386
+ white-space: nowrap;
1387
+ }
1388
+
1389
+ .shimmer-btn__content {
1390
+ display: block;
1391
+ padding: 0.75rem 1.8rem;
1392
+ z-index: 2;
1393
+ }
1394
+
1395
+ .shimmer-btn::after {
1396
+ content: "";
1397
+ position: absolute;
1398
+ inset: 0;
1399
+ background: linear-gradient(
1400
+ 120deg,
1401
+ transparent 30%,
1402
+ var(--shimmer-color) 50%,
1403
+ transparent 70%
1404
+ );
1405
+ background-size: 200% 100%;
1406
+ transform: translateX(-100%);
1407
+ animation: sweep var(--shimmer-speed) infinite;
1408
+ z-index: 1;
1409
+ }
1410
+
1411
+ @keyframes sweep {
1412
+ 0% { transform: translateX(-100%); }
1413
+ 40%, 100% { transform: translateX(100%); }
1414
+ }
1415
+
1416
+ .shimmer-btn:hover:not(:disabled) {
1417
+ transform: scale(1.02);
1418
+ }
1419
+
1420
+ .shimmer-btn:disabled {
1421
+ opacity: 0.5;
1422
+ cursor: not-allowed;
1423
+ filter: grayscale(1);
1424
+ }
1425
+
1426
+ /* Variants */
1427
+ .shimmer-btn--primary {
1428
+ background: var(--c-bg-2, #1e293b);
1429
+ color: #fff;
1430
+ border: 1px solid rgba(255, 255, 255, 0.1);
1431
+ }
1432
+
1433
+ .shimmer-btn--secondary {
1434
+ background: var(--c-primary, #6366f1);
1435
+ color: #fff;
1436
+ }
1437
+
1438
+ .shimmer-btn--ghost {
1439
+ background: transparent;
1440
+ color: var(--c-primary-light, #818cf8);
1441
+ border: 1px solid rgba(99, 102, 241, 0.4);
1442
+ --shimmer-color: rgba(99, 102, 241, 0.2);
1443
+ }
1444
+
1445
+ /* Sizes */
1446
+ .shimmer-btn--sm .shimmer-btn__content { padding: 0.45rem 1.2rem; font-size: 0.8rem; }
1447
+ .shimmer-btn--md .shimmer-btn__content { padding: 0.75rem 1.8rem; font-size: 0.95rem; }
1448
+ .shimmer-btn--lg .shimmer-btn__content { padding: 1.1rem 2.6rem; font-size: 1.1rem; }
1449
+ </style>
1450
+
1451
+ `,
1452
+ usage: `<ShimmerButton href="#" type="button" variant="primary" size="md" disabled={false}>Unlock Premium</ShimmerButton>`,
1453
+ },
1454
+ {
1455
+ slug: 'magnetic-button',
1456
+ name: 'Magnetic Button',
1457
+ description: 'A button that follows the cursor slightly when hovered (requires component-level JS).',
1458
+ category: 'buttons',
1459
+ tags: ['button', 'interactive', 'magnetic'],
1460
+ code: `---
1461
+ interface Props {
1462
+ href?: string;
1463
+ type?: 'button' | 'submit' | 'reset';
1464
+ variant?: 'primary' | 'secondary' | 'ghost';
1465
+ size?: 'sm' | 'md' | 'lg';
1466
+ strength?: number;
1467
+ disabled?: boolean;
1468
+ }
1469
+
1470
+ const {
1471
+ href,
1472
+ type = 'button',
1473
+ variant = 'secondary',
1474
+ size = 'md',
1475
+ strength = 0.5,
1476
+ disabled = false
1477
+ } = Astro.props;
1478
+
1479
+ const Tag = href ? 'a' : 'button';
1480
+ ---
1481
+
1482
+ <Tag
1483
+ href={href}
1484
+ type={href ? undefined : type}
1485
+ disabled={disabled}
1486
+ class={\`magnetic-btn magnetic-btn--\${variant} magnetic-btn--\${size}\`}
1487
+ data-strength={strength}
1488
+ >
1489
+ <span class="magnetic-btn__inner">
1490
+ <slot />
1491
+ </span>
1492
+ </Tag>
1493
+
1494
+ <style>
1495
+ .magnetic-btn {
1496
+ display: inline-flex;
1497
+ align-items: center;
1498
+ justify-content: center;
1499
+ padding: 0;
1500
+ border: none;
1501
+ border-radius: var(--r-full, 9999px);
1502
+ cursor: pointer;
1503
+ text-decoration: none;
1504
+ position: relative;
1505
+ transition: transform 0.4s cubic-bezier(0.23, 1, 0.32, 1), box-shadow 0.3s ease;
1506
+ user-select: none;
1507
+ font-family: var(--font-sans, inherit);
1508
+ font-weight: 600;
1509
+ white-space: nowrap;
1510
+ }
1511
+
1512
+ .magnetic-btn__inner {
1513
+ display: flex;
1514
+ align-items: center;
1515
+ justify-content: center;
1516
+ width: 100%;
1517
+ height: 100%;
1518
+ transition: transform 0.4s cubic-bezier(0.23, 1, 0.32, 1);
1519
+ pointer-events: none;
1520
+ }
1521
+
1522
+ /* Variants */
1523
+ .magnetic-btn--primary {
1524
+ background: var(--c-primary, #6366f1);
1525
+ color: #fff;
1526
+ box-shadow: 0 4px 15px rgba(99, 102, 241, 0.3);
1527
+ }
1528
+
1529
+ .magnetic-btn--secondary {
1530
+ background: rgba(255, 255, 255, 0.05);
1531
+ backdrop-filter: blur(12px);
1532
+ color: var(--c-text-1, #f8fafc);
1533
+ border: 1px solid rgba(255, 255, 255, 0.1);
1534
+ }
1535
+
1536
+ .magnetic-btn--ghost {
1537
+ background: transparent;
1538
+ color: var(--c-primary-light, #818cf8);
1539
+ border: 1px solid rgba(99, 102, 241, 0.3);
1540
+ }
1541
+
1542
+ .magnetic-btn:hover:not(:disabled) {
1543
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
1544
+ }
1545
+
1546
+ .magnetic-btn:disabled {
1547
+ opacity: 0.5;
1548
+ cursor: not-allowed;
1549
+ filter: grayscale(1);
1550
+ }
1551
+
1552
+ /* Sizes */
1553
+ .magnetic-btn--sm { min-width: 80px; min-height: 40px; }
1554
+ .magnetic-btn--sm .magnetic-btn__inner { padding: 0.5rem 1.2rem; font-size: 0.8rem; }
1555
+
1556
+ .magnetic-btn--md { min-width: 120px; min-height: 50px; }
1557
+ .magnetic-btn--md .magnetic-btn__inner { padding: 0.8rem 1.8rem; font-size: 0.95rem; }
1558
+
1559
+ .magnetic-btn--lg { min-width: 160px; min-height: 64px; }
1560
+ .magnetic-btn--lg .magnetic-btn__inner { padding: 1.1rem 2.5rem; font-size: 1.1rem; }
1561
+ </style>
1562
+
1563
+ <script>
1564
+ function initMagnetic() {
1565
+ const btns = document.querySelectorAll('.magnetic-btn:not([data-magnetic-init])');
1566
+
1567
+ btns.forEach(btn => {
1568
+ btn.setAttribute('data-magnetic-init', 'true');
1569
+ const inner = btn.querySelector('.magnetic-btn__inner') as HTMLElement;
1570
+ const strengthAttr = btn.getAttribute('data-strength');
1571
+ const strength = strengthAttr ? parseFloat(strengthAttr) : 0.5;
1572
+
1573
+ btn.addEventListener('mousemove', (e: any) => {
1574
+ const rect = btn.getBoundingClientRect();
1575
+ const x = (e.clientX - rect.left - rect.width / 2) * strength;
1576
+ const y = (e.clientY - rect.top - rect.height / 2) * strength;
1577
+
1578
+ (btn as HTMLElement).style.transform = \`translate(\${x}px, \${y}px)\`;
1579
+ if (inner) inner.style.transform = \`translate(\${x * 0.4}px, \${y * 0.4}px)\`;
1580
+ });
1581
+
1582
+ btn.addEventListener('mouseleave', () => {
1583
+ (btn as HTMLElement).style.transform = \`translate(0px, 0px)\`;
1584
+ if (inner) inner.style.transform = \`translate(0px, 0px)\`;
1585
+ });
1586
+ });
1587
+ }
1588
+
1589
+ // Initial and view transition support
1590
+ initMagnetic();
1591
+ document.addEventListener('astro:after-swap', initMagnetic);
1592
+ </script>
1593
+
1594
+ `,
1595
+ usage: `<MagneticButton href="#" type="button" variant="primary" size="md">Interact</MagneticButton>`,
1596
+ },
1597
+ {
1598
+ slug: 'double-border-button',
1599
+ name: 'Double Border Button',
1600
+ description: 'Elegant button with nested borders and smooth scaling.',
1601
+ category: 'buttons',
1602
+ tags: ['button', 'minimalist', 'border'],
1603
+ code: `---
1604
+ interface Props {
1605
+ href?: string;
1606
+ type?: 'button' | 'submit' | 'reset';
1607
+ variant?: 'primary' | 'secondary' | 'ghost';
1608
+ size?: 'sm' | 'md' | 'lg';
1609
+ disabled?: boolean;
1610
+ }
1611
+
1612
+ const {
1613
+ href,
1614
+ type = 'button',
1615
+ variant = 'primary',
1616
+ size = 'md',
1617
+ disabled = false
1618
+ } = Astro.props;
1619
+
1620
+ const Tag = href ? 'a' : 'button';
1621
+ ---
1622
+
1623
+ <Tag
1624
+ href={href}
1625
+ type={href ? undefined : type}
1626
+ disabled={disabled}
1627
+ class={\`double-border-btn double-border-btn--\${variant} double-border-btn--\${size}\`}
1628
+ >
1629
+ <span class="double-border-btn__content"><slot /></span>
1630
+ </Tag>
1631
+
1632
+ <style>
1633
+ .double-border-btn {
1634
+ --offset: 5px;
1635
+ --border-color: rgba(255, 255, 255, 0.4);
1636
+ --hover-color: #fff;
1637
+
1638
+ position: relative;
1639
+ display: inline-flex;
1640
+ align-items: center;
1641
+ justify-content: center;
1642
+ padding: 0;
1643
+ background: transparent;
1644
+ color: #fff;
1645
+ border: 1px solid var(--border-color);
1646
+ border-radius: var(--r-sm, 4px);
1647
+ cursor: pointer;
1648
+ text-decoration: none;
1649
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1650
+ z-index: 1;
1651
+ user-select: none;
1652
+ white-space: nowrap;
1653
+ font-family: var(--font-sans, inherit);
1654
+ font-weight: 500;
1655
+ }
1656
+
1657
+ .double-border-btn__content {
1658
+ display: block;
1659
+ padding: 0.6rem 1.6rem;
1660
+ }
1661
+
1662
+ .double-border-btn::before {
1663
+ content: '';
1664
+ position: absolute;
1665
+ top: var(--offset);
1666
+ left: var(--offset);
1667
+ width: 100%;
1668
+ height: 100%;
1669
+ border: 1px solid var(--border-color);
1670
+ border-radius: inherit;
1671
+ z-index: -1;
1672
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1673
+ opacity: 0.5;
1674
+ }
1675
+
1676
+ .double-border-btn:hover:not(:disabled) {
1677
+ color: var(--hover-color);
1678
+ border-color: var(--hover-color);
1679
+ transform: translate(calc(var(--offset) * -0.5), calc(var(--offset) * -0.5));
1680
+ }
1681
+
1682
+ .double-border-btn:hover:not(:disabled)::before {
1683
+ top: calc(var(--offset) * 1.5);
1684
+ left: calc(var(--offset) * 1.5);
1685
+ border-color: var(--hover-color);
1686
+ opacity: 1;
1687
+ }
1688
+
1689
+ .double-border-btn:active:not(:disabled) {
1690
+ transform: translate(0, 0);
1691
+ }
1692
+
1693
+ .double-border-btn:active:not(:disabled)::before {
1694
+ top: 0;
1695
+ left: 0;
1696
+ }
1697
+
1698
+ .double-border-btn:disabled {
1699
+ opacity: 0.5;
1700
+ cursor: not-allowed;
1701
+ filter: grayscale(1);
1702
+ }
1703
+
1704
+ /* Variants */
1705
+ .double-border-btn--primary {
1706
+ --border-color: rgba(99, 102, 241, 0.5);
1707
+ --hover-color: #818cf8;
1708
+ color: #818cf8;
1709
+ }
1710
+
1711
+ .double-border-btn--secondary {
1712
+ --border-color: rgba(255, 255, 255, 0.3);
1713
+ --hover-color: #fff;
1714
+ color: #fff;
1715
+ }
1716
+
1717
+ .double-border-btn--ghost {
1718
+ --border-color: rgba(255, 255, 255, 0.1);
1719
+ --hover-color: rgba(255, 255, 255, 0.8);
1720
+ color: rgba(255, 255, 255, 0.6);
1721
+ }
1722
+
1723
+ /* Sizes */
1724
+ .double-border-btn--sm { --offset: 3px; }
1725
+ .double-border-btn--sm .double-border-btn__content { padding: 0.45rem 1rem; font-size: 0.85rem; }
1726
+
1727
+ .double-border-btn--md { --offset: 5px; }
1728
+ .double-border-btn--md .double-border-btn__content { padding: 0.65rem 1.6rem; font-size: 1rem; }
1729
+
1730
+ .double-border-btn--lg { --offset: 8px; }
1731
+ .double-border-btn--lg .double-border-btn__content { padding: 0.85rem 2.2rem; font-size: 1.15rem; }
1732
+ </style>
1733
+
1734
+ `,
1735
+ usage: `<DoubleBorderButton href="#" type="button" variant="primary" size="md" disabled={false}>Sign Up</DoubleBorderButton>`,
1736
+ },
1737
+ {
1738
+ slug: 'animated-icon-button',
1739
+ name: 'Animated Icon Button',
1740
+ description: 'A premium button generated dynamically from codebase.',
1741
+ category: 'buttons',
1742
+ tags: ['button', 'premium'],
1743
+ code: `---
1744
+ /**
1745
+ * AnimatedIconButton — A button that elegantly slides an icon into view on hover.
1746
+ *
1747
+ * @param {string} href - Optional. If provided, renders an <a> tag instead of <button>.
1748
+ * @param {'button'|'submit'|'reset'} type - Optional. The HTML button type. Default is 'button'.
1749
+ * @param {'primary'|'secondary'|'ghost'} variant - Optional. The visual style variant. Default is 'primary'.
1750
+ * @param {'sm'|'md'|'lg'} size - Optional. The size variant of the button. Default is 'md'.
1751
+ * @param {'left'|'right'} iconPos - Optional. Side where the icon slides in. Default is 'right'.
1752
+ * @param {boolean} disabled - Optional. Whether the button is disabled. Default is false.
1753
+ */
1754
+ interface Props {
1755
+ href?: string;
1756
+ type?: 'button' | 'submit' | 'reset';
1757
+ variant?: 'primary' | 'secondary' | 'ghost';
1758
+ size?: 'sm' | 'md' | 'lg';
1759
+ iconPos?: 'left' | 'right';
1760
+ disabled?: boolean;
1761
+ }
1762
+
1763
+ const {
1764
+ href,
1765
+ type = 'button',
1766
+ variant = 'primary',
1767
+ size = 'md',
1768
+ iconPos = 'right',
1769
+ disabled = false
1770
+ } = Astro.props;
1771
+
1772
+ const Tag = href ? 'a' : 'button';
1773
+ ---
1774
+
1775
+ <Tag
1776
+ href={href}
1777
+ type={href ? undefined : type}
1778
+ disabled={disabled}
1779
+ class:list={[
1780
+ "icon-anim-btn",
1781
+ \`icon-anim-btn--\${variant}\`,
1782
+ \`icon-anim-btn--\${size}\`,
1783
+ \`icon-anim-btn--\${iconPos}\`
1784
+ ]}
1785
+ >
1786
+ <span class="icon-anim-btn__inner">
1787
+ {iconPos === 'left' && (
1788
+ <span class="icon-anim-btn__icon">
1789
+ <slot name="icon">
1790
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
1791
+ <polyline points="15 18 9 12 15 6"></polyline>
1792
+ </svg>
1793
+ </slot>
1794
+ </span>
1795
+ )}
1796
+ <span class="icon-anim-btn__text"><slot /></span>
1797
+ {iconPos === 'right' && (
1798
+ <span class="icon-anim-btn__icon">
1799
+ <slot name="icon">
1800
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
1801
+ <polyline points="9 18 15 12 9 6"></polyline>
1802
+ </svg>
1803
+ </slot>
1804
+ </span>
1805
+ )}
1806
+ </span>
1807
+ </Tag>
1808
+
1809
+ <style>
1810
+ .icon-anim-btn {
1811
+ --btn-bg: var(--c-primary, #6366f1);
1812
+ --btn-color: #fff;
1813
+ --btn-hover-bg: var(--c-primary-light, #818cf8);
1814
+
1815
+ position: relative;
1816
+ display: inline-flex;
1817
+ align-items: center;
1818
+ justify-content: center;
1819
+ background: var(--btn-bg);
1820
+ color: var(--btn-color);
1821
+ border: none;
1822
+ border-radius: var(--r-md, 12px);
1823
+ cursor: pointer;
1824
+ text-decoration: none;
1825
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1826
+ font-weight: 600;
1827
+ user-select: none;
1828
+ white-space: nowrap;
1829
+ overflow: hidden;
1830
+ font-family: var(--font-sans, inherit);
1831
+ }
1832
+
1833
+ .icon-anim-btn__inner {
1834
+ display: flex;
1835
+ align-items: center;
1836
+ gap: 0.6rem;
1837
+ transition: transform 0.3s ease;
1838
+ }
1839
+
1840
+ .icon-anim-btn__icon {
1841
+ display: flex;
1842
+ align-items: center;
1843
+ justify-content: center;
1844
+ width: 1.25em;
1845
+ height: 1.25em;
1846
+ transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
1847
+ }
1848
+
1849
+ .icon-anim-btn__icon svg {
1850
+ width: 100%;
1851
+ height: 100%;
1852
+ }
1853
+
1854
+ /* Hover Animations */
1855
+ .icon-anim-btn--right:hover:not(:disabled) .icon-anim-btn__icon {
1856
+ transform: translateX(4px) scale(1.1);
1857
+ }
1858
+
1859
+ .icon-anim-btn--left:hover:not(:disabled) .icon-anim-btn__icon {
1860
+ transform: translateX(-4px) scale(1.1);
1861
+ }
1862
+
1863
+ .icon-anim-btn:hover:not(:disabled) {
1864
+ background: var(--btn-hover-bg);
1865
+ box-shadow: 0 4px 20px -5px color-mix(in srgb, var(--btn-bg), transparent 40%);
1866
+ }
1867
+
1868
+ .icon-anim-btn:active:not(:disabled) {
1869
+ transform: scale(0.97);
1870
+ }
1871
+
1872
+ .icon-anim-btn:disabled {
1873
+ opacity: 0.5;
1874
+ cursor: not-allowed;
1875
+ filter: grayscale(1);
1876
+ }
1877
+
1878
+ /* Variants */
1879
+ .icon-anim-btn--secondary {
1880
+ --btn-bg: rgba(255, 255, 255, 0.05);
1881
+ --btn-color: var(--c-text-1, #f8fafc);
1882
+ --btn-hover-bg: rgba(255, 255, 255, 0.1);
1883
+ border: 1px solid rgba(255, 255, 255, 0.1);
1884
+ }
1885
+
1886
+ .icon-anim-btn--ghost {
1887
+ --btn-bg: transparent;
1888
+ --btn-color: var(--c-primary-light, #818cf8);
1889
+ --btn-hover-bg: rgba(99, 102, 241, 0.08);
1890
+ border: 1px solid rgba(99, 102, 241, 0.2);
1891
+ }
1892
+
1893
+ /* Sizes */
1894
+ .icon-anim-btn--sm { padding: 0.45rem 1.2rem; font-size: 0.85rem; }
1895
+ .icon-anim-btn--md { padding: 0.75rem 1.8rem; font-size: 1rem; }
1896
+ .icon-anim-btn--lg { padding: 1rem 2.5rem; font-size: 1.15rem; }
1897
+ </style>
1898
+
1899
+ `,
1900
+ usage: `<AnimatedIconButton href="#" type="button" variant="primary" size="md">Click Me</AnimatedIconButton>`,
1901
+ },
1902
+ {
1903
+ slug: 'border-draw-button',
1904
+ name: 'Border Draw Button',
1905
+ description: 'A premium button generated dynamically from codebase.',
1906
+ category: 'buttons',
1907
+ tags: ['button', 'premium'],
1908
+ code: `---
1909
+ /**
1910
+ * BorderDrawButton — A button that animates its SVG border being drawn on hover.
1911
+ *
1912
+ * @param {string} href - Optional. If provided, renders an <a> tag instead of <button>.
1913
+ * @param {'button'|'submit'|'reset'} type - Optional. The HTML button type. Default is 'button'.
1914
+ * @param {string} color - Optional. The stroke color of the drawing border. Default is '#3b82f6'.
1915
+ * @param {'sm'|'md'|'lg'} size - Optional. The size variant of the button. Default is 'md'.
1916
+ * @param {boolean} disabled - Optional. Whether the button is disabled. Default is false.
1917
+ */
1918
+ interface Props {
1919
+ href?: string;
1920
+ type?: 'button' | 'submit' | 'reset';
1921
+ color?: string;
1922
+ size?: 'sm' | 'md' | 'lg';
1923
+ disabled?: boolean;
1924
+ }
1925
+
1926
+ const {
1927
+ href,
1928
+ type = 'button',
1929
+ color = '#6366f1',
1930
+ size = 'md',
1931
+ disabled = false
1932
+ } = Astro.props;
1933
+
1934
+ const Tag = href ? 'a' : 'button';
1935
+ ---
1936
+
1937
+ <Tag
1938
+ href={href}
1939
+ type={href ? undefined : type}
1940
+ disabled={disabled}
1941
+ class={\`draw-btn draw-btn--\${size}\`}
1942
+ style={\`--btn-color: \${color}\`}
1943
+ >
1944
+ <span class="draw-btn__text"><slot /></span>
1945
+ </Tag>
1946
+
1947
+ <style>
1948
+ .draw-btn {
1949
+ position: relative;
1950
+ display: inline-flex;
1951
+ align-items: center;
1952
+ justify-content: center;
1953
+ background: transparent;
1954
+ color: var(--c-text-2, #94a3b8);
1955
+ border: 2px solid rgba(255, 255, 255, 0.05);
1956
+ cursor: pointer;
1957
+ text-decoration: none;
1958
+ transition: all 0.5s cubic-bezier(0.4, 0, 0.2, 1);
1959
+ font-weight: 600;
1960
+ user-select: none;
1961
+ white-space: nowrap;
1962
+ font-family: var(--font-sans, inherit);
1963
+ }
1964
+
1965
+ .draw-btn__text {
1966
+ position: relative;
1967
+ z-index: 2;
1968
+ transition: color 0.5s ease;
1969
+ }
1970
+
1971
+ .draw-btn::before,
1972
+ .draw-btn::after {
1973
+ content: '';
1974
+ position: absolute;
1975
+ width: 0;
1976
+ height: 0;
1977
+ border: 2px solid transparent;
1978
+ z-index: 1;
1979
+ transition: width 0.2s 0.2s ease-out, height 0.2s ease-out, border-color 0.1s;
1980
+ }
1981
+
1982
+ .draw-btn::before {
1983
+ top: -2px;
1984
+ left: -2px;
1985
+ }
1986
+
1987
+ .draw-btn::after {
1988
+ bottom: -2px;
1989
+ right: -2px;
1990
+ }
1991
+
1992
+ .draw-btn:hover:not(:disabled) {
1993
+ color: #fff;
1994
+ border-color: transparent;
1995
+ }
1996
+
1997
+ .draw-btn:hover:not(:disabled)::before {
1998
+ width: calc(100% + 4px);
1999
+ height: calc(100% + 4px);
2000
+ border-top-color: var(--btn-color);
2001
+ border-right-color: var(--btn-color);
2002
+ transition: width 0.2s ease-out, height 0.2s 0.2s ease-out;
2003
+ }
2004
+
2005
+ .draw-btn:hover:not(:disabled)::after {
2006
+ width: calc(100% + 4px);
2007
+ height: calc(100% + 4px);
2008
+ border-bottom-color: var(--btn-color);
2009
+ border-left-color: var(--btn-color);
2010
+ transition: width 0.2s ease-out, height 0.2s 0.2s ease-out;
2011
+ }
2012
+
2013
+ .draw-btn:active:not(:disabled) {
2014
+ transform: scale(0.98);
2015
+ }
2016
+
2017
+ .draw-btn:disabled {
2018
+ opacity: 0.5;
2019
+ cursor: not-allowed;
2020
+ filter: grayscale(1);
2021
+ }
2022
+
2023
+ /* Sizes */
2024
+ .draw-btn--sm { padding: 0.45rem 1.2rem; font-size: 0.85rem; }
2025
+ .draw-btn--md { padding: 0.75rem 1.8rem; font-size: 1rem; }
2026
+ .draw-btn--lg { padding: 1.1rem 2.5rem; font-size: 1.2rem; }
2027
+ </style>
2028
+
2029
+ `,
2030
+ usage: `<BorderDrawButton href="#" type="button" variant="primary" size="md">Click Me</BorderDrawButton>`,
2031
+ },
2032
+ {
2033
+ slug: 'ghost-outline-button',
2034
+ name: 'Ghost Outline Button',
2035
+ description: 'A premium button generated dynamically from codebase.',
2036
+ category: 'buttons',
2037
+ tags: ['button', 'premium'],
2038
+ code: `---
2039
+ /**
2040
+ * GhostOutlineButton — A minimalist transparent button with gradient outlines and animated borders.
2041
+ *
2042
+ * @param {string} href - Optional. If provided, renders an <a> tag instead of <button>.
2043
+ * @param {'button'|'submit'|'reset'} type - Optional. The HTML button type. Default is 'button'.
2044
+ * @param {string} primaryColor - Optional. Starts the gradient outline color.
2045
+ * @param {string} secondaryColor - Optional. Ends the gradient outline color.
2046
+ * @param {'sm'|'md'|'lg'} size - Optional. The size variant of the button. Default is 'md'.
2047
+ * @param {boolean} disabled - Optional. Whether the button is disabled. Default is false.
2048
+ */
2049
+ interface Props {
2050
+ href?: string;
2051
+ type?: 'button' | 'submit' | 'reset';
2052
+ primaryColor?: string;
2053
+ secondaryColor?: string;
2054
+ size?: 'sm' | 'md' | 'lg';
2055
+ disabled?: boolean;
2056
+ }
2057
+
2058
+ const {
2059
+ href,
2060
+ type = 'button',
2061
+ primaryColor = '#6366f1',
2062
+ secondaryColor = '#c084fc',
2063
+ size = 'md',
2064
+ disabled = false
2065
+ } = Astro.props;
2066
+
2067
+ const Tag = href ? 'a' : 'button';
2068
+ ---
2069
+
2070
+ <Tag
2071
+ href={href}
2072
+ type={href ? undefined : type}
2073
+ disabled={disabled}
2074
+ class={\`ghost-grad-btn ghost-grad-btn--\${size}\`}
2075
+ style={\`--primary-color: \${primaryColor}; --secondary-color: \${secondaryColor};\`}
2076
+ >
2077
+ <span class="ghost-grad-btn__text"><slot /></span>
2078
+ </Tag>
2079
+
2080
+ <style>
2081
+ .ghost-grad-btn {
2082
+ position: relative;
2083
+ display: inline-flex;
2084
+ align-items: center;
2085
+ justify-content: center;
2086
+ background: #0f172a;
2087
+ color: #fff;
2088
+ padding: 2px;
2089
+ border: none;
2090
+ border-radius: var(--r-md, 12px);
2091
+ cursor: pointer;
2092
+ text-decoration: none;
2093
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2094
+ font-weight: 700;
2095
+ user-select: none;
2096
+ white-space: nowrap;
2097
+ overflow: hidden;
2098
+ font-family: var(--font-sans, inherit);
2099
+ }
2100
+
2101
+ .ghost-grad-btn::before {
2102
+ content: '';
2103
+ position: absolute;
2104
+ inset: 0;
2105
+ background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
2106
+ z-index: 1;
2107
+ transition: opacity 0.3s ease;
2108
+ }
2109
+
2110
+ .ghost-grad-btn:hover:not(:disabled)::before {
2111
+ opacity: 0.8;
2112
+ }
2113
+
2114
+ .ghost-grad-btn__text {
2115
+ position: relative;
2116
+ display: block;
2117
+ width: 100%;
2118
+ height: 100%;
2119
+ background: #0f172a;
2120
+ padding: 0.7rem 1.8rem;
2121
+ border-radius: calc(var(--r-md, 12px) - 2px);
2122
+ z-index: 2;
2123
+ transition: background 0.3s ease, color 0.3s ease;
2124
+ }
2125
+
2126
+ .ghost-grad-btn:hover:not(:disabled) .ghost-grad-btn__text {
2127
+ background: transparent;
2128
+ color: #fff;
2129
+ }
2130
+
2131
+ .ghost-grad-btn:hover:not(:disabled) {
2132
+ box-shadow: 0 10px 25px -5px color-mix(in srgb, var(--primary-color), transparent 50%);
2133
+ transform: translateY(-1px);
2134
+ }
2135
+
2136
+ .ghost-grad-btn:active:not(:disabled) {
2137
+ transform: scale(0.98);
2138
+ }
2139
+
2140
+ .ghost-grad-btn:disabled {
2141
+ opacity: 0.5;
2142
+ cursor: not-allowed;
2143
+ filter: grayscale(1);
2144
+ }
2145
+
2146
+ /* Sizes */
2147
+ .ghost-grad-btn--sm { font-size: 0.85rem; }
2148
+ .ghost-grad-btn--sm .ghost-grad-btn__text { padding: 0.45rem 1.2rem; }
2149
+
2150
+ .ghost-grad-btn--md { font-size: 1rem; }
2151
+ .ghost-grad-btn--md .ghost-grad-btn__text { padding: 0.75rem 1.8rem; }
2152
+
2153
+ .ghost-grad-btn--lg { font-size: 1.15rem; }
2154
+ .ghost-grad-btn--lg .ghost-grad-btn__text { padding: 1.1rem 2.5rem; }
2155
+ </style>
2156
+
2157
+ `,
2158
+ usage: `<GhostOutlineButton href="#" type="button" variant="primary" size="md">Click Me</GhostOutlineButton>`,
2159
+ },
2160
+ {
2161
+ slug: 'liquid-button',
2162
+ name: 'Liquid Button',
2163
+ description: 'A premium button generated dynamically from codebase.',
2164
+ category: 'buttons',
2165
+ tags: ['button', 'premium'],
2166
+ code: `---
2167
+ /**
2168
+ * LiquidButton — A button featuring a fluid, liquid-like animated background layer.
2169
+ *
2170
+ * @param {string} href - Optional. If provided, renders an <a> tag instead of <button>.
2171
+ * @param {'button'|'submit'|'reset'} type - Optional. The HTML button type. Default is 'button'.
2172
+ * @param {string} color - Optional. The base color for the fluid liquid effect. Default is '#3b82f6'.
2173
+ * @param {'sm'|'md'|'lg'} size - Optional. The size variant of the button. Default is 'md'.
2174
+ * @param {boolean} disabled - Optional. Whether the button is disabled. Default is false.
2175
+ */
2176
+ interface Props {
2177
+ href?: string;
2178
+ type?: 'button' | 'submit' | 'reset';
2179
+ color?: string;
2180
+ size?: 'sm' | 'md' | 'lg';
2181
+ disabled?: boolean;
2182
+ }
2183
+
2184
+ const {
2185
+ href,
2186
+ type = 'button',
2187
+ color = '#6366f1',
2188
+ size = 'md',
2189
+ disabled = false
2190
+ } = Astro.props;
2191
+
2192
+ const Tag = href ? 'a' : 'button';
2193
+ // Create a unique filter ID to avoid conflicts when multiple buttons are on screen
2194
+ const filterId = \`gooey-\${Math.random().toString(36).substr(2, 9)}\`;
2195
+ ---
2196
+
2197
+ <Tag
2198
+ href={href}
2199
+ type={href ? undefined : type}
2200
+ disabled={disabled}
2201
+ class={\`liquid-btn liquid-btn--\${size}\`}
2202
+ style={\`--liquid-color: \${color}\`}
2203
+ >
2204
+ <span class="liquid-btn__text"><slot /></span>
2205
+
2206
+ <div class="liquid-wrapper">
2207
+ <div class="liquid-main"></div>
2208
+ <div class="liquid-container">
2209
+ <div class="liquid-blob"></div>
2210
+ <div class="liquid-blob"></div>
2211
+ <div class="liquid-blob"></div>
2212
+ <div class="liquid-blob"></div>
2213
+ </div>
2214
+ </div>
2215
+
2216
+ <svg style="position: absolute; width: 0; height: 0; pointer-events: none;">
2217
+ <defs>
2218
+ <filter id={filterId}>
2219
+ <feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur" />
2220
+ <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -9" result="goo" />
2221
+ <feComposite in="SourceGraphic" in2="goo" operator="atop" />
2222
+ </filter>
2223
+ </defs>
2224
+ </svg>
2225
+ </Tag>
2226
+
2227
+ <style define:vars={{ filterId: \`url(#\${filterId})\` }}>
2228
+ .liquid-btn {
2229
+ position: relative;
2230
+ display: inline-flex;
2231
+ align-items: center;
2232
+ justify-content: center;
2233
+ background: #080b14;
2234
+ color: #fff;
2235
+ border: none;
2236
+ border-radius: var(--r-md, 14px);
2237
+ cursor: pointer;
2238
+ text-decoration: none;
2239
+ overflow: hidden;
2240
+ transition: all 0.4s cubic-bezier(0.23, 1, 0.320, 1);
2241
+ z-index: 1;
2242
+ font-family: var(--font-sans, inherit);
2243
+ font-weight: 800;
2244
+ letter-spacing: 0.1em;
2245
+ text-transform: uppercase;
2246
+ user-select: none;
2247
+ white-space: nowrap;
2248
+ border: 1px solid rgba(255, 255, 255, 0.05);
2249
+ }
2250
+
2251
+ .liquid-btn__text {
2252
+ position: relative;
2253
+ z-index: 10;
2254
+ padding: 0 4px;
2255
+ transition: color 0.4s ease;
2256
+ }
2257
+
2258
+ .liquid-wrapper {
2259
+ position: absolute;
2260
+ inset: 0;
2261
+ z-index: 2;
2262
+ pointer-events: none;
2263
+ filter: var(--filterId);
2264
+ border-radius: inherit;
2265
+ /* Subtle base fill */
2266
+ opacity: 0.95;
2267
+ }
2268
+
2269
+ .liquid-main {
2270
+ position: absolute;
2271
+ bottom: -100%;
2272
+ left: 0;
2273
+ width: 100%;
2274
+ height: 100%;
2275
+ background: var(--liquid-color);
2276
+ transition: transform 0.8s cubic-bezier(0.19, 1, 0.22, 1);
2277
+ }
2278
+
2279
+ .liquid-container {
2280
+ position: absolute;
2281
+ inset: 0;
2282
+ z-index: 3;
2283
+ }
2284
+
2285
+ .liquid-blob {
2286
+ position: absolute;
2287
+ top: 100%;
2288
+ background: var(--liquid-color);
2289
+ border-radius: 50%;
2290
+ transition: transform 0.7s cubic-bezier(0.19, 1, 0.22, 1);
2291
+ }
2292
+
2293
+ .liquid-blob:nth-child(1) { left: 10%; width: 60px; height: 60px; transition-delay: 0s; }
2294
+ .liquid-blob:nth-child(2) { left: 40%; width: 80px; height: 80px; transition-delay: 0.05s; }
2295
+ .liquid-blob:nth-child(3) { left: 70%; width: 50px; height: 50px; transition-delay: 0.02s; }
2296
+ .liquid-blob:nth-child(4) { left: 30%; width: 70px; height: 70px; transition-delay: 0.08s; }
2297
+
2298
+ /* Hover States */
2299
+ .liquid-btn:hover:not(:disabled) {
2300
+ border-color: rgba(255, 255, 255, 0.1);
2301
+ transform: translateY(-2px);
2302
+ box-shadow: 0 20px 40px -10px color-mix(in srgb, var(--liquid-color), transparent 70%);
2303
+ }
2304
+
2305
+ .liquid-btn:hover:not(:disabled) .liquid-main {
2306
+ transform: translateY(-100%);
2307
+ }
2308
+
2309
+ .liquid-btn:hover:not(:disabled) .liquid-blob {
2310
+ transform: translateY(-180%) scale(1.4);
2311
+ }
2312
+
2313
+ /* Movement animation for blobs when hovered */
2314
+ .liquid-btn:hover:not(:disabled) .liquid-blob:nth-child(odd) {
2315
+ animation: liquid-wiggle 3s ease-in-out infinite alternate;
2316
+ }
2317
+ .liquid-btn:hover:not(:disabled) .liquid-blob:nth-child(even) {
2318
+ animation: liquid-wiggle 4s ease-in-out infinite alternate-reverse;
2319
+ }
2320
+
2321
+ @keyframes liquid-wiggle {
2322
+ 0% { margin-left: -5px; }
2323
+ 100% { margin-left: 5px; }
2324
+ }
2325
+
2326
+ .liquid-btn:active:not(:disabled) {
2327
+ transform: scale(0.96);
2328
+ }
2329
+
2330
+ .liquid-btn:disabled {
2331
+ opacity: 0.4;
2332
+ cursor: not-allowed;
2333
+ filter: grayscale(1);
2334
+ }
2335
+
2336
+ /* Sizes */
2337
+ .liquid-btn--sm { padding: 0.6rem 1.6rem; font-size: 0.8rem; }
2338
+ .liquid-btn--md { padding: 0.9rem 2.4rem; font-size: 0.95rem; }
2339
+ .liquid-btn--lg { padding: 1.2rem 3.5rem; font-size: 1.1rem; }
2340
+ </style>
2341
+
2342
+ `,
2343
+ usage: `<LiquidButton href="#" type="button" variant="primary" size="md">Click Me</LiquidButton>`,
2344
+ },
2345
+ {
2346
+ slug: 'minimal-pill-button',
2347
+ name: 'Minimal Pill Button',
2348
+ description: 'A premium button generated dynamically from codebase.',
2349
+ category: 'buttons',
2350
+ tags: ['button', 'premium'],
2351
+ code: `---
2352
+ /**
2353
+ * MinimalPillButton — A cleanly rounded pill button optimized for minimal interactions and tags.
2354
+ *
2355
+ * @param {string} href - Optional. If provided, renders an <a> tag instead of <button>.
2356
+ * @param {'button'|'submit'|'reset'} type - Optional. The HTML button type. Default is 'button'.
2357
+ * @param {'primary'|'secondary'|'ghost'} variant - Optional. The visual style variant. Default is 'primary'.
2358
+ * @param {'sm'|'md'|'lg'} size - Optional. The size variant of the button. Default is 'md'.
2359
+ * @param {boolean} disabled - Optional. Whether the button is disabled. Default is false.
2360
+ */
2361
+ interface Props {
2362
+ href?: string;
2363
+ type?: 'button' | 'submit' | 'reset';
2364
+ variant?: 'primary' | 'secondary' | 'ghost';
2365
+ size?: 'sm' | 'md' | 'lg';
2366
+ disabled?: boolean;
2367
+ }
2368
+
2369
+ const {
2370
+ href,
2371
+ type = 'button',
2372
+ variant = 'primary',
2373
+ size = 'md',
2374
+ disabled = false
2375
+ } = Astro.props;
2376
+
2377
+ const Tag = href ? 'a' : 'button';
2378
+ ---
2379
+
2380
+ <Tag
2381
+ href={href}
2382
+ type={href ? undefined : type}
2383
+ disabled={disabled}
2384
+ class={\`pill-btn pill-btn--\${variant} pill-btn--\${size}\`}
2385
+ >
2386
+ <slot />
2387
+ </Tag>
2388
+
2389
+ <style>
2390
+ .pill-btn {
2391
+ position: relative;
2392
+ display: inline-flex;
2393
+ align-items: center;
2394
+ justify-content: center;
2395
+ padding: 0.5rem 1.5rem;
2396
+ border-radius: var(--r-full, 9999px);
2397
+ font-size: 0.85rem;
2398
+ font-weight: 500;
2399
+ cursor: pointer;
2400
+ text-decoration: none;
2401
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
2402
+ user-select: none;
2403
+ white-space: nowrap;
2404
+ border: 1px solid transparent;
2405
+ font-family: var(--font-sans, inherit);
2406
+ }
2407
+
2408
+ .pill-btn:hover:not(:disabled) {
2409
+ transform: translateY(-1px);
2410
+ }
2411
+
2412
+ .pill-btn:active:not(:disabled) {
2413
+ transform: translateY(0);
2414
+ }
2415
+
2416
+ .pill-btn:disabled {
2417
+ opacity: 0.5;
2418
+ cursor: not-allowed;
2419
+ filter: grayscale(1);
2420
+ }
2421
+
2422
+ /* Variants */
2423
+ .pill-btn--primary {
2424
+ background: rgba(255, 255, 255, 0.05);
2425
+ color: var(--c-text-2, #94a3b8);
2426
+ border-color: rgba(255, 255, 255, 0.1);
2427
+ }
2428
+ .pill-btn--primary:hover:not(:disabled) {
2429
+ background: #fff;
2430
+ color: #000;
2431
+ border-color: #fff;
2432
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
2433
+ }
2434
+
2435
+ .pill-btn--secondary {
2436
+ background: var(--c-primary, #6366f1);
2437
+ color: #fff;
2438
+ box-shadow: 0 4px 12px rgba(99, 102, 241, 0.2);
2439
+ }
2440
+ .pill-btn--secondary:hover:not(:disabled) {
2441
+ background: var(--c-primary-light, #818cf8);
2442
+ box-shadow: 0 6px 16px rgba(99, 102, 241, 0.3);
2443
+ }
2444
+
2445
+ .pill-btn--ghost {
2446
+ background: transparent;
2447
+ color: var(--c-text-2, #94a3b8);
2448
+ border-color: rgba(255, 255, 255, 0.1);
2449
+ }
2450
+ .pill-btn--ghost:hover:not(:disabled) {
2451
+ background: rgba(255, 255, 255, 0.05);
2452
+ color: #fff;
2453
+ border-color: rgba(255, 255, 255, 0.3);
2454
+ }
2455
+
2456
+ /* Sizes */
2457
+ .pill-btn--sm { padding: 0.35rem 1.2rem; font-size: 0.75rem; }
2458
+ .pill-btn--md { padding: 0.55rem 1.6rem; font-size: 0.85rem; }
2459
+ .pill-btn--lg { padding: 0.8rem 2.2rem; font-size: 1rem; }
2460
+ </style>
2461
+
2462
+ `,
2463
+ usage: `<MinimalPillButton href="#" type="button" variant="primary" size="md">Click Me</MinimalPillButton>`,
2464
+ },
2465
+ {
2466
+ slug: 'retro-shadow-button',
2467
+ name: 'Retro Shadow Button',
2468
+ description: 'A premium button generated dynamically from codebase.',
2469
+ category: 'buttons',
2470
+ tags: ['button', 'premium'],
2471
+ code: `---
2472
+ /**
2473
+ * RetroShadowButton — A sharp-edged neo-brutalist button with a thick solid block shadow.
2474
+ *
2475
+ * @param {string} href - Optional. If provided, renders an <a> tag instead of <button>.
2476
+ * @param {'button'|'submit'|'reset'} type - Optional. The HTML button type. Default is 'button'.
2477
+ * @param {'pink'|'cyan'|'yellow'|'lime'} variant - Optional. The visual highly saturated retro style variant. Default is 'pink'.
2478
+ * @param {'sm'|'md'|'lg'} size - Optional. The size variant of the button. Default is 'md'.
2479
+ * @param {boolean} disabled - Optional. Whether the button is disabled. Default is false.
2480
+ */
2481
+ interface Props {
2482
+ href?: string;
2483
+ type?: 'button' | 'submit' | 'reset';
2484
+ variant?: 'pink' | 'cyan' | 'yellow' | 'lime';
2485
+ size?: 'sm' | 'md' | 'lg';
2486
+ disabled?: boolean;
2487
+ }
2488
+
2489
+ const {
2490
+ href,
2491
+ type = 'button',
2492
+ variant = 'pink',
2493
+ size = 'md',
2494
+ disabled = false
2495
+ } = Astro.props;
2496
+
2497
+ const Tag = href ? 'a' : 'button';
2498
+ ---
2499
+
2500
+ <Tag
2501
+ href={href}
2502
+ type={href ? undefined : type}
2503
+ disabled={disabled}
2504
+ class={\`retro-shadow-btn retro-shadow-btn--\${variant} retro-shadow-btn--\${size}\`}
2505
+ >
2506
+ <slot />
2507
+ </Tag>
2508
+
2509
+ <style>
2510
+ .retro-shadow-btn {
2511
+ --btn-bg: #ff7eb3;
2512
+ --shadow-color: #000;
2513
+ --shadow-offset: 6px;
2514
+
2515
+ position: relative;
2516
+ display: inline-flex;
2517
+ align-items: center;
2518
+ justify-content: center;
2519
+ background: var(--btn-bg);
2520
+ color: #000;
2521
+ border: 3px solid #000;
2522
+ font-family: var(--font-sans, inherit);
2523
+ font-weight: 800;
2524
+ text-transform: uppercase;
2525
+ cursor: pointer;
2526
+ text-decoration: none;
2527
+ transition: all 0.1s;
2528
+ box-shadow: var(--shadow-offset) var(--shadow-offset) 0 var(--shadow-color);
2529
+ user-select: none;
2530
+ white-space: nowrap;
2531
+ }
2532
+
2533
+ .retro-shadow-btn:hover:not(:disabled) {
2534
+ transform: translate(calc(var(--shadow-offset) * -0.2), calc(var(--shadow-offset) * -0.2));
2535
+ box-shadow: calc(var(--shadow-offset) * 1.2) calc(var(--shadow-offset) * 1.2) 0 var(--shadow-color);
2536
+ }
2537
+
2538
+ .retro-shadow-btn:active:not(:disabled) {
2539
+ transform: translate(calc(var(--shadow-offset) * 0.5), calc(var(--shadow-offset) * 0.5));
2540
+ box-shadow: calc(var(--shadow-offset) * 0.5) calc(var(--shadow-offset) * 0.5) 0 var(--shadow-color);
2541
+ }
2542
+
2543
+ .retro-shadow-btn:disabled {
2544
+ opacity: 0.6;
2545
+ cursor: not-allowed;
2546
+ filter: grayscale(0.5);
2547
+ box-shadow: 2px 2px 0 var(--shadow-color);
2548
+ }
2549
+
2550
+ /* Variants (90s Palette) */
2551
+ .retro-shadow-btn--pink { --btn-bg: #ff7eb3; }
2552
+ .retro-shadow-btn--cyan { --btn-bg: #00f5ff; }
2553
+ .retro-shadow-btn--yellow { --btn-bg: #fff01f; }
2554
+ .retro-shadow-btn--lime { --btn-bg: #bfff00; }
2555
+
2556
+ /* Sizes */
2557
+ .retro-shadow-btn--sm { padding: 0.4rem 1rem; font-size: 0.85rem; --shadow-offset: 4px; border-width: 2px; }
2558
+ .retro-shadow-btn--md { padding: 0.7rem 1.6rem; font-size: 1rem; --shadow-offset: 6px; border-width: 3px; }
2559
+ .retro-shadow-btn--lg { padding: 1rem 2.2rem; font-size: 1.2rem; --shadow-offset: 8px; border-width: 4px; }
2560
+ </style>
2561
+
2562
+ `,
2563
+ usage: `<RetroShadowButton href="#" type="button" variant="primary" size="md">Click Me</RetroShadowButton>`,
2564
+ },
2565
+ {
2566
+ slug: 'tilt-glow-button',
2567
+ name: 'Tilt Glow Button',
2568
+ description: 'A premium button generated dynamically from codebase.',
2569
+ category: 'buttons',
2570
+ tags: ['button', 'premium'],
2571
+ code: `---
2572
+ /**
2573
+ * TiltGlowButton — An interactive 3D button that tilts towards the cursor with dynamic lighting.
2574
+ *
2575
+ * @param {string} href - Optional. If provided, renders an <a> tag instead of <button>.
2576
+ * @param {'button'|'submit'|'reset'} type - Optional. The HTML button type. Default is 'button'.
2577
+ * @param {string} primaryColor - Optional. The primary light/gradient effect color.
2578
+ * @param {string} secondaryColor - Optional. The secondary lighting/gradient reflection color.
2579
+ * @param {'sm'|'md'|'lg'} size - Optional. The size variant of the button. Default is 'md'.
2580
+ * @param {boolean} disabled - Optional. Whether the button is disabled. Default is false.
2581
+ */
2582
+ interface Props {
2583
+ href?: string;
2584
+ type?: 'button' | 'submit' | 'reset';
2585
+ primaryColor?: string;
2586
+ secondaryColor?: string;
2587
+ size?: 'sm' | 'md' | 'lg';
2588
+ disabled?: boolean;
2589
+ }
2590
+
2591
+ const {
2592
+ href,
2593
+ type = 'button',
2594
+ primaryColor = '#6366f1',
2595
+ secondaryColor = '#c084fc',
2596
+ size = 'md',
2597
+ disabled = false
2598
+ } = Astro.props;
2599
+
2600
+ const Tag = href ? 'a' : 'button';
2601
+ ---
2602
+
2603
+ <div class={\`tilt-wrapper tilt-wrapper--\${size}\`}>
2604
+ <Tag
2605
+ href={href}
2606
+ type={href ? undefined : type}
2607
+ disabled={disabled}
2608
+ class="tilt-card"
2609
+ style={\`--primary-color: \${primaryColor}; --secondary-color: \${secondaryColor};\`}
2610
+ >
2611
+ <div class="tilt-card__glow"></div>
2612
+ <div class="tilt-card__content">
2613
+ <slot />
2614
+ </div>
2615
+ </Tag>
2616
+ </div>
2617
+
2618
+ <style>
2619
+ .tilt-wrapper {
2620
+ perspective: 1000px;
2621
+ display: inline-block;
2622
+ }
2623
+
2624
+ .tilt-card {
2625
+ position: relative;
2626
+ display: flex;
2627
+ align-items: center;
2628
+ justify-content: center;
2629
+ background: #0f172a;
2630
+ color: #fff;
2631
+ border: 1px solid rgba(255, 255, 255, 0.1);
2632
+ border-radius: var(--r-md, 12px);
2633
+ cursor: pointer;
2634
+ text-decoration: none;
2635
+ overflow: hidden;
2636
+ transition: transform 0.15s ease-out, box-shadow 0.3s ease;
2637
+ transform-style: preserve-3d;
2638
+ user-select: none;
2639
+ font-family: var(--font-sans, inherit);
2640
+ font-weight: 600;
2641
+ }
2642
+
2643
+ .tilt-card__glow {
2644
+ position: absolute;
2645
+ inset: 0;
2646
+ background: radial-gradient(
2647
+ circle at var(--mouse-x, 50%) var(--mouse-y, 50%),
2648
+ color-mix(in srgb, var(--primary-color), white 20%) 0%,
2649
+ transparent 70%
2650
+ );
2651
+ opacity: 0;
2652
+ transition: opacity 0.3s ease;
2653
+ pointer-events: none;
2654
+ z-index: 1;
2655
+ }
2656
+
2657
+ .tilt-card__content {
2658
+ position: relative;
2659
+ z-index: 2;
2660
+ transform: translateZ(20px);
2661
+ white-space: nowrap;
2662
+ }
2663
+
2664
+ .tilt-card:hover {
2665
+ box-shadow: 0 20px 40px -10px rgba(0, 0, 0, 0.5);
2666
+ border-color: rgba(255, 255, 255, 0.2);
2667
+ }
2668
+
2669
+ .tilt-card:hover .tilt-card__glow {
2670
+ opacity: 0.15;
2671
+ }
2672
+
2673
+ .tilt-card:active {
2674
+ transform: scale(0.98) translateZ(0);
2675
+ }
2676
+
2677
+ .tilt-card:disabled {
2678
+ opacity: 0.5;
2679
+ cursor: not-allowed;
2680
+ filter: grayscale(1);
2681
+ }
2682
+
2683
+ /* Sizes */
2684
+ .tilt-wrapper--sm .tilt-card { padding: 0.5rem 1.4rem; font-size: 0.8rem; }
2685
+ .tilt-wrapper--md .tilt-card { padding: 0.8rem 2rem; font-size: 0.95rem; }
2686
+ .tilt-wrapper--lg .tilt-card { padding: 1.1rem 3rem; font-size: 1.1rem; }
2687
+ </style>
2688
+
2689
+ <script>
2690
+ function initTilt() {
2691
+ const cards = document.querySelectorAll('.tilt-card:not([data-tilt-init])');
2692
+
2693
+ cards.forEach(card => {
2694
+ card.setAttribute('data-tilt-init', 'true');
2695
+ const htmlCard = card as HTMLElement;
2696
+
2697
+ card.addEventListener('mousemove', (e: any) => {
2698
+ const rect = htmlCard.getBoundingClientRect();
2699
+ const x = e.clientX - rect.left;
2700
+ const y = e.clientY - rect.top;
2701
+
2702
+ // Calculate percentages for the glow highlight
2703
+ const xPerc = (x / rect.width) * 100;
2704
+ const yPerc = (y / rect.height) * 100;
2705
+ htmlCard.style.setProperty('--mouse-x', \`\${xPerc}%\`);
2706
+ htmlCard.style.setProperty('--mouse-y', \`\${yPerc}%\`);
2707
+
2708
+ // Calculate rotation
2709
+ const rotateY = (x / rect.width - 0.5) * 25;
2710
+ const rotateX = (y / rect.height - 0.5) * -25;
2711
+ htmlCard.style.transform = \`rotateY(\${rotateY}deg) rotateX(\${rotateX}deg)\`;
2712
+ });
2713
+
2714
+ card.addEventListener('mouseleave', () => {
2715
+ htmlCard.style.transform = 'rotateY(0deg) rotateX(0deg)';
2716
+ htmlCard.style.setProperty('--mouse-x', '50%');
2717
+ htmlCard.style.setProperty('--mouse-y', '50%');
2718
+ });
2719
+ });
2720
+ }
2721
+
2722
+ initTilt();
2723
+ document.addEventListener('astro:after-swap', initTilt);
2724
+ </script>
2725
+
2726
+ `,
2727
+ usage: `<TiltGlowButton href="#" type="button" variant="primary" size="md">Click Me</TiltGlowButton>`,
2728
+ },
2729
+ {
2730
+ slug: 'ripple-button',
2731
+ name: 'Ripple Button',
2732
+ description: 'A sleek interactive button that triggers localized expanding ripple rings on click.',
2733
+ category: 'buttons',
2734
+ tags: ["button","ripple","interactive","micro-interaction"],
2735
+ featured: true,
2736
+ code: `---
2737
+ /**
2738
+ * RippleButton — Interactive button featuring an expanding click ripple effect with glass styling.
2739
+ *
2740
+ * @param {'primary' | 'secondary' | 'ghost'} variant - Visual style variant. Default is 'primary'.
2741
+ * @param {'sm' | 'md' | 'lg'} size - Button size variant. Default is 'md'.
2742
+ * @param {string} href - Optional link destination.
2743
+ * @param {boolean} disabled - Whether the button is disabled.
2744
+ * @param {string} className - Additional CSS class names.
2745
+ */
2746
+ interface Props {
2747
+ variant?: 'primary' | 'secondary' | 'ghost';
2748
+ size?: 'sm' | 'md' | 'lg';
2749
+ href?: string;
2750
+ disabled?: boolean;
2751
+ className?: string;
2752
+ }
2753
+
2754
+ const {
2755
+ variant = 'primary',
2756
+ size = 'md',
2757
+ href,
2758
+ disabled = false,
2759
+ className = '',
2760
+ } = Astro.props;
2761
+
2762
+ const Tag = href ? 'a' : 'button';
2763
+ ---
2764
+
2765
+ <Tag
2766
+ href={href}
2767
+ disabled={!href && disabled ? true : undefined}
2768
+ aria-disabled={disabled ? 'true' : undefined}
2769
+ class:list={['ripple-btn', \`ripple-btn--\${variant}\`, \`ripple-btn--\${size}\`, className]}
2770
+ data-ripple-btn
2771
+ >
2772
+ <span class="ripple-btn__label"><slot /></span>
2773
+ <span class="ripple-btn__container" aria-hidden="true"></span>
2774
+ </Tag>
2775
+
2776
+ <style>
2777
+ .ripple-btn {
2778
+ --btn-primary-bg: linear-gradient(135deg, rgba(184, 134, 11, 0.9), rgba(212, 160, 23, 0.9));
2779
+ --btn-primary-color: #050505;
2780
+ --btn-secondary-bg: rgba(255, 255, 255, 0.05);
2781
+ --btn-secondary-color: var(--c-text-1, #ffffff);
2782
+ --btn-ghost-bg: transparent;
2783
+ --btn-ghost-color: var(--c-primary-light, #d4a017);
2784
+
2785
+ position: relative;
2786
+ display: inline-flex;
2787
+ align-items: center;
2788
+ justify-content: center;
2789
+ overflow: hidden;
2790
+ font-family: var(--font-sans, system-ui, sans-serif);
2791
+ font-weight: 700;
2792
+ text-decoration: none;
2793
+ border-radius: var(--r-md, 12px);
2794
+ border: 1px solid rgba(255, 255, 255, 0.1);
2795
+ cursor: pointer;
2796
+ user-select: none;
2797
+ transition: transform 0.2s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s ease, border-color 0.2s ease;
2798
+ }
2799
+
2800
+ .ripple-btn:focus-visible {
2801
+ outline: 2px solid var(--c-primary-light, #d4a017);
2802
+ outline-offset: 3px;
2803
+ }
2804
+
2805
+ .ripple-btn[disabled],
2806
+ .ripple-btn[aria-disabled="true"] {
2807
+ opacity: 0.5;
2808
+ cursor: not-allowed;
2809
+ pointer-events: none;
2810
+ }
2811
+
2812
+ /* Variants */
2813
+ .ripple-btn--primary {
2814
+ background: var(--btn-primary-bg);
2815
+ color: var(--btn-primary-color);
2816
+ box-shadow: 0 4px 20px rgba(184, 134, 11, 0.25);
2817
+ }
2818
+ .ripple-btn--primary:hover {
2819
+ transform: translateY(-2px);
2820
+ box-shadow: 0 8px 30px rgba(184, 134, 11, 0.4);
2821
+ border-color: rgba(254, 243, 199, 0.4);
2822
+ }
2823
+
2824
+ .ripple-btn--secondary {
2825
+ background: var(--btn-secondary-bg);
2826
+ color: var(--btn-secondary-color);
2827
+ backdrop-filter: blur(12px);
2828
+ }
2829
+ .ripple-btn--secondary:hover {
2830
+ background: rgba(255, 255, 255, 0.09);
2831
+ border-color: rgba(184, 134, 11, 0.3);
2832
+ transform: translateY(-2px);
2833
+ }
2834
+
2835
+ .ripple-btn--ghost {
2836
+ background: var(--btn-ghost-bg);
2837
+ color: var(--btn-ghost-color);
2838
+ border-color: transparent;
2839
+ }
2840
+ .ripple-btn--ghost:hover {
2841
+ background: rgba(184, 134, 11, 0.1);
2842
+ border-color: rgba(184, 134, 11, 0.2);
2843
+ }
2844
+
2845
+ /* Sizes */
2846
+ .ripple-btn--sm {
2847
+ padding: 0.45rem 1rem;
2848
+ font-size: 0.8rem;
2849
+ letter-spacing: 0.02em;
2850
+ }
2851
+ .ripple-btn--md {
2852
+ padding: 0.75rem 1.6rem;
2853
+ font-size: 0.92rem;
2854
+ letter-spacing: 0.02em;
2855
+ }
2856
+ .ripple-btn--lg {
2857
+ padding: 1rem 2.2rem;
2858
+ font-size: 1.05rem;
2859
+ letter-spacing: 0.03em;
2860
+ }
2861
+
2862
+ .ripple-btn__label {
2863
+ position: relative;
2864
+ z-index: 2;
2865
+ display: inline-flex;
2866
+ align-items: center;
2867
+ gap: var(--sp-2, 0.5rem);
2868
+ pointer-events: none;
2869
+ }
2870
+
2871
+ .ripple-btn__container {
2872
+ position: absolute;
2873
+ inset: 0;
2874
+ z-index: 1;
2875
+ overflow: hidden;
2876
+ pointer-events: none;
2877
+ }
2878
+
2879
+ :global(.ripple-effect) {
2880
+ position: absolute;
2881
+ border-radius: 50%;
2882
+ transform: scale(0);
2883
+ animation: ripple-grow 0.6s cubic-bezier(0, 0, 0.2, 1) forwards;
2884
+ background: rgba(255, 255, 255, 0.35);
2885
+ pointer-events: none;
2886
+ }
2887
+
2888
+ @keyframes ripple-grow {
2889
+ to {
2890
+ transform: scale(4);
2891
+ opacity: 0;
2892
+ }
2893
+ }
2894
+
2895
+ @media (prefers-reduced-motion: reduce) {
2896
+ .ripple-btn { transition: none; }
2897
+ :global(.ripple-effect) { display: none; }
2898
+ }
2899
+ </style>
2900
+
2901
+ <script>
2902
+ function setupRipples() {
2903
+ const buttons = document.querySelectorAll('[data-ripple-btn]');
2904
+ buttons.forEach((btn) => {
2905
+ if (btn.getAttribute('data-ripple-init')) return;
2906
+ btn.setAttribute('data-ripple-init', 'true');
2907
+
2908
+ btn.addEventListener('pointerdown', (e: Event) => {
2909
+ const mouseEvent = e as MouseEvent;
2910
+ const target = mouseEvent.currentTarget as HTMLElement;
2911
+ const container = target.querySelector('.ripple-btn__container');
2912
+ if (!container) return;
2913
+
2914
+ const rect = target.getBoundingClientRect();
2915
+ const size = Math.max(rect.width, rect.height);
2916
+ const x = mouseEvent.clientX - rect.left - size / 2;
2917
+ const y = mouseEvent.clientY - rect.top - size / 2;
2918
+
2919
+ const ripple = document.createElement('span');
2920
+ ripple.className = 'ripple-effect';
2921
+ ripple.style.width = \`\${size}px\`;
2922
+ ripple.style.height = \`\${size}px\`;
2923
+ ripple.style.left = \`\${x}px\`;
2924
+ ripple.style.top = \`\${y}px\`;
2925
+
2926
+ container.appendChild(ripple);
2927
+ setTimeout(() => ripple.remove(), 650);
2928
+ });
2929
+ });
2930
+ }
2931
+
2932
+ setupRipples();
2933
+ document.addEventListener('astro:page-load', setupRipples);
2934
+ </script>
2935
+ `,
2936
+ usage: `<RippleButton variant="primary" size="md">Click Ripple</RippleButton>`,
2937
+ },
2938
+ {
2939
+ slug: 'neon-border-button',
2940
+ name: 'Neon Border Button',
2941
+ description: 'A futuristic cyber button with rotating conical neon laser border and glow reflection.',
2942
+ category: 'buttons',
2943
+ tags: ["button","neon","cyberpunk","glow","border"],
2944
+ featured: true,
2945
+ code: `---
2946
+ /**
2947
+ * NeonBorderButton — Futuristic button wrapped in an animated rotating neon conic-gradient border.
2948
+ *
2949
+ * @param {string} color - Primary neon hue (e.g. '#fbbf24', '#38bdf8', '#a855f7'). Default is '#d4a017'.
2950
+ * @param {'sm' | 'md' | 'lg'} size - Button size variant. Default is 'md'.
2951
+ * @param {string} href - Optional link destination.
2952
+ * @param {boolean} disabled - Whether the button is disabled.
2953
+ * @param {string} className - Additional CSS class names.
2954
+ */
2955
+ interface Props {
2956
+ color?: string;
2957
+ size?: 'sm' | 'md' | 'lg';
2958
+ href?: string;
2959
+ disabled?: boolean;
2960
+ className?: string;
2961
+ }
2962
+
2963
+ const {
2964
+ color = '#d4a017',
2965
+ size = 'md',
2966
+ href,
2967
+ disabled = false,
2968
+ className = '',
2969
+ } = Astro.props;
2970
+
2971
+ const Tag = href ? 'a' : 'button';
2972
+ ---
2973
+
2974
+ <Tag
2975
+ href={href}
2976
+ disabled={!href && disabled ? true : undefined}
2977
+ aria-disabled={disabled ? 'true' : undefined}
2978
+ class:list={['neon-border-btn', \`neon-border-btn--\${size}\`, className]}
2979
+ style={\`--neon-accent: \${color};\`}
2980
+ >
2981
+ <span class="neon-border-btn__glow" aria-hidden="true"></span>
2982
+ <span class="neon-border-btn__border" aria-hidden="true"></span>
2983
+ <span class="neon-border-btn__content">
2984
+ <slot />
2985
+ </span>
2986
+ </Tag>
2987
+
2988
+ <style>
2989
+ .neon-border-btn {
2990
+ --neon-speed: 4s;
2991
+ position: relative;
2992
+ display: inline-flex;
2993
+ align-items: center;
2994
+ justify-content: center;
2995
+ text-decoration: none;
2996
+ border: none;
2997
+ background: transparent;
2998
+ border-radius: var(--r-md, 12px);
2999
+ cursor: pointer;
3000
+ user-select: none;
3001
+ transition: transform 0.25s ease;
3002
+ }
3003
+
3004
+ .neon-border-btn:hover {
3005
+ transform: translateY(-2px);
3006
+ }
3007
+
3008
+ .neon-border-btn:focus-visible {
3009
+ outline: 2px solid var(--neon-accent);
3010
+ outline-offset: 4px;
3011
+ }
3012
+
3013
+ .neon-border-btn[disabled],
3014
+ .neon-border-btn[aria-disabled="true"] {
3015
+ opacity: 0.45;
3016
+ cursor: not-allowed;
3017
+ pointer-events: none;
3018
+ }
3019
+
3020
+ /* Sizes */
3021
+ .neon-border-btn--sm .neon-border-btn__content {
3022
+ padding: 0.45rem 1rem;
3023
+ font-size: 0.8rem;
3024
+ }
3025
+ .neon-border-btn--md .neon-border-btn__content {
3026
+ padding: 0.75rem 1.6rem;
3027
+ font-size: 0.92rem;
3028
+ }
3029
+ .neon-border-btn--lg .neon-border-btn__content {
3030
+ padding: 1rem 2.2rem;
3031
+ font-size: 1.05rem;
3032
+ }
3033
+
3034
+ /* Border track & animated beam */
3035
+ .neon-border-btn__border {
3036
+ position: absolute;
3037
+ inset: 0;
3038
+ border-radius: inherit;
3039
+ padding: 1.5px;
3040
+ background: conic-gradient(
3041
+ from 0deg,
3042
+ transparent 0deg,
3043
+ transparent 90deg,
3044
+ var(--neon-accent) 180deg,
3045
+ transparent 270deg,
3046
+ transparent 360deg
3047
+ );
3048
+ mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
3049
+ -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
3050
+ mask-composite: exclude;
3051
+ -webkit-mask-composite: xor;
3052
+ animation: neon-spin var(--neon-speed) linear infinite;
3053
+ pointer-events: none;
3054
+ }
3055
+
3056
+ .neon-border-btn__glow {
3057
+ position: absolute;
3058
+ inset: -3px;
3059
+ border-radius: inherit;
3060
+ background: radial-gradient(circle, var(--neon-accent), transparent 70%);
3061
+ opacity: 0;
3062
+ filter: blur(14px);
3063
+ transition: opacity 0.3s ease;
3064
+ pointer-events: none;
3065
+ }
3066
+
3067
+ .neon-border-btn:hover .neon-border-btn__glow {
3068
+ opacity: 0.5;
3069
+ }
3070
+
3071
+ .neon-border-btn__content {
3072
+ position: relative;
3073
+ z-index: 2;
3074
+ display: inline-flex;
3075
+ align-items: center;
3076
+ gap: var(--sp-2, 0.5rem);
3077
+ width: 100%;
3078
+ height: 100%;
3079
+ background: rgba(10, 10, 12, 0.95);
3080
+ border-radius: inherit;
3081
+ color: var(--c-text-1, #ffffff);
3082
+ font-family: var(--font-sans, system-ui, sans-serif);
3083
+ font-weight: 700;
3084
+ letter-spacing: 0.03em;
3085
+ backdrop-filter: blur(16px);
3086
+ transition: background 0.2s ease, color 0.2s ease;
3087
+ }
3088
+
3089
+ .neon-border-btn:hover .neon-border-btn__content {
3090
+ background: rgba(16, 16, 20, 0.98);
3091
+ color: var(--neon-accent);
3092
+ }
3093
+
3094
+ @keyframes neon-spin {
3095
+ to {
3096
+ transform: rotate(360deg);
3097
+ }
3098
+ }
3099
+
3100
+ @media (prefers-reduced-motion: reduce) {
3101
+ .neon-border-btn__border {
3102
+ animation: none;
3103
+ background: var(--neon-accent);
3104
+ }
3105
+ }
3106
+ </style>
3107
+ `,
3108
+ usage: `<NeonBorderButton variant="cyan" size="md">Neon Action</NeonBorderButton>`,
3109
+ },
3110
+ {
3111
+ slug: 'slide-reveal-button',
3112
+ name: 'Slide Reveal Button',
3113
+ description: 'A dual-state button that slides primary text out and reveals hidden secondary action text or icons.',
3114
+ category: 'buttons',
3115
+ tags: ["button","slide","reveal","animation","action"],
3116
+ featured: false,
3117
+ code: `---
3118
+ /**
3119
+ * SlideRevealButton — Interactive action button with a sliding icon drawer and kinetic typography transition.
3120
+ *
3121
+ * @param {'primary' | 'secondary' | 'accent'} variant - Visual tone variant. Default is 'primary'.
3122
+ * @param {'sm' | 'md' | 'lg'} size - Button size variant. Default is 'md'.
3123
+ * @param {string} href - Optional link destination.
3124
+ * @param {string} icon - SVG or character icon. Default is '→'.
3125
+ * @param {boolean} disabled - Whether the button is disabled.
3126
+ * @param {string} className - Additional CSS class names.
3127
+ */
3128
+ interface Props {
3129
+ variant?: 'primary' | 'secondary' | 'accent';
3130
+ size?: 'sm' | 'md' | 'lg';
3131
+ href?: string;
3132
+ icon?: string;
3133
+ disabled?: boolean;
3134
+ className?: string;
3135
+ }
3136
+
3137
+ const {
3138
+ variant = 'primary',
3139
+ size = 'md',
3140
+ href,
3141
+ icon = '→',
3142
+ disabled = false,
3143
+ className = '',
3144
+ } = Astro.props;
3145
+
3146
+ const Tag = href ? 'a' : 'button';
3147
+ ---
3148
+
3149
+ <Tag
3150
+ href={href}
3151
+ disabled={!href && disabled ? true : undefined}
3152
+ aria-disabled={disabled ? 'true' : undefined}
3153
+ class:list={['slide-reveal-btn', \`slide-reveal-btn--\${variant}\`, \`slide-reveal-btn--\${size}\`, className]}
3154
+ >
3155
+ <span class="slide-reveal-btn__text">
3156
+ <slot />
3157
+ </span>
3158
+ <span class="slide-reveal-btn__icon" aria-hidden="true">
3159
+ {icon}
3160
+ </span>
3161
+ </Tag>
3162
+
3163
+ <style>
3164
+ .slide-reveal-btn {
3165
+ position: relative;
3166
+ display: inline-flex;
3167
+ align-items: center;
3168
+ justify-content: center;
3169
+ overflow: hidden;
3170
+ font-family: var(--font-sans, system-ui, sans-serif);
3171
+ font-weight: 700;
3172
+ text-decoration: none;
3173
+ border-radius: var(--r-md, 12px);
3174
+ border: 1px solid rgba(255, 255, 255, 0.12);
3175
+ cursor: pointer;
3176
+ user-select: none;
3177
+ transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
3178
+ }
3179
+
3180
+ .slide-reveal-btn:focus-visible {
3181
+ outline: 2px solid var(--c-primary-light, #d4a017);
3182
+ outline-offset: 3px;
3183
+ }
3184
+
3185
+ .slide-reveal-btn[disabled],
3186
+ .slide-reveal-btn[aria-disabled="true"] {
3187
+ opacity: 0.5;
3188
+ cursor: not-allowed;
3189
+ pointer-events: none;
3190
+ }
3191
+
3192
+ /* Variants */
3193
+ .slide-reveal-btn--primary {
3194
+ background: rgba(184, 134, 11, 0.15);
3195
+ color: var(--c-accent, #fef3c7);
3196
+ border-color: rgba(184, 134, 11, 0.35);
3197
+ }
3198
+ .slide-reveal-btn--primary:hover {
3199
+ background: linear-gradient(135deg, #b8860b, #d4a017);
3200
+ color: #050505;
3201
+ box-shadow: 0 8px 24px rgba(184, 134, 11, 0.3);
3202
+ }
3203
+
3204
+ .slide-reveal-btn--secondary {
3205
+ background: rgba(255, 255, 255, 0.04);
3206
+ color: var(--c-text-1, #ffffff);
3207
+ backdrop-filter: blur(12px);
3208
+ }
3209
+ .slide-reveal-btn--secondary:hover {
3210
+ background: rgba(255, 255, 255, 0.1);
3211
+ border-color: rgba(255, 255, 255, 0.25);
3212
+ }
3213
+
3214
+ .slide-reveal-btn--accent {
3215
+ background: rgba(56, 189, 248, 0.12);
3216
+ color: #7dd3fc;
3217
+ border-color: rgba(56, 189, 248, 0.3);
3218
+ }
3219
+ .slide-reveal-btn--accent:hover {
3220
+ background: #0284c7;
3221
+ color: #ffffff;
3222
+ box-shadow: 0 8px 24px rgba(56, 189, 248, 0.25);
3223
+ }
3224
+
3225
+ /* Sizes */
3226
+ .slide-reveal-btn--sm {
3227
+ padding: 0.45rem 1.1rem;
3228
+ font-size: 0.8rem;
3229
+ }
3230
+ .slide-reveal-btn--sm .slide-reveal-btn__icon {
3231
+ font-size: 0.9rem;
3232
+ }
3233
+
3234
+ .slide-reveal-btn--md {
3235
+ padding: 0.75rem 1.6rem;
3236
+ font-size: 0.92rem;
3237
+ }
3238
+ .slide-reveal-btn--md .slide-reveal-btn__icon {
3239
+ font-size: 1.05rem;
3240
+ }
3241
+
3242
+ .slide-reveal-btn--lg {
3243
+ padding: 1rem 2.2rem;
3244
+ font-size: 1.05rem;
3245
+ }
3246
+ .slide-reveal-btn--lg .slide-reveal-btn__icon {
3247
+ font-size: 1.25rem;
3248
+ }
3249
+
3250
+ /* Text & sliding icon */
3251
+ .slide-reveal-btn__text {
3252
+ display: inline-block;
3253
+ transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1);
3254
+ }
3255
+
3256
+ .slide-reveal-btn__icon {
3257
+ display: inline-flex;
3258
+ align-items: center;
3259
+ justify-content: center;
3260
+ position: absolute;
3261
+ right: 1.2rem;
3262
+ opacity: 0;
3263
+ transform: translateX(-12px);
3264
+ transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.25s ease;
3265
+ }
3266
+
3267
+ .slide-reveal-btn:hover .slide-reveal-btn__text {
3268
+ transform: translateX(-12px);
3269
+ }
3270
+
3271
+ .slide-reveal-btn:hover .slide-reveal-btn__icon {
3272
+ opacity: 1;
3273
+ transform: translateX(0);
3274
+ }
3275
+ </style>
3276
+ `,
3277
+ usage: `<SlideRevealButton label="Download File" revealText="Start (.zip)" variant="primary" />`,
3278
+ },
3279
+ ];