@bigz-app/booking-widget 1.4.3 → 1.4.5

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 (42) hide show
  1. package/dist/booking-widget.js +17305 -17065
  2. package/dist/booking-widget.js.map +1 -1
  3. package/dist/components/Sidebar.d.ts.map +1 -1
  4. package/dist/components/UniversalBookingWidget.d.ts +12 -4
  5. package/dist/components/UniversalBookingWidget.d.ts.map +1 -1
  6. package/dist/components/booking/BookingForm.d.ts +1 -3
  7. package/dist/components/booking/BookingForm.d.ts.map +1 -1
  8. package/dist/components/booking/GiftCardOnlyBooking.d.ts.map +1 -1
  9. package/dist/components/booking/HoldCountdown.d.ts +10 -0
  10. package/dist/components/booking/HoldCountdown.d.ts.map +1 -0
  11. package/dist/components/booking/MolliePaymentForm.d.ts.map +1 -1
  12. package/dist/components/booking/StripePaymentForm.d.ts +1 -2
  13. package/dist/components/booking/StripePaymentForm.d.ts.map +1 -1
  14. package/dist/components/events/EventInstanceSelection.d.ts +3 -1
  15. package/dist/components/events/EventInstanceSelection.d.ts.map +1 -1
  16. package/dist/components/events/EventTypeDetailsDialog.d.ts +0 -1
  17. package/dist/components/events/EventTypeDetailsDialog.d.ts.map +1 -1
  18. package/dist/components/events/EventTypeSelection.d.ts +1 -1
  19. package/dist/components/events/EventTypeSelection.d.ts.map +1 -1
  20. package/dist/components/events/NextEventsPreview.d.ts +3 -1
  21. package/dist/components/events/NextEventsPreview.d.ts.map +1 -1
  22. package/dist/components/events/WaitlistDialog.d.ts +10 -0
  23. package/dist/components/events/WaitlistDialog.d.ts.map +1 -0
  24. package/dist/components/shared/Button.d.ts.map +1 -1
  25. package/dist/components/shared/SectionHeader.d.ts.map +1 -1
  26. package/dist/components/voucher/VoucherPurchaseForm.d.ts +0 -1
  27. package/dist/components/voucher/VoucherPurchaseForm.d.ts.map +1 -1
  28. package/dist/components/voucher/useVoucherConfig.d.ts +0 -1
  29. package/dist/components/voucher/useVoucherConfig.d.ts.map +1 -1
  30. package/dist/i18n/locales/de.d.ts.map +1 -1
  31. package/dist/i18n/locales/en.d.ts.map +1 -1
  32. package/dist/index.cjs +620 -380
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.esm.js +607 -367
  35. package/dist/index.esm.js.map +1 -1
  36. package/dist/styles/StyleProvider.d.ts.map +1 -1
  37. package/dist/styles/shared-styles.d.ts.map +1 -1
  38. package/dist/utils/analytics.d.ts +3 -1
  39. package/dist/utils/analytics.d.ts.map +1 -1
  40. package/dist/utils.d.ts.map +1 -1
  41. package/dist/validation/booking-schema.d.ts +4 -4
  42. package/package.json +1 -1
package/dist/index.esm.js CHANGED
@@ -4,170 +4,6 @@ import { createRoot } from 'react-dom/client';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import ReactDOM, { createPortal } from 'react-dom';
6
6
 
