@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.
- package/CHANGELOG.md +48 -0
- package/README.md +153 -41
- package/dist/booking-widget.css +1 -1
- package/dist/booking-widget.js +2637 -1924
- package/dist/booking-widget.min.css +1 -1
- package/dist/booking-widget.min.js +34 -16
- package/dist/booking-widget.umd.cjs +5 -3
- package/package.json +5 -2
- package/src/lib/BookingProvider.svelte +21 -0
- package/src/lib/CartBar.svelte +16 -31
- package/src/lib/CartBarView.svelte +20 -3
- package/src/lib/CartExpiryGuard.svelte +416 -410
- package/src/lib/CartOverview.svelte +16 -6
- package/src/lib/CartOverviewButton.svelte +29 -26
- package/src/lib/Checkout.svelte +111 -98
- package/src/lib/CheckoutModal.svelte +946 -805
- package/src/lib/CheckoutPanel.svelte +121 -0
- package/src/lib/PaymentPage.svelte +16 -2
- package/src/lib/PickupPointPicker.svelte +1 -1
- package/src/lib/ResultView.svelte +24 -4
- package/src/lib/TicketConfigurator.svelte +19 -5
- package/src/lib/UnitCounter.svelte +16 -2
- package/src/lib/WizardPage.svelte +102 -35
- package/src/lib/app.css +0 -6
- package/src/lib/booking-context.ts +33 -0
- package/src/lib/cart-overview.svelte.ts +97 -0
- package/src/lib/client-types.ts +10 -6
- package/src/lib/config.ts +9 -0
- package/src/lib/elements/bw-cart.svelte +21 -7
- package/src/lib/elements/bw-checkout.svelte +54 -54
- package/src/lib/elements/bw-configurator.svelte +30 -14
- package/src/lib/elements/register.ts +96 -121
- package/src/lib/elements/shared.ts +4 -0
- package/src/lib/elements/theme.css +0 -6
- package/src/lib/host.svelte.ts +336 -0
- package/src/lib/index.ts +137 -91
- package/src/lib/layout.svelte.ts +52 -0
- package/src/lib/messages.ts +97 -17
- package/src/lib/peach-sdk.ts +51 -5
- package/src/lib/portal.ts +23 -0
- package/src/lib/CartExpiryGuard.test.ts +0 -331
- package/src/lib/CheckoutModal.confirm-outcome.test.ts +0 -91
- package/src/lib/CheckoutModal.payment-timeout.test.ts +0 -140
- package/src/lib/test/fixtures.ts +0 -107
- 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.
|
|
13
|
-
integrity="sha384-
|
|
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.
|
|
24
|
-
integrity="sha384-
|
|
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.
|
|
80
|
-
integrity="sha384-
|
|
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
|
|
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
|
-
|
|
|
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-
|
|
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
|
|
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
|
|
144
|
+
import { createBookingHost, mountConfigurator, mountCheckout } from '@code-collective/booking-widget';
|
|
146
145
|
import '@code-collective/booking-widget/style.css';
|
|
147
146
|
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
|
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
|
-
|
|
|
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
|
-
|
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
|
|
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.
|
|
249
|
-
integrity="sha384-
|
|
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.
|
|
253
|
-
integrity="sha384-
|
|
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
|
|
package/dist/booking-widget.css
CHANGED
|
@@ -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*/
|