@code-collective/booking-widget 1.0.10 → 1.0.13

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 (45) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +153 -41
  3. package/dist/booking-widget.css +1 -1
  4. package/dist/booking-widget.js +2637 -1924
  5. package/dist/booking-widget.min.css +1 -1
  6. package/dist/booking-widget.min.js +34 -16
  7. package/dist/booking-widget.umd.cjs +5 -3
  8. package/package.json +5 -2
  9. package/src/lib/BookingProvider.svelte +21 -0
  10. package/src/lib/CartBar.svelte +16 -31
  11. package/src/lib/CartBarView.svelte +20 -3
  12. package/src/lib/CartExpiryGuard.svelte +416 -410
  13. package/src/lib/CartOverview.svelte +16 -6
  14. package/src/lib/CartOverviewButton.svelte +29 -26
  15. package/src/lib/Checkout.svelte +111 -98
  16. package/src/lib/CheckoutModal.svelte +946 -805
  17. package/src/lib/CheckoutPanel.svelte +121 -0
  18. package/src/lib/PaymentPage.svelte +16 -2
  19. package/src/lib/PickupPointPicker.svelte +1 -1
  20. package/src/lib/ResultView.svelte +24 -4
  21. package/src/lib/TicketConfigurator.svelte +19 -5
  22. package/src/lib/UnitCounter.svelte +16 -2
  23. package/src/lib/WizardPage.svelte +102 -35
  24. package/src/lib/app.css +0 -6
  25. package/src/lib/booking-context.ts +33 -0
  26. package/src/lib/cart-overview.svelte.ts +97 -0
  27. package/src/lib/client-types.ts +10 -6
  28. package/src/lib/config.ts +9 -0
  29. package/src/lib/elements/bw-cart.svelte +21 -7
  30. package/src/lib/elements/bw-checkout.svelte +54 -54
  31. package/src/lib/elements/bw-configurator.svelte +30 -14
  32. package/src/lib/elements/register.ts +96 -121
  33. package/src/lib/elements/shared.ts +4 -0
  34. package/src/lib/elements/theme.css +0 -6
  35. package/src/lib/host.svelte.ts +336 -0
  36. package/src/lib/index.ts +137 -91
  37. package/src/lib/layout.svelte.ts +52 -0
  38. package/src/lib/messages.ts +97 -17
  39. package/src/lib/peach-sdk.ts +51 -5
  40. package/src/lib/portal.ts +23 -0
  41. package/src/lib/CartExpiryGuard.test.ts +0 -331
  42. package/src/lib/CheckoutModal.confirm-outcome.test.ts +0 -91
  43. package/src/lib/CheckoutModal.payment-timeout.test.ts +0 -140
  44. package/src/lib/test/fixtures.ts +0 -107
  45. package/src/lib/test/messages-mock.ts +0 -34
package/CHANGELOG.md ADDED
@@ -0,0 +1,48 @@
1
+ # Changelog
2
+
3
+ The version in `package.json` had drifted behind npm (the repo said 1.0.7 while `latest` was 1.0.9), so
4
+ 1.0.8 and 1.0.9 shipped without an entry here. This file starts at the point that caused a consumer a real
5
+ detour.
6
+
7
+ ## 1.0.12
8
+
9
+ Published from the branch that carries this work, which 1.0.10 was not. 1.0.11 was prepared but never
10
+ published - the release script bumps patch before publishing, so the version that went out is one ahead of
11
+ the one the branch was prepared as. The contents are identical either way.
12
+
13
+ ## 1.0.10
14
+
15
+ Published from a checkout that did not contain this work - `npm run release` was run from the main repo
16
+ while the branch lived in a worktree - so it carries none of the entries below. `createBookingHost`,
17
+ `BookingProvider`, `CartExpiryGuard`, `portal` and `setPeachSdk` are all absent from its `src/lib/index.ts`,
18
+ which is the entry the `svelte` export condition points at and therefore the one a Svelte consumer compiles.
19
+ Nothing to roll back; it is 1.0.9's code under a new number. **Publish from a checkout that has the branch,
20
+ and check the entry exports before shipping** - `npm pack` the tarball and grep `package/src/lib/index.ts`.
21
+
22
+ See `.changeset/` for the individual entries. In summary:
23
+
24
+ - **Added** `createBookingHost()`, the shared host layer both builds now wire through, and
25
+ `BookingProvider` so components need no `api`/`cartManager` props.
26
+ - **Added** `mode="modal"` on `Checkout`, portalled to `document.body`.
27
+ - **Added** exports: `CartExpiryGuard`, `onWidgetMessage`, `postMessage`, the `WidgetMessage` union,
28
+ `portal`, `setBookingDefaults`.
29
+ - **Changed** add-to-cart now opens checkout on every build by default (`autoOpenCheckout`).
30
+ - **Changed** each `bw:*` event reaches `window` once per thing that happened. They were arriving twice,
31
+ and `bw:close` four times per dismissal. Anything counting `bw:order-confirmed` was double-counting.
32
+ - **Changed** each `bw-*` element dispatches its own events rather than all firing on the first element of
33
+ that tag.
34
+ - **Fixed** building a host no longer throws `window is not defined` during a server render.
35
+ - **Unchanged** `ApiClient`, `SessionManager` and `CartManager` are still exported; the custom-element
36
+ attributes, `bwOptions` and the `bw:*` event names are all as they were.
37
+
38
+ ## 1.0.9
39
+
40
+ - **Removed** `FakeApiClient` and the `useFakeApi` mount option, along with `src/lib/fake-api.ts`. This was
41
+ a breaking change shipped in a patch release with no note: a consumer importing `FakeApiClient` gets a
42
+ build error, because a missing named export is a hard failure in a bundler, not a runtime `undefined`.
43
+ It had been the offline development path for consumers with no checkout API configured.
44
+
45
+ **Decision: removed for good.** It is not coming back, and no later version reinstates it. A consumer
46
+ that wants to run without a checkout API should render its own fallback rather than a fake catalogue.
47
+ Where a stand-in client is genuinely needed - a test double, a demo page - pass one to
48
+ `createBookingHost({ api })`, which takes any `BookingApi` implementation.
package/README.md CHANGED
@@ -9,8 +9,8 @@ Drop in a script tag and use the custom elements directly. No framework or npm i
9
9
  ```html
10
10
  <head>
11
11
  <link rel="stylesheet"
12
- href="https://cdn.jsdelivr.net/npm/@code-collective/booking-widget@1.0.2/dist/booking-widget.min.css"
13
- integrity="sha384-Chz7xKNGRdJXbNwkaJzy45I9DiwPStkzNQV9VgWMNKgnlHCpgnhbiukEJEtbHt+v"
12
+ href="https://cdn.jsdelivr.net/npm/@code-collective/booking-widget@1.0.12/dist/booking-widget.min.css"
13
+ integrity="sha384-LDthHLROtZeufKbWmAhXBsAFLqqhkZjWFd8gWU+Ez11qyg5ebj9vpFnvJuZcM3ft"
14
14
  crossorigin="anonymous" />
15
15
  </head>
16
16
  <body>
@@ -20,8 +20,8 @@ Drop in a script tag and use the custom elements directly. No framework or npm i
20
20
  <bw-checkout></bw-checkout>
21
21
 
22
22
  <script
23
- src="https://cdn.jsdelivr.net/npm/@code-collective/booking-widget@1.0.2/dist/booking-widget.min.js"
24
- integrity="sha384-y2Ncw7PWI9NxiwKR0nhDk+ddkYmUyl+kxFyU+B4eDXWt3bfcMk1CRVTXXUwhTgJd"
23
+ src="https://cdn.jsdelivr.net/npm/@code-collective/booking-widget@1.0.12/dist/booking-widget.min.js"
24
+ integrity="sha384-nkQuTziRverWyDu3eR0AaBqefk10pn3x+o3IsjuNvdkkabowNQISYgXijIi8t0El"
25
25
  crossorigin="anonymous"></script>
26
26
  </body>
27
27
  ```
@@ -76,8 +76,8 @@ Set `window.bwOptions` before the widget script loads:
76
76
  editPages: '',
77
77
  };
78
78
  </script>
79
- <script src="https://cdn.jsdelivr.net/npm/@code-collective/booking-widget@1.0.2/dist/booking-widget.min.js"
80
- integrity="sha384-y2Ncw7PWI9NxiwKR0nhDk+ddkYmUyl+kxFyU+B4eDXWt3bfcMk1CRVTXXUwhTgJd"
79
+ <script src="https://cdn.jsdelivr.net/npm/@code-collective/booking-widget@1.0.12/dist/booking-widget.min.js"
80
+ integrity="sha384-nkQuTziRverWyDu3eR0AaBqefk10pn3x+o3IsjuNvdkkabowNQISYgXijIi8t0El"
81
81
  crossorigin="anonymous"></script>
