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,483 @@
1
+ ---
2
+ /**
3
+ * CartDrawer — Slide-in cart with line items, quantity steppers, remove, free-shipping
4
+ * progress, subtotal and checkout button. Reads and writes the shared cart store.
5
+ *
6
+ * Opens on the `ack:cart-open` event (dispatched by StoreHeader's cart button and by
7
+ * add-to-cart buttons) or on any `[data-cart-open]` element outside a StoreHeader.
8
+ *
9
+ * @param {string} cartKey - Cart storage key. Default 'ack-cart'.
10
+ * @param {string} title - Drawer heading. Default 'Your cart'.
11
+ * @param {string} checkoutHref - Checkout page URL. Default '#'.
12
+ * @param {string} checkoutLabel - Checkout button label. Default 'Checkout'.
13
+ * @param {string} currency - ISO currency. Default 'USD'.
14
+ * @param {string} locale - Number format locale. Default 'en-US'.
15
+ * @param {number} freeShippingThreshold - Shows progress towards free shipping (0 hides it). Default 75.
16
+ * @param {CartItem[]} demoItems - Seeds an empty cart once (demo stores only).
17
+ * @param {boolean} inline - Render as an always-visible panel instead of an overlay (docs/previews). Default false.
18
+ * @param {string} side - Slide-in side (right|left). Default 'right'.
19
+ * @param {string} size - Panel width (sm|md|lg). Default 'md'.
20
+ * @param {string} id - Base id for inner elements (unique per page). Default 'cart-drawer'.
21
+ * @param {string} className - Extra class.
22
+ *
23
+ * Accessibility: modal dialog with focus trap, Escape to close, focus returned to the trigger,
24
+ * background scroll locked; totals are announced through a polite live region.
25
+ */
26
+ import type { CartItem } from './types';
27
+
28
+ interface Props {
29
+ cartKey?: string;
30
+ title?: string;
31
+ checkoutHref?: string;
32
+ checkoutLabel?: string;
33
+ currency?: string;
34
+ locale?: string;
35
+ freeShippingThreshold?: number;
36
+ demoItems?: CartItem[];
37
+ inline?: boolean;
38
+ side?: 'right' | 'left';
39
+ size?: 'sm' | 'md' | 'lg';
40
+ id?: string;
41
+ className?: string;
42
+ }
43
+
44
+ const {
45
+ cartKey = 'ack-cart',
46
+ title = 'Your cart',
47
+ checkoutHref = '#',
48
+ checkoutLabel = 'Checkout',
49
+ currency = 'USD',
50
+ locale = 'en-US',
51
+ freeShippingThreshold = 75,
52
+ demoItems = [],
53
+ inline = false,
54
+ side = 'right',
55
+ size = 'md',
56
+ id = 'cart-drawer',
57
+ className = '',
58
+ } = Astro.props;
59
+
60
+ const uid = id;
61
+ ---
62
+
63
+ <div
64
+ class:list={['cart-drawer', `cart-drawer--${side}`, `cart-drawer--${size}`, { 'cart-drawer--inline': inline }, className]}
65
+ data-cart-drawer
66
+ data-key={cartKey}
67
+ data-currency={currency}
68
+ data-locale={locale}
69
+ data-free={freeShippingThreshold}
70
+ data-seed={demoItems.length ? JSON.stringify(demoItems) : undefined}
71
+ data-inline={inline ? '' : undefined}
72
+ hidden={!inline}
73
+ >
74
+ {!inline && <div class="cart-drawer__backdrop" data-cart-close></div>}
75
+ <aside
76
+ class="cart-drawer__panel"
77
+ role={inline ? 'region' : 'dialog'}
78
+ aria-modal={inline ? undefined : 'true'}
79
+ aria-labelledby={`${uid}-title`}
80
+ tabindex="-1"
81
+ >
82
+ <header class="cart-drawer__head">
83
+ <h2 class="cart-drawer__title" id={`${uid}-title`}>{title} <span class="cart-drawer__qty" data-cart-qty></span></h2>
84
+ {!inline && (
85
+ <button class="cart-drawer__close" type="button" aria-label="Close cart" data-cart-close>
86
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"/></svg>
87
+ </button>
88
+ )}
89
+ </header>
90
+
91
+ {freeShippingThreshold > 0 && (
92
+ <div class="cart-drawer__ship" data-ship>
93
+ <p class="cart-drawer__ship-text" data-ship-text></p>
94
+ <div class="cart-drawer__ship-bar" aria-hidden="true"><span data-ship-bar></span></div>
95
+ </div>
96
+ )}
97
+
98
+ <ul class="cart-drawer__lines" data-lines aria-label="Cart items"></ul>
99
+
100
+ <div class="cart-drawer__empty" data-empty hidden>
101
+ <svg width="44" height="44" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 7h12l-1 13H7z"/><path d="M9 7V6a3 3 0 0 1 6 0v1"/></svg>
102
+ <p><strong>Your cart is empty</strong></p>
103
+ <p>Add a few products and they will show up here.</p>
104
+ {!inline && <button class="cart-drawer__ghost" type="button" data-cart-close>Continue shopping</button>}
105
+ </div>
106
+
107
+ <footer class="cart-drawer__foot" data-foot>
108
+ <div class="cart-drawer__row">
109
+ <span>Subtotal</span>
110
+ <strong data-subtotal aria-live="polite">—</strong>
111
+ </div>
112
+ <p class="cart-drawer__note">Taxes and shipping are calculated at checkout.</p>
113
+ <a class="cart-drawer__checkout" href={checkoutHref}>
114
+ {checkoutLabel}
115
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14M13 6l6 6-6 6"/></svg>
116
+ </a>
117
+ </footer>
118
+ </aside>
119
+ </div>
120
+
121
+ <script>
122
+ import { createCart, formatMoney } from './cart-store';
123
+ import { productIcon } from './product-icons';
124
+ import type { CartItem } from './types';
125
+
126
+ const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex="-1"])';
127
+
128
+ document.querySelectorAll<HTMLElement>('[data-cart-drawer]:not([data-bound])').forEach((root) => {
129
+ root.dataset.bound = '';
130
+ const cart = createCart(root.dataset.key);
131
+ const money = { currency: root.dataset.currency, locale: root.dataset.locale };
132
+ const inline = root.hasAttribute('data-inline');
133
+ const free = Number(root.dataset.free) || 0;
134
+ const panel = root.querySelector<HTMLElement>('.cart-drawer__panel')!;
135
+ const list = root.querySelector<HTMLUListElement>('[data-lines]')!;
136
+ const empty = root.querySelector<HTMLElement>('[data-empty]')!;
137
+ const foot = root.querySelector<HTMLElement>('[data-foot]')!;
138
+ const subtotalEl = root.querySelector<HTMLElement>('[data-subtotal]')!;
139
+ const qtyEl = root.querySelector<HTMLElement>('[data-cart-qty]')!;
140
+ const ship = root.querySelector<HTMLElement>('[data-ship]');
141
+ const shipText = root.querySelector<HTMLElement>('[data-ship-text]');
142
+ const shipBar = root.querySelector<HTMLElement>('[data-ship-bar]');
143
+ let lastTrigger: HTMLElement | null = null;
144
+
145
+ if (root.dataset.seed) {
146
+ try { cart.seed(JSON.parse(root.dataset.seed)); } catch { /* bad seed */ }
147
+ }
148
+
149
+ const el = <K extends keyof HTMLElementTagNameMap>(tag: K, cls?: string, text?: string) => {
150
+ const node = document.createElement(tag);
151
+ if (cls) node.className = cls;
152
+ if (text !== undefined) node.textContent = text;
153
+ return node;
154
+ };
155
+
156
+ function line(item: CartItem): HTMLLIElement {
157
+ const li = el('li', 'cart-line');
158
+ const media = el('div', 'cart-line__media');
159
+ media.style.setProperty('--h', String(item.hue ?? 40));
160
+ if (item.image) {
161
+ const img = el('img');
162
+ img.src = item.image;
163
+ img.alt = '';
164
+ media.append(img);
165
+ } else {
166
+ media.innerHTML = `<svg viewBox="0 0 64 64" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${productIcon(item.icon)}</svg>`;
167
+ }
168
+ const info = el('div', 'cart-line__info');
169
+ info.append(el('p', 'cart-line__name', item.name));
170
+ if (item.variant) info.append(el('p', 'cart-line__variant', item.variant));
171
+
172
+ const stepper = el('div', 'cart-line__stepper');
173
+ const minus = el('button', 'cart-line__step', '−');
174
+ minus.type = 'button';
175
+ minus.setAttribute('aria-label', `Decrease quantity of ${item.name}`);
176
+ minus.addEventListener('click', () => cart.setQty(item.id, item.qty - 1));
177
+ const qty = el('span', 'cart-line__count', String(item.qty));
178
+ qty.setAttribute('aria-label', `Quantity ${item.qty}`);
179
+ const plus = el('button', 'cart-line__step', '+');
180
+ plus.type = 'button';
181
+ plus.setAttribute('aria-label', `Increase quantity of ${item.name}`);
182
+ plus.disabled = item.qty >= 99;
183
+ plus.addEventListener('click', () => cart.setQty(item.id, item.qty + 1));
184
+ stepper.append(minus, qty, plus);
185
+ info.append(stepper);
186
+
187
+ const side = el('div', 'cart-line__side');
188
+ side.append(el('p', 'cart-line__price', formatMoney(item.price * item.qty, money)));
189
+ const remove = el('button', 'cart-line__remove', 'Remove');
190
+ remove.type = 'button';
191
+ remove.setAttribute('aria-label', `Remove ${item.name}`);
192
+ remove.addEventListener('click', () => cart.remove(item.id));
193
+ side.append(remove);
194
+
195
+ li.append(media, info, side);
196
+ return li;
197
+ }
198
+
199
+ function render() {
200
+ const items = cart.items();
201
+ const count = items.reduce((n, i) => n + i.qty, 0);
202
+ const subtotal = items.reduce((n, i) => n + i.qty * i.price, 0);
203
+ const active = document.activeElement as HTMLElement | null;
204
+ const focusedLabel = active && root.contains(active) ? active.getAttribute('aria-label') : null;
205
+ list.replaceChildren(...items.map(line));
206
+ empty.hidden = items.length > 0;
207
+ foot.hidden = items.length === 0;
208
+ list.hidden = items.length === 0;
209
+ qtyEl.textContent = count ? `(${count})` : '';
210
+ subtotalEl.textContent = formatMoney(subtotal, money);
211
+ if (ship && shipText && shipBar) {
212
+ ship.hidden = items.length === 0;
213
+ const left = Math.max(0, free - subtotal);
214
+ shipText.textContent = left > 0
215
+ ? `You're ${formatMoney(left, money)} away from free shipping`
216
+ : 'You unlocked free shipping';
217
+ shipBar.style.width = `${Math.min(100, (subtotal / free) * 100)}%`;
218
+ ship.classList.toggle('is-done', left === 0);
219
+ }
220
+ // keep the keyboard position after re-rendering the lines
221
+ if (focusedLabel && !root.contains(document.activeElement)) {
222
+ const again = Array.from(root.querySelectorAll<HTMLElement>('[aria-label]')).find((n) => n.getAttribute('aria-label') === focusedLabel);
223
+ (again ?? panel).focus();
224
+ }
225
+ }
226
+
227
+ function open(trigger?: HTMLElement | null) {
228
+ if (inline) return;
229
+ lastTrigger = trigger ?? (document.activeElement as HTMLElement | null);
230
+ root.hidden = false;
231
+ document.documentElement.style.overflow = 'hidden';
232
+ requestAnimationFrame(() => {
233
+ root.classList.add('is-open');
234
+ panel.focus();
235
+ });
236
+ }
237
+
238
+ function close() {
239
+ if (inline || root.hidden) return;
240
+ root.classList.remove('is-open');
241
+ document.documentElement.style.overflow = '';
242
+ const done = () => { root.hidden = true; };
243
+ if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) done();
244
+ else window.setTimeout(done, 280);
245
+ lastTrigger?.focus();
246
+ }
247
+
248
+ render();
249
+ cart.subscribe(render);
250
+
251
+ document.addEventListener('ack:cart-open', (e) => {
252
+ const detail = (e as CustomEvent).detail || {};
253
+ if (!detail.key || detail.key === cart.key) open(detail.trigger);
254
+ });
255
+ document.querySelectorAll<HTMLElement>('[data-cart-open]').forEach((btn) => {
256
+ if (btn.closest('[data-store-header]')) return; // the header dispatches the event itself
257
+ btn.addEventListener('click', () => open(btn));
258
+ });
259
+ root.querySelectorAll('[data-cart-close]').forEach((n) => n.addEventListener('click', close));
260
+
261
+ root.addEventListener('keydown', (e) => {
262
+ if (inline) return;
263
+ if (e.key === 'Escape') { e.preventDefault(); close(); return; }
264
+ if (e.key !== 'Tab') return;
265
+ const nodes = Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE)).filter((n) => !n.closest('[hidden]'));
266
+ if (!nodes.length) return;
267
+ const first = nodes[0];
268
+ const last = nodes[nodes.length - 1];
269
+ if (e.shiftKey && (document.activeElement === first || document.activeElement === panel)) { e.preventDefault(); last.focus(); }
270
+ else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }
271
+ });
272
+ });
273
+ </script>
274
+
275
+ <style>
276
+ .cart-drawer {
277
+ --cd-w: 420px;
278
+ position: fixed;
279
+ inset: 0;
280
+ z-index: 1000;
281
+ color: var(--c-text-2, #a3a3a3);
282
+ }
283
+ .cart-drawer[hidden] { display: none; }
284
+ .cart-drawer--sm { --cd-w: 360px; }
285
+ .cart-drawer--lg { --cd-w: 500px; }
286
+
287
+ .cart-drawer__backdrop {
288
+ position: absolute;
289
+ inset: 0;
290
+ background: rgba(0, 0, 0, 0.6);
291
+ backdrop-filter: blur(4px);
292
+ opacity: 0;
293
+ transition: opacity 0.28s ease;
294
+ }
295
+ .cart-drawer.is-open .cart-drawer__backdrop { opacity: 1; }
296
+
297
+ .cart-drawer__panel {
298
+ position: absolute;
299
+ top: 0;
300
+ bottom: 0;
301
+ right: 0;
302
+ display: flex;
303
+ flex-direction: column;
304
+ width: min(var(--cd-w), 100vw);
305
+ background: var(--c-bg-1, #0a0a0a);
306
+ border-left: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08);
307
+ box-shadow: -30px 0 80px -30px rgba(0, 0, 0, 0.9);
308
+ transform: translateX(100%);
309
+ transition: transform 0.32s cubic-bezier(0.16, 1, 0.3, 1);
310
+ outline: none;
311
+ }
312
+ .cart-drawer--left .cart-drawer__panel { right: auto; left: 0; border-left: 0; border-right: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08); transform: translateX(-100%); }
313
+ .cart-drawer.is-open .cart-drawer__panel { transform: none; }
314
+
315
+ .cart-drawer--inline { position: relative; inset: auto; z-index: auto; }
316
+ .cart-drawer--inline .cart-drawer__panel {
317
+ position: relative;
318
+ width: 100%;
319
+ max-width: var(--cd-w);
320
+ margin: 0 auto;
321
+ transform: none;
322
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.08);
323
+ border-radius: var(--r-lg, 20px);
324
+ box-shadow: none;
325
+ min-height: 420px;
326
+ }
327
+
328
+ .cart-drawer__head {
329
+ display: flex;
330
+ align-items: center;
331
+ justify-content: space-between;
332
+ gap: var(--sp-3, 0.75rem);
333
+ padding: var(--sp-5, 1.25rem) var(--sp-6, 1.5rem);
334
+ border-bottom: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.05));
335
+ }
336
+ .cart-drawer__title { margin: 0; font-size: 1.1rem; font-weight: 800; color: var(--c-text-1, #fff); }
337
+ .cart-drawer__qty { color: var(--c-text-2, #a3a3a3); font-weight: 600; }
338
+ .cart-drawer__close {
339
+ display: grid;
340
+ place-items: center;
341
+ width: 44px;
342
+ height: 44px;
343
+ margin-right: -10px;
344
+ border: 0;
345
+ border-radius: var(--r-full, 9999px);
346
+ background: transparent;
347
+ color: var(--c-text-1, #fff);
348
+ cursor: pointer;
349
+ }
350
+ .cart-drawer__close:hover { background: rgb(var(--ink-rgb, 255 255 255) / 0.06); }
351
+
352
+ .cart-drawer__ship { padding: var(--sp-4, 1rem) var(--sp-6, 1.5rem) 0; }
353
+ .cart-drawer__ship[hidden] { display: none; }
354
+ .cart-drawer__ship-text { margin: 0 0 var(--sp-2, 0.5rem); font-size: 0.82rem; color: var(--c-text-1, #fff); }
355
+ .cart-drawer__ship-bar { height: 6px; border-radius: var(--r-full, 9999px); background: rgb(var(--ink-rgb, 255 255 255) / 0.07); overflow: hidden; }
356
+ .cart-drawer__ship-bar span {
357
+ display: block;
358
+ height: 100%;
359
+ width: 0;
360
+ border-radius: inherit;
361
+ background: linear-gradient(90deg, var(--c-primary, #b8860b), var(--c-primary-light, #d4a017));
362
+ transition: width 0.5s cubic-bezier(0.16, 1, 0.3, 1);
363
+ }
364
+ .cart-drawer__ship.is-done .cart-drawer__ship-bar span { background: var(--c-success, #34d399); }
365
+
366
+ .cart-drawer__lines {
367
+ flex: 1;
368
+ margin: 0;
369
+ padding: var(--sp-2, 0.5rem) var(--sp-6, 1.5rem);
370
+ list-style: none;
371
+ overflow-y: auto;
372
+ }
373
+ .cart-drawer__lines[hidden] { display: none; }
374
+ .cart-drawer__lines :global(.cart-line) {
375
+ display: grid;
376
+ grid-template-columns: 72px 1fr auto;
377
+ gap: var(--sp-3, 0.75rem);
378
+ align-items: start;
379
+ padding: var(--sp-4, 1rem) 0;
380
+ border-bottom: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.05));
381
+ animation: cd-in 0.3s ease-out;
382
+ }
383
+ .cart-drawer__lines :global(.cart-line__media) {
384
+ display: grid;
385
+ place-items: center;
386
+ width: 72px;
387
+ height: 72px;
388
+ border-radius: var(--r-md, 12px);
389
+ overflow: hidden;
390
+ color: hsl(var(--h) var(--pm-ink-s, 80%) var(--pm-ink-l, 80%));
391
+ background: radial-gradient(circle at 50% 40%, hsl(var(--h) 70% 50% / 0.3), transparent 70%), var(--c-bg-2, #111);
392
+ }
393
+ .cart-drawer__lines :global(.cart-line__media svg) { width: 60%; }
394
+ .cart-drawer__lines :global(.cart-line__media img) { width: 100%; height: 100%; object-fit: cover; }
395
+ .cart-drawer__lines :global(.cart-line__name) { margin: 0; font-size: 0.9rem; font-weight: 700; color: var(--c-text-1, #fff); line-height: 1.3; }
396
+ .cart-drawer__lines :global(.cart-line__variant) { margin: 2px 0 0; font-size: 0.78rem; color: var(--c-text-2, #a3a3a3); }
397
+ .cart-drawer__lines :global(.cart-line__stepper) {
398
+ display: inline-flex;
399
+ align-items: center;
400
+ margin-top: var(--sp-2, 0.5rem);
401
+ border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.1);
402
+ border-radius: var(--r-full, 9999px);
403
+ }
404
+ .cart-drawer__lines :global(.cart-line__step) {
405
+ width: 36px;
406
+ height: 36px;
407
+ border: 0;
408
+ background: transparent;
409
+ color: var(--c-text-1, #fff);
410
+ font-size: 1rem;
411
+ cursor: pointer;
412
+ border-radius: var(--r-full, 9999px);
413
+ }
414
+ .cart-drawer__lines :global(.cart-line__step:hover) { background: rgb(var(--ink-rgb, 255 255 255) / 0.06); }
415
+ .cart-drawer__lines :global(.cart-line__step:disabled) { opacity: 0.35; cursor: not-allowed; }
416
+ .cart-drawer__lines :global(.cart-line__count) { min-width: 24px; text-align: center; font-size: 0.85rem; font-weight: 700; color: var(--c-text-1, #fff); }
417
+ .cart-drawer__lines :global(.cart-line__side) { display: grid; justify-items: end; gap: var(--sp-2, 0.5rem); }
418
+ .cart-drawer__lines :global(.cart-line__price) { margin: 0; font-weight: 800; color: var(--c-text-1, #fff); font-size: 0.9rem; }
419
+ .cart-drawer__lines :global(.cart-line__remove) {
420
+ min-height: 32px;
421
+ padding: 0 4px;
422
+ border: 0;
423
+ background: transparent;
424
+ color: var(--c-text-2, #a3a3a3);
425
+ font: inherit;
426
+ font-size: 0.78rem;
427
+ text-decoration: underline;
428
+ text-underline-offset: 3px;
429
+ cursor: pointer;
430
+ }
431
+ .cart-drawer__lines :global(.cart-line__remove:hover) { color: var(--c-danger, #f87171); }
432
+ @keyframes cd-in { from { opacity: 0; transform: translateY(6px); } }
433
+
434
+ .cart-drawer__empty {
435
+ flex: 1;
436
+ display: flex;
437
+ flex-direction: column;
438
+ align-items: center;
439
+ justify-content: center;
440
+ gap: var(--sp-2, 0.5rem);
441
+ padding: var(--sp-8, 2rem);
442
+ text-align: center;
443
+ }
444
+ .cart-drawer__empty[hidden] { display: none; }
445
+ .cart-drawer__empty svg { color: var(--c-primary-light, #d4a017); margin-bottom: var(--sp-2, 0.5rem); }
446
+ .cart-drawer__empty p { margin: 0; font-size: 0.9rem; }
447
+ .cart-drawer__empty strong { color: var(--c-text-1, #fff); font-size: 1rem; }
448
+
449
+ .cart-drawer__foot {
450
+ display: grid;
451
+ gap: var(--sp-3, 0.75rem);
452
+ padding: var(--sp-5, 1.25rem) var(--sp-6, 1.5rem) var(--sp-6, 1.5rem);
453
+ border-top: 1px solid var(--c-border, rgb(var(--ink-rgb, 255 255 255) / 0.05));
454
+ background: rgb(var(--ink-rgb, 255 255 255) / 0.015);
455
+ }
456
+ .cart-drawer__foot[hidden] { display: none; }
457
+ .cart-drawer__row { display: flex; justify-content: space-between; align-items: baseline; color: var(--c-text-1, #fff); }
458
+ .cart-drawer__row strong { font-size: 1.2rem; }
459
+ .cart-drawer__note { margin: 0; font-size: 0.78rem; }
460
+ .cart-drawer__checkout,
461
+ .cart-drawer__ghost {
462
+ display: inline-flex;
463
+ align-items: center;
464
+ justify-content: center;
465
+ gap: var(--sp-2, 0.5rem);
466
+ min-height: 50px;
467
+ border-radius: var(--r-full, 9999px);
468
+ font: inherit;
469
+ font-weight: 700;
470
+ text-decoration: none;
471
+ cursor: pointer;
472
+ transition: filter 0.2s ease, transform 0.2s ease;
473
+ }
474
+ .cart-drawer__checkout { background: var(--c-primary-light, #d4a017); color: var(--c-on-primary, #050505); }
475
+ .cart-drawer__checkout:hover { filter: brightness(1.08); color: var(--c-on-primary, #050505); }
476
+ .cart-drawer__ghost { margin-top: var(--sp-3, 0.75rem); padding: 0 var(--sp-6, 1.5rem); min-height: 44px; border: 1px solid rgb(var(--ink-rgb, 255 255 255) / 0.14); background: transparent; color: var(--c-text-1, #fff); }
477
+ .cart-drawer :is(a, button):focus-visible { outline: 2px solid var(--c-primary-light, #d4a017); outline-offset: 2px; }
478
+
479
+ @media (prefers-reduced-motion: reduce) {
480
+ .cart-drawer__panel, .cart-drawer__backdrop, .cart-drawer__ship-bar span { transition: none; }
481
+ .cart-drawer__lines :global(.cart-line) { animation: none; }
482
+ }
483
+ </style>