@bigz-app/booking-widget 1.2.1 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/booking-widget.js +707 -180
  2. package/dist/booking-widget.js.map +1 -1
  3. package/dist/components/UniversalBookingWidget.d.ts +52 -2
  4. package/dist/components/UniversalBookingWidget.d.ts.map +1 -1
  5. package/dist/components/booking/BookingForm.d.ts +3 -0
  6. package/dist/components/booking/BookingForm.d.ts.map +1 -1
  7. package/dist/components/booking/BookingSuccessModal.d.ts.map +1 -1
  8. package/dist/components/events/EventTypeSelection.d.ts +14 -1
  9. package/dist/components/events/EventTypeSelection.d.ts.map +1 -1
  10. package/dist/components/events/SpecialsView.d.ts +13 -0
  11. package/dist/components/events/SpecialsView.d.ts.map +1 -0
  12. package/dist/components/events/index.d.ts +1 -0
  13. package/dist/components/events/index.d.ts.map +1 -1
  14. package/dist/components/shared/Button.d.ts.map +1 -1
  15. package/dist/components/shared/DialogPortal.d.ts.map +1 -1
  16. package/dist/components/upsells/UpsellCard.d.ts +2 -0
  17. package/dist/components/upsells/UpsellCard.d.ts.map +1 -1
  18. package/dist/components/upsells/UpsellsStep.d.ts +2 -0
  19. package/dist/components/upsells/UpsellsStep.d.ts.map +1 -1
  20. package/dist/i18n/i18n-context.d.ts.map +1 -1
  21. package/dist/i18n/locales/de.d.ts.map +1 -1
  22. package/dist/i18n/locales/en.d.ts.map +1 -1
  23. package/dist/i18n/locales/es.d.ts.map +1 -1
  24. package/dist/i18n/locales/pt.d.ts.map +1 -1
  25. package/dist/i18n/locales/sv.d.ts.map +1 -1
  26. package/dist/index.cjs +707 -180
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.esm.js +708 -181
  29. package/dist/index.esm.js.map +1 -1
  30. package/dist/styles/StyleProvider.d.ts.map +1 -1
  31. package/dist/styles/shared-styles.d.ts +1 -0
  32. package/dist/styles/shared-styles.d.ts.map +1 -1
  33. package/dist/validation/booking-schema.d.ts +54 -13
  34. package/dist/validation/booking-schema.d.ts.map +1 -1
  35. package/package.json +1 -1
package/dist/index.esm.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import React__default, { createContext, useState, useEffect, useCallback, useMemo, useContext, forwardRef, useRef, Fragment as Fragment$1 } from 'react';
2
+ import React__default, { createContext, useState, useCallback, useMemo, useContext, useEffect, forwardRef, useRef, Fragment as Fragment$1 } from 'react';
3
3
  import { createRoot } from 'react-dom/client';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import ReactDOM, { createPortal } from 'react-dom';