7
- var V3_URL = 'https://js.stripe.com/v3';
8
- var V3_URL_REGEX = /^https:\/\/js\.stripe\.com\/v3\/?(\?.*)?$/;
9
- var EXISTING_SCRIPT_MESSAGE = 'loadStripe.setLoadParameters was called but an existing Stripe.js script already exists in the document; existing script parameters will be used';
10
- var findScript = function findScript() {
11
- var scripts = document.querySelectorAll("script[src^=\"".concat(V3_URL, "\"]"));
12
-
13
- for (var i = 0; i < scripts.length; i++) {
14
- var script = scripts[i];
15
-
16
- if (!V3_URL_REGEX.test(script.src)) {
17
- continue;
18
- }
19
-
20
- return script;
21
- }
22
-
23
- return null;
24
- };
25
-
26
- var injectScript = function injectScript(params) {
27
- var queryString = '';
28
- var script = document.createElement('script');
29
- script.src = "".concat(V3_URL).concat(queryString);
30
- var headOrBody = document.head || document.body;
31
-
32
- if (!headOrBody) {
33
- throw new Error('Expected document.body not to be null. Stripe.js requires a <body> element.');
34
- }
35
-
36
- headOrBody.appendChild(script);
37
- return script;
38
- };
39
-
40
- var registerWrapper = function registerWrapper(stripe, startTime) {
41
- if (!stripe || !stripe._registerWrapper) {
42
- return;
43
- }
44
-
45
- stripe._registerWrapper({
46
- name: 'stripe-js',
47
- version: "4.6.0",
48
- startTime: startTime
49
- });
50
- };
51
-
52
- var stripePromise = null;
53
- var onErrorListener = null;
54
- var onLoadListener = null;
55
-
56
- var onError = function onError(reject) {
57
- return function () {
58
- reject(new Error('Failed to load Stripe.js'));
59
- };
60
- };
61
-
62
- var onLoad = function onLoad(resolve, reject) {
63
- return function () {
64
- if (window.Stripe) {
65
- resolve(window.Stripe);
66
- } else {
67
- reject(new Error('Stripe.js not available'));
68
- }
69
- };
70
- };
71
-
72
- var loadScript = function loadScript(params) {
73
- // Ensure that we only attempt to load Stripe.js at most once
74
- if (stripePromise !== null) {
75
- return stripePromise;
76
- }
77
-
78
- stripePromise = new Promise(function (resolve, reject) {
79
- if (typeof window === 'undefined' || typeof document === 'undefined') {
80
- // Resolve to null when imported server side. This makes the module
81
- // safe to import in an isomorphic code base.
82
- resolve(null);
83
- return;
84
- }
85
-
86
- if (window.Stripe) {
87
- resolve(window.Stripe);
88
- return;
89
- }
90
-
91
- try {
92
- var script = findScript();
93
-
94
- if (script && params) ; else if (!script) {
95
- script = injectScript(params);
96
- } else if (script && onLoadListener !== null && onErrorListener !== null) {
97
- var _script$parentNode;
98
-
99
- // remove event listeners
100
- script.removeEventListener('load', onLoadListener);
101
- script.removeEventListener('error', onErrorListener); // if script exists, but we are reloading due to an error,
102
- // reload script to trigger 'load' event
103
-
104
- (_script$parentNode = script.parentNode) === null || _script$parentNode === void 0 ? void 0 : _script$parentNode.removeChild(script);
105
- script = injectScript(params);
106
- }
107
-
108
- onLoadListener = onLoad(resolve, reject);
109
- onErrorListener = onError(reject);
110
- script.addEventListener('load', onLoadListener);
111
- script.addEventListener('error', onErrorListener);
112
- } catch (error) {
113
- reject(error);
114
- return;
115
- }
116
- }); // Resets stripePromise on error
117
-
118
- return stripePromise["catch"](function (error) {
119
- stripePromise = null;
120
- return Promise.reject(error);
121
- });
122
- };
123
- var initStripe = function initStripe(maybeStripe, args, startTime) {
124
- if (maybeStripe === null) {
125
- return null;
126
- }
127
-
128
- var stripe = maybeStripe.apply(undefined, args);
129
- registerWrapper(stripe, startTime);
130
- return stripe;
131
- }; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
132
-
133
- var stripePromise$1;
134
- var loadCalled = false;
135
-
136
- var getStripePromise = function getStripePromise() {
137
- if (stripePromise$1) {
138
- return stripePromise$1;
139
- }
140
-
141
- stripePromise$1 = loadScript(null)["catch"](function (error) {
142
- // clear cache on error
143
- stripePromise$1 = null;
144
- return Promise.reject(error);
145
- });
146
- return stripePromise$1;
147
- }; // Execute our own script injection after a tick to give users time to do their
148
- // own script injection.
149
-
150
-
151
- Promise.resolve().then(function () {
152
- return getStripePromise();
153
- })["catch"](function (error) {
154
- if (!loadCalled) {
155
- console.warn(error);
156
- }
157
- });
158
- var loadStripe = function loadStripe() {
159
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
160
- args[_key] = arguments[_key];
161
- }
162
-
163
- loadCalled = true;
164
- var startTime = Date.now(); // if previous attempts are unsuccessful, will re-load script
165
-
166
- return getStripePromise().then(function (maybeStripe) {
167
- return initStripe(maybeStripe, args, startTime);
168
- });
169
- };
170
-
171
7
  const de$1 = {
172
8
  // Common
173
9
  "common.back": "← Zurück",
@@ -247,6 +83,24 @@ const de$1 = {
247
83
  "instances.bestPrice": "bester Preis !!!",
248
84
  "instances.goodPrice": "günstiger Preis",
249
85
  "instances.oclock": "Uhr",
86
+ // Warteliste
87
+ "waitlist.joinCta": "Auf die Warteliste",
88
+ "waitlist.title": "Auf die Warteliste setzen",
89
+ "waitlist.subtitle": "{{name}} ist ausgebucht. Hinterlassen Sie Ihre Daten und wir melden uns, falls ein Platz frei wird.",
90
+ "waitlist.name": "Name",
91
+ "waitlist.email": "E-Mail",
92
+ "waitlist.phone": "Telefon (optional)",
93
+ "waitlist.requiredFields": "Bitte geben Sie Ihren Namen und Ihre E-Mail-Adresse ein.",
94
+ "waitlist.join": "Eintragen",
95
+ "waitlist.submitting": "Wird eingetragen...",
96
+ "waitlist.cancel": "Abbrechen",
97
+ "waitlist.submitError": "Etwas ist schiefgelaufen. Bitte versuchen Sie es erneut.",
98
+ "waitlist.successTitle": "Sie sind auf der Liste!",
99
+ "waitlist.successMessage": "Sie sind Nummer {{position}} auf der Warteliste. Wir melden uns, sobald ein Platz frei wird.",
100
+ "waitlist.done": "Fertig",
101
+ // Reservierungs-Countdown
102
+ "hold.reservedFor": "Platz reserviert für {{time}}",
103
+ "hold.expired": "Ihre Reservierung ist abgelaufen",
250
104
  // Next events preview
251
105
  "nextEvents.title": "Nächste verfügbare Termine",
252
106
  "nextEvents.subtitle": "Wähle einen Termin aus oder zeige alle verfügbaren Termine an",
@@ -537,6 +391,24 @@ const en = {
537
391
  "instances.bestPrice": "best price !!!",
538
392
  "instances.goodPrice": "good price",
539
393
  "instances.oclock": "",
394
+ // Waitlist
395
+ "waitlist.joinCta": "Join waitlist",
396
+ "waitlist.title": "Join the waitlist",
397
+ "waitlist.subtitle": "{{name}} is fully booked. Leave your details and we'll contact you if a spot opens up.",
398
+ "waitlist.name": "Name",
399
+ "waitlist.email": "Email",
400
+ "waitlist.phone": "Phone (optional)",
401
+ "waitlist.requiredFields": "Please enter your name and email.",
402
+ "waitlist.join": "Join waitlist",
403
+ "waitlist.submitting": "Joining...",
404
+ "waitlist.cancel": "Cancel",
405
+ "waitlist.submitError": "Something went wrong. Please try again.",
406
+ "waitlist.successTitle": "You're on the list!",
407
+ "waitlist.successMessage": "You are number {{position}} on the waitlist. We'll be in touch if a spot becomes available.",
408
+ "waitlist.done": "Done",
409
+ // Slot hold countdown
410
+ "hold.reservedFor": "Slot reserved for {{time}}",
411
+ "hold.expired": "Your reservation has expired",
540
412
  // Next events preview
541
413
  "nextEvents.title": "Next available dates",
542
414
  "nextEvents.subtitle": "Select a date or view all available dates",
@@ -2045,6 +1917,28 @@ const StyleProvider = ({ config, children, }) => {
2045
1917
  c = `0x${c.join("")}`;
2046
1918
  return [(c >> 16) & 255, (c >> 8) & 255, c & 255].join(",");
2047
1919
  };
1920
+ // Pick a readable text color (dark or light) for content sitting on top of
1921
+ // the given background color. Bright/neon highlights (teal, matrix green,
1922
+ // gold, etc.) need dark text — white text on them is unreadable.
1923
+ const getContrastingTextColor = (color, lightColor = "#ffffff", darkColor = "#0e1420") => {
1924
+ let rgb;
1925
+ if (color.startsWith("#")) {
1926
+ rgb = hexToRgb(color);
1927
+ }
1928
+ else if (color.startsWith("rgb")) {
1929
+ rgb = color.replace(/rgba?\(|\)/g, "").split(",").slice(0, 3).join(",");
1930
+ }
1931
+ else {
1932
+ return lightColor; // Can't parse (e.g. hsl/oklch/semantic) — keep existing default
1933
+ }
1934
+ const [r, g, b] = rgb.split(",").map((n) => parseInt(n, 10) / 255);
1935
+ if ([r, g, b].some((n) => Number.isNaN(n)))
1936
+ return lightColor;
1937
+ // Relative luminance (sRGB, WCAG)
1938
+ const toLinear = (n) => (n <= 0.03928 ? n / 12.92 : Math.pow((n + 0.055) / 1.055, 2.4));
1939
+ const luminance = 0.2126 * toLinear(r) + 0.7152 * toLinear(g) + 0.0722 * toLinear(b);
1940
+ return luminance > 0.45 ? darkColor : lightColor;
1941
+ };
2048
1942
  const colors = config.colors || {};
2049
1943
  const finalColors = {
2050
1944
  highlight: getCSSValue(colors.highlight, themeDefaults.highlight),
@@ -2064,7 +1958,7 @@ const StyleProvider = ({ config, children, }) => {
2064
1958
  "--bw-surface-color": finalColors.surface,
2065
1959
  "--bw-text-color": finalColors.text,
2066
1960
  "--bw-text-muted": addOpacity(finalColors.text, 0.7),
2067
- "--bw-button-text-color": themeDefaults.buttonTextColor || "#ffffff",
1961
+ "--bw-button-text-color": themeDefaults.buttonTextColor || getContrastingTextColor(finalColors.highlight),
2068
1962
  "--bw-border-color": finalColors.border,
2069
1963
  "--bw-success-color": finalColors.success,
2070
1964
  "--bw-warning-color": finalColors.warning,
@@ -2082,6 +1976,9 @@ const StyleProvider = ({ config, children, }) => {
2082
1976
  "--bw-highlight-muted": addOpacity(finalColors.highlight, 0.1),
2083
1977
  "--bw-highlight-subtle": addOpacity(finalColors.highlight, 0.05),
2084
1978
  "--bw-text-subtle": addOpacity(finalColors.text, 0.4),
1979
+ // Some themes (neon-brutalism) call for uppercased titles & buttons
1980
+ "--bw-text-transform": themeName === "dark-neon-brutalism" ? "uppercase" : "none",
1981
+ "--bw-letter-spacing": themeName === "dark-neon-brutalism" ? "0.04em" : "normal",
2085
1982
  colorScheme: (["navy-night", "green-deep", "green-matrix", "gold-luxury", "purple-electric", "dark-neon-brutalism", "amber-retro", "orange-raw"].includes(themeName) || themeName.startsWith("dark-")) ? "dark" : "light",
2086
1983
  };
2087
1984
  }, [
@@ -2217,6 +2114,7 @@ const inferConfigFromUrl = (baseConfig) => {
2217
2114
  const categoryId = urlParams.get("categoryId") || urlParams.get("category");
2218
2115
  const eventTypeId = urlParams.get("eventTypeId") || urlParams.get("type");
2219
2116
  const eventTypeIds = urlParams.get("eventTypeIds") || urlParams.get("types");
2117
+ const partnerContractId = urlParams.get("partnerContractId") || urlParams.get("contractId");
2220
2118
  const voucherValue = urlParams.get("voucherValue");
2221
2119
  const voucherCategory = urlParams.get("voucherCategory");
2222
2120
  const voucherEventType = urlParams.get("voucherEventType");
@@ -2229,11 +2127,14 @@ const inferConfigFromUrl = (baseConfig) => {
2229
2127
  : ["0", "false", "no", "off"].includes(voucherCardIntegration.toLowerCase())
2230
2128
  ? false
2231
2129
  : undefined;
2232
- const omitSelectionKeys = ({ categoryId: _c, eventTypeId: _e, eventTypeIds: _es, eventInstanceId: _i, voucherValue: _vv, voucherCategory: _vc, voucherEventType: _ve, voucherImageId: _vi, ...rest }) => rest;
2130
+ const omitSelectionKeys = ({ categoryId: _c, eventTypeId: _e, eventTypeIds: _es, eventInstanceId: _i, voucherValue: _vv, voucherCategory: _vc, voucherEventType: _ve, voucherImageId: _vi, partnerContractId: _pc, ...rest }) => rest;
2233
2131
  if (eventInstanceId && !Number.isNaN(Number(eventInstanceId))) {
2234
2132
  return {
2235
2133
  ...omitSelectionKeys(baseConfig),
2236
2134
  eventInstanceId: Number(eventInstanceId),
2135
+ ...(partnerContractId
2136
+ ? { partnerContractId }
2137
+ : {}),
2237
2138
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2238
2139
  };
2239
2140
  }
@@ -2241,6 +2142,9 @@ const inferConfigFromUrl = (baseConfig) => {
2241
2142
  return {
2242
2143
  ...omitSelectionKeys(baseConfig),
2243
2144
  categoryId: Number(categoryId),
2145
+ ...(partnerContractId
2146
+ ? { partnerContractId }
2147
+ : {}),
2244
2148
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2245
2149
  };
2246
2150
  }
@@ -2248,6 +2152,9 @@ const inferConfigFromUrl = (baseConfig) => {
2248
2152
  return {
2249
2153
  ...omitSelectionKeys(baseConfig),
2250
2154
  eventTypeId: Number(eventTypeId),
2155
+ ...(partnerContractId
2156
+ ? { partnerContractId }
2157
+ : {}),
2251
2158
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2252
2159
  };
2253
2160
  }
@@ -2260,6 +2167,9 @@ const inferConfigFromUrl = (baseConfig) => {
2260
2167
  return {
2261
2168
  ...omitSelectionKeys(baseConfig),
2262
2169
  eventTypeIds: typeIds,
2170
+ ...(partnerContractId
2171
+ ? { partnerContractId }
2172
+ : {}),
2263
2173
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2264
2174
  };
2265
2175
  }
@@ -2276,6 +2186,9 @@ const inferConfigFromUrl = (baseConfig) => {
2276
2186
  if (parsedVoucherValues.length || parsedVoucherCategories.length || parsedVoucherEventTypes.length) {
2277
2187
  return {
2278
2188
  ...baseConfig,
2189
+ ...(partnerContractId
2190
+ ? { partnerContractId }
2191
+ : {}),
2279
2192
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2280
2193
  ...(parsedVoucherValues.length > 0
2281
2194
  ? { voucherValue: parsedVoucherValues.length === 1 ? parsedVoucherValues[0] : parsedVoucherValues }
@@ -2293,6 +2206,9 @@ const inferConfigFromUrl = (baseConfig) => {
2293
2206
  }
2294
2207
  return {
2295
2208
  ...baseConfig,
2209
+ ...(partnerContractId
2210
+ ? { partnerContractId }
2211
+ : {}),
2296
2212
  ...(parsedVoucherCardIntegration !== undefined ? { voucherIntegration: parsedVoucherCardIntegration } : {}),
2297
2213
  };
2298
2214
  };
@@ -6305,6 +6221,7 @@ function GiftCardOnlyBooking({ config, eventDetails, formData, discountCode, gif
6305
6221
  customerPhone: formData.customerPhone?.trim(),
6306
6222
  comment: formData.comment?.trim(),
6307
6223
  paymentMethod: "gift_card",
6224
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6308
6225
  ...(upsellSelections.length > 0 && { upsellSelections }),
6309
6226
  };
6310
6227
  const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/create-gift-card-booking"), {
@@ -6581,6 +6498,7 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6581
6498
  customerEmail: formData.customerEmail?.trim(),
6582
6499
  customerPhone: formData.customerPhone?.trim(),
6583
6500
  comment: formData.comment?.trim(),
6501
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6584
6502
  ...(cardToken && { cardToken }),
6585
6503
  ...(selectedMethod && !cardToken && { method: selectedMethod }),
6586
6504
  ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
@@ -6807,6 +6725,170 @@ function MolliePaymentForm({ config, eventDetails, formData, totalAmount, discou
6807
6725
  }, children: isLoading ? (jsxs(Fragment, { children: [spinner("var(--bw-surface-color)"), " ", buttonLabel] })) : (buttonLabel) }))] }));
6808
6726
  }
6809
6727
 
6728
+ var V3_URL = 'https://js.stripe.com/v3';
6729
+ var V3_URL_REGEX = /^https:\/\/js\.stripe\.com\/v3\/?(\?.*)?$/;
6730
+ var EXISTING_SCRIPT_MESSAGE = 'loadStripe.setLoadParameters was called but an existing Stripe.js script already exists in the document; existing script parameters will be used';
6731
+ var findScript = function findScript() {
6732
+ var scripts = document.querySelectorAll("script[src^=\"".concat(V3_URL, "\"]"));
6733
+
6734
+ for (var i = 0; i < scripts.length; i++) {
6735
+ var script = scripts[i];
6736
+
6737
+ if (!V3_URL_REGEX.test(script.src)) {
6738
+ continue;
6739
+ }
6740
+
6741
+ return script;
6742
+ }
6743
+
6744
+ return null;
6745
+ };
6746
+
6747
+ var injectScript = function injectScript(params) {
6748
+ var queryString = '';
6749
+ var script = document.createElement('script');
6750
+ script.src = "".concat(V3_URL).concat(queryString);
6751
+ var headOrBody = document.head || document.body;
6752
+
6753
+ if (!headOrBody) {
6754
+ throw new Error('Expected document.body not to be null. Stripe.js requires a <body> element.');
6755
+ }
6756
+
6757
+ headOrBody.appendChild(script);
6758
+ return script;
6759
+ };
6760
+
6761
+ var registerWrapper = function registerWrapper(stripe, startTime) {
6762
+ if (!stripe || !stripe._registerWrapper) {
6763
+ return;
6764
+ }
6765
+
6766
+ stripe._registerWrapper({
6767
+ name: 'stripe-js',
6768
+ version: "4.6.0",
6769
+ startTime: startTime
6770
+ });
6771
+ };
6772
+
6773
+ var stripePromise = null;
6774
+ var onErrorListener = null;
6775
+ var onLoadListener = null;
6776
+
6777
+ var onError = function onError(reject) {
6778
+ return function () {
6779
+ reject(new Error('Failed to load Stripe.js'));
6780
+ };
6781
+ };
6782
+
6783
+ var onLoad = function onLoad(resolve, reject) {
6784
+ return function () {
6785
+ if (window.Stripe) {
6786
+ resolve(window.Stripe);
6787
+ } else {
6788
+ reject(new Error('Stripe.js not available'));
6789
+ }
6790
+ };
6791
+ };
6792
+
6793
+ var loadScript = function loadScript(params) {
6794
+ // Ensure that we only attempt to load Stripe.js at most once
6795
+ if (stripePromise !== null) {
6796
+ return stripePromise;
6797
+ }
6798
+
6799
+ stripePromise = new Promise(function (resolve, reject) {
6800
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
6801
+ // Resolve to null when imported server side. This makes the module
6802
+ // safe to import in an isomorphic code base.
6803
+ resolve(null);
6804
+ return;
6805
+ }
6806
+
6807
+ if (window.Stripe) {
6808
+ resolve(window.Stripe);
6809
+ return;
6810
+ }
6811
+
6812
+ try {
6813
+ var script = findScript();
6814
+
6815
+ if (script && params) ; else if (!script) {
6816
+ script = injectScript(params);
6817
+ } else if (script && onLoadListener !== null && onErrorListener !== null) {
6818
+ var _script$parentNode;
6819
+
6820
+ // remove event listeners
6821
+ script.removeEventListener('load', onLoadListener);
6822
+ script.removeEventListener('error', onErrorListener); // if script exists, but we are reloading due to an error,
6823
+ // reload script to trigger 'load' event
6824
+
6825
+ (_script$parentNode = script.parentNode) === null || _script$parentNode === void 0 ? void 0 : _script$parentNode.removeChild(script);
6826
+ script = injectScript(params);
6827
+ }
6828
+
6829
+ onLoadListener = onLoad(resolve, reject);
6830
+ onErrorListener = onError(reject);
6831
+ script.addEventListener('load', onLoadListener);
6832
+ script.addEventListener('error', onErrorListener);
6833
+ } catch (error) {
6834
+ reject(error);
6835
+ return;
6836
+ }
6837
+ }); // Resets stripePromise on error
6838
+
6839
+ return stripePromise["catch"](function (error) {
6840
+ stripePromise = null;
6841
+ return Promise.reject(error);
6842
+ });
6843
+ };
6844
+ var initStripe = function initStripe(maybeStripe, args, startTime) {
6845
+ if (maybeStripe === null) {
6846
+ return null;
6847
+ }
6848
+
6849
+ var stripe = maybeStripe.apply(undefined, args);
6850
+ registerWrapper(stripe, startTime);
6851
+ return stripe;
6852
+ }; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
6853
+
6854
+ var stripePromise$1;
6855
+ var loadCalled = false;
6856
+
6857
+ var getStripePromise = function getStripePromise() {
6858
+ if (stripePromise$1) {
6859
+ return stripePromise$1;
6860
+ }
6861
+
6862
+ stripePromise$1 = loadScript(null)["catch"](function (error) {
6863
+ // clear cache on error
6864
+ stripePromise$1 = null;
6865
+ return Promise.reject(error);
6866
+ });
6867
+ return stripePromise$1;
6868
+ }; // Execute our own script injection after a tick to give users time to do their
6869
+ // own script injection.
6870
+
6871
+
6872
+ Promise.resolve().then(function () {
6873
+ return getStripePromise();
6874
+ })["catch"](function (error) {
6875
+ if (!loadCalled) {
6876
+ console.warn(error);
6877
+ }
6878
+ });
6879
+ var loadStripe = function loadStripe() {
6880
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
6881
+ args[_key] = arguments[_key];
6882
+ }
6883
+
6884
+ loadCalled = true;
6885
+ var startTime = Date.now(); // if previous attempts are unsuccessful, will re-load script
6886
+
6887
+ return getStripePromise().then(function (maybeStripe) {
6888
+ return initStripe(maybeStripe, args, startTime);
6889
+ });
6890
+ };
6891
+
6810
6892
  // Inner component that uses the Stripe hooks
6811
6893
  function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onError, }) {
6812
6894
  const t = useTranslations();
@@ -6835,13 +6917,25 @@ function PaymentFormInner({ eventDetails, formData, totalAmount, onSuccess, onEr
6835
6917
  },
6836
6918
  }, submitContent: jsx(Fragment, { children: submitLabel }), loadingContent: jsxs(Fragment, { children: [spinner("var(--bw-surface-color)"), " ", t("button.processingPayment")] }) }));
6837
6919
  }
