astro-component-kit 0.3.1 → 0.4.1

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 (249) hide show
  1. package/README.md +133 -36
  2. package/package.json +17 -4
  3. package/src/components/index.ts +18 -202
  4. package/src/components/lib/Galaxy3D.astro +249 -137
  5. package/src/components/lib/app/AppSidebar.astro +249 -0
  6. package/src/components/lib/app/AppTopbar.astro +284 -0
  7. package/src/components/lib/app/ArrivalsBoard.astro +176 -0
  8. package/src/components/lib/app/AuthCard.astro +213 -0
  9. package/src/components/lib/app/DataTable.astro +346 -0
  10. package/src/components/lib/app/GuestProfileCard.astro +133 -0
  11. package/src/components/lib/app/HousekeepingBoard.astro +189 -0
  12. package/src/components/lib/app/PageHeader.astro +157 -0
  13. package/src/components/lib/app/PlanUsageCard.astro +112 -0
  14. package/src/components/lib/app/RoomRack.astro +207 -0
  15. package/src/components/lib/app/SettingsSection.astro +96 -0
  16. package/src/components/lib/app/SwitchRow.astro +71 -0
  17. package/src/components/lib/app/TeamMembers.astro +190 -0
  18. package/src/components/lib/app/app-icons.ts +52 -0
  19. package/src/components/lib/app/index.ts +19 -0
  20. package/src/components/lib/buttons/NeonBorderButton.astro +162 -0
  21. package/src/components/lib/buttons/RippleButton.astro +199 -0
  22. package/src/components/lib/buttons/SlideRevealButton.astro +159 -0
  23. package/src/components/lib/buttons/index.ts +4 -0
  24. package/src/components/lib/cards/GithubRepoCard.astro +176 -0
  25. package/src/components/lib/cards/PricingCompareMiniCard.astro +196 -0
  26. package/src/components/lib/cards/StatsTrendCard.astro +149 -0
  27. package/src/components/lib/cards/index.ts +3 -0
  28. package/src/components/lib/charts/ActivityRings.astro +105 -0
  29. package/src/components/lib/charts/AreaChart.astro +4 -4
  30. package/src/components/lib/charts/BarChart.astro +3 -3
  31. package/src/components/lib/charts/BoxPlotChart.astro +109 -0
  32. package/src/components/lib/charts/BubbleChart.astro +101 -0
  33. package/src/components/lib/charts/BulletChart.astro +160 -0
  34. package/src/components/lib/charts/BumpChart.astro +92 -0
  35. package/src/components/lib/charts/CandlestickChart.astro +105 -0
  36. package/src/components/lib/charts/ChartFrame.astro +373 -0
  37. package/src/components/lib/charts/DivergingBarChart.astro +98 -0
  38. package/src/components/lib/charts/DonutChart.astro +2 -2
  39. package/src/components/lib/charts/DumbbellChart.astro +92 -0
  40. package/src/components/lib/charts/ForecastLineChart.astro +125 -0
  41. package/src/components/lib/charts/FunnelChart.astro +122 -0
  42. package/src/components/lib/charts/GaugeChart.astro +111 -0
  43. package/src/components/lib/charts/GoalProgressChart.astro +97 -0
  44. package/src/components/lib/charts/GroupedBarChart.astro +125 -0
  45. package/src/components/lib/charts/HeatmapChart.astro +131 -0
  46. package/src/components/lib/charts/HistogramChart.astro +111 -0
  47. package/src/components/lib/charts/HorizontalBarChart.astro +155 -0
  48. package/src/components/lib/charts/KpiStatTile.astro +116 -0
  49. package/src/components/lib/charts/KpiTrendGrid.astro +60 -0
  50. package/src/components/lib/charts/LineChart.astro +160 -0
  51. package/src/components/lib/charts/LinearProgressGauge.astro +136 -0
  52. package/src/components/lib/charts/LollipopChart.astro +100 -0
  53. package/src/components/lib/charts/MeterGauge.astro +122 -0
  54. package/src/components/lib/charts/MetricDeltaTable.astro +125 -0
  55. package/src/components/lib/charts/PieChart.astro +135 -0
  56. package/src/components/lib/charts/PolarAreaChart.astro +85 -0
  57. package/src/components/lib/charts/PopulationPyramid.astro +122 -0
  58. package/src/components/lib/charts/PunchCardChart.astro +89 -0
  59. package/src/components/lib/charts/RadarChart.astro +94 -0
  60. package/src/components/lib/charts/RadarMetricChart.astro +168 -0
  61. package/src/components/lib/charts/RadialBarChart.astro +124 -0
  62. package/src/components/lib/charts/RangeAreaChart.astro +113 -0
  63. package/src/components/lib/charts/RetentionCohortTable.astro +126 -0
  64. package/src/components/lib/charts/SankeyChart.astro +128 -0
  65. package/src/components/lib/charts/ScatterPlot.astro +139 -0
  66. package/src/components/lib/charts/SegmentedBarChart.astro +119 -0
  67. package/src/components/lib/charts/SlopeChart.astro +105 -0
  68. package/src/components/lib/charts/SparklineChart.astro +87 -0
  69. package/src/components/lib/charts/StackedAreaChart.astro +130 -0
  70. package/src/components/lib/charts/StackedBarChart.astro +136 -0
  71. package/src/components/lib/charts/SunburstChart.astro +103 -0
  72. package/src/components/lib/charts/TreemapChart.astro +94 -0
  73. package/src/components/lib/charts/WaffleChart.astro +121 -0
  74. package/src/components/lib/charts/WaterfallChart.astro +118 -0
  75. package/src/components/lib/charts/chart-tooltip.ts +75 -0
  76. package/src/components/lib/charts/chart-utils.ts +271 -0
  77. package/src/components/lib/charts/index.ts +46 -0
  78. package/src/components/lib/data/ActivityHeatmapGrid.astro +157 -0
  79. package/src/components/lib/data/MetricComparisonRow.astro +109 -0
  80. package/src/components/lib/data/StatusTimelineFeed.astro +168 -0
  81. package/src/components/lib/data/index.ts +3 -0
  82. package/src/components/lib/ecommerce/CartDrawer.astro +483 -0
  83. package/src/components/lib/ecommerce/CartPageList.astro +290 -0
  84. package/src/components/lib/ecommerce/CartToast.astro +276 -0
  85. package/src/components/lib/ecommerce/CategoryTiles.astro +143 -0
  86. package/src/components/lib/ecommerce/CheckoutForm.astro +423 -0
  87. package/src/components/lib/ecommerce/CountdownBanner.astro +230 -0
  88. package/src/components/lib/ecommerce/FilterBar.astro +278 -0
  89. package/src/components/lib/ecommerce/FilterSidebar.astro +405 -0
  90. package/src/components/lib/ecommerce/LiveActivityToast.astro +213 -0
  91. package/src/components/lib/ecommerce/OrderSummary.astro +295 -0
  92. package/src/components/lib/ecommerce/ProductBuyBox.astro +247 -0
  93. package/src/components/lib/ecommerce/ProductCardMinimal.astro +229 -0
  94. package/src/components/lib/ecommerce/ProductGallery.astro +145 -0
  95. package/src/components/lib/ecommerce/ProductGrid.astro +118 -0
  96. package/src/components/lib/ecommerce/ProductMedia.astro +113 -0
  97. package/src/components/lib/ecommerce/ProductRating.astro +78 -0
  98. package/src/components/lib/ecommerce/ProductToolbar.astro +93 -0
  99. package/src/components/lib/ecommerce/PromoBar.astro +145 -0
  100. package/src/components/lib/ecommerce/StockIndicator.astro +127 -0
  101. package/src/components/lib/ecommerce/StoreHeader.astro +446 -0
  102. package/src/components/lib/ecommerce/StoreHero.astro +260 -0
  103. package/src/components/lib/ecommerce/StoreProductCard.astro +307 -0
  104. package/src/components/lib/ecommerce/TrustBadges.astro +101 -0
  105. package/src/components/lib/ecommerce/VariantPicker.astro +145 -0
  106. package/src/components/lib/ecommerce/cart-store.ts +151 -0
  107. package/src/components/lib/ecommerce/catalog-filter.ts +62 -0
  108. package/src/components/lib/ecommerce/index.ts +34 -0
  109. package/src/components/lib/ecommerce/product-icons.ts +28 -0
  110. package/src/components/lib/ecommerce/types.ts +70 -0
  111. package/src/components/lib/feedback/ProgressRingCounter.astro +118 -0
  112. package/src/components/lib/feedback/ToastNotificationGroup.astro +173 -0
  113. package/src/components/lib/feedback/index.ts +2 -0
  114. package/src/components/lib/forms/AnimatedInput.astro +4 -2
  115. package/src/components/lib/forms/CustomRadioGroup.astro +3 -1
  116. package/src/components/lib/forms/GlassInlineToggleGroup.astro +161 -0
  117. package/src/components/lib/forms/PasswordStrengthInput.astro +242 -0
  118. package/src/components/lib/forms/PhoneCountryInput.astro +145 -0
  119. package/src/components/lib/forms/TagsAutocompleteInput.astro +249 -0
  120. package/src/components/lib/forms/index.ts +5 -0
  121. package/src/components/lib/index.ts +2 -0
  122. package/src/components/lib/layout/BentoGrid3.astro +6 -4
  123. package/src/components/lib/layout/CenterFixedBox.astro +2 -2
  124. package/src/components/lib/layout/CenteredContainer.astro +4 -2
  125. package/src/components/lib/layout/ClusterHorizontal.astro +2 -2
  126. package/src/components/lib/layout/DiagonalSectionDivider.astro +69 -0
  127. package/src/components/lib/layout/FeatureHero.astro +4 -2
  128. package/src/components/lib/layout/FooterCenteredMinimal.astro +4 -2
  129. package/src/components/lib/layout/FullBleedSection.astro +2 -2
  130. package/src/components/lib/layout/GalleryGrid4.astro +2 -2
  131. package/src/components/lib/layout/HolyGrailGrid.astro +127 -0
  132. package/src/components/lib/layout/LandingSectionSplit.astro +5 -3
  133. package/src/components/lib/layout/MasonryGrid.astro +2 -2
  134. package/src/components/lib/layout/MinimalFooterGrid.astro +1 -1
  135. package/src/components/lib/layout/ScrollSnapSection.astro +1 -1
  136. package/src/components/lib/layout/SidebarLayoutWrapper.astro +4 -2
  137. package/src/components/lib/layout/SplitScreenLayout.astro +3 -3
  138. package/src/components/lib/layout/StepLayoutVertical.astro +1 -1
  139. package/src/components/lib/layout/StickySidebarWrapper.astro +4 -2
  140. package/src/components/lib/layout/index.ts +3 -1
  141. package/src/components/lib/navigation/QuickJumpDock.astro +125 -0
  142. package/src/components/lib/navigation/StepBreadcrumbBar.astro +151 -0
  143. package/src/components/lib/navigation/SubNavbarPills.astro +102 -0
  144. package/src/components/lib/navigation/index.ts +3 -0
  145. package/src/components/lib/overlays/CookiePreferencesDrawer.astro +226 -0
  146. package/src/components/lib/overlays/FloatingFeedbackWidget.astro +203 -0
  147. package/src/components/lib/overlays/ImageCompareSlider.astro +172 -0
  148. package/src/components/lib/overlays/index.ts +3 -0
  149. package/src/components/lib/sections/AgencyContactBrief.astro +194 -0
  150. package/src/components/lib/sections/AgencyFooterCta.astro +94 -0
  151. package/src/components/lib/sections/AgencyHeader.astro +161 -0
  152. package/src/components/lib/sections/AgencyHeroKinetic.astro +172 -0
  153. package/src/components/lib/sections/AgencyServicesList.astro +113 -0
  154. package/src/components/lib/sections/AgencyTeamGrid.astro +111 -0
  155. package/src/components/lib/sections/AgencyWorkGrid.astro +147 -0
  156. package/src/components/lib/sections/BlogGridSection.astro +133 -0
  157. package/src/components/lib/sections/ButtonsShowcase.astro +20 -18
  158. package/src/components/lib/sections/CardsShowcase.astro +28 -26
  159. package/src/components/lib/sections/CareersOpenings.astro +141 -0
  160. package/src/components/lib/sections/ChangelogSection.astro +151 -0
  161. package/src/components/lib/sections/ContactGlassForm.astro +10 -8
  162. package/src/components/lib/sections/ContactSplitMap.astro +141 -0
  163. package/src/components/lib/sections/CtaAppDownload.astro +13 -11
  164. package/src/components/lib/sections/CtaGlowBanner.astro +12 -10
  165. package/src/components/lib/sections/CtaNewsletterSection.astro +165 -0
  166. package/src/components/lib/sections/CtaSplitImage.astro +149 -0
  167. package/src/components/lib/sections/DataShowcase.astro +23 -21
  168. package/src/components/lib/sections/FaqAccordionSection.astro +8 -8
  169. package/src/components/lib/sections/FaqTwoColumn.astro +121 -0
  170. package/src/components/lib/sections/FeatureAlternatingRows.astro +192 -0
  171. package/src/components/lib/sections/FeatureBentoGrid.astro +13 -11
  172. package/src/components/lib/sections/FeatureIconGrid.astro +155 -0
  173. package/src/components/lib/sections/FeatureTabsShowcase.astro +182 -0
  174. package/src/components/lib/sections/FeatureTimelineFlow.astro +8 -6
  175. package/src/components/lib/sections/FeedbackShowcase.astro +15 -13
  176. package/src/components/lib/sections/FooterMegaSection.astro +157 -0
  177. package/src/components/lib/sections/FormsShowcase.astro +22 -20
  178. package/src/components/lib/sections/HeroDashboardPreview.astro +182 -0
  179. package/src/components/lib/sections/HeroGlass.astro +22 -20
  180. package/src/components/lib/sections/HeroGradient.astro +15 -13
  181. package/src/components/lib/sections/HeroMinimalCentered.astro +175 -0
  182. package/src/components/lib/sections/HeroShowcaseSplit.astro +306 -0
  183. package/src/components/lib/sections/HeroSplit.astro +13 -11
  184. package/src/components/lib/sections/HeroTerminal.astro +253 -0
  185. package/src/components/lib/sections/HeroWaitlist.astro +231 -0
  186. package/src/components/lib/sections/HowItWorksSteps.astro +180 -0
  187. package/src/components/lib/sections/IntegrationsGrid.astro +167 -0
  188. package/src/components/lib/sections/LayoutShowcase.astro +21 -19
  189. package/src/components/lib/sections/LogoCloudSection.astro +96 -0
  190. package/src/components/lib/sections/LogoWallGrid.astro +93 -0
  191. package/src/components/lib/sections/NavigationShowcase.astro +15 -13
  192. package/src/components/lib/sections/OverlaysShowcase.astro +21 -19
  193. package/src/components/lib/sections/PricingComparisonMatrix.astro +15 -13
  194. package/src/components/lib/sections/PricingSinglePlan.astro +159 -0
  195. package/src/components/lib/sections/PricingTierCards.astro +11 -9
  196. package/src/components/lib/sections/PricingToggleCards.astro +171 -0
  197. package/src/components/lib/sections/ShopCategoryHero.astro +103 -0
  198. package/src/components/lib/sections/ShopCollectionMosaic.astro +131 -0
  199. package/src/components/lib/sections/ShopFooterWordmark.astro +100 -0
  200. package/src/components/lib/sections/ShopHeaderEditorial.astro +396 -0
  201. package/src/components/lib/sections/ShopHeroEditorial.astro +151 -0
  202. package/src/components/lib/sections/ShopLookbook.astro +250 -0
  203. package/src/components/lib/sections/ShopNewsletterEditorial.astro +128 -0
  204. package/src/components/lib/sections/ShopProductRail.astro +150 -0
  205. package/src/components/lib/sections/ShopReviewsEditorial.astro +133 -0
  206. package/src/components/lib/sections/ShopValuesMarquee.astro +91 -0
  207. package/src/components/lib/sections/StatsCounterBand.astro +124 -0
  208. package/src/components/lib/sections/StatsDashboardSection.astro +85 -0
  209. package/src/components/lib/sections/StatsMetricShowcase.astro +9 -7
  210. package/src/components/lib/sections/TeamGridSection.astro +12 -10
  211. package/src/components/lib/sections/TestimonialCarousel.astro +167 -0
  212. package/src/components/lib/sections/TestimonialFeatured.astro +122 -0
  213. package/src/components/lib/sections/TestimonialGridSection.astro +188 -0
  214. package/src/components/lib/sections/TestimonialWallGlass.astro +11 -9
  215. package/src/components/lib/sections/TypographyShowcase.astro +10 -8
  216. package/src/components/lib/sections/index.ts +39 -0
  217. package/src/components/lib/templates/ecommerce/StoreCategoryPage.astro +160 -0
  218. package/src/components/lib/templates/ecommerce/StoreCheckoutPage.astro +98 -0
  219. package/src/components/lib/templates/ecommerce/StoreHomePage.astro +191 -0
  220. package/src/components/lib/templates/ecommerce/StoreLayout.astro +169 -0
  221. package/src/components/lib/templates/ecommerce/StoreProductPage.astro +165 -0
  222. package/src/components/lib/templates/ecommerce/data.ts +187 -0
  223. package/src/components/lib/templates/ecommerce/index.ts +14 -0
  224. package/src/components/lib/templates/ecommerce-editorial/EditorialBagPage.astro +52 -0
  225. package/src/components/lib/templates/ecommerce-editorial/EditorialCheckoutPage.astro +123 -0
  226. package/src/components/lib/templates/ecommerce-editorial/EditorialHomePage.astro +102 -0
  227. package/src/components/lib/templates/ecommerce-editorial/EditorialLayout.astro +216 -0
  228. package/src/components/lib/templates/ecommerce-editorial/EditorialProductPage.astro +205 -0
  229. package/src/components/lib/templates/ecommerce-editorial/EditorialShopPage.astro +112 -0
  230. package/src/components/lib/templates/ecommerce-editorial/data.ts +125 -0
  231. package/src/components/lib/templates/ecommerce-editorial/index.ts +16 -0
  232. package/src/components/lib/templates/saas-hotel/AppLayout.astro +83 -0
  233. package/src/components/lib/templates/saas-hotel/DashboardPage.astro +66 -0
  234. package/src/components/lib/templates/saas-hotel/GuestsPage.astro +65 -0
  235. package/src/components/lib/templates/saas-hotel/LandingPage.astro +213 -0
  236. package/src/components/lib/templates/saas-hotel/ReportsPage.astro +63 -0
  237. package/src/components/lib/templates/saas-hotel/ReservationsPage.astro +49 -0
  238. package/src/components/lib/templates/saas-hotel/RoomsPage.astro +48 -0
  239. package/src/components/lib/templates/saas-hotel/SettingsPage.astro +115 -0
  240. package/src/components/lib/templates/saas-hotel/SignInPage.astro +50 -0
  241. package/src/components/lib/templates/saas-hotel/data.ts +229 -0
  242. package/src/components/lib/templates/saas-hotel/index.ts +19 -0
  243. package/src/components/lib/templates/saas-hotel/lobby-theme.css +90 -0
  244. package/src/components/lib/typography/AnimatedCounterText.astro +102 -0
  245. package/src/components/lib/typography/CurvedPathHeading.astro +86 -0
  246. package/src/components/lib/typography/GradientTextMarquee.astro +105 -0
  247. package/src/components/lib/typography/HackerDecodeText.astro +85 -0
  248. package/src/components/lib/typography/index.ts +4 -0
  249. package/src/styles/global.css +113 -1
