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,229 @@
1
+ ---
2
+ /**
3
+ * ProductCardMinimal — Editorial product card: tall media that swaps to a second view on hover,
4
+ * quick-add size buttons, name, price and colour count. Square corners, typography-led.
5
+ *
6
+ * @param {Product} product - Product data. A demo product is used by default.
7
+ * @param {string} href - Product page URL. Default '#'.
8
+ * @param {number} index - Optional catalogue number shown as "N° 01".
9
+ * @param {string} currency - ISO currency. Default 'EUR'.
10
+ * @param {string} locale - Number format locale. Default 'en-GB'.
11
+ * @param {string} cartKey - Cart storage key. Default 'ack-cart'.
12
+ * @param {boolean} quickAdd - Show quick-add (sizes, or a single "Add" button). Default true.
13
+ * @param {string} ratio - Media aspect ratio. Default '3 / 4'.
14
+ * @param {string} variant - Style (plain|framed). Default 'plain'.
15
+ * @param {string} size - Type scale (sm|md|lg). Default 'md'.
16
+ * @param {string} headingLevel - Title element (h2|h3|h4). Default 'h3'.
17
+ * @param {string} className - Extra class.
18
+ *
19
+ * Accessibility: the title is a stretched link; quick-add buttons sit above it with their own
20
+ * labels ("Add size M"), are reachable by keyboard and appear on focus as well as hover.
21
+ */
22
+ import ProductMedia from './ProductMedia.astro';
23
+ import { formatMoney } from './cart-store';
24
+ import type { Product } from './types';
25
+
26
+ interface Props {
27
+ product?: Product;
28
+ href?: string;
29
+ index?: number;
30
+ currency?: string;
31
+ locale?: string;
32
+ cartKey?: string;
33
+ quickAdd?: boolean;
34
+ ratio?: string;
35
+ variant?: 'plain' | 'framed';
36
+ size?: 'sm' | 'md' | 'lg';
37
+ headingLevel?: 'h2' | 'h3' | 'h4';
38
+ className?: string;
39
+ }
40
+
41
+ const {
42
+ product = {
43
+ slug: 'linen-overshirt',
44
+ name: 'Linen Overshirt',
45
+ price: 145,
46
+ category: 'clothing',
47
+ badge: 'New',
48
+ colors: [{ name: 'Oat', hex: '#d9cbb3' }, { name: 'Ink', hex: '#22252b' }, { name: 'Rust', hex: '#9c3d1c' }],
49
+ sizes: ['XS', 'S', 'M', 'L', 'XL'],
50
+ icon: 'shirt',
51
+ hue: 30,
52
+ },
53
+ href = '#',
54
+ index,
55
+ currency = 'EUR',
56
+ locale = 'en-GB',
57
+ cartKey = 'ack-cart',
58
+ quickAdd = true,
59
+ ratio = '3 / 4',
60
+ variant = 'plain',
61
+ size = 'md',
62
+ headingLevel = 'h3',
63
+ className = '',
64
+ } = Astro.props;
65
+
66
+ const Heading = headingLevel;
67
+ const money = { currency, locale };
68
+ const inStock = product.inStock !== false;
69
+ const base = { id: product.slug, name: product.name, price: product.price, icon: product.icon, hue: product.hue, image: product.image };
70
+ const color = product.colors?.[0]?.name;
71
+ const titleId = `pcm-${product.slug}`;
72
+ ---
73
+
74
+ <article
75
+ class:list={['pcm', `pcm--${variant}`, `pcm--${size}`, className]}
76
+ aria-labelledby={titleId}
77
+ data-product
78
+ data-name={product.name.toLowerCase()}
79
+ data-category={product.category}
80
+ data-price={product.price}
81
+ data-rating={product.rating ?? 0}
82
+ data-colors={(product.colors ?? []).map((c) => c.name.toLowerCase()).join(',')}
83
+ data-stock={inStock ? 'in' : 'out'}
84
+ >
85
+ <div class="pcm__media">
86
+ <div class="pcm__view pcm__view--a">
87
+ <ProductMedia image={product.image} alt={product.name} icon={product.icon} hue={product.hue} ratio={ratio} variant="soft" size="lg" />
88
+ </div>
89
+ <div class="pcm__view pcm__view--b" aria-hidden="true">
90
+ <ProductMedia icon={product.icon} hue={(product.hue ?? 30) + 20} ratio={ratio} variant="glow" size="sm" />
91
+ </div>
92
+ {(product.badge || !inStock) && <span class="pcm__badge">{inStock ? product.badge : 'Sold out'}</span>}
93
+ {index !== undefined && <span class="pcm__index" aria-hidden="true">N° {String(index).padStart(2, '0')}</span>}
94
+
95
+ {quickAdd && inStock && (
96
+ <div class="pcm__quick" role="group" aria-label={`Quick add ${product.name}`}>
97
+ {product.sizes?.length ? (
98
+ <>
99
+ <span class="pcm__quick-label" aria-hidden="true">Quick add</span>
100
+ <div class="pcm__sizes">
101
+ {product.sizes.map((s) => (
102
+ <button
103
+ type="button"
104
+ class="pcm__size"
105
+ data-add-to-cart={JSON.stringify({ ...base, variant: [color, s].filter(Boolean).join(' / ') })}
106
+ data-cart-key={cartKey}
107
+ data-open-cart="false"
108
+ aria-label={`Add ${product.name}, size ${s}`}
109
+ ><span data-label>{s}</span></button>
110
+ ))}
111
+ </div>
112
+ </>
113
+ ) : (
114
+ <button
115
+ type="button"
116
+ class="pcm__size pcm__size--wide"
117
+ data-add-to-cart={JSON.stringify({ ...base, variant: color })}
118
+ data-cart-key={cartKey}
119
+ data-open-cart="false"
120
+ data-added-label="Added"
121
+ aria-label={`Add ${product.name} to bag`}
122
+ ><span data-label>Add to bag +</span></button>
123
+ )}
124
+ </div>
125
+ )}
126
+ </div>
127
+
128
+ <div class="pcm__info">
129
+ <Heading class="pcm__title" id={titleId}><a class="pcm__link" href={href}>{product.name}</a></Heading>
130
+ <p class="pcm__price">
131
+ <span>{formatMoney(product.price, money)}</span>
132
+ {product.compareAt && <s><span class="sr-only">Was </span>{formatMoney(product.compareAt, money)}</s>}
133
+ </p>
134
+ {product.colors && product.colors.length > 0 && (
135
+ <p class="pcm__colors">
136
+ <span class="pcm__dots" aria-hidden="true">{product.colors.slice(0, 4).map((c) => <i style={`--sw: ${c.hex}`}></i>)}</span>
137
+ {product.colors.length} {product.colors.length === 1 ? 'colour' : 'colours'}
138
+ </p>
139
+ )}
140
+ </div>
141
+ </article>
142
+
143
+ <script>
144
+ import { bindAddToCart } from './cart-store';
145
+ bindAddToCart(document);
146
+ </script>
147
+
148
+ <style>
149
+ .pcm { position: relative; display: flex; flex-direction: column; gap: var(--sp-3, 0.75rem); min-width: 0; }
150
+ .pcm--framed { padding: var(--sp-3, 0.75rem); border: 1px solid var(--c-border, rgba(255, 255, 255, 0.08)); }
151
+ .pcm__media { position: relative; overflow: hidden; border-radius: var(--r-sm, 2px); background: var(--c-bg-2, #111); }
152
+ .pcm__view { transition: opacity 0.5s ease, transform 0.8s cubic-bezier(0.16, 1, 0.3, 1); }
153
+ .pcm__view--b { position: absolute; inset: 0; opacity: 0; transform: scale(1.04); }
154
+ .pcm__view--b :global(.p-media__art) { transform: rotate(-12deg) scale(1.25); }
155
+ .pcm:hover .pcm__view--b, .pcm:focus-within .pcm__view--b { opacity: 1; transform: none; }
156
+ .pcm:hover .pcm__view--a { transform: scale(1.03); }
157
+
158
+ .pcm__badge, .pcm__index {
159
+ position: absolute;
160
+ top: var(--sp-3, 0.75rem);
161
+ z-index: 2;
162
+ font-size: 0.68rem;
163
+ font-weight: 700;
164
+ letter-spacing: 0.14em;
165
+ text-transform: uppercase;
166
+ }
167
+ .pcm__badge { left: var(--sp-3, 0.75rem); padding: 0.3rem 0.55rem; background: var(--c-bg-1, #0a0a0a); color: var(--c-text-1, #fff); }
168
+ .pcm__index { right: var(--sp-3, 0.75rem); color: var(--c-text-2, #a3a3a3); font-variant-numeric: tabular-nums; }
169
+
170
+ .pcm__quick {
171
+ position: absolute;
172
+ left: var(--sp-2, 0.5rem);
173
+ right: var(--sp-2, 0.5rem);
174
+ bottom: var(--sp-2, 0.5rem);
175
+ z-index: 3;
176
+ display: grid;
177
+ gap: 6px;
178
+ padding: var(--sp-3, 0.75rem);
179
+ background: rgb(var(--paper-rgb, 5 5 5) / 0.92);
180
+ backdrop-filter: blur(10px);
181
+ transform: translateY(calc(100% + 12px));
182
+ transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
183
+ }
184
+ .pcm:hover .pcm__quick, .pcm__quick:focus-within { transform: none; }
185
+ .pcm__quick-label { font-size: 0.66rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--c-text-2, #a3a3a3); }
186
+ .pcm__sizes { display: flex; flex-wrap: wrap; gap: 4px; }
187
+ .pcm__size {
188
+ flex: 1 0 36px;
189
+ min-height: 36px;
190
+ padding: 0 6px;
191
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.18);
192
+ border-radius: var(--r-sm, 2px);
193
+ background: transparent;
194
+ color: var(--c-text-1, #fff);
195
+ font: inherit;
196
+ font-size: 0.78rem;
197
+ font-weight: 600;
198
+ cursor: pointer;
199
+ transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
200
+ }
201
+ .pcm__size:hover { background: var(--c-text-1, #fff); color: var(--c-bg, #050505); border-color: var(--c-text-1, #fff); }
202
+ .pcm__size.is-added { background: var(--shop-accent, var(--c-primary-light, #d4a017)); border-color: transparent; color: var(--c-on-primary, #050505); }
203
+ .pcm__size--wide { flex: 1 1 100%; letter-spacing: 0.04em; }
204
+ .pcm__size:focus-visible { outline: 2px solid var(--shop-accent, var(--c-primary-light, #d4a017)); outline-offset: 2px; }
205
+
206
+ .pcm__info { display: grid; gap: 2px; }
207
+ .pcm__title { margin: 0; font-family: var(--font-display, Georgia, 'Times New Roman', serif); font-size: 1.05rem; font-weight: 500; letter-spacing: -0.01em; line-height: 1.25; }
208
+ .pcm--sm .pcm__title { font-size: 0.95rem; }
209
+ .pcm--lg .pcm__title { font-size: 1.3rem; }
210
+ .pcm__link { color: var(--c-text-1, #fff); text-decoration: none; }
211
+ .pcm__link::after { content: ''; position: absolute; inset: 0; z-index: 1; }
212
+ .pcm__link:focus-visible { outline: none; }
213
+ .pcm:focus-within:not(:has(.pcm__quick:focus-within)) { outline: 2px solid var(--shop-accent, var(--c-primary-light, #d4a017)); outline-offset: 4px; }
214
+ .pcm__price { display: flex; gap: var(--sp-2, 0.5rem); margin: 0; font-size: 0.9rem; color: var(--c-text-1, #fff); font-variant-numeric: tabular-nums; }
215
+ .pcm__price s { color: var(--c-text-2, #a3a3a3); }
216
+ .pcm__colors { display: flex; align-items: center; gap: var(--sp-2, 0.5rem); margin: 2px 0 0; font-size: 0.78rem; color: var(--c-text-2, #a3a3a3); }
217
+ .pcm__dots { display: inline-flex; }
218
+ .pcm__dots i { width: 10px; height: 10px; margin-right: -3px; border-radius: 50%; background: var(--sw); box-shadow: 0 0 0 1.5px var(--c-bg, #050505); }
219
+
220
+ .sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
221
+
222
+ @media (hover: none) {
223
+ .pcm__quick { position: static; transform: none; padding: var(--sp-2, 0.5rem) 0 0; background: transparent; backdrop-filter: none; }
224
+ .pcm__quick-label { display: none; }
225
+ }
226
+ @media (prefers-reduced-motion: reduce) {
227
+ .pcm__view, .pcm__quick { transition: none; }
228
+ }
229
+ </style>
@@ -0,0 +1,145 @@
1
+ ---
2
+ /**
3
+ * ProductGallery — Product image viewer with a large stage and thumbnail rail.
4
+ * Works with real photos (`images`) or generates several illustrated "angles".
5
+ *
6
+ * @param {string[]} images - Photo URLs. When empty, illustrated views are generated.
7
+ * @param {string} alt - Alt text base for photos. Default 'Product photo'.
8
+ * @param {string} icon - Illustration for generated views. Default 'headphones'.
9
+ * @param {number} hue - Backdrop hue for generated views. Default 38.
10
+ * @param {number} views - Number of generated views (2–5). Default 4.
11
+ * @param {string} layout - Thumbnail position (bottom|side). Default 'side'.
12
+ * @param {string} size - Stage ratio preset (sm|md|lg). Default 'md'.
13
+ * @param {string} className - Extra class.
14
+ *
15
+ * Accessibility: thumbnails are buttons with aria-pressed and labels; arrow keys move
16
+ * between thumbnails; the stage announces the current view number.
17
+ */
18
+ import ProductMedia from './ProductMedia.astro';
19
+
20
+ interface Props {
21
+ images?: string[];
22
+ alt?: string;
23
+ icon?: string;
24
+ hue?: number;
25
+ views?: number;
26
+ layout?: 'bottom' | 'side';
27
+ size?: 'sm' | 'md' | 'lg';
28
+ className?: string;
29
+ }
30
+
31
+ const {
32
+ images = [],
33
+ alt = 'Product photo',
34
+ icon = 'headphones',
35
+ hue = 38,
36
+ views = 4,
37
+ layout = 'side',
38
+ size = 'md',
39
+ className = '',
40
+ } = Astro.props;
41
+
42
+ const generated = Array.from({ length: Math.min(5, Math.max(2, views)) }, (_, i) => ({
43
+ hue: (hue + i * 18) % 360,
44
+ variant: (i % 2 === 0 ? 'glow' : 'soft') as 'glow' | 'soft',
45
+ turn: [0, -14, 12, -4, 20][i] ?? 0,
46
+ zoom: [1, 1.18, 0.92, 1.3, 1.05][i] ?? 1,
47
+ }));
48
+ const slides = images.length
49
+ ? images.map((src, i) => ({ src, label: `${alt} ${i + 1}` }))
50
+ : generated.map((g, i) => ({ ...g, label: `View ${i + 1}` }));
51
+ const ratio = size === 'sm' ? '1 / 1' : size === 'lg' ? '4 / 5' : '5 / 5.4';
52
+ ---
53
+
54
+ <div class="p-gallery-cq">
55
+ <div class:list={['p-gallery', `p-gallery--${layout}`, `p-gallery--${size}`, className]} data-gallery>
56
+ <div class="p-gallery__stage" aria-live="polite">
57
+ {slides.map((s: any, i) => (
58
+ <div class:list={['p-gallery__slide', { 'is-active': i === 0 }]} data-slide hidden={i !== 0} style={s.turn !== undefined ? `--turn: ${s.turn}deg; --zoom: ${s.zoom}` : ''}>
59
+ {s.src
60
+ ? <ProductMedia image={s.src} alt={s.label} ratio={ratio} />
61
+ : <ProductMedia icon={icon} hue={s.hue} variant={s.variant} ratio={ratio} size="lg" />}
62
+ <span class="sr-only">{s.label} of {slides.length}</span>
63
+ </div>
64
+ ))}
65
+ </div>
66
+ <div class="p-gallery__thumbs" role="group" aria-label="Product views">
67
+ {slides.map((s: any, i) => (
68
+ <button class:list={['p-gallery__thumb']} type="button" aria-pressed={i === 0 ? 'true' : 'false'} aria-label={`Show ${s.label}`} data-thumb={i} style={s.turn !== undefined ? `--turn: ${s.turn}deg; --zoom: ${s.zoom}` : ''}>
69
+ {s.src
70
+ ? <ProductMedia image={s.src} alt="" ratio="1 / 1" />
71
+ : <ProductMedia icon={icon} hue={s.hue} variant={s.variant} ratio="1 / 1" size="sm" />}
72
+ </button>
73
+ ))}
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ <script>
79
+ document.querySelectorAll<HTMLElement>('[data-gallery]:not([data-bound])').forEach((g) => {
80
+ g.dataset.bound = '';
81
+ const slides = Array.from(g.querySelectorAll<HTMLElement>('[data-slide]'));
82
+ const thumbs = Array.from(g.querySelectorAll<HTMLButtonElement>('[data-thumb]'));
83
+ const show = (i: number, focus = false) => {
84
+ slides.forEach((s, n) => { s.hidden = n !== i; s.classList.toggle('is-active', n === i); });
85
+ thumbs.forEach((t, n) => t.setAttribute('aria-pressed', String(n === i)));
86
+ if (focus) thumbs[i]?.focus();
87
+ };
88
+ thumbs.forEach((t, i) => {
89
+ t.addEventListener('click', () => show(i));
90
+ t.addEventListener('keydown', (e) => {
91
+ const keys: Record<string, number> = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 };
92
+ if (!(e.key in keys)) return;
93
+ e.preventDefault();
94
+ show((i + keys[e.key] + thumbs.length) % thumbs.length, true);
95
+ });
96
+ });
97
+ });
98
+ </script>
99
+
100
+ <style>
101
+ .p-gallery-cq { container-type: inline-size; }
102
+ .p-gallery { display: grid; gap: var(--sp-3, 0.75rem); }
103
+ .p-gallery--side { grid-template-columns: 84px minmax(0, 1fr); }
104
+ .p-gallery--side .p-gallery__stage { order: 2; }
105
+ .p-gallery--side .p-gallery__thumbs { order: 1; flex-direction: column; }
106
+
107
+ .p-gallery__stage {
108
+ position: relative;
109
+ border-radius: var(--r-xl, 28px);
110
+ border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.05));
111
+ overflow: hidden;
112
+ }
113
+ .p-gallery__slide[hidden] { display: none; }
114
+ .p-gallery__slide.is-active { animation: pg-in 0.35s ease-out; }
115
+ .p-gallery__slide :global(.p-media__art),
116
+ .p-gallery__thumb :global(.p-media__art) { transform: rotate(var(--turn, 0deg)) scale(var(--zoom, 1)); }
117
+
118
+ .p-gallery__thumbs { display: flex; gap: var(--sp-2, 0.5rem); }
119
+ .p-gallery__thumb {
120
+ flex: 0 0 84px;
121
+ width: 84px;
122
+ padding: 0;
123
+ border: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.05));
124
+ border-radius: var(--r-md, 12px);
125
+ overflow: hidden;
126
+ background: none;
127
+ cursor: pointer;
128
+ opacity: 0.6;
129
+ transition: opacity 0.2s ease, border-color 0.2s ease;
130
+ }
131
+ .p-gallery__thumb:hover { opacity: 1; }
132
+ .p-gallery__thumb[aria-pressed='true'] { opacity: 1; border-color: var(--c-primary-light, #d4a017); }
133
+ .p-gallery__thumb:focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
134
+
135
+ @keyframes pg-in { from { opacity: 0.4; transform: scale(0.985); } }
136
+ .sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
137
+
138
+ @container (max-width: 700px) {
139
+ .p-gallery--side { grid-template-columns: 1fr; }
140
+ .p-gallery--side .p-gallery__stage { order: 1; }
141
+ .p-gallery--side .p-gallery__thumbs { order: 2; flex-direction: row; }
142
+ .p-gallery__thumb { flex-basis: 64px; width: 64px; }
143
+ }
144
+ @media (prefers-reduced-motion: reduce) { .p-gallery__slide.is-active { animation: none; } }
145
+ </style>
@@ -0,0 +1,118 @@
1
+ ---
2
+ /**
3
+ * ProductGrid — Responsive grid of StoreProductCard tiles with an empty state.
4
+ * Works on its own or as the target of FilterSidebar / ProductToolbar (same `id`).
5
+ *
6
+ * @param {Product[]} products - Products to render.
7
+ * @param {string} id - Grid id; FilterSidebar and ProductToolbar point at it. Default 'product-grid'.
8
+ * @param {string} productBase - Product URL prefix; links become `${productBase}${slug}/`. Default '#'.
9
+ * @param {Record<string, string>} categoryNames - Category id → label for the card eyebrow.
10
+ * @param {number} columns - Desktop columns (2–5). Default 4.
11
+ * @param {string} cardVariant - StoreProductCard variant (glass|minimal|bordered). Default 'glass'.
12
+ * @param {string} cardSize - StoreProductCard size (sm|md|lg). Default 'md'.
13
+ * @param {string} currency - ISO currency. Default 'USD'.
14
+ * @param {string} locale - Number format locale. Default 'en-US'.
15
+ * @param {string} cartKey - Cart storage key. Default 'ack-cart'.
16
+ * @param {boolean} openCart - Open the drawer after adding (false = toast). Default true.
17
+ * @param {string} emptyTitle - Empty state heading. Default 'No products match'.
18
+ * @param {string} emptyText - Empty state copy.
19
+ * @param {string} className - Extra class.
20
+ *
21
+ * Accessibility: cards keep their own headings; the empty state is a polite status message.
22
+ */
23
+ import StoreProductCard from './StoreProductCard.astro';
24
+ import type { Product } from './types';
25
+
26
+ interface Props {
27
+ products?: Product[];
28
+ id?: string;
29
+ productBase?: string;
30
+ categoryNames?: Record<string, string>;
31
+ columns?: number;
32
+ cardVariant?: 'glass' | 'minimal' | 'bordered';
33
+ cardSize?: 'sm' | 'md' | 'lg';
34
+ currency?: string;
35
+ locale?: string;
36
+ cartKey?: string;
37
+ openCart?: boolean;
38
+ emptyTitle?: string;
39
+ emptyText?: string;
40
+ className?: string;
41
+ }
42
+
43
+ const {
44
+ products = [
45
+ { slug: 'aurora-headphones', name: 'Aurora Wireless Headphones', price: 189, compareAt: 229, category: 'audio', rating: 4.8, reviews: 312, badge: '-18%', badgeVariant: 'warning', icon: 'headphones', hue: 38 },
46
+ { slug: 'orbit-watch', name: 'Orbit Smart Watch', price: 249, category: 'wearables', rating: 4.6, reviews: 198, badge: 'New', badgeVariant: 'info', icon: 'watch', hue: 205 },
47
+ { slug: 'trail-backpack', name: 'Trail 24L Backpack', price: 119, category: 'bags', rating: 4.7, reviews: 87, icon: 'backpack', hue: 150 },
48
+ { slug: 'halo-lamp', name: 'Halo Desk Lamp', price: 79, category: 'home', rating: 4.5, reviews: 54, icon: 'lamp', hue: 25 },
49
+ ],
50
+ id = 'product-grid',
51
+ productBase = '#',
52
+ categoryNames = { audio: 'Audio', wearables: 'Wearables', bags: 'Bags', home: 'Home' },
53
+ columns = 4,
54
+ cardVariant = 'glass',
55
+ cardSize = 'md',
56
+ currency = 'USD',
57
+ locale = 'en-US',
58
+ cartKey = 'ack-cart',
59
+ openCart = true,
60
+ emptyTitle = 'No products match',
61
+ emptyText = 'Try removing a filter or widening the price range.',
62
+ className = '',
63
+ } = Astro.props;
64
+
65
+ const cols = Math.min(5, Math.max(2, columns));
66
+ const hrefFor = (slug: string) => (productBase === '#' ? '#' : `${productBase}${slug}/`);
67
+ ---
68
+
69
+ <div class:list={['p-grid-wrap', className]}>
70
+ <div class="p-grid" id={id} style={`--pg-cols: ${cols}`} data-product-list>
71
+ {products.map((p, i) => (
72
+ <StoreProductCard
73
+ product={p}
74
+ href={hrefFor(p.slug)}
75
+ categoryLabel={categoryNames[p.category] ?? ''}
76
+ variant={cardVariant}
77
+ size={cardSize}
78
+ currency={currency}
79
+ locale={locale}
80
+ cartKey={cartKey}
81
+ openCart={openCart}
82
+ />
83
+ ))}
84
+ </div>
85
+ <div class="p-grid__empty" role="status" data-product-empty hidden>
86
+ <svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5M8 11h6"/></svg>
87
+ <p><strong>{emptyTitle}</strong></p>
88
+ <p>{emptyText}</p>
89
+ </div>
90
+ </div>
91
+
92
+ <style>
93
+ .p-grid-wrap { container-type: inline-size; }
94
+ .p-grid {
95
+ display: grid;
96
+ grid-template-columns: repeat(var(--pg-cols), minmax(0, 1fr));
97
+ gap: var(--sp-5, 1.25rem);
98
+ }
99
+ .p-grid > :global([hidden]) { display: none; }
100
+ .p-grid__empty {
101
+ display: grid;
102
+ justify-items: center;
103
+ gap: var(--sp-2, 0.5rem);
104
+ padding: var(--sp-16, 4rem) var(--sp-6, 1.5rem);
105
+ border: 1px dashed rgb(var(--ink-rgb, 255 255 255) / 0.1);
106
+ border-radius: var(--r-lg, 20px);
107
+ text-align: center;
108
+ color: var(--c-text-2, #a3a3a3);
109
+ }
110
+ .p-grid__empty[hidden] { display: none; }
111
+ .p-grid__empty svg { color: var(--c-primary-light, #d4a017); }
112
+ .p-grid__empty p { margin: 0; }
113
+ .p-grid__empty strong { color: var(--c-text-1, #fff); font-size: 1.05rem; }
114
+
115
+ @container (max-width: 1100px) { .p-grid { grid-template-columns: repeat(min(var(--pg-cols), 3), minmax(0, 1fr)); } }
116
+ @container (max-width: 760px) { .p-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3, 0.75rem); } }
117
+ @container (max-width: 380px) { .p-grid { grid-template-columns: 1fr; } }
118
+ </style>
@@ -0,0 +1,113 @@
1
+ ---
2
+ /**
3
+ * ProductMedia — Product visual: a real photo, or a generated backdrop with a
4
+ * line-art illustration so demo catalogs look finished without external images.
5
+ *
6
+ * @param {string} image - Product photo URL. When set, replaces the illustration.
7
+ * @param {string} alt - Alternative text. Required for photos; illustrations are decorative.
8
+ * @param {string} icon - Built-in illustration (headphones|watch|sneaker|backpack|camera|lamp|keyboard|sunglasses|bottle|speaker|jacket|mug|plant|shirt|bag|phone).
9
+ * @param {number} hue - 0–360 hue of the generated backdrop. Default 40 (gold).
10
+ * @param {string} ratio - CSS aspect-ratio. Default '1 / 1'.
11
+ * @param {string} size - Illustration scale (sm|md|lg). Default 'md'.
12
+ * @param {string} variant - Backdrop style (glow|soft|flat). Default 'glow'.
13
+ * @param {string} className - Extra class.
14
+ *
15
+ * Accessibility: illustrations are aria-hidden; photos need `alt`.
16
+ */
17
+ import { productIcon } from './product-icons';
18
+
19
+ interface Props {
20
+ image?: string;
21
+ alt?: string;
22
+ icon?: string;
23
+ hue?: number;
24
+ ratio?: string;
25
+ size?: 'sm' | 'md' | 'lg';
26
+ variant?: 'glow' | 'soft' | 'flat';
27
+ className?: string;
28
+ }
29
+
30
+ const {
31
+ image,
32
+ alt = '',
33
+ icon = 'bag',
34
+ hue = 40,
35
+ ratio = '1 / 1',
36
+ size = 'md',
37
+ variant = 'glow',
38
+ className = '',
39
+ } = Astro.props;
40
+ ---
41
+
42
+ <div
43
+ class:list={['p-media', `p-media--${size}`, `p-media--${variant}`, className]}
44
+ style={`--pm-hue: ${hue}; --pm-ratio: ${ratio};`}
45
+ >
46
+ {image ? (
47
+ <img class="p-media__img" src={image} alt={alt} loading="lazy" decoding="async" />
48
+ ) : (
49
+ <svg class="p-media__art" viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><Fragment set:html={productIcon(icon)} /></svg>
50
+ )}
51
+ <slot />
52
+ </div>
53
+
54
+ <style>
55
+ .p-media {
56
+ --pm-bg-1: hsl(var(--pm-hue) 70% 50% / 0.28);
57
+ --pm-bg-2: hsl(var(--pm-hue) 60% 30% / 0.14);
58
+ --pm-ink: hsl(var(--pm-hue) var(--pm-ink-s, 80%) var(--pm-ink-l, 80%));
59
+ position: relative;
60
+ display: grid;
61
+ place-items: center;
62
+ aspect-ratio: var(--pm-ratio);
63
+ width: 100%;
64
+ overflow: hidden;
65
+ border-radius: inherit;
66
+ background:
67
+ radial-gradient(circle at 50% 42%, var(--pm-bg-1), transparent 62%),
68
+ linear-gradient(160deg, var(--pm-bg-2), var(--c-bg-1, #0a0a0a) 75%);
69
+ isolation: isolate;
70
+ }
71
+ .p-media--soft {
72
+ background: linear-gradient(160deg, hsl(var(--pm-hue) var(--pm-soft-s, 40%) var(--pm-soft-l, 20%) / var(--pm-soft-a, 0.5)), var(--c-bg-1, #0a0a0a));
73
+ }
74
+ .p-media--flat { background: var(--c-bg-2, #111); }
75
+
76
+ .p-media::after {
77
+ content: '';
78
+ position: absolute;
79
+ inset: auto 22% 14% 22%;
80
+ height: 10%;
81
+ border-radius: 50%;
82
+ background: radial-gradient(ellipse, rgba(0, 0, 0, 0.55), transparent 70%);
83
+ z-index: -1;
84
+ }
85
+ .p-media--flat::after { display: none; }
86
+
87
+ .p-media__art {
88
+ width: 46%;
89
+ height: auto;
90
+ color: var(--pm-ink);
91
+ filter: drop-shadow(0 6px 18px hsl(var(--pm-hue) 80% 50% / 0.35));
92
+ transition: transform 0.45s cubic-bezier(0.16, 1, 0.3, 1);
93
+ }
94
+ .p-media--sm .p-media__art { width: 58%; stroke-width: 2.4; }
95
+ .p-media--lg .p-media__art { width: 40%; stroke-width: 1.4; }
96
+
97
+ .p-media__img {
98
+ position: absolute;
99
+ inset: 0;
100
+ width: 100%;
101
+ height: 100%;
102
+ object-fit: cover;
103
+ }
104
+
105
+ :global(.is-hoverable:hover) .p-media__art,
106
+ :global(.is-hoverable:focus-within) .p-media__art {
107
+ transform: translateY(-4%) rotate(-4deg) scale(1.04);
108
+ }
109
+
110
+ @media (prefers-reduced-motion: reduce) {
111
+ .p-media__art { transition: none; }
112
+ }
113
+ </style>
@@ -0,0 +1,78 @@
1
+ ---
2
+ /**
3
+ * ProductRating — Read-only star rating with value and review count.
4
+ *
5
+ * @param {number} value - Average rating, 0–5. Default 4.6.
6
+ * @param {number} count - Number of reviews (optional).
7
+ * @param {string} size - Visual size (sm|md|lg). Default 'sm'.
8
+ * @param {boolean} showValue - Print the numeric value. Default true.
9
+ * @param {string} label - Accessible label template; `{value}` and `{count}` are replaced.
10
+ * @param {string} className - Extra class.
11
+ *
12
+ * Accessibility: exposed as one `role="img"` with a full text label; stars are decorative.
13
+ */
14
+ interface Props {
15
+ value?: number;
16
+ count?: number;
17
+ size?: 'sm' | 'md' | 'lg';
18
+ showValue?: boolean;
19
+ label?: string;
20
+ className?: string;
21
+ }
22
+
23
+ const {
24
+ value = 4.6,
25
+ count,
26
+ size = 'sm',
27
+ showValue = true,
28
+ label = 'Rated {value} out of 5{count}',
29
+ className = '',
30
+ } = Astro.props;
31
+
32
+ const clamped = Math.max(0, Math.min(5, value));
33
+ const pct = `${(clamped / 5) * 100}%`;
34
+ const aria = label
35
+ .replace('{value}', clamped.toFixed(1))
36
+ .replace('{count}', count !== undefined ? `, ${count} reviews` : '');
37
+ const stars = '★★★★★';
38
+ ---
39
+
40
+ <span class:list={['p-rating', `p-rating--${size}`, className]} role="img" aria-label={aria}>
41
+ <span class="p-rating__stars" aria-hidden="true" style={`--p-rating-fill: ${pct}`}>
42
+ <span class="p-rating__base">{stars}</span>
43
+ <span class="p-rating__fill">{stars}</span>
44
+ </span>
45
+ {showValue && <span class="p-rating__value" aria-hidden="true">{clamped.toFixed(1)}</span>}
46
+ {count !== undefined && <span class="p-rating__count" aria-hidden="true">({count})</span>}
47
+ </span>
48
+
49
+ <style>
50
+ .p-rating {
51
+ --p-rating-star: var(--c-primary-light, #d4a017);
52
+ display: inline-flex;
53
+ align-items: center;
54
+ gap: 0.35em;
55
+ font-size: 0.8rem;
56
+ line-height: 1;
57
+ color: var(--c-text-2, #a3a3a3);
58
+ }
59
+ .p-rating--md { font-size: 0.95rem; }
60
+ .p-rating--lg { font-size: 1.15rem; }
61
+
62
+ .p-rating__stars {
63
+ position: relative;
64
+ display: inline-block;
65
+ letter-spacing: 0.08em;
66
+ }
67
+ .p-rating__base { color: rgb(var(--ink-rgb, 255 255 255) / 0.14); }
68
+ .p-rating__fill {
69
+ position: absolute;
70
+ inset: 0 auto 0 0;
71
+ width: var(--p-rating-fill);
72
+ overflow: hidden;
73
+ white-space: nowrap;
74
+ color: var(--p-rating-star);
75
+ }
76
+ .p-rating__value { font-weight: 700; color: var(--c-text-1, #fff); }
77
+ .p-rating__count { color: var(--c-text-3, #737373); }
78
+ </style>