6838
- function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripePromise, stripeAppearance, upsellSelections = [], }) {
6920
+ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discountCode, giftCards, onSuccess, onError, systemConfig, stripeAppearance, upsellSelections = [], }) {
6839
6921
  const t = useTranslations();
6840
6922
  const { locale } = useLocale();
6841
6923
  const [clientSecret, setClientSecret] = useState(null);
6842
6924
  const [paymentIntentId, setPaymentIntentId] = useState(null);
6843
6925
  const [isCreatingPaymentIntent, setIsCreatingPaymentIntent] = useState(false);
6844
6926
  const [paymentError, setPaymentError] = useState(null);
6927
+ const stripePromise = useMemo(() => {
6928
+ if (!systemConfig?.stripePublishableKey) {
6929
+ return null;
6930
+ }
6931
+ const stripeOptions = {
6932
+ locale: locale,
6933
+ };
6934
+ if (systemConfig.connectedAccountId) {
6935
+ stripeOptions.stripeAccount = systemConfig.connectedAccountId;
6936
+ }
6937
+ return loadStripe(systemConfig.stripePublishableKey, stripeOptions);
6938
+ }, [systemConfig?.stripePublishableKey, systemConfig?.connectedAccountId, locale]);
6845
6939
  const storageKey = typeof window !== "undefined"
6846
6940
  ? `bw_pi_${config?.organizationId}_${config?.eventInstanceId || eventDetails?.id}`
6847
6941
  : "";
@@ -6920,6 +7014,7 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
6920
7014
  customerEmail: formData.customerEmail?.trim(),
6921
7015
  customerPhone: formData.customerPhone?.trim(),
6922
7016
  comment: formData.comment?.trim(),
7017
+ ...(config.partnerContractId && { partnerContractId: config.partnerContractId }),
6923
7018
  ...(paymentIntentId && { paymentIntentId }),
6924
7019
  ...(upsellSelections && upsellSelections.length > 0 && { upsellSelections }),
6925
7020
  };
@@ -6989,7 +7084,7 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
6989
7084
  if (isFullyCoveredByGiftCards && totalAmount <= 0) {
6990
7085
  return (jsx(GiftCardOnlyBooking, { config: config, eventDetails: eventDetails, formData: formData, discountCode: discountCode, giftCards: giftCards, onSuccess: onSuccess, onError: onError, upsellSelections: upsellSelections }));
6991
7086
  }
6992
- if (isCreatingPaymentIntent || !clientSecret) {
7087
+ if (isCreatingPaymentIntent || !clientSecret || !stripePromise) {
6993
7088
  return (jsxs("div", { style: {
6994
7089
  display: "flex",
6995
7090
  alignItems: "center",
@@ -7027,6 +7122,49 @@ function StripePaymentForm({ config, eventDetails, formData, totalAmount, discou
7027
7122
  }, onError: onError }) }));
7028
7123
  }
7029
7124
 
7125
+ const HOLD_DURATION_SECONDS = 15 * 60;
7126
+ /**
7127
+ * Shows the remaining time on the 15-minute slot hold that the server places on
7128
+ * the pending order while the customer completes checkout. The timer is started
7129
+ * client-side when the payment step opens — matching the moment the pending
7130
+ * order (and its `holdExpiresAt`) is created server-side.
7131
+ */
7132
+ function HoldCountdown({ onExpire }) {
7133
+ const t = useTranslations();
7134
+ const [remaining, setRemaining] = useState(HOLD_DURATION_SECONDS);
7135
+ useEffect(() => {
7136
+ const startedAt = Date.now();
7137
+ const interval = setInterval(() => {
7138
+ const elapsed = Math.floor((Date.now() - startedAt) / 1000);
7139
+ const left = Math.max(0, HOLD_DURATION_SECONDS - elapsed);
7140
+ setRemaining(left);
7141
+ if (left <= 0) {
7142
+ clearInterval(interval);
7143
+ onExpire?.();
7144
+ }
7145
+ }, 1000);
7146
+ return () => clearInterval(interval);
7147
+ }, [onExpire]);
7148
+ const minutes = Math.floor(remaining / 60);
7149
+ const seconds = remaining % 60;
7150
+ const formatted = `${minutes}:${String(seconds).padStart(2, "0")}`;
7151
+ const isLow = remaining <= 60;
7152
+ return (jsxs("div", { style: {
7153
+ display: "flex",
7154
+ alignItems: "center",
7155
+ gap: "8px",
7156
+ padding: "10px 14px",
7157
+ borderRadius: "var(--bw-border-radius)",
7158
+ border: `1px solid ${isLow ? "var(--bw-error-color)" : "var(--bw-border-color)"}`,
7159
+ backgroundColor: "var(--bw-surface-color)",
7160
+ color: isLow ? "var(--bw-error-color)" : "var(--bw-text-color)",
7161
+ fontFamily: "var(--bw-font-family)",
7162
+ fontSize: "14px",
7163
+ }, children: [jsx(IconClock, {}), jsx("span", { children: remaining > 0
7164
+ ? t("hold.reservedFor", { time: formatted })
7165
+ : t("hold.expired") })] }));
7166
+ }
7167
+
7030
7168
  const FLAG_EMOJIS = {
7031
7169
  de: "🇩🇪",
7032
7170
  en: "🇬🇧",
@@ -7241,6 +7379,8 @@ function Sidebar({ isOpen, onClose, title, children, width = "450px", footer, })
7241
7379
  fontSize: "16px",
7242
7380
  color: "var(--bw-text-color)",
7243
7381
  fontFamily: "var(--bw-font-family)",
7382
+ textTransform: "var(--bw-text-transform, none)",
7383
+ letterSpacing: "var(--bw-letter-spacing, normal)",
7244
7384
  flex: 1,
7245
7385
  paddingRight: "12px",
7246
7386
  minWidth: 0,
@@ -11418,6 +11558,8 @@ const buttonBase = {
11418
11558
  transition: "all 0.2s ease",
11419
11559
  whiteSpace: "nowrap",
11420
11560
  border: "none",
11561
+ textTransform: "var(--bw-text-transform, none)",
11562
+ letterSpacing: "var(--bw-letter-spacing, normal)",
11421
11563
  };
11422
11564
  // CSS class name for button hover effects
11423
11565
  const buttonClassName = "bw-button-hover";
@@ -11434,6 +11576,11 @@ const buttonStyles = {
11434
11576
  color: "var(--bw-text-muted)",
11435
11577
  border: "1px solid var(--bw-border-color)",
11436
11578
  },
11579
+ // Size variants
11580
+ small: {
11581
+ padding: "8px 12px",
11582
+ fontSize: "13px",
11583
+ },
11437
11584
  // Full width modifier
11438
11585
  fullWidth: {
11439
11586
  width: "100%",
@@ -11506,6 +11653,12 @@ const sectionStyles = {
11506
11653
  // TEXT
11507
11654
  // ============================================
11508
11655
  const textStyles = {
11656
+ body: {
11657
+ fontSize: "14px",
11658
+ color: "var(--bw-text-color)",
11659
+ fontFamily: "var(--bw-font-family)",
11660
+ lineHeight: 1.5,
11661
+ },
11509
11662
  muted: {
11510
11663
  fontSize: "14px",
11511
11664
  color: "var(--bw-text-muted)",
@@ -11575,7 +11728,7 @@ const sectionHeaderStyles$1 = sectionStyles.header;
11575
11728
  const labelStyles$1 = formStyles.label;
11576
11729
  const inputStyles$1 = formStyles.input;
11577
11730
  const errorTextStyles$1 = formStyles.error;
11578
- function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError, isOpen, onClose, systemConfig, selectedUpsells = [], upsells = [], persistedState = null, onPersistedStateChange, }) {
11731
+ function BookingForm({ config, eventDetails, onSuccess, onError, isOpen, onClose, systemConfig, selectedUpsells = [], upsells = [], persistedState = null, onPersistedStateChange, }) {
11579
11732
  const t$1 = useTranslations();
11580
11733
  const { locale } = useLocale();
11581
11734
  const timezone = useTimezone();
@@ -11775,7 +11928,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11775
11928
  newTotal: appliedDiscountCode.newTotal,
11776
11929
  }
11777
11930
  : null;
11778
- const hasPaymentProvider = Boolean(stripePromise || systemConfig?.paymentProvider === "mollie");
11931
+ const hasPaymentProvider = systemConfig?.paymentProvider === "stripe" || systemConfig?.paymentProvider === "mollie";
11779
11932
  const handleVoucherValidated = useCallback((voucher, _error) => {
11780
11933
  if (voucher) {
11781
11934
  setAppliedVouchers((prev) => [...prev, voucher]);
@@ -12041,7 +12194,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
12041
12194
  ...inputStyles$1,
12042
12195
  resize: "vertical",
12043
12196
  minHeight: "80px",
12044
- } })] })] })] })), checkoutStep === "payment" && (jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "24px" }, children: [jsxs("div", { style: cardStyles$1, children: [jsx("h2", { style: { ...sectionHeaderStyles$1, marginBottom: "16px" }, children: t$1("summary.title") }), jsxs("div", { style: { marginTop: "10px", display: "flex", flexDirection: "column", gap: "12px" }, children: [jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t$1("booking.price") }), jsxs("div", { style: {
12197
+ } })] })] })] })), checkoutStep === "payment" && (jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "24px" }, children: [jsx(HoldCountdown, {}), jsxs("div", { style: cardStyles$1, children: [jsx("h2", { style: { ...sectionHeaderStyles$1, marginBottom: "16px" }, children: t$1("summary.title") }), jsxs("div", { style: { marginTop: "10px", display: "flex", flexDirection: "column", gap: "12px" }, children: [jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t$1("booking.price") }), jsxs("div", { style: {
12045
12198
  color: "var(--bw-text-color)",
12046
12199
  fontWeight: 500,
12047
12200
  fontFamily: "var(--bw-font-family)",
@@ -12145,7 +12298,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
12145
12298
  fontFamily: "var(--bw-font-family)",
12146
12299
  marginTop: "8px",
12147
12300
  textAlign: "right",
12148
- }, children: t$1("summary.remainingOnSite", { amount: formatCurrency(totalAmount - depositAmount) }) }))] })] })] }), depositsOnlyBlocked && (jsx("div", { style: cardStyles$1, children: jsx("p", { style: { ...errorTextStyles$1, margin: 0 }, children: t$1("booking.depositNotConfigured") }) })), !hasPaymentProvider && !depositsOnlyBlocked && (jsx("div", { style: cardStyles$1, children: jsx("p", { style: { ...errorTextStyles$1, margin: 0 }, children: t$1("booking.paymentUnavailable") }) })), hasPaymentProvider && !depositsOnlyBlocked && (systemConfig?.paymentProvider === "mollie" ? (jsxs("div", { style: cardStyles$1, children: [jsx("h2", { style: { ...sectionHeaderStyles$1 }, children: t$1("summary.payment") }), jsx(MolliePaymentForm, { config: config, eventDetails: eventDetails, formData: paymentFormData, totalAmount: paymentAmount, discountCode: discountCodeProp, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, upsellSelections: aggregatedUpsellSelections(), mollieProfileId: systemConfig?.mollieProfileId, mollieTestmode: systemConfig?.mollieTestmode })] })) : (jsxs("div", { style: cardStyles$1, children: [jsx("h2", { style: { ...sectionHeaderStyles$1 }, children: t$1("summary.payment") }), jsx(StripePaymentForm, { config: config, eventDetails: eventDetails, formData: paymentFormData, totalAmount: paymentAmount, discountCode: discountCodeProp, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig ?? null, stripePromise: stripePromise, stripeAppearance: stripeAppearance, upsellSelections: aggregatedUpsellSelections() })] })))] }))] }) }));
12301
+ }, children: t$1("summary.remainingOnSite", { amount: formatCurrency(totalAmount - depositAmount) }) }))] })] })] }), depositsOnlyBlocked && (jsx("div", { style: cardStyles$1, children: jsx("p", { style: { ...errorTextStyles$1, margin: 0 }, children: t$1("booking.depositNotConfigured") }) })), !hasPaymentProvider && !depositsOnlyBlocked && (jsx("div", { style: cardStyles$1, children: jsx("p", { style: { ...errorTextStyles$1, margin: 0 }, children: t$1("booking.paymentUnavailable") }) })), hasPaymentProvider && !depositsOnlyBlocked && (systemConfig?.paymentProvider === "mollie" ? (jsxs("div", { style: cardStyles$1, children: [jsx("h2", { style: { ...sectionHeaderStyles$1 }, children: t$1("summary.payment") }), jsx(MolliePaymentForm, { config: config, eventDetails: eventDetails, formData: paymentFormData, totalAmount: paymentAmount, discountCode: discountCodeProp, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, upsellSelections: aggregatedUpsellSelections(), mollieProfileId: systemConfig?.mollieProfileId, mollieTestmode: systemConfig?.mollieTestmode })] })) : (jsxs("div", { style: cardStyles$1, children: [jsx("h2", { style: { ...sectionHeaderStyles$1 }, children: t$1("summary.payment") }), jsx(StripePaymentForm, { config: config, eventDetails: eventDetails, formData: paymentFormData, totalAmount: paymentAmount, discountCode: discountCodeProp, giftCards: appliedGiftCards, onSuccess: onSuccess, onError: onError, systemConfig: systemConfig ?? null, stripeAppearance: stripeAppearance, upsellSelections: aggregatedUpsellSelections() })] })))] }))] }) }));
12149
12302
  }
12150
12303
 
