@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.cjs CHANGED
@@ -253,6 +253,7 @@ const de$1 = {
253
253
  "events.soldOut": "Ausgebucht",
254
254
  "events.availableFrom": "Freie Plätze ab {{date}}",
255
255
  "events.noAvailableDates": "Keine Termine frei",
256
+ "events.previewSectionTitle": "Specials & nächste Termine",
256
257
  // Event instances
257
258
  "instances.title": "Terminauswahl",
258
259
  "instances.noAvailable": "Keine verfügbaren Termine",
@@ -269,6 +270,15 @@ const de$1 = {
269
270
  "nextEvents.noUpcomingMessage": "Aktuell sind keine Termine verfügbar. Bitte schaue später noch einmal vorbei oder kontaktiere uns direkt.",
270
271
  "nextEvents.showAll": "Alle Events anzeigen",
271
272
  "nextEvents.priceOnRequest": "Preis auf Anfrage",
273
+ // Specials view
274
+ "specials.title": "Sonderangebote",
275
+ "specials.subtitle": "Unsere aktuellen Angebote auf einen Blick",
276
+ "specials.noSpecials": "Keine Sonderangebote",
277
+ "specials.noSpecialsMessage": "Derzeit sind keine Sonderangebote verfügbar. Bitte schaue später noch einmal vorbei.",
278
+ "specials.save": "Spare {{amount}}",
279
+ "specials.savePercent": "{{percent}}% Rabatt",
280
+ "specials.spotsLeft": "Noch {{count}} Plätze frei",
281
+ "specials.bookNow": "Jetzt buchen",
272
282
  // Booking form
273
283
  "booking.title": "Buchung - {{name}}",
274
284
  "booking.notPossible": "Buchung nicht möglich",
@@ -290,6 +300,8 @@ const de$1 = {
290
300
  "booking.participantName": "Name *",
291
301
  "booking.participantNamePlaceholder": "Teilnehmername",
292
302
  "booking.participantAge": "Alter",
303
+ "booking.participantLevel": "Level",
304
+ "booking.participantLevelPlaceholder": "Level wählen...",
293
305
  "booking.addParticipant": "{{number}}. Teilnehmer hinzufügen",
294
306
  "booking.maxParticipants": "Maximale Anzahl an Teilnehmern erreicht. Es sind nur noch {{count}} Plätze verfügbar.",
295
307
  "booking.maxSpotsReached": "Maximal {{count}} Plätze verfügbar.",
@@ -438,7 +450,11 @@ const de$1 = {
438
450
  "validation.emailInvalid": "Ungültiges E-Mail-Format",
439
451
  "validation.emailDomainInvalid": "Ungültige E-Mail-Domain",
440
452
  "validation.participantRequired": "Mindestens ein Teilnehmer erforderlich",
453
+ "validation.ageRequired": "Alter ist erforderlich",
454
+ "validation.levelRequired": "Bitte ein Level auswählen",
441
455
  "validation.acceptTerms": "Bitte akzeptiere die Allgemeinen Geschäftsbedingungen",
456
+ "level.beginner": "Anfänger",
457
+ "level.advanced": "Fortgeschritten",
442
458
  // Sidebar
443
459
  "sidebar.close": "Schließen",
444
460
  // Promo
@@ -513,6 +529,7 @@ const en = {
513
529
  "events.soldOut": "Sold out",
514
530
  "events.availableFrom": "Available from {{date}}",
515
531
  "events.noAvailableDates": "No dates available",
532
+ "events.previewSectionTitle": "Specials & upcoming dates",
516
533
  // Event instances
517
534
  "instances.title": "Select a date",
518
535
  "instances.noAvailable": "No available dates",
@@ -529,6 +546,15 @@ const en = {
529
546
  "nextEvents.noUpcomingMessage": "There are currently no dates available. Please check back later or contact us directly.",
530
547
  "nextEvents.showAll": "Show all events",
531
548
  "nextEvents.priceOnRequest": "Price on request",
549
+ // Specials view
550
+ "specials.title": "Special Offers",
551
+ "specials.subtitle": "Our current deals at a glance",
552
+ "specials.noSpecials": "No special offers",
553
+ "specials.noSpecialsMessage": "There are currently no special offers available. Please check back later.",
554
+ "specials.save": "Save {{amount}}",
555
+ "specials.savePercent": "{{percent}}% off",
556
+ "specials.spotsLeft": "{{count}} spots left",
557
+ "specials.bookNow": "Book now",
532
558
  // Booking form
533
559
  "booking.title": "Booking - {{name}}",
534
560
  "booking.notPossible": "Booking not possible",
@@ -550,6 +576,8 @@ const en = {
550
576
  "booking.participantName": "Name *",
551
577
  "booking.participantNamePlaceholder": "Participant name",
552
578
  "booking.participantAge": "Age",
579
+ "booking.participantLevel": "Level",
580
+ "booking.participantLevelPlaceholder": "Select level...",
553
581
  "booking.addParticipant": "Add participant {{number}}",
554
582
  "booking.maxParticipants": "Maximum number of participants reached. Only {{count}} spots are available.",
555
583
  "booking.maxSpotsReached": "Maximum {{count}} spots available.",
@@ -698,7 +726,11 @@ const en = {
698
726
  "validation.emailInvalid": "Invalid email format",
699
727
  "validation.emailDomainInvalid": "Invalid email domain",
700
728
  "validation.participantRequired": "At least one participant is required",
729
+ "validation.ageRequired": "Age is required",
730
+ "validation.levelRequired": "Please select a level",
701
731
  "validation.acceptTerms": "Please accept the terms and conditions",
732
+ "level.beginner": "Beginner",
733
+ "level.advanced": "Advanced",
702
734
  // Sidebar
703
735
  "sidebar.close": "Close",
704
736
  // Promo
@@ -773,6 +805,7 @@ const es = {
773
805
  "events.soldOut": "Agotado",
774
806
  "events.availableFrom": "Disponible desde {{date}}",
775
807
  "events.noAvailableDates": "Sin fechas disponibles",
808
+ "events.previewSectionTitle": "Especiales & próximas fechas",
776
809
  // Event instances
777
810
  "instances.title": "Seleccionar fecha",
778
811
  "instances.noAvailable": "Sin fechas disponibles",
@@ -789,6 +822,15 @@ const es = {
789
822
  "nextEvents.noUpcomingMessage": "Actualmente no hay fechas disponibles. Por favor, vuelve más tarde o contáctanos directamente.",
790
823
  "nextEvents.showAll": "Mostrar todos los eventos",
791
824
  "nextEvents.priceOnRequest": "Precio bajo consulta",
825
+ // Specials view
826
+ "specials.title": "Ofertas especiales",
827
+ "specials.subtitle": "Nuestras ofertas actuales de un vistazo",
828
+ "specials.noSpecials": "Sin ofertas especiales",
829
+ "specials.noSpecialsMessage": "Actualmente no hay ofertas especiales disponibles. Vuelve más tarde.",
830
+ "specials.save": "Ahorra {{amount}}",
831
+ "specials.savePercent": "{{percent}}% de descuento",
832
+ "specials.spotsLeft": "{{count}} plazas disponibles",
833
+ "specials.bookNow": "Reservar ahora",
792
834
  // Booking form
793
835
  "booking.title": "Reserva - {{name}}",
794
836
  "booking.notPossible": "Reserva no posible",
@@ -810,6 +852,8 @@ const es = {
810
852
  "booking.participantName": "Nombre *",
811
853
  "booking.participantNamePlaceholder": "Nombre del participante",
812
854
  "booking.participantAge": "Edad",
855
+ "booking.participantLevel": "Nivel",
856
+ "booking.participantLevelPlaceholder": "Seleccionar nivel...",
813
857
  "booking.addParticipant": "Añadir participante {{number}}",
814
858
  "booking.maxParticipants": "Número máximo de participantes alcanzado. Solo quedan {{count}} plazas disponibles.",
815
859
  "booking.maxSpotsReached": "Máximo {{count}} plazas disponibles.",
@@ -958,7 +1002,11 @@ const es = {
958
1002
  "validation.emailInvalid": "Formato de correo electrónico inválido",
959
1003
  "validation.emailDomainInvalid": "Dominio de correo electrónico inválido",
960
1004
  "validation.participantRequired": "Se requiere al menos un participante",
1005
+ "validation.ageRequired": "La edad es obligatoria",
1006
+ "validation.levelRequired": "Selecciona un nivel",
961
1007
  "validation.acceptTerms": "Por favor, acepta los términos y condiciones",
1008
+ "level.beginner": "Principiante",
1009
+ "level.advanced": "Avanzado",
962
1010
  // Sidebar
963
1011
  "sidebar.close": "Cerrar",
964
1012
  // Promo
@@ -1033,6 +1081,7 @@ const pt = {
1033
1081
  "events.soldOut": "Esgotado",
1034
1082
  "events.availableFrom": "Disponível a partir de {{date}}",
1035
1083
  "events.noAvailableDates": "Sem datas disponíveis",
1084
+ "events.previewSectionTitle": "Especiais & próximas datas",
1036
1085
  // Event instances
1037
1086
  "instances.title": "Selecionar data",
1038
1087
  "instances.noAvailable": "Sem datas disponíveis",
@@ -1049,6 +1098,15 @@ const pt = {
1049
1098
  "nextEvents.noUpcomingMessage": "Atualmente não há datas disponíveis. Por favor, volte mais tarde ou contacte-nos diretamente.",
1050
1099
  "nextEvents.showAll": "Mostrar todos os eventos",
1051
1100
  "nextEvents.priceOnRequest": "Preço sob consulta",
1101
+ // Specials view
1102
+ "specials.title": "Ofertas especiais",
1103
+ "specials.subtitle": "As nossas ofertas atuais num relance",
1104
+ "specials.noSpecials": "Sem ofertas especiais",
1105
+ "specials.noSpecialsMessage": "Atualmente não há ofertas especiais disponíveis. Por favor, volte mais tarde.",
1106
+ "specials.save": "Poupe {{amount}}",
1107
+ "specials.savePercent": "{{percent}}% de desconto",
1108
+ "specials.spotsLeft": "{{count}} lugares disponíveis",
1109
+ "specials.bookNow": "Reservar agora",
1052
1110
  // Booking form
1053
1111
  "booking.title": "Reserva - {{name}}",
1054
1112
  "booking.notPossible": "Reserva não possível",
@@ -1070,6 +1128,8 @@ const pt = {
1070
1128
  "booking.participantName": "Nome *",
1071
1129
  "booking.participantNamePlaceholder": "Nome do participante",
1072
1130
  "booking.participantAge": "Idade",
1131
+ "booking.participantLevel": "Nível",
1132
+ "booking.participantLevelPlaceholder": "Selecionar nível...",
1073
1133
  "booking.addParticipant": "Adicionar participante {{number}}",
1074
1134
  "booking.maxParticipants": "Número máximo de participantes atingido. Apenas {{count}} lugares disponíveis.",
1075
1135
  "booking.maxSpotsReached": "Máximo {{count}} lugares disponíveis.",
@@ -1218,7 +1278,11 @@ const pt = {
1218
1278
  "validation.emailInvalid": "Formato de email inválido",
1219
1279
  "validation.emailDomainInvalid": "Domínio de email inválido",
1220
1280
  "validation.participantRequired": "É necessário pelo menos um participante",
1281
+ "validation.ageRequired": "A idade é obrigatória",
1282
+ "validation.levelRequired": "Por favor selecione um nível",
1221
1283
  "validation.acceptTerms": "Por favor, aceite os termos e condições",
1284
+ "level.beginner": "Iniciante",
1285
+ "level.advanced": "Avançado",
1222
1286
  // Sidebar
1223
1287
  "sidebar.close": "Fechar",
1224
1288
  // Promo
@@ -1293,6 +1357,7 @@ const sv = {
1293
1357
  "events.soldOut": "Fullbokat",
1294
1358
  "events.availableFrom": "Lediga platser från {{date}}",
1295
1359
  "events.noAvailableDates": "Inga datum lediga",
1360
+ "events.previewSectionTitle": "Specials & kommande datum",
1296
1361
  // Event instances
1297
1362
  "instances.title": "Välj datum",
1298
1363
  "instances.noAvailable": "Inga tillgängliga datum",
@@ -1309,6 +1374,15 @@ const sv = {
1309
1374
  "nextEvents.noUpcomingMessage": "Det finns för närvarande inga datum tillgängliga. Kom tillbaka senare eller kontakta oss direkt.",
1310
1375
  "nextEvents.showAll": "Visa alla evenemang",
1311
1376
  "nextEvents.priceOnRequest": "Pris på förfrågan",
1377
+ // Specials view
1378
+ "specials.title": "Specialerbjudanden",
1379
+ "specials.subtitle": "Våra aktuella erbjudanden i korthet",
1380
+ "specials.noSpecials": "Inga specialerbjudanden",
1381
+ "specials.noSpecialsMessage": "Det finns för närvarande inga specialerbjudanden tillgängliga. Kom tillbaka senare.",
1382
+ "specials.save": "Spara {{amount}}",
1383
+ "specials.savePercent": "{{percent}}% rabatt",
1384
+ "specials.spotsLeft": "{{count}} platser kvar",
1385
+ "specials.bookNow": "Boka nu",
1312
1386
  // Booking form
1313
1387
  "booking.title": "Bokning - {{name}}",
1314
1388
  "booking.notPossible": "Bokning inte möjlig",
@@ -1330,6 +1404,8 @@ const sv = {
1330
1404
  "booking.participantName": "Namn *",
1331
1405
  "booking.participantNamePlaceholder": "Deltagarens namn",
1332
1406
  "booking.participantAge": "Ålder",
1407
+ "booking.participantLevel": "Nivå",
1408
+ "booking.participantLevelPlaceholder": "Välj nivå...",
1333
1409
  "booking.addParticipant": "Lägg till deltagare {{number}}",
1334
1410
  "booking.maxParticipants": "Maximalt antal deltagare uppnått. Bara {{count}} platser är tillgängliga.",
1335
1411
  "booking.maxSpotsReached": "Maximalt {{count}} platser tillgängliga.",
@@ -1478,7 +1554,11 @@ const sv = {
1478
1554
  "validation.emailInvalid": "Ogiltigt e-postformat",
1479
1555
  "validation.emailDomainInvalid": "Ogiltig e-postdomän",
1480
1556
  "validation.participantRequired": "Minst en deltagare krävs",
1557
+ "validation.ageRequired": "Ålder krävs",
1558
+ "validation.levelRequired": "Välj en nivå",
1481
1559
  "validation.acceptTerms": "Acceptera villkoren",
1560
+ "level.beginner": "Nybörjare",
1561
+ "level.advanced": "Avancerad",
1482
1562
  // Sidebar
1483
1563
  "sidebar.close": "Stäng",
1484
1564
  // Promo
@@ -1571,18 +1651,9 @@ function persistLocale(locale) {
1571
1651
  }
1572
1652
  const I18nContext = React.createContext(null);
1573
1653
  function I18nProvider({ configLocale, children }) {
1574
- // Priority: configLocale (site owner) > persisted user choice > browser language > "de"
1575
- // If configLocale is set, the site owner has locked the language - don't restore user choice.
1576
- const [overrideLocale, setOverrideLocale] = React.useState(() => {
1577
- if (configLocale)
1578
- return null;
1579
- return readPersistedLocale();
1580
- });
1581
- React.useEffect(() => {
1582
- if (configLocale) {
1583
- setOverrideLocale(null);
1584
- }
1585
- }, [configLocale]);
1654
+ // Priority: persisted user choice > configLocale (org default) > browser language > "de"
1655
+ // This keeps org locale as default, but remembers explicit user overrides across reloads.
1656
+ const [overrideLocale, setOverrideLocale] = React.useState(() => readPersistedLocale());
1586
1657
  const locale = overrideLocale ?? resolveLocale(configLocale);
1587
1658
  const handleSetLocale = React.useCallback((next) => {
1588
1659
  persistLocale(next);
@@ -1714,129 +1785,153 @@ const resolveSemanticColor = (colorValue, fallbackValue) => {
1714
1785
  // If semantic resolution fails, use fallback or return the original value
1715
1786
  return fallbackValue || colorValue;
1716
1787
  };
1717
- // Predefined themes (modern, accessibility-tested)
1788
+ // Legacy theme name redirects (old name → new name)
1789
+ const legacyThemeRedirects = {
1790
+ "light-fresh": "teal-minimal",
1791
+ "light-elegant": "blue-business",
1792
+ "light-vibrant": "orange-raw",
1793
+ "light-professional": "blue-business",
1794
+ "dark-night": "navy-night",
1795
+ "dark-modern": "navy-night",
1796
+ "dark-forest": "green-deep",
1797
+ };
1798
+ // Predefined themes
1718
1799
  const themes = {
1719
1800
  // --- Light Themes ---
1720
- "light-fresh": {
1721
- highlight: "#00b1aa", // accent-strong
1722
- background: "#f8fdfe", // neutral-strong (background)
1723
- surface: "#ffffff", // card (pure white)
1724
- text: "#0e7490", // Turquoise 800
1725
- border: "#bae6fd", // Blue 200
1726
- success: "#38bdf8", // Blue 400
1727
- warning: "#fbbf24", // Amber 400
1728
- error: "#f43f5e", // Rose 500
1729
- borderRadius: "18px", // Very rounded corners
1801
+ "teal-minimal": {
1802
+ highlight: "#00b1aa",
1803
+ background: "#f8fdfe",
1804
+ surface: "#ffffff",
1805
+ text: "#0e7490",
1806
+ border: "#bae6fd",
1807
+ success: "#38bdf8",
1808
+ warning: "#fbbf24",
1809
+ error: "#f43f5e",
1810
+ borderRadius: "18px",
1730
1811
  fontFamily: "'Inter', system-ui, sans-serif",
1731
1812
  },
1732
- "light-elegant": {
1733
- highlight: "#8b5cf6", // Violet 500
1734
- background: "#f5f3ff", // Violet 50
1735
- surface: "#ffffff", // White
1736
- text: "#4c1d95", // Violet 900
1737
- border: "#ede9fe", // Violet 100
1738
- success: "#16a34a", // Green 600
1739
- warning: "#ca8a04", // Yellow 600
1740
- error: "#dc2626", // Red 600
1741
- borderRadius: "12px",
1742
- fontFamily: "'Playfair Display', serif",
1813
+ "blue-business": {
1814
+ highlight: "#2563eb",
1815
+ background: "#f8fafc",
1816
+ surface: "#ffffff",
1817
+ text: "#0f172a",
1818
+ border: "#cbd5e1",
1819
+ success: "#059669",
1820
+ warning: "#d97706",
1821
+ error: "#b91c1c",
1822
+ borderRadius: "6px",
1823
+ fontFamily: "'Plus Jakarta Sans', system-ui, sans-serif",
1743
1824
  },
1744
- "light-vibrant": {
1745
- highlight: "#ed702d", // blue-500 - bright blue accent
1746
- background: "#1f2630", // slate-900 - dark background
1747
- surface: "#1f2630", // slate-800 - dark cards
1748
- text: "#f1f5f9", // slate-100 - light text
1749
- border: "#ed702d", // slate-700 - subtle borders
1750
- success: "#22c55e", // green-500
1751
- warning: "#eab308", // yellow-500
1752
- error: "#ef4444", // red-500
1825
+ "orange-raw": {
1826
+ highlight: "#ed702d",
1827
+ background: "#1f2630",
1828
+ surface: "#1f2630",
1829
+ text: "#f1f5f9",
1830
+ border: "#ed702d",
1831
+ success: "#22c55e",
1832
+ warning: "#eab308",
1833
+ error: "#ef4444",
1753
1834
  borderRadius: "0px",
1754
1835
  fontFamily: "Inter, system-ui, sans-serif",
1755
1836
  },
1756
- "light-professional": {
1757
- highlight: "#2563eb", // Blue 600
1758
- background: "#f8fafc", // Slate 50
1759
- surface: "#ffffff", // White
1760
- text: "#1e293b", // Slate 800
1761
- border: "#e2e8f0", // Slate 200
1762
- success: "#059669", // Emerald 600
1763
- warning: "#d97706", // Amber 600
1764
- error: "#b91c1c", // Red 700
1765
- borderRadius: "4px",
1766
- fontFamily: "system-ui, -apple-system, sans-serif",
1837
+ // --- Dark Themes ---
1838
+ "navy-night": {
1839
+ highlight: "#60a5fa",
1840
+ background: "#0b1120",
1841
+ surface: "#111827",
1842
+ text: "#e2e8f0",
1843
+ border: "#1e3a5f",
1844
+ success: "#34d399",
1845
+ warning: "#fbbf24",
1846
+ error: "#f87171",
1847
+ borderRadius: "10px",
1848
+ fontFamily: "'Outfit', system-ui, sans-serif",
1767
1849
  },
1768
- "dark-night": {
1769
- highlight: "#3b82f6", // blue-500 - bright blue accent
1770
- background: "#0f172a", // slate-900 - dark background
1771
- surface: "#1e293b", // slate-800 - dark cards
1772
- text: "#f1f5f9", // slate-100 - light text
1773
- border: "#334155", // slate-700 - subtle borders
1774
- success: "#22c55e", // green-500
1775
- warning: "#eab308", // yellow-500
1776
- error: "#ef4444", // red-500
1777
- borderRadius: "8px",
1778
- fontFamily: "Inter, system-ui, sans-serif",
1779
- },
1780
- "dark-modern": {
1781
- highlight: "#3b82f6", // blue-500 - bright blue accent
1782
- background: "#0f172a", // slate-900 - dark background
1783
- surface: "#1e293b", // slate-800 - dark cards
1784
- text: "#f1f5f9", // slate-100 - light text
1785
- border: "#334155", // slate-700 - subtle borders
1786
- success: "#22c55e", // green-500
1787
- warning: "#eab308", // yellow-500
1788
- error: "#ef4444", // red-500
1789
- borderRadius: "8px",
1790
- fontFamily: "Inter, system-ui, sans-serif",
1791
- },
1792
- "dark-forest": {
1793
- highlight: "#34d399", // Emerald 400
1794
- background: "#05140d",
1795
- surface: "#062215",
1796
- text: "#d1fae5", // Emerald 100
1797
- border: "#043322",
1798
- success: "#4ade80", // Green 400
1799
- warning: "#facc15", // Yellow 400
1800
- error: "#f87171", // Red 400
1850
+ "green-deep": {
1851
+ highlight: "#34d399",
1852
+ background: "#030d07",
1853
+ surface: "#051a0e",
1854
+ text: "#d1fae5",
1855
+ border: "#064e20",
1856
+ success: "#4ade80",
1857
+ warning: "#facc15",
1858
+ error: "#f87171",
1801
1859
  borderRadius: "12px",
1802
- fontFamily: "system-ui, -apple-system, sans-serif",
1860
+ fontFamily: "'Instrument Sans', system-ui, sans-serif",
1803
1861
  },
1804
- "dark-matrix": {
1805
- highlight: "#33ff33",
1862
+ "green-matrix": {
1863
+ highlight: "#39ff14",
1806
1864
  background: "#000000",
1807
- surface: "#0a0a0a",
1808
- text: "#00ff00",
1809
- border: "#1a1a1a",
1810
- success: "#33ff33",
1865
+ surface: "#060f06",
1866
+ text: "#00ff41",
1867
+ border: "#0d2b0d",
1868
+ success: "#39ff14",
1811
1869
  warning: "#ffff00",
1812
1870
  error: "#ff3333",
1813
1871
  borderRadius: "0px",
1814
- fontFamily: "'Courier New', monospace",
1872
+ fontFamily: "'Share Tech Mono', monospace",
1815
1873
  },
1816
- "dark-luxury": {
1817
- highlight: "#fde047", // Yellow 300
1818
- background: "#1c1917", // Stone 900
1819
- surface: "#292524", // Stone 800
1820
- text: "#fafaf9", // Stone 50
1821
- border: "#44403c", // Stone 700
1822
- success: "#a3e635", // Lime 400
1823
- warning: "#f59e0b", // Amber 500
1824
- error: "#fca5a5", // Red 300
1874
+ "gold-luxury": {
1875
+ highlight: "#fde047",
1876
+ background: "#1c1917",
1877
+ surface: "#292524",
1878
+ text: "#fafaf9",
1879
+ border: "#44403c",
1880
+ success: "#a3e635",
1881
+ warning: "#f59e0b",
1882
+ error: "#fca5a5",
1825
1883
  borderRadius: "24px",
1826
- fontFamily: "'Cinzel', serif",
1884
+ fontFamily: "'Bodoni Moda', serif",
1827
1885
  },
1828
- "dark-energetic": {
1829
- highlight: "#d946ef", // Fuchsia 500
1830
- background: "#2e1046",
1831
- surface: "#401561",
1832
- text: "#f3e8ff", // Purple 50
1833
- border: "#581c87", // Purple 900
1834
- success: "#4ade80", // Green 400
1835
- warning: "#facc15", // Yellow 400
1836
- error: "#f87171", // Red 400
1886
+ "purple-electric": {
1887
+ highlight: "#d946ef",
1888
+ background: "#110820",
1889
+ surface: "#1a0d30",
1890
+ text: "#f3e8ff",
1891
+ border: "#3b0764",
1892
+ success: "#4ade80",
1893
+ warning: "#facc15",
1894
+ error: "#f87171",
1837
1895
  borderRadius: "14px",
1838
1896
  fontFamily: "'Geologica', sans-serif",
1839
1897
  },
1898
+ "dark-neon-brutalism": {
1899
+ highlight: "#00e5cc",
1900
+ background: "#0e1420",
1901
+ surface: "#0e1420",
1902
+ text: "#e8eef5",
1903
+ border: "#00e5cc",
1904
+ success: "#00e5cc",
1905
+ warning: "#ffe45e",
1906
+ error: "#ff4d6d",
1907
+ borderRadius: "4px",
1908
+ fontFamily: "'JetBrains Mono', monospace",
1909
+ buttonTextColor: "#0e1420",
1910
+ },
1911
+ "rose-editorial": {
1912
+ highlight: "#be3455",
1913
+ background: "#fdf8f5",
1914
+ surface: "#ffffff",
1915
+ text: "#1a0a0f",
1916
+ border: "#f2d5db",
1917
+ success: "#2d6a4f",
1918
+ warning: "#b5451b",
1919
+ error: "#9b1d20",
1920
+ borderRadius: "0px",
1921
+ fontFamily: "'Cormorant', serif",
1922
+ },
1923
+ "amber-retro": {
1924
+ highlight: "#f59e0b",
1925
+ background: "#1a1008",
1926
+ surface: "#241a0a",
1927
+ text: "#fef3c7",
1928
+ border: "#78350f",
1929
+ success: "#84cc16",
1930
+ warning: "#f59e0b",
1931
+ error: "#ef4444",
1932
+ borderRadius: "6px",
1933
+ fontFamily: "'Syne', sans-serif",
1934
+ },
1840
1935
  };
1841
1936
  const StyleProvider = ({ config, children, }) => {
1842
1937
  // Track hydration state to prevent mismatches
@@ -1846,8 +1941,10 @@ const StyleProvider = ({ config, children, }) => {
1846
1941
  }, []);
1847
1942
  // PERFORMANCE OPTIMIZATION: Memoize style calculations
1848
1943
  const themedStyles = React.useMemo(() => {
1849
- const themeName = config.theme || "light-fresh";
1850
- const themeDefaults = themes[themeName] || themes["light-fresh"];
1944
+ const rawThemeName = config.theme || "teal-minimal";
1945
+ // Redirect legacy theme names to new names
1946
+ const themeName = legacyThemeRedirects[rawThemeName] || rawThemeName;
1947
+ const themeDefaults = themes[themeName] || themes["teal-minimal"];
1851
1948
  const getCSSValue = (value, fallback) => {
1852
1949
  if (!value)
1853
1950
  return fallback;
@@ -1917,6 +2014,7 @@ const StyleProvider = ({ config, children, }) => {
1917
2014
  "--bw-surface-color": finalColors.surface,
1918
2015
  "--bw-text-color": finalColors.text,
1919
2016
  "--bw-text-muted": addOpacity(finalColors.text, 0.7),
2017
+ "--bw-button-text-color": themeDefaults.buttonTextColor || "#ffffff",
1920
2018
  "--bw-border-color": finalColors.border,
1921
2019
  "--bw-success-color": finalColors.success,
1922
2020
  "--bw-warning-color": finalColors.warning,
@@ -1934,7 +2032,7 @@ const StyleProvider = ({ config, children, }) => {
1934
2032
  "--bw-highlight-muted": addOpacity(finalColors.highlight, 0.1),
1935
2033
  "--bw-highlight-subtle": addOpacity(finalColors.highlight, 0.05),
1936
2034
  "--bw-text-subtle": addOpacity(finalColors.text, 0.4),
1937
- colorScheme: themeName.startsWith("dark-") || themeName === "dark-night" ? "dark" : "light",
2035
+ 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",
1938
2036
  };
1939
2037
  }, [
1940
2038
  config.theme,
@@ -4488,6 +4586,7 @@ function DialogWrapper({ isOpen, onClose, children, maxWidth = "700px", classNam
4488
4586
  "--bw-font-family": computedStyles.getPropertyValue("--bw-font-family").trim() || "system-ui, sans-serif",
4489
4587
  "--bw-shadow-md": computedStyles.getPropertyValue("--bw-shadow-md").trim() ||
4490
4588
  "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
4589
+ "--bw-button-text-color": computedStyles.getPropertyValue("--bw-button-text-color").trim() || "#ffffff",
4491
4590
  };
4492
4591
  setFallbackStyles(fallbacks);
4493
4592
  }
@@ -11137,16 +11236,37 @@ const objectType = ZodObject.create;
11137
11236
  ZodUnion.create;
11138
11237
  ZodIntersection.create;
11139
11238
  ZodTuple.create;
11140
- ZodEnum.create;
11239
+ const enumType = ZodEnum.create;
11141
11240
  ZodPromise.create;
11142
11241
  ZodOptional.create;
11143
11242
  ZodNullable.create;
11243
+ const preprocessType = ZodEffects.createWithPreprocess;
11144
11244
 
11145
- const participantSchema = (t) => objectType({
11146
- name: stringType().trim().min(1, t("validation.nameRequired")),
11245
+ const DEFAULT_PARTICIPANT_FIELDS_CONFIG = {
11246
+ name: { enabled: true, required: true },
11247
+ age: { enabled: true, required: false },
11248
+ level: { enabled: false, required: false },
11249
+ };
11250
+ const participantSchema = (t, fieldsConfig) => objectType({
11251
+ name: stringType().trim().optional(),
11147
11252
  age: numberType().min(0).max(120).optional(),
11253
+ level: preprocessType((value) => (value === "" ? undefined : value), enumType(["beginner", "advanced"]).optional()),
11254
+ })
11255
+ .superRefine((value, ctx) => {
11256
+ if (fieldsConfig.name.required && (!value.name || value.name.trim().length < 1)) {
11257
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.nameRequired"), path: ["name"] });
11258
+ }
11259
+ if (!fieldsConfig.name.enabled && value.name && value.name.trim().length > 0) {
11260
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.nameRequired"), path: ["name"] });
11261
+ }
11262
+ if (fieldsConfig.age.required && typeof value.age !== "number") {
11263
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.ageRequired"), path: ["age"] });
11264
+ }
11265
+ if (fieldsConfig.level.required && !value.level) {
11266
+ ctx.addIssue({ code: ZodIssueCode.custom, message: t("validation.levelRequired"), path: ["level"] });
11267
+ }
11148
11268
  });
11149
- function createBookingFormSchema(t) {
11269
+ function createBookingFormSchema(t, fieldsConfig = DEFAULT_PARTICIPANT_FIELDS_CONFIG) {
11150
11270
  const tr = t ?? ((key) => key);
11151
11271
  return objectType({
11152
11272
  customerName: stringType().trim().min(2, tr("validation.nameMinLength")),
@@ -11156,7 +11276,7 @@ function createBookingFormSchema(t) {
11156
11276
  .email(tr("validation.emailInvalid"))
11157
11277
  .regex(/\.[a-zA-Z]{2,}$/, tr("validation.emailDomainInvalid")),
11158
11278
  customerPhone: stringType().trim().optional(),
11159
- participants: arrayType(participantSchema(tr)).min(1, tr("validation.participantRequired")),
11279
+ participants: arrayType(participantSchema(tr, fieldsConfig)).min(1, tr("validation.participantRequired")),
11160
11280
  discountCode: stringType().trim().optional(),
11161
11281
  comment: stringType().trim().optional(),
11162
11282
  acceptTerms: booleanType().refine((val) => val === true, {
@@ -11197,11 +11317,13 @@ const buttonBase = {
11197
11317
  whiteSpace: "nowrap",
11198
11318
  border: "none",
11199
11319
  };
11320
+ // CSS class name for button hover effects
11321
+ const buttonClassName = "bw-button-hover";
11200
11322
  const buttonStyles = {
11201
11323
  primary: {
11202
11324
  ...buttonBase,
11203
11325
  backgroundColor: "var(--bw-highlight-color)",
11204
- color: "#ffffff",
11326
+ color: "var(--bw-button-text-color, #ffffff)",
11205
11327
  border: "none",
11206
11328
  },
11207
11329
  secondary: {
@@ -11297,7 +11419,8 @@ const participantUpsellStyles = {
11297
11419
  gap: "8px",
11298
11420
  marginTop: "10px",
11299
11421
  paddingTop: "10px",
11300
- borderTop: "1px dashed var(--bw-border-color)",
11422
+ paddingBottom: "25px",
11423
+ borderBottom: "1px dashed var(--bw-border-color)",
11301
11424
  },
11302
11425
  label: {
11303
11426
  display: "inline-flex",
@@ -11355,6 +11478,8 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11355
11478
  const { locale } = useLocale();
11356
11479
  const timezone = useTimezone();
11357
11480
  const roundEnabled = systemConfig?.roundPricesEnabled !== false;
11481
+ const participantFieldsConfig = eventDetails.participantFieldsConfig ?? DEFAULT_PARTICIPANT_FIELDS_CONFIG;
11482
+ const participantLevelOptions = eventDetails.participantLevelOptions ?? ["beginner", "advanced"];
11358
11483
  const roundDiscountUp = (minorUnits) => Math.ceil(minorUnits / 100) * 100;
11359
11484
  const calcPercentDiscountAmount = (baseAmount, basisPoints, round) => {
11360
11485
  const raw = Math.round((baseAmount * basisPoints) / 10000);
@@ -11367,18 +11492,19 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11367
11492
  // Per-participant upsell selections: participantIndex -> array of upsell package IDs
11368
11493
  const [participantUpsells, setParticipantUpsells] = React.useState({});
11369
11494
  const form = useForm({
11370
- resolver: t(createBookingFormSchema(t$1)),
11495
+ resolver: t(createBookingFormSchema(t$1, participantFieldsConfig)),
11371
11496
  defaultValues: {
11372
11497
  customerName: "",
11373
11498
  customerEmail: "",
11374
11499
  customerPhone: "",
11375
- participants: [{ name: "" }],
11500
+ participants: [{ name: "", level: undefined }],
11376
11501
  discountCode: "",
11377
11502
  comment: "",
11378
11503
  acceptTerms: false,
11379
11504
  },
11380
11505
  });
11381
11506
  const watchedParticipants = form.watch("participants");
11507
+ const participantCount = watchedParticipants.length;
11382
11508
  const watchedCustomerName = form.watch("customerName");
11383
11509
  const watchedCustomerEmail = form.watch("customerEmail");
11384
11510
  const watchedComment = form.watch("comment");
@@ -11420,14 +11546,13 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11420
11546
  const calculateBaseTotal = React.useCallback(() => {
11421
11547
  if (!eventDetails)
11422
11548
  return 0;
11423
- return eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length;
11424
- }, [eventDetails, watchedParticipants]);
11549
+ return eventDetails.price * participantCount;
11550
+ }, [eventDetails, participantCount]);
11425
11551
  // Calculate upsells total based on per-participant selections
11426
11552
  const calculateUpsellsTotal = React.useCallback(() => {
11427
11553
  let total = 0;
11428
- watchedParticipants.forEach((participant, index) => {
11429
- // Only count upsells for participants with names
11430
- if (participant.name.trim()) {
11554
+ watchedParticipants.forEach((_, index) => {
11555
+ if (participantCount > 0) {
11431
11556
  const participantUpsellIds = participantUpsells[index] || [];
11432
11557
  participantUpsellIds.forEach(upsellId => {
11433
11558
  const upsell = upsells.find(u => u.id === upsellId);
@@ -11438,7 +11563,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11438
11563
  }
11439
11564
  });
11440
11565
  return total;
11441
- }, [participantUpsells, upsells, watchedParticipants]);
11566
+ }, [participantUpsells, upsells, watchedParticipants, participantCount]);
11442
11567
  const calculateTotalDiscount = React.useCallback(() => {
11443
11568
  return appliedVouchers.reduce((total, voucher) => {
11444
11569
  if (voucher.type === "discount") {
@@ -11459,8 +11584,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11459
11584
  const calculateDeposit = () => {
11460
11585
  if (!eventDetails || !eventDetails.deposit)
11461
11586
  return 0;
11462
- const participantCount = watchedParticipants.filter((p) => p.name.trim()).length;
11463
- return eventDetails.deposit * participantCount;
11587
+ return eventDetails.deposit * watchedParticipants.length;
11464
11588
  };
11465
11589
  const baseTotal = calculateBaseTotal();
11466
11590
  const upsellsTotal = calculateUpsellsTotal();
@@ -11477,8 +11601,8 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11477
11601
  // Includes participantIndices to track which participants selected each upsell
11478
11602
  const aggregatedUpsellSelections = React.useCallback(() => {
11479
11603
  const upsellParticipantMap = {};
11480
- watchedParticipants.forEach((participant, index) => {
11481
- if (participant.name.trim()) {
11604
+ watchedParticipants.forEach((_, index) => {
11605
+ if (participantCount > 0) {
11482
11606
  const participantUpsellIds = participantUpsells[index] || [];
11483
11607
  participantUpsellIds.forEach(upsellId => {
11484
11608
  if (!upsellParticipantMap[upsellId]) {
@@ -11512,15 +11636,17 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11512
11636
  setAppliedVouchers((prev) => prev.filter((v) => v.code !== code));
11513
11637
  }, []);
11514
11638
  const isReadyForPayment = () => {
11515
- const participantsWithNames = watchedParticipants.filter((p) => p.name.trim()).length;
11639
+ const participantsWithNames = watchedParticipants.filter((p) => p.name?.trim()).length;
11516
11640
  const totalParticipantRows = watchedParticipants.length;
11517
- const allParticipantsHaveNames = participantsWithNames === totalParticipantRows;
11641
+ const allParticipantsHaveNames = participantFieldsConfig.name.required
11642
+ ? participantsWithNames === totalParticipantRows
11643
+ : true;
11518
11644
  const participantsWithinLimit = participantsWithNames <= (eventDetails?.availableSpots || 0);
11519
11645
  const hasValidCustomerName = watchedCustomerName && watchedCustomerName.trim().length >= 2;
11520
11646
  const hasValidCustomerEmail = watchedCustomerEmail && watchedCustomerEmail.trim().length > 0 && !customerEmailError;
11521
11647
  return allParticipantsHaveNames &&
11522
11648
  participantsWithinLimit &&
11523
- participantsWithNames > 0 &&
11649
+ (participantFieldsConfig.name.required ? participantsWithNames > 0 : totalParticipantRows > 0) &&
11524
11650
  hasValidCustomerName &&
11525
11651
  hasValidCustomerEmail &&
11526
11652
  watchedAcceptTerms;
@@ -11528,7 +11654,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11528
11654
  React.useEffect(() => {
11529
11655
  if (appliedVouchers.length > 0) {
11530
11656
  const newBaseTotal = eventDetails?.price
11531
- ? eventDetails.price * watchedParticipants.filter((p) => p.name.trim()).length
11657
+ ? eventDetails.price * watchedParticipants.length
11532
11658
  : 0;
11533
11659
  const currentUpsellsTotal = calculateUpsellsTotal();
11534
11660
  const orderTotal = newBaseTotal + currentUpsellsTotal;
@@ -11573,7 +11699,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11573
11699
  const currentParticipants = form.getValues("participants");
11574
11700
  const availableSpots = eventDetails?.availableSpots || 0;
11575
11701
  if (currentParticipants.length < availableSpots) {
11576
- form.setValue("participants", [...currentParticipants, { name: "" }]);
11702
+ form.setValue("participants", [...currentParticipants, { name: "", level: undefined }]);
11577
11703
  }
11578
11704
  else {
11579
11705
  alert(t$1("booking.maxParticipants", { count: availableSpots }));
@@ -11692,7 +11818,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11692
11818
  justifyContent: "space-between",
11693
11819
  alignItems: "center",
11694
11820
  marginBottom: "16px",
11695
- }, children: jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles$1, marginBottom: 0 }, children: t$1("booking.participants") }) }), jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [watchedParticipants.map((_, index) => (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [jsxRuntime.jsxs("div", { style: { display: "flex", gap: "12px", alignItems: "center" }, children: [jsxRuntime.jsxs("div", { style: { flex: 1 }, children: [jsxRuntime.jsx("label", { htmlFor: `participant-name-${index}`, style: labelStyles$1, children: t$1("booking.participantName") }), jsxRuntime.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 && (jsxRuntime.jsx("p", { style: errorTextStyles$1, children: form.formState.errors.participants[index]?.name?.message }))] }), jsxRuntime.jsxs("div", { style: { width: "80px" }, children: [jsxRuntime.jsx("label", { htmlFor: `participant-age-${index}`, style: labelStyles$1, children: t$1("booking.participantAge") }), jsxRuntime.jsx("input", { id: `participant-age-${index}`, ...form.register(`participants.${index}.age`, {
11821
+ }, children: jsxRuntime.jsx("h2", { style: { ...sectionHeaderStyles$1, marginBottom: 0 }, children: t$1("booking.participants") }) }), jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "16px" }, children: [watchedParticipants.map((_, index) => (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [jsxRuntime.jsxs("div", { style: { display: "flex", gap: "12px", alignItems: "center" }, children: [participantFieldsConfig.name.enabled && (jsxRuntime.jsxs("div", { style: { flex: 1 }, children: [jsxRuntime.jsx("label", { htmlFor: `participant-name-${index}`, style: labelStyles$1, children: t$1("booking.participantName") }), jsxRuntime.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 && (jsxRuntime.jsx("p", { style: errorTextStyles$1, children: form.formState.errors.participants[index]?.name?.message }))] })), participantFieldsConfig.age.enabled && (jsxRuntime.jsxs("div", { style: { width: "80px" }, children: [jsxRuntime.jsx("label", { htmlFor: `participant-age-${index}`, style: labelStyles$1, children: t$1("booking.participantAge") }), jsxRuntime.jsx("input", { id: `participant-age-${index}`, ...form.register(`participants.${index}.age`, {
11696
11822
  setValueAs: (value) => {
11697
11823
  if (value === "" || value === null || value === undefined) {
11698
11824
  return undefined;
@@ -11700,7 +11826,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11700
11826
  const num = Number(value);
11701
11827
  return Number.isNaN(num) ? undefined : num;
11702
11828
  },
11703
- }), type: "number", min: "0", max: "120", style: inputStyles$1, placeholder: "25" })] }), watchedParticipants.length > 1 && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { style: { ...labelStyles$1, visibility: "hidden" }, children: "\u00A0" }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeParticipant(index), style: {
11829
+ }), type: "number", min: "0", max: "120", style: inputStyles$1, placeholder: "25" })] })), watchedParticipants.length > 1 && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("label", { style: { ...labelStyles$1, visibility: "hidden" }, children: "\u00A0" }), jsxRuntime.jsx("button", { type: "button", onClick: () => removeParticipant(index), style: {
11704
11830
  color: "var(--bw-error-color)",
11705
11831
  backgroundColor: "var(--bw-surface-color)",
11706
11832
  border: "1px solid var(--bw-border-color)",
@@ -11716,7 +11842,7 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11716
11842
  fontWeight: 700,
11717
11843
  fontFamily: "var(--bw-font-family)",
11718
11844
  padding: 0,
11719
- }, children: "\u00D7" })] }))] }), upsells.length > 0 && (jsxRuntime.jsx("div", { style: participantUpsellStyles.container, children: upsells.map((upsell) => {
11845
+ }, children: "\u00D7" })] }))] }), participantFieldsConfig.level.enabled && (jsxRuntime.jsxs("div", { style: { minWidth: "140px" }, children: [jsxRuntime.jsx("label", { htmlFor: `participant-level-${index}`, style: labelStyles$1, children: t$1("booking.participantLevel") }), jsxRuntime.jsxs("select", { id: `participant-level-${index}`, ...form.register(`participants.${index}.level`), style: inputStyles$1, children: [jsxRuntime.jsx("option", { value: "", children: t$1("booking.participantLevelPlaceholder") }), participantLevelOptions.map((level) => (jsxRuntime.jsx("option", { value: level, children: t$1(`level.${level}`) }, level)))] }), form.formState.errors.participants?.[index]?.level && (jsxRuntime.jsx("p", { style: errorTextStyles$1, children: form.formState.errors.participants[index]?.level?.message }))] })), upsells.length > 0 && (jsxRuntime.jsx("div", { style: participantUpsellStyles.container, children: upsells.map((upsell) => {
11720
11846
  const isSelected = (participantUpsells[index] || []).includes(upsell.id);
11721
11847
  return (jsxRuntime.jsxs("label", { htmlFor: `upsell-${index}-${upsell.id}`, style: isSelected ? participantUpsellStyles.labelSelected : participantUpsellStyles.label, children: [jsxRuntime.jsx("input", { id: `upsell-${index}-${upsell.id}`, type: "checkbox", style: participantUpsellStyles.checkbox, checked: isSelected, onChange: () => toggleParticipantUpsell(index, upsell.id) }), jsxRuntime.jsx("span", { style: { fontWeight: 500 }, children: upsell.name }), jsxRuntime.jsxs("span", { style: { fontSize: "12px", opacity: 0.8 }, children: ["(+", formatCurrency(upsell.price), ")"] })] }, upsell.id));
11722
11848
  }) }))] }, index))), watchedParticipants.length < eventDetails.availableSpots ? (jsxRuntime.jsx("div", { style: {
@@ -11745,9 +11871,9 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11745
11871
  color: "var(--bw-text-color)",
11746
11872
  fontWeight: 500,
11747
11873
  fontFamily: "var(--bw-font-family)",
11748
- }, children: [jsxRuntime.jsxs("span", { style: { fontWeight: 200 }, children: [watchedParticipants.length > 1 ? watchedParticipants.filter((p) => p.name.trim()).length : 1, " x "] }), " ", formatCurrency(eventDetails.price)] })] }), upsellsTotal > 0 && (jsxRuntime.jsxs("div", { style: { marginTop: "8px", paddingTop: "8px", borderTop: "1px dashed var(--bw-border-color)" }, children: [jsxRuntime.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) => {
11874
+ }, children: [jsxRuntime.jsxs("span", { style: { fontWeight: 200 }, children: [watchedParticipants.length > 1 ? watchedParticipants.length : 1, " x "] }), " ", formatCurrency(eventDetails.price)] })] }), upsellsTotal > 0 && (jsxRuntime.jsxs("div", { style: { marginTop: "8px", paddingTop: "8px", borderTop: "1px dashed var(--bw-border-color)" }, children: [jsxRuntime.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) => {
11749
11875
  // Count how many participants have this upsell selected
11750
- const countWithUpsell = watchedParticipants.filter((p, idx) => p.name.trim() && (participantUpsells[idx] || []).includes(upsell.id)).length;
11876
+ const countWithUpsell = watchedParticipants.filter((_, idx) => (participantUpsells[idx] || []).includes(upsell.id)).length;
11751
11877
  if (countWithUpsell === 0)
11752
11878
  return null;
11753
11879
  const upsellLineTotal = upsell.price * countWithUpsell;
@@ -11848,15 +11974,17 @@ function BookingForm({ config, eventDetails, stripePromise, onSuccess, onError,
11848
11974
  }, children: t$1("summary.remainingOnSite", { amount: formatCurrency(totalAmount - depositAmount) }) }))] })] })] }), jsxRuntime.jsx("div", { ref: paymentSectionRef, children: (stripePromise || systemConfig?.paymentProvider === "mollie") &&
11849
11975
  (() => {
11850
11976
  if (!isReadyForPayment()) {
11851
- const participantsWithNames = watchedParticipants.filter((p) => p.name.trim()).length;
11977
+ const participantsWithNames = watchedParticipants.filter((p) => p.name?.trim()).length;
11852
11978
  const totalParticipantRows = watchedParticipants.length;
11853
11979
  const participantsWithoutNames = totalParticipantRows - participantsWithNames;
11854
11980
  const missing = [];
11855
- if (participantsWithNames === 0) {
11856
- missing.push(t$1("payment.needParticipant"));
11857
- }
11858
- else if (participantsWithoutNames > 0) {
11859
- missing.push(t$1("payment.needAllNames", { count: totalParticipantRows }));
11981
+ if (participantFieldsConfig.name.required) {
11982
+ if (participantsWithNames === 0) {
11983
+ missing.push(t$1("payment.needParticipant"));
11984
+ }
11985
+ else if (participantsWithoutNames > 0) {
11986
+ missing.push(t$1("payment.needAllNames", { count: totalParticipantRows }));
11987
+ }
11860
11988
  }
11861
11989
  if (participantsWithNames > (eventDetails?.availableSpots || 0)) {
11862
11990
  missing.push(t$1("payment.reduceParticipants", { count: eventDetails?.availableSpots || 0 }));
@@ -12031,7 +12159,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
12031
12159
  try {
12032
12160
  const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/get-booking-by-payment"), {
12033
12161
  method: "POST",
12034
- headers: createApiHeaders(config),
12162
+ headers: createApiHeaders(config, locale),
12035
12163
  body: JSON.stringify(createRequestBody(config, {
12036
12164
  paymentIntentId: targetPaymentIntentId,
12037
12165
  })),
@@ -12265,7 +12393,7 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
12265
12393
  flexDirection: "column",
12266
12394
  gap: "var(--bw-spacing-small)",
12267
12395
  }, children: formData.participants
12268
- .filter((p) => p.name.trim())
12396
+ .filter((p) => p.name?.trim() || p.age || p.level)
12269
12397
  .map((participant, index) => (jsxRuntime.jsx("div", { className: "print-participant", style: {
12270
12398
  display: "flex",
12271
12399
  justifyContent: "space-between",
@@ -12276,11 +12404,15 @@ const BookingSuccessModal = ({ isOpen, onClose, config, onError, paymentIntentId
12276
12404
  }, children: jsxRuntime.jsxs("div", { className: "print-participant-info", children: [jsxRuntime.jsx("div", { className: "print-participant-name", style: {
12277
12405
  color: "var(--bw-text-color)",
12278
12406
  fontFamily: "var(--bw-font-family)",
12279
- }, children: participant.name }), participant.age && (jsxRuntime.jsx("div", { className: "print-participant-age", style: {
12407
+ }, children: participant.name || `#${index + 1}` }), participant.age && (jsxRuntime.jsx("div", { className: "print-participant-age", style: {
12280
12408
  color: "var(--bw-text-muted)",
12281
12409
  fontSize: "var(--bw-font-size-small)",
12282
12410
  fontFamily: "var(--bw-font-family)",
12283
- }, children: t("success.age", { age: participant.age }) }))] }) }, index))) }) })] })), jsxRuntime.jsxs("div", { className: "print-booking-card", style: {
12411
+ }, children: t("success.age", { age: participant.age }) })), participant.level && (jsxRuntime.jsxs("div", { style: {
12412
+ color: "var(--bw-text-muted)",
12413
+ fontSize: "var(--bw-font-size-small)",
12414
+ fontFamily: "var(--bw-font-family)",
12415
+ }, children: [t("booking.participantLevel"), ": ", t(`level.${participant.level}`)] }))] }) }, index))) }) })] })), jsxRuntime.jsxs("div", { className: "print-booking-card", style: {
12284
12416
  backgroundColor: "var(--bw-surface-color)",
12285
12417
  border: `1px solid var(--bw-border-color)`,
12286
12418
  borderRadius: "var(--bw-border-radius)",
@@ -13688,8 +13820,48 @@ function formatDurationInfo(info, t) {
13688
13820
  const rest = formatted.slice(0, -1).join(", ");
13689
13821
  return `${t("duration.optionally")} ${rest} ${t("duration.or")} ${last} ${unitPlural}`;
13690
13822
  }
13691
- function EventTypeSelection({ eventTypes, onEventTypeSelect, isLoading = false, skeletonCount = 4, showVoucherAttachment = false, onVoucherClick, }) {
13823
+ function InfoBadge({ text }) {
13824
+ const [open, setOpen] = React.useState(false);
13825
+ const ref = React.useRef(null);
13826
+ return (jsxRuntime.jsxs("span", { ref: ref, onClick: (e) => { e.stopPropagation(); setOpen((v) => !v); }, style: {
13827
+ position: "relative",
13828
+ display: "inline-flex",
13829
+ alignItems: "center",
13830
+ justifyContent: "center",
13831
+ width: "16px",
13832
+ height: "16px",
13833
+ borderRadius: "50%",
13834
+ border: "1px solid var(--bw-highlight-color)",
13835
+ color: "var(--bw-highlight-color)",
13836
+ fontSize: "9px",
13837
+ fontWeight: 700,
13838
+ cursor: "pointer",
13839
+ flexShrink: 0,
13840
+ userSelect: "none",
13841
+ }, children: ["i", open && (jsxRuntime.jsx("span", { style: {
13842
+ position: "absolute",
13843
+ bottom: "calc(100% + 6px)",
13844
+ right: 0,
13845
+ backgroundColor: "var(--bw-surface-color)",
13846
+ border: "1px solid var(--bw-border-color)",
13847
+ borderRadius: "var(--bw-border-radius-small)",
13848
+ boxShadow: "var(--bw-shadow-md)",
13849
+ padding: "6px 10px",
13850
+ fontSize: "14px",
13851
+ color: "var(--bw-text-color)",
13852
+ fontWeight: 400,
13853
+ whiteSpace: "normal",
13854
+ width: "160px",
13855
+ lineHeight: 1.4,
13856
+ zIndex: 100,
13857
+ textAlign: "left",
13858
+ pointerEvents: "none",
13859
+ }, children: text }))] }));
13860
+ }
13861
+ function EventTypeSelection({ eventTypes, onEventTypeSelect, onInstancePreview, isLoading = false, skeletonCount = 4, showVoucherAttachment = false, onVoucherClick, }) {
13692
13862
  const t = useTranslations();
13863
+ const { locale } = useLocale();
13864
+ const timezone = useTimezone();
13693
13865
  // State for details dialog
13694
13866
  const [detailsDialogOpen, setDetailsDialogOpen] = React.useState(false);
13695
13867
  const [selectedEventTypeForDetails, setSelectedEventTypeForDetails] = React.useState(null);
@@ -13799,7 +13971,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, isLoading = false,
13799
13971
  display: "flex",
13800
13972
  flexDirection: "column",
13801
13973
  justifyContent: "space-between",
13802
- height: "400px",
13974
+ height: "490px",
13803
13975
  }, children: [jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("h2", { className: "event-type-title", style: {
13804
13976
  fontSize: "clamp(1.1rem, 2.5vw, 24px)",
13805
13977
  fontWeight: 700,
@@ -13880,7 +14052,43 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, isLoading = false,
13880
14052
  color: "var(--bw-text-color)",
13881
14053
  fontFamily: "var(--bw-font-family)",
13882
14054
  textAlign: "right",
13883
- }, children: jsxRuntime.jsxs("span", { children: [t("common.from"), " ", formatCurrency(eventType.minPrice)] }) })] }), jsxRuntime.jsxs("div", { style: {
14055
+ }, children: jsxRuntime.jsxs("span", { children: [t("common.from"), " ", formatCurrency(eventType.minPrice)] }) })] }), (() => {
14056
+ const preview = eventType.cardPreview ?? [];
14057
+ return (jsxRuntime.jsxs("div", { style: {
14058
+ marginTop: "12px",
14059
+ borderTop: "1px solid var(--bw-border-color)",
14060
+ paddingTop: "8px",
14061
+ marginBottom: "16px",
14062
+ }, children: [jsxRuntime.jsx("div", { style: {
14063
+ fontSize: "11px",
14064
+ fontWeight: 700,
14065
+ color: "var(--bw-text-muted)",
14066
+ textTransform: "uppercase",
14067
+ letterSpacing: "0.05em",
14068
+ marginBottom: "4px",
14069
+ }, children: t("events.previewSectionTitle") }), jsxRuntime.jsx("div", { style: { height: "102px" }, children: Array.from({ length: 3 }).map((_, i) => {
14070
+ const item = preview[i];
14071
+ if (!item)
14072
+ return jsxRuntime.jsx("div", { style: { height: "34px" } }, i);
14073
+ const hasDiscount = item.basePrice > 0 && item.price < item.basePrice;
14074
+ return (jsxRuntime.jsxs("div", { onClick: (e) => { e.stopPropagation(); onInstancePreview?.(item.id, eventType.id); }, onMouseEnter: (e) => { if (onInstancePreview)
14075
+ e.currentTarget.style.backgroundColor = "var(--bw-border-color)"; }, onMouseLeave: (e) => { e.currentTarget.style.backgroundColor = "transparent"; }, style: {
14076
+ height: "34px",
14077
+ display: "grid",
14078
+ gridTemplateColumns: "auto 1fr auto 20px",
14079
+ alignItems: "center",
14080
+ gap: "8px",
14081
+ width: "100%",
14082
+ fontSize: "13px",
14083
+ color: "var(--bw-text-muted)",
14084
+ cursor: onInstancePreview ? "pointer" : "default",
14085
+ borderRadius: "4px",
14086
+ padding: "0 2px",
14087
+ transition: "background 0.15s",
14088
+ boxSizing: "border-box",
14089
+ }, children: [jsxRuntime.jsxs("span", { style: { whiteSpace: "nowrap", display: "flex", alignItems: "center", gap: "3px" }, children: [item.isSpecial && (jsxRuntime.jsx("span", { style: { color: "var(--bw-highlight-color)", fontSize: "11px", lineHeight: 1 }, children: "\u2605" })), formatWeekday(item.startTime, timezone, locale), " ", formatDate(item.startTime, timezone, locale)] }), jsxRuntime.jsx("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", opacity: 0.75 }, children: item.name }), jsxRuntime.jsxs("span", { style: { display: "flex", alignItems: "center", gap: "4px", whiteSpace: "nowrap" }, children: [hasDiscount && (jsxRuntime.jsx("span", { style: { textDecoration: "line-through", opacity: 0.55, fontSize: "11px" }, children: formatCurrency(item.basePrice) })), jsxRuntime.jsx("span", { style: { fontWeight: 700, color: item.isSpecial ? "var(--bw-highlight-color)" : "var(--bw-text-color)" }, children: formatCurrency(item.price) })] }), item.specialDescription ? (jsxRuntime.jsx(InfoBadge, { text: item.specialDescription })) : (jsxRuntime.jsx("span", {}))] }, item.id));
14090
+ }) })] }));
14091
+ })(), jsxRuntime.jsxs("div", { style: {
13884
14092
  display: "flex",
13885
14093
  justifyContent: "flex-end",
13886
14094
  alignItems: "center",
@@ -13895,7 +14103,6 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, isLoading = false,
13895
14103
  backgroundColor: "var(--bw-surface-color)",
13896
14104
  padding: "12px",
13897
14105
  borderRadius: "var(--bw-border-radius)",
13898
- fontSize: "clamp(0.8rem, 2vw, 16px)",
13899
14106
  fontWeight: 600,
13900
14107
  fontFamily: "var(--bw-font-family)",
13901
14108
  display: "flex",
@@ -13908,7 +14115,7 @@ function EventTypeSelection({ eventTypes, onEventTypeSelect, isLoading = false,
13908
14115
  }, children: t("button.moreDetails") })), isAvailable && (jsxRuntime.jsxs("div", { style: {
13909
14116
  backgroundColor: "var(--bw-highlight-color)",
13910
14117
  color: "var(--bw-surface-color)",
13911
- padding: "12px 24px",
14118
+ padding: "12px 14px",
13912
14119
  borderRadius: "var(--bw-border-radius)",
13913
14120
  fontSize: "clamp(1rem, 2vw, 16px)",
13914
14121
  fontWeight: 600,
@@ -14566,6 +14773,172 @@ function NextEventsPreview({ events, onEventSelect, onShowAll, showAllButtonText
14566
14773
  }, children: "\u27F3" }), t("common.loading")] })) : (showAllButtonText) }) }))] }));
14567
14774
  }
14568
14775
 
14776
+ function SpecialsView({ specials, onEventSelect, isLoading = false, showSavingsAmount = true, showSavingsPercent = false, emptyStateText, isLoadingEventDetails = false, }) {
14777
+ const t = useTranslations();
14778
+ const { locale } = useLocale();
14779
+ const timezone = useTimezone();
14780
+ const [selectedId, setSelectedId] = React.useState(null);
14781
+ const handleSelect = (id) => {
14782
+ setSelectedId(id);
14783
+ onEventSelect(id);
14784
+ };
14785
+ if (isLoading) {
14786
+ return jsxRuntime.jsx(NextEventsSkeleton, { count: 3 });
14787
+ }
14788
+ if (specials.length === 0) {
14789
+ return (jsxRuntime.jsx("div", { style: { maxWidth: "500px", margin: "0 auto", padding: "16px" }, children: jsxRuntime.jsxs("div", { style: {
14790
+ display: "flex",
14791
+ flexDirection: "column",
14792
+ alignItems: "center",
14793
+ justifyContent: "center",
14794
+ textAlign: "center",
14795
+ backgroundColor: "var(--bw-surface-color)",
14796
+ border: "1px solid var(--bw-border-color)",
14797
+ borderRadius: "var(--bw-border-radius)",
14798
+ padding: "24px",
14799
+ fontFamily: "var(--bw-font-family)",
14800
+ minHeight: "300px",
14801
+ }, children: [jsxRuntime.jsx("div", { style: {
14802
+ display: "flex",
14803
+ alignItems: "center",
14804
+ justifyContent: "center",
14805
+ borderRadius: "50%",
14806
+ width: "64px",
14807
+ height: "64px",
14808
+ backgroundColor: "var(--bw-highlight-color)",
14809
+ marginBottom: "16px",
14810
+ fontSize: "32px",
14811
+ color: "#ffffff",
14812
+ opacity: 0.8,
14813
+ }, children: "\uD83C\uDFF7\uFE0F" }), jsxRuntime.jsx("h3", { style: {
14814
+ fontWeight: 600,
14815
+ margin: "0 0 8px 0",
14816
+ fontSize: "20px",
14817
+ color: "var(--bw-text-color)",
14818
+ fontFamily: "var(--bw-font-family)",
14819
+ }, children: t("specials.noSpecials") }), jsxRuntime.jsx("p", { style: {
14820
+ margin: 0,
14821
+ color: "var(--bw-text-muted)",
14822
+ fontSize: "16px",
14823
+ lineHeight: 1.6,
14824
+ fontFamily: "var(--bw-font-family)",
14825
+ maxWidth: "400px",
14826
+ }, children: emptyStateText ?? t("specials.noSpecialsMessage") })] }) }));
14827
+ }
14828
+ return (jsxRuntime.jsxs("div", { style: {
14829
+ maxWidth: "500px",
14830
+ margin: "0 auto",
14831
+ padding: "16px",
14832
+ fontFamily: "var(--bw-font-family)",
14833
+ }, children: [jsxRuntime.jsxs("div", { style: { textAlign: "center", marginBottom: "24px" }, children: [jsxRuntime.jsx("h2", { style: {
14834
+ fontWeight: 600,
14835
+ margin: "0 0 8px 0",
14836
+ fontSize: "18px",
14837
+ color: "var(--bw-text-color)",
14838
+ fontFamily: "var(--bw-font-family)",
14839
+ }, children: t("specials.title") }), jsxRuntime.jsx("p", { style: {
14840
+ margin: 0,
14841
+ fontSize: "16px",
14842
+ color: "var(--bw-text-muted)",
14843
+ fontFamily: "var(--bw-font-family)",
14844
+ }, children: t("specials.subtitle") })] }), jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px" }, children: specials.map((special) => {
14845
+ const isFullyBooked = special.availableSpots === 0;
14846
+ const isDisabled = isFullyBooked || !special.bookingOpen;
14847
+ const hasDiscount = special.basePrice > 0 && special.price < special.basePrice;
14848
+ return (jsxRuntime.jsxs("div", { style: {
14849
+ position: "relative",
14850
+ backgroundColor: "var(--bw-surface-color)",
14851
+ borderRadius: "var(--bw-border-radius)",
14852
+ border: "1px solid var(--bw-highlight-color)",
14853
+ overflow: "hidden",
14854
+ opacity: isDisabled ? 0.5 : 1,
14855
+ cursor: isDisabled ? "not-allowed" : "pointer",
14856
+ transition: "all 0.2s ease",
14857
+ }, onClick: () => {
14858
+ if (!isDisabled)
14859
+ handleSelect(special.id);
14860
+ }, children: [selectedId === special.id && isLoadingEventDetails && (jsxRuntime.jsx("div", { style: {
14861
+ position: "absolute",
14862
+ inset: 0,
14863
+ display: "flex",
14864
+ alignItems: "center",
14865
+ justifyContent: "center",
14866
+ backgroundColor: "rgba(15, 23, 42, 0.8)",
14867
+ borderRadius: "var(--bw-border-radius)",
14868
+ zIndex: 10,
14869
+ }, children: jsxRuntime.jsx("div", { style: { fontSize: "32px", color: "var(--bw-highlight-color)", animation: "spin 1s linear infinite" }, children: "\u27F3" }) })), jsxRuntime.jsxs("div", { style: { display: "flex", gap: "12px", padding: "12px" }, children: [special.images.length > 0 && (jsxRuntime.jsx("div", { style: {
14870
+ flexShrink: 0,
14871
+ width: "72px",
14872
+ height: "72px",
14873
+ borderRadius: "var(--bw-border-radius-small)",
14874
+ overflow: "hidden",
14875
+ }, children: jsxRuntime.jsx("img", { src: special.images[0], alt: special.eventTypeName, style: { width: "100%", height: "100%", objectFit: "cover" } }) })), jsxRuntime.jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [jsxRuntime.jsx("div", { style: { marginBottom: "4px" }, children: jsxRuntime.jsx("span", { style: {
14876
+ fontSize: "11px",
14877
+ fontWeight: 600,
14878
+ color: "var(--bw-text-muted)",
14879
+ textTransform: "uppercase",
14880
+ letterSpacing: "0.05em",
14881
+ }, children: special.categoryName }) }), jsxRuntime.jsx("h4", { style: {
14882
+ margin: "0 0 2px 0",
14883
+ fontSize: "15px",
14884
+ fontWeight: 600,
14885
+ color: "var(--bw-text-color)",
14886
+ lineHeight: 1.3,
14887
+ overflow: "hidden",
14888
+ textOverflow: "ellipsis",
14889
+ whiteSpace: "nowrap",
14890
+ }, children: special.eventTypeName }), jsxRuntime.jsxs("div", { style: {
14891
+ fontSize: "13px",
14892
+ fontWeight: 500,
14893
+ color: "var(--bw-highlight-color)",
14894
+ marginBottom: "2px",
14895
+ overflow: "hidden",
14896
+ textOverflow: "ellipsis",
14897
+ whiteSpace: "nowrap",
14898
+ }, children: ["\u2605 ", special.name] }), special.specialDescription && (jsxRuntime.jsx("div", { style: {
14899
+ fontSize: "12px",
14900
+ color: "var(--bw-text-muted)",
14901
+ marginBottom: "6px",
14902
+ lineHeight: 1.4,
14903
+ }, children: special.specialDescription })), jsxRuntime.jsxs("div", { style: { fontSize: "13px", color: "var(--bw-text-muted)", marginBottom: "8px" }, children: [formatWeekday(special.startTime, timezone, locale), ",", " ", formatDate(special.startTime, timezone, locale)] }), jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" }, children: [hasDiscount && (jsxRuntime.jsx("span", { style: {
14904
+ fontSize: "13px",
14905
+ color: "var(--bw-text-muted)",
14906
+ textDecoration: "line-through",
14907
+ }, children: formatCurrency(special.basePrice) })), jsxRuntime.jsx("span", { style: {
14908
+ fontSize: "16px",
14909
+ fontWeight: 700,
14910
+ color: "var(--bw-highlight-color)",
14911
+ }, children: formatCurrency(special.price) }), hasDiscount && showSavingsAmount && (jsxRuntime.jsx("span", { style: {
14912
+ fontSize: "12px",
14913
+ fontWeight: 600,
14914
+ color: "#ffffff",
14915
+ backgroundColor: "var(--bw-success-color, #22c55e)",
14916
+ borderRadius: "4px",
14917
+ padding: "2px 6px",
14918
+ }, children: t("specials.save").replace("{{amount}}", formatCurrency(special.savings)) })), hasDiscount && showSavingsPercent && (jsxRuntime.jsx("span", { style: {
14919
+ fontSize: "12px",
14920
+ fontWeight: 600,
14921
+ color: "#ffffff",
14922
+ backgroundColor: "var(--bw-success-color, #22c55e)",
14923
+ borderRadius: "4px",
14924
+ padding: "2px 6px",
14925
+ }, children: t("specials.savePercent").replace("{{percent}}", String(special.savingsPercent)) }))] })] })] }), jsxRuntime.jsxs("div", { style: {
14926
+ borderTop: "1px solid var(--bw-border-color)",
14927
+ padding: "8px 12px",
14928
+ display: "flex",
14929
+ justifyContent: "space-between",
14930
+ alignItems: "center",
14931
+ backgroundColor: "var(--bw-background-color)",
14932
+ }, children: [jsxRuntime.jsx("span", { style: { fontSize: "12px", color: "var(--bw-text-muted)" }, children: isFullyBooked
14933
+ ? t("common.fullyBooked")
14934
+ : t("specials.spotsLeft").replace("{{count}}", String(special.availableSpots)) }), jsxRuntime.jsxs("span", { style: {
14935
+ fontSize: "13px",
14936
+ fontWeight: 600,
14937
+ color: "var(--bw-highlight-color)",
14938
+ }, children: [t("specials.bookNow"), " \u2192"] })] })] }, special.id));
14939
+ }) })] }));
14940
+ }
14941
+
14569
14942
  const getThemeConfig = (theme = "generic") => {
14570
14943
  switch (theme) {
14571
14944
  case "christmas":
@@ -14825,8 +15198,8 @@ const cardDisabledStyles = {
14825
15198
  };
14826
15199
  const checkboxContainerStyles = {
14827
15200
  position: "absolute",
14828
- top: "12px",
14829
- right: "12px",
15201
+ bottom: "12px",
15202
+ left: "12px",
14830
15203
  zIndex: 1,
14831
15204
  };
14832
15205
  const checkboxInnerStyles = {
@@ -14921,6 +15294,7 @@ const priceContainerStyles = {
14921
15294
  alignItems: "flex-end",
14922
15295
  marginTop: "8px",
14923
15296
  paddingTop: "8px",
15297
+ paddingBottom: "0px",
14924
15298
  borderTop: "1px solid var(--bw-border-color)",
14925
15299
  };
14926
15300
  const pricePerPersonStyles = {
@@ -15012,7 +15386,7 @@ function UpsellsStep({ upsells, selectedUpsells, participantCount, isLoading, is
15012
15386
  };
15013
15387
  const selectedTotal = calculateTotal();
15014
15388
  const selectedCount = selectedUpsells.length;
15015
- const footerContent = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { type: "button", onClick: onBack, style: mergeStyles(buttonStyles.secondary, buttonStyles.fullWidth), children: t("common.back") }), jsxRuntime.jsx("button", { type: "button", onClick: onContinue, style: mergeStyles(buttonStyles.primary, buttonStyles.fullWidth), children: selectedCount === 0 ? t("button.continueWithout") : t("button.continue") })] }));
15389
+ const footerContent = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("button", { type: "button", onClick: onBack, style: mergeStyles(buttonStyles.secondary, buttonStyles.fullWidth), className: buttonClassName, children: t("common.back") }), jsxRuntime.jsx("button", { type: "button", onClick: onContinue, style: mergeStyles(buttonStyles.primary, buttonStyles.fullWidth), className: buttonClassName, children: selectedCount === 0 ? t("button.continueWithout") : t("button.continue") })] }));
15016
15390
  return (jsxRuntime.jsx(Sidebar, { isOpen: isOpen, onClose: onClose, title: t("upsells.title"), footer: footerContent, children: jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", height: "100%", padding: "16px 16px" }, children: [isLoading && (jsxRuntime.jsxs("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: "12px", padding: "40px 20px", ...textStyles.muted }, children: [spinner(), jsxRuntime.jsx("span", { children: t("upsells.loading") })] })), !isLoading && upsells.length === 0 && (jsxRuntime.jsx("div", { style: { textAlign: "center", padding: "40px 20px", ...textStyles.muted }, children: jsxRuntime.jsx("p", { children: t("upsells.noExtras") }) })), !isLoading && upsells.length > 0 && (jsxRuntime.jsx("div", { style: { display: "flex", flexDirection: "column", gap: "12px", flex: 1, overflowY: "auto", paddingBottom: "16px" }, children: upsells.map((upsell) => (jsxRuntime.jsx(UpsellCard, { upsell: upsell, isSelected: isSelected(upsell.id), participantCount: participantCount, onSelect: () => selectUpsell(upsell.id) }, upsell.id))) })), selectedCount > 0 && (jsxRuntime.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: [jsxRuntime.jsx("span", { style: textStyles.muted, children: selectedCount === 1 ? t("upsells.selected", { count: selectedCount }) : t("upsells.selectedPlural", { count: selectedCount }) }), jsxRuntime.jsxs("span", { style: { fontWeight: 600, color: "var(--bw-highlight-color)", fontFamily: "var(--bw-font-family)" }, children: ["+", formatCurrency(selectedTotal)] })] }))] }) }));
