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,122 @@
1
+ ---
2
+ /**
3
+ * TestimonialFeatured — A single large customer quote paired with the company's
4
+ * headline results (case-study style), plus a link to the full story.
5
+ *
6
+ * @param {string} quote - The testimonial text.
7
+ * @param {string} name - Author name.
8
+ * @param {string} role - Author role and company.
9
+ * @param {string} company - Company wordmark text.
10
+ * @param {Array<{value: string, label: string}>} metrics - Outcome stats.
11
+ * @param {{label: string, href: string}} link - Case study link.
12
+ * @param {string} variant - Layout style (split|centered|card).
13
+ * @param {string} size - Vertical rhythm (sm|md|lg).
14
+ * @param {string} className - Extra CSS class.
15
+ */
16
+ interface Props {
17
+ quote?: string;
18
+ name?: string;
19
+ role?: string;
20
+ company?: string;
21
+ metrics?: Array<{ value: string; label: string }>;
22
+ link?: { label: string; href: string };
23
+ variant?: 'split' | 'centered' | 'card';
24
+ size?: 'sm' | 'md' | 'lg';
25
+ className?: string;
26
+ }
27
+
28
+ const {
29
+ quote = 'We replaced three tools and a chart library with this kit. Our product pages load twice as fast and our conversion rate has never been higher.',
30
+ name = 'Elena Vasquez',
31
+ role = 'VP of Marketing, Orbitly',
32
+ company = 'Orbitly',
33
+ metrics = [
34
+ { value: '2.1×', label: 'faster page loads' },
35
+ { value: '+38%', label: 'trial conversion' },
36
+ { value: '−64%', label: 'front-end bundle size' },
37
+ ],
38
+ link = { label: 'Read the case study', href: '#' },
39
+ variant = 'split',
40
+ size = 'md',
41
+ className = '',
42
+ } = Astro.props;
43
+
44
+ const initials = name.split(' ').map((p) => p[0]).slice(0, 2).join('');
45
+ ---
46
+
47
+ <div class="cq-testimonial-featured-cq">
48
+ <section class={`tf tf--${variant} tf--${size} ${className}`} aria-label={`Customer story: ${company}`}>
49
+ <div class="tf__inner">
50
+ <figure class="tf__figure">
51
+ <p class="tf__company" aria-hidden="true">
52
+ <svg width="22" height="22" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" stroke-width="2" /><circle cx="12" cy="12" r="4" fill="currentColor" /></svg>
53
+ {company}
54
+ </p>
55
+ <svg class="tf__mark" width="48" height="36" viewBox="0 0 48 36" aria-hidden="true"><path d="M0 36V22C0 9 7 2 20 0l2 5C14 7 11 12 11 18h9v18H0zm28 0V22C28 9 35 2 48 0l2 5c-8 2-11 7-11 13h9v18H28z" fill="currentColor" /></svg>
56
+ <blockquote class="tf__quote"><p>{quote}</p></blockquote>
57
+ <figcaption class="tf__author">
58
+ <span class="tf__avatar" aria-hidden="true">{initials}</span>
59
+ <span><strong>{name}</strong><span class="tf__role">{role}</span></span>
60
+ </figcaption>
61
+ </figure>
62
+
63
+ <div class="tf__side">
64
+ <dl class="tf__metrics">
65
+ {metrics.map((mt) => (
66
+ <div class="tf__metric">
67
+ <dt>{mt.label}</dt>
68
+ <dd>{mt.value}</dd>
69
+ </div>
70
+ ))}
71
+ </dl>
72
+ {link && (
73
+ <a class="tf__link" href={link.href}>
74
+ {link.label}
75
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6" /></svg>
76
+ </a>
77
+ )}
78
+ </div>
79
+ </div>
80
+ </section>
81
+ </div>
82
+ <style>
83
+ .cq-testimonial-featured-cq { container-type: inline-size; }
84
+ .tf {
85
+ --tf-py: 5.5rem;
86
+ padding: var(--tf-py) var(--sp-6, 1.5rem);
87
+ background: var(--c-bg, #050505);
88
+ }
89
+ .tf--sm { --tf-py: 3.5rem; }
90
+ .tf--lg { --tf-py: 7.5rem; }
91
+ .tf__inner { max-width: 1120px; margin: 0 auto; display: grid; grid-template-columns: 1.6fr 1fr; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
92
+ .tf--card .tf__inner {
93
+ padding: clamp(2rem, 5vw, 3.5rem);
94
+ border-radius: var(--r-xl, 28px);
95
+ background: radial-gradient(circle at 0% 0%, var(--c-primary-glow, color-mix(in srgb, var(--c-primary, #b8860b) 14%, transparent)), transparent 55%), rgb(var(--ink-rgb, 255 255 255) / 0.025);
96
+ border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.08));
97
+ }
98
+ .tf--centered .tf__inner { grid-template-columns: 1fr; text-align: center; max-width: 860px; }
99
+ .tf--centered .tf__author, .tf--centered .tf__company { justify-content: center; }
100
+ .tf--centered .tf__mark { margin-inline: auto; }
101
+ .tf__figure { margin: 0; }
102
+ .tf__company { display: flex; align-items: center; gap: 0.5rem; margin: 0 0 2rem; font-size: 1.125rem; font-weight: 750; letter-spacing: -0.02em; color: var(--c-text-1, #fff); }
103
+ .tf__mark { display: block; color: var(--c-primary-light, #d4a017); opacity: 0.8; margin-bottom: 1.25rem; }
104
+ .tf__quote { margin: 0 0 2rem; }
105
+ .tf__quote p { margin: 0; font-size: clamp(1.35rem, 2.6vw, 1.875rem); font-weight: 550; line-height: 1.4; letter-spacing: -0.015em; color: var(--c-text-1, #fff); text-wrap: pretty; }
106
+ .tf__author { display: flex; align-items: center; gap: 0.85rem; }
107
+ .tf__author strong { display: block; color: var(--c-text-1, #fff); }
108
+ .tf__role { display: block; font-size: 0.875rem; color: var(--c-text-2, #a3a3a3); }
109
+ .tf__avatar { display: grid; place-items: center; width: 48px; height: 48px; border-radius: 50%; background: linear-gradient(135deg, var(--chart-7, #9085e9), var(--chart-1, #3987e5)); color: #fff; font-weight: 700; }
110
+ .tf__side { display: grid; gap: 1.5rem; }
111
+ .tf__metrics { margin: 0; display: grid; gap: 1px; border-radius: var(--r-lg, 20px); overflow: hidden; background: rgb(var(--ink-rgb, 255 255 255) / 0.07); }
112
+ .tf--centered .tf__metrics { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); }
113
+ .tf__metric { display: flex; flex-direction: column-reverse; gap: 0.25rem; padding: 1.25rem 1.5rem; background: var(--c-bg-1, #0a0a0a); }
114
+ .tf__metric dt { font-size: 0.875rem; color: var(--c-text-2, #a3a3a3); }
115
+ .tf__metric dd { margin: 0; font-size: 2rem; font-weight: 800; letter-spacing: -0.03em; color: var(--c-text-1, #fff); font-variant-numeric: tabular-nums; }
116
+ .tf__link { display: inline-flex; align-items: center; gap: 0.45rem; min-height: 44px; font-weight: 650; color: var(--c-primary-light, #d4a017); text-decoration: none; }
117
+ .tf--centered .tf__link { justify-self: center; }
118
+ .tf__link svg { transition: transform 0.2s ease; }
119
+ .tf__link:hover svg { transform: translateX(3px); }
120
+ .tf__link:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; border-radius: 4px; }
121
+ @container (max-width: 820px) { .tf__inner { grid-template-columns: 1fr; } }
122
+ </style>
@@ -0,0 +1,188 @@
1
+ ---
2
+ /**
3
+ * TestimonialGridSection — Bento grid showcase of verified customer reviews with dark glass styling and star ratings.
4
+ *
5
+ * @param {string} eyebrow - Small uppercase category lead. Default is 'WALL OF LOVE'.
6
+ * @param {string} title - Section main headline. Default is 'Trusted by innovative engineering teams'.
7
+ * @param {string} subtitle - Subtitle description.
8
+ */
9
+ interface Props {
10
+ eyebrow?: string;
11
+ title?: string;
12
+ subtitle?: string;
13
+ }
14
+
15
+ const {
16
+ eyebrow = 'WALL OF LOVE',
17
+ title = 'Trusted by thousands of Astro builders',
18
+ subtitle = 'Discover why developers, indie hackers, and creative agencies rely on Astro Components Kit for production websites.',
19
+ } = Astro.props;
20
+
21
+ const testimonials = [
22
+ {
23
+ name: 'Elena Rostova',
24
+ role: 'Principal Architect @ Veloce Labs',
25
+ avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&auto=format&fit=crop&q=80',
26
+ quote: 'The pure zero-JS philosophy with Astro 5 is game-changing. Our Core Web Vitals score hit 100 on every audited route without tweaking hydration.',
27
+ stars: 5,
28
+ highlight: true,
29
+ },
30
+ {
31
+ name: 'Marcus Vance',
32
+ role: 'Founder @ HyperSync',
33
+ avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&auto=format&fit=crop&q=80',
34
+ quote: 'Copying and pasting self-contained Astro components directly shaved two full sprints off our SaaS launch timeline.',
35
+ stars: 5,
36
+ },
37
+ {
38
+ name: 'Sofia Chen',
39
+ role: 'Senior UI/UX Designer',
40
+ avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100&auto=format&fit=crop&q=80',
41
+ quote: 'The glassmorphic dark palette with gold accents feels extraordinarily premium right out of the box. Highly recommended.',
42
+ stars: 5,
43
+ },
44
+ ];
45
+ ---
46
+
47
+ <section class="testi-section">
48
+ <div class="testi-section__header">
49
+ <span class="testi-section__eyebrow">{eyebrow}</span>
50
+ <h2 class="testi-section__title">{title}</h2>
51
+ <p class="testi-section__subtitle">{subtitle}</p>
52
+ </div>
53
+
54
+ <div class="testi-section__grid">
55
+ {testimonials.map((t) => (
56
+ <article class:list={['testi-card', { 'testi-card--highlight': t.highlight }]}>
57
+ <div class="testi-card__stars" aria-label={`${t.stars} out of 5 stars`}>
58
+ {Array.from({ length: t.stars }).map(() => (
59
+ <span>★</span>
60
+ ))}
61
+ </div>
62
+ <blockquote class="testi-card__quote">
63
+ "{t.quote}"
64
+ </blockquote>
65
+ <div class="testi-card__author">
66
+ <img src={t.avatar} alt={t.name} class="testi-card__avatar" loading="lazy" />
67
+ <div class="testi-card__meta">
68
+ <h5 class="testi-card__name">{t.name}</h5>
69
+ <span class="testi-card__role">{t.role}</span>
70
+ </div>
71
+ </div>
72
+ </article>
73
+ ))}
74
+ </div>
75
+ </section>
76
+
77
+ <style>
78
+ .testi-section {
79
+ padding: var(--sp-12, 3rem) var(--sp-6, 1.5rem);
80
+ max-width: 1200px;
81
+ margin: 0 auto;
82
+ font-family: var(--font-sans, system-ui, sans-serif);
83
+ }
84
+
85
+ .testi-section__header {
86
+ text-align: center;
87
+ max-width: 640px;
88
+ margin: 0 auto 3rem auto;
89
+ }
90
+
91
+ .testi-section__eyebrow {
92
+ display: inline-block;
93
+ font-size: 0.75rem;
94
+ font-weight: 800;
95
+ letter-spacing: 0.12em;
96
+ color: var(--c-primary-light, #d4a017);
97
+ padding: 0.35rem 0.8rem;
98
+ border: 1px solid color-mix(in srgb, var(--c-primary, #b8860b) 30%, transparent);
99
+ border-radius: var(--r-sm, 8px);
100
+ margin-bottom: 1rem;
101
+ text-transform: uppercase;
102
+ }
103
+
104
+ .testi-section__title {
105
+ font-size: clamp(1.8rem, 4vw, 2.8rem);
106
+ font-weight: 900;
107
+ color: var(--c-text-1, #ffffff);
108
+ letter-spacing: -0.03em;
109
+ margin: 0 0 1rem 0;
110
+ }
111
+
112
+ .testi-section__subtitle {
113
+ font-size: 0.95rem;
114
+ color: var(--c-text-2, #a3a3a3);
115
+ line-height: 1.6;
116
+ margin: 0;
117
+ }
118
+
119
+ .testi-section__grid {
120
+ display: grid;
121
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
122
+ gap: 1.5rem;
123
+ }
124
+
125
+ .testi-card {
126
+ display: flex;
127
+ flex-direction: column;
128
+ padding: var(--sp-8, 2rem);
129
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.02);
130
+ backdrop-filter: blur(16px);
131
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08);
132
+ border-radius: var(--r-xl, 24px);
133
+ transition: transform 0.3s ease, border-color 0.3s ease;
134
+ }
135
+
136
+ .testi-card:hover {
137
+ transform: translateY(-4px);
138
+ border-color: color-mix(in srgb, var(--c-primary, #b8860b) 30%, transparent);
139
+ }
140
+
141
+ .testi-card--highlight {
142
+ background: radial-gradient(circle at top left, color-mix(in srgb, var(--c-primary, #b8860b) 10%, transparent), rgb(var(--ink-rgb, 255 255 255) / 0.02) 70%);
143
+ border-color: color-mix(in srgb, var(--c-primary, #b8860b) 40%, transparent);
144
+ }
145
+
146
+ .testi-card__stars {
147
+ color: #fbbf24;
148
+ font-size: 1rem;
149
+ letter-spacing: 2px;
150
+ margin-bottom: 1rem;
151
+ }
152
+
153
+ .testi-card__quote {
154
+ font-size: 0.95rem;
155
+ color: var(--c-text-1, #ffffff);
156
+ line-height: 1.65;
157
+ margin: 0 0 1.5rem 0;
158
+ flex: 1;
159
+ font-style: italic;
160
+ }
161
+
162
+ .testi-card__author {
163
+ display: flex;
164
+ align-items: center;
165
+ gap: 12px;
166
+ margin-top: auto;
167
+ }
168
+
169
+ .testi-card__avatar {
170
+ width: 44px;
171
+ height: 44px;
172
+ border-radius: 50%;
173
+ object-fit: cover;
174
+ border: 2px solid color-mix(in srgb, var(--c-primary, #b8860b) 40%, transparent);
175
+ }
176
+
177
+ .testi-card__name {
178
+ font-size: 0.92rem;
179
+ font-weight: 700;
180
+ color: var(--c-text-1, #ffffff);
181
+ margin: 0 0 2px 0;
182
+ }
183
+
184
+ .testi-card__role {
185
+ font-size: 0.75rem;
186
+ color: var(--c-text-3, #737373);
187
+ }
188
+ </style>
@@ -21,6 +21,7 @@ const {
21
21
  } = Astro.props;
22
22
  ---
23
23
 
24
+ <div class="cq-testimonial-wall-glass-cq">
24
25
  <section class={`testimonials-section ${className}`} aria-labelledby="testimonials-title">
25
26
  <div class="testimonials-bg" aria-hidden="true">
26
27
  <div class="test-glow test-glow--1"></div>
@@ -151,8 +152,9 @@ const {
151
152
  </div>
152
153
  </div>
153
154
  </section>
154
-
155
+ </div>
155
156
  <style>
157
+ .cq-testimonial-wall-glass-cq { container-type: inline-size; }
156
158
  .testimonials-section {
157
159
  position: relative;
158
160
  padding: var(--sp-20, 5rem) var(--sp-6, 1.5rem);
@@ -211,7 +213,7 @@ const {
211
213
  font-weight: 700;
212
214
  letter-spacing: 0.06em;
213
215
  text-transform: uppercase;
214
- background: rgba(184, 134, 11, 0.12);
216
+ background: color-mix(in srgb, var(--c-primary, #b8860b) 12%, transparent);
215
217
  color: #eab308;
216
218
  border: 1px solid rgba(234, 179, 8, 0.25);
217
219
  margin-bottom: var(--sp-4, 1rem);
@@ -241,7 +243,7 @@ const {
241
243
  gap: 2rem;
242
244
  padding: 1.25rem 2rem;
243
245
  background: rgba(15, 23, 42, 0.7);
244
- border: 1px solid rgba(255, 255, 255, 0.08);
246
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08);
245
247
  border-radius: 16px;
246
248
  backdrop-filter: blur(12px);
247
249
  max-width: 750px;
@@ -284,7 +286,7 @@ const {
284
286
  .trust-sep {
285
287
  width: 1px;
286
288
  height: 40px;
287
- background: rgba(255, 255, 255, 0.1);
289
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.1);
288
290
  }
289
291
 
290
292
  /* Wall Grid */
@@ -299,7 +301,7 @@ const {
299
301
  background: rgba(15, 23, 42, 0.55);
300
302
  backdrop-filter: blur(20px);
301
303
  -webkit-backdrop-filter: blur(20px);
302
- border: 1px solid rgba(255, 255, 255, 0.08);
304
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08);
303
305
  border-radius: var(--r-xl, 18px);
304
306
  padding: 1.75rem;
305
307
  display: flex;
@@ -310,7 +312,7 @@ const {
310
312
 
311
313
  .test-card:hover {
312
314
  transform: translateY(-4px);
313
- border-color: rgba(255, 255, 255, 0.2);
315
+ border-color: rgb(var(--ink-rgb, 255 255 255) / 0.2);
314
316
  }
315
317
 
316
318
  .test-card--featured {
@@ -352,7 +354,7 @@ const {
352
354
  align-items: center;
353
355
  gap: 0.85rem;
354
356
  padding-top: 1rem;
355
- border-top: 1px solid rgba(255, 255, 255, 0.06);
357
+ border-top: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.06);
356
358
  }
357
359
 
358
360
  .author-avatar {
@@ -364,7 +366,7 @@ const {
364
366
  font-weight: 800;
365
367
  font-size: 0.85rem;
366
368
  color: #fff;
367
- border: 2px solid rgba(255, 255, 255, 0.15);
369
+ border: 2px solid rgb(var(--ink-rgb, 255 255 255) / 0.15);
368
370
  }
369
371
 
370
372
  .author-avatar--1 { background: linear-gradient(135deg, #6366f1, #a855f7); }
@@ -390,7 +392,7 @@ const {
390
392
  color: #64748b;
391
393
  }
392
394
 
393
- @media (max-width: 960px) {
395
+ @container (max-width: 960px) {
394
396
  .trust-bar {
395
397
  flex-direction: column;
396
398
  gap: 1rem;
@@ -8,6 +8,7 @@ interface Props {
8
8
  const { title = "Typography", subtitle = "Headings and text patterns." } = Astro.props;
9
9
  ---
10
10
 
11
+ <div class="cq-typography-showcase-cq">
11
12
  <section class="typography-showcase">
12
13
  <div class="container">
13
14
  <div class="showcase-header">
@@ -54,8 +55,9 @@ const { title = "Typography", subtitle = "Headings and text patterns." } = Astro
54
55
  </div>
55
56
  </div>
56
57
  </section>
57
-
58
+ </div>
58
59
  <style>
60
+ .cq-typography-showcase-cq { container-type: inline-size; }
59
61
  .typography-showcase {
60
62
  padding: var(--sp-16) 0;
61
63
  position: relative;
@@ -72,8 +74,8 @@ const { title = "Typography", subtitle = "Headings and text patterns." } = Astro
72
74
  text-transform: uppercase;
73
75
  letter-spacing: 0.1em;
74
76
  color: var(--c-primary-light);
75
- background: rgba(184,134,11,0.1);
76
- border: 1px solid rgba(184,134,11,0.25);
77
+ background: color-mix(in srgb, var(--c-primary, #b8860b) 10%, transparent);
78
+ border: 1px solid color-mix(in srgb, var(--c-primary, #b8860b) 25%, transparent);
77
79
  border-radius: var(--r-full);
78
80
  margin-bottom: var(--sp-4);
79
81
  }
@@ -95,7 +97,7 @@ const { title = "Typography", subtitle = "Headings and text patterns." } = Astro
95
97
  padding: var(--sp-12);
96
98
  background: rgba(10, 10, 10, 0.6);
97
99
  backdrop-filter: blur(20px);
98
- border: 1px solid rgba(255,255,255,0.08);
100
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08);
99
101
  border-radius: var(--r-xl);
100
102
  margin-bottom: var(--sp-6);
101
103
  box-shadow: 0 20px 60px rgba(0,0,0,0.4);
@@ -117,9 +119,9 @@ const { title = "Typography", subtitle = "Headings and text patterns." } = Astro
117
119
  /* Typography Mockups */
118
120
  .typo-mockup {
119
121
  padding: var(--sp-6);
120
- background: rgba(255,255,255,0.02);
122
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.02);
121
123
  border-radius: var(--r-md);
122
- border: 1px solid rgba(255,255,255,0.05);
124
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.05);
123
125
  }
124
126
 
125
127
  /* Gradient Heading */
@@ -221,7 +223,7 @@ const { title = "Typography", subtitle = "Headings and text patterns." } = Astro
221
223
  .usage-code {
222
224
  text-align: center;
223
225
  padding: var(--sp-4);
224
- background: rgba(255,255,255,0.02);
226
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.02);
225
227
  border-radius: var(--r-md);
226
228
  }
227
229
  .usage-code code {
@@ -230,7 +232,7 @@ const { title = "Typography", subtitle = "Headings and text patterns." } = Astro
230
232
  color: var(--c-primary-light);
231
233
  }
232
234
 
233
- @media (max-width: 640px) {
235
+ @container (max-width: 640px) {
234
236
  .typo-stack { gap: var(--sp-5); }
235
237
  .typo-mockup { padding: var(--sp-4); }
236
238
  .preview-stage { padding: var(--sp-8) var(--sp-4); }
@@ -26,3 +26,42 @@ export { default as FaqAccordionSection } from './FaqAccordionSection.astro';
26
26
  export { default as StatsMetricShowcase } from './StatsMetricShowcase.astro';
27
27
  export { default as TeamGridSection } from './TeamGridSection.astro';
28
28
  export { default as ContactGlassForm } from './ContactGlassForm.astro';
29
+ export { default as TestimonialGridSection } from './TestimonialGridSection.astro';
30
+ export { default as LogoCloudSection } from './LogoCloudSection.astro';
31
+ export { default as HeroShowcaseSplit } from './HeroShowcaseSplit.astro';
32
+
33
+ // Additional sections (v0.4)
34
+ export { default as HeroMinimalCentered } from './HeroMinimalCentered.astro';
35
+ export { default as HeroTerminal } from './HeroTerminal.astro';
36
+ export { default as HeroWaitlist } from './HeroWaitlist.astro';
37
+ export { default as HeroDashboardPreview } from './HeroDashboardPreview.astro';
38
+ export { default as FeatureIconGrid } from './FeatureIconGrid.astro';
39
+ export { default as FeatureAlternatingRows } from './FeatureAlternatingRows.astro';
40
+ export { default as FeatureTabsShowcase } from './FeatureTabsShowcase.astro';
41
+ export { default as HowItWorksSteps } from './HowItWorksSteps.astro';
42
+ export { default as IntegrationsGrid } from './IntegrationsGrid.astro';
43
+ export { default as PricingToggleCards } from './PricingToggleCards.astro';
44
+ export { default as PricingSinglePlan } from './PricingSinglePlan.astro';
45
+ export { default as TestimonialCarousel } from './TestimonialCarousel.astro';
46
+ export { default as TestimonialFeatured } from './TestimonialFeatured.astro';
47
+ export { default as CtaNewsletterSection } from './CtaNewsletterSection.astro';
48
+ export { default as CtaSplitImage } from './CtaSplitImage.astro';
49
+ export { default as StatsCounterBand } from './StatsCounterBand.astro';
50
+ export { default as StatsDashboardSection } from './StatsDashboardSection.astro';
51
+ export { default as CareersOpenings } from './CareersOpenings.astro';
52
+ export { default as FaqTwoColumn } from './FaqTwoColumn.astro';
53
+ export { default as ContactSplitMap } from './ContactSplitMap.astro';
54
+ export { default as LogoWallGrid } from './LogoWallGrid.astro';
55
+ export { default as BlogGridSection } from './BlogGridSection.astro';
56
+ export { default as ChangelogSection } from './ChangelogSection.astro';
57
+ export { default as FooterMegaSection } from './FooterMegaSection.astro';
58
+ export { default as ShopHeaderEditorial } from './ShopHeaderEditorial.astro';
59
+ export { default as ShopHeroEditorial } from './ShopHeroEditorial.astro';
60
+ export { default as ShopCollectionMosaic } from './ShopCollectionMosaic.astro';
61
+ export { default as ShopProductRail } from './ShopProductRail.astro';
62
+ export { default as ShopLookbook } from './ShopLookbook.astro';
63
+ export { default as ShopValuesMarquee } from './ShopValuesMarquee.astro';
64
+ export { default as ShopReviewsEditorial } from './ShopReviewsEditorial.astro';
65
+ export { default as ShopNewsletterEditorial } from './ShopNewsletterEditorial.astro';
66
+ export { default as ShopFooterWordmark } from './ShopFooterWordmark.astro';
67
+ export { default as ShopCategoryHero } from './ShopCategoryHero.astro';
@@ -0,0 +1,160 @@
1
+ ---
2
+ /**
3
+ * StoreCategoryPage — E-commerce template catalogue: breadcrumb, category chips, toolbar
4
+ * with live count + sort, FilterSidebar and ProductGrid (instant client-side filtering).
5
+ *
6
+ * Usage (src/pages/shop.astro):
7
+ * import { StoreCategoryPage } from 'astro-component-kit/templates/ecommerce';
8
+ * <StoreCategoryPage />
9
+ * Category links use `?category=<id>`; pass `category` to build one static page per category.
10
+ *
11
+ * @param {StoreConfig} store - Catalogue and settings. Default: demo store.
12
+ * @param {StoreRoutes} routes - Page URLs.
13
+ * @param {string} category - Pre-selected category id (optional).
14
+ * @param {boolean} seedCart - Pre-fill the cart with demo items. Default false.
15
+ * @param {string} theme - Initial theme (dark|light|auto). Default 'dark'.
16
+ * @param {boolean} noindex - Add robots noindex. Default false.
17
+ */
18
+ import StoreLayout from './StoreLayout.astro';
19
+ import ProductToolbar from '../../ecommerce/ProductToolbar.astro';
20
+ import FilterSidebar from '../../ecommerce/FilterSidebar.astro';
21
+ import ProductGrid from '../../ecommerce/ProductGrid.astro';
22
+ import TrustBadges from '../../ecommerce/TrustBadges.astro';
23
+ import { demoStore, defaultRoutes, categoryHref, categoryNames, type StoreConfig, type StoreRoutes } from './data';
24
+
25
+ interface Props {
26
+ store?: StoreConfig;
27
+ routes?: StoreRoutes;
28
+ category?: string;
29
+ seedCart?: boolean;
30
+ noindex?: boolean;
31
+ theme?: 'dark' | 'light' | 'auto';
32
+ }
33
+
34
+ const { store = demoStore, routes = defaultRoutes, category, seedCart = false, noindex = false, theme = 'dark' } = Astro.props;
35
+
36
+ const active = store.categories.find((c) => c.id === category);
37
+ const names = categoryNames(store);
38
+ const maxPrice = Math.ceil(Math.max(...store.products.map((p) => p.price)) / 50) * 50;
39
+ const colors = Array.from(new Map(store.products.flatMap((p) => p.colors ?? []).map((c) => [c.name, c])).values());
40
+ const filterCats = store.categories.map((c) => ({ id: c.id, name: c.name, count: store.products.filter((p) => p.category === c.id).length }));
41
+ const title = active ? active.name : 'All products';
42
+ const description = active?.description ?? 'Everything we make, in one place. Filter by category, price, colour and rating.';
43
+ ---
44
+
45
+ <StoreLayout theme={theme} title={title} description={description} store={store} routes={routes} currentHref={routes.shop} seedCart={seedCart} noindex={noindex}>
46
+ <div class="store-container shop">
47
+ <nav class="crumbs" aria-label="Breadcrumb">
48
+ <ol>
49
+ <li><a href={routes.home}>Home</a></li>
50
+ <li><a href={routes.shop} aria-current={active ? undefined : 'page'}>Shop</a></li>
51
+ {active && <li><span aria-current="page">{active.name}</span></li>}
52
+ </ol>
53
+ </nav>
54
+
55
+ <ProductToolbar title={title} description={description} total={store.products.length} target="shop-grid" filtersId="shop-filters" />
56
+
57
+ <ul class="chips" aria-label="Categories">
58
+ <li><a class="chip" href={routes.shop} aria-current={!active ? 'page' : undefined}>All</a></li>
59
+ {store.categories.map((c) => (
60
+ <li><a class="chip" href={categoryHref(routes, c.id)} aria-current={active?.id === c.id ? 'page' : undefined} data-chip={c.id}>{c.name}</a></li>
61
+ ))}
62
+ </ul>
63
+
64
+ <div class="shop__layout">
65
+ <div class="shop__filters">
66
+ <FilterSidebar
67
+ target="shop-grid"
68
+ id="shop-filters"
69
+ categories={filterCats}
70
+ selected={active ? [active.id] : []}
71
+ maxPrice={maxPrice}
72
+ colors={colors}
73
+ currency={store.currency}
74
+ locale={store.locale}
75
+ />
76
+ </div>
77
+ <ProductGrid
78
+ id="shop-grid"
79
+ products={store.products}
80
+ productBase={routes.product}
81
+ categoryNames={names}
82
+ columns={3}
83
+ currency={store.currency}
84
+ locale={store.locale}
85
+ cartKey={store.cartKey}
86
+ openCart={false}
87
+ />
88
+ </div>
89
+
90
+ <div class="shop__trust">
91
+ <TrustBadges variant="divided" size="sm" />
92
+ </div>
93
+ </div>
94
+ </StoreLayout>
95
+
96
+ <script>
97
+ // Category chips filter in place (no reload) and keep FilterSidebar in sync.
98
+ document.querySelectorAll<HTMLAnchorElement>('.chips a').forEach((chip) => {
99
+ chip.addEventListener('click', (e) => {
100
+ const form = document.getElementById('shop-filters') as HTMLFormElement | null;
101
+ if (!form) return;
102
+ e.preventDefault();
103
+ const id = chip.dataset.chip;
104
+ form.querySelectorAll<HTMLInputElement>('input[name="category"]').forEach((i) => { i.checked = id ? i.value === id : false; });
105
+ form.dispatchEvent(new Event('change', { bubbles: true }));
106
+ });
107
+ });
108
+ const syncChips = () => {
109
+ const form = document.getElementById('shop-filters') as HTMLFormElement | null;
110
+ if (!form) return;
111
+ const checked = Array.from(form.querySelectorAll<HTMLInputElement>('input[name="category"]:checked')).map((i) => i.value);
112
+ document.querySelectorAll<HTMLAnchorElement>('.chips a').forEach((chip) => {
113
+ const id = chip.dataset.chip;
114
+ const on = id ? checked.length === 1 && checked[0] === id : checked.length === 0;
115
+ if (on) chip.setAttribute('aria-current', 'page');
116
+ else chip.removeAttribute('aria-current');
117
+ });
118
+ };
119
+ document.getElementById('shop-filters')?.addEventListener('change', syncChips);
120
+ document.getElementById('shop-filters')?.addEventListener('reset', () => window.setTimeout(syncChips, 0));
121
+ syncChips();
122
+ </script>
123
+
124
+ <style>
125
+ .shop { padding-block: var(--sp-6) clamp(3rem, 7vw, 5rem); }
126
+
127
+ .crumbs ol { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin: 0 0 var(--sp-6); padding: 0; list-style: none; font-size: 0.85rem; }
128
+ .crumbs li { display: flex; align-items: center; gap: var(--sp-2); color: var(--c-text-2); }
129
+ .crumbs li + li::before { content: '/'; opacity: 0.5; }
130
+ .crumbs a { color: var(--c-text-2); }
131
+ .crumbs a:hover { color: var(--c-text-1); }
132
+ .crumbs [aria-current='page'] { color: var(--c-text-1); font-weight: 600; }
133
+
134
+ .chips { display: flex; gap: var(--sp-2); margin: 0 0 var(--sp-6); padding: 0 0 4px; list-style: none; overflow-x: auto; scrollbar-width: none; }
135
+ .chip {
136
+ display: inline-flex;
137
+ align-items: center;
138
+ min-height: 40px;
139
+ padding: 0 var(--sp-4);
140
+ border-radius: var(--r-full);
141
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.1);
142
+ color: var(--c-text-2);
143
+ font-size: 0.88rem;
144
+ font-weight: 600;
145
+ white-space: nowrap;
146
+ transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
147
+ }
148
+ .chip:hover { color: var(--c-text-1); border-color: rgb(var(--ink-rgb, 255 255 255) / 0.25); }
149
+ .chip[aria-current='page'] { background: var(--c-text-1); color: var(--c-bg); border-color: var(--c-text-1); }
150
+ .chip:focus-visible { outline: 2px solid var(--c-primary-light); outline-offset: 2px; }
151
+
152
+ .shop__layout { display: grid; grid-template-columns: 260px minmax(0, 1fr); gap: var(--sp-8); align-items: start; }
153
+ .shop__filters { position: sticky; top: 88px; }
154
+ .shop__trust { margin-top: clamp(3rem, 6vw, 4.5rem); }
155
+
156
+ @media (max-width: 900px) {
157
+ .shop__layout { grid-template-columns: 1fr; gap: var(--sp-4); }
158
+ .shop__filters { position: static; }
159
+ }
160
+ </style>