12151
12304
  /**
@@ -12826,19 +12979,21 @@ function EventTypeDetailsDialog({ isOpen, onClose, eventType, onEventTypeSelect,
12826
12979
  textAlign: "right",
12827
12980
  }, children: jsxs("span", { children: [t("common.from"), " ", formatCurrency(eventType.minPrice)] }) })] }), isAvailable && (jsxs("button", { onClick: handleBookingClick, style: {
12828
12981
  backgroundColor: "var(--bw-highlight-color)",
12829
- color: "#ffffff",
12982
+ color: "var(--bw-button-text-color, #ffffff)",
12830
12983
  padding: "14px 28px",
12831
12984
  border: "none",
12832
12985
  borderRadius: "var(--bw-border-radius)",
12833
12986
  fontSize: "16px",
12834
12987
  fontWeight: 600,
12835
12988
  fontFamily: "var(--bw-font-family)",
12989
+ textTransform: "var(--bw-text-transform, none)",
12990
+ letterSpacing: "var(--bw-letter-spacing, normal)",
12836
12991
  display: "flex",
12837
12992
  alignItems: "center",
12838
12993
  gap: "8px",
12839
12994
  cursor: "pointer",
12840
12995
  transition: "all 0.2s ease",
12841
- }, children: [jsx(IconWave, { size: 20, color: "white" }), t("button.bookNow")] }))] }), !isAvailable && (jsx("div", { style: {
12996
+ }, children: [jsx(IconWave, { size: 20, color: "var(--bw-button-text-color, #ffffff)" }), t("button.bookNow")] }))] }), !isAvailable && (jsx("div", { style: {
12842
12997
  position: "absolute",
12843
12998
  inset: 0,
12844
12999
  backgroundColor: "rgba(0, 0, 0, 0.3)",
@@ -13252,6 +13407,8 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13252
13407
  const [stripeAppearance, setStripeAppearance] = useState(null);
13253
13408
  // Initialize Stripe
13254
13409
  useEffect(() => {
13410
+ if (!isOpen)
13411
+ return;
13255
13412
  if (paymentProvider !== "stripe")
13256
13413
  return;
13257
13414
  if (systemConfig?.stripePublishableKey && !stripePromise) {
@@ -13263,7 +13420,7 @@ function VoucherPurchaseForm({ config, voucherConfig, eventTypes, isOpen, onClos
13263
13420
  }
13264
13421
  setStripePromise(loadStripe(systemConfig.stripePublishableKey, stripeOptions));
13265
13422
  }
13266
- }, [paymentProvider, systemConfig, locale, stripePromise]);
13423
+ }, [isOpen, paymentProvider, systemConfig, locale, stripePromise]);
13267
13424
  // Initialize Stripe appearance
13268
13425
  useEffect(() => {
13269
13426
  const container = document.querySelector(".booking-widget-container");
@@ -14124,15 +14281,15 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14124
14281
  backgroundColor: isAvailable
14125
14282
  ? "var(--bw-success-color)"
14126
14283
  : "var(--bw-error-color)",
14127
- }, children: isAvailable ? t("events.spotsAvailable") : t("events.soldOut") }) }), jsx("div", { style: { position: "absolute", top: "16px", left: "16px", zIndex: 10 }, children: jsx("div", { style: {
14128
- fontSize: "13px",
14129
- color: "var(--bw-surface-color)",
14130
- fontWeight: 600,
14131
- backgroundColor: "var(--bw-highlight-color)",
14132
- padding: "2px 8px",
14133
- borderRadius: "var(--bw-border-radius)",
14134
- fontFamily: "var(--bw-font-family)",
14135
- }, children: eventType.category.name }) }), eventType.images && eventType.images.length > 0 && (jsx("div", { className: "event-type-img", style: { position: "relative", width: "100%", height: "300px" }, children: jsx(ImageCarousel, { images: eventType.images, eventName: eventType.name }) })), jsxs("div", { className: "event-type-content", style: {
14284
+ }, children: isAvailable ? t("events.spotsAvailable") : t("events.soldOut") }) }), eventType.images && eventType.images.length > 0 && (jsxs(Fragment, { children: [jsx("div", { style: { position: "absolute", top: "16px", left: "16px", zIndex: 10 }, children: jsx("div", { style: {
14285
+ fontSize: "13px",
14286
+ color: "var(--bw-surface-color)",
14287
+ fontWeight: 600,
14288
+ backgroundColor: "var(--bw-highlight-color)",
14289
+ padding: "2px 8px",
14290
+ borderRadius: "var(--bw-border-radius)",
14291
+ fontFamily: "var(--bw-font-family)",
14292
+ }, children: eventType.category.name }) }), jsx("div", { className: "event-type-img", style: { position: "relative", width: "100%", height: "300px" }, children: jsx(ImageCarousel, { images: eventType.images, eventName: eventType.name }) })] })), jsxs("div", { className: "event-type-content", style: {
14136
14293
  padding: "12px 18px",
14137
14294
  display: "flex",
14138
14295
  flexDirection: "column",
@@ -14227,8 +14384,17 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14227
14384
  if (!item)
14228
14385
  return jsx("div", { style: { height: "34px" } }, i);
14229
14386
  const hasDiscount = item.basePrice > 0 && item.price < item.basePrice;
14230
- return (jsxs("div", { onClick: (e) => { e.stopPropagation(); onInstancePreview?.(item.id, eventType.id); }, onMouseEnter: (e) => { if (onInstancePreview)
14231
- e.currentTarget.style.backgroundColor = "var(--bw-border-color)"; }, onMouseLeave: (e) => { e.currentTarget.style.backgroundColor = "transparent"; }, style: {
14387
+ const isRowBookable = item.bookingOpen && item.availableSpots > 0;
14388
+ return (jsxs("div", { onClick: (e) => {
14389
+ e.stopPropagation();
14390
+ if (isRowBookable) {
14391
+ onInstancePreview?.(item.id, eventType.id);
14392
+ }
14393
+ }, onMouseEnter: (e) => {
14394
+ if (onInstancePreview && isRowBookable) {
14395
+ e.currentTarget.style.backgroundColor = "var(--bw-highlight-subtle)";
14396
+ }
14397
+ }, onMouseLeave: (e) => { e.currentTarget.style.backgroundColor = "transparent"; }, style: {
14232
14398
  height: "34px",
14233
14399
  display: "grid",
14234
14400
  gridTemplateColumns: "auto 1fr auto 20px",
@@ -14237,12 +14403,13 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14237
14403
  width: "100%",
14238
14404
  fontSize: "13px",
14239
14405
  color: "var(--bw-text-muted)",
14240
- cursor: onInstancePreview ? "pointer" : "default",
14406
+ cursor: onInstancePreview && isRowBookable ? "pointer" : "default",
14241
14407
  borderRadius: "4px",
14242
14408
  padding: "0 2px",
14243
14409
  transition: "background 0.15s",
14244
14410
  boxSizing: "border-box",
14245
- }, children: [jsxs("span", { style: { whiteSpace: "nowrap", display: "flex", alignItems: "center", gap: "3px" }, children: [item.isSpecial && (jsx("span", { style: { color: "var(--bw-highlight-color)", fontSize: "11px", lineHeight: 1 }, children: "\u2605" })), formatWeekday(item.startTime, timezone, locale), " ", formatDate(item.startTime, timezone, locale)] }), jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", opacity: 0.75 }, children: item.name }), jsxs("span", { style: { display: "flex", alignItems: "center", gap: "4px", whiteSpace: "nowrap" }, children: [hasDiscount && (jsx("span", { style: { textDecoration: "line-through", opacity: 0.55, fontSize: "11px" }, children: formatCurrency(item.basePrice) })), jsx("span", { style: { fontWeight: 700, color: item.isSpecial ? "var(--bw-highlight-color)" : "var(--bw-text-color)" }, children: formatCurrency(item.price) })] }), item.specialDescription ? (jsx(InfoBadge, { text: item.specialDescription })) : (jsx("span", {}))] }, item.id));
14411
+ opacity: isRowBookable ? 1 : 0.55,
14412
+ }, children: [jsxs("span", { style: { whiteSpace: "nowrap", display: "flex", alignItems: "center", gap: "3px" }, children: [item.isSpecial && (jsx("span", { style: { color: "var(--bw-highlight-color)", fontSize: "11px", lineHeight: 1 }, children: "\u2605" })), formatWeekday(item.startTime, timezone, locale), " ", formatDate(item.startTime, timezone, locale)] }), jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", opacity: 0.75 }, children: item.name }), jsxs("span", { style: { display: "flex", alignItems: "center", gap: "4px", whiteSpace: "nowrap" }, children: [hasDiscount && (jsx("span", { style: { textDecoration: "line-through", opacity: 0.55, fontSize: "11px" }, children: formatCurrency(item.basePrice) })), jsx("span", { style: { fontWeight: 700, color: item.isSpecial ? "var(--bw-highlight-color)" : "var(--bw-text-color)" }, children: formatCurrency(item.price) })] }), item.isSpecial && item.specialDescription ? (jsx(InfoBadge, { text: item.specialDescription })) : (jsx("span", {}))] }, item.id));
14246
14413
  }) }), hasMoreDates && (jsx("button", { type: "button", onClick: (e) => {
14247
14414
  e.stopPropagation();
14248
14415
  onEventTypeSelect(eventType);
@@ -14294,12 +14461,14 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14294
14461
  transition: "all 0.2s ease",
14295
14462
  }, children: t("button.moreDetails") })), isAvailable && (jsxs("div", { style: {
14296
14463
  backgroundColor: "var(--bw-highlight-color)",
14297
- color: "var(--bw-surface-color)",
14464
+ color: "var(--bw-button-text-color, #ffffff)",
14298
14465
  padding: "12px 14px",
14299
14466
  borderRadius: "var(--bw-border-radius)",
14300
14467
  fontSize: "clamp(1rem, 2vw, 16px)",
14301
14468
  fontWeight: 600,
14302
14469
  fontFamily: "var(--bw-font-family)",
14470
+ textTransform: "var(--bw-text-transform, none)",
14471
+ letterSpacing: "var(--bw-letter-spacing, normal)",
14303
14472
  display: "flex",
14304
14473
  alignItems: "center",
14305
14474
  justifyContent: "center",
@@ -14308,7 +14477,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14308
14477
  border: "none",
14309
14478
  cursor: "pointer",
14310
14479
  transition: "all 0.2s ease",
14311
- }, children: [jsx(IconWave, { size: 15, color: "var(--bw-surface-color)" }), " ", t("button.bookNow")] }))] })] })] }), showVoucherAttachment && onVoucherClick && (jsx(VoucherAttachment, { onClick: () => onVoucherClick(eventType.id) })), !isAvailable && (jsx("div", { style: {
14480
+ }, children: [jsx(IconWave, { size: 15, color: "var(--bw-button-text-color, #ffffff)" }), " ", t("button.bookNow")] }))] })] })] }), showVoucherAttachment && onVoucherClick && (jsx(VoucherAttachment, { onClick: () => onVoucherClick(eventType.id) })), !isAvailable && (jsx("div", { style: {
14312
14481
  position: "absolute",
14313
14482
  inset: 0,
14314
14483
  backgroundColor: "rgba(0, 0, 0, 0.3)",
@@ -14329,6 +14498,74 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview,
14329
14498
  }) }) })), jsx(PaymentLogosStrip, {}), jsx(EventTypeDetailsDialog, { isOpen: detailsDialogOpen, onClose: handleCloseDetails, eventType: selectedEventTypeForDetails, onEventTypeSelect: onEventTypeSelect })] }));
14330
14499
  }
14331
14500
 
14501
+ function WaitlistDialog({ apiBaseUrl, organizationId, eventInstanceId, eventName, onClose, }) {
14502
+ const t = useTranslations();
14503
+ const [name, setName] = useState("");
14504
+ const [email, setEmail] = useState("");
14505
+ const [phone, setPhone] = useState("");
14506
+ const [isSubmitting, setIsSubmitting] = useState(false);
14507
+ const [error, setError] = useState(null);
14508
+ const [position, setPosition] = useState(null);
14509
+ const handleSubmit = async () => {
14510
+ setError(null);
14511
+ if (!name.trim() || !email.trim()) {
14512
+ setError(t("waitlist.requiredFields"));
14513
+ return;
14514
+ }
14515
+ setIsSubmitting(true);
14516
+ try {
14517
+ const response = await fetch(getApiUrl(apiBaseUrl, "/booking/waitlist"), {
14518
+ method: "POST",
14519
+ headers: { "Content-Type": "application/json" },
14520
+ body: JSON.stringify({
14521
+ organizationId,
14522
+ eventInstanceId,
14523
+ name: name.trim(),
14524
+ email: email.trim(),
14525
+ phone: phone.trim() || undefined,
14526
+ }),
14527
+ });
14528
+ if (!response.ok) {
14529
+ throw new Error("request_failed");
14530
+ }
14531
+ const data = (await response.json());
14532
+ setPosition(data.position ?? null);
14533
+ }
14534
+ catch {
14535
+ setError(t("waitlist.submitError"));
14536
+ }
14537
+ finally {
14538
+ setIsSubmitting(false);
14539
+ }
14540
+ };
14541
+ return (jsx("div", { style: {
14542
+ position: "fixed",
14543
+ inset: 0,
14544
+ zIndex: 1000,
14545
+ display: "flex",
14546
+ alignItems: "center",
14547
+ justifyContent: "center",
14548
+ backgroundColor: "rgba(15, 23, 42, 0.6)",
14549
+ padding: "16px",
14550
+ fontFamily: "var(--bw-font-family)",
14551
+ }, onClick: onClose, children: jsx("div", { style: {
14552
+ width: "100%",
14553
+ maxWidth: "420px",
14554
+ backgroundColor: "var(--bw-surface-color)",
14555
+ border: "1px solid var(--bw-border-color)",
14556
+ borderRadius: "var(--bw-border-radius)",
14557
+ padding: "24px",
14558
+ }, onClick: (e) => e.stopPropagation(), children: position !== null ? (jsxs("div", { style: { textAlign: "center" }, children: [jsx("h3", { style: {
14559
+ margin: "0 0 8px",
14560
+ color: "var(--bw-highlight-color)",
14561
+ fontSize: "18px",
14562
+ }, children: t("waitlist.successTitle") }), jsx("p", { style: mergeStyles(textStyles.body, { marginBottom: "20px" }), children: t("waitlist.successMessage", { position }) }), jsx("button", { type: "button", style: mergeStyles(buttonStyles.primary, buttonStyles.fullWidth), onClick: onClose, children: t("waitlist.done") })] })) : (jsxs(Fragment, { children: [jsx("h3", { style: {
14563
+ margin: "0 0 4px",
14564
+ color: "var(--bw-highlight-color)",
14565
+ fontSize: "18px",
14566
+ }, children: t("waitlist.title") }), jsx("p", { style: mergeStyles(textStyles.muted, { marginBottom: "16px" }), children: t("waitlist.subtitle", { name: eventName }) }), jsxs("div", { style: { marginBottom: "12px" }, children: [jsx("label", { style: formStyles.label, htmlFor: "wl-name", children: t("waitlist.name") }), jsx("input", { id: "wl-name", style: formStyles.input, value: name, onChange: (e) => setName(e.target.value) })] }), jsxs("div", { style: { marginBottom: "12px" }, children: [jsx("label", { style: formStyles.label, htmlFor: "wl-email", children: t("waitlist.email") }), jsx("input", { id: "wl-email", type: "email", style: formStyles.input, value: email, onChange: (e) => setEmail(e.target.value) })] }), jsxs("div", { style: { marginBottom: "12px" }, children: [jsx("label", { style: formStyles.label, htmlFor: "wl-phone", children: t("waitlist.phone") }), jsx("input", { id: "wl-phone", type: "tel", style: formStyles.input, value: phone, onChange: (e) => setPhone(e.target.value) })] }), error && jsx("p", { style: formStyles.error, children: error }), jsxs("div", { style: { display: "flex", gap: "8px", marginTop: "16px" }, children: [jsx("button", { type: "button", style: mergeStyles(buttonStyles.secondary, buttonStyles.fullWidth), onClick: onClose, disabled: isSubmitting, children: t("waitlist.cancel") }), jsx("button", { type: "button", style: mergeStyles(buttonStyles.primary, buttonStyles.fullWidth), onClick: handleSubmit, disabled: isSubmitting, children: isSubmitting ? t("waitlist.submitting") : t("waitlist.join") })] })] })) }) }));
14567
+ }
14568
+
14332
14569
  const getAllocationBadgeInfo = (availableSpots, maxParticipants, t) => {
14333
14570
  const availabilityRatio = availableSpots / maxParticipants;
14334
14571
  if (availabilityRatio >= 0.6)
@@ -14453,12 +14690,13 @@ const PriceDisplay = ({ price, yearPrices, t }) => {
14453
14690
  boxShadow: displayInfo ? "0 2px 4px rgba(0, 0, 0, 0.2)" : "none",
14454
14691
  }, children: formatCurrency(price) }));
14455
14692
  };
14456
- function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, }) {
14693
+ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInstanceSelect, onBackToEventTypes, isOpen, onClose, isLoadingEventInstances = false, isLoadingEventDetails = false, hasUpsellsStep = false, apiBaseUrl, organizationId, }) {
14457
14694
  const t = useTranslations();
14458
14695
  const { locale } = useLocale();
14459
14696
  const timezone = useTimezone();
14460
14697
  const [selectedEventInstanceId, setSelectedEventInstanceId] = useState(null);
14461
14698
  const [openGroups, setOpenGroups] = useState(new Set());
14699
+ const [waitlistEvent, setWaitlistEvent] = useState(null);
14462
14700
  const getMonthPriceDisplayInfo = (minPrice) => {
14463
14701
  return getPriceDisplayInfo(minPrice, yearPrices, t);
14464
14702
  };
@@ -14619,119 +14857,130 @@ function EventInstanceSelection({ eventInstances, selectedEventType, onEventInst
14619
14857
  fontFamily: "var(--bw-font-family)",
14620
14858
  }, children: t("instances.noAvailable") }), jsx("p", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)" }, children: t("instances.noAvailableMessage") })] }) }) }));
14621
14859
  }
14622
- return (jsx(Sidebar, { isOpen: isOpen, onClose: handleClose, title: `${selectedEventType?.name}`, footer: footerNav, children: jsxs("div", { style: { padding: "20px 10px" }, children: [jsx(FlowProgress, { currentStep: "date", hasUpsellsStep: hasUpsellsStep }), jsx("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: monthYearGroups.map(({ key, label, events, minPrice, year }, idx) => {
14623
- const monthPriceDisplayInfo = getMonthPriceDisplayInfo(minPrice);
14624
- return (jsxs(Fragment$1, { children: [idx > 0 && monthYearGroups[idx - 1].year !== year && (jsx("div", { style: { height: "1px", backgroundColor: "var(--bw-border-color)", margin: "4px 0" } })), jsx(Accordion, { title: label, priceInfo: jsx("div", { style: {
14625
- fontSize: "16px",
14626
- fontWeight: 500,
14627
- marginLeft: "auto",
14628
- padding: "4px 8px",
14629
- borderRadius: "var(--bw-border-radius-small)",
14630
- backgroundColor: monthPriceDisplayInfo
14631
- ? monthPriceDisplayInfo.backgroundColor
14632
- : "#14532d",
14633
- color: monthPriceDisplayInfo
14634
- ? monthPriceDisplayInfo.textColor
14635
- : "#4ade80",
14636
- boxShadow: monthPriceDisplayInfo
14637
- ? "0 2px 4px rgba(0, 0, 0, 0.2)"
14638
- : undefined,
14639
- }, children: `${t("common.from")} ${formatCurrency(minPrice)}` }), isOpen: openGroups.has(key), onToggle: () => toggleGroup(key), children: jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", paddingTop: "12px" }, children: events.map((event) => {
14640
- const availableSpots = event.maxParticipants - event.participantCount;
14641
- const isFullyBooked = availableSpots === 0;
14642
- const startDate = new Date(event.startTime);
14643
- const isPastEvent = today.toISOString() >= startDate.toISOString();
14644
- const isDisabled = isFullyBooked || isPastEvent || !event.bookingOpen;
14645
- return (jsxs("div", { style: {
14646
- position: "relative",
14647
- border: "1px solid var(--bw-border-color)",
14648
- backgroundColor: "var(--bw-surface-color)",
14649
- borderRadius: "var(--bw-border-radius)",
14650
- padding: "16px 10px",
14651
- transition: "all 0.2s ease",
14652
- fontFamily: "var(--bw-font-family)",
14653
- opacity: isDisabled ? 0.3 : 1,
14654
- filter: isDisabled ? "grayscale(40%)" : "none",
14655
- cursor: isDisabled ? "not-allowed" : "pointer",
14656
- }, onClick: () => {
14657
- if (!isDisabled) {
14658
- handleEventInstanceSelect(event);
14659
- }
14660
- }, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsx("div", { style: {
14661
- position: "absolute",
14662
- inset: 0,
14663
- backgroundColor: "rgba(15, 23, 42, 0.8)",
14664
- borderRadius: "var(--bw-border-radius)",
14665
- display: "flex",
14666
- alignItems: "center",
14667
- justifyContent: "center",
14668
- }, children: jsx("div", { style: {
14669
- width: "32px",
14670
- height: "32px",
14671
- color: "var(--bw-highlight-color)",
14672
- opacity: 0.8,
14673
- fontSize: "32px",
14674
- }, children: spinner() }) })), jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices, t: t }), jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants, t: t }), jsxs("div", { style: {
14675
- display: "flex",
14676
- justifyContent: "space-between",
14677
- width: "100%",
14678
- alignItems: "flex-start",
14679
- gap: "12px",
14680
- marginBottom: "4px",
14681
- }, children: [jsxs("div", { style: { display: "flex", alignItems: "flex-start", gap: "12px" }, children: [jsx("div", { style: {
14682
- fontSize: "16px",
14683
- transition: "all 0.2s ease",
14684
- borderRadius: "var(--bw-border-radius-small)",
14685
- borderTop: "4px solid var(--bw-border-color)",
14686
- border: "1px solid var(--bw-border-color)",
14687
- width: "40px",
14688
- height: "40px",
14689
- display: "flex",
14690
- alignItems: "center",
14691
- justifyContent: "center",
14692
- fontWeight: 700,
14693
- color: "var(--bw-text-color)",
14694
- backgroundColor: "var(--bw-background-color)",
14695
- }, children: startDate.getDate() }), jsxs("div", { style: {
14696
- fontSize: "16px",
14697
- color: "var(--bw-text-color)",
14698
- display: "flex",
14699
- flexDirection: "column",
14700
- alignItems: "flex-start",
14701
- justifyContent: "flex-start",
14702
- lineHeight: 1.25,
14703
- }, children: [jsxs("div", { children: [jsx("span", { style: { fontWeight: 600, marginBottom: "2px", textTransform: "capitalize" }, children: formatWeekday(event.startTime, timezone, locale) }), formatWeekday(event.startTime, timezone, locale) !==
14704
- formatWeekday(event.endTime, timezone, locale) && (jsxs(Fragment, { children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsx("span", { style: { fontWeight: 600, marginBottom: "2px", textTransform: "capitalize" }, children: formatWeekday(event.endTime, timezone, locale) })] }))] }), jsx("div", { children: formatWeekday(event.startTime, timezone, locale) ===
14705
- formatWeekday(event.endTime, timezone, locale) ? (jsxs(Fragment, { children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.startTime, timezone, locale) }), jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.endTime, timezone, locale) })] })) : (jsxs("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: [formatTime(event.startTime, timezone, locale), " ", t("instances.oclock")] })) })] }), jsxs("span", { style: {
14706
- fontSize: "12px",
14707
- fontWeight: 400,
14708
- color: "var(--bw-text-muted)",
14709
- marginLeft: "6px",
14710
- backgroundColor: "rgba(0, 0, 0, 0.05)",
14711
- whiteSpace: "nowrap",
14712
- padding: "2px 6px",
14713
- borderRadius: "var(--bw-border-radius-small)",
14714
- }, children: [event.durationDays, " ", event.durationDays > 1 ? t("common.days") : t("common.day")] })] }), jsx("div", { style: {
14715
- textAlign: "right",
14716
- display: "flex",
14717
- flexDirection: "column",
14718
- alignItems: "flex-end",
14719
- }, children: jsx(PriceDisplay, { price: event.price, yearPrices: yearPrices, t: t }) })] }), event.name !== selectedEventType?.name && (jsx("h4", { style: {
14720
- fontSize: "16px",
14721
- fontWeight: 600,
14722
- color: "var(--bw-text-color)",
14723
- lineHeight: 1.25,
14724
- margin: "0 0 2px 0",
14725
- display: "flex",
14726
- alignItems: "center",
14727
- gap: "8px",
14728
- maxWidth: "230px",
14729
- }, children: event.name }))] }, event.id));
14730
- }) }) })] }, key));
14731
- }) })] }) }));
14860
+ return (jsxs(Sidebar, { isOpen: isOpen, onClose: handleClose, title: `${selectedEventType?.name}`, footer: footerNav, children: [jsxs("div", { style: { padding: "20px 10px" }, children: [jsx(FlowProgress, { currentStep: "date", hasUpsellsStep: hasUpsellsStep }), jsx("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: monthYearGroups.map(({ key, label, events, minPrice, year }, idx) => {
14861
+ const monthPriceDisplayInfo = getMonthPriceDisplayInfo(minPrice);
14862
+ return (jsxs(Fragment$1, { children: [idx > 0 && monthYearGroups[idx - 1].year !== year && (jsx("div", { style: { height: "1px", backgroundColor: "var(--bw-border-color)", margin: "4px 0" } })), jsx(Accordion, { title: label, priceInfo: jsx("div", { style: {
14863
+ fontSize: "16px",
14864
+ fontWeight: 500,
14865
+ marginLeft: "auto",
14866
+ padding: "4px 8px",
14867
+ borderRadius: "var(--bw-border-radius-small)",
14868
+ backgroundColor: monthPriceDisplayInfo
14869
+ ? monthPriceDisplayInfo.backgroundColor
14870
+ : "#14532d",
14871
+ color: monthPriceDisplayInfo
14872
+ ? monthPriceDisplayInfo.textColor
14873
+ : "#4ade80",
14874
+ boxShadow: monthPriceDisplayInfo
14875
+ ? "0 2px 4px rgba(0, 0, 0, 0.2)"
14876
+ : undefined,
14877
+ }, children: `${t("common.from")} ${formatCurrency(minPrice)}` }), isOpen: openGroups.has(key), onToggle: () => toggleGroup(key), children: jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", paddingTop: "12px" }, children: events.map((event) => {
14878
+ const availableSpots = event.maxParticipants - event.participantCount;
14879
+ const isFullyBooked = availableSpots === 0;
14880
+ const startDate = new Date(event.startTime);
14881
+ const isPastEvent = today.toISOString() >= startDate.toISOString();
14882
+ const isDisabled = isFullyBooked || isPastEvent || !event.bookingOpen;
14883
+ const canWaitlist = isFullyBooked &&
14884
+ !isPastEvent &&
14885
+ !!apiBaseUrl &&
14886
+ !!organizationId;
14887
+ return (jsxs("div", { style: {
14888
+ position: "relative",
14889
+ border: "1px solid var(--bw-border-color)",
14890
+ backgroundColor: "var(--bw-surface-color)",
14891
+ borderRadius: "var(--bw-border-radius)",
14892
+ padding: "16px 10px",
14893
+ transition: "all 0.2s ease",
14894
+ fontFamily: "var(--bw-font-family)",
14895
+ opacity: isDisabled && !canWaitlist ? 0.3 : 1,
14896
+ filter: isDisabled && !canWaitlist ? "grayscale(40%)" : "none",
14897
+ cursor: isDisabled
14898
+ ? canWaitlist
14899
+ ? "default"
14900
+ : "not-allowed"
14901
+ : "pointer",
14902
+ }, onClick: () => {
14903
+ if (!isDisabled) {
14904
+ handleEventInstanceSelect(event);
14905
+ }
14906
+ }, children: [selectedEventInstanceId === event.id && isLoadingEventDetails && (jsx("div", { style: {
14907
+ position: "absolute",
14908
+ inset: 0,
14909
+ backgroundColor: "rgba(15, 23, 42, 0.8)",
14910
+ borderRadius: "var(--bw-border-radius)",
14911
+ display: "flex",
14912
+ alignItems: "center",
14913
+ justifyContent: "center",
14914
+ }, children: jsx("div", { style: {
14915
+ width: "32px",
14916
+ height: "32px",
14917
+ color: "var(--bw-highlight-color)",
14918
+ opacity: 0.8,
14919
+ fontSize: "32px",
14920
+ }, children: spinner() }) })), jsx(SpecialPriceBadge, { price: event.price, yearPrices: yearPrices, t: t }), jsx(AllocationBadge, { availableSpots: availableSpots, maxParticipants: event.maxParticipants, t: t }), jsxs("div", { style: {
14921
+ display: "flex",
14922
+ justifyContent: "space-between",
14923
+ width: "100%",
14924
+ alignItems: "flex-start",
14925
+ gap: "12px",
14926
+ marginBottom: "4px",
14927
+ }, children: [jsxs("div", { style: { display: "flex", alignItems: "flex-start", gap: "12px" }, children: [jsx("div", { style: {
14928
+ fontSize: "16px",
14929
+ transition: "all 0.2s ease",
14930
+ borderRadius: "var(--bw-border-radius-small)",
14931
+ borderTop: "4px solid var(--bw-border-color)",
14932
+ border: "1px solid var(--bw-border-color)",
14933
+ width: "40px",
14934
+ height: "40px",
14935
+ display: "flex",
14936
+ alignItems: "center",
14937
+ justifyContent: "center",
14938
+ fontWeight: 700,
14939
+ color: "var(--bw-text-color)",
14940
+ backgroundColor: "var(--bw-background-color)",
14941
+ }, children: startDate.getDate() }), jsxs("div", { style: {
14942
+ fontSize: "16px",
14943
+ color: "var(--bw-text-color)",
14944
+ display: "flex",
14945
+ flexDirection: "column",
14946
+ alignItems: "flex-start",
14947
+ justifyContent: "flex-start",
14948
+ lineHeight: 1.25,
14949
+ }, children: [jsxs("div", { children: [jsx("span", { style: { fontWeight: 600, marginBottom: "2px", textTransform: "capitalize" }, children: formatWeekday(event.startTime, timezone, locale) }), formatWeekday(event.startTime, timezone, locale) !==
14950
+ formatWeekday(event.endTime, timezone, locale) && (jsxs(Fragment, { children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsx("span", { style: { fontWeight: 600, marginBottom: "2px", textTransform: "capitalize" }, children: formatWeekday(event.endTime, timezone, locale) })] }))] }), jsx("div", { children: formatWeekday(event.startTime, timezone, locale) ===
14951
+ formatWeekday(event.endTime, timezone, locale) ? (jsxs(Fragment, { children: [jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.startTime, timezone, locale) }), jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: " - " }), jsx("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: formatTime(event.endTime, timezone, locale) })] })) : (jsxs("span", { style: { color: "var(--bw-text-muted)", fontSize: "14px" }, children: [formatTime(event.startTime, timezone, locale), " ", t("instances.oclock")] })) })] }), jsxs("span", { style: {
14952
+ fontSize: "12px",
14953
+ fontWeight: 400,
14954
+ color: "var(--bw-text-muted)",
14955
+ marginLeft: "6px",
14956
+ backgroundColor: "rgba(0, 0, 0, 0.05)",
14957
+ whiteSpace: "nowrap",
14958
+ padding: "2px 6px",
14959
+ borderRadius: "var(--bw-border-radius-small)",
14960
+ }, children: [event.durationDays, " ", event.durationDays > 1 ? t("common.days") : t("common.day")] })] }), jsx("div", { style: {
14961
+ textAlign: "right",
14962
+ display: "flex",
14963
+ flexDirection: "column",
14964
+ alignItems: "flex-end",
14965
+ }, children: jsx(PriceDisplay, { price: event.price, yearPrices: yearPrices, t: t }) })] }), event.name !== selectedEventType?.name && (jsx("h4", { style: {
14966
+ fontSize: "16px",
14967
+ fontWeight: 600,
14968
+ color: "var(--bw-text-color)",
14969
+ lineHeight: 1.25,
14970
+ margin: "0 0 2px 0",
14971
+ display: "flex",
14972
+ alignItems: "center",
14973
+ gap: "8px",
14974
+ maxWidth: "230px",
14975
+ }, children: event.name })), canWaitlist && (jsx("button", { type: "button", style: mergeStyles(buttonStyles.secondary, buttonStyles.fullWidth, buttonStyles.small, { marginTop: "8px" }), onClick: (e) => {
14976
+ e.stopPropagation();
14977
+ setWaitlistEvent(event);
14978
+ }, children: t("waitlist.joinCta") }))] }, event.id));
14979
+ }) }) })] }, key));
14980
+ }) })] }), waitlistEvent && apiBaseUrl && organizationId && (jsx(WaitlistDialog, { apiBaseUrl: apiBaseUrl, organizationId: organizationId, eventInstanceId: waitlistEvent.id, eventName: waitlistEvent.name, onClose: () => setWaitlistEvent(null) }))] }));
14732
14981
  }
14733
14982
 
14734
- function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText, showAllButton, isLoadingEventDetails = false, isLoadingShowAll = false, isLoading = false, }) {
14983
+ function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText, showAllButton, isLoadingEventDetails = false, isLoadingShowAll = false, isLoading = false, count = 3, }) {
14735
14984
  const t = useTranslations();
14736
14985
  const { locale } = useLocale();
14737
14986
  const timezone = useTimezone();
@@ -14783,7 +15032,7 @@ function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText
14783
15032
  };
14784
15033
  // Show loading skeleton
14785
15034
  if (isLoading) {
14786
- return jsx(NextEventsSkeleton, { count: 3 });
15035
+ return jsx(NextEventsSkeleton, { count: count });
14787
15036
  }
14788
15037
  if (events.length === 0) {
14789
15038
  return (jsx("div", { style: { maxWidth: "500px", margin: "0 auto", padding: "16px" }, children: jsxs("div", { style: {
@@ -15622,6 +15871,7 @@ let buffer = [];
15622
15871
  let flushTimer = null;
15623
15872
  let currentApiBaseUrl = "";
15624
15873
  let currentOrganizationId = "";
15874
+ let currentPartnerContractId = null;
15625
15875
  const FLUSH_INTERVAL_MS = 3000;
15626
15876
  function flush() {
15627
15877
  if (buffer.length === 0)
@@ -15663,9 +15913,10 @@ function scheduleFlush() {
15663
15913
  /**
15664
15914
  * Initialise the analytics module. Must be called once before `trackEvent`.
15665
15915
  */