15017
15391
  }
15018
15392
 
@@ -15060,6 +15434,8 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15060
15434
  : (config.voucherIntegration ?? (hasEventSelection && !isDirectInstanceMode));
15061
15435
  // Selection flow state
15062
15436
  const [currentStep, setCurrentStep] = React.useState("eventTypes");
15437
+ // Tracks where to return when closing the booking form
15438
+ const bookingReturnStep = React.useRef("eventInstances");
15063
15439
  const [eventTypes, setEventTypes] = React.useState([]);
15064
15440
  const [selectedEventType, setSelectedEventType] = React.useState(null);
15065
15441
  const [eventInstances, setEventInstances] = React.useState([]);
@@ -15072,6 +15448,9 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15072
15448
  // State for upcoming events (next-events view mode)
15073
15449
  const [upcomingEvents, setUpcomingEvents] = React.useState([]);
15074
15450
  const [showingPreview, setShowingPreview] = React.useState(true);
15451
+ // State for specials view mode
15452
+ const [specials, setSpecials] = React.useState([]);
15453
+ const [isLoadingSpecials, setIsLoadingSpecials] = React.useState(false);
15075
15454
  // New: sidebar open state for single event type mode
15076
15455
  const [sidebarOpen, setSidebarOpen] = React.useState(false);