82
82
  ```
83
83
 
@@ -102,7 +102,6 @@ window.addEventListener('bw:order-confirmed', (e) => {
102
102
  |---|---|---|
103
103
  | `bw:cart-change` | `{ itemCount, cartItemId, totalFormatted }` | Item added to cart |
104
104
  | `bw:cart-updated` | `{ cart }` (full `CheckoutCartDetailDto`, or `null` if empty) | Cart changed - add, edit, remove, extend, or expiry. Use this to build a custom cart summary (item count, remaining time - see Cart expiry below, item details via `cart.items`) instead of `bw-cart`. Fires with `cart: null` when the cart expires, same as a manual clear |
105
- | `bw:cart-expired` | -- | The cart's own clock ran out (or checkout loaded onto a cart the server had already let go). A strict subset of `bw:cart-updated`'s own `cart: null` case - fires alongside it, for a listener that wants to react to expiry specifically (analytics, a redirect, its own message) without having to infer it from an absent cart, which a manual clear also produces |
106
105
  | `bw:order-confirmed` | `{ cartToken, value, currency }` | Payment completed and confirmed. Fires as soon as confirmation succeeds, while the success screen is still showing - it does not close the modal (see Payment outcomes below) |
107
106
  | `bw:modal-open` | -- | Checkout modal opens |
108
107
  | `bw:modal-close` | -- | Checkout modal closes |
@@ -117,9 +116,9 @@ Peach's own charge and this gateway's confirmation of it are two separate steps
117
116
  | Outcome | Shown when | Shopper can retry payment? |
118
117
  |---|---|---|
119
118
  | Payment Successful | Charge succeeded and confirmed | -- |
120
- | Payment Received (pending) | Charge succeeded, confirmation still waiting on the webhook after ~15s of automatic retries | No - a "Check Again" button re-checks on demand instead |
119
+ | Still Checking Your Payment (pending) | No final answer after ~15s of automatic retries - a charge whose webhook is slow, or a decline this gateway could not read back | No - a "Check Again" button re-checks on demand instead |
121
120
  | Payment Failed (not charged) | Peach reports the session expired, errored, or was never charged | Yes - nothing was charged |
122
- | Payment Failed (partial) | Charge succeeded but some cart items didn't confirm | No - the charge already happened; the shopper is told to contact support instead |
121
+ | Booking Incomplete (partial) | Charge succeeded but some cart items didn't confirm | No - the charge already happened; the shopper is told to contact support instead |
123
122
 
124
123
  `bw:order-confirmed` only fires for the first outcome. A shopper who dismisses the "pending" screen before it resolves gets no order-confirmed event even though the payment may still confirm moments later via the webhook - build any "wait for confirmation" UI around the event, not around the modal closing.
125
124
 
@@ -129,9 +128,9 @@ A cart is created with a 15-minute idle window and a hard ceiling of 35 minutes
129
128
 
130
129
  At two minutes remaining, an "Are you still there?" prompt appears. Unlike the rest of the widget's UI it is page-wide: it renders above whatever the shopper is looking at, whether or not a `<bw-checkout>` is even open, since a merchant might embed only `<bw-configurator>`/`<bw-cart>` with no checkout element on the page at all. Answering "Yes, I'm still here" resets the idle window as above and fires `bw:cart-updated` with the extended cart. Once the idle deadline has already reached the ceiling - or an answer came back with the deadline unmoved because a supplier hold could not be extended - the prompt only warns and offers OK: there is no more time to be had. While a payment is in progress the cart is exempt from expiry and cannot be extended (Peach's own session cannot be either), so the prompt does not appear over the card form; if the payment is cancelled or fails and the cart reopens, the countdown resumes from the server's deadline. A page reload during payment is remembered: reopening checkout returns the shopper to the same card form, or offers to start over if Peach no longer accepts it.
131
130
 
132
- If the deadline passes unanswered, the widget checks with the server and, once the server confirms it has let the cart go, clears it: a "Your cart has expired" message replaces the prompt, `bw:cart-expired` and `bw:cart-updated` (with `cart: null`) both fire, and there is no way back into the expired cart - only a fresh one. The browser's clock only decides when to ask; a clock running ahead of the server's never throws away a live cart. If you compute your own "time remaining", read it from `cart.idleExpiresAt`.
131
+ If the deadline passes unanswered, the widget checks with the server and, once the server confirms it has let the cart go, clears it: a "Your cart has expired" message replaces the prompt, `bw:cart-updated` fires with `cart: null`, and there is no way back into the expired cart - only a fresh one. The browser's clock only decides when to ask; a clock running ahead of the server's never throws away a live cart. If you compute your own "time remaining", read it from `cart.idleExpiresAt`.
133
132
 
134
- The prompt, the extension it offers, and the automatic clear-on-expiry are part of the custom-elements script only - `register.ts` mounts them once per page when the bundle loads. Option 2 (mount functions) and Option 3 (Svelte components) do not include them: the cart still expires server-side on the same schedule and `onCartUpdated` still reports `cart: null` once the widget next hears the server refuse it, but nothing warns the shopper beforehand or offers to extend. Build that UI yourself from `cart.idleExpiresAt` if you need it on those paths.
133
+ The prompt, the extension it offers, and the automatic clear-on-expiry come with `createBookingHost()`, which mounts `CartExpiryGuard` on `<body>` once per page - so every option below has them, not just the custom-elements script. Pass `mountExpiryGuard: false` to opt out, or mount `<CartExpiryGuard />` yourself. Only a consumer building `ApiClient` / `SessionManager` / `CartManager` by hand goes without: then the cart still expires server-side on the same schedule and `onCartUpdated` still reports `cart: null` once the widget next hears the server refuse it, but nothing warns the shopper beforehand.
135
134
 
136
135
  ## Option 2: JavaScript mount functions
137
136
 
@@ -142,19 +141,28 @@ npm install @code-collective/booking-widget
142
141
  ```
143
142
 
144
143
  ```js
145
- import { mountConfigurator, mountCheckout, mountCartOverview } from '@code-collective/booking-widget';
144
+ import { createBookingHost, mountConfigurator, mountCheckout } from '@code-collective/booking-widget';
146
145
  import '@code-collective/booking-widget/style.css';
147
146
 
148
- const widget = await mountConfigurator(document.getElementById('configurator'), {
149
- productId: 'your-product-id',
147
+ // One host for the page: one session, one cart, one expiry guard. Pass it to every mount function that
148
+ // should share them - without it, each mount builds its own and you get two of everything.
149
+ const host = createBookingHost({
150
150
  checkoutKey: 'your-checkout-key',
151
151
  apiBaseUrl: 'https://checkout.yourdomain.com',
152
+ });
153
+
154
+ const configurator = await mountConfigurator(document.getElementById('configurator'), {
155
+ host,
156
+ productId: 'your-product-id',
152
157
  onCartChange({ itemCount, totalFormatted }) {
153
158
  console.log(`${itemCount} items, ${totalFormatted}`);
154
159
  },
155
160
  });
156
161
 
157
- // Later: widget.destroy();
162
+ // Adding an item opens this automatically - see Host wiring below.
163
+ const checkout = await mountCheckout(document.getElementById('checkout'), { host, mode: 'modal' });
164
+
165
+ // Later: configurator.destroy(); checkout.destroy(); host.destroy();
158
166
  ```
159
167
 
160
168
  ### Mount functions