15666
- function initAnalytics(apiBaseUrl, organizationId) {
15916
+ function initAnalytics(apiBaseUrl, organizationId, options) {
15667
15917
  currentApiBaseUrl = apiBaseUrl;
15668
15918
  currentOrganizationId = organizationId;
15919
+ currentPartnerContractId = options?.partnerContractId?.trim() || null;
15669
15920
  if (typeof window !== "undefined") {
15670
15921
  window.addEventListener("pagehide", flush);
15671
15922
  window.addEventListener("visibilitychange", () => {
@@ -15682,9 +15933,12 @@ function trackEvent(event, properties = {}) {
15682
15933
  return;
15683
15934
  if (!currentOrganizationId)
15684
15935
  return;
15936
+ const mergedProperties = currentPartnerContractId
15937
+ ? { ...properties, partnerContractId: currentPartnerContractId }
15938
+ : properties;
15685
15939
  buffer.push({
15686
15940
  event,
15687
- properties,
15941
+ properties: mergedProperties,
15688
15942
  domain: window.location.hostname,
15689
15943
  url: window.location.href,
15690
15944
  referrer: document.referrer,
@@ -15760,7 +16014,6 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15760
16014
  const [sidebarOpen, setSidebarOpen] = useState(false);
15761
16015
  // Booking flow state
15762
16016
  const [eventDetails, setEventDetails] = useState(null);
15763
- const [stripePromise, setStripePromise] = useState(null);
15764
16017
  // SEPARATED LOADING STATES
15765
16018
  const [isLoading, setIsLoading] = useState(true);
15766
16019
  const [isLoadingEventInstances, setIsLoadingEventInstances] = useState(false);
@@ -15895,7 +16148,9 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15895
16148
  useEffect(() => {
15896
16149
  if (!analyticsInitRef.current && config.organizationId) {
15897
16150
  analyticsInitRef.current = true;
15898
- initAnalytics(config.apiBaseUrl, config.organizationId);
16151
+ initAnalytics(config.apiBaseUrl, config.organizationId, {
16152
+ partnerContractId: config.partnerContractId,
16153
+ });
15899
16154
  trackEvent("widget_loaded", {
15900
16155
  viewMode,
15901
16156
  eventTypeId: config.eventTypeId,
@@ -15904,7 +16159,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15904
16159
  isStandaloneVoucherMode,
15905
16160
  });
15906
16161
  }
15907
- }, [config.organizationId, config.apiBaseUrl]);
16162
+ }, [config.organizationId, config.apiBaseUrl, config.partnerContractId, viewMode, config.eventTypeId, config.categoryId, config.eventInstanceId, isStandaloneVoucherMode]);
15908
16163
  // Fire widget pageview once when Google Ads config is received from API
15909
16164
  const pageviewFiredRef = useRef(false);
15910
16165
  useEffect(() => {
@@ -15918,6 +16173,23 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15918
16173
  const initializeWidget = async () => {
15919
16174
  try {
15920
16175
  setIsLoading(true);
16176
+ if (config.partnerContractId) {
16177
+ const validationResponse = await fetch(getApiUrl(config.apiBaseUrl, `/booking/validate-partner?organizationId=${encodeURIComponent(config.organizationId)}&contractId=${config.partnerContractId}`), {
16178
+ method: "GET",
16179
+ headers: createApiHeaders(config, locale),
16180
+ });
16181
+ if (!validationResponse.ok) {
16182
+ setError("Invalid partner configuration");
16183
+ config.onError?.("Invalid partner configuration");
16184
+ return;
16185
+ }
16186
+ const validationData = (await validationResponse.json());
16187
+ if (!validationData.valid) {
16188
+ setError("Invalid partner configuration");
16189
+ config.onError?.("Invalid partner configuration");
16190
+ return;
16191
+ }
16192
+ }
15921
16193
  // Load voucher config in parallel if voucher mode is requested or there's event selection
15922
16194
  if (isVoucherModeRequested || hasEventSelection) {
15923
16195
  void loadVoucherConfig();
@@ -16053,11 +16325,18 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16053
16325
  setVoucherPurchaseResult(voucherData.voucherResult);
16054
16326
  setIsSuccess(true);
16055
16327
  setSuccessPaymentId(null);
16328
+ return;
16056
16329
  }
16057
16330
  }
16058
16331
  catch {
16059
- // Ignore lookup errors; webhook eventual consistency may delay data.
16060
- }
16332
+ // Fall back to booking success flow if voucher lookup fails.
16333
+ }
16334
+ // Not a voucher purchase: treat as a booking return and open the
16335
+ // booking confirmation, mirroring the Stripe redirect handler.
16336
+ trackEvent("booking_completed", { paymentIntentId: mollieReturn.molliePaymentId, source: "mollie_redirect" });
16337
+ setVoucherPurchaseResult(null);
16338
+ setSuccessPaymentId(mollieReturn.molliePaymentId);
16339
+ setIsSuccess(true);
16061
16340
  };
16062
16341
  if (!isLoading) {
16063
16342
  void handleMollieVoucherReturn();
@@ -16246,27 +16525,6 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16246
16525
  widgetPaymentMode: data.widgetPaymentMode ?? "all",
16247
16526
  });
16248
16527
  }
16249
- if (data.stripePublishableKey) {
16250
- const stripeOptions = {
16251
- locale: locale,
16252
- };
16253
- if (data.connectedAccountId) {
16254
- stripeOptions.stripeAccount = data.connectedAccountId;
16255
- }
16256
- if (typeof window !== "undefined" && window.document) {
16257
- try {
16258
- void (window.parent === window || window.parent.location.href);
16259
- if (!stripeOptions.betas) {
16260
- stripeOptions.betas = [];
16261
- }
16262
- }
16263
- catch {
16264
- console.warn("[WIDGET] Detected restricted environment, adjusting Stripe options");
16265
- stripeOptions.betas = [];
16266
- }
16267
- }
16268
- setStripePromise(loadStripe(data.stripePublishableKey, stripeOptions));
16269
- }
16270
16528
  if (!skipInstanceAutoSelectRef.current && data.eventInstances.length === 1) {
16271
16529
  setSelectedEventInstance(data.eventInstances[0]);
16272
16530
  setCurrentStep("booking");
@@ -16306,29 +16564,6 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16306
16564
  roundPricesEnabled: data.roundPricesEnabled ?? true,
16307
16565
  widgetPaymentMode: data.widgetPaymentMode ?? "all",
16308
16566
  });
16309
- if (!stripePromise && data.stripePublishableKey) {
16310
- const stripeOptions = {
16311
- locale: locale,
16312
- };
16313
- if (data.connectedAccountId) {
16314
- stripeOptions.stripeAccount = data.connectedAccountId;
16315
- }
16316
- if (typeof window !== "undefined" && window.document) {
16317
- try {
16318
- // Check if we have parent access (not in a cross-origin iframe)
16319
- void (window.parent === window || window.parent.location.href);
16320
- stripeOptions.apiVersion = "2025-02-24.acacia";
16321
- if (!stripeOptions.betas) {
16322
- stripeOptions.betas = [];
16323
- }
16324
- }
16325
- catch {
16326
- console.warn("[WIDGET] Detected restricted environment, adjusting Stripe options");
16327
- stripeOptions.betas = [];
16328
- }
16329
- }
16330
- setStripePromise(loadStripe(data.stripePublishableKey, stripeOptions));
16331
- }
16332
16567
  }
16333
16568
  else {
16334
16569
  const errorMessage = data.error || t("error.loadEventDetails");
@@ -16597,12 +16832,12 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16597
16832
  startTime: cardPreviewItem.startTime,
16598
16833
  endTime: cardPreviewItem.startTime,
16599
16834
  price: cardPreviewItem.price,
16600
- maxParticipants: cardPreviewItem.availableSpots + 1,
16601
- participantCount: 1,
16835
+ maxParticipants: Math.max(cardPreviewItem.availableSpots, 0),
16836
+ participantCount: 0,
16602
16837
  availableSpots: cardPreviewItem.availableSpots,
16603
16838
  durationDays: 1,
16604
16839
  durationPerDay: 1,
16605
- bookingOpen: true,
16840
+ bookingOpen: cardPreviewItem.bookingOpen && cardPreviewItem.availableSpots > 0,
16606
16841
  }
16607
16842
  : null;
16608
16843
  if (eventInstance) {
@@ -16790,7 +17025,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16790
17025
  }
16791
17026
  // Main view based on view mode
16792
17027
  if (viewMode === "next-events" && showingPreview) {
16793
- return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [jsx(NextEventsPreview, { events: upcomingEvents, onEventSelect: handleUpcomingEventSelect, onShowAll: handleShowAllEvents, showAllButtonText: nextEventsSettings.showAllButtonText, showAllButton: nextEventsSettings.showAllButton, isLoadingEventDetails: isLoadingEventDetails, isLoadingShowAll: isLoadingShowAll, isLoading: isLoading }), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17028
+ return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [jsx(NextEventsPreview, { events: upcomingEvents, onEventSelect: handleUpcomingEventSelect, onShowAll: handleShowAllEvents, showAllButtonText: nextEventsSettings.showAllButtonText, showAllButton: nextEventsSettings.showAllButton, isLoadingEventDetails: isLoadingEventDetails, isLoadingShowAll: isLoadingShowAll, isLoading: isLoading, count: nextEventsSettings.count }), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16794
17029
  setIsSuccess(false);
16795
17030
  setCurrentStep("eventTypes");
16796
17031
  setShowingPreview(true);
@@ -16811,7 +17046,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16811
17046
  }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16812
17047
  }
16813
17048
  if (viewMode === "specials" && showingPreview) {
16814
- return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [jsx(SpecialsView, { specials: specials, onEventSelect: handleUpcomingEventSelect, isLoading: isLoadingSpecials, showSavingsAmount: config.specialsSettings?.showSavingsAmount ?? true, showSavingsPercent: config.specialsSettings?.showSavingsPercent ?? false, emptyStateText: config.specialsSettings?.emptyStateText }), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17049
+ return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [jsx(SpecialsView, { specials: specials, onEventSelect: handleUpcomingEventSelect, isLoading: isLoadingSpecials, showSavingsAmount: config.specialsSettings?.showSavingsAmount ?? true, showSavingsPercent: config.specialsSettings?.showSavingsPercent ?? false, emptyStateText: config.specialsSettings?.emptyStateText }), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16815
17050
  setIsSuccess(false);
16816
17051
  setCurrentStep("eventTypes");
16817
17052
  setShowingPreview(true);
@@ -16825,13 +17060,13 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16825
17060
  }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16826
17061
  }
16827
17062
  if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
16828
- return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
17063
+ return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [jsx(NextEventsPreview, { events: upcomingEvents, onEventSelect: handleUpcomingEventSelect, onShowAll: handleShowAllEvents, showAllButtonText: nextEventsSettings.showAllButtonText, showAllButton: nextEventsSettings.showAllButton, isLoadingEventDetails: isLoadingEventDetails, isLoadingShowAll: isLoadingShowAll, isLoading: isLoading, count: nextEventsSettings.count }), shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
16829
17064
  setShowingPreview(true);
16830
17065
  setCurrentStep("eventTypes");
16831
17066
  }, isOpen: currentStep === "eventInstances", onClose: () => {
16832
17067
  setShowingPreview(true);
16833
17068
  setCurrentStep("eventTypes");
16834
- }, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17069
+ }, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16835
17070
  setIsSuccess(false);
16836
17071
  setCurrentStep("eventTypes");
16837
17072
  setShowingPreview(true);
@@ -16848,8 +17083,11 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16848
17083
  }, config: config, googleAdsConfig: googleAdsConfig, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16849
17084
  }
16850
17085
  if (viewMode === "button" && (isSingleEventTypeMode || isDirectInstanceMode)) {
17086
+ // Busy only while genuinely loading. Note: we intentionally do NOT key off
17087
+ // `shouldRenderInstanceSelection && !sidebarOpen` — that flag stays true after the
17088
+ // sidebar is closed (only `sidebarOpen` resets), which left the spinner stuck on.
16851
17089
  const isButtonBusy = (isDirectInstanceMode && isLoadingEventDetails) ||
16852
- (!isDirectInstanceMode && (isLoadingEventInstances || (shouldRenderInstanceSelection && !sidebarOpen)));
17090
+ (!isDirectInstanceMode && isLoadingEventInstances);
16853
17091
  return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, style: {
16854
17092
  display: "flex",
16855
17093
  justifyContent: "center",
@@ -16860,13 +17098,15 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16860
17098
  alignItems: "center",
16861
17099
  gap: "10px",
16862
17100
  backgroundColor: "var(--bw-highlight-color)",
16863
- color: "white",
17101
+ color: "var(--bw-button-text-color, #ffffff)",
16864
17102
  padding: "16px 32px",
16865
17103
  border: "none",
16866
17104
  borderRadius: "var(--bw-border-radius)",
16867
17105
  fontSize: "18px",
16868
17106
  fontWeight: 600,
16869
17107
  fontFamily: "var(--bw-font-family)",
17108
+ textTransform: "var(--bw-text-transform, none)",
17109
+ letterSpacing: "var(--bw-letter-spacing, normal)",
16870
17110
  boxShadow: "var(--bw-shadow-md)",
16871
17111
  cursor: isButtonBusy ? "default" : "pointer",
16872
17112
  opacity: isButtonBusy ? 0.85 : 1,
@@ -16881,8 +17121,8 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16881
17121
  setSidebarOpen(true);
16882
17122
  setShouldRenderInstanceSelection(true);
16883
17123
  }
16884
- }, children: [isButtonBusy && jsx(Spinner, { size: 20, borderColor: "white" }), config.buttonText ||
16885
- (isDirectInstanceMode ? t("button.bookNow") : t("button.viewDates"))] }), shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep })), shouldRenderUpsells && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
17124
+ }, children: [isButtonBusy && jsx(Spinner, { size: 20, borderColor: "var(--bw-button-text-color, #ffffff)" }), config.buttonText ||
17125
+ (isDirectInstanceMode ? t("button.bookNow") : t("button.viewDates"))] }), shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => setSidebarOpen(false), isOpen: sidebarOpen && currentStep === "eventInstances", onClose: () => setSidebarOpen(false), isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), shouldRenderUpsells && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: handleBackFromBooking, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16886
17126
  setIsSuccess(false);