@@ -233,6 +233,7 @@ const de$1 = {
233
233
  "events.soldOut": "Ausgebucht",
234
234
  "events.availableFrom": "Freie Plätze ab {{date}}",
235
235
  "events.noAvailableDates": "Keine Termine frei",
236
+ "events.previewSectionTitle": "Specials & nächste Termine",
236
237
  // Event instances
237
238
  "instances.title": "Terminauswahl",
238
239
  "instances.noAvailable": "Keine verfügbaren Termine",
@@ -249,6 +250,15 @@ const de$1 = {
249
250
  "nextEvents.noUpcomingMessage": "Aktuell sind keine Termine verfügbar. Bitte schaue später noch einmal vorbei oder kontaktiere uns direkt.",
250
251
  "nextEvents.showAll": "Alle Events anzeigen",
251
252
  "nextEvents.priceOnRequest": "Preis auf Anfrage",
253
+ // Specials view
254
+ "specials.title": "Sonderangebote",
255
+ "specials.subtitle": "Unsere aktuellen Angebote auf einen Blick",
256
+ "specials.noSpecials": "Keine Sonderangebote",
257
+ "specials.noSpecialsMessage": "Derzeit sind keine Sonderangebote verfügbar. Bitte schaue später noch einmal vorbei.",
258
+ "specials.save": "Spare {{amount}}",
259
+ "specials.savePercent": "{{percent}}% Rabatt",
260
+ "specials.spotsLeft": "Noch {{count}} Plätze frei",
261
+ "specials.bookNow": "Jetzt buchen",
252
262
  // Booking form
253
263
  "booking.title": "Buchung - {{name}}",
254
264
  "booking.notPossible": "Buchung nicht möglich",
@@ -270,6 +280,8 @@ const de$1 = {
270
280
  "booking.participantName": "Name *",
271
281
  "booking.participantNamePlaceholder": "Teilnehmername",
272
282
  "booking.participantAge": "Alter",
283
+ "booking.participantLevel": "Level",
284
+ "booking.participantLevelPlaceholder": "Level wählen...",
273
285
  "booking.addParticipant": "{{number}}. Teilnehmer hinzufügen",
274
286
  "booking.maxParticipants": "Maximale Anzahl an Teilnehmern erreicht. Es sind nur noch {{count}} Plätze verfügbar.",
275
287
  "booking.maxSpotsReached": "Maximal {{count}} Plätze verfügbar.",
@@ -418,7 +430,11 @@ const de$1 = {
418
430
  "validation.emailInvalid": "Ungültiges E-Mail-Format",
419
431
  "validation.emailDomainInvalid": "Ungültige E-Mail-Domain",
420
432
  "validation.participantRequired": "Mindestens ein Teilnehmer erforderlich",
433
+ "validation.ageRequired": "Alter ist erforderlich",
434
+ "validation.levelRequired": "Bitte ein Level auswählen",
421
435
  "validation.acceptTerms": "Bitte akzeptiere die Allgemeinen Geschäftsbedingungen",
436
+ "level.beginner": "Anfänger",
437
+ "level.advanced": "Fortgeschritten",
422
438
  // Sidebar
423
439
  "sidebar.close": "Schließen",
424
440
  // Promo
@@ -493,6 +509,7 @@ const en = {
493
509
  "events.soldOut": "Sold out",
494
510
  "events.availableFrom": "Available from {{date}}",
495
511
  "events.noAvailableDates": "No dates available",
512
+ "events.previewSectionTitle": "Specials & upcoming dates",
496
513
  // Event instances
497
514
  "instances.title": "Select a date",
498
515
  "instances.noAvailable": "No available dates",
@@ -509,6 +526,15 @@ const en = {
509
526
  "nextEvents.noUpcomingMessage": "There are currently no dates available. Please check back later or contact us directly.",
510
527
  "nextEvents.showAll": "Show all events",
511
528
  "nextEvents.priceOnRequest": "Price on request",
529
+ // Specials view
530
+ "specials.title": "Special Offers",
531
+ "specials.subtitle": "Our current deals at a glance",
532
+ "specials.noSpecials": "No special offers",
533
+ "specials.noSpecialsMessage": "There are currently no special offers available. Please check back later.",
534
+ "specials.save": "Save {{amount}}",
535
+ "specials.savePercent": "{{percent}}% off",
536
+ "specials.spotsLeft": "{{count}} spots left",
537
+ "specials.bookNow": "Book now",
512
538
  // Booking form
513
539
  "booking.title": "Booking - {{name}}",
514
540
  "booking.notPossible": "Booking not possible",
@@ -530,6 +556,8 @@ const en = {
530
556
  "booking.participantName": "Name *",
531
557
  "booking.participantNamePlaceholder": "Participant name",
532
558
  "booking.participantAge": "Age",
559
+ "booking.participantLevel": "Level",
560
+ "booking.participantLevelPlaceholder": "Select level...",
533
561
  "booking.addParticipant": "Add participant {{number}}",
534
562
  "booking.maxParticipants": "Maximum number of participants reached. Only {{count}} spots are available.",
535
563
  "booking.maxSpotsReached": "Maximum {{count}} spots available.",
@@ -678,7 +706,11 @@ const en = {
678
706
  "validation.emailInvalid": "Invalid email format",
679
707
  "validation.emailDomainInvalid": "Invalid email domain",
680
708
  "validation.participantRequired": "At least one participant is required",
709
+ "validation.ageRequired": "Age is required",
710
+ "validation.levelRequired": "Please select a level",
681
711
  "validation.acceptTerms": "Please accept the terms and conditions",
712
+ "level.beginner": "Beginner",
713
+ "level.advanced": "Advanced",
682
714
  // Sidebar
683
715
  "sidebar.close": "Close",
684
716
  // Promo
@@ -753,6 +785,7 @@ const es = {
753
785
  "events.soldOut": "Agotado",
754
786
  "events.availableFrom": "Disponible desde {{date}}",
755
787
  "events.noAvailableDates": "Sin fechas disponibles",
788
+ "events.previewSectionTitle": "Especiales & próximas fechas",
756
789
  // Event instances
757
790
  "instances.title": "Seleccionar fecha",
758
791
  "instances.noAvailable": "Sin fechas disponibles",
@@ -769,6 +802,15 @@ const es = {
769
802
  "nextEvents.noUpcomingMessage": "Actualmente no hay fechas disponibles. Por favor, vuelve más tarde o contáctanos directamente.",
770
803
  "nextEvents.showAll": "Mostrar todos los eventos",
771
804
  "nextEvents.priceOnRequest": "Precio bajo consulta",
805
+ // Specials view
806
+ "specials.title": "Ofertas especiales",
807
+ "specials.subtitle": "Nuestras ofertas actuales de un vistazo",
808
+ "specials.noSpecials": "Sin ofertas especiales",
809
+ "specials.noSpecialsMessage": "Actualmente no hay ofertas especiales disponibles. Vuelve más tarde.",
810
+ "specials.save": "Ahorra {{amount}}",
811
+ "specials.savePercent": "{{percent}}% de descuento",
812
+ "specials.spotsLeft": "{{count}} plazas disponibles",
813
+ "specials.bookNow": "Reservar ahora",
772
814
  // Booking form
773
815
  "booking.title": "Reserva - {{name}}",
774
816
  "booking.notPossible": "Reserva no posible",
@@ -790,6 +832,8 @@ const es = {
790
832
  "booking.participantName": "Nombre *",
791
833
  "booking.participantNamePlaceholder": "Nombre del participante",
792
834
  "booking.participantAge": "Edad",
835
+ "booking.participantLevel": "Nivel",
836
+ "booking.participantLevelPlaceholder": "Seleccionar nivel...",
793
837
  "booking.addParticipant": "Añadir participante {{number}}",
794
838
  "booking.maxParticipants": "Número máximo de participantes alcanzado. Solo quedan {{count}} plazas disponibles.",
795
839
  "booking.maxSpotsReached": "Máximo {{count}} plazas disponibles.",
@@ -938,7 +982,11 @@ const es = {
938
982
  "validation.emailInvalid": "Formato de correo electrónico inválido",
939
983
  "validation.emailDomainInvalid": "Dominio de correo electrónico inválido",
940
984
  "validation.participantRequired": "Se requiere al menos un participante",
985
+ "validation.ageRequired": "La edad es obligatoria",
986
+ "validation.levelRequired": "Selecciona un nivel",
941
987
  "validation.acceptTerms": "Por favor, acepta los términos y condiciones",
988
+ "level.beginner": "Principiante",
989
+ "level.advanced": "Avanzado",
942
990
  // Sidebar
943
991
  "sidebar.close": "Cerrar",
944
992
  // Promo
@@ -1013,6 +1061,7 @@ const pt = {
1013
1061
  "events.soldOut": "Esgotado",
1014
1062
  "events.availableFrom": "Disponível a partir de {{date}}",
1015
1063
  "events.noAvailableDates": "Sem datas disponíveis",
1064
+ "events.previewSectionTitle": "Especiais & próximas datas",
1016
1065
  // Event instances
1017
1066
  "instances.title": "Selecionar data",
1018
1067
  "instances.noAvailable": "Sem datas disponíveis",
@@ -1029,6 +1078,15 @@ const pt = {
1029
1078
  "nextEvents.noUpcomingMessage": "Atualmente não há datas disponíveis. Por favor, volte mais tarde ou contacte-nos diretamente.",
1030
1079
  "nextEvents.showAll": "Mostrar todos os eventos",
1031
1080
  "nextEvents.priceOnRequest": "Preço sob consulta",
1081
+ // Specials view
1082
+ "specials.title": "Ofertas especiais",
1083
+ "specials.subtitle": "As nossas ofertas atuais num relance",
1084
+ "specials.noSpecials": "Sem ofertas especiais",
1085
+ "specials.noSpecialsMessage": "Atualmente não há ofertas especiais disponíveis. Por favor, volte mais tarde.",
1086
+ "specials.save": "Poupe {{amount}}",
1087
+ "specials.savePercent": "{{percent}}% de desconto",
1088
+ "specials.spotsLeft": "{{count}} lugares disponíveis",
1089
+ "specials.bookNow": "Reservar agora",
1032
1090
  // Booking form
1033
1091
  "booking.title": "Reserva - {{name}}",
1034
1092
  "booking.notPossible": "Reserva não possível",
@@ -1050,6 +1108,8 @@ const pt = {
1050
1108
  "booking.participantName": "Nome *",
1051
1109
  "booking.participantNamePlaceholder": "Nome do participante",
1052
1110
  "booking.participantAge": "Idade",
1111
+ "booking.participantLevel": "Nível",
1112
+ "booking.participantLevelPlaceholder": "Selecionar nível...",
1053
1113
  "booking.addParticipant": "Adicionar participante {{number}}",
1054
1114
  "booking.maxParticipants": "Número máximo de participantes atingido. Apenas {{count}} lugares disponíveis.",
1055
1115
  "booking.maxSpotsReached": "Máximo {{count}} lugares disponíveis.",
@@ -1198,7 +1258,11 @@ const pt = {
1198
1258
  "validation.emailInvalid": "Formato de email inválido",
1199
1259
  "validation.emailDomainInvalid": "Domínio de email inválido",
1200
1260
  "validation.participantRequired": "É necessário pelo menos um participante",
1261
+ "validation.ageRequired": "A idade é obrigatória",
1262
+ "validation.levelRequired": "Por favor selecione um nível",
1201
1263
  "validation.acceptTerms": "Por favor, aceite os termos e condições",
1264
+ "level.beginner": "Iniciante",
1265
+ "level.advanced": "Avançado",
1202
1266
  // Sidebar
1203
1267
  "sidebar.close": "Fechar",
1204
1268
  // Promo
@@ -1273,6 +1337,7 @@ const sv = {
1273
1337
  "events.soldOut": "Fullbokat",
1274
1338
  "events.availableFrom": "Lediga platser från {{date}}",
1275
1339
  "events.noAvailableDates": "Inga datum lediga",
1340
+ "events.previewSectionTitle": "Specials & kommande datum",
1276
1341
  // Event instances
1277
1342
  "instances.title": "Välj datum",
1278
1343
  "instances.noAvailable": "Inga tillgängliga datum",
@@ -1289,6 +1354,15 @@ const sv = {
1289
1354
  "nextEvents.noUpcomingMessage": "Det finns för närvarande inga datum tillgängliga. Kom tillbaka senare eller kontakta oss direkt.",
1290
1355
  "nextEvents.showAll": "Visa alla evenemang",
1291
1356
  "nextEvents.priceOnRequest": "Pris på förfrågan",
1357
+ // Specials view
1358
+ "specials.title": "Specialerbjudanden",
1359
+ "specials.subtitle": "Våra aktuella erbjudanden i korthet",
1360
+ "specials.noSpecials": "Inga specialerbjudanden",
1361
+ "specials.noSpecialsMessage": "Det finns för närvarande inga specialerbjudanden tillgängliga. Kom tillbaka senare.",
1362
+ "specials.save": "Spara {{amount}}",
1363
+ "specials.savePercent": "{{percent}}% rabatt",
1364
+ "specials.spotsLeft": "{{count}} platser kvar",
1365
+ "specials.bookNow": "Boka nu",
1292
1366
  // Booking form
1293
1367
  "booking.title": "Bokning - {{name}}",
1294
1368
  "booking.notPossible": "Bokning inte möjlig",
@@ -1310,6 +1384,8 @@ const sv = {
1310
1384
  "booking.participantName": "Namn *",
1311
1385
  "booking.participantNamePlaceholder": "Deltagarens namn",
1312
1386
  "booking.participantAge": "Ålder",
1387
+ "booking.participantLevel": "Nivå",
1388
+ "booking.participantLevelPlaceholder": "Välj nivå...",
1313
1389
  "booking.addParticipant": "Lägg till deltagare {{number}}",
1314
1390
  "booking.maxParticipants": "Maximalt antal deltagare uppnått. Bara {{count}} platser är tillgängliga.",
1315
1391
  "booking.maxSpotsReached": "Maximalt {{count}} platser tillgängliga.",
@@ -1458,7 +1534,11 @@ const sv = {
1458
1534
  "validation.emailInvalid": "Ogiltigt e-postformat",
1459
1535
  "validation.emailDomainInvalid": "Ogiltig e-postdomän",
1460
1536
  "validation.participantRequired": "Minst en deltagare krävs",
1537
+ "validation.ageRequired": "Ålder krävs",
1538
+ "validation.levelRequired": "Välj en nivå",
1461
1539
  "validation.acceptTerms": "Acceptera villkoren",
1540
+ "level.beginner": "Nybörjare",
1541
+ "level.advanced": "Avancerad",
1462
1542
  // Sidebar
1463
1543
  "sidebar.close": "Stäng",
1464
1544
  // Promo
@@ -1551,18 +1631,9 @@ function persistLocale(locale) {
1551
1631
  }
1552
1632
  const I18nContext = createContext(null);
1553
1633
  function I18nProvider({ configLocale, children }) {
1554
- // Priority: configLocale (site owner) > persisted user choice > browser language > "de"
1555
- // If configLocale is set, the site owner has locked the language - don't restore user choice.
1556
- const [overrideLocale, setOverrideLocale] = useState(() => {
1557
- if (configLocale)
1558
- return null;
1559
- return readPersistedLocale();
1560
- });
1561
- useEffect(() => {
1562
- if (configLocale) {
1563
- setOverrideLocale(null);
1564
- }
1565
- }, [configLocale]);
1634
+ // Priority: persisted user choice > configLocale (org default) > browser language > "de"
1635
+ // This keeps org locale as default, but remembers explicit user overrides across reloads.
1636
+ const [overrideLocale, setOverrideLocale] = useState(() => readPersistedLocale());
1566
1637
  const locale = overrideLocale ?? resolveLocale(configLocale);
1567
1638
  const handleSetLocale = useCallback((next) => {
1568
1639
  persistLocale(next);
@@ -1694,129 +1765,153 @@ const resolveSemanticColor = (colorValue, fallbackValue) => {
1694
1765
  // If semantic resolution fails, use fallback or return the original value
1695
1766
  return fallbackValue || colorValue;
1696
1767
  };
1697
- // Predefined themes (modern, accessibility-tested)
1768
+ // Legacy theme name redirects (old name → new name)
1769
+ const legacyThemeRedirects = {
1770
+ "light-fresh": "teal-minimal",
1771
+ "light-elegant": "blue-business",
1772
+ "light-vibrant": "orange-raw",
1773
+ "light-professional": "blue-business",
1774
+ "dark-night": "navy-night",
1775
+ "dark-modern": "navy-night",
1776
+ "dark-forest": "green-deep",
1777
+ };
1778
+ // Predefined themes
1698
1779
  const themes = {
1699
1780
  // --- Light Themes ---
1700
- "light-fresh": {
1701
- highlight: "#00b1aa", // accent-strong
1702
- background: "#f8fdfe", // neutral-strong (background)
1703
- surface: "#ffffff", // card (pure white)
1704
- text: "#0e7490", // Turquoise 800
1705
- border: "#bae6fd", // Blue 200
1706
- success: "#38bdf8", // Blue 400
1707
- warning: "#fbbf24", // Amber 400
1708
- error: "#f43f5e", // Rose 500
1709
- borderRadius: "18px", // Very rounded corners
1781
+ "teal-minimal": {
1782
+ highlight: "#00b1aa",
1783
+ background: "#f8fdfe",
1784
+ surface: "#ffffff",
1785
+ text: "#0e7490",
1786
+ border: "#bae6fd",
1787
+ success: "#38bdf8",
1788
+ warning: "#fbbf24",
1789
+ error: "#f43f5e",
1790
+ borderRadius: "18px",
1710
1791
  fontFamily: "'Inter', system-ui, sans-serif",
1711
1792
  },
1712
- "light-elegant": {
1713
- highlight: "#8b5cf6", // Violet 500
1714
- background: "#f5f3ff", // Violet 50
1715
- surface: "#ffffff", // White
1716
- text: "#4c1d95", // Violet 900
1717
- border: "#ede9fe", // Violet 100
1718
- success: "#16a34a", // Green 600
1719
- warning: "#ca8a04", // Yellow 600
1720
- error: "#dc2626", // Red 600
1721
- borderRadius: "12px",
1722
- fontFamily: "'Playfair Display', serif",
1793
+ "blue-business": {
1794
+ highlight: "#2563eb",
1795
+ background: "#f8fafc",
1796
+ surface: "#ffffff",
1797
+ text: "#0f172a",
1798
+ border: "#cbd5e1",
1799
+ success: "#059669",
1800
+ warning: "#d97706",
1801
+ error: "#b91c1c",
1802
+ borderRadius: "6px",
1803
+ fontFamily: "'Plus Jakarta Sans', system-ui, sans-serif",
1723
1804
  },
1724
- "light-vibrant": {
1725
- highlight: "#ed702d", // blue-500 - bright blue accent
1726
- background: "#1f2630", // slate-900 - dark background
1727
- surface: "#1f2630", // slate-800 - dark cards
1728
- text: "#f1f5f9", // slate-100 - light text
1729
- border: "#ed702d", // slate-700 - subtle borders
1730
- success: "#22c55e", // green-500
1731
- warning: "#eab308", // yellow-500
1732
- error: "#ef4444", // red-500
1805
+ "orange-raw": {
1806
+ highlight: "#ed702d",
1807
+ background: "#1f2630",
1808
+ surface: "#1f2630",
1809
+ text: "#f1f5f9",
1810
+ border: "#ed702d",
1811
+ success: "#22c55e",
1812
+ warning: "#eab308",
1813
+ error: "#ef4444",
1733
1814
  borderRadius: "0px",
1734
1815
  fontFamily: "Inter, system-ui, sans-serif",
1735
1816
  },
1736
- "light-professional": {
1737
- highlight: "#2563eb", // Blue 600
1738
- background: "#f8fafc", // Slate 50
1739
- surface: "#ffffff", // White
1740
- text: "#1e293b", // Slate 800
1741
- border: "#e2e8f0", // Slate 200
1742
- success: "#059669", // Emerald 600
1743
- warning: "#d97706", // Amber 600
1744
- error: "#b91c1c", // Red 700
1745
- borderRadius: "4px",
1746
- fontFamily: "system-ui, -apple-system, sans-serif",
1817
+ // --- Dark Themes ---
1818
+ "navy-night": {
1819
+ highlight: "#60a5fa",
1820
+ background: "#0b1120",
1821
+ surface: "#111827",
1822
+ text: "#e2e8f0",
1823
+ border: "#1e3a5f",
1824
+ success: "#34d399",
1825
+ warning: "#fbbf24",
1826
+ error: "#f87171",
1827
+ borderRadius: "10px",
1828
+ fontFamily: "'Outfit', system-ui, sans-serif",
1747
1829
  },
1748
- "dark-night": {
1749
- highlight: "#3b82f6", // blue-500 - bright blue accent
1750
- background: "#0f172a", // slate-900 - dark background
1751
- surface: "#1e293b", // slate-800 - dark cards
1752
- text: "#f1f5f9", // slate-100 - light text
1753
- border: "#334155", // slate-700 - subtle borders
1754
- success: "#22c55e", // green-500
1755
- warning: "#eab308", // yellow-500
1756
- error: "#ef4444", // red-500
1757
- borderRadius: "8px",
1758
- fontFamily: "Inter, system-ui, sans-serif",
1759
- },
1760
- "dark-modern": {
1761
- highlight: "#3b82f6", // blue-500 - bright blue accent
1762
- background: "#0f172a", // slate-900 - dark background
1763
- surface: "#1e293b", // slate-800 - dark cards
1764
- text: "#f1f5f9", // slate-100 - light text
1765
- border: "#334155", // slate-700 - subtle borders
1766
- success: "#22c55e", // green-500
1767
- warning: "#eab308", // yellow-500
1768
- error: "#ef4444", // red-500
1769
- borderRadius: "8px",
1770
- fontFamily: "Inter, system-ui, sans-serif",
1771
- },
1772
- "dark-forest": {
1773
- highlight: "#34d399", // Emerald 400
1774
- background: "#05140d",
1775
- surface: "#062215",
1776
- text: "#d1fae5", // Emerald 100
1777
- border: "#043322",
1778
- success: "#4ade80", // Green 400
1779
- warning: "#facc15", // Yellow 400
1780
- error: "#f87171", // Red 400
1830
+ "green-deep": {
1831
+ highlight: "#34d399",
1832
+ background: "#030d07",
1833
+ surface: "#051a0e",
1834
+ text: "#d1fae5",
1835
+ border: "#064e20",
1836
+ success: "#4ade80",
1837
+ warning: "#facc15",
1838
+ error: "#f87171",
1781
1839
  borderRadius: "12px",
1782
- fontFamily: "system-ui, -apple-system, sans-serif",
1840
+ fontFamily: "'Instrument Sans', system-ui, sans-serif",
1783
1841
  },
1784
- "dark-matrix": {
1785
- highlight: "#33ff33",
1842
+ "green-matrix": {
1843
+ highlight: "#39ff14",
1786
1844
  background: "#000000",
1787
- surface: "#0a0a0a",
1788
- text: "#00ff00",
1789
- border: "#1a1a1a",
1790
- success: "#33ff33",
1845
+ surface: "#060f06",
1846
+ text: "#00ff41",
1847
+ border: "#0d2b0d",
1848
+ success: "#39ff14",
1791
1849
  warning: "#ffff00",
1792
1850
  error: "#ff3333",
1793
1851
  borderRadius: "0px",
1794
- fontFamily: "'Courier New', monospace",
1852
+ fontFamily: "'Share Tech Mono', monospace",
1795
1853
  },
1796
- "dark-luxury": {
1797
- highlight: "#fde047", // Yellow 300
1798
- background: "#1c1917", // Stone 900
1799
- surface: "#292524", // Stone 800
1800
- text: "#fafaf9", // Stone 50
1801
- border: "#44403c", // Stone 700
1802
- success: "#a3e635", // Lime 400
1803
- warning: "#f59e0b", // Amber 500
1804
- error: "#fca5a5", // Red 300
1854
+ "gold-luxury": {
1855
+ highlight: "#fde047",
1856
+ background: "#1c1917",
1857
+ surface: "#292524",
1858
+ text: "#fafaf9",
1859
+ border: "#44403c",
1860
+ success: "#a3e635",
1861
+ warning: "#f59e0b",
1862
+ error: "#fca5a5",
1805
1863
  borderRadius: "24px",
1806
- fontFamily: "'Cinzel', serif",
1864
+ fontFamily: "'Bodoni Moda', serif",
1807
1865
  },
1808
- "dark-energetic": {
1809
- highlight: "#d946ef", // Fuchsia 500
1810
- background: "#2e1046",
1811
- surface: "#401561",
1812
- text: "#f3e8ff", // Purple 50
1813
- border: "#581c87", // Purple 900
1814
- success: "#4ade80", // Green 400
1815
- warning: "#facc15", // Yellow 400
1816
- error: "#f87171", // Red 400
1866
+ "purple-electric": {
1867
+ highlight: "#d946ef",
1868
+ background: "#110820",
1869
+ surface: "#1a0d30",
1870
+ text: "#f3e8ff",
1871
+ border: "#3b0764",
1872
+ success: "#4ade80",
1873
+ warning: "#facc15",
1874
+ error: "#f87171",
1817
1875
  borderRadius: "14px",
1818
1876
  fontFamily: "'Geologica', sans-serif",
1819
1877
  },
1878
+ "dark-neon-brutalism": {
1879
+ highlight: "#00e5cc",
1880
+ background: "#0e1420",
1881
+ surface: "#0e1420",
1882
+ text: "#e8eef5",
1883
+ border: "#00e5cc",
1884
+ success: "#00e5cc",
1885
+ warning: "#ffe45e",
1886
+ error: "#ff4d6d",
1887
+ borderRadius: "4px",
1888
+ fontFamily: "'JetBrains Mono', monospace",
1889
+ buttonTextColor: "#0e1420",
1890
+ },
1891
+ "rose-editorial": {
1892
+ highlight: "#be3455",
1893
+ background: "#fdf8f5",
1894
+ surface: "#ffffff",
1895
+ text: "#1a0a0f",
1896
+ border: "#f2d5db",
1897
+ success: "#2d6a4f",
1898
+ warning: "#b5451b",
1899
+ error: "#9b1d20",
1900
+ borderRadius: "0px",
1901
+ fontFamily: "'Cormorant', serif",
1902
+ },
1903
+ "amber-retro": {
1904
+ highlight: "#f59e0b",
1905
+ background: "#1a1008",
1906
+ surface: "#241a0a",
1907
+ text: "#fef3c7",
1908
+ border: "#78350f",
1909
+ success: "#84cc16",
1910
+ warning: "#f59e0b",
1911
+ error: "#ef4444",
1912
+ borderRadius: "6px",
1913
+ fontFamily: "'Syne', sans-serif",
1914
+ },
1820
1915
  };
1821
1916
  const StyleProvider = ({ config, children, }) => {
1822
1917
  // Track hydration state to prevent mismatches
@@ -1826,8 +1921,10 @@ const StyleProvider = ({ config, children, }) => {
1826
1921
  }, []);
1827
1922
  // PERFORMANCE OPTIMIZATION: Memoize style calculations
1828
1923
  const themedStyles = useMemo(() => {
1829
- const themeName = config.theme || "light-fresh";
1830
- const themeDefaults = themes[themeName] || themes["light-fresh"];
1924
+ const rawThemeName = config.theme || "teal-minimal";
1925
+ // Redirect legacy theme names to new names
1926
+ const themeName = legacyThemeRedirects[rawThemeName] || rawThemeName;
1927
+ const themeDefaults = themes[themeName] || themes["teal-minimal"];
1831
1928
  const getCSSValue = (value, fallback) => {
1832
1929
  if (!value)
1833
1930
  return fallback;
@@ -1897,6 +1994,7 @@ const StyleProvider = ({ config, children, }) => {
1897
1994
  "--bw-surface-color": finalColors.surface,
1898
1995
  "--bw-text-color": finalColors.text,
1899
1996
  "--bw-text-muted": addOpacity(finalColors.text, 0.7),
1997
+ "--bw-button-text-color": themeDefaults.buttonTextColor || "#ffffff",
1900
1998
  "--bw-border-color": finalColors.border,
1901
1999
  "--bw-success-color": finalColors.success,
1902
2000
  "--bw-warning-color": finalColors.warning,
@@ -1914,7 +2012,7 @@ const StyleProvider = ({ config, children, }) => {
1914
2012
  "--bw-highlight-muted": addOpacity(finalColors.highlight, 0.1),
1915
2013
  "--bw-highlight-subtle": addOpacity(finalColors.highlight, 0.05),
1916
2014
  "--bw-text-subtle": addOpacity(finalColors.text, 0.4),
1917
- colorScheme: themeName.startsWith("dark-") || themeName === "dark-night" ? "dark" : "light",
2015
+ 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",
1918
2016
  };
1919
2017
  }, [
1920
2018
  config.theme,
@@ -4468,6 +4566,7 @@ function DialogWrapper({ isOpen, onClose, children, maxWidth = "700px", classNam
4468
4566
  "--bw-font-family": computedStyles.getPropertyValue("--bw-font-family").trim() || "system-ui, sans-serif",
4469
4567
  "--bw-shadow-md": computedStyles.getPropertyValue("--bw-shadow-md").trim() ||
4470
4568
  "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
4569
+ "--bw-button-text-color": computedStyles.getPropertyValue("--bw-button-text-color").trim() || "#ffffff",
4471
4570
  };
4472
4571
  setFallbackStyles(fallbacks);
4473
4572
  }
@@ -11117,16 +11216,37 @@ const objectType = ZodObject.create;
11117
11216
  ZodUnion.create;
11118
11217
  ZodIntersection.create;
11119
11218
  ZodTuple.create;
11120
- ZodEnum.create;
11219
+ const enumType = ZodEnum.create;
11121
11220
  ZodPromise.create;
11122
11221
  ZodOptional.create;
11123
11222
  ZodNullable.create;
11223
+ const preprocessType = ZodEffects.createWithPreprocess;
11124
11224
 
11125
- const participantSchema = (t) => objectType({
11126
- name: stringType().trim().min(1, t("validation.nameRequired")),
11225
+ const DEFAULT_PARTICIPANT_FIELDS_CONFIG = {
11226
+ name: { enabled: true, required: true },
11227
+ age: { enabled: true, required: false },
11228
+ level: { enabled: false, required: false },
11229
+ };
11230
+ const participantSchema = (t, fieldsConfig) => objectType({
11231
+ name: stringType().trim().optional(),
11127
11232
  age: numberType().min(0).max(120).optional(),
11233
+ level: preprocessType((value) => (value === "" ? undefined : value), enumType(["beginner", "advanced"]).optional()),
11234
+ })
11235
+ .superRefine((value, ctx) => {
11236
+ if (fieldsConfig.name.required && (!value.name || value.name.trim().length < 1)) {
11237
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.nameRequired"), path: ["name"] });
11238
+ }
11239
+ if (!fieldsConfig.name.enabled && value.name && value.name.trim().length > 0) {
11240
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.nameRequired"), path: ["name"] });
11241
+ }
11242
+ if (fieldsConfig.age.required && typeof value.age !== "number") {
11243
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.ageRequired"), path: ["age"] });
11244
+ }
11245
+ if (fieldsConfig.level.required && !value.level) {
11246
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.levelRequired"), path: ["level"] });
11247
+ }
11128
11248
  });
11129
- function createBookingFormSchema(t) {
11249
+ function createBookingFormSchema(t, fieldsConfig = DEFAULT_PARTICIPANT_FIELDS_CONFIG) {
11130
11250
  const tr = t ?? ((key) => key);
11131
11251
  return objectType({
11132
11252
  customerName: stringType().trim().min(2, tr("validation.nameMinLength")),
@@ -11136,7 +11256,7 @@ function createBookingFormSchema(t) {
11136
11256
  .email(tr("validation.emailInvalid"))
11137
11257
  .regex(/\.[a-zA-Z]{2,}$/, tr("validation.emailDomainInvalid")),
11138
11258
  customerPhone: stringType().trim().optional(),
11139
- participants: arrayType(participantSchema(tr)).min(1, tr("validation.participantRequired")),
11259
+ participants: arrayType(participantSchema(tr, fieldsConfig)).min(1, tr("validation.participantRequired")),
11140
11260
  discountCode: stringType().trim().optional(),
11141
11261
  comment: stringType().trim().optional(),
11142
11262
  acceptTerms: booleanType().refine((val) => val === true, {
@@ -11177,11 +11297,13 @@ const buttonBase = {
11177
11297
  whiteSpace: "nowrap",
11178
11298
  border: "none",
11179
11299
  };
11300
+ // CSS class name for button hover effects
11301
+ const buttonClassName = "bw-button-hover";
11180
11302
  const buttonStyles = {
11181
11303
  primary: {
11182
11304
  ...buttonBase,
11183
11305
  backgroundColor: "var(--bw-highlight-color)",
11184
- color: "#ffffff",
11306
+ color: "var(--bw-button-text-color, #ffffff)",
11185
11307
  border: "none",
11186
11308
  },
11187
11309
  secondary: {
@@ -11277,7 +11399,8 @@ const participantUpsellStyles = {
11277
11399
  gap: "8px",
11278
11400
  marginTop: "10px",
11279
11401
  paddingTop: "10px",
11280
- borderTop: "1px dashed var(--bw-border-color)",
11402
+ paddingBottom: "25px",
11403
+ borderBottom: "1px dashed var(--bw-border-color)",
11281
11404
  },
11282
11405
  label: {
11283
11406
  display: "inline-flex",
@@ -11335,6 +11458,8 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11335
11458
  const { locale } = useLocale();
11336
11459
  const timezone = useTimezone();
11337
11460
  const roundEnabled = systemConfig?.roundPricesEnabled !== false;
11461
+ const participantFieldsConfig = eventDetails.participantFieldsConfig ?? DEFAULT_PARTICIPANT_FIELDS_CONFIG;
11462
+ const participantLevelOptions = eventDetails.participantLevelOptions ?? ["beginner", "advanced"];
11338
11463
  const roundDiscountUp = (minorUnits) => Math.ceil(minorUnits / 100) * 100;
11339
11464
  const calcPercentDiscountAmount = (baseAmount, basisPoints, round) => {
11340
11465
  const raw = Math.round((baseAmount * basisPoints) / 10000);
@@ -11347,18 +11472,19 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11347
11472
  // Per-participant upsell selections: participantIndex -> array of upsell package IDs
11348
11473
  const [participantUpsells, setParticipantUpsells] = useState({});
11349
11474
  const form = useForm({
11350
- resolver: t(createBookingFormSchema(t$1)),
11475
+ resolver: t(createBookingFormSchema(t$1, participantFieldsConfig)),
11351
11476
  defaultValues: {
11352
11477
  customerName: "",
11353
11478
  customerEmail: "",
11354
11479
  customerPhone: "",
11355
- participants: [{ name: "" }],
11480
+ participants: [{ name: "", level: undefined }],
11356
11481
  discountCode: "",
11357
11482
  comment: "",
11358
11483
  acceptTerms: false,
11359
11484
  },
11360
11485
  });
11361
11486
  const watchedParticipants = form.watch("participants");
11487
+ const participantCount = watchedParticipants.length;
11362
11488
  const watchedCustomerName = form.watch("customerName");
11363
11489
  const watchedCustomerEmail = form.watch("customerEmail");
11364
11490
  const watchedComment = form.watch("comment");
@@ -11400,14 +11526,13 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11400
11526
  const calculateBaseTotal = useCallback(() => {
11401
11527
  if (!eventDetails)
11402
11528
  return 0;
11403
- return eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length;
11404
- }, [eventDetails, watchedParticipants]);
11529
+ return eventDetails.price * participantCount;
11530
+ }, [eventDetails, participantCount]);
11405
11531
  // Calculate upsells total based on per-participant selections
11406
11532
  const calculateUpsellsTotal = useCallback(() => {
11407
11533
  let total = 0;
11408
- watchedParticipants.forEach((participant, index) => {
11409
- // Only count upsells for participants with names
11410
- if (participant.name.trim()) {
11534
+ watchedParticipants.forEach((_, index) => {
11535
+ if (participantCount > 0) {
11411
11536
  const participantUpsellIds = participantUpsells[index] || [];
11412
11537
  participantUpsellIds.forEach(upsellId => {
11413
11538
  const upsell = upsells.find(u => u.id === upsellId);
@@ -11418,7 +11543,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11418
11543
  }
11419
11544
  });
11420
11545
  return total;
11421
- }, [participantUpsells, upsells, watchedParticipants]);
11546
+ }, [participantUpsells, upsells, watchedParticipants, participantCount]);
11422
11547
  const calculateTotalDiscount = useCallback(() => {
11423
11548
  return appliedVouchers.reduce((total, voucher) => {
11424
11549
  if (voucher.type === "discount") {
@@ -11439,8 +11564,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11439
11564
  const calculateDeposit = () => {
11440
11565
  if (!eventDetails || !eventDetails.deposit)
11441
11566
  return 0;
11442
- const participantCount = watchedParticipants.filter((p) => p.name.trim()).length;
11443
- return eventDetails.deposit * participantCount;
11567
+ return eventDetails.deposit * watchedParticipants.length;
11444
11568
  };
11445
11569
  const baseTotal = calculateBaseTotal();
11446
11570
  const upsellsTotal = calculateUpsellsTotal();
@@ -11457,8 +11581,8 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11457
11581
  // Includes participantIndices to track which participants selected each upsell
11458
11582
  const aggregatedUpsellSelections = useCallback(() => {
11459
11583
  const upsellParticipantMap = {};
11460
- watchedParticipants.forEach((participant, index) => {
11461
- if (participant.name.trim()) {
11584
+ watchedParticipants.forEach((_, index) => {
11585
+ if (participantCount > 0) {
11462
11586
  const participantUpsellIds = participantUpsells[index] || [];
11463
11587
  participantUpsellIds.forEach(upsellId => {
11464
11588
  if (!upsellParticipantMap[upsellId]) {
@@ -11492,15 +11616,17 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11492
11616
  setAppliedVouchers((prev) => prev.filter((v) => v.code !== code));
11493
11617
  }, []);
11494
11618
  const isReadyForPayment = () => {
11495
- const participantsWithNames = watchedParticipants.filter((p) => p.name.trim()).length;
11619
+ const participantsWithNames = watchedParticipants.filter((p) => p.name?.trim()).length;
11496
11620
  const totalParticipantRows = watchedParticipants.length;
11497
- const allParticipantsHaveNames = participantsWithNames === totalParticipantRows;
11621
+ const allParticipantsHaveNames = participantFieldsConfig.name.required
11622
+ ? participantsWithNames === totalParticipantRows
11623
+ : true;
11498
11624
  const participantsWithinLimit = participantsWithNames <= (eventDetails?.availableSpots || 0);
11499
11625
  const hasValidCustomerName = watchedCustomerName && watchedCustomerName.trim().length >= 2;
11500
11626
  const hasValidCustomerEmail = watchedCustomerEmail && watchedCustomerEmail.trim().length > 0 && !customerEmailError;
11501
11627
  return allParticipantsHaveNames &&
11502
11628
  participantsWithinLimit &&
11503
- participantsWithNames > 0 &&
11629
+ (participantFieldsConfig.name.required ? participantsWithNames > 0 : totalParticipantRows > 0) &&
11504
11630
  hasValidCustomerName &&
11505
11631
  hasValidCustomerEmail &&
11506
11632
  watchedAcceptTerms;
@@ -11508,7 +11634,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11508
11634
  useEffect(() => {
11509
11635
  if (appliedVouchers.length > 0) {
11510
11636
  const newBaseTotal = eventDetails?.price
11511
- ? eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length
11637
+ ? eventDetails.price * watchedParticipants.length
11512
11638
  : 0;
11513
11639
  const currentUpsellsTotal = calculateUpsellsTotal();
11514
11640
  const orderTotal = newBaseTotal + currentUpsellsTotal;
@@ -11553,7 +11679,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11553
11679
  const currentParticipants = form.getValues("participants");
11554
11680
  const availableSpots = eventDetails?.availableSpots || 0;
11555
11681
  if (currentParticipants.length < availableSpots) {
11556
- form.setValue("participants", [...currentParticipants, { name: "" }]);
11682
+ form.setValue("participants", [...currentParticipants, { name: "", level: undefined }]);
11557
11683
  }
11558
11684
  else {
11559
11685
  alert(t$1("booking.maxParticipants", { count: availableSpots }));
@@ -11672,7 +11798,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11672
11798
  justifyContent: "space-between",
11673
11799
  alignItems: "center",
11674
11800
  marginBottom: "16px",
11675
- }, children: jsx("h2", { style: { ...sectionHeaderStyles$1, marginBottom: 0 }, children: t$1("booking.participants") }) }), jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [watchedParticipants.map((_, index) => (jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [jsxs("div", { style: { display: "flex", gap: "12px", alignItems: "center" }, children: [jsxs("div", { style: { flex: 1 }, children: [jsx("label", { htmlFor: `participant-name-${index}`, style: labelStyles$1, children: t$1("booking.participantName") }), jsx("input", { id: `participant-name-${index}`, ...form.register(`participants.${index}.name`), type: "text", style: inputStyles$1, placeholder: t$1("booking.participantNamePlaceholder") }), form.formState.errors.participants?.[index]?.name && (jsx("p", { style: errorTextStyles$1, children: form.formState.errors.participants[index]?.name?.message }))] }), jsxs("div", { style: { width: "80px" }, children: [jsx("label", { htmlFor: `participant-age-${index}`, style: labelStyles$1, children: t$1("booking.participantAge") }), jsx("input", { id: `participant-age-${index}`, ...form.register(`participants.${index}.age`, {
11801
+ }, children: jsx("h2", { style: { ...sectionHeaderStyles$1, marginBottom: 0 }, children: t$1("booking.participants") }) }), jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [watchedParticipants.map((_, index) => (jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [jsxs("div", { style: { display: "flex", gap: "12px", alignItems: "center" }, children: [participantFieldsConfig.name.enabled && (jsxs("div", { style: { flex: 1 }, children: [jsx("label", { htmlFor: `participant-name-${index}`, style: labelStyles$1, children: t$1("booking.participantName") }), jsx("input", { id: `participant-name-${index}`, ...form.register(`participants.${index}.name`), type: "text", style: inputStyles$1, placeholder: t$1("booking.participantNamePlaceholder") }), form.formState.errors.participants?.[index]?.name && (jsx("p", { style: errorTextStyles$1, children: form.formState.errors.participants[index]?.name?.message }))] })), participantFieldsConfig.age.enabled && (jsxs("div", { style: { width: "80px" }, children: [jsx("label", { htmlFor: `participant-age-${index}`, style: labelStyles$1, children: t$1("booking.participantAge") }), jsx("input", { id: `participant-age-${index}`, ...form.register(`participants.${index}.age`, {
11676
11802
  setValueAs: (value) => {
11677
11803
  if (value === "" || value === null || value === undefined) {
11678
11804
  return undefined;
@@ -11680,7 +11806,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11680
11806
  const num = Number(value);
11681
11807
  return Number.isNaN(num) ? undefined : num;
11682
11808
  },
11683
- }), type: "number", min: "0", max: "120", style: inputStyles$1, placeholder: "25" })] }), watchedParticipants.length > 1 && (jsxs("div", { children: [jsx("label", { style: { ...labelStyles$1, visibility: "hidden" }, children: "\u00A0" }), jsx("button", { type: "button", onClick: () => removeParticipant(index), style: {
11809
+ }), type: "number", min: "0", max: "120", style: inputStyles$1, placeholder: "25" })] })), watchedParticipants.length > 1 && (jsxs("div", { children: [jsx("label", { style: { ...labelStyles$1, visibility: "hidden" }, children: "\u00A0" }), jsx("button", { type: "button", onClick: () => removeParticipant(index), style: {
11684
11810
  color: "var(--bw-error-color)",
11685
11811
  backgroundColor: "var(--bw-surface-color)",
11686
11812
  border: "1px solid var(--bw-border-color)",
@@ -11696,7 +11822,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11696
11822
  fontWeight: 700,
11697
11823
  fontFamily: "var(--bw-font-family)",
11698
11824
  padding: 0,
11699
- }, children: "\u00D7" })] }))] }), upsells.length > 0 && (jsx("div", { style: participantUpsellStyles.container, children: upsells.map((upsell) => {
11825
+ }, children: "\u00D7" })] }))] }), participantFieldsConfig.level.enabled && (jsxs("div", { style: { minWidth: "140px" }, children: [jsx("label", { htmlFor: `participant-level-${index}`, style: labelStyles$1, children: t$1("booking.participantLevel") }), jsxs("select", { id: `participant-level-${index}`, ...form.register(`participants.${index}.level`), style: inputStyles$1, children: [jsx("option", { value: "", children: t$1("booking.participantLevelPlaceholder") }), participantLevelOptions.map((level) => (jsx("option", { value: level, children: t$1(`level.${level}`) }, level)))] }), form.formState.errors.participants?.[index]?.level && (jsx("p", { style: errorTextStyles$1, children: form.formState.errors.participants[index]?.level?.message }))] })), upsells.length > 0 && (jsx("div", { style: participantUpsellStyles.container, children: upsells.map((upsell) => {
11700
11826
  const isSelected = (participantUpsells[index] || []).includes(upsell.id);
11701
11827
  return (jsxs("label", { htmlFor: `upsell-${index}-${upsell.id}`, style: isSelected ? participantUpsellStyles.labelSelected : participantUpsellStyles.label, children: [jsx("input", { id: `upsell-${index}-${upsell.id}`, type: "checkbox", style: participantUpsellStyles.checkbox, checked: isSelected, onChange: () => toggleParticipantUpsell(index, upsell.id) }), jsx("span", { style: { fontWeight: 500 }, children: upsell.name }), jsxs("span", { style: { fontSize: "12px", opacity: 0.8 }, children: ["(+", formatCurrency(upsell.price), ")"] })] }, upsell.id));
11702
11828
  }) }))] }, index))), watchedParticipants.length < eventDetails.availableSpots ? (jsx("div", { style: {
@@ -11725,9 +11851,9 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11725
11851
  color: "var(--bw-text-color)",
11726
11852
  fontWeight: 500,
11727
11853
  fontFamily: "var(--bw-font-family)",
11728
- }, children: [jsxs("span", { style: { fontWeight: 200 }, children: [watchedParticipants.length > 1 ? watchedParticipants.filter((p) => p.name.trim()).length : 1, " x "] }), " ", formatCurrency(eventDetails.price)] })] }), upsellsTotal > 0 && (jsxs("div", { style: { marginTop: "8px", paddingTop: "8px", borderTop: "1px dashed var(--bw-border-color)" }, children: [jsxs("span", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "13px", display: "block", marginBottom: "4px" }, children: [t$1("common.extras"), ":"] }), upsells.map((upsell) => {
11854
+ }, children: [jsxs("span", { style: { fontWeight: 200 }, children: [watchedParticipants.length > 1 ? watchedParticipants.length : 1, " x "] }), " ", formatCurrency(eventDetails.price)] })] }), upsellsTotal > 0 && (jsxs("div", { style: { marginTop: "8px", paddingTop: "8px", borderTop: "1px dashed var(--bw-border-color)" }, children: [jsxs("span", { style: { color: "var(--bw-text-muted)", fontFamily: "var(--bw-font-family)", fontSize: "13px", display: "block", marginBottom: "4px" }, children: [t$1("common.extras"), ":"] }), upsells.map((upsell) => {
11729
11855
  // Count how many participants have this upsell selected
11730
- const countWithUpsell = watchedParticipants.filter((p, idx) => p.name.trim() && (participantUpsells[idx] || []).includes(upsell.id)).length;
11856
+ const countWithUpsell = watchedParticipants.filter((_, idx) => (participantUpsells[idx] || []).includes(upsell.id)).length;
11731
11857
  if (countWithUpsell === 0)
11732
11858
  return null;
11733
11859
  const upsellLineTotal = upsell.price * countWithUpsell;
@@ -11828,15 +11954,17 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11828
11954
  }, children: t$1("summary.remainingOnSite", { amount: formatCurrency(totalAmount - depositAmount) }) }))] })] })] }), jsx("div", { ref: paymentSectionRef, children: (stripePromise || systemConfig?.paymentProvider === "mollie") &&
11829
11955
  (() => {
11830
11956
  if (!isReadyForPayment()) {
11831
- const participantsWithNames = watchedParticipants.filter((p) => p.name.trim()).length;
11957
+ const participantsWithNames = watchedParticipants.filter((p) => p.name?.trim()).length;
11832
11958
  const totalParticipantRows = watchedParticipants.length;
11833
11959
  const participantsWithoutNames = totalParticipantRows - participantsWithNames;
11834
11960
  const missing = [];
11835
- if (participantsWithNames === 0) {
11836
- missing.push(t$1("payment.needParticipant"));
11837
- }
11838
- else if (participantsWithoutNames > 0) {
11839
- missing.push(t$1("payment.needAllNames", { count: totalParticipantRows }));
11961
+ if (participantFieldsConfig.name.required) {
11962
+ if (participantsWithNames === 0) {
11963
+ missing.push(t$1("payment.needParticipant"));
11964
+ }
11965
+ else if (participantsWithoutNames > 0) {
11966
+ missing.push(t$1("payment.needAllNames", { count: totalParticipantRows }));
11967
+ }
11840
11968
  }
11841
11969
  if (participantsWithNames > (eventDetails?.availableSpots || 0)) {
11842
11970
  missing.push(t$1("payment.reduceParticipants", { count: eventDetails?.availableSpots || 0 }));
@@ -12011,7 +12139,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
12011
12139
  try {
12012
12140
  const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/get-booking-by-payment"), {
12013
12141
  method: "POST",
12014
- headers: createApiHeaders(config),
12142
+ headers: createApiHeaders(config, locale),
12015
12143
  body: JSON.stringify(createRequestBody(config, {
12016
12144
  paymentIntentId: targetPaymentIntentId,
12017
12145
  })),
@@ -12245,7 +12373,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
12245
12373
  flexDirection: "column",
12246
12374
  gap: "var(--bw-spacing-small)",
12247
12375
  }, children: formData.participants
12248
- .filter((p) => p.name.trim())
12376
+ .filter((p) => p.name?.trim() || p.age || p.level)
12249
12377
  .map((participant, index) => (jsx("div", { className: "print-participant", style: {
12250
12378
  display: "flex",
12251
12379
  justifyContent: "space-between",
@@ -12256,11 +12384,15 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
12256
12384
  }, children: jsxs("div", { className: "print-participant-info", children: [jsx("div", { className: "print-participant-name", style: {
12257
12385
  color: "var(--bw-text-color)",
12258
12386
  fontFamily: "var(--bw-font-family)",
12259
- }, children: participant.name }), participant.age && (jsx("div", { className: "print-participant-age", style: {
12387
+ }, children: participant.name || `#${index + 1}` }), participant.age && (jsx("div", { className: "print-participant-age", style: {
12260
12388
  color: "var(--bw-text-muted)",
12261
12389
  fontSize: "var(--bw-font-size-small)",
12262
12390
  fontFamily: "var(--bw-font-family)",
12263
- }, children: t("success.age", { age: participant.age }) }))] }) }, index))) }) })] })), jsxs("div", { className: "print-booking-card", style: {
12391
+ }, children: t("success.age", { age: participant.age }) })), participant.level && (jsxs("div", { style: {
12392
+ color: "var(--bw-text-muted)",
12393
+ fontSize: "var(--bw-font-size-small)",
12394
+ fontFamily: "var(--bw-font-family)",
12395
+ }, children: [t("booking.participantLevel"), ": ", t(`level.${participant.level}`)] }))] }) }, index))) }) })] })), jsxs("div", { className: "print-booking-card", style: {
12264
12396
  backgroundColor: "var(--bw-surface-color)",
12265
12397
  border: `1px solid var(--bw-border-color)`,
12266
12398
  borderRadius: "var(--bw-border-radius)",
@@ -13668,8 +13800,48 @@ function formatDurationInfo(info, t) {
13668
13800
  const rest = formatted.slice(0, -1).join(", ");
13669
13801
  return `${t("duration.optionally")} ${rest} ${t("duration.or")} ${last} ${unitPlural}`;
13670
13802
  }
13671
- function EventTypeSelection({ eventTypes, onEventTypeSelect, isLoading = false, skeletonCount = 4, showVoucherAttachment = false, onVoucherClick, }) {
13803
+ function InfoBadge({ text }) {
13804
+ const [open, setOpen] = useState(false);
13805
+ const ref = useRef(null);
13806
+ return (jsxs("span", { ref: ref, onClick: (e) => { e.stopPropagation(); setOpen((v) => !v); }, style: {
13807
+ position: "relative",
13808
+ display: "inline-flex",
13809
+ alignItems: "center",
13810
+ justifyContent: "center",
13811
+ width: "16px",
13812
+ height: "16px",
13813
+ borderRadius: "50%",
13814
+ border: "1px solid var(--bw-highlight-color)",
13815
+ color: "var(--bw-highlight-color)",
13816
+ fontSize: "9px",
13817
+ fontWeight: 700,
13818
+ cursor: "pointer",
13819
+ flexShrink: 0,
13820
+ userSelect: "none",
13821
+ }, children: ["i", open && (jsx("span", { style: {
13822
+ position: "absolute",
13823
+ bottom: "calc(100% + 6px)",
13824
+ right: 0,
13825
+ backgroundColor: "var(--bw-surface-color)",
13826
+ border: "1px solid var(--bw-border-color)",
13827
+ borderRadius: "var(--bw-border-radius-small)",
13828
+ boxShadow: "var(--bw-shadow-md)",
13829
+ padding: "6px 10px",
13830
+ fontSize: "14px",
13831
+ color: "var(--bw-text-color)",
13832
+ fontWeight: 400,
13833
+ whiteSpace: "normal",
13834
+ width: "160px",
13835
+ lineHeight: 1.4,
13836
+ zIndex: 100,
13837
+ textAlign: "left",
13838
+ pointerEvents: "none",
13839
+ }, children: text }))] }));
13840
+ }
13841
+ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview, isLoading = false, skeletonCount = 4, showVoucherAttachment = false, onVoucherClick, }) {
13672
13842
  const t = useTranslations();
13843
+ const { locale } = useLocale();
13844
+ const timezone = useTimezone();
13673
13845
  // State for details dialog
13674
13846
  const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
13675
13847
  const [selectedEventTypeForDetails, setSelectedEventTypeForDetails] = useState(null);
@@ -13779,7 +13951,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, isLoading = false,
13779
13951
  display: "flex",
13780
13952
  flexDirection: "column",
13781
13953
  justifyContent: "space-between",
13782
- height: "400px",
13954
+ height: "490px",
13783
13955
  }, children: [jsxs("div", { children: [jsx("h2", { className: "event-type-title", style: {
13784
13956
  fontSize: "clamp(1.1rem, 2.5vw, 24px)",
13785
13957
  fontWeight: 700,
@@ -13860,7 +14032,43 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, isLoading = false,
13860
14032
  color: "var(--bw-text-color)",
13861
14033
  fontFamily: "var(--bw-font-family)",
13862
14034
  textAlign: "right",
13863
- }, children: jsxs("span", { children: [t("common.from"), " ", formatCurrency(eventType.minPrice)] }) })] }), jsxs("div", { style: {
14035
+ }, children: jsxs("span", { children: [t("common.from"), " ", formatCurrency(eventType.minPrice)] }) })] }), (() => {
14036
+ const preview = eventType.cardPreview ?? [];
14037
+ return (jsxs("div", { style: {
14038
+ marginTop: "12px",
14039
+ borderTop: "1px solid var(--bw-border-color)",
14040
+ paddingTop: "8px",
14041
+ marginBottom: "16px",
14042
+ }, children: [jsx("div", { style: {
14043
+ fontSize: "11px",
14044
+ fontWeight: 700,
14045
+ color: "var(--bw-text-muted)",
14046
+ textTransform: "uppercase",
14047
+ letterSpacing: "0.05em",
14048
+ marginBottom: "4px",
14049
+ }, children: t("events.previewSectionTitle") }), jsx("div", { style: { height: "102px" }, children: Array.from({ length: 3 }).map((_, i) => {
14050
+ const item = preview[i];
14051
+ if (!item)
14052
+ return jsx("div", { style: { height: "34px" } }, i);
14053
+ const hasDiscount = item.basePrice > 0 && item.price < item.basePrice;
14054
+ return (jsxs("div", { onClick: (e) => { e.stopPropagation(); onInstancePreview?.(item.id, eventType.id); }, onMouseEnter: (e) => { if (onInstancePreview)
14055
+ e.currentTarget.style.backgroundColor = "var(--bw-border-color)"; }, onMouseLeave: (e) => { e.currentTarget.style.backgroundColor = "transparent"; }, style: {
14056
+ height: "34px",
14057
+ display: "grid",
14058
+ gridTemplateColumns: "auto 1fr auto 20px",
14059
+ alignItems: "center",
14060
+ gap: "8px",
14061
+ width: "100%",
14062
+ fontSize: "13px",
14063
+ color: "var(--bw-text-muted)",
14064
+ cursor: onInstancePreview ? "pointer" : "default",
14065
+ borderRadius: "4px",
14066
+ padding: "0 2px",
14067
+ transition: "background 0.15s",
14068
+ boxSizing: "border-box",
14069
+ }, 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));
14070
+ }) })] }));
14071
+ })(), jsxs("div", { style: {
13864
14072
  display: "flex",
13865
14073
  justifyContent: "flex-end",
13866
14074
  alignItems: "center",
@@ -13875,7 +14083,6 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, isLoading = false,
13875
14083
  backgroundColor: "var(--bw-surface-color)",
13876
14084
  padding: "12px",
13877
14085
  borderRadius: "var(--bw-border-radius)",
13878
- fontSize: "clamp(0.8rem, 2vw, 16px)",
13879
14086
  fontWeight: 600,
13880
14087
  fontFamily: "var(--bw-font-family)",
13881
14088
  display: "flex",
@@ -13888,7 +14095,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, isLoading = false,
13888
14095
  }, children: t("button.moreDetails") })), isAvailable && (jsxs("div", { style: {
13889
14096
  backgroundColor: "var(--bw-highlight-color)",
13890
14097
  color: "var(--bw-surface-color)",
13891
- padding: "12px 24px",
14098
+ padding: "12px 14px",
13892
14099
  borderRadius: "var(--bw-border-radius)",
13893
14100
  fontSize: "clamp(1rem, 2vw, 16px)",
13894
14101
  fontWeight: 600,
@@ -14546,6 +14753,172 @@ function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText
14546
14753
  }, children: "\u27F3" }), t("common.loading")] })) : (showAllButtonText) }) }))] }));
14547
14754
  }
14548
14755
 
14756
+ function SpecialsView({ specials, onEventSelect, isLoading = false, showSavingsAmount = true, showSavingsPercent = false, emptyStateText, isLoadingEventDetails = false, }) {
14757
+ const t = useTranslations();
14758
+ const { locale } = useLocale();
14759
+ const timezone = useTimezone();
14760
+ const [selectedId, setSelectedId] = useState(null);
14761
+ const handleSelect = (id) => {
14762
+ setSelectedId(id);
14763
+ onEventSelect(id);
14764
+ };
14765
+ if (isLoading) {
14766
+ return jsx(NextEventsSkeleton, { count: 3 });
14767
+ }
14768
+ if (specials.length === 0) {
14769
+ return (jsx("div", { style: { maxWidth: "500px", margin: "0 auto", padding: "16px" }, children: jsxs("div", { style: {
14770
+ display: "flex",
14771
+ flexDirection: "column",
14772
+ alignItems: "center",
14773
+ justifyContent: "center",
14774
+ textAlign: "center",
14775
+ backgroundColor: "var(--bw-surface-color)",
14776
+ border: "1px solid var(--bw-border-color)",
14777
+ borderRadius: "var(--bw-border-radius)",
14778
+ padding: "24px",
14779
+ fontFamily: "var(--bw-font-family)",
14780
+ minHeight: "300px",
14781
+ }, children: [jsx("div", { style: {
14782
+ display: "flex",
14783
+ alignItems: "center",
14784
+ justifyContent: "center",
14785
+ borderRadius: "50%",
14786
+ width: "64px",
14787
+ height: "64px",
14788
+ backgroundColor: "var(--bw-highlight-color)",
14789
+ marginBottom: "16px",
14790
+ fontSize: "32px",
14791
+ color: "#ffffff",
14792
+ opacity: 0.8,
14793
+ }, children: "\uD83C\uDFF7\uFE0F" }), jsx("h3", { style: {
14794
+ fontWeight: 600,
14795
+ margin: "0 0 8px 0",
14796
+ fontSize: "20px",
14797
+ color: "var(--bw-text-color)",
14798
+ fontFamily: "var(--bw-font-family)",
14799
+ }, children: t("specials.noSpecials") }), jsx("p", { style: {
14800
+ margin: 0,
14801
+ color: "var(--bw-text-muted)",
14802
+ fontSize: "16px",
14803
+ lineHeight: 1.6,
14804
+ fontFamily: "var(--bw-font-family)",
14805
+ maxWidth: "400px",
14806
+ }, children: emptyStateText ?? t("specials.noSpecialsMessage") })] }) }));
14807
+ }
14808
+ return (jsxs("div", { style: {
14809
+ maxWidth: "500px",
14810
+ margin: "0 auto",
14811
+ padding: "16px",
14812
+ fontFamily: "var(--bw-font-family)",
14813
+ }, children: [jsxs("div", { style: { textAlign: "center", marginBottom: "24px" }, children: [jsx("h2", { style: {
14814
+ fontWeight: 600,
14815
+ margin: "0 0 8px 0",
14816
+ fontSize: "18px",
14817
+ color: "var(--bw-text-color)",
14818
+ fontFamily: "var(--bw-font-family)",
14819
+ }, children: t("specials.title") }), jsx("p", { style: {
14820
+ margin: 0,
14821
+ fontSize: "16px",
14822
+ color: "var(--bw-text-muted)",
14823
+ fontFamily: "var(--bw-font-family)",
14824
+ }, children: t("specials.subtitle") })] }), jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: specials.map((special) => {
14825
+ const isFullyBooked = special.availableSpots === 0;
14826
+ const isDisabled = isFullyBooked || !special.bookingOpen;
14827
+ const hasDiscount = special.basePrice > 0 && special.price < special.basePrice;
14828
+ return (jsxs("div", { style: {
14829
+ position: "relative",
14830
+ backgroundColor: "var(--bw-surface-color)",
14831
+ borderRadius: "var(--bw-border-radius)",
14832
+ border: "1px solid var(--bw-highlight-color)",
14833
+ overflow: "hidden",
14834
+ opacity: isDisabled ? 0.5 : 1,
14835
+ cursor: isDisabled ? "not-allowed" : "pointer",
14836
+ transition: "all 0.2s ease",
14837
+ }, onClick: () => {
14838
+ if (!isDisabled)
14839
+ handleSelect(special.id);
14840
+ }, children: [selectedId === special.id && isLoadingEventDetails && (jsx("div", { style: {
14841
+ position: "absolute",
14842
+ inset: 0,
14843
+ display: "flex",
14844
+ alignItems: "center",
14845
+ justifyContent: "center",
14846
+ backgroundColor: "rgba(15, 23, 42, 0.8)",
14847
+ borderRadius: "var(--bw-border-radius)",
14848
+ zIndex: 10,
14849
+ }, children: jsx("div", { style: { fontSize: "32px", color: "var(--bw-highlight-color)", animation: "spin 1s linear infinite" }, children: "\u27F3" }) })), jsxs("div", { style: { display: "flex", gap: "12px", padding: "12px" }, children: [special.images.length > 0 && (jsx("div", { style: {
14850
+ flexShrink: 0,
14851
+ width: "72px",
14852
+ height: "72px",
14853
+ borderRadius: "var(--bw-border-radius-small)",
14854
+ overflow: "hidden",
14855
+ }, children: jsx("img", { src: special.images[0], alt: special.eventTypeName, style: { width: "100%", height: "100%", objectFit: "cover" } }) })), jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [jsx("div", { style: { marginBottom: "4px" }, children: jsx("span", { style: {
14856
+ fontSize: "11px",
14857
+ fontWeight: 600,
14858
+ color: "var(--bw-text-muted)",
14859
+ textTransform: "uppercase",
14860
+ letterSpacing: "0.05em",
14861
+ }, children: special.categoryName }) }), jsx("h4", { style: {
14862
+ margin: "0 0 2px 0",
14863
+ fontSize: "15px",
14864
+ fontWeight: 600,
14865
+ color: "var(--bw-text-color)",
14866
+ lineHeight: 1.3,
14867
+ overflow: "hidden",
14868
+ textOverflow: "ellipsis",
14869
+ whiteSpace: "nowrap",
14870
+ }, children: special.eventTypeName }), jsxs("div", { style: {
14871
+ fontSize: "13px",
14872
+ fontWeight: 500,
14873
+ color: "var(--bw-highlight-color)",
14874
+ marginBottom: "2px",
14875
+ overflow: "hidden",
14876
+ textOverflow: "ellipsis",
14877
+ whiteSpace: "nowrap",
14878
+ }, children: ["\u2605 ", special.name] }), special.specialDescription && (jsx("div", { style: {
14879
+ fontSize: "12px",
14880
+ color: "var(--bw-text-muted)",
14881
+ marginBottom: "6px",
14882
+ lineHeight: 1.4,
14883
+ }, children: special.specialDescription })), jsxs("div", { style: { fontSize: "13px", color: "var(--bw-text-muted)", marginBottom: "8px" }, children: [formatWeekday(special.startTime, timezone, locale), ",", " ", formatDate(special.startTime, timezone, locale)] }), jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" }, children: [hasDiscount && (jsx("span", { style: {
14884
+ fontSize: "13px",
14885
+ color: "var(--bw-text-muted)",
14886
+ textDecoration: "line-through",
14887
+ }, children: formatCurrency(special.basePrice) })), jsx("span", { style: {
14888
+ fontSize: "16px",
14889
+ fontWeight: 700,
14890
+ color: "var(--bw-highlight-color)",
14891
+ }, children: formatCurrency(special.price) }), hasDiscount && showSavingsAmount && (jsx("span", { style: {
14892
+ fontSize: "12px",
14893
+ fontWeight: 600,
14894
+ color: "#ffffff",
14895
+ backgroundColor: "var(--bw-success-color, #22c55e)",
14896
+ borderRadius: "4px",
14897
+ padding: "2px 6px",
14898
+ }, children: t("specials.save").replace("{{amount}}", formatCurrency(special.savings)) })), hasDiscount && showSavingsPercent && (jsx("span", { style: {
14899
+ fontSize: "12px",
14900
+ fontWeight: 600,
14901
+ color: "#ffffff",
14902
+ backgroundColor: "var(--bw-success-color, #22c55e)",
14903
+ borderRadius: "4px",
14904
+ padding: "2px 6px",
14905
+ }, children: t("specials.savePercent").replace("{{percent}}", String(special.savingsPercent)) }))] })] })] }), jsxs("div", { style: {
14906
+ borderTop: "1px solid var(--bw-border-color)",
14907
+ padding: "8px 12px",
14908
+ display: "flex",
14909
+ justifyContent: "space-between",
14910
+ alignItems: "center",
14911
+ backgroundColor: "var(--bw-background-color)",
14912
+ }, children: [jsx("span", { style: { fontSize: "12px", color: "var(--bw-text-muted)" }, children: isFullyBooked
14913
+ ? t("common.fullyBooked")
14914
+ : t("specials.spotsLeft").replace("{{count}}", String(special.availableSpots)) }), jsxs("span", { style: {
14915
+ fontSize: "13px",
14916
+ fontWeight: 600,
14917
+ color: "var(--bw-highlight-color)",
14918
+ }, children: [t("specials.bookNow"), " \u2192"] })] })] }, special.id));
14919
+ }) })] }));
14920
+ }
14921
+
14549
14922
  const getThemeConfig = (theme = "generic") => {
14550
14923
  switch (theme) {
14551
14924
  case "christmas":
@@ -14805,8 +15178,8 @@ const cardDisabledStyles = {
14805
15178
  };
14806
15179
  const checkboxContainerStyles = {
14807
15180
  position: "absolute",
14808
- top: "12px",
14809
- right: "12px",
15181
+ bottom: "12px",
15182
+ left: "12px",
14810
15183
  zIndex: 1,
14811
15184
  };
14812
15185
  const checkboxInnerStyles = {
@@ -14901,6 +15274,7 @@ const priceContainerStyles = {
14901
15274
  alignItems: "flex-end",
14902
15275
  marginTop: "8px",
14903
15276
  paddingTop: "8px",
15277
+ paddingBottom: "0px",
14904
15278
  borderTop: "1px solid var(--bw-border-color)",
14905
15279
  };
14906
15280
  const pricePerPersonStyles = {
@@ -14992,7 +15366,7 @@ function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, is
14992
15366
  };
14993
15367
  const selectedTotal = calculateTotal();
14994
15368
  const selectedCount = selectedUpsells.length;
14995
- const footerContent = (jsxs(Fragment, { children: [jsx("button", { type: "button", onClick: onBack, style: mergeStyles(buttonStyles.secondary, buttonStyles.fullWidth), children: t("common.back") }), jsx("button", { type: "button", onClick: onContinue, style: mergeStyles(buttonStyles.primary, buttonStyles.fullWidth), children: selectedCount === 0 ? t("button.continueWithout") : t("button.continue") })] }));
15369
+ const footerContent = (jsxs(Fragment, { children: [jsx("button", { type: "button", onClick: onBack, style: mergeStyles(buttonStyles.secondary, buttonStyles.fullWidth), className: buttonClassName, children: t("common.back") }), jsx("button", { type: "button", onClick: onContinue, style: mergeStyles(buttonStyles.primary, buttonStyles.fullWidth), className: buttonClassName, children: selectedCount === 0 ? t("button.continueWithout") : t("button.continue") })] }));
14996
15370
  return (jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: t("upsells.title"), footer: footerContent, children: jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%", padding: "16px 16px" }, children: [isLoading && (jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "12px", padding: "40px 20px", ...textStyles.muted }, children: [spinner(), jsx("span", { children: t("upsells.loading") })] })), !isLoading && upsells.length === 0 && (jsx("div", { style: { textAlign: "center", padding: "40px 20px", ...textStyles.muted }, children: jsx("p", { children: t("upsells.noExtras") }) })), !isLoading && upsells.length > 0 && (jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", flex: 1, overflowY: "auto", paddingBottom: "16px" }, children: upsells.map((upsell) => (jsx(UpsellCard, { upsell: upsell, isSelected: isSelected(upsell.id), participantCount: participantCount, onSelect: () => selectUpsell(upsell.id) }, upsell.id))) })), selectedCount > 0 && (jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: "16px", paddingBottom: "16px", paddingTop: "16px", borderTop: "1px solid var(--bw-border-color)", fontSize: "14px" }, children: [jsx("span", { style: textStyles.muted, children: selectedCount === 1 ? t("upsells.selected", { count: selectedCount }) : t("upsells.selectedPlural", { count: selectedCount }) }), jsxs("span", { style: { fontWeight: 600, color: "var(--bw-highlight-color)", fontFamily: "var(--bw-font-family)" }, children: ["+", formatCurrency(selectedTotal)] })] }))] }) }));
14997
15371
  }
14998
15372
 
@@ -15040,6 +15414,8 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15040
15414
  : (config.voucherIntegration ?? (hasEventSelection && !isDirectInstanceMode));
15041
15415
  // Selection flow state
15042
15416
  const [currentStep, setCurrentStep] = useState("eventTypes");
15417
+ // Tracks where to return when closing the booking form
15418
+ const bookingReturnStep = useRef("eventInstances");
15043
15419
  const [eventTypes, setEventTypes] = useState([]);
15044
15420
  const [selectedEventType, setSelectedEventType] = useState(null);
15045
15421
  const [eventInstances, setEventInstances] = useState([]);
@@ -15052,6 +15428,9 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15052
15428
  // State for upcoming events (next-events view mode)
15053
15429
  const [upcomingEvents, setUpcomingEvents] = useState([]);
15054
15430
  const [showingPreview, setShowingPreview] = useState(true);
15431
+ // State for specials view mode
15432
+ const [specials, setSpecials] = useState([]);
15433
+ const [isLoadingSpecials, setIsLoadingSpecials] = useState(false);
15055
15434
  // New: sidebar open state for single event type mode
15056
15435
  const [sidebarOpen, setSidebarOpen] = useState(false);
15057
15436
  // Booking flow state
@@ -15202,6 +15581,11 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15202
15581
  await loadUpcomingEvents();
15203
15582
  return;
15204
15583
  }
15584
+ // Specials view mode: load special offers
15585
+ if (viewMode === "specials") {
15586
+ await loadSpecials();
15587
+ return;
15588
+ }
15205
15589
  // Single event type mode: load event type and instances, but don't open sidebar yet
15206
15590
  if (isSingleEventTypeMode) {
15207
15591
  await loadEventTypes();
@@ -15425,6 +15809,45 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15425
15809
  setError(data.error || t("error.loadUpcomingEvents"));
15426
15810
  }
15427
15811
  };
15812
+ const loadSpecials = async () => {
15813
+ setIsLoadingSpecials(true);
15814
+ const specialsSettings = config.specialsSettings ?? {};
15815
+ const requestBody = {
15816
+ organizationId: config.organizationId,
15817
+ limit: specialsSettings.count ?? 20,
15818
+ };
15819
+ if (config.categoryId) {
15820
+ requestBody.categoryId = config.categoryId;
15821
+ }
15822
+ else if (config.eventTypeIds) {
15823
+ requestBody.eventTypeIds = config.eventTypeIds;
15824
+ }
15825
+ else if (config.eventTypeId) {
15826
+ requestBody.eventTypeId = config.eventTypeId;
15827
+ }
15828
+ try {
15829
+ const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/specials"), {
15830
+ method: "POST",
15831
+ headers: createApiHeaders(config, locale),
15832
+ body: JSON.stringify(requestBody),
15833
+ });
15834
+ const data = await response.json();
15835
+ if (response.ok) {
15836
+ const wl = extractWidgetLanguagePayload(data);
15837
+ if (wl) {
15838
+ onWidgetLanguage?.(wl);
15839
+ onTimezone?.(wl.timezone);
15840
+ }
15841
+ setSpecials(data.specials || []);
15842
+ }
15843
+ else {
15844
+ setError(data.error || t("error.loadUpcomingEvents"));
15845
+ }
15846
+ }
15847
+ finally {
15848
+ setIsLoadingSpecials(false);
15849
+ }
15850
+ };
15428
15851
  const loadEventInstances = async (eventTypeId) => {
15429
15852
  const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/event-instances"), {
15430
15853
  method: "POST",
@@ -15609,6 +16032,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15609
16032
  // Event instance selection handlers
15610
16033
  const handleEventInstanceSelect = async (eventInstance) => {
15611
16034
  setSelectedEventInstance(eventInstance);
16035
+ bookingReturnStep.current = "eventInstances";
15612
16036
  // Set default participant count for upsell calculations
15613
16037
  const defaultParticipantCount = 1;
15614
16038
  setTempParticipantCount(defaultParticipantCount);
@@ -15621,7 +16045,14 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15621
16045
  if (availableUpsells.length > 0) {
15622
16046
  // Show upsells step
15623
16047
  setUpsells(availableUpsells);
15624
- setSelectedUpsells([]);
16048
+ // Pre-select default-checked upsells
16049
+ const defaultSelections = availableUpsells
16050
+ .filter((upsell) => upsell.defaultChecked && upsell.available)
16051
+ .map((upsell) => ({
16052
+ upsellPackageId: upsell.id,
16053
+ quantity: defaultParticipantCount,
16054
+ }));
16055
+ setSelectedUpsells(defaultSelections);
15625
16056
  setCurrentStep("upsells");
15626
16057
  setIsLoadingUpsells(false);
15627
16058
  return; // Don't proceed to booking yet
@@ -15652,7 +16083,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15652
16083
  setEventInstances([]);
15653
16084
  };
15654
16085
  const handleBackToEventInstances = () => {
15655
- setCurrentStep("eventInstances");
16086
+ setCurrentStep(bookingReturnStep.current);
15656
16087
  setSelectedEventInstance(null);
15657
16088
  setEventDetails(null);
15658
16089
  };
@@ -15686,8 +16117,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15686
16117
  }
15687
16118
  };
15688
16119
  const handleUpsellsBack = () => {
15689
- // Go back to event instance selection
15690
- setCurrentStep("eventInstances");
16120
+ setCurrentStep(bookingReturnStep.current);
15691
16121
  setSelectedUpsells([]);
15692
16122
  setUpsells([]);
15693
16123
  };
@@ -15722,10 +16152,36 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15722
16152
  setError(errorMessage);
15723
16153
  config.onError?.(errorMessage);
15724
16154
  };
15725
- const handleUpcomingEventSelect = async (eventInstanceId) => {
16155
+ const handleUpcomingEventSelect = async (eventInstanceId, eventTypeId) => {
16156
+ // Resolve the event type — may come from card preview (eventTypeId provided) or
16157
+ // from the next-events list where selectedEventType is already set
16158
+ const resolvedEventType = eventTypeId != null
16159
+ ? (eventTypes.find((et) => et.id === eventTypeId) ?? selectedEventType)
16160
+ : selectedEventType;
16161
+ if (resolvedEventType && resolvedEventType !== selectedEventType) {
16162
+ setSelectedEventType(resolvedEventType);
16163
+ }
16164
+ // Check if this is coming from a card preview (eventTypeId was provided)
16165
+ // In that case, we need to load event instances so back navigation works properly
16166
+ const isFromCardPreview = eventTypeId != null;
16167
+ if (isFromCardPreview && resolvedEventType) {
16168
+ // Load event instances in background so back navigation shows instances
16169
+ setShouldRenderInstanceSelection(true);
16170
+ void loadEventInstances(resolvedEventType.id);
16171
+ // Record that we should return to instance selection (not event types)
16172
+ bookingReturnStep.current = "eventInstances";
16173
+ }
16174
+ else {
16175
+ // Record where to return when the booking form is closed
16176
+ bookingReturnStep.current = currentStep === "eventInstances" ? "eventInstances" : "eventTypes";
16177
+ }
16178
+ // First try to find the event in upcomingEvents (for next-events view mode)
15726
16179
  const upcomingEvent = upcomingEvents.find((event) => event.id === eventInstanceId);
15727
- if (upcomingEvent) {
15728
- const eventInstance = {
16180
+ // If not found in upcomingEvents, try to find in card preview items
16181
+ const cardPreviewItem = !upcomingEvent && resolvedEventType?.cardPreview?.find((item) => item.id === eventInstanceId);
16182
+ // Build the event instance from either source
16183
+ const eventInstance = upcomingEvent
16184
+ ? {
15729
16185
  id: upcomingEvent.id,
15730
16186
  name: upcomingEvent.name,
15731
16187
  startTime: upcomingEvent.startTime,
@@ -15739,13 +16195,63 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15739
16195
  bookingOpen: upcomingEvent.bookingOpen,
15740
16196
  ...(upcomingEvent.deposit !== undefined && { deposit: upcomingEvent.deposit }),
15741
16197
  ...(upcomingEvent.notes !== undefined && { notes: upcomingEvent.notes }),
15742
- };
16198
+ }
16199
+ : cardPreviewItem
16200
+ ? {
16201
+ id: cardPreviewItem.id,
16202
+ name: cardPreviewItem.name,
16203
+ startTime: cardPreviewItem.startTime,
16204
+ endTime: cardPreviewItem.startTime,
16205
+ price: cardPreviewItem.price,
16206
+ maxParticipants: cardPreviewItem.availableSpots + 1,
16207
+ participantCount: 1,
16208
+ availableSpots: cardPreviewItem.availableSpots,
16209
+ durationDays: 1,
16210
+ durationPerDay: 1,
16211
+ bookingOpen: true,
16212
+ }
16213
+ : null;
16214
+ if (eventInstance) {
15743
16215
  setSelectedEventInstance(eventInstance);
15744
16216
  }
16217
+ setError(null);
16218
+ // Check for upsells before going to booking (same as handleEventInstanceSelect)
16219
+ const eventTypeForUpsells = resolvedEventType;
16220
+ if (eventTypeForUpsells) {
16221
+ const defaultParticipantCount = 1;
16222
+ setTempParticipantCount(defaultParticipantCount);
16223
+ setIsLoadingUpsells(true);
16224
+ setShouldRenderUpsells(true);
16225
+ try {
16226
+ const availableUpsells = await loadUpsells(eventTypeForUpsells.id, eventInstanceId, defaultParticipantCount);
16227
+ if (availableUpsells.length > 0) {
16228
+ setUpsells(availableUpsells);
16229
+ // Pre-select default-checked upsells
16230
+ const defaultSelections = availableUpsells
16231
+ .filter((upsell) => upsell.defaultChecked && upsell.available)
16232
+ .map((upsell) => ({
16233
+ upsellPackageId: upsell.id,
16234
+ quantity: defaultParticipantCount,
16235
+ }));
16236
+ setSelectedUpsells(defaultSelections);
16237
+ setCurrentStep("upsells");
16238
+ setIsLoadingUpsells(false);
16239
+ // Load event details in background for when user continues past upsells
16240
+ void loadEventDetails(eventInstanceId);
16241
+ return;
16242
+ }
16243
+ }
16244
+ catch (err) {
16245
+ console.error("Error loading upsells:", err);
16246
+ }
16247
+ finally {
16248
+ setIsLoadingUpsells(false);
16249
+ }
16250
+ }
16251
+ // No upsells — go directly to booking
15745
16252
  setCurrentStep("booking");
15746
16253
  setShouldRenderBookingForm(true);
15747
16254
  setIsLoadingEventDetails(true);
15748
- setError(null);
15749
16255
  try {
15750
16256
  await loadEventDetails(eventInstanceId);
15751
16257
  }
@@ -15902,6 +16408,31 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15902
16408
  window.history.replaceState({}, "", url.toString());
15903
16409
  }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
15904
16410
  }
16411
+ if (viewMode === "specials" && showingPreview) {
16412
+ 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, onBackToEventInstances: () => {
16413
+ setCurrentStep("eventTypes");
16414
+ setShowingPreview(true);
16415
+ setEventDetails(null);
16416
+ }, onBackToEventTypes: () => {
16417
+ setCurrentStep("eventTypes");
16418
+ setShowingPreview(true);
16419
+ setEventDetails(null);
16420
+ }, selectedEventType: selectedEventType, selectedEventInstance: selectedEventInstance, isOpen: currentStep === "booking" && !!eventDetails, onClose: () => {
16421
+ setCurrentStep("eventTypes");
16422
+ setShowingPreview(true);
16423
+ setEventDetails(null);
16424
+ }, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells })), jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16425
+ setIsSuccess(false);
16426
+ setCurrentStep("eventTypes");
16427
+ setShowingPreview(true);
16428
+ setSuccessPaymentId(null);
16429
+ setShouldRenderInstanceSelection(false);
16430
+ setShouldRenderUpsells(false);
16431
+ setShouldRenderBookingForm(false);
16432
+ setSelectedUpsells([]);
16433
+ setUpsells([]);
16434
+ }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16435
+ }
15905
16436
  if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
15906
16437
  return (jsxs(StyleProvider, { config: config, children: [jsxs("div", { ref: setWidgetContainerRef, children: [shouldRenderInstanceSelection && (jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
15907
16438
  setShowingPreview(true);
@@ -15972,7 +16503,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15972
16503
  }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
15973
16504
  }
15974
16505
  // Cards mode (default) - show event type selection with optional voucher card
15975
- const cardsView = (jsxs(Fragment, { children: [hasEventSelection && (jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, isLoading: isLoading, skeletonCount: getSkeletonCount(), showVoucherAttachment: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled && !isStandaloneVoucherMode), onVoucherClick: handleVoucherAttachmentClick })), isStandaloneVoucherMode && (jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, systemConfig: systemConfig, isFormOpen: false, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: null, voucherPurchaseResult: null, isSuccess: false, showStandaloneCard: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled), onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => { } })), isStandaloneVoucherMode && isLoading && !voucherConfig && (jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsx("div", { style: {
16506
+ const cardsView = (jsxs(Fragment, { children: [hasEventSelection && (jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, onInstancePreview: (instanceId, eventTypeId) => void handleUpcomingEventSelect(instanceId, eventTypeId), isLoading: isLoading, skeletonCount: getSkeletonCount(), showVoucherAttachment: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled && !isStandaloneVoucherMode), onVoucherClick: handleVoucherAttachmentClick })), isStandaloneVoucherMode && (jsx(VoucherIntegration, { config: config, voucherConfig: voucherConfig, eventTypes: voucherEventTypes, systemConfig: systemConfig, isFormOpen: false, isLoadingConfig: isLoadingVoucherConfig, preselectedEventTypeId: null, voucherPurchaseResult: null, isSuccess: false, showStandaloneCard: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled), onCardClick: handleVoucherCardClick, onFormClose: handleVoucherFormClose, onSuccess: handleVoucherSuccess, onError: handleVoucherError, onSuccessModalClose: () => { } })), isStandaloneVoucherMode && isLoading && !voucherConfig && (jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsx("div", { style: {
15976
16507
  display: "inline-block",
15977
16508
  width: "32px",
15978
16509
  height: "32px",
@@ -16040,11 +16571,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16040
16571
  function UniversalBookingWidget(props) {
16041
16572
  const [languagePolicy, setLanguagePolicy] = useState(null);
16042
16573
  const [orgTimezone, setOrgTimezone] = useState("Europe/Berlin");
16043
- const serverLockedLocale = languagePolicy && !languagePolicy.multiLanguageEnabled
16044
- ? languagePolicy.organizationLocale
16045
- : undefined;
16046
- const i18nConfigLocale = serverLockedLocale !== undefined ? serverLockedLocale : props.config.locale;
16047
- const providerProps = i18nConfigLocale ? { configLocale: i18nConfigLocale } : {};
16574
+ const providerProps = props.config.locale ? { configLocale: props.config.locale } : {};
16048
16575
  const showLanguagePicker = props.config.showLanguagePicker !== false &&
16049
16576
  (languagePolicy === null || languagePolicy.multiLanguageEnabled);
16050
16577
  return (jsx(I18nProvider, { ...providerProps, children: jsx(ShowLanguagePickerProvider, { value: showLanguagePicker, children: jsx(TimezoneProvider, { value: orgTimezone, children: jsx(UniversalBookingWidgetInner, { ...props, onWidgetLanguage: setLanguagePolicy, onTimezone: setOrgTimezone }) }) }) }));
@@ -16077,7 +16604,7 @@ function styleInject(css, ref) {
16077
16604
  }
16078
16605
  }
16079
16606
 
16080
- 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%}@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}}";
16607
+ 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}}";
16081
16608
  styleInject(css_248z);
16082
16609
 
16083
16610
  // Export init function for vanilla JS usage