@@ -164,40 +172,42 @@ const widget = await mountConfigurator(document.getElementById('configurator'),
164
172
  | Config | Required | Description |
165
173
  |---|---|---|
166
174
  | `productId` | Yes | OCTO product ID |
175
+ | `host` | No | A `BookingHost` to share (see [Host wiring](#host-wiring)). Without one, this mount builds its own |
167
176
  | `checkoutKey` | No | Supplier checkout key |
168
177
  | `apiBaseUrl` | No | Checkout API URL |
169
178
  | `wizardPages` | No | Wizard step ordering |
170
179
  | `autoSelectSingleTimeSlot` | No | Skip time picker if single slot |
171
- | `onCartChange` | No | Callback: `({ itemCount, cartItemId, totalFormatted })` |
172
- | `onCartUpdated` | No | Callback: `(cart: CheckoutCartDetailDto \| null)`, fires on any cart change - see `bw:cart-updated` above |
173
- | `onCartExpired` | No | Callback, fires when the cart expires - see `bw:cart-expired` above |
180
+ | Cart callbacks | No | `onCartChange`, `onCartUpdated`, `onCartExpired`, `onOrderConfirmed` - see [Host wiring](#host-wiring) |
174
181
  | `onCancel` | No | Callback when cancelled |
175
182
 
176
183
  **`mountCheckout(target, config)`** — Cart review, contact form, payment
177
184
 
178
185
  | Config | Required | Description |
179
186
  |---|---|---|
187
+ | `host` | No | A `BookingHost` to share (see [Host wiring](#host-wiring)). Without one, this mount builds its own |
180
188
  | `checkoutKey` | No | Supplier checkout key |
181
189
  | `apiBaseUrl` | No | Checkout API URL |
190
+ | `mode` | No | `inline` (default) or `modal` - a portalled overlay with a scrim |
182
191
  | `onClose` | No | Callback when closed |
183
192
  | `onOrderConfirmed` | No | Callback: `({ cartToken, value, currency })` |
184
- | `onCartUpdated` | No | Callback: `(cart: CheckoutCartDetailDto \| null)`, fires on any cart change - see `bw:cart-updated` above |
185
- | `onCartExpired` | No | Callback, fires when the cart expires - see `bw:cart-expired` above |
193
+ | Cart callbacks | No | `onCartChange`, `onCartUpdated`, `onCartExpired`, `onOrderConfirmed` - see [Host wiring](#host-wiring) |
186
194
 
187
195
  **`mountCartOverview(target, config)`** — Cart summary
188
196
 
189
197
  | Config | Required | Description |
190
198
  |---|---|---|
199
+ | `host` | No | A `BookingHost` to share (see [Host wiring](#host-wiring)). Without one, this mount builds its own |
191
200
  | `checkoutKey` | No | Supplier checkout key |
192
201
  | `apiBaseUrl` | No | Checkout API URL |
193
202
  | `display` | No | `bar` or `button` |
194
203
  | `onCheckout` | No | Callback when checkout clicked |
195
- | `onCartUpdated` | No | Callback: `(cart: CheckoutCartDetailDto \| null)`, fires on any cart change - see `bw:cart-updated` above |
196
- | `onCartExpired` | No | Callback, fires when the cart expires - see `bw:cart-expired` above |
204
+ | Cart callbacks | No | `onCartChange`, `onCartUpdated`, `onCartExpired`, `onOrderConfirmed` - see [Host wiring](#host-wiring) |
197
205
 
198
- All mount functions return `Promise<{ destroy(): void }>`.
206
+ All mount functions return `Promise<{ destroy(): void }>`. Every config also accepts the
207
+ [host options](#host-wiring) (`autoOpenCheckout`, `wizardPages`, `autoSelectSingleTimeSlot`, `onCartUpdated`,
208
+ ...), which are used to build the host when you do not pass one.
199
209
 
200
- The page-wide "Are you still there?" prompt and extend flow are not part of this option - see [Cart expiry](#cart-expiry) above.
210
+ The page-wide "Are you still there?" prompt and extend flow come with the host, so this option has them.
201
211
 
202
212
  ## Option 3: Svelte components
203
213
 
@@ -209,27 +219,129 @@ npm install @code-collective/booking-widget
209
219
 
210
220
  ```svelte
211
221
  <script lang="ts">
212
- import { TicketConfigurator, Checkout, CartBar, CartOverviewButton } from '@code-collective/booking-widget';
213
- import { ApiClient, SessionManager, CartManager } from '@code-collective/booking-widget';
222
+ import { createBookingHost, BookingProvider, TicketConfigurator, Checkout, CartOverviewButton } from '@code-collective/booking-widget';
214
223
  import '@code-collective/booking-widget/style.css';
215
224
 
216
- const api = new ApiClient('https://checkout.yourdomain.com');
217
- const sessionManager = new SessionManager(api);
218
- const cartManager = new CartManager(api);
219
- sessionManager.startBackgroundRefresh();
220
-
225
+ let host = $state<BookingHost | null>(null);
221
226
  let ready = $state(false);
222
- sessionManager.ensureSession('your-checkout-key').then(() => { ready = true; });
227
+
228
+ // In onMount, not the script body: anything server-rendered (an Astro island, SvelteKit SSR) runs the
229
+ // body once with no DOM, and a host built there opens a session the hydrated page throws away.
230
+ onMount(() => {
231
+ const created = createBookingHost({
232
+ apiBaseUrl: 'https://checkout.yourdomain.com',
233
+ checkoutKey: 'your-checkout-key',
234
+ });
235
+ host = created;
236
+ // ready rejects if the checkout service turns the session down - usually the key not being allowlisted
237
+ // for this origin. Uncaught, the page sits on its loading state forever.
238
+ created.ready.then(() => { ready = true; }).catch(() => { /* render your own fallback */ });
239
+ return () => created.destroy();
240
+ });
223
241
  </script>
224
242
 
225
- {#if ready}
226
- <TicketConfigurator {api} {cartManager} productId="your-product-id" />
227
- <CartOverviewButton {api} {cartManager} />
228
- <Checkout {api} />
243
+ {#if ready && host}
244
+ <BookingProvider host={host}>
245
+ <TicketConfigurator productId="your-product-id" />
246
+ <CartOverviewButton />
247
+ <Checkout mode="modal" />
248
+ </BookingProvider>
229
249
  {/if}
230
250
  ```
231
251
 
232
- The page-wide "Are you still there?" prompt and extend flow are not part of this option either - see [Cart expiry](#cart-expiry) above.
252
+ Adding an item opens the checkout, the modal portals itself out of any sticky or transformed ancestor, and
253
+ the page-wide "Are you still there?" prompt is mounted for you - all from the host, with no wiring of your
254
+ own. See [Host wiring](#host-wiring).
255
+
256
+ `BookingProvider` puts the host in Svelte context, which is why the components inside it need no `api` or
257
+ `cartManager`, and why `Checkout` needs no `open` or `onClose`. Every one of those props still exists and
258
+ still wins when you pass it - the `bw-*` custom elements pass them explicitly, since context does not cross
259
+ a custom element's own root. Without a provider and without the prop, a component throws and names both
260
+ ways to fix it rather than failing somewhere deeper.
261
+
262
+ Building the services by hand (`new ApiClient(...)`, `new SessionManager(...)`, `new CartManager(...)`) still
263
+ works and is what `createBookingHost` does internally, but none of the behaviour above comes with it. If you
264
+ go that route, mount `CartExpiryGuard` yourself:
265
+
266
+ ```svelte
267
+ <CartExpiryGuard {api} {cartManager} />
268
+ ```
269
+
270
+ ## Host wiring
271
+
272
+ `createBookingHost(config)` is the one place the widget's host behaviour lives. The custom-elements build is
273
+ a thin DOM adapter over it, so both builds auto-wire through exactly the same code.
274
+
275
+ ```ts
276
+ const host = createBookingHost({
277
+ apiBaseUrl, checkoutKey,
278
+ autoOpenCheckout: true, // default - opt out, never opt in
279
+ shouldBottomCloseOnModal: true, // hide bar-display carts while checkout is open
280
+ onCartChange({ itemCount, cartItemId, totalFormatted }) {}, // an add, with its own detail
281
+ onCartUpdated(cart) {}, // any change, with the whole cart
282
+ onCartExpired() {}, // the cart's window ran out
283
+ onCheckoutOpenChange(open) {},
284
+ onOrderConfirmed({ cartToken, value, currency }) {},
285
+ });
286
+
287
+ host.api / host.cartManager / host.sessionManager // one set, shared by every component
288
+ host.ready // resolves once the session exists; rejects if it is refused
289
+ host.isCheckoutOpen // reactive
290
+ host.openCheckout() / host.closeCheckout()
291
+ host.destroy()
292
+ ```
293
+
294
+ | Option | Type | Default | Description |
295
+ |---|---|---|---|
296
+ | `autoOpenCheckout` | `boolean \| (() => boolean)` | `true` | Whether adding an item opens checkout. A function is re-asked on every add, so a layout that already shows the configurator inline can answer `false` for that case only |
297
+ | `shouldBottomCloseOnModal` | `boolean` | `true` | Hide bar-display cart overviews while checkout is open |
298
+ | `autoSelectSingleTimeSlot` | `boolean` | `false` | Skip the time picker when only one slot is available |
299
+ | `wizardPages` / `editPages` | `WizardPages` | - | Wizard step ordering, handed to every component the host serves |
300
+ | `mountExpiryGuard` | `boolean` | `true` | Mount `CartExpiryGuard` on `<body>` |
301
+ | `peachEnv` | `'prod' \| 'qa'` | `'prod'` | Which Peach Payments SDK the card form loads. **Set it whenever `apiBaseUrl` is not production** - the checkout API creates the Peach checkout, so a sandbox API with the production SDK gives a card form that refuses to render |
302
+ | `peachSdkUrl` | `string` | - | An explicit Peach SDK URL, for an environment `peachEnv` does not name |
303
+ | `api` | `BookingApi` | - | An API client to use instead of building one from `apiBaseUrl` - a test double, or a fake for a demo page |
304
+
305
+ `setBookingDefaults({ ... })` applies the same options site-wide to every host created afterwards - the ES
306
+ equivalent of `window.bwOptions`. Anything passed to `createBookingHost` wins over a default.
307
+
308
+ ### Callbacks
309
+
310
+ | Callback | Fires |
311
+ |---|---|
312
+ | `onCartChange` | On an add, with the detail only the add knows: `{ itemCount, cartItemId, totalFormatted }`. Fires whether or not checkout opened |
313
+ | `onCartUpdated` | After any add, edit or remove, carrying the whole cart the widget already fetched. What a custom cart summary reads |
314
+ | `onCartExpired` | The cart's window ran out - distinct from the `null` `onCartUpdated` that a confirmed order also produces |
315
+ | `onCheckoutOpenChange` | Checkout opened or closed, once per change. `false` after an order means the shopper dismissed the result - this is the one to navigate on |
316
+ | `onOrderConfirmed` | Payment succeeded *and* was confirmed - not while a webhook is still pending. `{ cartToken, value, currency }`. Fires while the shopper is still looking at the result screen, so do not close or navigate on it |
317
+
318
+ Each is **one slot, fixed when the host is built**: set it twice and the second wins, and there is no
319
+ detaching it short of `host.destroy()`. Where more than one part of the page needs the same event, or a
320
+ subscription should come and go with a component, subscribe to the message bus instead - see below. The two
321
+ ride the same bus and fire at the same moment.
322
+
323
+ The mount functions take these on their own config too. When you hand them a `host`, the callbacks on that
324
+ config are wired to the same events rather than dropped - but each event still reaches your handler once.
325
+
326
+ ### Messages
327
+
328
+ `onWidgetMessage(handler)` subscribes to the widget's own message bus and returns an unsubscribe function;
329
+ `postMessage(message)` sends one. Both are exported, and every message is a member of the `WidgetMessage`
330
+ union (`cart:change`, `cart:updated`, `cart:expired`, `modal:open`, `modal:close`, `order:complete`,
331
+ `payment:started`, `payment:ended`, `payment:timed-out`), so a handler narrows on `message.type`.
332
+
333
+ ```ts
334
+ import { onWidgetMessage } from '@code-collective/booking-widget';
335
+
336
+ const stop = onWidgetMessage((m) => {
337
+ if (m.type === 'cart:change') track('add_to_cart', m.totalFormatted);
338
+ });
339
+ // later
340
+ stop();
341
+ ```
342
+
343
+ Prefer the host callbacks where they cover what you need - they are the same events, already parsed. Reach
344
+ for `onWidgetMessage` when you need several subscribers, or one that comes and goes.
233
345
 
234
346
  ## Astro integration
235
347
 
@@ -245,12 +357,12 @@ const product = await getProduct(Astro.params.slug);
245
357
  <bw-checkout checkout-key={product.checkoutKey}></bw-checkout>
246
358
 
247
359
  <link rel="stylesheet"
248
- href="https://cdn.jsdelivr.net/npm/@code-collective/booking-widget@1.0.2/dist/booking-widget.min.css"
249
- integrity="sha384-Chz7xKNGRdJXbNwkaJzy45I9DiwPStkzNQV9VgWMNKgnlHCpgnhbiukEJEtbHt+v"
360
+ href="https://cdn.jsdelivr.net/npm/@code-collective/booking-widget@1.0.12/dist/booking-widget.min.css"
361
+ integrity="sha384-LDthHLROtZeufKbWmAhXBsAFLqqhkZjWFd8gWU+Ez11qyg5ebj9vpFnvJuZcM3ft"
250
362
  crossorigin="anonymous" />
251
363
  <script is:inline
252
- src="https://cdn.jsdelivr.net/npm/@code-collective/booking-widget@1.0.2/dist/booking-widget.min.js"
253
- integrity="sha384-y2Ncw7PWI9NxiwKR0nhDk+ddkYmUyl+kxFyU+B4eDXWt3bfcMk1CRVTXXUwhTgJd"
364
+ src="https://cdn.jsdelivr.net/npm/@code-collective/booking-widget@1.0.12/dist/booking-widget.min.js"
365
+ integrity="sha384-nkQuTziRverWyDu3eR0AaBqefk10pn3x+o3IsjuNvdkkabowNQISYgXijIi8t0El"
254
366
  crossorigin="anonymous"></script>
255
367
  ```
256
368
 
@@ -1,2 +1,2 @@
1
- :root{--bw-color-primary:var(--header-background,#e30613);--bw-color-primary-dark:var(--footer-background,#c00510);--bw-color-primary-light:#e3061314;--bw-color-text:#212121;--bw-color-text-secondary:#757575;--bw-color-border:#e0e0e0;--bw-color-bg:#fff;--bw-color-surface:#f5f5f5;--bw-color-success:#4caf50;--bw-color-error:var(--header-background,#e30613);--bw-font-family:var(--default-font-family,"Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);--bw-radius-sm:4px;--bw-radius-md:var(--radius,8px);--bw-radius-lg:12px;--bw-radius-pill:9999px;--bw-transition:.15s ease;--bw-shadow-1:0 1px 3px #0000001a, 0 1px 2px #0000000f;--bw-shadow-2:0 4px 12px #00000014, 0 2px 4px #0000000a;--bw-shadow-3:0 10px 24px #0000001a, 0 4px 8px #0000000a}.bw-widget,.bw-widget *{box-sizing:border-box;margin:0;padding:0}.bw-widget{font-family:var(--bw-font-family);color:var(--bw-color-text);background:var(--bw-color-bg);-webkit-font-smoothing:antialiased;font-size:14px;line-height:1.5}.bw-widget button,button.bw-widget{cursor:pointer;font-family:inherit}.bw-widget :is(input,select,textarea),:is(input,select,textarea).bw-widget{font-family:inherit}@keyframes spin{to{transform:rotate(360deg)}}.spinner{border:3px solid var(--bw-color-border);border-top-color:var(--bw-color-primary);border-radius:50%;width:36px;height:36px;animation:.7s linear infinite spin}.loading-center{flex:1;justify-content:center;align-items:center;min-height:120px;display:flex}.page-header{border-bottom:1px solid var(--bw-color-border);background:var(--bw-color-bg);align-items:center;gap:8px;padding:12px 16px;display:flex}.page-header h2{letter-spacing:-.01em;font-size:18px;font-weight:700}.page-header .spacer{flex:1}.icon-btn{border-radius:var(--bw-radius-pill);width:36px;height:36px;color:var(--bw-color-text);transition:background var(--bw-transition);background:0 0;border:none;justify-content:center;align-items:center;font-size:20px;display:flex}.icon-btn:hover{background:var(--bw-color-surface)}.card{background:var(--bw-color-bg);border:1px solid var(--bw-color-border);border-radius:var(--bw-radius-lg);transition:border-color var(--bw-transition);padding:16px}.card-selectable{cursor:pointer;text-align:left}.card-selectable:hover{border-color:var(--bw-color-text)}.card-selected{border-color:var(--bw-color-text);border-width:2px}.action-bar{border-top:1px solid var(--bw-color-border);background:var(--bw-color-bg);z-index:10;flex-shrink:0;gap:10px;padding:12px 16px;display:flex;position:sticky;bottom:0}.action-bar .btn-secondary{border:1px solid var(--bw-color-border);background:var(--bw-color-bg);height:48px;padding:0 20px}.action-bar .btn-primary{flex:1;height:48px;font-size:16px;font-weight:700;position:relative}.action-bar .btn-primary .arrow{font-size:20px;position:absolute;right:16px}.btn{border-radius:var(--bw-radius-md);transition:background var(--bw-transition), box-shadow var(--bw-transition), opacity var(--bw-transition);border:none;justify-content:center;align-items:center;gap:8px;padding:10px 20px;font-size:14px;font-weight:600;display:inline-flex}.btn:disabled{opacity:.45;cursor:default;pointer-events:none}.btn-primary{background:var(--bw-color-primary);color:#fff}.btn-primary:hover:not(:disabled){background:var(--bw-color-primary-dark);box-shadow:var(--bw-shadow-1)}.btn-secondary{background:var(--bw-color-bg);color:var(--bw-color-text)}.btn-secondary:hover:not(:disabled){background:var(--bw-color-border)}.btn-outline{border:1px solid var(--bw-color-primary);color:var(--bw-color-primary);background:0 0}.btn-outline:hover:not(:disabled){background:var(--bw-color-primary-light)}.btn-split{justify-content:space-between;padding:0 20px}.input{border:1px solid var(--bw-color-border);border-radius:var(--bw-radius-md);width:100%;transition:border-color var(--bw-transition), box-shadow var(--bw-transition);outline:none;padding:10px 12px;font-size:14px}.input:focus{border-color:var(--bw-color-primary);box-shadow:0 0 0 3px var(--bw-color-primary-light)}.input-error{border-color:var(--bw-color-error);background:#fef2f2}.input-error:focus{box-shadow:0 0 0 3px #e306131f}.section-title{color:var(--bw-color-text-secondary);margin-bottom:12px;font-size:14px;font-weight:400}.layout.svelte-idp8p9{align-items:center;gap:12px;display:flex}.text.svelte-idp8p9{flex:1}.heading.svelte-idp8p9{margin-bottom:4px;font-size:15px;font-weight:700}.body.svelte-idp8p9{color:var(--bw-color-text-secondary);font-size:13px;line-height:1.5}.radio.svelte-idp8p9{border:2px solid var(--bw-color-border);border-radius:50%;flex-shrink:0;width:22px;height:22px}.radio-selected.svelte-idp8p9{border-color:var(--bw-color-text);background:var(--bw-color-text)}.pricing.svelte-19wqhng{margin-top:8px;font-size:13px;font-weight:700}.row.svelte-bzhcxd{align-items:center;display:flex}.info.svelte-bzhcxd{flex-direction:column;flex:1;gap:2px;display:flex}.name.svelte-bzhcxd{font-size:15px;font-weight:700}.price.svelte-bzhcxd{color:var(--bw-color-text-secondary);font-size:13px}.stepper.svelte-bzhcxd{align-items:center;gap:4px;display:flex}.step-btn.svelte-bzhcxd{border:1.5px solid var(--bw-color-border);width:44px;height:44px;color:var(--bw-color-primary);transition:background var(--bw-transition), color var(--bw-transition), border-color var(--bw-transition);background:0 0;border-radius:50%;justify-content:center;align-items:center;font-size:20px;display:flex}.step-btn.svelte-bzhcxd:hover:not(:disabled){background:var(--bw-color-primary);color:#fff}.step-btn.svelte-bzhcxd:disabled{opacity:.3;cursor:default}.qty.svelte-bzhcxd{border:1.5px solid var(--bw-color-border);width:64px;height:44px;color:var(--bw-color-text);text-align:center;font-variant-numeric:tabular-nums;background:0 0;border-radius:12px;font-family:inherit;font-size:17px;font-weight:800}.qty.svelte-bzhcxd:focus{border-color:var(--bw-color-primary);outline:none}.limit-message.svelte-bzhcxd{color:var(--bw-color-error);margin:4px 0 0;font-size:13px}.calendar.svelte-10gu2tq{width:100%}.header.svelte-10gu2tq{justify-content:space-between;align-items:center;margin-bottom:16px;display:flex}.month.svelte-10gu2tq{font-size:16px;font-weight:700}.nav-btn.svelte-10gu2tq{border:1.5px solid var(--bw-color-border);width:36px;height:36px;color:var(--bw-color-text);transition:border-color var(--bw-transition);background:0 0;border-radius:50%;justify-content:center;align-items:center;font-size:20px;display:flex}.nav-btn.svelte-10gu2tq:hover{border-color:var(--bw-color-text)}.day-names.svelte-10gu2tq{text-align:center;grid-template-columns:repeat(7,1fr);margin-bottom:8px;display:grid}.day-names.svelte-10gu2tq span:where(.svelte-10gu2tq){color:var(--bw-color-text-secondary);padding:4px 0;font-size:11px;font-weight:700}.grid.svelte-10gu2tq{grid-template-columns:repeat(7,1fr);gap:6px;display:grid}.cell.svelte-10gu2tq{border-radius:var(--bw-radius-md);min-height:48px;transition:border-color var(--bw-transition), background var(--bw-transition);background:0 0;border:1.5px solid #0000;flex-direction:column;justify-content:center;align-items:center;padding:4px 2px;font-size:14px;display:flex}.cell.empty.svelte-10gu2tq{pointer-events:none}.cell.available.svelte-10gu2tq{color:var(--bw-color-text);border-color:var(--bw-color-border);font-weight:600}.cell.available.svelte-10gu2tq:hover{border-color:var(--bw-color-text)}.cell.selected.svelte-10gu2tq{border-color:var(--bw-color-text);border-width:2px;font-weight:700}.cell.sold-out.svelte-10gu2tq{color:var(--bw-color-text-secondary);cursor:default;text-decoration:line-through}.cell.unavailable.svelte-10gu2tq{color:#d1d5db;cursor:default}.day-num.svelte-10gu2tq{line-height:1}.day-price.svelte-10gu2tq{color:var(--bw-color-text-secondary);margin-top:2px;font-size:9px;font-weight:600;line-height:1}.selected.svelte-10gu2tq .day-price:where(.svelte-10gu2tq){color:var(--bw-color-text)}.hint.svelte-1hu99a4{text-align:center;color:var(--bw-color-text-secondary);padding:12px;font-size:14px}.slots.svelte-1hu99a4{flex-direction:column;gap:10px;display:flex}.wizard.svelte-9w5z1v{flex-direction:column;height:100%;max-height:100%;display:flex;overflow:hidden}.wizard-header.svelte-9w5z1v{border-bottom:1px solid var(--bw-color-border);padding:20px 16px 16px}.wizard-header.svelte-9w5z1v h2:where(.svelte-9w5z1v){font-size:20px;font-weight:800}.page-title-row.svelte-9w5z1v{justify-content:space-between;align-items:center;padding:12px 16px 0;display:flex}.page-title.svelte-9w5z1v{color:var(--bw-color-text);font-size:16px;font-weight:700}.page-dots.svelte-9w5z1v{gap:6px;display:flex}.dot.svelte-9w5z1v{background:var(--bw-color-border);border-radius:50%;width:8px;height:8px}.dot.active.svelte-9w5z1v{background:var(--bw-color-text)}.accordion-section.svelte-9w5z1v{border:1px solid var(--bw-color-border);border-radius:var(--bw-radius-lg);background:var(--bw-color-bg);margin-bottom:12px;overflow:hidden}.accordion-section.invalidated.svelte-9w5z1v{border-color:var(--bw-color-error)}.accordion-header.svelte-9w5z1v{cursor:pointer;text-align:left;background:0 0;border:none;justify-content:space-between;align-items:center;width:100%;padding:16px 20px;font-family:inherit;display:flex}.accordion-header-text.svelte-9w5z1v{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.accordion-title.svelte-9w5z1v{letter-spacing:.08em;color:var(--bw-color-text-secondary);font-size:11px;font-weight:700}.accordion-summary.svelte-9w5z1v{color:var(--bw-color-text);font-size:15px;font-weight:600}.accordion-summary.invalidated.svelte-9w5z1v{color:var(--bw-color-error)}.accordion-chevron.svelte-9w5z1v{border-right:2px solid var(--bw-color-text-secondary);border-bottom:2px solid var(--bw-color-text-secondary);flex-shrink:0;width:12px;height:12px;margin-left:12px;transition:transform .15s;transform:rotate(45deg)}.accordion-chevron.open.svelte-9w5z1v{transform:rotate(-135deg)}.accordion-body.svelte-9w5z1v{padding:0 20px 20px}.wizard-body.svelte-9w5z1v{flex:1;padding:16px;overflow-y:auto}.section.svelte-9w5z1v{margin-bottom:24px}.option-list.svelte-9w5z1v,.pickup-list.svelte-9w5z1v,.unit-list.svelte-9w5z1v{flex-direction:column;gap:10px;display:flex}.dialog-backdrop.svelte-9w5z1v{z-index:100;background:#00000059;justify-content:center;align-items:center;width:100%;height:100%;display:flex;position:fixed;top:0;left:0}.dialog.svelte-9w5z1v{border-radius:var(--bw-radius-lg);width:90%;max-width:360px;box-shadow:var(--bw-shadow-3);background:#fff;padding:24px}.dialog-title.svelte-9w5z1v{margin-bottom:8px;font-size:17px;font-weight:700}.dialog-body.svelte-9w5z1v{color:var(--bw-color-text-secondary);margin-bottom:24px;font-size:14px}.dialog-actions.svelte-9w5z1v{justify-content:flex-end;gap:8px;display:flex}.dialog-btn-text.svelte-9w5z1v{color:var(--bw-color-primary);border-radius:var(--bw-radius-md);background:0 0;border:none;padding:10px 16px;font-size:14px;font-weight:600}.dialog-btn-text.svelte-9w5z1v:hover{background:var(--bw-color-primary-light)}.dialog-btn-fill.svelte-9w5z1v{background:var(--bw-color-primary);color:#fff;border-radius:var(--bw-radius-md);border:none;padding:10px 20px;font-size:14px;font-weight:600}.dialog-btn-fill.svelte-9w5z1v:hover{background:var(--bw-color-primary-dark)}.bw-widget.svelte-19qd5my{display:contents}.add-to-cart-error.svelte-19qd5my{color:#b3261e;background:#fdecea;margin:0;padding:12px 16px;font-size:14px}.form.svelte-12xyhj1{flex-direction:column;gap:14px;display:flex}.field.svelte-12xyhj1{flex-direction:column;gap:4px;display:flex}label.svelte-12xyhj1{color:var(--bw-color-text-secondary);font-size:13px;font-weight:600}.consent.svelte-19pmjwi{flex-direction:column;gap:12px;display:flex}.check.svelte-19pmjwi{cursor:pointer;align-items:flex-start;gap:10px;font-size:13px;line-height:1.4;display:flex}.check.error.svelte-19pmjwi span:where(.svelte-19pmjwi){color:var(--bw-color-error)}input[type=checkbox].svelte-19pmjwi{accent-color:var(--bw-color-primary);flex-shrink:0;width:16px;height:16px;margin-top:2px}.timer.svelte-1u82ue3{color:var(--bw-color-text-secondary);font-variant-numeric:tabular-nums;font-size:13px;font-weight:600}.payment-overlay.svelte-1ubcp68{z-index:10001;background:#00000080;justify-content:center;align-items:center;padding:16px;display:flex;position:fixed;inset:0}.payment-dialog.svelte-1ubcp68{background:var(--bw-color-bg);border-radius:var(--bw-radius-lg);width:100%;max-width:480px;height:min(700px,90vh);box-shadow:var(--bw-shadow-3);flex-direction:column;display:flex;overflow:hidden}.body.svelte-1ubcp68{flex:1;min-height:0;padding:16px;overflow-y:auto}#peach-container.svelte-1ubcp68{height:100%}.error-state.svelte-1ubcp68{text-align:center;padding:32px 16px}.error-icon.svelte-1ubcp68{border:2px solid var(--bw-color-border);width:48px;height:48px;color:var(--bw-color-text-secondary);border-radius:50%;justify-content:center;align-items:center;margin:0 auto 16px;font-size:24px;display:flex}.error-title.svelte-1ubcp68{margin-bottom:8px;font-size:16px;font-weight:600}.error-msg.svelte-1ubcp68{color:var(--bw-color-text-secondary);font-size:14px}.error-state.svelte-1ubcp68 .btn:where(.svelte-1ubcp68){margin-top:16px}.result.svelte-16gfovn{text-align:center;flex-direction:column;justify-content:center;align-items:center;height:100%;padding:40px 24px;display:flex}.hint.svelte-16gfovn{color:var(--bw-color-text-secondary);margin-top:16px;font-size:14px}.icon.svelte-16gfovn{border-radius:50%;justify-content:center;align-items:center;width:64px;height:64px;margin-bottom:20px;font-size:32px;display:flex}.icon.success.svelte-16gfovn{border:2px solid var(--bw-color-success);color:var(--bw-color-success);background:#4caf5014}.icon.failure.svelte-16gfovn{border:2px solid var(--bw-color-error);color:var(--bw-color-error);background:var(--bw-color-primary-light)}.icon.pending.svelte-16gfovn{color:#b8860b;background:#b8860b14;border:2px solid #b8860b}h2.svelte-16gfovn{margin-bottom:8px;font-size:20px;font-weight:700}.desc.svelte-16gfovn{color:var(--bw-color-text-secondary);max-width:280px;margin-bottom:28px;font-size:14px;line-height:1.5}.actions.svelte-16gfovn{flex-direction:column;gap:8px;width:100%;max-width:220px;display:flex}.edit-view.svelte-15l4305{flex-direction:column;height:100%;max-height:100%;display:flex;overflow:hidden}.edit-error.svelte-15l4305{color:#b3261e;background:#fdecea;margin:0;padding:12px 16px;font-size:14px}.edit-body.svelte-15l4305{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.edit-body.saving.svelte-15l4305{pointer-events:none}.saving-overlay.svelte-15l4305{background:#ffffffb3;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.modal.svelte-i0rtg0{background:var(--bw-color-surface);flex-direction:column;height:100%;max-height:100%;display:flex;overflow:hidden}.body.svelte-i0rtg0{flex:1;padding:16px;overflow-y:auto}.keep-shopping-btn.svelte-i0rtg0{border-radius:var(--bw-radius-md);color:#222;cursor:pointer;background:0 0;border:2px solid #222;padding:8px 16px;font-size:13px;font-weight:600}.keep-shopping-btn.svelte-i0rtg0:hover{background:#f5f5f5}.cart-card.svelte-i0rtg0{border:1px solid var(--bw-color-border);border-radius:var(--bw-radius-lg);margin-bottom:16px;padding:20px}.cart-card-top.svelte-i0rtg0{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.cart-product.svelte-i0rtg0{flex:1;font-size:16px;font-weight:700}.cart-item-total.svelte-i0rtg0{white-space:nowrap;font-size:16px;font-weight:800}.cart-detail-lines.svelte-i0rtg0{margin-top:4px}.cart-detail.svelte-i0rtg0{color:var(--bw-color-text-secondary);font-size:13px;line-height:1.5}.cart-divider.svelte-i0rtg0{border-top:1px dashed var(--bw-color-border);margin:14px 0}.cart-units.svelte-i0rtg0{flex-direction:column;gap:6px;margin-bottom:14px;display:flex}.cart-unit-line.svelte-i0rtg0{justify-content:space-between;align-items:center;font-size:14px;display:flex}.cart-unit-label.svelte-i0rtg0{color:#333}.cart-unit-price.svelte-i0rtg0{font-variant-numeric:tabular-nums;font-weight:600}.cart-actions.svelte-i0rtg0{gap:16px;display:flex}.action-edit.svelte-i0rtg0{color:var(--bw-color-primary);cursor:pointer;background:0 0;border:none;padding:0;font-size:13px;font-weight:700}.action-edit.svelte-i0rtg0:hover{text-decoration:underline}.action-remove.svelte-i0rtg0{color:#555;cursor:pointer;background:0 0;border:none;padding:0;font-size:13px;font-weight:600}.action-remove.svelte-i0rtg0:hover{text-decoration:underline}.cart-total-divider.svelte-i0rtg0{border-top:3px solid #111;margin:8px 0 20px}.cart-total-row.svelte-i0rtg0{justify-content:space-between;align-items:center;margin-bottom:20px;font-size:16px;font-weight:700;display:flex}.cart-total-amount.svelte-i0rtg0{font-size:20px;font-weight:800}.pay-error.svelte-i0rtg0{color:#b3261e;border-radius:var(--bw-radius-md);background:#fdecea;margin:0 0 16px;padding:12px 16px;font-size:14px}.contact-header.svelte-i0rtg0{justify-content:space-between;align-items:center;margin-bottom:20px;display:flex}.contact-title.svelte-i0rtg0{font-size:16px;font-weight:700}.required-hint.svelte-i0rtg0{color:var(--bw-color-primary);font-size:12px}.pay-bar.svelte-i0rtg0{background:var(--bw-color-primary);color:#fff;flex-shrink:0;align-items:center;display:flex;position:sticky;bottom:0}.pay-bar-total.svelte-i0rtg0{padding:0 20px;font-size:16px;font-weight:700}.pay-bar-btn.svelte-i0rtg0{color:#fff;background:0 0;border:none;flex:1;justify-content:flex-end;align-items:center;gap:8px;height:52px;padding:0 20px;font-size:16px;font-weight:700;display:flex}.pay-bar-btn.svelte-i0rtg0:disabled{opacity:.7;cursor:default}.pay-bar-btn.svelte-i0rtg0 .arrow:where(.svelte-i0rtg0){font-size:20px}.expired.svelte-bznxud{text-align:center;flex-direction:column;justify-content:center;align-items:center;height:100%;padding:40px 24px;display:flex}.icon.svelte-bznxud{border:2px solid var(--bw-color-border);width:64px;height:64px;color:var(--bw-color-text-secondary);border-radius:50%;justify-content:center;align-items:center;margin-bottom:20px;font-size:32px;display:flex}h2.svelte-bznxud{margin-bottom:8px;font-size:20px;font-weight:700}.desc.svelte-bznxud{color:var(--bw-color-text-secondary);max-width:300px;margin-bottom:28px;font-size:14px;line-height:1.5}.bw-widget.svelte-5e515v{display:contents}.cart-bar.svelte-1xt0fxx{border-top:1px solid var(--bw-color-border);background:#fff;justify-content:space-between;align-items:center;width:100%;padding:12px 16px;display:flex;box-shadow:0 -2px 8px #0000000f}.cart-info.svelte-1xt0fxx{flex-direction:column;gap:2px;display:flex}.item-count.svelte-1xt0fxx{color:var(--bw-color-text-secondary);font-size:12px}.total.svelte-1xt0fxx{font-size:16px;font-weight:700}.cart-overview-btn.svelte-16bjfem{background:var(--bw-color-primary);color:#fff;cursor:pointer;border:none;border-radius:999px;justify-content:center;align-items:center;gap:14px;width:100%;padding:14px 24px;transition:background .15s;display:flex}.cart-overview-btn.svelte-16bjfem:hover{background:var(--bw-color-primary-dark)}.cart-icon.svelte-16bjfem{flex-shrink:0;width:22px;height:22px}.cart-badge.svelte-16bjfem{width:26px;height:26px;color:var(--bw-color-primary);background:#fff;border-radius:50%;flex-shrink:0;justify-content:center;align-items:center;font-size:14px;font-weight:800;display:flex}.cart-timer.svelte-16bjfem{font-variant-numeric:tabular-nums;flex-shrink:0;font-size:20px;font-weight:700}
1
+ :root{--bw-color-primary:var(--header-background,#e30613);--bw-color-primary-dark:var(--footer-background,#c00510);--bw-color-primary-light:#e3061314;--bw-color-text:#212121;--bw-color-text-secondary:#757575;--bw-color-border:#e0e0e0;--bw-color-bg:#fff;--bw-color-surface:#f5f5f5;--bw-color-success:#4caf50;--bw-color-error:var(--header-background,#e30613);--bw-font-family:var(--default-font-family,"Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);--bw-radius-sm:4px;--bw-radius-md:var(--radius,8px);--bw-radius-lg:12px;--bw-radius-pill:9999px;--bw-transition:.15s ease;--bw-shadow-1:0 1px 3px #0000001a, 0 1px 2px #0000000f;--bw-shadow-2:0 4px 12px #00000014, 0 2px 4px #0000000a;--bw-shadow-3:0 10px 24px #0000001a, 0 4px 8px #0000000a}.bw-widget,.bw-widget *{box-sizing:border-box;margin:0;padding:0}.bw-widget{font-family:var(--bw-font-family);color:var(--bw-color-text);background:var(--bw-color-bg);-webkit-font-smoothing:antialiased;font-size:14px;line-height:1.5}.bw-widget button,button.bw-widget{cursor:pointer;font-family:inherit}.bw-widget :is(input,select,textarea),:is(input,select,textarea).bw-widget{font-family:inherit}@keyframes spin{to{transform:rotate(360deg)}}.spinner{border:3px solid var(--bw-color-border);border-top-color:var(--bw-color-primary);border-radius:50%;width:36px;height:36px;animation:.7s linear infinite spin}.loading-center{flex:1;justify-content:center;align-items:center;min-height:120px;display:flex}.page-header{border-bottom:1px solid var(--bw-color-border);background:var(--bw-color-bg);align-items:center;gap:8px;padding:12px 16px;display:flex}.page-header h2{letter-spacing:-.01em;font-size:18px;font-weight:700}.page-header .spacer{flex:1}.icon-btn{border-radius:var(--bw-radius-pill);width:36px;height:36px;color:var(--bw-color-text);transition:background var(--bw-transition);background:0 0;border:none;justify-content:center;align-items:center;font-size:20px;display:flex}.icon-btn:hover{background:var(--bw-color-surface)}.card{background:var(--bw-color-bg);border:1px solid var(--bw-color-border);border-radius:var(--bw-radius-lg);transition:border-color var(--bw-transition);padding:16px}.card-selectable{cursor:pointer;text-align:left}.card-selectable:hover{border-color:var(--bw-color-text)}.card-selected{border-color:var(--bw-color-text);border-width:2px}.action-bar{border-top:1px solid var(--bw-color-border);background:var(--bw-color-bg);z-index:10;flex-shrink:0;gap:10px;padding:12px 16px;display:flex;position:sticky;bottom:0}.action-bar .btn-secondary{border:1px solid var(--bw-color-border);background:var(--bw-color-bg);height:48px;padding:0 20px}.action-bar .btn-primary{flex:1;height:48px;font-size:16px;font-weight:700}.btn{border-radius:var(--bw-radius-md);transition:background var(--bw-transition), box-shadow var(--bw-transition), opacity var(--bw-transition);border:none;justify-content:center;align-items:center;gap:8px;padding:10px 20px;font-size:14px;font-weight:600;display:inline-flex}.btn:disabled{opacity:.45;cursor:default;pointer-events:none}.btn-primary{background:var(--bw-color-primary);color:#fff}.btn-primary:hover:not(:disabled){background:var(--bw-color-primary-dark);box-shadow:var(--bw-shadow-1)}.btn-secondary{background:var(--bw-color-bg);color:var(--bw-color-text)}.btn-secondary:hover:not(:disabled){background:var(--bw-color-border)}.btn-outline{border:1px solid var(--bw-color-primary);color:var(--bw-color-primary);background:0 0}.btn-outline:hover:not(:disabled){background:var(--bw-color-primary-light)}.btn-split{justify-content:space-between;padding:0 20px}.input{border:1px solid var(--bw-color-border);border-radius:var(--bw-radius-md);width:100%;transition:border-color var(--bw-transition), box-shadow var(--bw-transition);outline:none;padding:10px 12px;font-size:14px}.input:focus{border-color:var(--bw-color-primary);box-shadow:0 0 0 3px var(--bw-color-primary-light)}.input-error{border-color:var(--bw-color-error);background:#fef2f2}.input-error:focus{box-shadow:0 0 0 3px #e306131f}.section-title{color:var(--bw-color-text-secondary);margin-bottom:12px;font-size:14px;font-weight:400}.prompt-card.svelte-udumw1{background:var(--bw-color-bg);border-radius:var(--bw-radius-lg);width:100%;max-width:360px;box-shadow:var(--bw-shadow-3);text-align:center;padding:28px 24px}h3.svelte-udumw1{margin-bottom:10px;font-size:18px;font-weight:700}p.svelte-udumw1{color:var(--bw-color-text-secondary);margin-bottom:20px;font-size:14px;line-height:1.5}.remaining.svelte-udumw1{color:var(--bw-color-text);font-variant-numeric:tabular-nums}.btn.svelte-udumw1{width:100%}.expired.svelte-bznxud{text-align:center;flex-direction:column;justify-content:center;align-items:center;height:100%;padding:40px 24px;display:flex}.icon.svelte-bznxud{border:2px solid var(--bw-color-border);width:64px;height:64px;color:var(--bw-color-text-secondary);border-radius:50%;justify-content:center;align-items:center;margin-bottom:20px;font-size:32px;display:flex}h2.svelte-bznxud{margin-bottom:8px;font-size:20px;font-weight:700}.desc.svelte-bznxud{color:var(--bw-color-text-secondary);max-width:300px;margin-bottom:28px;font-size:14px;line-height:1.5}.guard-overlay.svelte-x4iaex{z-index:20000;background:#00000073;justify-content:center;align-items:center;padding:16px;display:flex;position:fixed;inset:0}.guard-card.svelte-x4iaex{background:var(--bw-color-bg);border-radius:var(--bw-radius-lg);width:100%;max-width:360px;box-shadow:var(--bw-shadow-3);overflow:hidden}.layout.svelte-idp8p9{align-items:center;gap:12px;display:flex}.text.svelte-idp8p9{flex:1}.heading.svelte-idp8p9{margin-bottom:4px;font-size:15px;font-weight:700}.body.svelte-idp8p9{color:var(--bw-color-text-secondary);font-size:13px;line-height:1.5}.radio.svelte-idp8p9{border:2px solid var(--bw-color-border);border-radius:50%;flex-shrink:0;width:22px;height:22px}.radio-selected.svelte-idp8p9{border-color:var(--bw-color-text);background:var(--bw-color-text)}.pricing.svelte-19wqhng{margin-top:8px;font-size:13px;font-weight:700}.row.svelte-bzhcxd{align-items:center;display:flex}.plain.svelte-bzhcxd{padding:12px 0}.divided.svelte-bzhcxd:not(:first-child){border-top:1px solid var(--bw-color-border)}.info.svelte-bzhcxd{flex-direction:column;flex:1;gap:2px;display:flex}.name.svelte-bzhcxd{font-size:15px;font-weight:700}.price.svelte-bzhcxd{color:var(--bw-color-text-secondary);font-size:13px}.stepper.svelte-bzhcxd{align-items:center;gap:4px;display:flex}.step-btn.svelte-bzhcxd{border:1.5px solid var(--bw-color-border);width:44px;height:44px;color:var(--bw-color-primary);transition:background var(--bw-transition), color var(--bw-transition), border-color var(--bw-transition);background:0 0;border-radius:50%;justify-content:center;align-items:center;font-size:20px;display:flex}.step-btn.svelte-bzhcxd:hover:not(:disabled){background:var(--bw-color-primary);color:#fff}.step-btn.svelte-bzhcxd:disabled{opacity:.3;cursor:default}.qty.svelte-bzhcxd{border:1.5px solid var(--bw-color-border);width:64px;height:44px;color:var(--bw-color-text);text-align:center;font-variant-numeric:tabular-nums;background:0 0;border-radius:12px;font-family:inherit;font-size:17px;font-weight:800}.qty.svelte-bzhcxd:focus{border-color:var(--bw-color-primary);outline:none}.limit-message.svelte-bzhcxd{color:var(--bw-color-error);margin:4px 0 0;font-size:13px}.calendar.svelte-10gu2tq{width:100%}.header.svelte-10gu2tq{justify-content:space-between;align-items:center;margin-bottom:16px;display:flex}.month.svelte-10gu2tq{font-size:16px;font-weight:700}.nav-btn.svelte-10gu2tq{border:1.5px solid var(--bw-color-border);width:36px;height:36px;color:var(--bw-color-text);transition:border-color var(--bw-transition);background:0 0;border-radius:50%;justify-content:center;align-items:center;font-size:20px;display:flex}.nav-btn.svelte-10gu2tq:hover{border-color:var(--bw-color-text)}.day-names.svelte-10gu2tq{text-align:center;grid-template-columns:repeat(7,1fr);margin-bottom:8px;display:grid}.day-names.svelte-10gu2tq span:where(.svelte-10gu2tq){color:var(--bw-color-text-secondary);padding:4px 0;font-size:11px;font-weight:700}.grid.svelte-10gu2tq{grid-template-columns:repeat(7,1fr);gap:6px;display:grid}.cell.svelte-10gu2tq{border-radius:var(--bw-radius-md);min-height:48px;transition:border-color var(--bw-transition), background var(--bw-transition);background:0 0;border:1.5px solid #0000;flex-direction:column;justify-content:center;align-items:center;padding:4px 2px;font-size:14px;display:flex}.cell.empty.svelte-10gu2tq{pointer-events:none}.cell.available.svelte-10gu2tq{color:var(--bw-color-text);border-color:var(--bw-color-border);font-weight:600}.cell.available.svelte-10gu2tq:hover{border-color:var(--bw-color-text)}.cell.selected.svelte-10gu2tq{border-color:var(--bw-color-text);border-width:2px;font-weight:700}.cell.sold-out.svelte-10gu2tq{color:var(--bw-color-text-secondary);cursor:default;text-decoration:line-through}.cell.unavailable.svelte-10gu2tq{color:#d1d5db;cursor:default}.day-num.svelte-10gu2tq{line-height:1}.day-price.svelte-10gu2tq{color:var(--bw-color-text-secondary);margin-top:2px;font-size:9px;font-weight:600;line-height:1}.selected.svelte-10gu2tq .day-price:where(.svelte-10gu2tq){color:var(--bw-color-text)}.hint.svelte-1hu99a4{text-align:center;color:var(--bw-color-text-secondary);padding:12px;font-size:14px}.slots.svelte-1hu99a4{flex-direction:column;gap:10px;display:flex}.wizard.svelte-9w5z1v{flex-direction:column;height:100%;max-height:100%;display:flex;overflow:hidden}.wizard.inline.svelte-9w5z1v{height:auto;max-height:none}.wizard-header.svelte-9w5z1v{border-bottom:1px solid var(--bw-color-border);padding:20px 16px 16px}.wizard-header.svelte-9w5z1v h2:where(.svelte-9w5z1v){font-size:20px;font-weight:800}.body-title.svelte-9w5z1v{margin-bottom:16px;font-size:20px;font-weight:800}.page-title.svelte-9w5z1v{color:var(--bw-color-text);margin-bottom:4px;font-size:16px;font-weight:700}.wizard-footer.svelte-9w5z1v{z-index:10;background:var(--bw-color-bg);border-top:1px solid var(--bw-color-border);flex-shrink:0;position:sticky;bottom:0}.wizard-footer.svelte-9w5z1v .action-bar:where(.svelte-9w5z1v){border-top:none;position:static}.page-dots.svelte-9w5z1v{justify-content:center;gap:6px;padding:14px 16px 0;display:flex}.wizard.inline.svelte-9w5z1v .wizard-footer:where(.svelte-9w5z1v){background:var(--bw-color-surface)}.wizard.inline.svelte-9w5z1v .wizard-footer:where(.svelte-9w5z1v) .action-bar:where(.svelte-9w5z1v){background:0 0}.dot.svelte-9w5z1v{background:var(--bw-color-border);border-radius:50%;width:8px;height:8px}.dot.active.svelte-9w5z1v{background:var(--bw-color-text)}.accordion-section.svelte-9w5z1v{border:1px solid var(--bw-color-border);border-radius:var(--bw-radius-lg);background:var(--bw-color-bg);margin-bottom:12px;overflow:hidden}.accordion-section.invalidated.svelte-9w5z1v{border-color:var(--bw-color-error)}.accordion-header.svelte-9w5z1v{cursor:pointer;text-align:left;background:0 0;border:none;justify-content:space-between;align-items:center;width:100%;padding:16px 20px;font-family:inherit;display:flex}.accordion-header-text.svelte-9w5z1v{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}.accordion-title.svelte-9w5z1v{letter-spacing:.08em;color:var(--bw-color-text-secondary);font-size:11px;font-weight:700}.accordion-summary.svelte-9w5z1v{color:var(--bw-color-text);font-size:15px;font-weight:600}.accordion-summary.invalidated.svelte-9w5z1v{color:var(--bw-color-error)}.accordion-chevron.svelte-9w5z1v{border-right:2px solid var(--bw-color-text-secondary);border-bottom:2px solid var(--bw-color-text-secondary);flex-shrink:0;width:12px;height:12px;margin-left:12px;transition:transform .15s;transform:rotate(45deg)}.accordion-chevron.open.svelte-9w5z1v{transform:rotate(-135deg)}.accordion-body.svelte-9w5z1v{padding:0 20px 20px}.wizard-body.svelte-9w5z1v{flex:1;padding:16px;overflow-y:auto}.section.svelte-9w5z1v{margin-bottom:24px}.option-list.svelte-9w5z1v,.pickup-list.svelte-9w5z1v,.unit-list.svelte-9w5z1v{flex-direction:column;gap:10px;display:flex}.unit-list.divided.svelte-9w5z1v{gap:0}.dialog-backdrop.svelte-9w5z1v{z-index:100;background:#00000059;justify-content:center;align-items:center;width:100%;height:100%;display:flex;position:fixed;top:0;left:0}.dialog.svelte-9w5z1v{border-radius:var(--bw-radius-lg);width:90%;max-width:360px;box-shadow:var(--bw-shadow-3);background:#fff;padding:24px}.dialog-title.svelte-9w5z1v{margin-bottom:8px;font-size:17px;font-weight:700}.dialog-body.svelte-9w5z1v{color:var(--bw-color-text-secondary);margin-bottom:24px;font-size:14px}.dialog-actions.svelte-9w5z1v{justify-content:flex-end;gap:8px;display:flex}.dialog-btn-text.svelte-9w5z1v{color:var(--bw-color-primary);border-radius:var(--bw-radius-md);background:0 0;border:none;padding:10px 16px;font-size:14px;font-weight:600}.dialog-btn-text.svelte-9w5z1v:hover{background:var(--bw-color-primary-light)}.dialog-btn-fill.svelte-9w5z1v{background:var(--bw-color-primary);color:#fff;border-radius:var(--bw-radius-md);border:none;padding:10px 20px;font-size:14px;font-weight:600}.dialog-btn-fill.svelte-9w5z1v:hover{background:var(--bw-color-primary-dark)}.bw-widget.svelte-19qd5my{display:contents}.add-to-cart-error.svelte-19qd5my{color:#b3261e;background:#fdecea;margin:0;padding:12px 16px;font-size:14px}.form.svelte-12xyhj1{flex-direction:column;gap:14px;display:flex}.field.svelte-12xyhj1{flex-direction:column;gap:4px;display:flex}label.svelte-12xyhj1{color:var(--bw-color-text-secondary);font-size:13px;font-weight:600}.consent.svelte-19pmjwi{flex-direction:column;gap:12px;display:flex}.check.svelte-19pmjwi{cursor:pointer;align-items:flex-start;gap:10px;font-size:13px;line-height:1.4;display:flex}.check.error.svelte-19pmjwi span:where(.svelte-19pmjwi){color:var(--bw-color-error)}input[type=checkbox].svelte-19pmjwi{accent-color:var(--bw-color-primary);flex-shrink:0;width:16px;height:16px;margin-top:2px}.timer.svelte-1u82ue3{color:var(--bw-color-text-secondary);font-variant-numeric:tabular-nums;font-size:13px;font-weight:600}.payment-overlay.svelte-1ubcp68{z-index:10001;background:#00000080;justify-content:center;align-items:center;padding:16px;display:flex;position:fixed;inset:0}.payment-dialog.svelte-1ubcp68{background:var(--bw-color-bg);border-radius:var(--bw-radius-lg);width:100%;max-width:480px;height:min(700px,90vh);box-shadow:var(--bw-shadow-3);flex-direction:column;display:flex;overflow:hidden}.body.svelte-1ubcp68{flex:1;min-height:0;padding:16px;overflow-y:auto}#peach-container.svelte-1ubcp68{height:100%}.error-state.svelte-1ubcp68{text-align:center;padding:32px 16px}.error-icon.svelte-1ubcp68{border:2px solid var(--bw-color-border);width:48px;height:48px;color:var(--bw-color-text-secondary);border-radius:50%;justify-content:center;align-items:center;margin:0 auto 16px;font-size:24px;display:flex}.error-title.svelte-1ubcp68{margin-bottom:8px;font-size:16px;font-weight:600}.error-msg.svelte-1ubcp68{color:var(--bw-color-text-secondary);font-size:14px}.error-state.svelte-1ubcp68 .btn:where(.svelte-1ubcp68){margin-top:16px}.result.svelte-16gfovn{text-align:center;flex-direction:column;justify-content:center;align-items:center;height:100%;padding:40px 24px;display:flex}.hint.svelte-16gfovn{color:var(--bw-color-text-secondary);margin-top:16px;font-size:14px}.icon.svelte-16gfovn{border-radius:50%;justify-content:center;align-items:center;width:64px;height:64px;margin-bottom:20px;font-size:32px;display:flex}.icon.success.svelte-16gfovn{border:2px solid var(--bw-color-success);color:var(--bw-color-success);background:#4caf5014}.icon.failure.svelte-16gfovn{border:2px solid var(--bw-color-error);color:var(--bw-color-error);background:var(--bw-color-primary-light)}.icon.pending.svelte-16gfovn,.icon.partial.svelte-16gfovn{color:#b8860b;background:#b8860b14;border:2px solid #b8860b}h2.svelte-16gfovn{margin-bottom:8px;font-size:20px;font-weight:700}.desc.svelte-16gfovn{color:var(--bw-color-text-secondary);max-width:280px;margin-bottom:28px;font-size:14px;line-height:1.5}.actions.svelte-16gfovn{flex-direction:column;gap:8px;width:100%;max-width:220px;display:flex}.edit-view.svelte-15l4305{flex-direction:column;height:100%;max-height:100%;display:flex;overflow:hidden}.edit-error.svelte-15l4305{color:#b3261e;background:#fdecea;margin:0;padding:12px 16px;font-size:14px}.edit-body.svelte-15l4305{flex-direction:column;flex:1;min-height:0;display:flex;position:relative}.edit-body.saving.svelte-15l4305{pointer-events:none}.saving-overlay.svelte-15l4305{background:#ffffffb3;justify-content:center;align-items:center;display:flex;position:absolute;inset:0}.modal.svelte-i0rtg0{background:var(--bw-color-surface);flex-direction:column;height:100%;max-height:100%;display:flex;overflow:hidden}.body.svelte-i0rtg0{flex:1;padding:16px;overflow-y:auto}.keep-shopping-btn.svelte-i0rtg0{border-radius:var(--bw-radius-md);color:#222;cursor:pointer;background:0 0;border:2px solid #222;padding:8px 16px;font-size:13px;font-weight:600}.keep-shopping-btn.svelte-i0rtg0:hover{background:#f5f5f5}.cart-card.svelte-i0rtg0{border:1px solid var(--bw-color-border);border-radius:var(--bw-radius-lg);margin-bottom:16px;padding:20px}.cart-card-top.svelte-i0rtg0{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.cart-product.svelte-i0rtg0{flex:1;font-size:16px;font-weight:700}.cart-item-total.svelte-i0rtg0{white-space:nowrap;font-size:16px;font-weight:800}.cart-detail-lines.svelte-i0rtg0{margin-top:4px}.cart-detail.svelte-i0rtg0{color:var(--bw-color-text-secondary);font-size:13px;line-height:1.5}.cart-divider.svelte-i0rtg0{border-top:1px dashed var(--bw-color-border);margin:14px 0}.cart-units.svelte-i0rtg0{flex-direction:column;gap:6px;margin-bottom:14px;display:flex}.cart-unit-line.svelte-i0rtg0{justify-content:space-between;align-items:center;font-size:14px;display:flex}.cart-unit-label.svelte-i0rtg0{color:#333}.cart-unit-price.svelte-i0rtg0{font-variant-numeric:tabular-nums;font-weight:600}.cart-actions.svelte-i0rtg0{gap:16px;display:flex}.action-edit.svelte-i0rtg0{color:var(--bw-color-primary);cursor:pointer;background:0 0;border:none;padding:0;font-size:13px;font-weight:700}.action-edit.svelte-i0rtg0:hover{text-decoration:underline}.action-remove.svelte-i0rtg0{color:#555;cursor:pointer;background:0 0;border:none;padding:0;font-size:13px;font-weight:600}.action-remove.svelte-i0rtg0:hover{text-decoration:underline}.cart-total-divider.svelte-i0rtg0{border-top:3px solid #111;margin:8px 0 20px}.cart-total-row.svelte-i0rtg0{justify-content:space-between;align-items:center;margin-bottom:20px;font-size:16px;font-weight:700;display:flex}.cart-total-amount.svelte-i0rtg0{font-size:20px;font-weight:800}.pay-error.svelte-i0rtg0{color:#b3261e;border-radius:var(--bw-radius-md);background:#fdecea;margin:0 0 16px;padding:12px 16px;font-size:14px}.contact-header.svelte-i0rtg0{justify-content:space-between;align-items:center;margin-bottom:20px;display:flex}.contact-title.svelte-i0rtg0{font-size:16px;font-weight:700}.required-hint.svelte-i0rtg0{color:var(--bw-color-primary);font-size:12px}.pay-bar.svelte-i0rtg0{background:var(--bw-color-primary);color:#fff;flex-shrink:0;align-items:center;display:flex;position:sticky;bottom:0}.pay-bar-total.svelte-i0rtg0{padding:0 20px;font-size:16px;font-weight:700}.pay-bar-btn.svelte-i0rtg0{color:#fff;background:0 0;border:none;flex:1;justify-content:flex-end;align-items:center;gap:8px;height:52px;padding:0 20px;font-size:16px;font-weight:700;display:flex}.pay-bar-btn.svelte-i0rtg0:disabled{opacity:.7;cursor:default}.bw-widget.svelte-16hmqxh{display:contents}.bw-modal-overlay.svelte-5e515v{z-index:10000;background:#00000080;position:fixed;inset:0}.bw-modal-inner.svelte-5e515v{background:#fff;border-radius:12px;width:100%;max-width:900px;height:95vh;margin:2.5vh auto;overflow:hidden;box-shadow:0 8px 40px #0000004d}.cart-bar.svelte-1xt0fxx{border-top:1px solid var(--bw-color-border);background:#fff;justify-content:space-between;align-items:center;width:100%;padding:12px 16px;display:flex;box-shadow:0 -2px 8px #0000000f}.cart-info.svelte-1xt0fxx{flex-direction:column;gap:2px;display:flex}.item-count.svelte-1xt0fxx{color:var(--bw-color-text-secondary);font-size:12px}.error-detail.svelte-1xt0fxx{font-size:14px;font-weight:600}.total.svelte-1xt0fxx{font-size:16px;font-weight:700}.cart-overview-btn.svelte-16bjfem{background:var(--bw-color-primary);color:#fff;cursor:pointer;border:none;border-radius:999px;justify-content:center;align-items:center;gap:14px;width:100%;padding:14px 24px;transition:background .15s;display:flex}.cart-overview-btn.svelte-16bjfem:hover{background:var(--bw-color-primary-dark)}.cart-icon.svelte-16bjfem{flex-shrink:0;width:22px;height:22px}.cart-badge.svelte-16bjfem{width:26px;height:26px;color:var(--bw-color-primary);background:#fff;border-radius:50%;flex-shrink:0;justify-content:center;align-items:center;font-size:14px;font-weight:800;display:flex}.cart-timer.svelte-16bjfem{font-variant-numeric:tabular-nums;flex-shrink:0;font-size:20px;font-weight:700}.cart-error-label.svelte-16bjfem{font-size:15px;font-weight:600}
2
2
  /*$vite$:1*/