astro-component-kit 0.4.0 → 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.
Files changed (208) hide show
  1. package/README.md +139 -27
  2. package/bin/cli.js +67 -0
  3. package/bin/installer.js +179 -0
  4. package/package.json +35 -5
  5. package/src/components/index.ts +2 -0
  6. package/src/components/lib/Galaxy3D.astro +249 -137
  7. package/src/components/lib/app/AppSidebar.astro +249 -0
  8. package/src/components/lib/app/AppTopbar.astro +284 -0
  9. package/src/components/lib/app/ArrivalsBoard.astro +176 -0
  10. package/src/components/lib/app/AuthCard.astro +213 -0
  11. package/src/components/lib/app/DataTable.astro +346 -0
  12. package/src/components/lib/app/GuestProfileCard.astro +133 -0
  13. package/src/components/lib/app/HousekeepingBoard.astro +189 -0
  14. package/src/components/lib/app/PageHeader.astro +157 -0
  15. package/src/components/lib/app/PlanUsageCard.astro +112 -0
  16. package/src/components/lib/app/RoomRack.astro +207 -0
  17. package/src/components/lib/app/SettingsSection.astro +96 -0
  18. package/src/components/lib/app/SwitchRow.astro +71 -0
  19. package/src/components/lib/app/TeamMembers.astro +190 -0
  20. package/src/components/lib/app/app-icons.ts +52 -0
  21. package/src/components/lib/app/index.ts +19 -0
  22. package/src/components/lib/charts/AreaChart.astro +4 -4
  23. package/src/components/lib/charts/BarChart.astro +3 -3
  24. package/src/components/lib/charts/ChartFrame.astro +10 -10
  25. package/src/components/lib/charts/DonutChart.astro +2 -2
  26. package/src/components/lib/charts/DumbbellChart.astro +1 -1
  27. package/src/components/lib/charts/ForecastLineChart.astro +1 -1
  28. package/src/components/lib/charts/GaugeChart.astro +1 -1
  29. package/src/components/lib/charts/HeatmapChart.astro +1 -1
  30. package/src/components/lib/charts/HorizontalBarChart.astro +2 -2
  31. package/src/components/lib/charts/KpiStatTile.astro +4 -4
  32. package/src/components/lib/charts/LinearProgressGauge.astro +4 -4
  33. package/src/components/lib/charts/MetricDeltaTable.astro +3 -3
  34. package/src/components/lib/charts/RadarMetricChart.astro +4 -4
  35. package/src/components/lib/charts/RadialBarChart.astro +1 -1
  36. package/src/components/lib/charts/RetentionCohortTable.astro +2 -2
  37. package/src/components/lib/charts/TreemapChart.astro +1 -1
  38. package/src/components/lib/ecommerce/CartDrawer.astro +483 -0
  39. package/src/components/lib/ecommerce/CartPageList.astro +290 -0
  40. package/src/components/lib/ecommerce/CartToast.astro +276 -0
  41. package/src/components/lib/ecommerce/CategoryTiles.astro +143 -0
  42. package/src/components/lib/ecommerce/CheckoutForm.astro +423 -0
  43. package/src/components/lib/ecommerce/CountdownBanner.astro +230 -0
  44. package/src/components/lib/ecommerce/FilterBar.astro +278 -0
  45. package/src/components/lib/ecommerce/FilterSidebar.astro +405 -0
  46. package/src/components/lib/ecommerce/LiveActivityToast.astro +213 -0
  47. package/src/components/lib/ecommerce/OrderSummary.astro +295 -0
  48. package/src/components/lib/ecommerce/ProductBuyBox.astro +247 -0
  49. package/src/components/lib/ecommerce/ProductCardMinimal.astro +229 -0
  50. package/src/components/lib/ecommerce/ProductGallery.astro +145 -0
  51. package/src/components/lib/ecommerce/ProductGrid.astro +118 -0
  52. package/src/components/lib/ecommerce/ProductMedia.astro +113 -0
  53. package/src/components/lib/ecommerce/ProductRating.astro +78 -0
  54. package/src/components/lib/ecommerce/ProductToolbar.astro +93 -0
  55. package/src/components/lib/ecommerce/PromoBar.astro +145 -0
  56. package/src/components/lib/ecommerce/StockIndicator.astro +127 -0
  57. package/src/components/lib/ecommerce/StoreHeader.astro +446 -0
  58. package/src/components/lib/ecommerce/StoreHero.astro +260 -0
  59. package/src/components/lib/ecommerce/StoreProductCard.astro +307 -0
  60. package/src/components/lib/ecommerce/TrustBadges.astro +101 -0
  61. package/src/components/lib/ecommerce/VariantPicker.astro +145 -0
  62. package/src/components/lib/ecommerce/cart-store.ts +151 -0
  63. package/src/components/lib/ecommerce/catalog-filter.ts +62 -0
  64. package/src/components/lib/ecommerce/index.ts +34 -0
  65. package/src/components/lib/ecommerce/product-icons.ts +28 -0
  66. package/src/components/lib/ecommerce/types.ts +70 -0
  67. package/src/components/lib/forms/AnimatedInput.astro +4 -2
  68. package/src/components/lib/forms/CustomRadioGroup.astro +3 -1
  69. package/src/components/lib/index.ts +2 -0
  70. package/src/components/lib/layout/BentoGrid3.astro +6 -4
  71. package/src/components/lib/layout/CenterFixedBox.astro +2 -2
  72. package/src/components/lib/layout/CenteredContainer.astro +4 -2
  73. package/src/components/lib/layout/ClusterHorizontal.astro +2 -2
  74. package/src/components/lib/layout/FeatureHero.astro +4 -2
  75. package/src/components/lib/layout/FooterCenteredMinimal.astro +4 -2
  76. package/src/components/lib/layout/FullBleedSection.astro +2 -2
  77. package/src/components/lib/layout/GalleryGrid4.astro +2 -2
  78. package/src/components/lib/layout/HolyGrailGrid.astro +11 -9
  79. package/src/components/lib/layout/LandingSectionSplit.astro +5 -3
  80. package/src/components/lib/layout/MasonryGrid.astro +2 -2
  81. package/src/components/lib/layout/MinimalFooterGrid.astro +1 -1
  82. package/src/components/lib/layout/ScrollSnapSection.astro +1 -1
  83. package/src/components/lib/layout/SidebarLayoutWrapper.astro +4 -2
  84. package/src/components/lib/layout/SplitScreenLayout.astro +3 -3
  85. package/src/components/lib/layout/StepLayoutVertical.astro +1 -1
  86. package/src/components/lib/layout/StickySidebarWrapper.astro +4 -2
  87. package/src/components/lib/sections/AgencyContactBrief.astro +194 -0
  88. package/src/components/lib/sections/AgencyFooterCta.astro +94 -0
  89. package/src/components/lib/sections/AgencyHeader.astro +161 -0
  90. package/src/components/lib/sections/AgencyHeroKinetic.astro +172 -0
  91. package/src/components/lib/sections/AgencyServicesList.astro +113 -0
  92. package/src/components/lib/sections/AgencyTeamGrid.astro +111 -0
  93. package/src/components/lib/sections/AgencyWorkGrid.astro +147 -0
  94. package/src/components/lib/sections/BlogGridSection.astro +9 -7
  95. package/src/components/lib/sections/ButtonsShowcase.astro +20 -18
  96. package/src/components/lib/sections/CardsShowcase.astro +28 -26
  97. package/src/components/lib/sections/CareersOpenings.astro +10 -8
  98. package/src/components/lib/sections/ChangelogSection.astro +20 -7
  99. package/src/components/lib/sections/ContactGlassForm.astro +10 -8
  100. package/src/components/lib/sections/ContactSplitMap.astro +11 -9
  101. package/src/components/lib/sections/CtaAppDownload.astro +13 -11
  102. package/src/components/lib/sections/CtaGlowBanner.astro +12 -10
  103. package/src/components/lib/sections/CtaNewsletterSection.astro +13 -11
  104. package/src/components/lib/sections/CtaSplitImage.astro +13 -11
  105. package/src/components/lib/sections/DataShowcase.astro +23 -21
  106. package/src/components/lib/sections/FaqAccordionSection.astro +8 -8
  107. package/src/components/lib/sections/FaqTwoColumn.astro +9 -7
  108. package/src/components/lib/sections/FeatureAlternatingRows.astro +10 -8
  109. package/src/components/lib/sections/FeatureBentoGrid.astro +13 -11
  110. package/src/components/lib/sections/FeatureIconGrid.astro +6 -6
  111. package/src/components/lib/sections/FeatureTabsShowcase.astro +13 -11
  112. package/src/components/lib/sections/FeatureTimelineFlow.astro +8 -6
  113. package/src/components/lib/sections/FeedbackShowcase.astro +15 -13
  114. package/src/components/lib/sections/FooterMegaSection.astro +13 -11
  115. package/src/components/lib/sections/FormsShowcase.astro +22 -20
  116. package/src/components/lib/sections/HeroDashboardPreview.astro +11 -9
  117. package/src/components/lib/sections/HeroGlass.astro +22 -20
  118. package/src/components/lib/sections/HeroGradient.astro +15 -13
  119. package/src/components/lib/sections/HeroMinimalCentered.astro +10 -10
  120. package/src/components/lib/sections/HeroShowcaseSplit.astro +18 -16
  121. package/src/components/lib/sections/HeroSplit.astro +13 -11
  122. package/src/components/lib/sections/HeroTerminal.astro +14 -12
  123. package/src/components/lib/sections/HeroWaitlist.astro +10 -8
  124. package/src/components/lib/sections/HowItWorksSteps.astro +12 -10
  125. package/src/components/lib/sections/IntegrationsGrid.astro +7 -7
  126. package/src/components/lib/sections/LayoutShowcase.astro +21 -19
  127. package/src/components/lib/sections/LogoCloudSection.astro +4 -4
  128. package/src/components/lib/sections/LogoWallGrid.astro +6 -4
  129. package/src/components/lib/sections/NavigationShowcase.astro +15 -13
  130. package/src/components/lib/sections/OverlaysShowcase.astro +21 -19
  131. package/src/components/lib/sections/PricingComparisonMatrix.astro +15 -13
  132. package/src/components/lib/sections/PricingSinglePlan.astro +13 -11
  133. package/src/components/lib/sections/PricingTierCards.astro +11 -9
  134. package/src/components/lib/sections/PricingToggleCards.astro +13 -13
  135. package/src/components/lib/sections/ShopCategoryHero.astro +103 -0
  136. package/src/components/lib/sections/ShopCollectionMosaic.astro +131 -0
  137. package/src/components/lib/sections/ShopFooterWordmark.astro +100 -0
  138. package/src/components/lib/sections/ShopHeaderEditorial.astro +396 -0
  139. package/src/components/lib/sections/ShopHeroEditorial.astro +151 -0
  140. package/src/components/lib/sections/ShopLookbook.astro +250 -0
  141. package/src/components/lib/sections/ShopNewsletterEditorial.astro +128 -0
  142. package/src/components/lib/sections/ShopProductRail.astro +150 -0
  143. package/src/components/lib/sections/ShopReviewsEditorial.astro +133 -0
  144. package/src/components/lib/sections/ShopValuesMarquee.astro +91 -0
  145. package/src/components/lib/sections/StatsCounterBand.astro +9 -7
  146. package/src/components/lib/sections/StatsDashboardSection.astro +4 -2
  147. package/src/components/lib/sections/StatsMetricShowcase.astro +9 -7
  148. package/src/components/lib/sections/TeamGridSection.astro +12 -10
  149. package/src/components/lib/sections/TestimonialCarousel.astro +10 -8
  150. package/src/components/lib/sections/TestimonialFeatured.astro +7 -5
  151. package/src/components/lib/sections/TestimonialGridSection.astro +7 -7
  152. package/src/components/lib/sections/TestimonialWallGlass.astro +11 -9
  153. package/src/components/lib/sections/TypographyShowcase.astro +10 -8
  154. package/src/components/lib/sections/index.ts +10 -0
  155. package/src/components/lib/templates/ecommerce/StoreCategoryPage.astro +160 -0
  156. package/src/components/lib/templates/ecommerce/StoreCheckoutPage.astro +98 -0
  157. package/src/components/lib/templates/ecommerce/StoreHomePage.astro +191 -0
  158. package/src/components/lib/templates/ecommerce/StoreLayout.astro +169 -0
  159. package/src/components/lib/templates/ecommerce/StoreProductPage.astro +165 -0
  160. package/src/components/lib/templates/ecommerce/data.ts +187 -0
  161. package/src/components/lib/templates/ecommerce/index.ts +14 -0
  162. package/src/components/lib/templates/ecommerce-editorial/EditorialBagPage.astro +52 -0
  163. package/src/components/lib/templates/ecommerce-editorial/EditorialCheckoutPage.astro +123 -0
  164. package/src/components/lib/templates/ecommerce-editorial/EditorialHomePage.astro +102 -0
  165. package/src/components/lib/templates/ecommerce-editorial/EditorialLayout.astro +216 -0
  166. package/src/components/lib/templates/ecommerce-editorial/EditorialProductPage.astro +205 -0
  167. package/src/components/lib/templates/ecommerce-editorial/EditorialShopPage.astro +112 -0
  168. package/src/components/lib/templates/ecommerce-editorial/data.ts +125 -0
  169. package/src/components/lib/templates/ecommerce-editorial/index.ts +16 -0
  170. package/src/components/lib/templates/saas-hotel/AppLayout.astro +83 -0
  171. package/src/components/lib/templates/saas-hotel/DashboardPage.astro +66 -0
  172. package/src/components/lib/templates/saas-hotel/GuestsPage.astro +65 -0
  173. package/src/components/lib/templates/saas-hotel/LandingPage.astro +213 -0
  174. package/src/components/lib/templates/saas-hotel/ReportsPage.astro +63 -0
  175. package/src/components/lib/templates/saas-hotel/ReservationsPage.astro +49 -0
  176. package/src/components/lib/templates/saas-hotel/RoomsPage.astro +48 -0
  177. package/src/components/lib/templates/saas-hotel/SettingsPage.astro +115 -0
  178. package/src/components/lib/templates/saas-hotel/SignInPage.astro +50 -0
  179. package/src/components/lib/templates/saas-hotel/data.ts +229 -0
  180. package/src/components/lib/templates/saas-hotel/index.ts +19 -0
  181. package/src/components/lib/templates/saas-hotel/lobby-theme.css +90 -0
  182. package/src/data/registry/app.ts +93 -0
  183. package/src/data/registry/buttons.ts +3279 -0
  184. package/src/data/registry/cards.ts +1753 -0
  185. package/src/data/registry/charts-svg.ts +557 -0
  186. package/src/data/registry/charts.ts +500 -0
  187. package/src/data/registry/data.ts +986 -0
  188. package/src/data/registry/ecommerce.ts +269 -0
  189. package/src/data/registry/feedback.ts +1824 -0
  190. package/src/data/registry/forms.ts +2513 -0
  191. package/src/data/registry/index.ts +64 -0
  192. package/src/data/registry/layout.ts +206 -0
  193. package/src/data/registry/navigation.ts +936 -0
  194. package/src/data/registry/overlays.ts +1133 -0
  195. package/src/data/registry/sections-extra.ts +397 -0
  196. package/src/data/registry/sections.ts +361 -0
  197. package/src/data/registry/sources.ts +41 -0
  198. package/src/data/registry/templates.ts +414 -0
  199. package/src/data/registry/types.ts +30 -0
  200. package/src/data/registry/typography.ts +777 -0
  201. package/src/mcp/http-server.ts +324 -0
  202. package/src/mcp/index.ts +67 -0
  203. package/src/mcp/installer.ts +178 -0
  204. package/src/mcp/registry-adapter.ts +278 -0
  205. package/src/mcp/stdio-server.ts +11 -0
  206. package/src/mcp/tools.ts +247 -0
  207. package/src/mcp/types.ts +53 -0
  208. package/src/styles/global.css +95 -0
@@ -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
+ ];