15077
15456
  // Booking flow state
@@ -15222,6 +15601,11 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15222
15601
  await loadUpcomingEvents();
15223
15602
  return;
15224
15603
  }
15604
+ // Specials view mode: load special offers
15605
+ if (viewMode === "specials") {
15606
+ await loadSpecials();
15607
+ return;
15608
+ }
15225
15609
  // Single event type mode: load event type and instances, but don't open sidebar yet
15226
15610
  if (isSingleEventTypeMode) {
15227
15611
  await loadEventTypes();
@@ -15445,6 +15829,45 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15445
15829
  setError(data.error || t("error.loadUpcomingEvents"));
15446
15830
  }
15447
15831
  };
15832
+ const loadSpecials = async () => {
15833
+ setIsLoadingSpecials(true);
15834
+ const specialsSettings = config.specialsSettings ?? {};
15835
+ const requestBody = {
15836
+ organizationId: config.organizationId,
15837
+ limit: specialsSettings.count ?? 20,
15838
+ };
15839
+ if (config.categoryId) {
15840
+ requestBody.categoryId = config.categoryId;
15841
+ }
15842
+ else if (config.eventTypeIds) {
15843
+ requestBody.eventTypeIds = config.eventTypeIds;
15844
+ }
15845
+ else if (config.eventTypeId) {
15846
+ requestBody.eventTypeId = config.eventTypeId;
15847
+ }
15848
+ try {
15849
+ const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/specials"), {
15850
+ method: "POST",
15851
+ headers: createApiHeaders(config, locale),
15852
+ body: JSON.stringify(requestBody),
15853
+ });
15854
+ const data = await response.json();
15855
+ if (response.ok) {
15856
+ const wl = extractWidgetLanguagePayload(data);
15857
+ if (wl) {
15858
+ onWidgetLanguage?.(wl);
15859
+ onTimezone?.(wl.timezone);
15860
+ }
15861
+ setSpecials(data.specials || []);
15862
+ }
15863
+ else {
15864
+ setError(data.error || t("error.loadUpcomingEvents"));
15865
+ }
15866
+ }
15867
+ finally {
15868
+ setIsLoadingSpecials(false);
15869
+ }
15870
+ };
15448
15871
  const loadEventInstances = async (eventTypeId) => {
15449
15872
  const response = await fetch(getApiUrl(config.apiBaseUrl, "/booking/event-instances"), {
15450
15873
  method: "POST",
@@ -15629,6 +16052,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15629
16052
  // Event instance selection handlers
15630
16053
  const handleEventInstanceSelect = async (eventInstance) => {
15631
16054
  setSelectedEventInstance(eventInstance);
16055
+ bookingReturnStep.current = "eventInstances";
15632
16056
  // Set default participant count for upsell calculations
15633
16057
  const defaultParticipantCount = 1;
15634
16058
  setTempParticipantCount(defaultParticipantCount);
@@ -15641,7 +16065,14 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15641
16065
  if (availableUpsells.length > 0) {
15642
16066
  // Show upsells step
15643
16067
  setUpsells(availableUpsells);
15644
- setSelectedUpsells([]);
16068
+ // Pre-select default-checked upsells
16069
+ const defaultSelections = availableUpsells
16070
+ .filter((upsell) => upsell.defaultChecked && upsell.available)
16071
+ .map((upsell) => ({
16072
+ upsellPackageId: upsell.id,
16073
+ quantity: defaultParticipantCount,
16074
+ }));
16075
+ setSelectedUpsells(defaultSelections);
15645
16076
  setCurrentStep("upsells");
15646
16077
  setIsLoadingUpsells(false);
15647
16078
  return; // Don't proceed to booking yet
@@ -15672,7 +16103,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15672
16103
  setEventInstances([]);
15673
16104
  };
15674
16105
  const handleBackToEventInstances = () => {
15675
- setCurrentStep("eventInstances");
16106
+ setCurrentStep(bookingReturnStep.current);
15676
16107
  setSelectedEventInstance(null);
15677
16108
  setEventDetails(null);
15678
16109
  };
@@ -15706,8 +16137,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15706
16137
  }
15707
16138
  };
15708
16139
  const handleUpsellsBack = () => {
15709
- // Go back to event instance selection
15710
- setCurrentStep("eventInstances");
16140
+ setCurrentStep(bookingReturnStep.current);
15711
16141
  setSelectedUpsells([]);
15712
16142
  setUpsells([]);
15713
16143
  };
@@ -15742,10 +16172,36 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15742
16172
  setError(errorMessage);
15743
16173
  config.onError?.(errorMessage);
15744
16174
  };
15745
- const handleUpcomingEventSelect = async (eventInstanceId) => {
16175
+ const handleUpcomingEventSelect = async (eventInstanceId, eventTypeId) => {
16176
+ // Resolve the event type — may come from card preview (eventTypeId provided) or
16177
+ // from the next-events list where selectedEventType is already set
16178
+ const resolvedEventType = eventTypeId != null
16179
+ ? (eventTypes.find((et) => et.id === eventTypeId) ?? selectedEventType)
16180
+ : selectedEventType;
16181
+ if (resolvedEventType && resolvedEventType !== selectedEventType) {
16182
+ setSelectedEventType(resolvedEventType);
16183
+ }
16184
+ // Check if this is coming from a card preview (eventTypeId was provided)
16185
+ // In that case, we need to load event instances so back navigation works properly
16186
+ const isFromCardPreview = eventTypeId != null;
16187
+ if (isFromCardPreview && resolvedEventType) {
16188
+ // Load event instances in background so back navigation shows instances
16189
+ setShouldRenderInstanceSelection(true);
16190
+ void loadEventInstances(resolvedEventType.id);
16191
+ // Record that we should return to instance selection (not event types)
16192
+ bookingReturnStep.current = "eventInstances";
16193
+ }
16194
+ else {
16195
+ // Record where to return when the booking form is closed
16196
+ bookingReturnStep.current = currentStep === "eventInstances" ? "eventInstances" : "eventTypes";
16197
+ }
16198
+ // First try to find the event in upcomingEvents (for next-events view mode)
15746
16199
  const upcomingEvent = upcomingEvents.find((event) => event.id === eventInstanceId);
15747
- if (upcomingEvent) {
15748
- const eventInstance = {
16200
+ // If not found in upcomingEvents, try to find in card preview items
16201
+ const cardPreviewItem = !upcomingEvent && resolvedEventType?.cardPreview?.find((item) => item.id === eventInstanceId);
16202
+ // Build the event instance from either source
16203
+ const eventInstance = upcomingEvent
16204
+ ? {
15749
16205
  id: upcomingEvent.id,
15750
16206
  name: upcomingEvent.name,
15751
16207
  startTime: upcomingEvent.startTime,
@@ -15759,13 +16215,63 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15759
16215
  bookingOpen: upcomingEvent.bookingOpen,
15760
16216
  ...(upcomingEvent.deposit !== undefined && { deposit: upcomingEvent.deposit }),
15761
16217
  ...(upcomingEvent.notes !== undefined && { notes: upcomingEvent.notes }),
15762
- };
16218
+ }
16219
+ : cardPreviewItem
16220
+ ? {
16221
+ id: cardPreviewItem.id,
16222
+ name: cardPreviewItem.name,
16223
+ startTime: cardPreviewItem.startTime,
16224
+ endTime: cardPreviewItem.startTime,
16225
+ price: cardPreviewItem.price,
16226
+ maxParticipants: cardPreviewItem.availableSpots + 1,
16227
+ participantCount: 1,
16228
+ availableSpots: cardPreviewItem.availableSpots,
16229
+ durationDays: 1,
16230
+ durationPerDay: 1,
16231
+ bookingOpen: true,
16232
+ }
16233
+ : null;
16234
+ if (eventInstance) {
15763
16235
  setSelectedEventInstance(eventInstance);
15764
16236
  }
16237
+ setError(null);
16238
+ // Check for upsells before going to booking (same as handleEventInstanceSelect)
16239
+ const eventTypeForUpsells = resolvedEventType;
16240
+ if (eventTypeForUpsells) {
16241
+ const defaultParticipantCount = 1;
16242
+ setTempParticipantCount(defaultParticipantCount);
16243
+ setIsLoadingUpsells(true);
16244
+ setShouldRenderUpsells(true);
16245
+ try {
16246
+ const availableUpsells = await loadUpsells(eventTypeForUpsells.id, eventInstanceId, defaultParticipantCount);
16247
+ if (availableUpsells.length > 0) {
16248
+ setUpsells(availableUpsells);
16249
+ // Pre-select default-checked upsells
16250
+ const defaultSelections = availableUpsells
16251
+ .filter((upsell) => upsell.defaultChecked && upsell.available)
16252
+ .map((upsell) => ({
16253
+ upsellPackageId: upsell.id,
16254
+ quantity: defaultParticipantCount,
16255
+ }));
16256
+ setSelectedUpsells(defaultSelections);
16257
+ setCurrentStep("upsells");
16258
+ setIsLoadingUpsells(false);
16259
+ // Load event details in background for when user continues past upsells
16260
+ void loadEventDetails(eventInstanceId);
16261
+ return;
16262
+ }
16263
+ }
16264
+ catch (err) {
16265
+ console.error("Error loading upsells:", err);
16266
+ }
16267
+ finally {
16268
+ setIsLoadingUpsells(false);
16269
+ }
16270
+ }
16271
+ // No upsells — go directly to booking
15765
16272
  setCurrentStep("booking");
15766
16273
  setShouldRenderBookingForm(true);
15767
16274
  setIsLoadingEventDetails(true);
15768
- setError(null);
15769
16275
  try {
15770
16276
  await loadEventDetails(eventInstanceId);
15771
16277
  }
@@ -15922,6 +16428,31 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15922
16428
  window.history.replaceState({}, "", url.toString());
15923
16429
  }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
15924
16430
  }
16431
+ if (viewMode === "specials" && showingPreview) {
16432
+ return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [jsxRuntime.jsx(SpecialsView, { specials: specials, onEventSelect: handleUpcomingEventSelect, isLoading: isLoadingSpecials, showSavingsAmount: config.specialsSettings?.showSavingsAmount ?? true, showSavingsPercent: config.specialsSettings?.showSavingsPercent ?? false, emptyStateText: config.specialsSettings?.emptyStateText }), shouldRenderBookingForm && eventDetails && (jsxRuntime.jsx(BookingForm, { config: config, eventDetails: eventDetails, stripePromise: stripePromise, onSuccess: handleBookingSuccess, onError: handleBookingError, onBackToEventInstances: () => {
16433
+ setCurrentStep("eventTypes");
16434
+ setShowingPreview(true);
16435
+ setEventDetails(null);
16436
+ }, onBackToEventTypes: () => {
16437
+ setCurrentStep("eventTypes");
16438
+ setShowingPreview(true);
16439
+ setEventDetails(null);
16440
+ }, selectedEventType: selectedEventType, selectedEventInstance: selectedEventInstance, isOpen: currentStep === "booking" && !!eventDetails, onClose: () => {
16441
+ setCurrentStep("eventTypes");
16442
+ setShowingPreview(true);
16443
+ setEventDetails(null);
16444
+ }, systemConfig: systemConfig, selectedUpsells: selectedUpsells, upsells: upsells })), jsxRuntime.jsx(BookingSuccessModal, { isOpen: isSuccess, onClose: () => {
16445
+ setIsSuccess(false);
16446
+ setCurrentStep("eventTypes");
16447
+ setShowingPreview(true);
16448
+ setSuccessPaymentId(null);
16449
+ setShouldRenderInstanceSelection(false);
16450
+ setShouldRenderUpsells(false);
16451
+ setShouldRenderBookingForm(false);
16452
+ setSelectedUpsells([]);
16453
+ setUpsells([]);
16454
+ }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
16455
+ }
15925
16456
  if (viewMode === "next-events" && !showingPreview && currentStep === "eventInstances") {
15926
16457
  return (jsxRuntime.jsxs(StyleProvider, { config: config, children: [jsxRuntime.jsxs("div", { ref: setWidgetContainerRef, children: [shouldRenderInstanceSelection && (jsxRuntime.jsx(EventInstanceSelection, { eventInstances: eventInstances, selectedEventType: selectedEventType, onEventInstanceSelect: handleEventInstanceSelect, onBackToEventTypes: () => {
15927
16458
  setShowingPreview(true);
@@ -15992,7 +16523,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
15992
16523
  }, config: config, onError: setError, paymentIntentId: successPaymentId })] }), showPromoDialog && config.promo && (jsxRuntime.jsx(PromoDialog, { config: config.promo, onClose: handlePromoDialogClose, onCtaClick: handlePromoCtaClick }))] }));
15993
16524
  }
15994
16525
  // Cards mode (default) - show event type selection with optional voucher card
15995
- const cardsView = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasEventSelection && (jsxRuntime.jsx(EventTypeSelection, { eventTypes: eventTypes, onEventTypeSelect: handleEventTypeSelect, isLoading: isLoading, skeletonCount: getSkeletonCount(), showVoucherAttachment: Boolean(voucherConfig?.enabled && voucherCardIntegrationEnabled && !isStandaloneVoucherMode), onVoucherClick: handleVoucherAttachmentClick })), isStandaloneVoucherMode && (jsxRuntime.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 && (jsxRuntime.jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsxRuntime.jsx("div", { style: {
16526
+ const cardsView = (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [hasEventSelection && (jsxRuntime.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 && (jsxRuntime.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 && (jsxRuntime.jsx("div", { style: { padding: "24px", textAlign: "center" }, children: jsxRuntime.jsx("div", { style: {
15996
16527
  display: "inline-block",
15997
16528
  width: "32px",
15998
16529
  height: "32px",
@@ -16060,11 +16591,7 @@ function UniversalBookingWidgetInner({ config: baseConfig, onWidgetLanguage, onT
16060
16591
  function UniversalBookingWidget(props) {
16061
16592
  const [languagePolicy, setLanguagePolicy] = React.useState(null);
16062
16593
  const [orgTimezone, setOrgTimezone] = React.useState("Europe/Berlin");
16063
- const serverLockedLocale = languagePolicy && !languagePolicy.multiLanguageEnabled
16064
- ? languagePolicy.organizationLocale
16065
- : undefined;
16066
- const i18nConfigLocale = serverLockedLocale !== undefined ? serverLockedLocale : props.config.locale;
16067
- const providerProps = i18nConfigLocale ? { configLocale: i18nConfigLocale } : {};
16594
+ const providerProps = props.config.locale ? { configLocale: props.config.locale } : {};
16068
16595
  const showLanguagePicker = props.config.showLanguagePicker !== false &&
16069
16596
  (languagePolicy === null || languagePolicy.multiLanguageEnabled);
16070
16597
  return (jsxRuntime.jsx(I18nProvider, { ...providerProps, children: jsxRuntime.jsx(ShowLanguagePickerProvider, { value: showLanguagePicker, children: jsxRuntime.jsx(TimezoneProvider, { value: orgTimezone, children: jsxRuntime.jsx(UniversalBookingWidgetInner, { ...props, onWidgetLanguage: setLanguagePolicy, onTimezone: setOrgTimezone }) }) }) }));
@@ -16097,7 +16624,7 @@ function styleInject(css, ref) {
16097
16624
  }
16098
16625
  }
16099
16626
 
16100
- 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}}";
16627
+ 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}}";
16101
16628
  styleInject(css_248z);
16102
16629
 
16103
16630
  // Export init function for vanilla JS usage