@@ -0,0 +1,85 @@
1
+ ---
2
+ /**
3
+ * PolarAreaChart — Equal-angle wedges whose radius encodes value (area-correct
4
+ * via square-root scaling). Good for cyclical categories like months or hours.
5
+ *
6
+ * @param {Array<{label: string, value: number}>} items - Wedges (max 12).
7
+ * @param {string} color - Wedge hue (single series).
8
+ * @param {string} suffix - Value suffix.
9
+ * @param {string} variant - Card style (glass|solid|ghost).
10
+ * @param {string} size - Density (sm|md|lg).
11
+ */
12
+ import ChartFrame from './ChartFrame.astro';
13
+ import { arcPath, polar, formatValue } from './chart-utils';
14
+
15
+ interface Props {
16
+ title?: string;
17
+ subtitle?: string;
18
+ items?: Array<{ label: string; value: number }>;
19
+ color?: string;
20
+ prefix?: string;
21
+ suffix?: string;
22
+ variant?: 'glass' | 'solid' | 'ghost';
23
+ size?: 'sm' | 'md' | 'lg';
24
+ className?: string;
25
+ }
26
+
27
+ const {
28
+ title = 'Orders by month',
29
+ subtitle = 'Seasonality at a glance',
30
+ items = [
31
+ { label: 'Jan', value: 320 }, { label: 'Feb', value: 280 }, { label: 'Mar', value: 360 },
32
+ { label: 'Apr', value: 410 }, { label: 'May', value: 470 }, { label: 'Jun', value: 520 },
33
+ { label: 'Jul', value: 490 }, { label: 'Aug', value: 450 }, { label: 'Sep', value: 430 },
34
+ { label: 'Oct', value: 510 }, { label: 'Nov', value: 690 }, { label: 'Dec', value: 820 },
35
+ ],
36
+ color = 'var(--chart-7, #9085e9)',
37
+ prefix = '',
38
+ suffix = '',
39
+ variant = 'glass',
40
+ size = 'md',
41
+ className = '',
42
+ } = Astro.props;
43
+
44
+ const S = 320;
45
+ const c = S / 2;
46
+ const R = S / 2 - 26;
47
+ const max = Math.max(...items.map((i) => i.value)) || 1;
48
+ const n = Math.min(items.length, 12);
49
+ const sweep = (Math.PI * 2) / n;
50
+ const rings = [0.25, 0.5, 0.75, 1];
51
+ const fmt = (v: number) => formatValue(v, prefix, suffix);
52
+ ---
53
+
54
+ <ChartFrame
55
+ title={title}
56
+ subtitle={subtitle}
57
+ variant={variant}
58
+ size={size}
59
+ className={className}
60
+ table={{ headers: ['Item', 'Value'], rows: items.map((i) => [i.label, fmt(i.value)]) }}
61
+ >
62
+ <div class="pa">
63
+ <svg viewBox={`0 0 ${S} ${S}`} role="img" aria-label={`${title}: polar area chart of ${n} categories`}>
64
+ {rings.map((f) => <circle class="cf-grid" cx={c} cy={c} r={R * Math.sqrt(f)} fill="none" />)}
65
+ {items.slice(0, n).map((it, i) => {
66
+ const r = R * Math.sqrt(it.value / max);
67
+ const a0 = i * sweep + 0.012;
68
+ const a1 = (i + 1) * sweep - 0.012;
69
+ const [lx, ly] = polar(c, c, R + 14, i * sweep + sweep / 2);
70
+ const isMax = it.value === max;
71
+ return (
72
+ <g>
73
+ <path class="cf-mark cf-dim" d={arcPath(c, c, r, 0, a0, a1)} fill={color} fill-opacity={0.35 + 0.6 * (it.value / max)}
74
+ tabindex="0" role="img" aria-label={`${it.label}: ${fmt(it.value)}`} data-tip-title={it.label} data-tip={`${color}::${fmt(it.value)}`} />
75
+ <text class={isMax ? 'cf-label cf-label--strong' : 'cf-label'} x={lx} y={ly + 4} text-anchor="middle">{it.label}</text>
76
+ </g>
77
+ );
78
+ })}
79
+ </svg>
80
+ </div>
81
+ </ChartFrame>
82
+
83
+ <style>
84
+ .pa { max-width: 360px; margin: 0 auto; }
85
+ </style>
@@ -0,0 +1,122 @@
1
+ ---
2
+ /**
3
+ * PopulationPyramid — Back-to-back horizontal bars comparing two groups across
4
+ * ordered bands (age groups, tiers, price brackets).
5
+ *
6
+ * @param {string[]} bands - Row labels, top to bottom.
7
+ * @param {number[]} left - Values for the left group.
8
+ * @param {number[]} right - Values for the right group.
9
+ * @param {string} leftLabel - Left group name.
10
+ * @param {string} rightLabel - Right group name.
11
+ * @param {string} suffix - Value suffix.
12
+ * @param {string} variant - Card style (glass|solid|ghost).
13
+ * @param {string} size - Density (sm|md|lg).
14
+ */
15
+ import ChartFrame from './ChartFrame.astro';
16
+ import { formatValue, colorAt } from './chart-utils';
17
+
18
+ interface Props {
19
+ title?: string;
20
+ subtitle?: string;
21
+ bands?: string[];
22
+ left?: number[];
23
+ right?: number[];
24
+ leftLabel?: string;
25
+ rightLabel?: string;
26
+ prefix?: string;
27
+ suffix?: string;
28
+ colors?: string[];
29
+ variant?: 'glass' | 'solid' | 'ghost';
30
+ size?: 'sm' | 'md' | 'lg';
31
+ className?: string;
32
+ }
33
+
34
+ const {
35
+ title = 'Audience by age',
36
+ subtitle = 'Share of users per age band',
37
+ bands = ['65+', '55–64', '45–54', '35–44', '25–34', '18–24'],
38
+ left = [2.1, 4.8, 8.2, 12.6, 15.4, 7.9],
39
+ right = [1.7, 4.1, 7.4, 11.8, 16.2, 8.8],
40
+ leftLabel = 'Women',
41
+ rightLabel = 'Men',
42
+ prefix = '',
43
+ suffix = '%',
44
+ colors,
45
+ variant = 'glass',
46
+ size = 'md',
47
+ className = '',
48
+ } = Astro.props;
49
+
50
+ const max = Math.max(...left, ...right) || 1;
51
+ const cl = colorAt(0, colors);
52
+ const cr = colorAt(1, colors);
53
+ const fmt = (v: number) => formatValue(v, prefix, suffix);
54
+ ---
55
+
56
+ <ChartFrame
57
+ title={title}
58
+ subtitle={subtitle}
59
+ variant={variant}
60
+ size={size}
61
+ className={className}
62
+ legend={[{ label: leftLabel, color: cl }, { label: rightLabel, color: cr }]}
63
+ table={{ headers: ['Band', leftLabel, rightLabel], rows: bands.map((b, i) => [b, fmt(left[i]), fmt(right[i])]) }}
64
+ >
65
+ <div class="pp" role="list">
66
+ {bands.map((b, i) => (
67
+ <div class="pp__row" role="listitem">
68
+ <div class="pp__side pp__side--l">
69
+ <span class="pp__val">{fmt(left[i])}</span>
70
+ <span class="pp__bar cf-mark" tabindex="0" style={`width:${(left[i] / max) * 100}%; background:${cl}`}
71
+ aria-label={`${b}, ${leftLabel}: ${fmt(left[i])}`} data-tip-title={`${b} · ${leftLabel}`} data-tip={`${cl}::${fmt(left[i])}`}></span>
72
+ </div>
73
+ <span class="pp__band">{b}</span>
74
+ <div class="pp__side pp__side--r">
75
+ <span class="pp__bar cf-mark" tabindex="0" style={`width:${(right[i] / max) * 100}%; background:${cr}`}
76
+ aria-label={`${b}, ${rightLabel}: ${fmt(right[i])}`} data-tip-title={`${b} · ${rightLabel}`} data-tip={`${cr}::${fmt(right[i])}`}></span>
77
+ <span class="pp__val">{fmt(right[i])}</span>
78
+ </div>
79
+ </div>
80
+ ))}
81
+ </div>
82
+ </ChartFrame>
83
+
84
+ <style>
85
+ .pp { display: grid; gap: 4px; }
86
+ .pp__row {
87
+ display: grid;
88
+ grid-template-columns: 1fr 4rem 1fr;
89
+ align-items: center;
90
+ gap: 0.5rem;
91
+ }
92
+ .pp__side {
93
+ display: flex;
94
+ align-items: center;
95
+ gap: 0.5rem;
96
+ height: 24px;
97
+ }
98
+ .pp__side--l { justify-content: flex-end; }
99
+ .pp__bar {
100
+ height: 100%;
101
+ min-width: 2px;
102
+ max-width: calc(100% - 3rem);
103
+ transition: opacity 0.2s ease;
104
+ }
105
+ .pp__side--l .pp__bar { border-radius: 4px 0 0 4px; }
106
+ .pp__side--r .pp__bar { border-radius: 0 4px 4px 0; }
107
+ .pp__bar:hover { opacity: 0.85; }
108
+ .pp__bar:focus-visible { outline: 2px solid var(--c-text-1, #fff); outline-offset: 1px; }
109
+ .pp__band {
110
+ text-align: center;
111
+ font-size: 0.75rem;
112
+ font-weight: 600;
113
+ color: var(--c-text-1, #fff);
114
+ }
115
+ .pp__val {
116
+ font-size: 0.75rem;
117
+ color: var(--c-text-2, #a3a3a3);
118
+ font-variant-numeric: tabular-nums;
119
+ min-width: 2.5rem;
120
+ }
121
+ .pp__side--l .pp__val { text-align: right; }
122
+ </style>
@@ -0,0 +1,89 @@
1
+ ---
2
+ /**
3
+ * PunchCardChart — Day × hour dot matrix where dot area encodes volume.
4
+ * Classic "when are people active" view.
5
+ *
6
+ * @param {string[]} days - Row labels.
7
+ * @param {number[]} hours - Column hours (0–23).
8
+ * @param {number[][]} values - values[day][hourIndex].
9
+ * @param {string} unit - Unit noun for tooltips (e.g. 'commits').
10
+ * @param {string} color - Dot color.
11
+ * @param {string} variant - Card style (glass|solid|ghost).
12
+ * @param {string} size - Density (sm|md|lg).
13
+ */
14
+ import ChartFrame from './ChartFrame.astro';
15
+
16
+ interface Props {
17
+ title?: string;
18
+ subtitle?: string;
19
+ days?: string[];
20
+ hours?: number[];
21
+ values?: number[][];
22
+ unit?: string;
23
+ color?: string;
24
+ variant?: 'glass' | 'solid' | 'ghost';
25
+ size?: 'sm' | 'md' | 'lg';
26
+ className?: string;
27
+ }
28
+
29
+ const defaultHours = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22];
30
+ const shape = [0.1, 0.05, 0.02, 0.1, 0.6, 0.95, 0.8, 1, 0.9, 0.55, 0.35, 0.2];
31
+ const dayW = [0.9, 1, 1, 0.95, 0.75, 0.25, 0.2];
32
+
33
+ const {
34
+ title = 'Deploy activity',
35
+ subtitle = 'Deploys by weekday and hour (UTC)',
36
+ days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
37
+ hours = defaultHours,
38
+ values = dayW.map((d, di) => shape.map((s, hi) => Math.round(d * s * 42 + ((di * 7 + hi * 3) % 5)))),
39
+ unit = 'deploys',
40
+ color = 'var(--chart-3, #199e70)',
41
+ variant = 'glass',
42
+ size = 'md',
43
+ className = '',
44
+ } = Astro.props;
45
+
46
+ const W = 640;
47
+ const m = { t: 10, r: 10, b: 26, l: 46 };
48
+ const cols = hours.length;
49
+ const cellW = (W - m.l - m.r) / cols;
50
+ const cellH = 34;
51
+ const H = m.t + days.length * cellH + m.b;
52
+ const max = Math.max(...values.flat()) || 1;
53
+ const rMax = Math.min(cellW, cellH) / 2 - 2;
54
+ const pad = (h: number) => `${String(h).padStart(2, '0')}:00`;
55
+ const total = values.flat().reduce((a, b) => a + b, 0);
56
+ ---
57
+
58
+ <ChartFrame
59
+ title={title}
60
+ subtitle={subtitle}
61
+ value={total.toLocaleString('en-US')}
62
+ delta={`total ${unit}`}
63
+ variant={variant}
64
+ size={size}
65
+ className={className}
66
+ table={{ headers: ['Day', ...hours.map(pad)], rows: days.map((d, di) => [d, ...values[di]]) }}
67
+ >
68
+ <svg viewBox={`0 0 ${W} ${H}`} role="img" aria-label={`${title}: punch card of ${unit} by day and hour`}>
69
+ {days.map((d, di) => (
70
+ <g>
71
+ <line class="cf-grid" x1={m.l} x2={W - m.r} y1={m.t + di * cellH + cellH / 2} y2={m.t + di * cellH + cellH / 2} />
72
+ <text class="cf-label" x={m.l - 10} y={m.t + di * cellH + cellH / 2 + 4} text-anchor="end">{d}</text>
73
+ </g>
74
+ ))}
75
+ {hours.map((h, hi) => <text class="cf-label" x={m.l + hi * cellW + cellW / 2} y={H - 8} text-anchor="middle">{pad(h)}</text>)}
76
+ {days.map((d, di) => hours.map((h, hi) => {
77
+ const v = values[di]?.[hi] ?? 0;
78
+ const rad = v ? 2 + Math.sqrt(v / max) * (rMax - 2) : 0;
79
+ const cx = m.l + hi * cellW + cellW / 2;
80
+ const cy = m.t + di * cellH + cellH / 2;
81
+ return (
82
+ <g class="cf-mark cf-dim" tabindex="0" role="img" aria-label={`${d} ${pad(h)}: ${v} ${unit}`} data-tip-title={`${d} · ${pad(h)}`} data-tip={`${color}::${v} ${unit}`}>
83
+ <rect x={cx - cellW / 2} y={cy - cellH / 2} width={cellW} height={cellH} fill="transparent" />
84
+ {rad > 0 && <circle cx={cx} cy={cy} r={rad} fill={color} fill-opacity={0.35 + (v / max) * 0.65} />}
85
+ </g>
86
+ );
87
+ }))}
88
+ </svg>
89
+ </ChartFrame>
@@ -0,0 +1,94 @@
1
+ ---
2
+ /**
3
+ * RadarChart — Pure SVG spider chart comparing up to 3 entities across 5–8
4
+ * axes on a shared 0–max scale, with value tooltips on every vertex.
5
+ *
6
+ * @param {string[]} axes - Attribute names (5–8 recommended).
7
+ * @param {Array<{name: string, data: number[]}>} series - Entities (max 3).
8
+ * @param {number} max - Scale maximum.
9
+ * @param {string[]} colors - Optional per-series colors.
10
+ * @param {string} variant - Card style (glass|solid|ghost).
11
+ * @param {string} size - Density (sm|md|lg).
12
+ */
13
+ import ChartFrame from './ChartFrame.astro';
14
+ import { polar, linePath, colorAt, type Point } from './chart-utils';
15
+
16
+ interface Props {
17
+ title?: string;
18
+ subtitle?: string;
19
+ axes?: string[];
20
+ series?: Array<{ name: string; data: number[] }>;
21
+ max?: number;
22
+ colors?: string[];
23
+ variant?: 'glass' | 'solid' | 'ghost';
24
+ size?: 'sm' | 'md' | 'lg';
25
+ className?: string;
26
+ }
27
+
28
+ const {
29
+ title = 'Framework comparison',
30
+ subtitle = 'Scored 0–10 by the team',
31
+ axes = ['Performance', 'DX', 'Ecosystem', 'SEO', 'Learning curve', 'Flexibility'],
32
+ series = [
33
+ { name: 'Astro', data: [9.5, 8.5, 7, 9.5, 8.5, 8] },
34
+ { name: 'Next', data: [7.5, 8, 9.5, 8, 6.5, 8.5] },
35
+ ],
36
+ max = 10,
37
+ colors,
38
+ variant = 'glass',
39
+ size = 'md',
40
+ className = '',
41
+ } = Astro.props;
42
+
43
+ const S = 340;
44
+ const c = S / 2;
45
+ const R = S / 2 - 54;
46
+ const n = axes.length;
47
+ const ang = (i: number) => (i / n) * Math.PI * 2;
48
+ const levels = [0.25, 0.5, 0.75, 1];
49
+ const shapes = series.slice(0, 3).map((s, si) => {
50
+ const pts: Point[] = s.data.map((v, i) => polar(c, c, R * Math.max(0, Math.min(1, v / max)), ang(i)));
51
+ return { ...s, pts, d: `${linePath(pts)} Z`, color: colorAt(si, colors) };
52
+ });
53
+ ---
54
+
55
+ <ChartFrame
56
+ title={title}
57
+ subtitle={subtitle}
58
+ variant={variant}
59
+ size={size}
60
+ className={className}
61
+ legend={shapes.map((s) => ({ label: s.name, color: s.color }))}
62
+ table={{ headers: ['Axis', ...series.map((s) => s.name)], rows: axes.map((a, i) => [a, ...series.map((s) => s.data[i])]) }}
63
+ >
64
+ <div class="rd">
65
+ <svg viewBox={`0 0 ${S} ${S}`} role="img" aria-label={`${title}: radar chart across ${n} axes`}>
66
+ {levels.map((l) => (
67
+ <path class="cf-grid" fill="none" d={`${linePath(axes.map((_, i) => polar(c, c, R * l, ang(i))))} Z`} />
68
+ ))}
69
+ {axes.map((a, i) => {
70
+ const [x, y] = polar(c, c, R, ang(i));
71
+ const [lx, ly] = polar(c, c, R + 18, ang(i));
72
+ const anchor = 4 > Math.abs(lx - c) ? 'middle' : lx > c ? 'start' : 'end';
73
+ return (
74
+ <g>
75
+ <line class="cf-grid" x1={c} y1={c} x2={x} y2={y} />
76
+ <text class="cf-label" x={lx} y={ly + 4} text-anchor={anchor}>{a}</text>
77
+ </g>
78
+ );
79
+ })}
80
+ {shapes.map((s) => (
81
+ <path d={s.d} fill={s.color} fill-opacity="0.18" stroke={s.color} stroke-width="2" stroke-linejoin="round" aria-hidden="true" />
82
+ ))}
83
+ {shapes.map((s) => s.pts.map(([x, y], i) => (
84
+ <circle class="cf-mark" cx={x} cy={y} r="4.5" fill={s.color} stroke="var(--chart-surface, #0d0d0f)" stroke-width="2"
85
+ tabindex="0" role="img" aria-label={`${s.name}, ${axes[i]}: ${s.data[i]} of ${max}`}
86
+ data-tip-title={axes[i]} data-tip={shapes.map((o) => `${o.color}::${o.name}: ${o.data[i]}`).join('|')} />
87
+ )))}
88
+ </svg>
89
+ </div>
90
+ </ChartFrame>
91
+
92
+ <style>
93
+ .rd { max-width: 400px; margin: 0 auto; }
94
+ </style>
@@ -0,0 +1,168 @@
1
+ ---
2
+ /**
3
+ * RadarMetricChart — Pure SVG radar / spider chart for multi-axis skill and performance visualization.
4
+ *
5
+ * @param {string} title - Chart title. Default is 'Performance Metrics'.
6
+ * @param {Array<{axis: string, value: number}>} data - 5 or 6 axis points (values 0 to 100).
7
+ * @param {string} color - Primary chart fill & stroke color. Default is '#d4a017'.
8
+ * @param {number} size - Pixel width/height of the radar. Default is 320.
9
+ */
10
+ interface Props {
11
+ title?: string;
12
+ data?: Array<{ axis: string; value: number }>;
13
+ color?: string;
14
+ size?: number;
15
+ }
16
+
17
+ const {
18
+ title = 'System Capabilities',
19
+ data = [
20
+ { axis: 'Speed', value: 95 },
21
+ { axis: 'A11y', value: 98 },
22
+ { axis: 'SEO', value: 100 },
23
+ { axis: 'Design', value: 92 },
24
+ { axis: 'Zero-JS', value: 96 },
25
+ { axis: 'Security', value: 90 },
26
+ ],
27
+ color = '#d4a017',
28
+ size = 320,
29
+ } = Astro.props;
30
+
31
+ const center = size / 2;
32
+ const radius = (size - 70) / 2;
33
+ const totalAxes = data.length;
34
+ const angleSlice = (Math.PI * 2) / totalAxes;
35
+
36
+ // Concentric polygon grid levels (20%, 40%, 60%, 80%, 100%)
37
+ const levels = [0.2, 0.4, 0.6, 0.8, 1.0];
38
+ const gridPolygons = levels.map((lvl) => {
39
+ return data
40
+ .map((_, i) => {
41
+ const r = radius * lvl;
42
+ const x = center + r * Math.cos(angleSlice * i - Math.PI / 2);
43
+ const y = center + r * Math.sin(angleSlice * i - Math.PI / 2);
44
+ return `${x},${y}`;
45
+ })
46
+ .join(' ');
47
+ });
48
+
49
+ // Data polygon points
50
+ const dataPoints = data
51
+ .map((d, i) => {
52
+ const r = radius * (Math.min(100, Math.max(0, d.value)) / 100);
53
+ const x = center + r * Math.cos(angleSlice * i - Math.PI / 2);
54
+ const y = center + r * Math.sin(angleSlice * i - Math.PI / 2);
55
+ return `${x},${y}`;
56
+ })
57
+ .join(' ');
58
+ ---
59
+
60
+ <div class="radar-card" style={`--radar-size: ${size}px; --radar-color: ${color};`}>
61
+ {title && <h5 class="radar-card__title">{title}</h5>}
62
+
63
+ <div class="radar-card__wrap">
64
+ <svg width={size} height={size} class="radar-card__svg">
65
+ <!-- Background concentric webs -->
66
+ {gridPolygons.map((pts) => (
67
+ <polygon points={pts} class="radar-web" />
68
+ ))}
69
+
70
+ <!-- Axes radial spokes -->
71
+ {data.map((_, i) => {
72
+ const x2 = center + radius * Math.cos(angleSlice * i - Math.PI / 2);
73
+ const y2 = center + radius * Math.sin(angleSlice * i - Math.PI / 2);
74
+ return <line x1={center} y1={center} x2={x2} y2={y2} class="radar-spoke" />;
75
+ })}
76
+
77
+ <!-- Data polygon -->
78
+ <polygon points={dataPoints} class="radar-data" />
79
+
80
+ <!-- Data nodes -->
81
+ {data.map((d, i) => {
82
+ const r = radius * (d.value / 100);
83
+ const cx = center + r * Math.cos(angleSlice * i - Math.PI / 2);
84
+ const cy = center + r * Math.sin(angleSlice * i - Math.PI / 2);
85
+ return <circle cx={cx} cy={cy} r="4" class="radar-node" />;
86
+ })}
87
+
88
+ <!-- Axis Labels -->
89
+ {data.map((d, i) => {
90
+ const labelRadius = radius + 22;
91
+ const lx = center + labelRadius * Math.cos(angleSlice * i - Math.PI / 2);
92
+ const ly = center + labelRadius * Math.sin(angleSlice * i - Math.PI / 2);
93
+ return (
94
+ <text
95
+ x={lx}
96
+ y={ly}
97
+ class="radar-label"
98
+ text-anchor="middle"
99
+ dominant-baseline="central"
100
+ >
101
+ {d.axis} ({d.value})
102
+ </text>
103
+ );
104
+ })}
105
+ </svg>
106
+ </div>
107
+ </div>
108
+
109
+ <style>
110
+ .radar-card {
111
+ display: flex;
112
+ flex-direction: column;
113
+ align-items: center;
114
+ padding: var(--sp-6, 1.5rem);
115
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.02);
116
+ backdrop-filter: blur(16px);
117
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08);
118
+ border-radius: var(--r-xl, 24px);
119
+ width: 100%;
120
+ max-width: fit-content;
121
+ font-family: var(--font-sans, system-ui, sans-serif);
122
+ }
123
+
124
+ .radar-card__title {
125
+ font-size: 0.95rem;
126
+ font-weight: 800;
127
+ color: var(--c-text-1, #ffffff);
128
+ margin: 0 0 12px 0;
129
+ }
130
+
131
+ .radar-card__wrap {
132
+ display: flex;
133
+ align-items: center;
134
+ justify-content: center;
135
+ }
136
+
137
+ .radar-web {
138
+ fill: none;
139
+ stroke: rgb(var(--ink-rgb, 255 255 255) / 0.08);
140
+ stroke-width: 1;
141
+ }
142
+
143
+ .radar-spoke {
144
+ stroke: rgb(var(--ink-rgb, 255 255 255) / 0.08);
145
+ stroke-width: 1;
146
+ }
147
+
148
+ .radar-data {
149
+ fill: rgba(184, 134, 11, 0.25);
150
+ stroke: var(--radar-color);
151
+ stroke-width: 2.5;
152
+ transition: all 0.4s ease;
153
+ }
154
+
155
+ .radar-node {
156
+ fill: var(--radar-color);
157
+ stroke: #050505;
158
+ stroke-width: 2;
159
+ }
160
+
161
+ .radar-label {
162
+ fill: var(--c-text-2, #a3a3a3);
163
+ font-size: 10.5px;
164
+ font-weight: 700;
165
+ text-transform: uppercase;
166
+ letter-spacing: 0.05em;
167
+ }
168
+ </style>
@@ -0,0 +1,124 @@
1
+ ---
2
+ /**
3
+ * RadialBarChart — Concentric progress arcs (one per item) sharing a 0–100%
4
+ * scale. Labels sit at each arc's start so identity never relies on color.
5
+ *
6
+ * @param {Array<{label: string, value: number, max?: number}>} items - Values (default max 100).
7
+ * @param {number} sweep - Arc sweep in degrees (180–360).
8
+ * @param {string[]} colors - Optional per-item colors.
9
+ * @param {string} variant - Card style (glass|solid|ghost).
10
+ * @param {string} size - Density (sm|md|lg).
11
+ */
12
+ import ChartFrame from './ChartFrame.astro';
13
+ import { arcStroke, colorAt } from './chart-utils';
14
+
15
+ interface Props {
16
+ title?: string;
17
+ subtitle?: string;
18
+ items?: Array<{ label: string; value: number; max?: number }>;
19
+ sweep?: number;
20
+ colors?: string[];
21
+ variant?: 'glass' | 'solid' | 'ghost';
22
+ size?: 'sm' | 'md' | 'lg';
23
+ className?: string;
24
+ }
25
+
26
+ const {
27
+ title = 'Feature adoption',
28
+ subtitle = 'Share of active workspaces',
29
+ items = [
30
+ { label: 'Dashboards', value: 86 },
31
+ { label: 'Alerts', value: 64 },
32
+ { label: 'API', value: 47 },
33
+ { label: 'SSO', value: 29 },
34
+ ],
35
+ sweep = 270,
36
+ colors,
37
+ variant = 'glass',
38
+ size = 'md',
39
+ className = '',
40
+ } = Astro.props;
41
+
42
+ const S = 300;
43
+ const cx = S / 2;
44
+ const cy = S / 2;
45
+ const stroke = 16;
46
+ const gap = 8;
47
+ const outer = S / 2 - 14;
48
+ const sweepRad = (Math.min(360, Math.max(180, sweep)) * Math.PI) / 180;
49
+ const start = -sweepRad / 2 - (sweep === 360 ? 0 : 0);
50
+ const rings = items.slice(0, 8).map((it, i) => {
51
+ const r = outer - i * (stroke + gap);
52
+ const pct = Math.max(0, Math.min(1, it.value / (it.max ?? 100)));
53
+ return { ...it, r, pct, color: colorAt(i, colors) };
54
+ });
55
+ ---
56
+
57
+ <ChartFrame
58
+ title={title}
59
+ subtitle={subtitle}
60
+ variant={variant}
61
+ size={size}
62
+ className={className}
63
+ table={{ headers: ['Item', 'Value'], rows: items.map((i) => [i.label, `${i.value}${i.max ? ` / ${i.max}` : '%'}`]) }}
64
+ >
65
+ <div class="rb">
66
+ <svg viewBox={`0 0 ${S} ${S}`} role="img" aria-label={`${title}: radial bar chart`}>
67
+ {rings.map((r) => (
68
+ <g class="cf-mark cf-dim" tabindex="0" role="img" aria-label={`${r.label}: ${Math.round(r.pct * 100)}%`}
69
+ data-tip-title={r.label} data-tip={`${r.color}::${r.value}${r.max ? ` / ${r.max}` : '%'}`}>
70
+ <path d={arcStroke(cx, cy, r.r, start, start + sweepRad)} fill="none" stroke="rgb(var(--ink-rgb, 255 255 255) / 0.06)" stroke-width={stroke} stroke-linecap="round" />
71
+ {r.pct > 0 && (
72
+ <path class="rb__arc" d={arcStroke(cx, cy, r.r, start, start + sweepRad * r.pct)} fill="none" stroke={r.color} stroke-width={stroke} stroke-linecap="round" />
73
+ )}
74
+ </g>
75
+ ))}
76
+ </svg>
77
+ <ul class="rb__labels" aria-hidden="true">
78
+ {rings.map((r) => (
79
+ <li><span style={`background:${r.color}`}></span>{r.label}<b>{Math.round(r.pct * 100)}%</b></li>
80
+ ))}
81
+ </ul>
82
+ </div>
83
+ </ChartFrame>
84
+
85
+ <style>
86
+ .rb {
87
+ display: grid;
88
+ grid-template-columns: minmax(0, 300px) 1fr;
89
+ gap: 1.5rem;
90
+ align-items: center;
91
+ }
92
+ .rb__arc {
93
+ stroke-dasharray: 1200;
94
+ stroke-dashoffset: 1200;
95
+ animation: rb-draw 1.1s cubic-bezier(0.4, 0, 0.2, 1) forwards;
96
+ }
97
+ @keyframes rb-draw { to { stroke-dashoffset: 0; } }
98
+ .rb__labels {
99
+ list-style: none;
100
+ margin: 0;
101
+ padding: 0;
102
+ display: grid;
103
+ gap: 0.75rem;
104
+ }
105
+ .rb__labels li {
106
+ display: flex;
107
+ align-items: center;
108
+ gap: 0.6rem;
109
+ font-size: 0.875rem;
110
+ color: var(--c-text-2, #a3a3a3);
111
+ }
112
+ .rb__labels span { width: 10px; height: 10px; border-radius: 3px; flex-shrink: 0; }
113
+ .rb__labels b {
114
+ margin-left: auto;
115
+ color: var(--c-text-1, #fff);
116
+ font-variant-numeric: tabular-nums;
117
+ }
118
+ @media (max-width: 560px) {
119
+ .rb { grid-template-columns: 1fr; }
120
+ }
121
+ @media (prefers-reduced-motion: reduce) {
122
+ .rb__arc { animation: none; stroke-dashoffset: 0; }
123
+ }
124
+ </style>