astro-component-kit 0.4.0 → 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 (180) hide show
  1. package/README.md +105 -27
  2. package/package.json +16 -3
  3. package/src/components/index.ts +2 -0
  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/charts/AreaChart.astro +4 -4
  21. package/src/components/lib/charts/BarChart.astro +3 -3
  22. package/src/components/lib/charts/ChartFrame.astro +10 -10
  23. package/src/components/lib/charts/DonutChart.astro +2 -2
  24. package/src/components/lib/charts/DumbbellChart.astro +1 -1
  25. package/src/components/lib/charts/ForecastLineChart.astro +1 -1
  26. package/src/components/lib/charts/GaugeChart.astro +1 -1
  27. package/src/components/lib/charts/HeatmapChart.astro +1 -1
  28. package/src/components/lib/charts/HorizontalBarChart.astro +2 -2
  29. package/src/components/lib/charts/KpiStatTile.astro +4 -4
  30. package/src/components/lib/charts/LinearProgressGauge.astro +4 -4
  31. package/src/components/lib/charts/MetricDeltaTable.astro +3 -3
  32. package/src/components/lib/charts/RadarMetricChart.astro +4 -4
  33. package/src/components/lib/charts/RadialBarChart.astro +1 -1
  34. package/src/components/lib/charts/RetentionCohortTable.astro +2 -2
  35. package/src/components/lib/charts/TreemapChart.astro +1 -1
  36. package/src/components/lib/ecommerce/CartDrawer.astro +483 -0
  37. package/src/components/lib/ecommerce/CartPageList.astro +290 -0
  38. package/src/components/lib/ecommerce/CartToast.astro +276 -0
  39. package/src/components/lib/ecommerce/CategoryTiles.astro +143 -0
  40. package/src/components/lib/ecommerce/CheckoutForm.astro +423 -0
  41. package/src/components/lib/ecommerce/CountdownBanner.astro +230 -0
  42. package/src/components/lib/ecommerce/FilterBar.astro +278 -0
  43. package/src/components/lib/ecommerce/FilterSidebar.astro +405 -0
  44. package/src/components/lib/ecommerce/LiveActivityToast.astro +213 -0
  45. package/src/components/lib/ecommerce/OrderSummary.astro +295 -0
  46. package/src/components/lib/ecommerce/ProductBuyBox.astro +247 -0
  47. package/src/components/lib/ecommerce/ProductCardMinimal.astro +229 -0
  48. package/src/components/lib/ecommerce/ProductGallery.astro +145 -0
  49. package/src/components/lib/ecommerce/ProductGrid.astro +118 -0
  50. package/src/components/lib/ecommerce/ProductMedia.astro +113 -0
  51. package/src/components/lib/ecommerce/ProductRating.astro +78 -0
  52. package/src/components/lib/ecommerce/ProductToolbar.astro +93 -0
  53. package/src/components/lib/ecommerce/PromoBar.astro +145 -0
  54. package/src/components/lib/ecommerce/StockIndicator.astro +127 -0
  55. package/src/components/lib/ecommerce/StoreHeader.astro +446 -0
  56. package/src/components/lib/ecommerce/StoreHero.astro +260 -0
  57. package/src/components/lib/ecommerce/StoreProductCard.astro +307 -0
  58. package/src/components/lib/ecommerce/TrustBadges.astro +101 -0
  59. package/src/components/lib/ecommerce/VariantPicker.astro +145 -0
  60. package/src/components/lib/ecommerce/cart-store.ts +151 -0
  61. package/src/components/lib/ecommerce/catalog-filter.ts +62 -0
  62. package/src/components/lib/ecommerce/index.ts +34 -0
  63. package/src/components/lib/ecommerce/product-icons.ts +28 -0
  64. package/src/components/lib/ecommerce/types.ts +70 -0
  65. package/src/components/lib/forms/AnimatedInput.astro +4 -2
  66. package/src/components/lib/forms/CustomRadioGroup.astro +3 -1
  67. package/src/components/lib/index.ts +2 -0
  68. package/src/components/lib/layout/BentoGrid3.astro +6 -4
  69. package/src/components/lib/layout/CenterFixedBox.astro +2 -2
  70. package/src/components/lib/layout/CenteredContainer.astro +4 -2
  71. package/src/components/lib/layout/ClusterHorizontal.astro +2 -2
  72. package/src/components/lib/layout/FeatureHero.astro +4 -2
  73. package/src/components/lib/layout/FooterCenteredMinimal.astro +4 -2
  74. package/src/components/lib/layout/FullBleedSection.astro +2 -2
  75. package/src/components/lib/layout/GalleryGrid4.astro +2 -2
  76. package/src/components/lib/layout/HolyGrailGrid.astro +11 -9
  77. package/src/components/lib/layout/LandingSectionSplit.astro +5 -3
  78. package/src/components/lib/layout/MasonryGrid.astro +2 -2
  79. package/src/components/lib/layout/MinimalFooterGrid.astro +1 -1
  80. package/src/components/lib/layout/ScrollSnapSection.astro +1 -1
  81. package/src/components/lib/layout/SidebarLayoutWrapper.astro +4 -2
  82. package/src/components/lib/layout/SplitScreenLayout.astro +3 -3
  83. package/src/components/lib/layout/StepLayoutVertical.astro +1 -1
  84. package/src/components/lib/layout/StickySidebarWrapper.astro +4 -2
  85. package/src/components/lib/sections/AgencyContactBrief.astro +194 -0
  86. package/src/components/lib/sections/AgencyFooterCta.astro +94 -0
  87. package/src/components/lib/sections/AgencyHeader.astro +161 -0
  88. package/src/components/lib/sections/AgencyHeroKinetic.astro +172 -0
  89. package/src/components/lib/sections/AgencyServicesList.astro +113 -0
  90. package/src/components/lib/sections/AgencyTeamGrid.astro +111 -0
  91. package/src/components/lib/sections/AgencyWorkGrid.astro +147 -0
  92. package/src/components/lib/sections/BlogGridSection.astro +9 -7
  93. package/src/components/lib/sections/ButtonsShowcase.astro +20 -18
  94. package/src/components/lib/sections/CardsShowcase.astro +28 -26
  95. package/src/components/lib/sections/CareersOpenings.astro +10 -8
  96. package/src/components/lib/sections/ChangelogSection.astro +14 -7
  97. package/src/components/lib/sections/ContactGlassForm.astro +10 -8
  98. package/src/components/lib/sections/ContactSplitMap.astro +11 -9
  99. package/src/components/lib/sections/CtaAppDownload.astro +13 -11
  100. package/src/components/lib/sections/CtaGlowBanner.astro +12 -10
  101. package/src/components/lib/sections/CtaNewsletterSection.astro +13 -11
  102. package/src/components/lib/sections/CtaSplitImage.astro +13 -11
  103. package/src/components/lib/sections/DataShowcase.astro +23 -21
  104. package/src/components/lib/sections/FaqAccordionSection.astro +8 -8
  105. package/src/components/lib/sections/FaqTwoColumn.astro +9 -7
  106. package/src/components/lib/sections/FeatureAlternatingRows.astro +10 -8
  107. package/src/components/lib/sections/FeatureBentoGrid.astro +13 -11
  108. package/src/components/lib/sections/FeatureIconGrid.astro +6 -6
  109. package/src/components/lib/sections/FeatureTabsShowcase.astro +13 -11
  110. package/src/components/lib/sections/FeatureTimelineFlow.astro +8 -6
  111. package/src/components/lib/sections/FeedbackShowcase.astro +15 -13
  112. package/src/components/lib/sections/FooterMegaSection.astro +13 -11
  113. package/src/components/lib/sections/FormsShowcase.astro +22 -20
  114. package/src/components/lib/sections/HeroDashboardPreview.astro +11 -9
  115. package/src/components/lib/sections/HeroGlass.astro +22 -20
  116. package/src/components/lib/sections/HeroGradient.astro +15 -13
  117. package/src/components/lib/sections/HeroMinimalCentered.astro +10 -10
  118. package/src/components/lib/sections/HeroShowcaseSplit.astro +18 -16
  119. package/src/components/lib/sections/HeroSplit.astro +13 -11
  120. package/src/components/lib/sections/HeroTerminal.astro +14 -12
  121. package/src/components/lib/sections/HeroWaitlist.astro +10 -8
  122. package/src/components/lib/sections/HowItWorksSteps.astro +12 -10
  123. package/src/components/lib/sections/IntegrationsGrid.astro +7 -7
  124. package/src/components/lib/sections/LayoutShowcase.astro +21 -19
  125. package/src/components/lib/sections/LogoCloudSection.astro +4 -4
  126. package/src/components/lib/sections/LogoWallGrid.astro +6 -4
  127. package/src/components/lib/sections/NavigationShowcase.astro +15 -13
  128. package/src/components/lib/sections/OverlaysShowcase.astro +21 -19
  129. package/src/components/lib/sections/PricingComparisonMatrix.astro +15 -13
  130. package/src/components/lib/sections/PricingSinglePlan.astro +13 -11
  131. package/src/components/lib/sections/PricingTierCards.astro +11 -9
  132. package/src/components/lib/sections/PricingToggleCards.astro +13 -13
  133. package/src/components/lib/sections/ShopCategoryHero.astro +103 -0
  134. package/src/components/lib/sections/ShopCollectionMosaic.astro +131 -0
  135. package/src/components/lib/sections/ShopFooterWordmark.astro +100 -0
  136. package/src/components/lib/sections/ShopHeaderEditorial.astro +396 -0
  137. package/src/components/lib/sections/ShopHeroEditorial.astro +151 -0
  138. package/src/components/lib/sections/ShopLookbook.astro +250 -0
  139. package/src/components/lib/sections/ShopNewsletterEditorial.astro +128 -0
  140. package/src/components/lib/sections/ShopProductRail.astro +150 -0
  141. package/src/components/lib/sections/ShopReviewsEditorial.astro +133 -0
  142. package/src/components/lib/sections/ShopValuesMarquee.astro +91 -0
  143. package/src/components/lib/sections/StatsCounterBand.astro +9 -7
  144. package/src/components/lib/sections/StatsDashboardSection.astro +4 -2
  145. package/src/components/lib/sections/StatsMetricShowcase.astro +9 -7
  146. package/src/components/lib/sections/TeamGridSection.astro +12 -10
  147. package/src/components/lib/sections/TestimonialCarousel.astro +10 -8
  148. package/src/components/lib/sections/TestimonialFeatured.astro +7 -5
  149. package/src/components/lib/sections/TestimonialGridSection.astro +7 -7
  150. package/src/components/lib/sections/TestimonialWallGlass.astro +11 -9
  151. package/src/components/lib/sections/TypographyShowcase.astro +10 -8
  152. package/src/components/lib/sections/index.ts +10 -0
  153. package/src/components/lib/templates/ecommerce/StoreCategoryPage.astro +160 -0
  154. package/src/components/lib/templates/ecommerce/StoreCheckoutPage.astro +98 -0
  155. package/src/components/lib/templates/ecommerce/StoreHomePage.astro +191 -0
  156. package/src/components/lib/templates/ecommerce/StoreLayout.astro +169 -0
  157. package/src/components/lib/templates/ecommerce/StoreProductPage.astro +165 -0
  158. package/src/components/lib/templates/ecommerce/data.ts +187 -0
  159. package/src/components/lib/templates/ecommerce/index.ts +14 -0
  160. package/src/components/lib/templates/ecommerce-editorial/EditorialBagPage.astro +52 -0
  161. package/src/components/lib/templates/ecommerce-editorial/EditorialCheckoutPage.astro +123 -0
  162. package/src/components/lib/templates/ecommerce-editorial/EditorialHomePage.astro +102 -0
  163. package/src/components/lib/templates/ecommerce-editorial/EditorialLayout.astro +216 -0
  164. package/src/components/lib/templates/ecommerce-editorial/EditorialProductPage.astro +205 -0
  165. package/src/components/lib/templates/ecommerce-editorial/EditorialShopPage.astro +112 -0
  166. package/src/components/lib/templates/ecommerce-editorial/data.ts +125 -0
  167. package/src/components/lib/templates/ecommerce-editorial/index.ts +16 -0
  168. package/src/components/lib/templates/saas-hotel/AppLayout.astro +83 -0
  169. package/src/components/lib/templates/saas-hotel/DashboardPage.astro +66 -0
  170. package/src/components/lib/templates/saas-hotel/GuestsPage.astro +65 -0
  171. package/src/components/lib/templates/saas-hotel/LandingPage.astro +213 -0
  172. package/src/components/lib/templates/saas-hotel/ReportsPage.astro +63 -0
  173. package/src/components/lib/templates/saas-hotel/ReservationsPage.astro +49 -0
  174. package/src/components/lib/templates/saas-hotel/RoomsPage.astro +48 -0
  175. package/src/components/lib/templates/saas-hotel/SettingsPage.astro +115 -0
  176. package/src/components/lib/templates/saas-hotel/SignInPage.astro +50 -0
  177. package/src/components/lib/templates/saas-hotel/data.ts +229 -0
  178. package/src/components/lib/templates/saas-hotel/index.ts +19 -0
  179. package/src/components/lib/templates/saas-hotel/lobby-theme.css +90 -0
  180. package/src/styles/global.css +95 -0
