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,1824 @@
1
+ // src/data/registry/feedback.ts
2
+ import type { ComponentDoc } from './types';
3
+
4
+ export const feedback: ComponentDoc[] = [
5
+ {
6
+ slug: 'animated-checkmark',
7
+ name: 'Success Checkmark',
8
+ description: 'Animated SVG success icon.',
9
+ category: 'feedback',
10
+ tags: ["icon","success"],
11
+ featured: true,
12
+ code: `---
13
+ /**
14
+ * AnimatedCheckmark — A decorative SVG checkmark with a circular fill and draw animation.
15
+ *
16
+ * @param {number|string} size - The width/height of the checkmark in pixels. Default is 56.
17
+ * @param {string} color - The primary color of the success state. Default is '#7ac142'.
18
+ */
19
+ interface Props {
20
+ size?: number | string;
21
+ color?: string;
22
+ }
23
+
24
+ const { size = 56, color = '#7ac142' } = Astro.props;
25
+ ---
26
+
27
+ <div class="checkmark-container" style={\`--check-size: \${size}px; --check-color: \${color};\`}>
28
+ <svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
29
+ <circle class="checkmark__circle" cx="26" cy="26" r="25" fill="none"/>
30
+ <path class="checkmark__check" fill="none" d="M14.1 27.2l7.1 7.2 16.7-16.8"/>
31
+ </svg>
32
+ </div>
33
+
34
+ <style>
35
+ .checkmark-container {
36
+ width: var(--check-size);
37
+ height: var(--check-size);
38
+ margin: var(--sp-4, 1rem) auto;
39
+ }
40
+
41
+ .checkmark {
42
+ width: 100%;
43
+ height: 100%;
44
+ border-radius: var(--r-full, 50%);
45
+ display: block;
46
+ stroke-width: 2;
47
+ stroke: #fff;
48
+ stroke-miterlimit: 10;
49
+ box-shadow: inset 0px 0px 0px transparent;
50
+ animation: checkmark-fill .4s ease-in-out .4s forwards, checkmark-scale .3s ease-in-out .9s both;
51
+ }
52
+
53
+ .checkmark__circle {
54
+ stroke-dasharray: 166;
55
+ stroke-dashoffset: 166;
56
+ stroke-width: 2;
57
+ stroke-miterlimit: 10;
58
+ stroke: var(--check-color);
59
+ fill: none;
60
+ animation: checkmark-stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
61
+ }
62
+
63
+ .checkmark__check {
64
+ transform-origin: 50% 50%;
65
+ stroke-dasharray: 48;
66
+ stroke-dashoffset: 48;
67
+ animation: checkmark-stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
68
+ }
69
+
70
+ @keyframes checkmark-stroke { 100% { stroke-dashoffset: 0; } }
71
+ @keyframes checkmark-fill { 100% { box-shadow: inset 0px 0px 0px 30px var(--check-color); } }
72
+ @keyframes checkmark-scale { 0%, 100% { transform: none; } 50% { transform: scale3d(1.1, 1.1, 1); } }
73
+ </style>
74
+ `,
75
+ usage: `<AnimatedCheckmark size={64} />`,
76
+ },
77
+ {
78
+ slug: 'badge',
79
+ name: 'Badge',
80
+ description: 'Versatile status tags.',
81
+ category: 'feedback',
82
+ tags: ["badge","tag"],
83
+ featured: true,
84
+ code: `---
85
+ /**
86
+ * Badge — A compact visual indicator for labels, categories, or status tags.
87
+ *
88
+ * @param {string} variant - Visual theme (default|success|warning|danger|info). Default is 'default'.
89
+ * @param {boolean} dot - If true, displays a small decorative circle inside the badge.
90
+ * @param {boolean} pulse - If true, adds a subtle breathing animation to the internal dot.
91
+ * @param {string} className - Optional additional CSS classes.
92
+ */
93
+ interface Props {
94
+ variant?: 'default' | 'success' | 'warning' | 'danger' | 'info';
95
+ dot?: boolean;
96
+ pulse?: boolean;
97
+ className?: string;
98
+ }
99
+
100
+ const { variant = 'default', dot = false, pulse = false, className = '' } = Astro.props;
101
+ ---
102
+
103
+ <span class:list={['badge', \`badge--\${variant}\`, className]}>
104
+ {dot && <span class:list={['badge__dot', { 'badge__dot--pulse': pulse }]}></span>}
105
+ <slot />
106
+ </span>
107
+
108
+ <style>
109
+ .badge {
110
+ display: inline-flex;
111
+ align-items: center;
112
+ gap: var(--sp-2, 0.4rem);
113
+ padding: var(--sp-1, 0.25rem) var(--sp-3, 0.7rem);
114
+ border-radius: var(--r-full, 100px);
115
+ font-size: 0.72rem;
116
+ font-weight: 700;
117
+ letter-spacing: 0.05em;
118
+ text-transform: uppercase;
119
+ white-space: nowrap;
120
+ border: 1px solid transparent;
121
+ transition: 0.2s;
122
+ }
123
+
124
+ .badge--default { background: rgba(148, 163, 184, 0.12); color: var(--c-text-2, #94a3b8); border-color: var(--c-border, rgba(148, 163, 184, 0.2)); }
125
+ .badge--success { background: rgba(52, 211, 153, 0.12); color: #34d399; border-color: rgba(52, 211, 153, 0.25); }
126
+ .badge--warning { background: rgba(251, 191, 36, 0.12); color: #fbbf24; border-color: rgba(251, 191, 36, 0.25); }
127
+ .badge--danger { background: rgba(248, 113, 113, 0.12); color: #f87171; border-color: rgba(248, 113, 113, 0.25); }
128
+ .badge--info { background: rgba(129, 140, 248, 0.12); color: var(--c-primary-light, #818cf8); border-color: rgba(129, 140, 248, 0.25); }
129
+
130
+ .badge__dot { width: 6px; height: 6px; border-radius: var(--r-full, 50%); background: currentColor; flex-shrink: 0; }
131
+ .badge__dot--pulse { animation: badge-dot-pulse 2s ease-in-out infinite; }
132
+
133
+ @keyframes badge-dot-pulse {
134
+ 0%, 100% { opacity: 1; transform: scale(1); }
135
+ 50% { opacity: 0.4; transform: scale(0.7); }
136
+ }
137
+ </style>
138
+ `,
139
+ usage: `---
140
+ import { Badge } from 'astro-component-kit';
141
+ ---
142
+
143
+ <div class="flex-row gap-2">
144
+ <Badge variant="success" dot pulse>System Online</Badge>
145
+ <Badge variant="warning">Maintenance</Badge>
146
+ <Badge variant="danger" dot>Critical Error</Badge>
147
+ <Badge variant="info">New Update</Badge>
148
+ </div>`,
149
+ },
150
+ {
151
+ slug: 'circle-progress',
152
+ name: 'Circle Gauge',
153
+ description: 'Circular percentage-of-completion chart.',
154
+ category: 'feedback',
155
+ tags: ["progress","circle"],
156
+ featured: true,
157
+ code: `---
158
+ /**
159
+ * CircleProgress — A circular chart indicating a percentage of completion.
160
+ *
161
+ * @param {number} percent - Completion percentage (0 to 100).
162
+ * @param {number} size - Pixel size of the square SVG container. Default is 80.
163
+ * @param {string} color - Primary gauge color. Default is 'var(--c-primary)'.
164
+ */
165
+ interface Props {
166
+ percent: number;
167
+ size?: number;
168
+ color?: string;
169
+ }
170
+
171
+ const { percent, size = 80, color = 'var(--c-primary, #6366f1)' } = Astro.props;
172
+
173
+ const radius = (size / 2) - 10;
174
+ const circum = 2 * Math.PI * radius;
175
+ const offset = circum - (Math.min(100, Math.max(0, percent)) / 100) * circum;
176
+ ---
177
+
178
+ <div class="circle-box" style={\`--gauge-size: \${size}px; --gauge-color: \${color};\`}>
179
+ <svg width={size} height={size}>
180
+ <circle class="circle-bg" cx={size/2} cy={size/2} r={radius} />
181
+ <circle
182
+ class="circle-val"
183
+ cx={size/2} cy={size/2} r={radius}
184
+ stroke-dasharray={circum}
185
+ stroke-dashoffset={offset}
186
+ stroke={color}
187
+ />
188
+ </svg>
189
+ <div class="circle-label" role="presentation">{percent}%</div>
190
+ </div>
191
+
192
+ <style>
193
+ .circle-box {
194
+ position: relative;
195
+ width: var(--gauge-size);
196
+ height: var(--gauge-size);
197
+ display: grid; place-items: center;
198
+ }
199
+
200
+ svg { transform: rotate(-90deg); }
201
+
202
+ circle { fill: transparent; stroke-width: 6; stroke-linecap: round; }
203
+
204
+ .circle-bg {
205
+ stroke: var(--c-bg-elev, rgba(255,255,255,0.05));
206
+ stroke-width: 5;
207
+ }
208
+
209
+ .circle-val {
210
+ transition: stroke-dashoffset 1s cubic-bezier(0.4, 0, 0.2, 1);
211
+ filter: drop-shadow(0 0 3px var(--gauge-color));
212
+ }
213
+
214
+ .circle-label {
215
+ position: absolute;
216
+ font-size: 0.85rem;
217
+ font-weight: 800;
218
+ color: var(--c-text-1, #fff);
219
+ font-family: inherit;
220
+ }
221
+ </style>
222
+ `,
223
+ usage: `<CircleProgress percent={75} />`,
224
+ },
225
+ {
226
+ slug: 'confetti-feedback',
227
+ name: 'Confetti Success',
228
+ description: 'Feedback with falling confetti celebration.',
229
+ category: 'feedback',
230
+ tags: ["feedback","animation"],
231
+ featured: true,
232
+ code: `---
233
+ /**
234
+ * ConfettiFeedback — A celebration component that triggers a falling confetti animation.
235
+ *
236
+ * @param {string} label - Text to display briefly before/during confetti. Default is "Success!".
237
+ * @param {string} color - Primary color of the confetti pieces. Default is 'var(--c-primary)'.
238
+ * @param {boolean} active - If true, starts the animation immediately. Default is true.
239
+ */
240
+ interface Props {
241
+ label?: string;
242
+ color?: string;
243
+ active?: boolean;
244
+ }
245
+
246
+ const { label = "Success!", color = 'var(--c-primary, #6366f1)', active = true } = Astro.props;
247
+ ---
248
+
249
+ <div class:list={["confetti-trigger", { "confetti-trigger--active": active }]}>
250
+ <span class="confetti-label">{label}</span>
251
+ <div class="confetti-container" aria-hidden="true" style={\`--confetti-color: \${color}\`}>
252
+ {Array.from({ length: 12 }).map((_, i) => (
253
+ <div
254
+ class="confetti-piece"
255
+ style={\`--l: \${Math.random() * 100}%; --d: \${Math.random() * 2}s; --r: \${Math.random() * 360}deg; --s: \${0.5 + Math.random()} \`}
256
+ ></div>
257
+ ))}
258
+ </div>
259
+ </div>
260
+
261
+ <style>
262
+ .confetti-trigger {
263
+ position: relative;
264
+ display: flex;
265
+ flex-direction: column;
266
+ align-items: center;
267
+ padding: var(--sp-4, 1rem);
268
+ width: fit-content;
269
+ overflow: hidden;
270
+ }
271
+
272
+ .confetti-label {
273
+ font-size: 1.25rem;
274
+ font-weight: 800;
275
+ color: var(--c-text-1, #fff);
276
+ z-index: 1;
277
+ text-shadow: 0 4px 10px rgba(0,0,0,0.3);
278
+ }
279
+
280
+ .confetti-container {
281
+ position: absolute;
282
+ inset: 0;
283
+ pointer-events: none;
284
+ opacity: 0;
285
+ transition: opacity 0.3s;
286
+ }
287
+
288
+ .confetti-trigger--active .confetti-container { opacity: 1; }
289
+
290
+ .confetti-piece {
291
+ position: absolute;
292
+ left: var(--l);
293
+ top: -20px;
294
+ width: 10px; height: 10px;
295
+ background: var(--confetti-color);
296
+ border-radius: 2px;
297
+ transform: rotate(var(--r)) scale(var(--s));
298
+ opacity: 0;
299
+ }
300
+
301
+ .confetti-trigger--active .confetti-piece {
302
+ animation: confetti-fall 2.5s linear infinite var(--d);
303
+ }
304
+
305
+ @keyframes confetti-fall {
306
+ 0% { transform: translateY(0) rotate(0deg) scale(var(--s)); opacity: 1; }
307
+ 25% { transform: translateY(40px) rotate(90deg) translateX(10px); }
308
+ 50% { transform: translateY(80px) rotate(180deg) translateX(-10px); }
309
+ 75% { transform: translateY(120px) rotate(270deg) translateX(5px); }
310
+ 100% { transform: translateY(160px) rotate(360deg); opacity: 0; }
311
+ }
312
+ </style>
313
+ `,
314
+ usage: `<ConfettiFeedback label="Mission Accomplished" />`,
315
+ },
316
+ {
317
+ slug: 'cyber-glitch-alert',
318
+ name: 'Glitch Alert',
319
+ description: 'Loud cyberpunk-style alert.',
320
+ category: 'feedback',
321
+ tags: ["alert","cyber"],
322
+ featured: true,
323
+ code: `---
324
+ /**
325
+ * CyberGlitchAlert — A loud, high-contrast alert box with a CSS glitch animation effect.
326
+ *
327
+ * @param {string} message - Primary alert text. Default is "CRITICAL ERROR".
328
+ * @param {string} accentColor - The secondary glitch shadow color. Default is '#00e0ff'.
329
+ */
330
+ interface Props {
331
+ message?: string;
332
+ accentColor?: string;
333
+ }
334
+
335
+ const { message = "CRITICAL ERROR", accentColor = "#00e0ff" } = Astro.props;
336
+ ---
337
+
338
+ <div class="glitch-alert" role="alert" style={\`--glitch-accent: \${accentColor}\`}>
339
+ <span class="glitch-text" data-text={message}>{message}</span>
340
+ </div>
341
+
342
+ <style>
343
+ .glitch-alert {
344
+ background: var(--c-primary, #ff003c);
345
+ padding: var(--sp-6, 1.5rem);
346
+ border: 3px solid var(--c-text-1, #fff);
347
+ position: relative;
348
+ overflow: hidden;
349
+ width: 100%;
350
+ box-sizing: border-box;
351
+ box-shadow: 8px 8px 0 rgba(0,0,0,0.5);
352
+ }
353
+
354
+ .glitch-text {
355
+ font-family: 'Courier New', Courier, monospace;
356
+ font-weight: 900;
357
+ color: var(--c-text-1, #fff);
358
+ font-size: 1.25rem;
359
+ display: block;
360
+ text-align: center;
361
+ position: relative;
362
+ letter-spacing: 0.1em;
363
+ }
364
+
365
+ .glitch-text::before,
366
+ .glitch-text::after {
367
+ content: attr(data-text);
368
+ position: absolute;
369
+ top: 0; left: 0;
370
+ width: 100%; height: 100%;
371
+ background: var(--c-primary, #ff003c);
372
+ }
373
+
374
+ .glitch-text::before {
375
+ left: 2px;
376
+ text-shadow: -2px 0 var(--glitch-accent);
377
+ clip: rect(44px, 450px, 56px, 0);
378
+ animation: glitch-anim 2s infinite linear alternate-reverse;
379
+ }
380
+
381
+ .glitch-text::after {
382
+ left: -2px;
383
+ text-shadow: -2px 0 #fff;
384
+ clip: rect(12px, 450px, 80px, 0);
385
+ animation: glitch-anim 3s infinite linear alternate-reverse;
386
+ }
387
+
388
+ @keyframes glitch-anim {
389
+ 0% { clip: rect(10px, 9999px, 30px, 0); }
390
+ 20% { clip: rect(40px, 9999px, 60px, 0); }
391
+ 40% { clip: rect(70px, 9999px, 10px, 0); }
392
+ 60% { clip: rect(20px, 9999px, 50px, 0); }
393
+ 80% { clip: rect(90px, 9999px, 20px, 0); }
394
+ 100% { clip: rect(50px, 9999px, 80px, 0); }
395
+ }
396
+ </style>
397
+ `,
398
+ usage: `<CyberGlitchAlert message="SYSTEM BREACH" />`,
399
+ },
400
+ {
401
+ slug: 'empty-state-card',
402
+ name: 'Empty State',
403
+ description: 'Placeholder for missing content or results.',
404
+ category: 'feedback',
405
+ tags: ["placeholder","empty"],
406
+ featured: true,
407
+ code: `---
408
+ /**
409
+ * EmptyStateCard — A placeholder component for empty views or failed searches.
410
+ *
411
+ * @param {string} icon - The emoji or symbol to display. Default is '📁'.
412
+ * @param {string} title - The primary bold headline. Default is 'No results found'.
413
+ * @param {string} message - Supporting descriptive text.
414
+ * @param {string} ctaLabel - Optional button text for an action.
415
+ */
416
+ interface Props {
417
+ icon?: string;
418
+ title?: string;
419
+ message?: string;
420
+ ctaLabel?: string;
421
+ }
422
+
423
+ const {
424
+ icon = "📁",
425
+ title = "No results found",
426
+ message = "Try adjusting your filters or adding a new item.",
427
+ ctaLabel
428
+ } = Astro.props;
429
+ ---
430
+
431
+ <div class="empty-state">
432
+ <div class="empty-state__icon" aria-hidden="true">{icon}</div>
433
+ <h4 class="empty-state__title">{title}</h4>
434
+ <p class="empty-state__message">{message}</p>
435
+ {ctaLabel && <button class="empty-state__btn" type="button">{ctaLabel}</button>}
436
+ </div>
437
+
438
+ <style>
439
+ .empty-state {
440
+ text-align: center;
441
+ padding: var(--sp-16, 4rem) var(--sp-8, 2rem);
442
+ border: 2px dashed var(--c-border, rgba(255,255,255,0.1));
443
+ border-radius: var(--r-xl, 24px);
444
+ color: var(--c-text-2, #94a3b8);
445
+ background: var(--c-bg-elev, rgba(255,255,255,0.02));
446
+ width: 100%;
447
+ box-sizing: border-box;
448
+ }
449
+
450
+ .empty-state__icon { font-size: 3.5rem; margin-bottom: var(--sp-4, 1rem); opacity: 0.6; filter: grayscale(0.5); }
451
+
452
+ .empty-state__title { color: var(--c-text-1, #fff); margin: 0; font-size: 1.25rem; font-weight: 700; }
453
+
454
+ .empty-state__message { font-size: 0.95rem; margin-top: 0.75rem; max-width: 320px; margin-inline: auto; line-height: 1.5; }
455
+
456
+ .empty-state__btn {
457
+ margin-top: var(--sp-6, 1.5rem);
458
+ background: var(--c-primary, #6366f1);
459
+ color: #fff; border: none;
460
+ padding: 0.6rem 1.4rem;
461
+ border-radius: var(--r-md, 8px);
462
+ font-weight: 700; cursor: pointer;
463
+ transition: transform 0.2s;
464
+ }
465
+
466
+ .empty-state__btn:hover { transform: translateY(-2px); filter: brightness(1.1); }
467
+ </style>
468
+ `,
469
+ usage: `<EmptyStateCard icon="🔍" title="No Matches" message="Try again." ctaLabel="Reset" />`,
470
+ },
471
+ {
472
+ slug: 'glass-alert',
473
+ name: 'Glass Alert',
474
+ description: 'Translucent feedback bar.',
475
+ category: 'feedback',
476
+ tags: ["alert","glass"],
477
+ featured: true,
478
+ code: `---
479
+ /**
480
+ * GlassAlert — A translucent feedback bar for displaying situational messages.
481
+ *
482
+ * @param {'info'|'success'|'warning'|'error'} type - The severity or nature of the alert. Default is 'info'.
483
+ * @param {string} title - Bold header text for the alert.
484
+ */
485
+ interface Props {
486
+ type?: 'info' | 'success' | 'warning' | 'error';
487
+ title: string;
488
+ }
489
+
490
+ const { type = 'info', title } = Astro.props;
491
+
492
+ const icons = {
493
+ info: "i",
494
+ success: "✓",
495
+ warning: "!",
496
+ error: "✕"
497
+ };
498
+ ---
499
+
500
+ <div class:list={['glass-alert', \`glass-alert--\${type}\` ]} role="alert">
501
+ <div class="glass-alert__icon" aria-hidden="true">{icons[type]}</div>
502
+ <div class="glass-alert__content">
503
+ <div class="glass-alert__title">{title}</div>
504
+ <div class="glass-alert__body"><slot /></div>
505
+ </div>
506
+ </div>
507
+
508
+ <style>
509
+ .glass-alert {
510
+ display: flex;
511
+ gap: var(--sp-4, 1rem);
512
+ padding: var(--sp-5, 1.25rem);
513
+ border-radius: var(--r-md, 16px);
514
+ border: 1px solid var(--c-border, rgba(255,255,255,0.1));
515
+ backdrop-filter: blur(12px);
516
+ background: var(--c-bg-elev, rgba(255,255,255,0.03));
517
+ width: 100%;
518
+ box-sizing: border-box;
519
+ }
520
+
521
+ .glass-alert--success { border-color: rgba(52, 211, 153, 0.3); background: rgba(52, 211, 153, 0.05); }
522
+ .glass-alert--warning { border-color: rgba(245, 158, 11, 0.3); background: rgba(245, 158, 11, 0.05); }
523
+ .glass-alert--error { border-color: rgba(239, 68, 68, 0.3); background: rgba(239, 68, 68, 0.05); }
524
+
525
+ .glass-alert__icon {
526
+ font-weight: 900;
527
+ background: rgba(255, 255, 255, 0.1);
528
+ width: 24px; height: 24px;
529
+ display: grid; place-items: center;
530
+ border-radius: var(--r-full, 50%);
531
+ font-size: 0.8rem;
532
+ flex-shrink: 0;
533
+ font-family: monospace;
534
+ color: var(--c-text-1, #fff);
535
+ }
536
+
537
+ .glass-alert--success .glass-alert__icon { background: #10b981; color: #fff; }
538
+ .glass-alert--warning .glass-alert__icon { background: #f59e0b; color: #fff; }
539
+ .glass-alert--error .glass-alert__icon { background: #ef4444; color: #fff; }
540
+
541
+ .glass-alert__title { font-weight: 700; color: var(--c-text-1, #fff); margin-bottom: 2px; font-size: 0.95rem; }
542
+ .glass-alert__body { font-size: 0.85rem; color: var(--c-text-2, #94a3b8); line-height: 1.5; }
543
+ </style>
544
+ `,
545
+ usage: `---
546
+ import { GlassAlert } from 'astro-component-kit';
547
+ ---
548
+
549
+ <div class="stack-v gap-4">
550
+ <GlassAlert type="info" title="System Update">
551
+ A new version of the dashboard is available. Please refresh to see changes.
552
+ </GlassAlert>
553
+
554
+ <GlassAlert type="success" title="Payment Successful">
555
+ Your subscription has been renewed for another month.
556
+ </GlassAlert>
557
+
558
+ <GlassAlert type="error" title="Connection Lost">
559
+ We couldn't reach the server. Checking your internet connection...
560
+ </GlassAlert>
561
+ </div>`,
562
+ },
563
+ {
564
+ slug: 'glass-modal',
565
+ name: 'Glass Modal',
566
+ description: 'Premium overlay confirmation box.',
567
+ category: 'feedback',
568
+ tags: ["modal","glass"],
569
+ featured: true,
570
+ code: `---
571
+ /**
572
+ * GlassModal — A premium overlay dialog for critical confirmations or focused interactions.
573
+ *
574
+ * @param {string} id - HTML ID for toggle and script and association.
575
+ * @param {string} title - Header text. Default is "Confirm Action".
576
+ * @param {string} confirmLabel - Label for the primary button. Default is "Confirm".
577
+ * @param {string} cancelLabel - Label for the secondary button. Default is "Cancel".
578
+ */
579
+ interface Props {
580
+ id: string;
581
+ title?: string;
582
+ confirmLabel?: string;
583
+ cancelLabel?: string;
584
+ }
585
+
586
+ const {
587
+ id,
588
+ title = "Confirm Action",
589
+ confirmLabel = "Confirm",
590
+ cancelLabel = "Cancel"
591
+ } = Astro.props;
592
+ ---
593
+
594
+ <dialog id={id} class="modal-dialog">
595
+ <div class="modal-box">
596
+ <div class="modal-header"><h3>{title}</h3></div>
597
+ <div class="modal-body">
598
+ <slot />
599
+ </div>
600
+ <div class="modal-footer">
601
+ <form method="dialog">
602
+ <button class="modal-btn modal-btn--sec" type="submit">{cancelLabel}</button>
603
+ </form>
604
+ <button class="modal-btn modal-btn--pri" data-modal-confirm>{confirmLabel}</button>
605
+ </div>
606
+ </div>
607
+ </dialog>
608
+
609
+ <style>
610
+ .modal-dialog {
611
+ background: transparent;
612
+ border: none;
613
+ padding: 0;
614
+ max-width: 400px;
615
+ width: 100%;
616
+ overflow: visible;
617
+ }
618
+
619
+ .modal-dialog::backdrop {
620
+ background: rgba(0, 0, 0, 0.4);
621
+ backdrop-filter: blur(8px);
622
+ animation: fade-in 0.3s forwards;
623
+ }
624
+
625
+ .modal-box {
626
+ background: var(--c-bg-elev, rgba(255,255,255,0.05));
627
+ border: 1px solid var(--c-border, rgba(255,255,255,0.1));
628
+ border-radius: var(--r-xl, 24px);
629
+ padding: var(--sp-8, 2rem);
630
+ box-shadow: 0 30px 100px rgba(0,0,0,0.6);
631
+ color: var(--c-text-1, #fff);
632
+ }
633
+
634
+ .animate-open { animation: scale-up 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards; }
635
+
636
+ .modal-header h3 { margin: 0 0 var(--sp-4, 1rem); color: var(--c-text-1, #fff); font-size: 1.25rem; }
637
+
638
+ .modal-body { color: var(--c-text-2, #94a3b8); font-size: 0.95rem; line-height: 1.6; }
639
+
640
+ .modal-footer { display: flex; justify-content: flex-end; gap: var(--sp-4, 1rem); margin-top: var(--sp-8, 2rem); }
641
+
642
+ .modal-btn {
643
+ padding: 0.7rem 1.4rem;
644
+ border-radius: var(--r-md, 12px);
645
+ border: none;
646
+ font-weight: 700;
647
+ cursor: pointer;
648
+ font-family: inherit;
649
+ transition: 0.2s;
650
+ }
651
+
652
+ .modal-btn--sec { background: transparent; color: var(--c-text-1, #fff); border: 1px solid var(--c-border, rgba(255,255,255,0.1)); }
653
+ .modal-btn--sec:hover { background: rgba(255,255,255,0.05); }
654
+
655
+ .modal-btn--pri {
656
+ background: var(--c-primary, #6366f1);
657
+ color: #fff;
658
+ box-shadow: 0 4px 15px rgba(99, 102, 241, 0.3);
659
+ }
660
+ .modal-btn--pri:hover { transform: translateY(-2px); filter: brightness(1.1); }
661
+
662
+ @keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
663
+ @keyframes scale-up { 0% { transform: scale(0.8); opacity: 0; } 100% { transform: scale(1); opacity: 1; } }
664
+ </style>
665
+
666
+ <script>
667
+ // Add simple show/hide logic helper if needed, but <dialog> works natively
668
+ document.querySelectorAll('dialog').forEach(modal => {
669
+ modal.addEventListener('click', (e) => {
670
+ if (e.target === modal) modal.close();
671
+ });
672
+ });
673
+ </script>
674
+ `,
675
+ usage: `<GlassModal id="m1" title="Delete Account" confirmLabel="Delete">Proceed with caution.</GlassModal>`,
676
+ },
677
+ {
678
+ slug: 'glass-progress',
679
+ name: 'Glass Progress',
680
+ description: 'Linear progress bar with glass effect.',
681
+ category: 'feedback',
682
+ tags: ["progress","bar"],
683
+ featured: true,
684
+ code: `---
685
+ /**
686
+ * GlassProgress — A horizontal linear progress bar with glassmorphism and gradient fill.
687
+ *
688
+ * @param {number} value - The current progress from 0 to 100.
689
+ * @param {string} label - Optional accessible label message.
690
+ */
691
+ interface Props {
692
+ value: number;
693
+ label?: string;
694
+ }
695
+
696
+ const { value, label = "Progress bar" } = Astro.props;
697
+ ---
698
+
699
+ <div
700
+ class="progress-container"
701
+ role="progressbar"
702
+ aria-valuenow={value}
703
+ aria-valuemin="0"
704
+ aria-valuemax="100"
705
+ aria-label={label}
706
+ >
707
+ <div class="progress-fill" style={\`width: \${Math.min(100, Math.max(0, value))}%\`}></div>
708
+ </div>
709
+
710
+ <style>
711
+ .progress-container {
712
+ height: 10px;
713
+ background: var(--c-bg-elev, rgba(255,255,255,0.05));
714
+ border-radius: var(--r-full, 10px);
715
+ overflow: hidden;
716
+ border: 1px solid var(--c-border, rgba(255,255,255,0.1));
717
+ width: 100%;
718
+ }
719
+
720
+ .progress-fill {
721
+ height: 100%;
722
+ background: linear-gradient(90deg, var(--c-primary, #6366f1), var(--c-primary-light, #c084fc));
723
+ border-radius: inherit;
724
+ transition: width 0.6s cubic-bezier(0.16, 1, 0.3, 1);
725
+ box-shadow: 0 0 10px rgba(99,102,241,0.3);
726
+ }
727
+ </style>
728
+ `,
729
+ usage: `---
730
+ import { GlassProgress } from 'astro-component-kit';
731
+ ---
732
+
733
+ <div class="progress-stack">
734
+ <div class="flex-between mb-2">
735
+ <span>Uploading assets...</span>
736
+ <span>75%</span>
737
+ </div>
738
+ <GlassProgress value={75} />
739
+
740
+ <div class="flex-between mt-4 mb-2">
741
+ <span>Processing data...</span>
742
+ <span>40%</span>
743
+ </div>
744
+ <GlassProgress value={40} />
745
+ </div>`,
746
+ },
747
+ {
748
+ slug: 'indicator-badge-minimal',
749
+ name: 'Notification Dot',
750
+ description: 'Corner-floating notification indicator.',
751
+ category: 'feedback',
752
+ tags: ["indicator","dot"],
753
+ featured: true,
754
+ code: `---
755
+ /**
756
+ * IndicatorBadgeMinimal — A small notification dot typically placed on the corner of an icon or avatar.
757
+ *
758
+ * @param {string} color - CSS color for the indicator dot. Default is 'var(--c-primary)'.
759
+ * @param {string} position - Corner position (top-right|top-left|bottom-right|bottom-left). Default is 'top-right'.
760
+ * @param {boolean} pulse - If true, adds a glowing pulse animation.
761
+ */
762
+ interface Props {
763
+ color?: string;
764
+ position?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
765
+ pulse?: boolean;
766
+ }
767
+
768
+ const {
769
+ color = 'var(--c-primary, #6366f1)',
770
+ position = 'top-right',
771
+ pulse = false
772
+ } = Astro.props;
773
+
774
+ const positionStyles = {
775
+ 'top-right': 'top: 0; right: 0; transform: translate(25%, -25%);',
776
+ 'top-left': 'top: 0; left: 0; transform: translate(-25%, -25%);',
777
+ 'bottom-right': 'bottom: 0; right: 0; transform: translate(25%, 25%);',
778
+ 'bottom-left': 'bottom: 0; left: 0; transform: translate(-25%, 25%);'
779
+ };
780
+ ---
781
+
782
+ <div class="indicator-wrap">
783
+ <slot />
784
+ <span
785
+ class:list={['dot-indicator', { 'dot-indicator--pulse': pulse }]}
786
+ style={\`background: \${color}; \${positionStyles[position]}\`}
787
+ aria-hidden="true"
788
+ ></span>
789
+ </div>
790
+
791
+ <style>
792
+ .indicator-wrap { position: relative; width: fit-content; display: inline-flex; }
793
+ .dot-indicator {
794
+ position: absolute;
795
+ width: 10px; height: 10px;
796
+ border: 2px solid var(--c-bg, #080b14);
797
+ border-radius: var(--r-full, 50%);
798
+ z-index: 10;
799
+ }
800
+
801
+ .dot-indicator--pulse::after {
802
+ content: "";
803
+ position: absolute;
804
+ inset: -2px;
805
+ border-radius: inherit;
806
+ border: 2px solid inherit;
807
+ background: inherit;
808
+ opacity: 0.6;
809
+ animation: indicator-pulse 2s infinite;
810
+ }
811
+
812
+ @keyframes indicator-pulse {
813
+ 0% { transform: scale(1); opacity: 0.6; }
814
+ 100% { transform: scale(2.5); opacity: 0; }
815
+ }
816
+ </style>
817
+ `,
818
+ usage: `<IndicatorBadgeMinimal color="#ef4444" position="top-right" pulse><button>Inbox</button></IndicatorBadgeMinimal>`,
819
+ },
820
+ {
821
+ slug: 'neon-spinner',
822
+ name: 'Neon Spinner',
823
+ description: 'Glow-style loading spinner.',
824
+ category: 'feedback',
825
+ tags: ["loading","spinner"],
826
+ featured: true,
827
+ code: `---
828
+ /**
829
+ * NeonSpinner — A sleek, glowing loading spinner with customizable color and size.
830
+ *
831
+ * @param {string} color - CSS color for the primary spinning segment. Default is 'var(--c-primary)'.
832
+ * @param {'sm'|'md'|'lg'} size - Predetermined size variant. Default is 'md'.
833
+ */
834
+ interface Props {
835
+ color?: string;
836
+ size?: 'sm' | 'md' | 'lg';
837
+ }
838
+
839
+ const { color = 'var(--c-primary, #6366f1)', size = 'md' } = Astro.props;
840
+
841
+ const dimensions = {
842
+ sm: '24px',
843
+ md: '40px',
844
+ lg: '64px'
845
+ };
846
+ ---
847
+
848
+ <div
849
+ class="neon-spinner"
850
+ style={\`--spinner-color: \${color}; --spinner-size: \${dimensions[size]};\`}
851
+ role="status"
852
+ aria-label="Loading"
853
+ ></div>
854
+
855
+ <style>
856
+ .neon-spinner {
857
+ width: var(--spinner-size);
858
+ height: var(--spinner-size);
859
+ border: 3px solid var(--c-bg-elev, #111);
860
+ border-top-color: var(--spinner-color);
861
+ border-radius: var(--r-full, 50%);
862
+ animation: neon-spin 0.8s linear infinite;
863
+ filter: drop-shadow(0 0 5px var(--spinner-color));
864
+ }
865
+
866
+ @keyframes neon-spin {
867
+ from { transform: rotate(0deg); }
868
+ to { transform: rotate(360deg); }
869
+ }
870
+ </style>
871
+ `,
872
+ usage: `<NeonSpinner size="md" />`,
873
+ },
874
+ {
875
+ slug: 'pulse-dot',
876
+ name: 'Pulse Dot',
877
+ description: 'Status label with pulsing effect.',
878
+ category: 'feedback',
879
+ tags: ["indicator","status"],
880
+ featured: true,
881
+ code: `---
882
+ /**
883
+ * PulseDot — A status indicator with a labeled text and a pulsing dot animation.
884
+ *
885
+ * @param {string} label - Text label (e.g. "System Active").
886
+ * @param {string} color - CSS color for the pulse and text. Default is '#34d399' (Success).
887
+ */
888
+ interface Props {
889
+ label: string;
890
+ color?: string;
891
+ }
892
+
893
+ const { label, color = '#34d399' } = Astro.props;
894
+ ---
895
+
896
+ <div class="status-wrap" style={\`--status-color: \${color};\`}>
897
+ <div class="pulse-dot" aria-hidden="true"></div>
898
+ <span class="status-text">{label}</span>
899
+ </div>
900
+
901
+ <style>
902
+ .status-wrap {
903
+ display: flex;
904
+ align-items: center;
905
+ gap: var(--sp-3, 0.6rem);
906
+ color: var(--status-color);
907
+ font-weight: 700;
908
+ font-size: 0.75rem;
909
+ text-transform: uppercase;
910
+ letter-spacing: 0.05em;
911
+ }
912
+
913
+ .pulse-dot {
914
+ width: 8px;
915
+ height: 8px;
916
+ background: var(--status-color);
917
+ border-radius: var(--r-full, 50%);
918
+ position: relative;
919
+ }
920
+
921
+ .pulse-dot::after {
922
+ content: "";
923
+ position: absolute;
924
+ inset: 0;
925
+ border-radius: 50%;
926
+ border: 2px solid var(--status-color);
927
+ animation: status-pulse 1.8s infinite;
928
+ }
929
+
930
+ @keyframes status-pulse {
931
+ 0% { transform: scale(1); opacity: 0.8; }
932
+ 100% { transform: scale(3.5); opacity: 0; }
933
+ }
934
+
935
+ .status-text { white-space: nowrap; }
936
+ </style>
937
+ `,
938
+ usage: `<PulseDot label="Recording" color="#ef4444" />`,
939
+ },
940
+ {
941
+ slug: 'rating-pill',
942
+ name: 'Rating Pill',
943
+ description: 'Compact numeric score badge.',
944
+ category: 'feedback',
945
+ tags: ["rating","score"],
946
+ featured: true,
947
+ code: `---
948
+ /**
949
+ * RatingPill — A compact badge displaying a numeric score or rating stars.
950
+ *
951
+ * @param {number} score - The rating value (0 to 5).
952
+ * @param {boolean} showStar - If true, displays a star icon suffix. Default is true.
953
+ */
954
+ interface Props {
955
+ score: number;
956
+ showStar?: boolean;
957
+ }
958
+
959
+ const { score, showStar = true } = Astro.props;
960
+
961
+ const status = score >= 4 ? 'high' : score >= 3 ? 'mid' : 'low';
962
+ ---
963
+
964
+ <div class:list={["rate-pill", \`rate-pill--\${status}\`]}>
965
+ <span class="rate-pill__val">{score.toFixed(1)}</span>
966
+ {showStar && <span class="rate-pill__star" aria-hidden="true">★</span>}
967
+ </div>
968
+
969
+ <style>
970
+ .rate-pill {
971
+ display: inline-flex;
972
+ align-items: center;
973
+ gap: 4px;
974
+ padding: var(--sp-1, 4px) var(--sp-3, 12px);
975
+ border-radius: var(--r-full, 100px);
976
+ font-weight: 800;
977
+ font-size: 0.75rem;
978
+ color: #fff;
979
+ width: fit-content;
980
+ box-shadow: 0 4px 10px rgba(0,0,0,0.1);
981
+ }
982
+
983
+ .rate-pill--high { background: #10b981; }
984
+ .rate-pill--mid { background: #f59e0b; }
985
+ .rate-pill--low { background: #ef4444; }
986
+
987
+ .rate-pill__star { font-size: 0.8rem; margin-top: -1px; }
988
+ .rate-pill__val { font-family: inherit; }
989
+ </style>
990
+ `,
991
+ usage: `<RatingPill score={4.8} />`,
992
+ },
993
+ {
994
+ slug: 'shake-error-group',
995
+ name: 'Shake Feedback',
996
+ description: 'Horizontal shake indicator for errors.',
997
+ category: 'feedback',
998
+ tags: ["error","feedback"],
999
+ featured: true,
1000
+ code: `---
1001
+ /**
1002
+ * ShakeError — A feedback container that shakes horizontally to indicate failure or invalid input.
1003
+ *
1004
+ * @param {string} message - The error message to display.
1005
+ * @param {boolean} active - If true, triggers the shake animation once.
1006
+ */
1007
+ interface Props {
1008
+ message: string;
1009
+ active?: boolean;
1010
+ }
1011
+
1012
+ const { message, active = false } = Astro.props;
1013
+ ---
1014
+
1015
+ <div class:list={["shake-box", { "shake-box--active": active }]} role="alert">
1016
+ <span class="shake-box__icon" aria-hidden="true">⚠️</span>
1017
+ <span class="shake-box__msg">{message}</span>
1018
+ </div>
1019
+
1020
+ <style>
1021
+ .shake-box {
1022
+ display: flex;
1023
+ align-items: center;
1024
+ gap: var(--sp-3, 0.75rem);
1025
+ background: rgba(239, 68, 68, 0.08);
1026
+ border: 1px solid var(--c-error, #ef4444);
1027
+ color: #f87171;
1028
+ padding: var(--sp-4, 1rem);
1029
+ border-radius: var(--r-md, 10px);
1030
+ text-align: center;
1031
+ font-weight: 600;
1032
+ width: 100%;
1033
+ box-sizing: border-box;
1034
+ }
1035
+
1036
+ .shake-box--active { animation: shake-kb 0.5s cubic-bezier(.36,.07,.19,.97) both; }
1037
+
1038
+ .shake-box__icon { font-size: 1rem; }
1039
+ .shake-box__msg { font-size: 0.9rem; }
1040
+
1041
+ @keyframes shake-kb {
1042
+ 10%, 90% { transform: translateX(-1px); }
1043
+ 20%, 80% { transform: translateX(2px); }
1044
+ 30%, 50%, 70% { transform: translateX(-4px); }
1045
+ 40%, 60% { transform: translateX(4px); }
1046
+ }
1047
+ </style>
1048
+ `,
1049
+ usage: `<ShakeErrorGroup message="Wrong password" active />`,
1050
+ },
1051
+ {
1052
+ slug: 'simple-tooltip',
1053
+ name: 'Simple Tooltip',
1054
+ description: 'Hover-based hint message.',
1055
+ category: 'feedback',
1056
+ tags: ["tooltip","hint"],
1057
+ featured: true,
1058
+ code: `---
1059
+ /**
1060
+ * SimpleTooltip — A lightweight hover-based hint for buttons or icons.
1061
+ *
1062
+ * @param {string} text - The tooltip content text.
1063
+ * @param {'top'|'bottom'|'left'|'right'} position - Preferred placement relative to the child. Default is 'top'.
1064
+ */
1065
+ interface Props {
1066
+ text: string;
1067
+ position?: 'top' | 'bottom' | 'left' | 'right';
1068
+ }
1069
+
1070
+ const { text, position = 'top' } = Astro.props;
1071
+ ---
1072
+
1073
+ <div class:list={["tooltip-wrap", \`tooltip-wrap--\${position}\`]} data-tip={text}>
1074
+ <slot />
1075
+ </div>
1076
+
1077
+ <style>
1078
+ .tooltip-wrap {
1079
+ position: relative;
1080
+ display: inline-flex;
1081
+ cursor: help;
1082
+ }
1083
+
1084
+ .tooltip-wrap::after {
1085
+ content: attr(data-tip);
1086
+ position: absolute;
1087
+ padding: var(--sp-2, 6px) var(--sp-3, 12px);
1088
+ background: var(--c-bg-elev, #111);
1089
+ color: var(--c-text-1, #fff);
1090
+ font-size: 0.72rem;
1091
+ font-weight: 600;
1092
+ border-radius: var(--r-sm, 6px);
1093
+ white-space: nowrap;
1094
+ opacity: 0;
1095
+ pointer-events: none;
1096
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
1097
+ border: 1px solid var(--c-border, rgba(255,255,255,0.1));
1098
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
1099
+ z-index: 100;
1100
+ }
1101
+
1102
+ /* Positions */
1103
+ .tooltip-wrap--top::after { bottom: 125%; left: 50%; transform: translateX(-50%) translateY(5px); }
1104
+ .tooltip-wrap--bottom::after { top: 125%; left: 50%; transform: translateX(-50%) translateY(-5px); }
1105
+ .tooltip-wrap--left::after { right: 125%; top: 50%; transform: translateY(-50%) translateX(5px); }
1106
+ .tooltip-wrap--right::after { left: 125%; top: 50%; transform: translateY(-50%) translateX(-5px); }
1107
+
1108
+ /* Hover States */
1109
+ .tooltip-wrap:hover::after {
1110
+ opacity: 1;
1111
+ }
1112
+ .tooltip-wrap--top:hover::after { transform: translateX(-50%) translateY(0); }
1113
+ .tooltip-wrap--bottom:hover::after { transform: translateX(-50%) translateY(0); }
1114
+ .tooltip-wrap--left:hover::after { transform: translateY(-50%) translateX(0); }
1115
+ .tooltip-wrap--right:hover::after { transform: translateY(-50%) translateX(0); }
1116
+ </style>
1117
+ `,
1118
+ usage: `<SimpleTooltip text="Edit profile" position="top"><button>Settings</button></SimpleTooltip>`,
1119
+ },
1120
+ {
1121
+ slug: 'skeleton-list',
1122
+ name: 'Skeleton Loader',
1123
+ description: 'Placeholder content for loading feeds.',
1124
+ category: 'feedback',
1125
+ tags: ["loading","skeleton"],
1126
+ featured: true,
1127
+ code: `---
1128
+ /**
1129
+ * SkeletonList — Shimmering loading placeholder for lists or feed items.
1130
+ *
1131
+ * @param {number} count - Number of skeleton items to render. Default is 3.
1132
+ */
1133
+ interface Props {
1134
+ count?: number;
1135
+ }
1136
+
1137
+ const { count = 3 } = Astro.props;
1138
+ ---
1139
+
1140
+ <div class="skeleton-list" aria-hidden="true">
1141
+ {Array.from({ length: count }).map(() => (
1142
+ <div class="skeleton-item">
1143
+ <div class="skeleton-avatar"></div>
1144
+ <div class="skeleton-content">
1145
+ <div class="skeleton-line skeleton-line--title"></div>
1146
+ <div class="skeleton-line skeleton-line--body"></div>
1147
+ </div>
1148
+ </div>
1149
+ ))}
1150
+ </div>
1151
+
1152
+ <style>
1153
+ .skeleton-list { display: flex; flex-direction: column; width: 100%; }
1154
+
1155
+ .skeleton-item {
1156
+ display: flex; gap: var(--sp-4, 1rem);
1157
+ padding: var(--sp-4, 1rem) 0;
1158
+ border-bottom: 1px solid var(--c-border, rgba(255,255,255,0.05));
1159
+ }
1160
+
1161
+ .skeleton-avatar {
1162
+ width: 48px; height: 48px;
1163
+ border-radius: var(--r-full, 50%);
1164
+ background: var(--c-bg-elev, rgba(255,255,255,0.05));
1165
+ flex-shrink: 0;
1166
+ }
1167
+
1168
+ .skeleton-content { flex: 1; display: flex; flex-direction: column; gap: 10px; justify-content: center; }
1169
+
1170
+ .skeleton-line {
1171
+ height: 12px;
1172
+ background: var(--c-bg-elev, rgba(255,255,255,0.05));
1173
+ border-radius: var(--r-sm, 6px);
1174
+ position: relative; overflow: hidden;
1175
+ }
1176
+
1177
+ .skeleton-line--title { width: 40%; height: 14px; }
1178
+ .skeleton-line--body { width: 85%; }
1179
+
1180
+ .skeleton-item div::after {
1181
+ content: "";
1182
+ position: absolute;
1183
+ inset: 0;
1184
+ background: linear-gradient(90deg, transparent, rgba(255,255,255,0.04), transparent);
1185
+ animation: skeleton-shimmer 1.8s infinite;
1186
+ transform: translateX(-100%);
1187
+ }
1188
+
1189
+ @keyframes skeleton-shimmer {
1190
+ 100% { transform: translateX(100%); }
1191
+ }
1192
+ </style>
1193
+ `,
1194
+ usage: `<SkeletonList count={4} />`,
1195
+ },
1196
+ {
1197
+ slug: 'spotlight',
1198
+ name: 'Focus Spotlight',
1199
+ description: 'Highlighting effect for specific elements.',
1200
+ category: 'feedback',
1201
+ tags: ["focus","highlight"],
1202
+ featured: true,
1203
+ code: `---
1204
+ /**
1205
+ * Spotlight — A visual emphasis component that dims the page and highlights its children.
1206
+ *
1207
+ * @param {boolean} active - If true, the spotlight overlay is visible. Default is false.
1208
+ * @param {number} opacity - Background overlay opacity (0 to 1). Default is 0.7.
1209
+ */
1210
+ interface Props {
1211
+ active?: boolean;
1212
+ opacity?: number;
1213
+ }
1214
+
1215
+ const { active = false, opacity = 0.7 } = Astro.props;
1216
+ ---
1217
+
1218
+ <div class:list={["spotlight-wrap", { "spotlight-wrap--active": active }]} style={\`--spotlight-opacity: \${opacity};\`}>
1219
+ <div class="spotlight-content">
1220
+ <slot />
1221
+ </div>
1222
+ </div>
1223
+
1224
+ <style>
1225
+ .spotlight-wrap {
1226
+ position: relative;
1227
+ display: inline-block;
1228
+ transition: z-index 0.3s;
1229
+ }
1230
+
1231
+ .spotlight-wrap--active { z-index: 2000; }
1232
+
1233
+ .spotlight-wrap--active::after {
1234
+ content: "";
1235
+ position: fixed;
1236
+ inset: 0;
1237
+ background: rgba(0,0,0,var(--spotlight-opacity));
1238
+ backdrop-filter: blur(4px);
1239
+ z-index: -1;
1240
+ animation: spotlight-fade 0.3s forwards;
1241
+ pointer-events: none;
1242
+ }
1243
+
1244
+ .spotlight-content {
1245
+ position: relative;
1246
+ border-radius: var(--r-md, 12px);
1247
+ transition: transform 0.3s;
1248
+ }
1249
+
1250
+ .spotlight-wrap--active .spotlight-content {
1251
+ transform: scale(1.05);
1252
+ box-shadow: 0 0 50px rgba(255,255,255,0.1);
1253
+ }
1254
+
1255
+ @keyframes spotlight-fade { from { opacity: 0; } to { opacity: 1; } }
1256
+ </style>
1257
+ `,
1258
+ usage: `<Spotlight active opacity={0.8}><div style="padding: 20px; background: #fff; color: #000">Focused Content</div></Spotlight>`,
1259
+ },
1260
+ {
1261
+ slug: 'step-progress',
1262
+ name: 'Step Tracker',
1263
+ description: 'Multi-stage progress tracker.',
1264
+ category: 'feedback',
1265
+ tags: ["stepper","workflow"],
1266
+ featured: true,
1267
+ code: `---
1268
+ /**
1269
+ * StepProgress — A multi-stage stepper indicating flow through several discrete points.
1270
+ *
1271
+ * @param {Array<{label: string}>} steps - List of steps to represent.
1272
+ * @param {number} activeIndex - The index (0-based) of the current step. Default is 0.
1273
+ */
1274
+ interface Props {
1275
+ steps?: Array<{ label: string }>;
1276
+ activeIndex?: number;
1277
+ }
1278
+
1279
+ const {
1280
+ steps = [
1281
+ { label: 'Details' },
1282
+ { label: 'Payment' },
1283
+ { label: 'Finish' }
1284
+ ],
1285
+ activeIndex = 0
1286
+ } = Astro.props;
1287
+ ---
1288
+
1289
+ <div class="steps-container">
1290
+ <div class="steps-line" aria-hidden="true" style={\`--progress: \${(activeIndex / (steps.length - 1)) * 100}%\`}></div>
1291
+ <div class="steps-list">
1292
+ {steps.map((step, i) => (
1293
+ <div
1294
+ class:list={['step-item', { 'step-item--active': i === activeIndex, 'step-item--complete': i < activeIndex }]}
1295
+ aria-current={i === activeIndex ? "step" : undefined}
1296
+ >
1297
+ <div class="step-circle">{i + 1}</div>
1298
+ <span class="step-label">{step.label}</span>
1299
+ </div>
1300
+ ))}
1301
+ </div>
1302
+ </div>
1303
+
1304
+ <style>
1305
+ .steps-container { position: relative; margin-bottom: var(--sp-8, 2rem); width: 100%; padding: 0 var(--sp-4, 1rem); box-sizing: border-box; }
1306
+
1307
+ .steps-line {
1308
+ position: absolute;
1309
+ top: 16px; left: 0; right: 0;
1310
+ height: 3px;
1311
+ background: var(--c-bg-elev, rgba(255,255,255,0.05));
1312
+ z-index: 0;
1313
+ }
1314
+
1315
+ .steps-line::after {
1316
+ content: "";
1317
+ position: absolute;
1318
+ left: 0; top: 0;
1319
+ height: 100%;
1320
+ width: var(--progress);
1321
+ background: var(--c-primary, #6366f1);
1322
+ transition: width 0.4s ease-in-out;
1323
+ box-shadow: 0 0 10px rgba(99,102,241,0.3);
1324
+ }
1325
+
1326
+ .steps-list { display: flex; justify-content: space-between; position: relative; z-index: 1; }
1327
+
1328
+ .step-circle {
1329
+ width: 32px; height: 32px;
1330
+ border-radius: var(--r-full, 50%);
1331
+ background: var(--c-bg, #1e293b);
1332
+ color: var(--c-text-2, #64748b);
1333
+ border: 2px solid var(--c-border, rgba(255,255,255,0.1));
1334
+ display: grid; place-items: center;
1335
+ font-size: 0.85rem; font-weight: 800;
1336
+ transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1337
+ }
1338
+
1339
+ .step-item--active .step-circle {
1340
+ background: var(--c-primary, #6366f1);
1341
+ border-color: var(--c-primary, #6366f1);
1342
+ color: #fff;
1343
+ box-shadow: 0 0 20px rgba(99,102,241,0.5);
1344
+ transform: scale(1.1);
1345
+ }
1346
+
1347
+ .step-item--complete .step-circle {
1348
+ background: var(--c-primary, #6366f1);
1349
+ border-color: var(--c-primary, #6366f1);
1350
+ color: #fff;
1351
+ }
1352
+
1353
+ .step-label {
1354
+ position: absolute;
1355
+ top: 100%; left: 50%;
1356
+ transform: translateX(-50%);
1357
+ margin-top: 8px;
1358
+ font-size: 0.7rem;
1359
+ font-weight: 700;
1360
+ white-space: nowrap;
1361
+ color: var(--c-text-2, #64748b);
1362
+ text-transform: uppercase;
1363
+ letter-spacing: 0.05em;
1364
+ }
1365
+
1366
+ .step-item--active .step-label, .step-item--complete .step-label { color: var(--c-text-1, #fff); }
1367
+ </style>
1368
+ `,
1369
+ usage: `<StepProgress activeIndex={1} steps={[{label: "Step 1"}, {label: "Step 2"}, {label: "Step 3"}]} />`,
1370
+ },
1371
+ {
1372
+ slug: 'toast-notif',
1373
+ name: 'Toast Message',
1374
+ description: 'Transient snackbar notification.',
1375
+ category: 'feedback',
1376
+ tags: ["toast","notification"],
1377
+ featured: true,
1378
+ code: `---
1379
+ /**
1380
+ * ToastNotif — A transient snackbar notification that appears at the corner of the screen.
1381
+ *
1382
+ * @param {string} message - The notification content.
1383
+ * @param {string} icon - Emoji or SVG icon. Default is '✅'.
1384
+ * @param {'success'|'info'|'error'} variant - Theme of the toast. Default is 'success'.
1385
+ * @param {boolean} show - Initial visibility. Default is true.
1386
+ */
1387
+ interface Props {
1388
+ message: string;
1389
+ icon?: string;
1390
+ variant?: 'success' | 'info' | 'error';
1391
+ show?: boolean;
1392
+ }
1393
+
1394
+ const {
1395
+ message,
1396
+ icon = "✅",
1397
+ variant = 'success',
1398
+ show = true
1399
+ } = Astro.props;
1400
+ ---
1401
+
1402
+ <div class:list={['toast', \`toast--\${variant}\`, { 'toast--show': show }]} role="status">
1403
+ <div class="toast__icon" aria-hidden="true">{icon}</div>
1404
+ <span class="toast__msg">{message}</span>
1405
+ </div>
1406
+
1407
+ <style>
1408
+ .toast {
1409
+ position: fixed;
1410
+ bottom: 2rem;
1411
+ right: 2rem;
1412
+ background: var(--c-bg-elev, #0f172a);
1413
+ color: var(--c-text-1, #fff);
1414
+ padding: var(--sp-4, 1rem) var(--sp-6, 1.5rem);
1415
+ border-radius: var(--r-md, 14px);
1416
+ border: 1px solid var(--c-border, rgba(255,255,255,0.1));
1417
+ display: flex;
1418
+ align-items: center;
1419
+ gap: var(--sp-3, 0.8rem);
1420
+ box-shadow: 0 15px 45px rgba(0,0,0,0.4);
1421
+ transform: translateY(150%) scale(0.9);
1422
+ transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
1423
+ z-index: 10000;
1424
+ }
1425
+
1426
+ .toast--show { transform: translateY(0) scale(1); }
1427
+
1428
+ .toast--success { border-left: 4px solid #10b981; }
1429
+ .toast--info { border-left: 4px solid var(--c-primary, #6366f1); }
1430
+ .toast--error { border-left: 4px solid #ef4444; }
1431
+
1432
+ .toast__icon {
1433
+ background: rgba(255,255,255,0.05);
1434
+ width: 28px; height: 28px;
1435
+ display: grid; place-items: center;
1436
+ border-radius: var(--r-sm, 6px);
1437
+ font-size: 1rem;
1438
+ flex-shrink: 0;
1439
+ }
1440
+
1441
+ .toast__msg { font-size: 0.9rem; font-weight: 500; white-space: nowrap; }
1442
+ </style>
1443
+ `,
1444
+ usage: `<ToastNotif message="File uploaded" variant="success" />`,
1445
+ },
1446
+ {
1447
+ slug: 'typing-indicator',
1448
+ name: 'Typing Dots',
1449
+ description: 'Classic messenger-style typing indicator.',
1450
+ category: 'feedback',
1451
+ tags: ["loading","typing"],
1452
+ featured: true,
1453
+ code: `---
1454
+ /**
1455
+ * TypingIndicator — A classic three-dot animation indicating a message is being composed.
1456
+ *
1457
+ * @param {string} dotColor - CSS color for the dots. Default is 'var(--c-primary)'.
1458
+ * @param {string} label - Accessible ARIA label. Default is "Typing...".
1459
+ */
1460
+ interface Props {
1461
+ dotColor?: string;
1462
+ label?: string;
1463
+ }
1464
+
1465
+ const {
1466
+ dotColor = 'var(--c-primary, #6366f1)',
1467
+ label = "Someone is typing..."
1468
+ } = Astro.props;
1469
+ ---
1470
+
1471
+ <div class="typing-wrap" role="status" aria-label={label}>
1472
+ <div class="typing-dots" style={\`--dot-color: \${dotColor};\`}>
1473
+ <span></span>
1474
+ <span></span>
1475
+ <span></span>
1476
+ </div>
1477
+ </div>
1478
+
1479
+ <style>
1480
+ .typing-wrap {
1481
+ display: flex;
1482
+ padding: var(--sp-3, 0.75rem) var(--sp-5, 1.25rem);
1483
+ background: var(--c-bg-elev, rgba(255,255,255,0.05));
1484
+ border-radius: var(--r-xl, 20px);
1485
+ width: fit-content;
1486
+ border: 1px solid var(--c-border, rgba(255,255,255,0.1));
1487
+ }
1488
+
1489
+ .typing-dots { display: flex; gap: var(--sp-1, 4px); }
1490
+
1491
+ .typing-dots span {
1492
+ width: 6px;
1493
+ height: 6px;
1494
+ background: var(--c-text-muted, #64748b);
1495
+ border-radius: var(--r-full, 50%);
1496
+ animation: typing-bounce 0.8s infinite alternate cubic-bezier(0.455, 0.03, 0.515, 0.955);
1497
+ }
1498
+
1499
+ .typing-dots span:nth-child(2) { animation-delay: 0.2s; }
1500
+ .typing-dots span:nth-child(3) { animation-delay: 0.4s; }
1501
+
1502
+ @keyframes typing-bounce {
1503
+ to {
1504
+ transform: translateY(-4px);
1505
+ background: var(--dot-color);
1506
+ filter: drop-shadow(0 0 2px var(--dot-color));
1507
+ }
1508
+ }
1509
+ </style>
1510
+ `,
1511
+ usage: `<TypingIndicator />`,
1512
+ },
1513
+ {
1514
+ slug: 'progress-ring-counter',
1515
+ name: 'Progress Ring Counter',
1516
+ description: 'Circular SVG progress gauge with smooth stroke dashoffset transition, customizable track colors, and centered value readout.',
1517
+ category: 'feedback',
1518
+ tags: ["feedback","progress","ring","circular","meter"],
1519
+ featured: true,
1520
+ code: `---
1521
+ /**
1522
+ * ProgressRingCounter — Circular SVG progress ring with centered countdown or metric value.
1523
+ *
1524
+ * @param {number} value - Current counter or progress value. Default is 75.
1525
+ * @param {number} max - Maximum value threshold. Default is 100.
1526
+ * @param {number} size - Pixel diameter of the ring. Default is 96.
1527
+ * @param {number} strokeWidth - Thickness of the ring stroke. Default is 6.
1528
+ * @param {string} color - Primary gauge fill color. Default is '#d4a017'.
1529
+ * @param {string} label - Subtitle label beneath figure. Default is 'COMPLETED'.
1530
+ */
1531
+ interface Props {
1532
+ value?: number;
1533
+ max?: number;
1534
+ size?: number;
1535
+ strokeWidth?: number;
1536
+ color?: string;
1537
+ label?: string;
1538
+ }
1539
+
1540
+ const {
1541
+ value = 75,
1542
+ max = 100,
1543
+ size = 96,
1544
+ strokeWidth = 6,
1545
+ color = '#d4a017',
1546
+ label = 'COMPLETED',
1547
+ } = Astro.props;
1548
+
1549
+ const radius = (size - strokeWidth) / 2;
1550
+ const circumference = 2 * Math.PI * radius;
1551
+ const percent = Math.min(100, Math.max(0, (value / max) * 100));
1552
+ const strokeDashoffset = circumference - (percent / 100) * circumference;
1553
+ ---
1554
+
1555
+ <div class="ring-counter" style={\`--ring-size: \${size}px; --ring-color: \${color};\`}>
1556
+ <svg width={size} height={size} class="ring-counter__svg">
1557
+ <circle
1558
+ class="ring-counter__bg"
1559
+ cx={size / 2}
1560
+ cy={size / 2}
1561
+ r={radius}
1562
+ stroke-width={strokeWidth}
1563
+ />
1564
+ <circle
1565
+ class="ring-counter__meter"
1566
+ cx={size / 2}
1567
+ cy={size / 2}
1568
+ r={radius}
1569
+ stroke-width={strokeWidth}
1570
+ stroke-dasharray={circumference}
1571
+ stroke-dashoffset={strokeDashoffset}
1572
+ />
1573
+ </svg>
1574
+
1575
+ <div class="ring-counter__center">
1576
+ <span class="val">{Math.round(percent)}%</span>
1577
+ {label && <span class="lbl">{label}</span>}
1578
+ </div>
1579
+ </div>
1580
+
1581
+ <style>
1582
+ .ring-counter {
1583
+ position: relative;
1584
+ width: var(--ring-size);
1585
+ height: var(--ring-size);
1586
+ display: inline-flex;
1587
+ align-items: center;
1588
+ justify-content: center;
1589
+ font-family: var(--font-sans, system-ui, sans-serif);
1590
+ user-select: none;
1591
+ }
1592
+
1593
+ .ring-counter__svg {
1594
+ transform: rotate(-90deg);
1595
+ width: 100%;
1596
+ height: 100%;
1597
+ }
1598
+
1599
+ .ring-counter__bg {
1600
+ fill: none;
1601
+ stroke: rgba(255, 255, 255, 0.08);
1602
+ }
1603
+
1604
+ .ring-counter__meter {
1605
+ fill: none;
1606
+ stroke: var(--ring-color);
1607
+ stroke-linecap: round;
1608
+ transition: stroke-dashoffset 0.8s cubic-bezier(0.16, 1, 0.3, 1);
1609
+ filter: drop-shadow(0 0 6px rgba(212, 160, 23, 0.35));
1610
+ }
1611
+
1612
+ .ring-counter__center {
1613
+ position: absolute;
1614
+ inset: 0;
1615
+ display: flex;
1616
+ flex-direction: column;
1617
+ align-items: center;
1618
+ justify-content: center;
1619
+ text-align: center;
1620
+ }
1621
+
1622
+ .ring-counter__center .val {
1623
+ font-size: 1.25rem;
1624
+ font-weight: 800;
1625
+ color: var(--c-text-1, #ffffff);
1626
+ line-height: 1;
1627
+ }
1628
+
1629
+ .ring-counter__center .lbl {
1630
+ font-size: 0.55rem;
1631
+ font-weight: 700;
1632
+ letter-spacing: 0.08em;
1633
+ color: var(--c-text-3, #737373);
1634
+ margin-top: 4px;
1635
+ text-transform: uppercase;
1636
+ }
1637
+ </style>
1638
+ `,
1639
+ usage: `<ProgressRingCounter value={78} max={100} size={110} strokeWidth={8} color="var(--c-primary)" label="Score" />`,
1640
+ },
1641
+ {
1642
+ slug: 'toast-notification-group',
1643
+ name: 'Toast Notification Group',
1644
+ description: 'A modern stacked toast notification tray supporting success, error, warning, and info alert toasts with dismiss actions.',
1645
+ category: 'feedback',
1646
+ tags: ["feedback","toast","notification","alert","stack"],
1647
+ featured: true,
1648
+ code: `---
1649
+ /**
1650
+ * ToastNotificationGroup — Stackable notification banner with auto-timed progress bar, icon, and dismiss action.
1651
+ *
1652
+ * @param {'success' | 'warning' | 'error' | 'info'} type - Alert status variant. Default is 'success'.
1653
+ * @param {string} title - Notification title headline. Default is 'Changes Saved'.
1654
+ * @param {string} message - Explanatory message text.
1655
+ * @param {string} actionText - Optional action link text (e.g. 'Undo').
1656
+ */
1657
+ interface Props {
1658
+ type?: 'success' | 'warning' | 'error' | 'info';
1659
+ title?: string;
1660
+ message?: string;
1661
+ actionText?: string;
1662
+ }
1663
+
1664
+ const {
1665
+ type = 'success',
1666
+ title = 'Changes Saved',
1667
+ message = 'Your project settings were synchronized with production.',
1668
+ actionText = 'Undo',
1669
+ } = Astro.props;
1670
+
1671
+ const icons = {
1672
+ success: '✓',
1673
+ warning: '!',
1674
+ error: '✕',
1675
+ info: 'ℹ',
1676
+ };
1677
+ ---
1678
+
1679
+ <div class:list={['toast-item', \`toast-item--\${type}\`]} role="alert" data-toast-item>
1680
+ <div class="toast-item__body">
1681
+ <span class="toast-item__icon" aria-hidden="true">{icons[type]}</span>
1682
+ <div class="toast-item__content">
1683
+ <h5 class="toast-item__title">{title}</h5>
1684
+ <p class="toast-item__msg">{message}</p>
1685
+ </div>
1686
+ {actionText && (
1687
+ <button type="button" class="toast-item__action">
1688
+ {actionText}
1689
+ </button>
1690
+ )}
1691
+ <button type="button" class="toast-item__close" aria-label="Dismiss alert" data-toast-close>
1692
+ ✕
1693
+ </button>
1694
+ </div>
1695
+ <div class="toast-item__progress" aria-hidden="true"></div>
1696
+ </div>
1697
+
1698
+ <style>
1699
+ .toast-item {
1700
+ position: relative;
1701
+ display: flex;
1702
+ flex-direction: column;
1703
+ width: 100%;
1704
+ max-width: 380px;
1705
+ background: rgba(14, 14, 18, 0.95);
1706
+ backdrop-filter: blur(20px);
1707
+ border: 1px solid rgba(255, 255, 255, 0.1);
1708
+ border-radius: var(--r-md, 14px);
1709
+ overflow: hidden;
1710
+ box-shadow: 0 16px 40px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.05);
1711
+ font-family: var(--font-sans, system-ui, sans-serif);
1712
+ transition: transform 0.25s ease, opacity 0.25s ease;
1713
+ }
1714
+
1715
+ .toast-item--success { --toast-accent: #10b981; }
1716
+ .toast-item--warning { --toast-accent: #f59e0b; }
1717
+ .toast-item--error { --toast-accent: #ef4444; }
1718
+ .toast-item--info { --toast-accent: #38bdf8; }
1719
+
1720
+ .toast-item__body {
1721
+ display: flex;
1722
+ align-items: flex-start;
1723
+ gap: 12px;
1724
+ padding: 1rem 1.15rem;
1725
+ }
1726
+
1727
+ .toast-item__icon {
1728
+ width: 24px;
1729
+ height: 24px;
1730
+ border-radius: 50%;
1731
+ background: rgba(255, 255, 255, 0.06);
1732
+ border: 1px solid var(--toast-accent);
1733
+ color: var(--toast-accent);
1734
+ display: grid;
1735
+ place-items: center;
1736
+ font-size: 0.75rem;
1737
+ font-weight: 900;
1738
+ flex-shrink: 0;
1739
+ margin-top: 2px;
1740
+ }
1741
+
1742
+ .toast-item__content {
1743
+ flex: 1;
1744
+ }
1745
+
1746
+ .toast-item__title {
1747
+ font-size: 0.88rem;
1748
+ font-weight: 700;
1749
+ color: var(--c-text-1, #ffffff);
1750
+ margin: 0 0 2px 0;
1751
+ }
1752
+
1753
+ .toast-item__msg {
1754
+ font-size: 0.78rem;
1755
+ color: var(--c-text-2, #a3a3a3);
1756
+ margin: 0;
1757
+ line-height: 1.4;
1758
+ }
1759
+
1760
+ .toast-item__action {
1761
+ background: transparent;
1762
+ border: none;
1763
+ color: var(--toast-accent);
1764
+ font-size: 0.78rem;
1765
+ font-weight: 700;
1766
+ cursor: pointer;
1767
+ padding: 0.2rem 0.4rem;
1768
+ border-radius: 4px;
1769
+ transition: background 0.2s;
1770
+ }
1771
+
1772
+ .toast-item__action:hover {
1773
+ background: rgba(255, 255, 255, 0.08);
1774
+ }
1775
+
1776
+ .toast-item__close {
1777
+ background: transparent;
1778
+ border: none;
1779
+ color: var(--c-text-3, #737373);
1780
+ font-size: 0.85rem;
1781
+ cursor: pointer;
1782
+ padding: 0.2rem;
1783
+ line-height: 1;
1784
+ transition: color 0.2s;
1785
+ }
1786
+
1787
+ .toast-item__close:hover {
1788
+ color: var(--c-text-1, #ffffff);
1789
+ }
1790
+
1791
+ .toast-item__progress {
1792
+ height: 3px;
1793
+ background: var(--toast-accent);
1794
+ width: 100%;
1795
+ animation: toast-timer 5s linear forwards;
1796
+ }
1797
+
1798
+ @keyframes toast-timer {
1799
+ from { width: 100%; }
1800
+ to { width: 0%; }
1801
+ }
1802
+ </style>
1803
+
1804
+ <script>
1805
+ function setupToasts() {
1806
+ document.querySelectorAll('[data-toast-item]').forEach((toast) => {
1807
+ const closeBtn = toast.querySelector('[data-toast-close]');
1808
+ if (closeBtn) {
1809
+ closeBtn.addEventListener('click', () => {
1810
+ (toast as HTMLElement).style.opacity = '0';
1811
+ (toast as HTMLElement).style.transform = 'translateY(10px) scale(0.95)';
1812
+ setTimeout(() => toast.remove(), 250);
1813
+ });
1814
+ }
1815
+ });
1816
+ }
1817
+
1818
+ setupToasts();
1819
+ document.addEventListener('astro:page-load', setupToasts);
1820
+ </script>
1821
+ `,
1822
+ usage: `<ToastNotificationGroup toasts={[{ id: '1', title: 'Saved', message: 'Project configurations synchronized.', variant: 'success' }]} />`,
1823
+ },
1824
+ ];