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,290 @@
1
+ ---
2
+ /**
3
+ * CartPageList — Full shopping-bag page: line items with quantity and remove, gift note,
4
+ * free-shipping progress and a summary card with the checkout button. Reads the cart store live.
5
+ *
6
+ * @param {string} cartKey - Cart storage key. Default 'ack-cart'.
7
+ * @param {string} title - Page heading. Default 'Your bag'.
8
+ * @param {string} checkoutHref - Checkout URL. Default '#'.
9
+ * @param {string} continueHref - "Continue shopping" URL. Default '#'.
10
+ * @param {number} freeShippingThreshold - Free shipping from this subtotal (0 hides it). Default 150.
11
+ * @param {number} shippingFlat - Shipping estimate below the threshold. Default 8.
12
+ * @param {string} currency - ISO currency. Default 'EUR'.
13
+ * @param {string} locale - Number format locale. Default 'en-GB'.
14
+ * @param {CartItem[]} demoItems - Seeds an empty cart once (demos only).
15
+ * @param {string} variant - Style (editorial|card). Default 'editorial'.
16
+ * @param {string} className - Extra class.
17
+ *
18
+ * Accessibility: one h1; the item list is a real list with labelled quantity buttons;
19
+ * totals are a polite live region; removing an item moves focus to the next row (or the heading).
20
+ */
21
+ import type { CartItem } from './types';
22
+
23
+ interface Props {
24
+ cartKey?: string;
25
+ title?: string;
26
+ checkoutHref?: string;
27
+ continueHref?: string;
28
+ freeShippingThreshold?: number;
29
+ shippingFlat?: number;
30
+ currency?: string;
31
+ locale?: string;
32
+ demoItems?: CartItem[];
33
+ variant?: 'editorial' | 'card';
34
+ className?: string;
35
+ }
36
+
37
+ const {
38
+ cartKey = 'ack-cart',
39
+ title = 'Your bag',
40
+ checkoutHref = '#',
41
+ continueHref = '#',
42
+ freeShippingThreshold = 150,
43
+ shippingFlat = 8,
44
+ currency = 'EUR',
45
+ locale = 'en-GB',
46
+ demoItems = [],
47
+ variant = 'editorial',
48
+ className = '',
49
+ } = Astro.props;
50
+ ---
51
+
52
+ <section
53
+ class:list={['bag', `bag--${variant}`, className]}
54
+ aria-labelledby="bag-title"
55
+ data-bag
56
+ data-key={cartKey}
57
+ data-currency={currency}
58
+ data-locale={locale}
59
+ data-free={freeShippingThreshold}
60
+ data-ship={shippingFlat}
61
+ data-seed={demoItems.length ? JSON.stringify(demoItems) : undefined}
62
+ >
63
+ <header class="bag__head">
64
+ <h1 class="bag__title" id="bag-title" tabindex="-1">{title} <sup data-count></sup></h1>
65
+ <a class="bag__continue" href={continueHref}>← Continue shopping</a>
66
+ </header>
67
+
68
+ <div class="bag__layout">
69
+ <div class="bag__main">
70
+ <div class="bag__cols" aria-hidden="true"><span>Item</span><span>Quantity</span><span>Total</span></div>
71
+ <ul class="bag__lines" data-lines aria-label="Items in your bag"></ul>
72
+ <div class="bag__empty" data-empty hidden>
73
+ <p class="bag__empty-title">Your bag is empty.</p>
74
+ <p>Pieces you add will wait here for you.</p>
75
+ <a class="bag__btn" href={continueHref}>Discover the collection</a>
76
+ </div>
77
+ <details class="bag__note" data-note-wrap>
78
+ <summary>Add a gift note</summary>
79
+ <label class="sr-only" for="bag-note">Gift note</label>
80
+ <textarea id="bag-note" rows="3" maxlength="200" placeholder="Write a short message — we'll print it on a card." data-note></textarea>
81
+ </details>
82
+ </div>
83
+
84
+ <aside class="bag__summary" aria-label="Order summary" data-summary>
85
+ <div class="bag__ship" data-ship-wrap>
86
+ <p data-ship-text></p>
87
+ <div class="bag__ship-bar" aria-hidden="true"><span data-ship-bar></span></div>
88
+ </div>
89
+ <dl class="bag__totals" aria-live="polite">
90
+ <div><dt>Subtotal</dt><dd data-t="subtotal">—</dd></div>
91
+ <div><dt>Shipping</dt><dd data-t="shipping">—</dd></div>
92
+ <div class="bag__total"><dt>Estimated total</dt><dd data-t="total">—</dd></div>
93
+ </dl>
94
+ <p class="bag__taxes">Taxes included. Discount codes are applied at checkout.</p>
95
+ <a class="bag__btn bag__btn--block" href={checkoutHref} data-checkout>Checkout</a>
96
+ <ul class="bag__assurance">
97
+ <li>Free returns within 30 days</li>
98
+ <li>Secure payment · Cards, PayPal, Apple Pay</li>
99
+ </ul>
100
+ </aside>
101
+ </div>
102
+ </section>
103
+
104
+ <script>
105
+ import { createCart, formatMoney } from './cart-store';
106
+ import { productIcon } from './product-icons';
107
+ import type { CartItem } from './types';
108
+
109
+ document.querySelectorAll<HTMLElement>('[data-bag]:not([data-bound])').forEach((root) => {
110
+ root.dataset.bound = '';
111
+ const cart = createCart(root.dataset.key);
112
+ const money = { currency: root.dataset.currency, locale: root.dataset.locale };
113
+ const free = Number(root.dataset.free) || 0;
114
+ const flat = Number(root.dataset.ship) || 0;
115
+ const list = root.querySelector<HTMLUListElement>('[data-lines]')!;
116
+ const empty = root.querySelector<HTMLElement>('[data-empty]')!;
117
+ const summary = root.querySelector<HTMLElement>('[data-summary]')!;
118
+ const cols = root.querySelector<HTMLElement>('.bag__cols')!;
119
+ const noteWrap = root.querySelector<HTMLElement>('[data-note-wrap]')!;
120
+ const note = root.querySelector<HTMLTextAreaElement>('[data-note]')!;
121
+ const heading = root.querySelector<HTMLElement>('#bag-title')!;
122
+ const out = (k: string) => root.querySelector<HTMLElement>(`[data-t="${k}"]`)!;
123
+ const noteKey = `${cart.key}:note`;
124
+
125
+ if (root.dataset.seed) { try { cart.seed(JSON.parse(root.dataset.seed)); } catch { /* ignore */ } }
126
+ try { note.value = window.localStorage.getItem(noteKey) || ''; } catch { /* ignore */ }
127
+ note.addEventListener('input', () => { try { window.localStorage.setItem(noteKey, note.value); } catch { /* ignore */ } });
128
+
129
+ const make = <K extends keyof HTMLElementTagNameMap>(tag: K, cls: string, text?: string) => {
130
+ const n = document.createElement(tag);
131
+ n.className = cls;
132
+ if (text !== undefined) n.textContent = text;
133
+ return n;
134
+ };
135
+
136
+ function row(item: CartItem, i: number, all: CartItem[]) {
137
+ const li = make('li', 'bag-line');
138
+ const media = make('div', 'bag-line__media');
139
+ media.style.setProperty('--h', String(item.hue ?? 30));
140
+ media.innerHTML = item.image
141
+ ? `<img src="${encodeURI(item.image)}" alt="">`
142
+ : `<svg viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${productIcon(item.icon)}</svg>`;
143
+ const info = make('div', 'bag-line__info');
144
+ info.append(make('p', 'bag-line__name', item.name));
145
+ if (item.variant) info.append(make('p', 'bag-line__meta', item.variant));
146
+ info.append(make('p', 'bag-line__unit', formatMoney(item.price, money)));
147
+ const remove = make('button', 'bag-line__remove', 'Remove');
148
+ remove.type = 'button';
149
+ remove.setAttribute('aria-label', `Remove ${item.name}`);
150
+ remove.addEventListener('click', () => {
151
+ const next = all[i + 1] ?? all[i - 1];
152
+ cart.remove(item.id);
153
+ const target = next ? root.querySelector<HTMLElement>(`[aria-label="Remove ${CSS.escape(next.name)}"]`) : null;
154
+ (target ?? heading).focus();
155
+ });
156
+ info.append(remove);
157
+
158
+ const qty = make('div', 'bag-line__qty');
159
+ const minus = make('button', 'bag-line__step', '−');
160
+ minus.type = 'button';
161
+ minus.setAttribute('aria-label', `Decrease quantity of ${item.name}`);
162
+ minus.addEventListener('click', () => cart.setQty(item.id, item.qty - 1));
163
+ const count = make('span', 'bag-line__count', String(item.qty));
164
+ const plus = make('button', 'bag-line__step', '+');
165
+ plus.type = 'button';
166
+ plus.setAttribute('aria-label', `Increase quantity of ${item.name}`);
167
+ plus.addEventListener('click', () => cart.setQty(item.id, item.qty + 1));
168
+ qty.append(minus, count, plus);
169
+
170
+ li.append(media, info, qty, make('p', 'bag-line__total', formatMoney(item.price * item.qty, money)));
171
+ return li;
172
+ }
173
+
174
+ function render() {
175
+ const items = cart.items();
176
+ const focused = document.activeElement?.getAttribute('aria-label');
177
+ list.replaceChildren(...items.map((it, i) => row(it, i, items)));
178
+ const has = items.length > 0;
179
+ empty.hidden = has;
180
+ summary.hidden = !has;
181
+ cols.hidden = !has;
182
+ noteWrap.hidden = !has;
183
+ const count = items.reduce((n, i) => n + i.qty, 0);
184
+ root.querySelector('[data-count]')!.textContent = has ? `(${count})` : '';
185
+ const subtotal = items.reduce((n, i) => n + i.qty * i.price, 0);
186
+ const shipping = !has || (free > 0 && subtotal >= free) ? 0 : flat;
187
+ out('subtotal').textContent = formatMoney(subtotal, money);
188
+ out('shipping').textContent = shipping === 0 ? 'Free' : formatMoney(shipping, money);
189
+ out('total').textContent = formatMoney(subtotal + shipping, money);
190
+ const shipWrap = root.querySelector<HTMLElement>('[data-ship-wrap]')!;
191
+ shipWrap.hidden = free <= 0;
192
+ const left = Math.max(0, free - subtotal);
193
+ root.querySelector('[data-ship-text]')!.textContent = left > 0 ? `${formatMoney(left, money)} away from free shipping` : 'Free shipping unlocked';
194
+ root.querySelector<HTMLElement>('[data-ship-bar]')!.style.width = `${free ? Math.min(100, (subtotal / free) * 100) : 100}%`;
195
+ if (focused && !root.contains(document.activeElement)) {
196
+ root.querySelector<HTMLElement>(`[aria-label="${CSS.escape(focused)}"]`)?.focus();
197
+ }
198
+ }
199
+
200
+ render();
201
+ cart.subscribe(render);
202
+ });
203
+ </script>
204
+
205
+ <style>
206
+ .bag { container-type: inline-size; min-width: 0; }
207
+ .bag__head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-4, 1rem); padding-bottom: var(--sp-5, 1.25rem); border-bottom: 1px solid var(--c-text-1, #fff); }
208
+ .bag__title { margin: 0; font-family: var(--font-display, Georgia, 'Times New Roman', serif); font-size: clamp(2.2rem, 5vw, 3.6rem); font-weight: 400; letter-spacing: -0.03em; line-height: 1; color: var(--c-text-1, #fff); outline: none; }
209
+ .bag__title sup { font-family: var(--font-sans, system-ui); font-size: 0.9rem; letter-spacing: 0; vertical-align: super; color: var(--c-text-2, #a3a3a3); }
210
+ .bag__continue { min-height: 44px; display: inline-flex; align-items: center; font-size: 0.88rem; color: var(--c-text-1, #fff); text-decoration: underline; text-underline-offset: 4px; }
211
+
212
+ .bag__layout { display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: clamp(2rem, 5vw, 4.5rem); align-items: start; padding-top: var(--sp-6, 1.5rem); }
213
+ .bag__cols { display: grid; grid-template-columns: 1fr 140px 110px; padding-bottom: var(--sp-3, 0.75rem); border-bottom: 1px solid var(--c-border, rgba(255, 255, 255, 0.1)); font-size: 0.68rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--c-text-2, #a3a3a3); }
214
+ .bag__cols span:nth-child(3) { text-align: right; }
215
+ .bag__cols[hidden] { display: none; }
216
+
217
+ .bag__lines { margin: 0; padding: 0; list-style: none; }
218
+ .bag__lines :global(.bag-line) {
219
+ display: grid;
220
+ grid-template-columns: 96px 1fr 140px 110px;
221
+ gap: var(--sp-4, 1rem);
222
+ align-items: start;
223
+ padding: var(--sp-5, 1.25rem) 0;
224
+ border-bottom: 1px solid var(--c-border, rgba(255, 255, 255, 0.1));
225
+ }
226
+ .bag__lines :global(.bag-line__media) {
227
+ display: grid;
228
+ place-items: center;
229
+ aspect-ratio: 3 / 4;
230
+ color: hsl(var(--h) var(--pm-ink-s, 80%) var(--pm-ink-l, 80%));
231
+ background: linear-gradient(160deg, hsl(var(--h) 40% 50% / 0.18), var(--c-bg-2, #111));
232
+ border-radius: var(--r-sm, 2px);
233
+ }
234
+ .bag__lines :global(.bag-line__media svg) { width: 58%; }
235
+ .bag__lines :global(.bag-line__media img) { width: 100%; height: 100%; object-fit: cover; }
236
+ .bag__lines :global(.bag-line__info) { display: grid; gap: 2px; min-width: 0; }
237
+ .bag__lines :global(.bag-line__name) { margin: 0; font-family: var(--font-display, Georgia, serif); font-size: 1.1rem; color: var(--c-text-1, #fff); }
238
+ .bag__lines :global(.bag-line__meta), .bag__lines :global(.bag-line__unit) { margin: 0; font-size: 0.82rem; color: var(--c-text-2, #a3a3a3); }
239
+ .bag__lines :global(.bag-line__remove) { justify-self: start; min-height: 36px; margin-top: 6px; padding: 0; border: 0; background: none; color: var(--c-text-1, #fff); font: inherit; font-size: 0.8rem; text-decoration: underline; text-underline-offset: 3px; cursor: pointer; }
240
+ .bag__lines :global(.bag-line__qty) { display: inline-flex; align-items: center; width: fit-content; border: 1px solid var(--c-border, rgba(255, 255, 255, 0.15)); border-radius: var(--r-sm, 2px); }
241
+ .bag__lines :global(.bag-line__step) { width: 40px; height: 40px; border: 0; background: none; color: var(--c-text-1, #fff); font-size: 1rem; cursor: pointer; }
242
+ .bag__lines :global(.bag-line__step:hover) { background: rgb(var(--ink-rgb, 255 255 255) / 0.06); }
243
+ .bag__lines :global(.bag-line__count) { min-width: 28px; text-align: center; font-variant-numeric: tabular-nums; color: var(--c-text-1, #fff); }
244
+ .bag__lines :global(.bag-line__total) { margin: 0; text-align: right; font-variant-numeric: tabular-nums; color: var(--c-text-1, #fff); font-weight: 600; }
245
+ .bag :global(button:focus-visible), .bag a:focus-visible, .bag summary:focus-visible, .bag textarea:focus-visible { outline: 2px solid var(--shop-accent, var(--c-primary-light, #d4a017)); outline-offset: 2px; }
246
+
247
+ .bag__empty { display: grid; justify-items: start; gap: var(--sp-2, 0.5rem); padding: var(--sp-10, 2.5rem) 0; }
248
+ .bag__empty[hidden] { display: none; }
249
+ .bag__empty p { margin: 0; color: var(--c-text-2, #a3a3a3); }
250
+ .bag__empty-title { font-family: var(--font-display, Georgia, serif); font-size: 1.6rem; color: var(--c-text-1, #fff) !important; }
251
+
252
+ .bag__note { margin-top: var(--sp-5, 1.25rem); }
253
+ .bag__note[hidden] { display: none; }
254
+ .bag__note summary { min-height: 44px; display: flex; align-items: center; font-size: 0.88rem; color: var(--c-text-1, #fff); cursor: pointer; }
255
+ .bag__note textarea { width: 100%; padding: var(--sp-3, 0.75rem); border: 1px solid var(--c-border, rgba(255, 255, 255, 0.15)); border-radius: var(--r-sm, 2px); background: transparent; color: var(--c-text-1, #fff); font: inherit; font-size: 0.9rem; resize: vertical; }
256
+
257
+ .bag__summary { position: sticky; top: calc(var(--shop-header-h, 0px) + 1.5rem); display: grid; gap: var(--sp-4, 1rem); padding: var(--sp-6, 1.5rem); background: var(--c-bg-1, #0a0a0a); border: 1px solid var(--c-border, rgba(255, 255, 255, 0.1)); }
258
+ .bag--card .bag__summary { border-radius: var(--r-lg, 20px); }
259
+ .bag__summary[hidden] { display: none; }
260
+ .bag__ship p { margin: 0 0 var(--sp-2, 0.5rem); font-size: 0.85rem; color: var(--c-text-1, #fff); }
261
+ .bag__ship[hidden] { display: none; }
262
+ .bag__ship-bar { height: 3px; background: rgb(var(--ink-rgb, 255 255 255) / 0.12); }
263
+ .bag__ship-bar span { display: block; height: 100%; width: 0; background: var(--shop-accent, var(--c-primary-light, #d4a017)); transition: width 0.5s cubic-bezier(0.16, 1, 0.3, 1); }
264
+ .bag__totals { display: grid; gap: var(--sp-2, 0.5rem); margin: 0; }
265
+ .bag__totals div { display: flex; justify-content: space-between; font-size: 0.9rem; }
266
+ .bag__totals dt { color: var(--c-text-2, #a3a3a3); }
267
+ .bag__totals dd { margin: 0; color: var(--c-text-1, #fff); font-variant-numeric: tabular-nums; }
268
+ .bag__total { padding-top: var(--sp-3, 0.75rem); border-top: 1px solid var(--c-border, rgba(255, 255, 255, 0.1)); }
269
+ .bag__totals .bag__total dt { color: var(--c-text-1, #fff); font-weight: 600; }
270
+ .bag__totals .bag__total dd { font-size: 1.25rem; font-weight: 700; }
271
+ .bag__taxes { margin: 0; font-size: 0.78rem; color: var(--c-text-2, #a3a3a3); }
272
+ .bag__btn { display: inline-flex; align-items: center; justify-content: center; min-height: 52px; padding: 0 1.5rem; border-radius: var(--r-sm, 2px); background: var(--c-text-1, #fff); color: var(--c-bg, #050505); font-weight: 700; font-size: 0.9rem; letter-spacing: 0.04em; text-decoration: none; transition: background 0.2s ease, color 0.2s ease; }
273
+ .bag__btn:hover { background: var(--shop-accent, var(--c-primary-light, #d4a017)); color: var(--c-on-primary, #050505); }
274
+ .bag__btn--block { width: 100%; }
275
+ .bag__assurance { display: grid; gap: 4px; margin: 0; padding: 0; list-style: none; font-size: 0.78rem; color: var(--c-text-2, #a3a3a3); }
276
+ .bag__assurance li::before { content: '— '; }
277
+
278
+ .sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
279
+
280
+ @container (max-width: 900px) {
281
+ .bag__layout { grid-template-columns: 1fr; }
282
+ .bag__summary { position: static; }
283
+ }
284
+ @container (max-width: 600px) {
285
+ .bag__cols { display: none; }
286
+ .bag__lines :global(.bag-line) { grid-template-columns: 84px 1fr; }
287
+ .bag__lines :global(.bag-line__qty) { grid-column: 2; }
288
+ .bag__lines :global(.bag-line__total) { grid-column: 2; text-align: left; }
289
+ }
290
+ </style>
@@ -0,0 +1,276 @@
1
+ ---
2
+ /**
3
+ * CartToast — Stacked live notifications for the shop: "Added to cart" (with a View cart
4
+ * action) whenever an add-to-cart button skips the drawer, plus any custom notice sent with
5
+ * `document.dispatchEvent(new CustomEvent('ack:notify', { detail: { title, text, tone } }))`.
6
+ *
7
+ * @param {string} cartKey - Only react to this cart. Default 'ack-cart'.
8
+ * @param {string} position - Screen corner (bottom-right|bottom-left|top-right|top-center). Default 'bottom-right'.
9
+ * @param {number} duration - Auto-dismiss delay in ms (paused on hover/focus). Default 4500.
10
+ * @param {number} max - Maximum toasts visible at once. Default 3.
11
+ * @param {string} addedTitle - Title for cart notices. Default 'Added to cart'.
12
+ * @param {string} actionLabel - Cart action label. Default 'View cart'.
13
+ * @param {boolean} always - Also toast when the drawer opens on add. Default false.
14
+ * @param {boolean} demo - Render one static example toast (docs/previews). Default false.
15
+ * @param {string} variant - Style (glass|solid). Default 'glass'.
16
+ * @param {string} className - Extra class.
17
+ *
18
+ * Accessibility: polite live region (role="status"), every toast has a close button,
19
+ * timers pause while hovered or focused, motion is removed with prefers-reduced-motion.
20
+ */
21
+ interface Props {
22
+ cartKey?: string;
23
+ position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-center';
24
+ duration?: number;
25
+ max?: number;
26
+ addedTitle?: string;
27
+ actionLabel?: string;
28
+ always?: boolean;
29
+ demo?: boolean;
30
+ variant?: 'glass' | 'solid';
31
+ className?: string;
32
+ }
33
+
34
+ const {
35
+ cartKey = 'ack-cart',
36
+ position = 'bottom-right',
37
+ duration = 4500,
38
+ max = 3,
39
+ addedTitle = 'Added to cart',
40
+ actionLabel = 'View cart',
41
+ always = false,
42
+ demo = false,
43
+ variant = 'glass',
44
+ className = '',
45
+ } = Astro.props;
46
+ ---
47
+
48
+ <div
49
+ class:list={['cart-toasts', `cart-toasts--${position}`, `cart-toasts--${variant}`, { 'cart-toasts--demo': demo }, className]}
50
+ role="status"
51
+ aria-live="polite"
52
+ data-cart-toasts
53
+ data-key={cartKey}
54
+ data-duration={duration}
55
+ data-max={max}
56
+ data-title={addedTitle}
57
+ data-action={actionLabel}
58
+ data-always={always ? '' : undefined}
59
+ >
60
+ {demo && (
61
+ <div class="cart-toast cart-toast--success">
62
+ <span class="cart-toast__icon" aria-hidden="true"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg></span>
63
+ <div class="cart-toast__body"><p class="cart-toast__title">{addedTitle}</p><p class="cart-toast__text">Aurora Wireless Headphones · Graphite</p></div>
64
+ <button class="cart-toast__action" type="button">{actionLabel}</button>
65
+ <button class="cart-toast__close" type="button" aria-label="Dismiss">×</button>
66
+ <span class="cart-toast__timer" aria-hidden="true"></span>
67
+ </div>
68
+ <div class="cart-toast cart-toast--warning">
69
+ <span class="cart-toast__icon" aria-hidden="true"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M12 9v4M12 17h.01"/><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/></svg></span>
70
+ <div class="cart-toast__body"><p class="cart-toast__title">Only 2 left in stock</p><p class="cart-toast__text">Drift Everyday Sneakers · size 42</p></div>
71
+ <button class="cart-toast__close" type="button" aria-label="Dismiss">×</button>
72
+ </div>
73
+ )}
74
+ </div>
75
+
76
+ <script>
77
+ import { ADDED_EVENT, NOTIFY_EVENT } from './cart-store';
78
+
79
+ const ICONS: Record<string, string> = {
80
+ success: '<path d="M20 6 9 17l-5-5"/>',
81
+ info: '<circle cx="12" cy="12" r="9"/><path d="M12 16v-4M12 8h.01"/>',
82
+ warning: '<path d="M12 9v4M12 17h.01"/><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0z"/>',
83
+ danger: '<circle cx="12" cy="12" r="9"/><path d="m15 9-6 6M9 9l6 6"/>',
84
+ };
85
+
86
+ document.querySelectorAll<HTMLElement>('[data-cart-toasts]:not([data-bound])').forEach((region) => {
87
+ region.dataset.bound = '';
88
+ if (region.classList.contains('cart-toasts--demo')) return;
89
+ const duration = Number(region.dataset.duration) || 4500;
90
+ const max = Number(region.dataset.max) || 3;
91
+ const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
92
+
93
+ function show(opts: { title: string; text?: string; tone?: string; action?: { label: string; run: () => void } }) {
94
+ const tone = ICONS[opts.tone || ''] ? opts.tone! : 'info';
95
+ const toast = document.createElement('div');
96
+ toast.className = `cart-toast cart-toast--${tone}`;
97
+ toast.style.setProperty('--toast-ms', `${duration}ms`);
98
+ toast.innerHTML = `<span class="cart-toast__icon" aria-hidden="true"><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round">${ICONS[tone]}</svg></span>`;
99
+ const body = document.createElement('div');
100
+ body.className = 'cart-toast__body';
101
+ const title = document.createElement('p');
102
+ title.className = 'cart-toast__title';
103
+ title.textContent = opts.title;
104
+ body.append(title);
105
+ if (opts.text) {
106
+ const text = document.createElement('p');
107
+ text.className = 'cart-toast__text';
108
+ text.textContent = opts.text;
109
+ body.append(text);
110
+ }
111
+ toast.append(body);
112
+ if (opts.action) {
113
+ const btn = document.createElement('button');
114
+ btn.type = 'button';
115
+ btn.className = 'cart-toast__action';
116
+ btn.textContent = opts.action.label;
117
+ btn.addEventListener('click', () => { opts.action!.run(); dismiss(); });
118
+ toast.append(btn);
119
+ }
120
+ const close = document.createElement('button');
121
+ close.type = 'button';
122
+ close.className = 'cart-toast__close';
123
+ close.setAttribute('aria-label', 'Dismiss notification');
124
+ close.textContent = '×';
125
+ close.addEventListener('click', () => dismiss());
126
+ const bar = document.createElement('span');
127
+ bar.className = 'cart-toast__timer';
128
+ bar.setAttribute('aria-hidden', 'true');
129
+ toast.append(close, bar);
130
+
131
+ let left = duration;
132
+ let started = Date.now();
133
+ let timer = window.setTimeout(() => dismiss(), left);
134
+ const pause = () => { window.clearTimeout(timer); left -= Date.now() - started; toast.classList.add('is-paused'); };
135
+ const resume = () => { started = Date.now(); timer = window.setTimeout(() => dismiss(), Math.max(800, left)); toast.classList.remove('is-paused'); };
136
+ toast.addEventListener('mouseenter', pause);
137
+ toast.addEventListener('mouseleave', resume);
138
+ toast.addEventListener('focusin', pause);
139
+ toast.addEventListener('focusout', resume);
140
+
141
+ function dismiss() {
142
+ window.clearTimeout(timer);
143
+ if (!toast.isConnected) return;
144
+ toast.classList.add('is-leaving');
145
+ window.setTimeout(() => toast.remove(), reduced ? 0 : 220);
146
+ }
147
+
148
+ region.append(toast);
149
+ while (region.children.length > max) region.firstElementChild?.remove();
150
+ }
151
+
152
+ document.addEventListener(ADDED_EVENT, (e) => {
153
+ const d = (e as CustomEvent).detail || {};
154
+ if (d.key && d.key !== region.dataset.key) return;
155
+ if (d.openCart && !region.hasAttribute('data-always')) return;
156
+ const item = d.item || {};
157
+ show({
158
+ title: region.dataset.title || 'Added to cart',
159
+ text: [item.name, item.variant].filter(Boolean).join(' · ') + (item.qty > 1 ? ` × ${item.qty}` : ''),
160
+ tone: 'success',
161
+ action: {
162
+ label: region.dataset.action || 'View cart',
163
+ run: () => document.dispatchEvent(new CustomEvent('ack:cart-open', { detail: { key: region.dataset.key } })),
164
+ },
165
+ });
166
+ });
167
+
168
+ document.addEventListener(NOTIFY_EVENT, (e) => {
169
+ const d = (e as CustomEvent).detail || {};
170
+ if (d.title) show({ title: d.title, text: d.text, tone: d.tone });
171
+ });
172
+ });
173
+ </script>
174
+
175
+ <style>
176
+ .cart-toasts {
177
+ position: fixed;
178
+ z-index: 1100;
179
+ display: flex;
180
+ flex-direction: column;
181
+ gap: var(--sp-2, 0.5rem);
182
+ width: min(380px, calc(100vw - 2rem));
183
+ pointer-events: none;
184
+ }
185
+ .cart-toasts--bottom-right { right: 1rem; bottom: 1rem; }
186
+ .cart-toasts--bottom-left { left: 1rem; bottom: 1rem; }
187
+ .cart-toasts--top-right { right: 1rem; top: 1rem; flex-direction: column-reverse; }
188
+ .cart-toasts--top-center { left: 50%; top: 1rem; transform: translateX(-50%); flex-direction: column-reverse; }
189
+ .cart-toasts--demo { position: relative; inset: auto; transform: none; margin: 0 auto; }
190
+
191
+ .cart-toasts :global(.cart-toast) {
192
+ --tone: var(--c-primary-light, #d4a017);
193
+ position: relative;
194
+ display: grid;
195
+ grid-template-columns: auto 1fr auto auto;
196
+ align-items: center;
197
+ gap: var(--sp-3, 0.75rem);
198
+ padding: var(--sp-3, 0.75rem) var(--sp-3, 0.75rem) var(--sp-3, 0.75rem) var(--sp-4, 1rem);
199
+ border-radius: var(--r-md, 12px);
200
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.1);
201
+ background: rgb(var(--paper-rgb, 5 5 5) / 0.86);
202
+ backdrop-filter: blur(16px) saturate(1.4);
203
+ box-shadow: 0 20px 50px -20px rgba(0, 0, 0, 0.6);
204
+ color: var(--c-text-1, #fff);
205
+ overflow: hidden;
206
+ pointer-events: auto;
207
+ animation: ct-in 0.32s cubic-bezier(0.16, 1, 0.3, 1);
208
+ }
209
+ .cart-toasts--solid :global(.cart-toast) { background: var(--c-bg-1, #0a0a0a); backdrop-filter: none; }
210
+ .cart-toasts :global(.cart-toast--success) { --tone: var(--c-success, #34d399); }
211
+ .cart-toasts :global(.cart-toast--warning) { --tone: var(--c-warning, #fbbf24); }
212
+ .cart-toasts :global(.cart-toast--danger) { --tone: var(--c-danger, #f87171); }
213
+ .cart-toasts :global(.cart-toast--info) { --tone: var(--c-primary-light, #d4a017); }
214
+ .cart-toasts :global(.cart-toast.is-leaving) { animation: ct-out 0.22s ease forwards; }
215
+
216
+ .cart-toasts :global(.cart-toast__icon) {
217
+ display: grid;
218
+ place-items: center;
219
+ width: 32px;
220
+ height: 32px;
221
+ border-radius: 50%;
222
+ color: var(--tone);
223
+ background: color-mix(in srgb, var(--tone) 15%, transparent);
224
+ }
225
+ .cart-toasts :global(.cart-toast__body) { min-width: 0; }
226
+ .cart-toasts :global(.cart-toast__title) { margin: 0; font-size: 0.88rem; font-weight: 700; }
227
+ .cart-toasts :global(.cart-toast__text) { margin: 2px 0 0; font-size: 0.8rem; color: var(--c-text-2, #a3a3a3); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
228
+ .cart-toasts :global(.cart-toast__action) {
229
+ min-height: 36px;
230
+ padding: 0 var(--sp-3, 0.75rem);
231
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.16);
232
+ border-radius: var(--r-full, 9999px);
233
+ background: transparent;
234
+ color: var(--c-text-1, #fff);
235
+ font: inherit;
236
+ font-size: 0.8rem;
237
+ font-weight: 700;
238
+ white-space: nowrap;
239
+ cursor: pointer;
240
+ }
241
+ .cart-toasts :global(.cart-toast__action:hover) { background: rgb(var(--ink-rgb, 255 255 255) / 0.07); }
242
+ .cart-toasts :global(.cart-toast__close) {
243
+ width: 32px;
244
+ height: 32px;
245
+ border: 0;
246
+ border-radius: 50%;
247
+ background: transparent;
248
+ color: var(--c-text-2, #a3a3a3);
249
+ font-size: 1.3rem;
250
+ line-height: 1;
251
+ cursor: pointer;
252
+ }
253
+ .cart-toasts :global(.cart-toast__close:hover) { color: var(--c-text-1, #fff); background: rgb(var(--ink-rgb, 255 255 255) / 0.06); }
254
+ .cart-toasts :global(.cart-toast button:focus-visible) { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
255
+ .cart-toasts :global(.cart-toast__timer) {
256
+ position: absolute;
257
+ left: 0;
258
+ bottom: 0;
259
+ height: 2px;
260
+ width: 100%;
261
+ background: var(--tone);
262
+ transform-origin: left;
263
+ animation: ct-timer var(--toast-ms, 4500ms) linear forwards;
264
+ }
265
+ .cart-toasts :global(.cart-toast.is-paused .cart-toast__timer) { animation-play-state: paused; }
266
+ .cart-toasts--demo :global(.cart-toast__timer) { animation: none; transform: scaleX(0.6); }
267
+
268
+ @keyframes ct-in { from { opacity: 0; transform: translateY(12px) scale(0.97); } }
269
+ @keyframes ct-out { to { opacity: 0; transform: translateX(24px); } }
270
+ @keyframes ct-timer { to { transform: scaleX(0); } }
271
+
272
+ @media (prefers-reduced-motion: reduce) {
273
+ .cart-toasts :global(.cart-toast), .cart-toasts :global(.cart-toast.is-leaving) { animation: none; }
274
+ .cart-toasts :global(.cart-toast__timer) { display: none; }
275
+ }
276
+ </style>