@@ -0,0 +1,191 @@
1
+ ---
2
+ /**
3
+ * StoreHomePage — E-commerce template home: hero, guarantees, categories, best sellers,
4
+ * editorial banner, new arrivals, press logos and newsletter.
5
+ *
6
+ * Usage (src/pages/index.astro):
7
+ * import { StoreHomePage } from 'astro-component-kit/templates/ecommerce';
8
+ * <StoreHomePage />
9
+ *
10
+ * @param {StoreConfig} store - Catalogue and settings. Default: demo store.
11
+ * @param {StoreRoutes} routes - Page URLs. Default: '/', '/shop/', '/product/', '/checkout/'.
12
+ * @param {boolean} seedCart - Pre-fill the cart with demo items. Default false.
13
+ * @param {string} theme - Initial theme (dark|light|auto). Default 'dark'.
14
+ * @param {boolean} noindex - Add robots noindex. Default false.
15
+ */
16
+ import StoreLayout from './StoreLayout.astro';
17
+ import StoreHero from '../../ecommerce/StoreHero.astro';
18
+ import TrustBadges from '../../ecommerce/TrustBadges.astro';
19
+ import CategoryTiles from '../../ecommerce/CategoryTiles.astro';
20
+ import ProductGrid from '../../ecommerce/ProductGrid.astro';
21
+ import ProductMedia from '../../ecommerce/ProductMedia.astro';
22
+ import CountdownBanner from '../../ecommerce/CountdownBanner.astro';
23
+ import LogoCloudSection from '../../sections/LogoCloudSection.astro';
24
+ import CtaNewsletterSection from '../../sections/CtaNewsletterSection.astro';
25
+ import { demoStore, defaultRoutes, productHref, categoryHref, categoryNames, pickProducts, type StoreConfig, type StoreRoutes } from './data';
26
+
27
+ interface Props {
28
+ store?: StoreConfig;
29
+ routes?: StoreRoutes;
30
+ seedCart?: boolean;
31
+ noindex?: boolean;
32
+ theme?: 'dark' | 'light' | 'auto';
33
+ }
34
+
35
+ const { store = demoStore, routes = defaultRoutes, seedCart = false, noindex = false, theme = 'dark' } = Astro.props;
36
+
37
+ const names = categoryNames(store);
38
+ const best = pickProducts(store, store.bestSellers);
39
+ const fresh = pickProducts(store, store.newArrivals);
40
+ const heroProduct = best[0] ?? store.products[0];
41
+ const tiles = store.categories.map((c) => ({
42
+ ...c,
43
+ href: categoryHref(routes, c.id),
44
+ count: store.products.filter((p) => p.category === c.id).length,
45
+ }));
46
+ const feature = store.products.find((p) => p.slug === 'storm-jacket') ?? store.products[store.products.length - 1];
47
+ const shared = { productBase: routes.product, categoryNames: names, currency: store.currency, locale: store.locale, cartKey: store.cartKey, openCart: false };
48
+ ---
49
+
50
+ <StoreLayout theme={theme} title={`${store.name} — ${store.tagline.split('.')[0]}`} description={store.tagline} store={store} routes={routes} currentHref={routes.home} seedCart={seedCart} noindex={noindex} liveActivity>
51
+ <StoreHero
52
+ eyebrow="New season · 2026"
53
+ title="Gear that feels"
54
+ highlight="made for you"
55
+ subtitle={`${store.tagline} Free shipping over ${new Intl.NumberFormat(store.locale, { style: 'currency', currency: store.currency, maximumFractionDigits: 0 }).format(store.freeShippingThreshold)}.`}
56
+ primary={{ label: 'Shop the collection', href: routes.shop }}
57
+ secondary={{ label: 'See best sellers', href: `${routes.shop}?sort=rating` }}
58
+ product={heroProduct}
59
+ productHref={productHref(routes, heroProduct.slug)}
60
+ currency={store.currency}
61
+ locale={store.locale}
62
+ />
63
+
64
+ <div class="store-container home-block home-block--tight">
65
+ <TrustBadges />
66
+ </div>
67
+
68
+ <div class="store-container home-block">
69
+ <CategoryTiles title="Shop by category" subtitle="Five small collections, each one obsessively edited." categories={tiles} columns={5} />
70
+ </div>
71
+
72
+ <section class="store-container home-block" aria-labelledby="best-title">
73
+ <div class="home-head">
74
+ <div>
75
+ <p class="home-eyebrow">Loved by 12k+ customers</p>
76
+ <h2 id="best-title">Best sellers</h2>
77
+ </div>
78
+ <a class="home-link" href={`${routes.shop}?sort=rating`}>View all <span aria-hidden="true">→</span></a>
79
+ </div>
80
+ <ProductGrid id="best-sellers" products={best} {...shared} />
81
+ </section>
82
+
83
+ <section class="store-container home-block" aria-labelledby="feature-title">
84
+ <div class="feature">
85
+ <div class="feature__copy">
86
+ <p class="home-eyebrow">The Storm edit</p>
87
+ <h2 id="feature-title">Built for weather you'd rather avoid</h2>
88
+ <p>Three-layer shells, sealed seams and recycled fabrics. Tested in the Scottish Highlands so you don't have to.</p>
89
+ <ul class="feature__list">
90
+ <li>Waterproof to 20,000 mm</li>
91
+ <li>Made from 100% recycled nylon</li>
92
+ <li>Lifetime repair programme</li>
93
+ </ul>
94
+ <a class="feature__cta" href={productHref(routes, feature.slug)}>Shop {feature.name}</a>
95
+ </div>
96
+ <div class="feature__art" aria-hidden="true">
97
+ <ProductMedia icon={feature.icon} hue={feature.hue} ratio="4 / 3" size="lg" />
98
+ </div>
99
+ </div>
100
+ </section>
101
+
102
+ <div class="store-container home-block">
103
+ <CountdownBanner
104
+ eyebrow="Flash sale · ends soon"
105
+ title="Up to 30% off the Storm collection"
106
+ text="Waterproof shells and insulated layers at their lowest price of the season. Free returns, as always."
107
+ cta={{ label: 'Shop the sale', href: productHref(routes, feature.slug) }}
108
+ hours={36}
109
+ storageKey={`${store.cartKey}-sale`}
110
+ />
111
+ </div>
112
+
113
+ <section class="store-container home-block" aria-labelledby="new-title">
114
+ <div class="home-head">
115
+ <div>
116
+ <p class="home-eyebrow">Just landed</p>
117
+ <h2 id="new-title">New arrivals</h2>
118
+ </div>
119
+ <a class="home-link" href={routes.shop}>Shop all <span aria-hidden="true">→</span></a>
120
+ </div>
121
+ <ProductGrid id="new-arrivals" products={fresh} {...shared} />
122
+ </section>
123
+
124
+ <LogoCloudSection
125
+ title="AS FEATURED IN"
126
+ logos={[
127
+ { name: 'Northwind', symbol: '◆' },
128
+ { name: 'Lumen Daily', symbol: '●' },
129
+ { name: 'Gearbox', symbol: '▲' },
130
+ { name: 'Field Notes', symbol: '✦' },
131
+ { name: 'Outpost', symbol: '◎' },
132
+ { name: 'Kinfolk Lab', symbol: '■' },
133
+ ]}
134
+ />
135
+
136
+ <div class="store-container home-block">
137
+ <CtaNewsletterSection
138
+ title="Get 10% off your first order"
139
+ subtitle="Join the list for early access to drops, restocks and members-only offers."
140
+ perks={['One email a week', 'Early access to new drops', 'Unsubscribe anytime']}
141
+ buttonLabel="Join the list"
142
+ privacy="By subscribing you agree to our privacy policy."
143
+ variant="glass"
144
+ />
145
+ </div>
146
+ </StoreLayout>
147
+
148
+ <style>
149
+ .home-block { padding-block: clamp(2.5rem, 6vw, 4.5rem) 0; }
150
+ .home-block--tight { padding-top: 0; }
151
+ .home-block:last-child { padding-bottom: clamp(3rem, 7vw, 5rem); }
152
+
153
+ .home-head { display: flex; align-items: flex-end; justify-content: space-between; gap: var(--sp-4); margin-bottom: var(--sp-6); }
154
+ .home-head h2, .feature h2 { margin: 0; font-size: clamp(1.6rem, 3.4vw, 2.3rem); font-weight: 900; letter-spacing: -0.035em; color: var(--c-text-1); }
155
+ .home-eyebrow { margin: 0 0 var(--sp-2); font-size: 0.75rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; color: var(--c-primary-light); }
156
+ .home-link { display: inline-flex; align-items: center; gap: 0.35rem; min-height: 44px; font-weight: 700; white-space: nowrap; }
157
+
158
+ .feature {
159
+ display: grid;
160
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
161
+ align-items: center;
162
+ gap: clamp(1.5rem, 4vw, 3.5rem);
163
+ padding: clamp(1.5rem, 4vw, 3rem);
164
+ border-radius: var(--r-xl);
165
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.07);
166
+ background: radial-gradient(circle at 0% 0%, rgba(147, 51, 234, 0.12), transparent 50%), var(--c-surface);
167
+ }
168
+ .feature__copy p:not(.home-eyebrow) { margin: var(--sp-4) 0 0; max-width: 32rem; line-height: 1.7; }
169
+ .feature__list { display: grid; gap: var(--sp-2); margin: var(--sp-5) 0 0; padding: 0; list-style: none; }
170
+ .feature__list li { display: flex; align-items: center; gap: var(--sp-2); color: var(--c-text-1); font-size: 0.92rem; }
171
+ .feature__list li::before { content: ''; width: 18px; height: 18px; flex-shrink: 0; border-radius: 50%; background: rgba(212, 160, 23, 0.15) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23d4a017' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E") center / 11px no-repeat; }
172
+ .feature__cta {
173
+ display: inline-flex;
174
+ align-items: center;
175
+ min-height: 50px;
176
+ margin-top: var(--sp-6);
177
+ padding: 0 var(--sp-6);
178
+ border-radius: var(--r-full);
179
+ background: var(--c-text-1);
180
+ color: var(--c-bg);
181
+ font-weight: 800;
182
+ }
183
+ .feature__cta:hover { color: var(--c-bg); background: var(--c-accent); }
184
+ .feature__cta:focus-visible, .home-link:focus-visible { outline: 2px solid var(--c-primary-light); outline-offset: 3px; }
185
+ .feature__art { border-radius: var(--r-lg); overflow: hidden; border: 1px solid var(--c-border); }
186
+
187
+ @media (max-width: 800px) {
188
+ .feature { grid-template-columns: 1fr; }
189
+ .feature__art { order: -1; }
190
+ }
191
+ </style>
@@ -0,0 +1,169 @@
1
+ ---
2
+ /**
3
+ * StoreLayout — Full HTML document for the e-commerce template: promo bar, StoreHeader with
4
+ * category menu, CartDrawer, main content slot and FooterMegaSection.
5
+ *
6
+ * @param {string} title - Page title (store name is appended).
7
+ * @param {string} description - Meta description.
8
+ * @param {StoreConfig} store - Catalogue and settings. Default: the demo store.
9
+ * @param {StoreRoutes} routes - Page URLs. Default: '/', '/shop/', '/product/', '/checkout/'.
10
+ * @param {string} currentHref - Highlights the matching menu link.
11
+ * @param {boolean} seedCart - Pre-fill an empty cart with two demo items (previews only). Default false.
12
+ * @param {boolean} noindex - Add robots noindex (previews, staging). Default false.
13
+ * @param {boolean} minimal - Hide promo bar and menu (checkout focus mode). Default false.
14
+ * @param {string} lang - Document language. Default 'en'.
15
+ * @param {string} theme - Initial theme (dark|light|auto). The header toggle overrides it per visitor. Default 'dark'.
16
+ * @param {boolean} liveActivity - Show rotating recent-purchase notices (store.recentBuyers). Default false.
17
+ * @param {boolean} themeToggle - Show the light/dark toggle in the header. Default true.
18
+ *
19
+ * Slots: default (page content), `head` (extra tags in <head>).
20
+ */
21
+ import '../../../../styles/global.css';
22
+ import PromoBar from '../../ecommerce/PromoBar.astro';
23
+ import StoreHeader from '../../ecommerce/StoreHeader.astro';
24
+ import CartDrawer from '../../ecommerce/CartDrawer.astro';
25
+ import FooterMegaSection from '../../sections/FooterMegaSection.astro';
26
+ import CartToast from '../../ecommerce/CartToast.astro';
27
+ import LiveActivityToast from '../../ecommerce/LiveActivityToast.astro';
28
+ import { demoStore, defaultRoutes, categoryHref, type StoreConfig, type StoreRoutes } from './data';
29
+
30
+ interface Props {
31
+ title: string;
32
+ description?: string;
33
+ store?: StoreConfig;
34
+ routes?: StoreRoutes;
35
+ currentHref?: string;
36
+ seedCart?: boolean;
37
+ noindex?: boolean;
38
+ minimal?: boolean;
39
+ lang?: string;
40
+ theme?: 'dark' | 'light' | 'auto';
41
+ themeToggle?: boolean;
42
+ liveActivity?: boolean;
43
+ }
44
+
45
+ const {
46
+ title,
47
+ description,
48
+ store = demoStore,
49
+ routes = defaultRoutes,
50
+ currentHref,
51
+ seedCart = false,
52
+ noindex = false,
53
+ minimal = false,
54
+ lang = 'en',
55
+ theme = 'dark',
56
+ themeToggle = true,
57
+ liveActivity = false,
58
+ } = Astro.props;
59
+
60
+ const fullTitle = title.includes(store.name) ? title : `${title} — ${store.name}`;
61
+ const links = [
62
+ {
63
+ label: 'Shop',
64
+ href: routes.shop,
65
+ children: store.categories.map((c) => ({ label: c.name, href: categoryHref(routes, c.id), description: c.description })),
66
+ },
67
+ { label: 'New in', href: `${routes.shop}?sort=featured` },
68
+ { label: 'Top rated', href: `${routes.shop}?sort=rating` },
69
+ { label: 'Sale', href: `${routes.shop}?sort=price-asc` },
70
+ ];
71
+ const seed = seedCart
72
+ ? store.products.slice(0, 1).concat(store.products.filter((p) => p.slug === 'ember-mug')).map((p, i) => ({
73
+ id: p.colors?.[0] ? `${p.slug}::${p.colors[0].name}` : p.slug,
74
+ name: p.name,
75
+ price: p.price,
76
+ qty: i === 0 ? 1 : 2,
77
+ variant: p.colors?.[0]?.name,
78
+ icon: p.icon,
79
+ hue: p.hue,
80
+ }))
81
+ : [];
82
+ const activity = liveActivity
83
+ ? store.recentBuyers
84
+ .map((b) => {
85
+ const p = store.products.find((x) => x.slug === b.product);
86
+ return p ? { name: b.name, city: b.city, ago: b.ago, product: p.name, href: `${routes.product}${p.slug}/`, icon: p.icon, hue: p.hue } : null;
87
+ })
88
+ .filter((x): x is NonNullable<typeof x> => Boolean(x))
89
+ : [];
90
+ const footer = [
91
+ { title: 'Shop', links: store.categories.map((c) => ({ label: c.name, href: categoryHref(routes, c.id) })) },
92
+ ...store.footer.slice(1),
93
+ ];
94
+ ---
95
+
96
+ <!doctype html>
97
+ <html lang={lang} data-theme={theme === 'auto' ? 'dark' : theme}>
98
+ <head>
99
+ <meta charset="utf-8" />
100
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
101
+ <title>{fullTitle}</title>
102
+ {description && <meta name="description" content={description} />}
103
+ {noindex && <meta name="robots" content="noindex, follow" />}
104
+ <meta name="theme-color" content={theme === 'light' ? '#f6f5f2' : '#050505'} />
105
+ <script is:inline define:vars={{ initial: theme, allowToggle: themeToggle }}>
106
+ (function () {
107
+ var t = initial;
108
+ try { if (allowToggle) t = localStorage.getItem('ack-theme') || t; } catch (e) {}
109
+ if (t === 'auto') t = matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
110
+ document.documentElement.dataset.theme = t;
111
+ })();
112
+ </script>
113
+ <slot name="head" />
114
+ </head>
115
+ <body class="store">
116
+ <a class="store__skip" href="#main">Skip to content</a>
117
+ {!minimal && <PromoBar messages={store.promoMessages} storageKey={`${store.cartKey}-promo`} />}
118
+ <StoreHeader
119
+ brand={store.name}
120
+ brandHref={routes.home}
121
+ links={minimal ? [] : links}
122
+ searchAction={routes.shop}
123
+ accountHref=""
124
+ cartKey={store.cartKey}
125
+ currentHref={currentHref}
126
+ themeToggle={themeToggle}
127
+ />
128
+ <main id="main" class="store__main" tabindex="-1">
129
+ <slot />
130
+ </main>
131
+ <FooterMegaSection
132
+ brand={store.name}
133
+ tagline={store.tagline}
134
+ columns={footer}
135
+ status="All orders shipping on time"
136
+ copyright={`© ${new Date().getFullYear()} ${store.name}. Demo store built with Astro Components Kit.`}
137
+ />
138
+ <CartDrawer
139
+ cartKey={store.cartKey}
140
+ checkoutHref={routes.checkout}
141
+ currency={store.currency}
142
+ locale={store.locale}
143
+ freeShippingThreshold={store.freeShippingThreshold}
144
+ demoItems={seed}
145
+ />
146
+ <CartToast cartKey={store.cartKey} />
147
+ {activity.length > 0 && <LiveActivityToast items={activity} storageKey={`${store.cartKey}-activity`} />}
148
+ </body>
149
+ </html>
150
+
151
+ <style is:global>
152
+ body.store { overflow-x: clip; }
153
+ .store__main { position: relative; z-index: 1; outline: none; }
154
+ .store__skip {
155
+ position: fixed;
156
+ top: -60px;
157
+ left: 12px;
158
+ z-index: 2000;
159
+ padding: 0.6rem 1rem;
160
+ border-radius: 999px;
161
+ background: var(--c-primary-light);
162
+ color: var(--c-on-primary);
163
+ font-weight: 700;
164
+ transition: top 0.2s ease;
165
+ }
166
+ .store__skip:focus { top: 12px; color: var(--c-on-primary); }
167
+ .store-container { width: 100%; max-width: 1280px; margin: 0 auto; padding: 0 var(--sp-6); }
168
+ @media (max-width: 640px) { .store-container { padding: 0 var(--sp-4); } }
169
+ </style>
@@ -0,0 +1,165 @@
1
+ ---
2
+ /**
3
+ * StoreProductPage — E-commerce template product detail: breadcrumb, ProductGallery,
4
+ * ProductBuyBox, reviews summary and related products.
5
+ *
6
+ * Usage (src/pages/product/[slug].astro):
7
+ * import { StoreProductPage, demoStore } from 'astro-component-kit/templates/ecommerce';
8
+ * export const getStaticPaths = () => demoStore.products.map((p) => ({ params: { slug: p.slug } }));
9
+ * <StoreProductPage slug={Astro.params.slug} />
10
+ *
11
+ * @param {string} slug - Product slug to show. Default: first product.
12
+ * @param {StoreConfig} store - Catalogue and settings. Default: demo store.
13
+ * @param {StoreRoutes} routes - Page URLs.
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 ProductGallery from '../../ecommerce/ProductGallery.astro';
20
+ import ProductBuyBox from '../../ecommerce/ProductBuyBox.astro';
21
+ import ProductRating from '../../ecommerce/ProductRating.astro';
22
+ import ProductGrid from '../../ecommerce/ProductGrid.astro';
23
+ import { demoStore, defaultRoutes, categoryHref, categoryNames, type StoreConfig, type StoreRoutes } from './data';
24
+
25
+ interface Props {
26
+ slug?: string;
27
+ store?: StoreConfig;
28
+ routes?: StoreRoutes;
29
+ seedCart?: boolean;
30
+ noindex?: boolean;
31
+ theme?: 'dark' | 'light' | 'auto';
32
+ }
33
+
34
+ const { slug, store = demoStore, routes = defaultRoutes, seedCart = false, noindex = false, theme = 'dark' } = Astro.props;
35
+
36
+ const product = store.products.find((p) => p.slug === slug) ?? store.products[0];
37
+ const category = store.categories.find((c) => c.id === product.category);
38
+ const related = store.products
39
+ .filter((p) => p.slug !== product.slug)
40
+ .sort((a, b) => Number(b.category === product.category) - Number(a.category === product.category))
41
+ .slice(0, 4);
42
+
43
+ // Deterministic demo review distribution derived from the rating.
44
+ const total = product.reviews ?? 0;
45
+ const r = product.rating ?? 0;
46
+ const weights = [Math.max(0, r - 3.6) * 3, 1.2, 0.5, 0.2, 0.12];
47
+ const sum = weights.reduce((a, b) => a + b, 0) || 1;
48
+ const bars = [5, 4, 3, 2, 1].map((stars, i) => ({ stars, count: Math.round((weights[i] / sum) * total) }));
49
+ const reviews = [
50
+ { name: 'Maya R.', title: 'Exceeded expectations', body: 'Beautifully made and it arrived in two days. I have already ordered a second one as a gift.', rating: 5 },
51
+ { name: 'Jonas K.', title: 'Great quality', body: 'Feels premium and the details are spot on. Packaging was plastic-free, which I appreciated.', rating: 5 },
52
+ { name: 'Lea M.', title: 'Almost perfect', body: 'Love the colour and the finish. Took a few days to get used to, but now I use it every day.', rating: 4 },
53
+ ];
54
+ const money = new Intl.NumberFormat(store.locale, { style: 'currency', currency: store.currency, maximumFractionDigits: 0 }).format(store.freeShippingThreshold);
55
+ ---
56
+
57
+ <StoreLayout theme={theme} title={product.name} description={product.description} store={store} routes={routes} seedCart={seedCart} noindex={noindex}>
58
+ <div class="store-container pdp">
59
+ <nav class="crumbs" aria-label="Breadcrumb">
60
+ <ol>
61
+ <li><a href={routes.home}>Home</a></li>
62
+ <li><a href={routes.shop}>Shop</a></li>
63
+ {category && <li><a href={categoryHref(routes, category.id)}>{category.name}</a></li>}
64
+ <li><span aria-current="page">{product.name}</span></li>
65
+ </ol>
66
+ </nav>
67
+
68
+ <div class="pdp__main">
69
+ <ProductGallery images={product.image ? [product.image] : []} alt={product.name} icon={product.icon} hue={product.hue} />
70
+ <ProductBuyBox
71
+ product={product}
72
+ categoryLabel={category?.name}
73
+ checkoutHref={routes.checkout}
74
+ currency={store.currency}
75
+ locale={store.locale}
76
+ cartKey={store.cartKey}
77
+ details={[
78
+ { title: 'Details', body: product.description ?? '' },
79
+ { title: 'Shipping & returns', body: `Free standard shipping over ${money}, delivered in 2–4 business days. Return anything within 30 days for a full refund.` },
80
+ { title: 'Warranty', body: 'Two-year limited warranty covering manufacturing defects, with free repair or replacement.' },
81
+ ]}
82
+ />
83
+ </div>
84
+
85
+ {total > 0 && (
86
+ <section class="reviews" id="reviews" aria-labelledby="reviews-title">
87
+ <div class="reviews__summary">
88
+ <h2 id="reviews-title">Customer reviews</h2>
89
+ <p class="reviews__score">{r.toFixed(1)}<span>/5</span></p>
90
+ <ProductRating value={r} count={total} size="lg" showValue={false} />
91
+ <ul class="reviews__bars" aria-label="Rating distribution">
92
+ {bars.map((b) => (
93
+ <li>
94
+ <span>{b.stars} ★</span>
95
+ <span class="reviews__bar" aria-hidden="true"><span style={`width: ${total ? (b.count / total) * 100 : 0}%`}></span></span>
96
+ <span class="reviews__count">{b.count}<span class="sr-only"> reviews with {b.stars} stars</span></span>
97
+ </li>
98
+ ))}
99
+ </ul>
100
+ </div>
101
+ <ul class="reviews__list">
102
+ {reviews.map((rv) => (
103
+ <li class="review">
104
+ <ProductRating value={rv.rating} showValue={false} />
105
+ <h3>{rv.title}</h3>
106
+ <p>{rv.body}</p>
107
+ <p class="review__by">{rv.name} · <span>Verified buyer</span></p>
108
+ </li>
109
+ ))}
110
+ </ul>
111
+ </section>
112
+ )}
113
+
114
+ <section class="related" aria-labelledby="related-title">
115
+ <h2 id="related-title">You may also like</h2>
116
+ <ProductGrid id="related" products={related} productBase={routes.product} categoryNames={categoryNames(store)} currency={store.currency} locale={store.locale} cartKey={store.cartKey} openCart={false} />
117
+ </section>
118
+ </div>
119
+ </StoreLayout>
120
+
121
+ <style>
122
+ .pdp { padding-block: var(--sp-6) clamp(3rem, 7vw, 5rem); }
123
+ .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; }
124
+ .crumbs li { display: flex; align-items: center; gap: var(--sp-2); color: var(--c-text-2); }
125
+ .crumbs li + li::before { content: '/'; opacity: 0.5; }
126
+ .crumbs a { color: var(--c-text-2); }
127
+ .crumbs a:hover { color: var(--c-text-1); }
128
+ .crumbs [aria-current='page'] { color: var(--c-text-1); font-weight: 600; }
129
+
130
+ .pdp__main { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr); gap: clamp(1.5rem, 4vw, 4rem); align-items: start; }
131
+ .pdp__main > :global(.p-gallery-cq) { position: sticky; top: 88px; }
132
+
133
+ .reviews {
134
+ display: grid;
135
+ grid-template-columns: 300px minmax(0, 1fr);
136
+ gap: clamp(1.5rem, 4vw, 3.5rem);
137
+ margin-top: clamp(3rem, 7vw, 5rem);
138
+ padding-top: clamp(2rem, 5vw, 3rem);
139
+ border-top: 1px solid var(--c-border);
140
+ }
141
+ .reviews h2, .related h2 { margin: 0 0 var(--sp-4); font-size: clamp(1.4rem, 3vw, 1.9rem); font-weight: 900; letter-spacing: -0.03em; color: var(--c-text-1); }
142
+ .reviews__score { margin: 0; font-size: 3.2rem; font-weight: 900; line-height: 1; color: var(--c-text-1); letter-spacing: -0.04em; }
143
+ .reviews__score span { font-size: 1.1rem; color: var(--c-text-2); font-weight: 600; }
144
+ .reviews__summary :global(.p-rating) { margin: var(--sp-3) 0 var(--sp-5); }
145
+ .reviews__bars { display: grid; gap: var(--sp-2); margin: 0; padding: 0; list-style: none; }
146
+ .reviews__bars li { display: grid; grid-template-columns: 36px 1fr 36px; align-items: center; gap: var(--sp-3); font-size: 0.82rem; }
147
+ .reviews__bar { height: 8px; border-radius: 999px; background: rgb(var(--ink-rgb, 255 255 255) / 0.07); overflow: hidden; }
148
+ .reviews__bar span { display: block; height: 100%; border-radius: inherit; background: var(--c-primary-light); }
149
+ .reviews__count { text-align: right; color: var(--c-text-2); }
150
+
151
+ .reviews__list { display: grid; gap: var(--sp-4); margin: 0; padding: 0; list-style: none; }
152
+ .review { display: grid; gap: var(--sp-2); padding: var(--sp-5); border-radius: var(--r-lg); border: 1px solid var(--c-border); background: var(--c-surface); }
153
+ .review h3 { margin: 0; font-size: 1rem; color: var(--c-text-1); }
154
+ .review p { margin: 0; line-height: 1.65; font-size: 0.92rem; }
155
+ .review__by { font-size: 0.82rem !important; color: var(--c-text-1); font-weight: 600; }
156
+ .review__by span { color: var(--c-success, #34d399); font-weight: 500; }
157
+
158
+ .related { margin-top: clamp(3rem, 7vw, 5rem); }
159
+ .sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
160
+
161
+ @media (max-width: 900px) {
162
+ .pdp__main, .reviews { grid-template-columns: 1fr; }
163
+ .pdp__main > :global(.p-gallery-cq) { position: static; }
164
+ }
165
+ </style>