16887
17127
  setCurrentStep("eventTypes");
16888
17128
  setSidebarOpen(false);
@@ -16943,7 +17183,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16943
17183
  };
16944
17184
  };
16945
17185
  const backHandlers = getBackHandlers();
16946
- return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep })), shouldRenderUpsells && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: backHandlers.onClose, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsx(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
17186
+ return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [cardsView, shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: handleBackToEventTypes, isOpen: currentStep === "eventInstances", onClose: handleBackToEventTypes, isLoadingEventInstances: isLoadingEventInstances, isLoadingEventDetails: isLoadingEventDetails, hasUpsellsStep: hasUpsellsFlowStep, apiBaseUrl: config.apiBaseUrl, organizationId: config.organizationId })), shouldRenderUpsells && (jsx(UpsellsStep, { upsells: upsells, selectedUpsells: selectedUpsells, participantCount: tempParticipantCount, isLoading: isLoadingUpsells, isOpen: currentStep === "upsells", onClose: () => setCurrentStep("eventInstances"), onSelect: handleUpsellsSelect, onContinue: handleUpsellsContinue, onBack: handleUpsellsBack })), shouldRenderBookingForm && eventDetails && (jsx(BookingForm, { config: config, eventDetails: eventDetails, onSuccess: handleBookingSuccess, onError: handleBookingError, isOpen: currentStep === "booking" && !!eventDetails, onClose: backHandlers.onClose, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells, persistedState: bookingPersistedState, onPersistedStateChange: setBookingPersistedState })), jsx(BookingSuccessModal, { isOpen: isSuccess && !voucherPurchaseResult, onClose: () => {
16947
17187
  setIsSuccess(false);
16948
17188
  setCurrentStep("eventTypes");
16949
17189
  setSuccessPaymentId(null);
@@ -17007,7 +17247,7 @@ function styleInject(css, ref) {
17007
17247
  }
17008
17248
  }
17009
17249
 
17010
- var css_248z = ".booking-widget-container{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;box-sizing:border-box;color:var(--bw-text-color,#1e293b);direction:ltr;display:block;font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);isolation:isolate;line-height:1.5;position:relative;text-align:left}.booking-widget-container *,.booking-widget-container :after,.booking-widget-container :before{box-sizing:border-box;margin:0;padding:0}.booking-widget-container input,.booking-widget-container select,.booking-widget-container textarea{font-family:inherit;font-size:inherit;line-height:inherit}.booking-widget-container button{background:none;border:none;cursor:pointer;font-family:inherit;font-size:inherit}.booking-widget-container a{color:inherit;text-decoration:none}.booking-widget-container img{display:block;height:auto;max-width:100%;vertical-align:middle}.booking-widget-container ol,.booking-widget-container ul{list-style:none}.booking-widget-container h1,.booking-widget-container h2,.booking-widget-container h3,.booking-widget-container h4,.booking-widget-container h5,.booking-widget-container h6{font-size:inherit;font-weight:inherit}#booking-widget-portal{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--bw-text-color,#1e293b);direction:ltr;font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);isolation:isolate;line-height:1.5;text-align:left}#booking-widget-portal *,#booking-widget-portal :after,#booking-widget-portal :before{box-sizing:border-box}#booking-widget-portal-root{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--bw-text-color,#1e293b);font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);line-height:1.5}:root{--bw-highlight-color:#00b1aa;--bw-highlight-color-rgb:0,177,170;--bw-background-color:#f8fdfe;--bw-surface-color:#fff;--bw-text-color:#0e7490;--bw-text-muted:rgba(14,116,144,.7);--bw-border-color:#bae6fd;--bw-success-color:#38bdf8;--bw-warning-color:#fbbf24;--bw-error-color:#f43f5e;--bw-border-radius:18px;--bw-border-radius-small:calc(var(--bw-border-radius)*0.8);--bw-spacing:16px;--bw-spacing-large:24px;--bw-font-family:\"Inter\",system-ui,sans-serif;--bw-font-size:14px;--bw-font-size-large:18px;--bw-font-size-small:12px;--bw-shadow-md:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);--bw-shadow-lg:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05);--bw-highlight-muted:rgba(0,177,170,.1);--bw-highlight-subtle:rgba(0,177,170,.05);--bw-text-subtle:rgba(14,116,144,.4)}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes shimmer{0%{transform:translateX(-100%)}to{transform:translateX(100%)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes fade-out{0%{opacity:1}to{opacity:0}}@keyframes slide-in-right{0%{opacity:0;transform:translateX(100%)}to{opacity:1;transform:translateX(0)}}@keyframes slide-out-right{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(100%)}}@keyframes slide-in-up{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes scale-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.animate-spin{animation:spin 1s linear infinite}.animate-shimmer{animation:shimmer 2s infinite}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-in-up{animation:slide-in-up .3s ease-out}.animate-scale-in{animation:scale-in .2s ease-out}.skeleton-shimmer{overflow:hidden;position:relative}.skeleton-shimmer:after{animation:shimmer 1.5s infinite;background:linear-gradient(90deg,transparent,hsla(0,0%,100%,.3),transparent);content:\"\";height:100%;left:0;position:absolute;top:0;width:100%}.bw-btn{transition:all .2s ease!important}.bw-btn:hover:not(:disabled):not([disabled]){box-shadow:0 4px 12px rgba(0,0,0,.15);transform:translateY(-1px)}.bw-btn:active:not(:disabled):not([disabled]){box-shadow:0 2px 4px rgba(0,0,0,.1)}.bw-btn-primary:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-color);filter:brightness(1.1)}.bw-btn-secondary:hover:not(:disabled):not([disabled]){background-color:var(--bw-surface-color);border-color:var(--bw-highlight-color);color:var(--bw-highlight-color)}.bw-btn-ghost:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-muted)}.bw-btn-outline:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-color);color:var(--bw-button-text-color,#fff)}.bw-btn:disabled,.bw-btn[disabled]{cursor:not-allowed!important;opacity:.5!important}button[class*=bw-btn],button[style*=transition]{transition:all .2s ease!important}button[data-variant=primary]:hover:not(:disabled),button[style*=\"--bw-highlight-color\"]:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,0,0,.15);filter:brightness(1.1);transform:translateY(-1px)}button[data-variant=secondary]:hover:not(:disabled){border-color:var(--bw-highlight-color)!important;color:var(--bw-highlight-color)!important}button[data-variant=ghost]:hover:not(:disabled){background-color:var(--bw-highlight-muted)!important}button[data-variant=outline]:hover:not(:disabled){background-color:var(--bw-highlight-color)!important;color:var(--bw-button-text-color,#fff)!important}.bw-button-hover:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,0,0,.15);transform:translateY(-1px)}.bw-button-hover:active:not(:disabled){box-shadow:0 2px 4px rgba(0,0,0,.1);transform:translateY(0)}@media (max-width:768px){.sidebar-mobile{border-radius:0!important;max-width:100%!important;width:100%!important}}@media (max-width:600px){.event-type-list{gap:12px!important;padding:8px!important}.event-type-card{flex:1 1 100%!important;max-width:100%!important;padding:0!important}.event-type-img{height:160px!important}.event-type-title{font-size:1.1rem!important}.event-type-desc{font-size:.8rem!important;max-height:100px!important;min-height:100px!important}.event-type-content{padding:16px 24px!important}}.event-type-markdown{overflow:visible!important}.event-type-markdown p{color:var(--bw-text-muted);font-family:var(--bw-font-family);line-height:1.6;margin:0 0 8px}.event-type-markdown p:last-child{margin-bottom:0}.event-type-markdown h2{font-size:18px!important;font-weight:700!important;margin:12px 0 6px!important}.event-type-markdown h2,.event-type-markdown h3{color:var(--bw-text-color)!important;line-height:1.3!important}.event-type-markdown h3{font-size:16px!important;font-weight:600!important;margin:10px 0 4px!important}.event-type-markdown strong{color:var(--bw-text-color);font-weight:600}.event-type-markdown em{font-style:italic}.event-type-markdown u{text-decoration:underline}.event-type-markdown ul{list-style:none!important;margin:6px 0!important;padding:0 0 0 24px!important;position:relative!important}.event-type-markdown ul li{color:var(--bw-text-muted)!important;font-family:var(--bw-font-family)!important;margin-bottom:2px!important;padding-left:0!important;position:relative!important}.event-type-markdown ul li:before{color:var(--bw-text-color)!important;content:\"•\"!important;font-weight:700!important;left:-16px!important;position:absolute!important;top:0!important}.event-type-markdown ol{counter-reset:list-counter!important;list-style:none!important;margin:6px 0!important;padding:0 0 0 24px!important;position:relative!important}.event-type-markdown ol li{color:var(--bw-text-muted)!important;counter-increment:list-counter!important;font-family:var(--bw-font-family)!important;margin-bottom:2px!important;padding-left:0!important;position:relative!important}.event-type-markdown ol li:before{color:var(--bw-text-color)!important;content:counter(list-counter) \".\"!important;font-weight:700!important;left:-20px!important;position:absolute!important;top:0!important}.event-type-markdown blockquote{border-left:2px solid var(--bw-border-color);color:var(--bw-text-muted);font-style:italic;margin:4px 0;padding-left:12px}.event-type-markdown a{color:var(--bw-highlight-color);text-decoration:underline}.markdown-content h1,.markdown-content h2,.markdown-content h3,.markdown-content h4,.markdown-content h5,.markdown-content h6{color:var(--bw-text-color);font-weight:600;margin-bottom:.5em}.markdown-content h1{font-size:1.5em}.markdown-content h2{font-size:1.25em}.markdown-content h3{font-size:1.1em}.markdown-content p{line-height:1.6;margin-bottom:1em}.markdown-content ol,.markdown-content ul{margin-bottom:1em;padding-left:1.5em}.markdown-content ul{list-style-type:disc}.markdown-content ol{list-style-type:decimal}.markdown-content li{margin-bottom:.25em}.markdown-content a{color:var(--bw-highlight-color);text-decoration:underline}.markdown-content a:hover{opacity:.8}.markdown-content strong{font-weight:600}.markdown-content em{font-style:italic}.markdown-content code{background:var(--bw-highlight-subtle);border-radius:4px;font-family:monospace;font-size:.9em;padding:.125em .25em}.markdown-content blockquote{border-left:3px solid var(--bw-highlight-color);color:var(--bw-text-muted);margin:1em 0;padding-left:1em}.print-only{display:none}.print-hidden{display:block}@media print{.print-only{display:block}.print-hidden{display:none!important}.print-booking-header{border-bottom:2px solid #000;display:block;margin-bottom:24px;padding-bottom:16px;text-align:center}.print-booking-header h1{font-size:24px;margin:0 0 8px}.print-booking-header .subtitle{color:#666;font-size:14px}.print-booking-card{border:1px solid #ccc;border-radius:8px;margin-bottom:16px;padding:16px;page-break-inside:avoid}.print-section-title{border-bottom:1px solid #ddd;display:block;font-size:16px;font-weight:600;margin-bottom:12px;padding-bottom:8px}.print-detail-grid{display:grid;gap:12px;grid-template-columns:1fr 1fr}.print-detail-item{margin-bottom:8px}.print-detail-label{color:#666;font-size:12px;margin-bottom:4px}.print-detail-value{font-size:14px;font-weight:600}.print-status-badge{border-radius:9999px;display:inline-block;font-size:12px;font-weight:600;padding:4px 12px}.print-status-paid{background-color:#dcfce7;color:#166534;display:inline-block}.print-participant{align-items:center;background-color:#f9fafb;border-radius:4px;display:flex;justify-content:space-between;margin-bottom:8px;padding:8px}.print-participant-name{font-weight:600}.print-participant-age{color:#666;font-size:12px}.print-payment-summary{display:block}.print-payment-row{border-bottom:1px solid #eee;display:flex;justify-content:space-between;padding:4px 0}.print-payment-row:last-child{border-bottom:none;font-weight:600}.print-footer{border-top:1px solid #ddd;color:#666;display:block;font-size:12px;margin-top:24px;padding-top:16px;text-align:center}.print-footer p{margin:4px 0}}";
17250
+ var css_248z = ".booking-widget-container{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;box-sizing:border-box;color:var(--bw-text-color,#1e293b);direction:ltr;display:block;font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);isolation:isolate;line-height:1.5;position:relative;text-align:left}.booking-widget-container *,.booking-widget-container :after,.booking-widget-container :before{box-sizing:border-box;margin:0;padding:0}.booking-widget-container input,.booking-widget-container select,.booking-widget-container textarea{font-family:inherit;font-size:inherit;line-height:inherit}.booking-widget-container button{background:none;border:none;cursor:pointer;font-family:inherit;font-size:inherit}.booking-widget-container a{color:inherit;text-decoration:none}.booking-widget-container img{display:block;height:auto;max-width:100%;vertical-align:middle}.booking-widget-container ol,.booking-widget-container ul{list-style:none}.booking-widget-container h1,.booking-widget-container h2,.booking-widget-container h3,.booking-widget-container h4,.booking-widget-container h5,.booking-widget-container h6{font-size:inherit;font-weight:inherit}#booking-widget-portal{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--bw-text-color,#1e293b);direction:ltr;font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);isolation:isolate;line-height:1.5;text-align:left}#booking-widget-portal *,#booking-widget-portal :after,#booking-widget-portal :before{box-sizing:border-box}#booking-widget-portal-root{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:var(--bw-text-color,#1e293b);font-family:var(--bw-font-family,system-ui,-apple-system,sans-serif);font-size:var(--bw-font-size,14px);line-height:1.5}:root{--bw-highlight-color:#00b1aa;--bw-highlight-color-rgb:0,177,170;--bw-background-color:#f8fdfe;--bw-surface-color:#fff;--bw-text-color:#0e7490;--bw-text-muted:rgba(14,116,144,.7);--bw-border-color:#bae6fd;--bw-success-color:#38bdf8;--bw-warning-color:#fbbf24;--bw-error-color:#f43f5e;--bw-border-radius:18px;--bw-border-radius-small:calc(var(--bw-border-radius)*0.8);--bw-spacing:16px;--bw-spacing-large:24px;--bw-font-family:\"Inter\",system-ui,sans-serif;--bw-font-size:14px;--bw-font-size-large:18px;--bw-font-size-small:12px;--bw-shadow-md:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);--bw-shadow-lg:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -2px rgba(0,0,0,.05);--bw-highlight-muted:rgba(0,177,170,.1);--bw-highlight-subtle:rgba(0,177,170,.05);--bw-text-subtle:rgba(14,116,144,.4)}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}@keyframes shimmer{0%{transform:translateX(-100%)}to{transform:translateX(100%)}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}@keyframes fade-out{0%{opacity:1}to{opacity:0}}@keyframes slide-in-right{0%{opacity:0;transform:translateX(100%)}to{opacity:1;transform:translateX(0)}}@keyframes slide-out-right{0%{opacity:1;transform:translateX(0)}to{opacity:0;transform:translateX(100%)}}@keyframes slide-in-up{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes scale-in{0%{opacity:0;transform:scale(.95)}to{opacity:1;transform:scale(1)}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.5}}.animate-spin{animation:spin 1s linear infinite}.animate-shimmer{animation:shimmer 2s infinite}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.animate-fade-in{animation:fade-in .2s ease-out}.animate-slide-in-up{animation:slide-in-up .3s ease-out}.animate-scale-in{animation:scale-in .2s ease-out}.skeleton-shimmer{overflow:hidden;position:relative}.skeleton-shimmer:after{animation:shimmer 1.5s infinite;background:linear-gradient(90deg,transparent,hsla(0,0%,100%,.3),transparent);content:\"\";height:100%;left:0;position:absolute;top:0;width:100%}.bw-btn{letter-spacing:var(--bw-letter-spacing,normal);text-transform:var(--bw-text-transform,none);transition:all .2s ease!important}.bw-btn:hover:not(:disabled):not([disabled]){box-shadow:0 4px 12px rgba(0,0,0,.15);transform:translateY(-1px)}.bw-btn:active:not(:disabled):not([disabled]){box-shadow:0 2px 4px rgba(0,0,0,.1)}.bw-btn-primary:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-color);filter:brightness(1.1)}.bw-btn-secondary:hover:not(:disabled):not([disabled]){background-color:var(--bw-surface-color);border-color:var(--bw-highlight-color);color:var(--bw-highlight-color)}.bw-btn-ghost:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-muted)}.bw-btn-outline:hover:not(:disabled):not([disabled]){background-color:var(--bw-highlight-color);color:var(--bw-button-text-color,#fff)}.bw-btn:disabled,.bw-btn[disabled]{cursor:not-allowed!important;opacity:.5!important}button[class*=bw-btn],button[style*=transition]{transition:all .2s ease!important}button[data-variant=primary]:hover:not(:disabled),button[style*=\"--bw-highlight-color\"]:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,0,0,.15);filter:brightness(1.1);transform:translateY(-1px)}button[data-variant=secondary]:hover:not(:disabled){border-color:var(--bw-highlight-color)!important;color:var(--bw-highlight-color)!important}button[data-variant=ghost]:hover:not(:disabled){background-color:var(--bw-highlight-muted)!important}button[data-variant=outline]:hover:not(:disabled){background-color:var(--bw-highlight-color)!important;color:var(--bw-button-text-color,#fff)!important}.bw-button-hover:hover:not(:disabled){box-shadow:0 4px 12px rgba(0,0,0,.15);transform:translateY(-1px)}.bw-button-hover:active:not(:disabled){box-shadow:0 2px 4px rgba(0,0,0,.1);transform:translateY(0)}@media (max-width:768px){.sidebar-mobile{border-radius:0!important;max-width:100%!important;width:100%!important}}@media (max-width:600px){.event-type-list{gap:12px!important;padding:8px!important}.event-type-card{flex:1 1 100%!important;max-width:100%!important;padding:0!important}.event-type-img{height:160px!important}.event-type-title{font-size:1.1rem!important}.event-type-desc{font-size:.8rem!important;max-height:100px!important;min-height:100px!important}.event-type-content{padding:16px 24px!important}}.event-type-markdown{overflow:visible!important}.event-type-markdown p{color:var(--bw-text-muted);font-family:var(--bw-font-family);line-height:1.6;margin:0 0 8px}.event-type-markdown p:last-child{margin-bottom:0}.event-type-markdown h2{font-size:18px!important;font-weight:700!important;margin:12px 0 6px!important}.event-type-markdown h2,.event-type-markdown h3{color:var(--bw-text-color)!important;line-height:1.3!important}.event-type-markdown h3{font-size:16px!important;font-weight:600!important;margin:10px 0 4px!important}.event-type-markdown strong{color:var(--bw-text-color);font-weight:600}.event-type-markdown em{font-style:italic}.event-type-markdown u{text-decoration:underline}.event-type-markdown ul{list-style:none!important;margin:6px 0!important;padding:0 0 0 24px!important;position:relative!important}.event-type-markdown ul li{color:var(--bw-text-muted)!important;font-family:var(--bw-font-family)!important;margin-bottom:2px!important;padding-left:0!important;position:relative!important}.event-type-markdown ul li:before{color:var(--bw-text-color)!important;content:\"•\"!important;font-weight:700!important;left:-16px!important;position:absolute!important;top:0!important}.event-type-markdown ol{counter-reset:list-counter!important;list-style:none!important;margin:6px 0!important;padding:0 0 0 24px!important;position:relative!important}.event-type-markdown ol li{color:var(--bw-text-muted)!important;counter-increment:list-counter!important;font-family:var(--bw-font-family)!important;margin-bottom:2px!important;padding-left:0!important;position:relative!important}.event-type-markdown ol li:before{color:var(--bw-text-color)!important;content:counter(list-counter) \".\"!important;font-weight:700!important;left:-20px!important;position:absolute!important;top:0!important}.event-type-markdown blockquote{border-left:2px solid var(--bw-border-color);color:var(--bw-text-muted);font-style:italic;margin:4px 0;padding-left:12px}.event-type-markdown a{color:var(--bw-highlight-color);text-decoration:underline}.markdown-content h1,.markdown-content h2,.markdown-content h3,.markdown-content h4,.markdown-content h5,.markdown-content h6{color:var(--bw-text-color);font-weight:600;margin-bottom:.5em}.markdown-content h1{font-size:1.5em}.markdown-content h2{font-size:1.25em}.markdown-content h3{font-size:1.1em}.markdown-content p{line-height:1.6;margin-bottom:1em}.markdown-content ol,.markdown-content ul{margin-bottom:1em;padding-left:1.5em}.markdown-content ul{list-style-type:disc}.markdown-content ol{list-style-type:decimal}.markdown-content li{margin-bottom:.25em}.markdown-content a{color:var(--bw-highlight-color);text-decoration:underline}.markdown-content a:hover{opacity:.8}.markdown-content strong{font-weight:600}.markdown-content em{font-style:italic}.markdown-content code{background:var(--bw-highlight-subtle);border-radius:4px;font-family:monospace;font-size:.9em;padding:.125em .25em}.markdown-content blockquote{border-left:3px solid var(--bw-highlight-color);color:var(--bw-text-muted);margin:1em 0;padding-left:1em}.print-only{display:none}.print-hidden{display:block}@media print{.print-only{display:block}.print-hidden{display:none!important}.print-booking-header{border-bottom:2px solid #000;display:block;margin-bottom:24px;padding-bottom:16px;text-align:center}.print-booking-header h1{font-size:24px;margin:0 0 8px}.print-booking-header .subtitle{color:#666;font-size:14px}.print-booking-card{border:1px solid #ccc;border-radius:8px;margin-bottom:16px;padding:16px;page-break-inside:avoid}.print-section-title{border-bottom:1px solid #ddd;display:block;font-size:16px;font-weight:600;margin-bottom:12px;padding-bottom:8px}.print-detail-grid{display:grid;gap:12px;grid-template-columns:1fr 1fr}.print-detail-item{margin-bottom:8px}.print-detail-label{color:#666;font-size:12px;margin-bottom:4px}.print-detail-value{font-size:14px;font-weight:600}.print-status-badge{border-radius:9999px;display:inline-block;font-size:12px;font-weight:600;padding:4px 12px}.print-status-paid{background-color:#dcfce7;color:#166534;display:inline-block}.print-participant{align-items:center;background-color:#f9fafb;border-radius:4px;display:flex;justify-content:space-between;margin-bottom:8px;padding:8px}.print-participant-name{font-weight:600}.print-participant-age{color:#666;font-size:12px}.print-payment-summary{display:block}.print-payment-row{border-bottom:1px solid #eee;display:flex;justify-content:space-between;padding:4px 0}.print-payment-row:last-child{border-bottom:none;font-weight:600}.print-footer{border-top:1px solid #ddd;color:#666;display:block;font-size:12px;margin-top:24px;padding-top:16px;text-align:center}.print-footer p{margin:4px 0}}";
17011
17251
  styleInject(css_248z);
17012
17252
 
17013
17253
  // Export init function for vanilla JS usage