@seatlayer/core 0.1.3 → 0.2.0

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.
@@ -11,7 +11,10 @@ var de = {
11
11
  "picker.seatsHeld": "Pl\xE4tze reserviert \u2014 {time}",
12
12
  "picker.holdExpired": "Ihre Reservierung ist abgelaufen \u2014 die Pl\xE4tze wurden freigegeben. Bitte erneut ausw\xE4hlen.",
13
13
  "picker.seatTaken": "Platz {label} wurde gerade von einem anderen K\xE4ufer gebucht.",
14
- "picker.poweredBy": "Bereitgestellt von SeatMap",
14
+ "picker.poweredBy": "Bereitgestellt von SeatLayer",
15
+ "picker.testMode": "TESTMODUS",
16
+ "picker.colorblind": "Farbenblind-freundliche Farben",
17
+ "picker.orphanHint": "Dadurch bleibt ein einzelner Platz \xFCbrig \u2014 r\xFCcken Sie ggf. einen Platz weiter.",
15
18
  "picker.seats.one": "{count} Platz",
16
19
  "picker.seats.other": "{count} Pl\xE4tze",
17
20
  // renderer (drawn on the Konva map — shared by the embed SDK)
@@ -67,4 +70,4 @@ var de = {
67
70
  export {
68
71
  de
69
72
  };
70
- //# sourceMappingURL=de-Y3E6YTGP.js.map
73
+ //# sourceMappingURL=de-YMAVF5S2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/i18n/locales/de.ts"],"sourcesContent":["/**\n * German bundle — native-quality translations of the English reference dictionary.\n * Keys are identical to en.ts; values are translated to German (de).\n *\n * Conventions:\n * - flat dot-namespaced keys: \"<surface>.<element>\" (picker.holdSeats)\n * - {name} interpolation, {count} reserved for tCount plural keys\n * - plural keys end in \".one\" / \".other\" (Intl.PluralRules categories)\n */\n\nimport type { Dict } from '../index';\n\nexport const de: Dict = {\n // shared\n 'common.cancel': 'Abbrechen',\n 'common.close': 'Schließen',\n 'common.done': 'Fertig',\n 'common.copied': '✓ Kopiert',\n\n // buyer picker (extraction in progress — P4 of the port plan)\n 'picker.holdSeats': 'Plätze reservieren & Kasse',\n 'picker.completeBooking': 'Buchung abschließen',\n 'picker.seatsHeld': 'Plätze reserviert — {time}',\n 'picker.holdExpired': 'Ihre Reservierung ist abgelaufen — die Plätze wurden freigegeben. Bitte erneut auswählen.',\n 'picker.seatTaken': 'Platz {label} wurde gerade von einem anderen Käufer gebucht.',\n 'picker.poweredBy': 'Bereitgestellt von SeatLayer',\n 'picker.testMode': 'TESTMODUS',\n 'picker.colorblind': 'Farbenblind-freundliche Farben',\n 'picker.orphanHint': 'Dadurch bleibt ein einzelner Platz übrig — rücken Sie ggf. einen Platz weiter.',\n 'picker.seats.one': '{count} Platz',\n 'picker.seats.other': '{count} Plätze',\n\n // renderer (drawn on the Konva map — shared by the embed SDK)\n 'map.aria': 'Sitzplan. Mit Pfeiltasten zwischen Plätzen navigieren, Eingabe zum Auswählen.',\n 'map.seatsLeft': '{count} FREI',\n 'map.fromPrice': 'AB {price}',\n\n // buyer picker page (src/pages/PickerPage.tsx)\n 'picker.language': 'Sprache',\n 'picker.zoomToFit': 'Auf Größe anpassen',\n 'picker.viewMode': 'Ansichtsmodus',\n 'picker.floor': 'Etage',\n 'picker.zoomLevel': 'Zoomstufe',\n 'picker.rungTip.zones': 'Fernste Stufe — Zonen-Blöcke (Etage / Unteres Parterre / Oberes Parterre)',\n 'picker.rungTip.sections': 'Bereichs-Blöcke — Kategoriesortierung + Verfügbarkeitstönung',\n 'picker.rungTip.seats': 'Einzelne Plätze — Blöcke lösen sich zu Punkten auf',\n 'picker.rungLabel.zones': 'ZONEN',\n 'picker.rungLabel.sections': 'BEREICHE',\n 'picker.rungLabel.seats': 'PLÄTZE',\n 'picker.sectionSummaryAria': 'Bereichszusammenfassung {label}',\n 'picker.closeSectionSummary': 'Bereichszusammenfassung schließen',\n 'picker.seatsLeftInSection.one': '{count} Platz verfügbar',\n 'picker.seatsLeftInSection.other': '{count} Plätze verfügbar',\n 'picker.overview': 'Übersicht',\n 'picker.tapSeatHint': 'Tippen Sie auf einen Platz, um die Ansicht zu prüfen',\n 'picker.chartSize': 'Kartengröße',\n 'picker.custom': 'Benutzerdefiniert',\n 'picker.categories': 'Kategorien',\n 'picker.accessibility': 'Barrierefreiheit',\n 'picker.showAnyAccessible': 'Jeden barrierefreien Platz anzeigen',\n 'picker.any': 'Beliebig',\n 'picker.yourSeats': 'Ihre Plätze',\n 'picker.emptySeats': 'Tippen Sie auf die Plätze auf der Karte',\n 'picker.emptySeatsWithGa': 'Tippen Sie auf die Plätze auf der Karte · tippen Sie auf einen Stehbereich für Stehplatztickets',\n 'picker.oneFewer': 'Einen weniger',\n 'picker.oneMore': 'Einen mehr',\n 'picker.remove': '{label} entfernen',\n 'picker.ticketTierFor': 'Ticketklasse für {label}',\n 'picker.total': 'Gesamt: {amount}',\n 'picker.viewFromSeat': 'Ansicht von Platz {label}',\n 'picker.real360': 'REAL 360°',\n 'picker.preview': 'VORSCHAU',\n 'picker.open360': 'Öffnen Sie die 360°-Ansicht',\n 'picker.sightline': '≈ {rows} Reihen von der Bühne · {angle}° seitlich versetzt',\n 'picker.bookedDemo': 'Gebucht! (Demo)',\n 'picker.bookButton.one': '{count} Ticket buchen — {amount}',\n 'picker.bookButton.other': '{count} Tickets buchen — {amount}',\n 'picker.simulateCrowd': 'Menschenmenge simulieren: {state}',\n 'picker.on': 'AN',\n 'picker.off': 'AUS',\n 'picker.panorama360': '360°-Veranstaltungsfotos',\n 'picker.illustrationCaption': 'Illustration · ≈ {m} m von der Bühne',\n};\n"],"mappings":";AAYO,IAAM,KAAW;AAAA;AAAA,EAEtB,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,iBAAiB;AAAA;AAAA,EAGjB,oBAAoB;AAAA,EACpB,0BAA0B;AAAA,EAC1B,oBAAoB;AAAA,EACpB,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,oBAAoB;AAAA,EACpB,sBAAsB;AAAA;AAAA,EAGtB,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,iBAAiB;AAAA;AAAA,EAGjB,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB,mBAAmB;AAAA,EACnB,gBAAgB;AAAA,EAChB,oBAAoB;AAAA,EACpB,wBAAwB;AAAA,EACxB,2BAA2B;AAAA,EAC3B,wBAAwB;AAAA,EACxB,0BAA0B;AAAA,EAC1B,6BAA6B;AAAA,EAC7B,0BAA0B;AAAA,EAC1B,6BAA6B;AAAA,EAC7B,8BAA8B;AAAA,EAC9B,iCAAiC;AAAA,EACjC,mCAAmC;AAAA,EACnC,mBAAmB;AAAA,EACnB,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,iBAAiB;AAAA,EACjB,qBAAqB;AAAA,EACrB,wBAAwB;AAAA,EACxB,4BAA4B;AAAA,EAC5B,cAAc;AAAA,EACd,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,2BAA2B;AAAA,EAC3B,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,wBAAwB;AAAA,EACxB,gBAAgB;AAAA,EAChB,uBAAuB;AAAA,EACvB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,yBAAyB;AAAA,EACzB,2BAA2B;AAAA,EAC3B,wBAAwB;AAAA,EACxB,aAAa;AAAA,EACb,cAAc;AAAA,EACd,sBAAsB;AAAA,EACtB,8BAA8B;AAChC;","names":[]}
@@ -11,7 +11,10 @@ var es = {
11
11
  "picker.seatsHeld": "Asientos retenidos \u2014 {time}",
12
12
  "picker.holdExpired": "Tu retenci\xF3n expir\xF3 \u2014 los asientos fueron liberados. Elige de nuevo.",
13
13
  "picker.seatTaken": "El asiento {label} fue tomado por otro comprador.",
14
- "picker.poweredBy": "Con la tecnolog\xEDa de SeatMap",
14
+ "picker.poweredBy": "Con la tecnolog\xEDa de SeatLayer",
15
+ "picker.testMode": "MODO DE PRUEBA",
16
+ "picker.colorblind": "Colores aptos para daltonismo",
17
+ "picker.orphanHint": "Esto deja un asiento aislado \u2014 considera desplazarte un asiento.",
15
18
  "picker.seats.one": "{count} asiento",
16
19
  "picker.seats.other": "{count} asientos",
17
20
  // renderer (drawn on the Konva map — shared by the embed SDK)
@@ -67,4 +70,4 @@ var es = {
67
70
  export {
68
71
  es
69
72
  };
70
- //# sourceMappingURL=es-UM4ZS357.js.map
73
+ //# sourceMappingURL=es-MLM3IFIY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/i18n/locales/es.ts"],"sourcesContent":["/**\n * Spanish (es) bundle — neutral/international Spanish translation.\n * Follows the same key structure as en.ts; all placeholders and brand\n * names preserved.\n */\n\nimport type { Dict } from '../index';\n\nexport const es: Dict = {\n // shared\n 'common.cancel': 'Cancelar',\n 'common.close': 'Cerrar',\n 'common.done': 'Listo',\n 'common.copied': '✓ Copiado',\n\n // buyer picker (extraction in progress — P4 of the port plan)\n 'picker.holdSeats': 'Retener asientos y comprar',\n 'picker.completeBooking': 'Completar reserva',\n 'picker.seatsHeld': 'Asientos retenidos — {time}',\n 'picker.holdExpired': 'Tu retención expiró — los asientos fueron liberados. Elige de nuevo.',\n 'picker.seatTaken': 'El asiento {label} fue tomado por otro comprador.',\n 'picker.poweredBy': 'Con la tecnología de SeatLayer',\n 'picker.testMode': 'MODO DE PRUEBA',\n 'picker.colorblind': 'Colores aptos para daltonismo',\n 'picker.orphanHint': 'Esto deja un asiento aislado — considera desplazarte un asiento.',\n 'picker.seats.one': '{count} asiento',\n 'picker.seats.other': '{count} asientos',\n\n // renderer (drawn on the Konva map — shared by the embed SDK)\n 'map.aria': 'Mapa de asientos. Usa las flechas para moverte entre asientos, Intro para seleccionar.',\n 'map.seatsLeft': '{count} LIBRES',\n 'map.fromPrice': 'DESDE {price}',\n\n // buyer picker page (src/pages/PickerPage.tsx)\n 'picker.language': 'Idioma',\n 'picker.zoomToFit': 'Ajustar zoom',\n 'picker.viewMode': 'Modo de vista',\n 'picker.floor': 'Piso',\n 'picker.zoomLevel': 'Nivel de zoom',\n 'picker.rungTip.zones': 'Nivel más lejano — bloques de zona (Piso / Patio Bajo / Patio Alto)',\n 'picker.rungTip.sections': 'Bloques de sección — mezcla de categorías + matiz de disponibilidad',\n 'picker.rungTip.seats': 'Asientos individuales — los bloques se convierten en puntos',\n 'picker.rungLabel.zones': 'ZONAS',\n 'picker.rungLabel.sections': 'SECCIONES',\n 'picker.rungLabel.seats': 'ASIENTOS',\n 'picker.sectionSummaryAria': 'Resumen de sección {label}',\n 'picker.closeSectionSummary': 'Cerrar resumen de sección',\n 'picker.seatsLeftInSection.one': '{count} asiento disponible',\n 'picker.seatsLeftInSection.other': '{count} asientos disponibles',\n 'picker.overview': 'Descripción general',\n 'picker.tapSeatHint': 'Toca cualquier asiento para ver su vista',\n 'picker.chartSize': 'Tamaño del gráfico',\n 'picker.custom': 'Personalizado',\n 'picker.categories': 'Categorías',\n 'picker.accessibility': 'Accesibilidad',\n 'picker.showAnyAccessible': 'Mostrar cualquier asiento accesible',\n 'picker.any': 'Cualquiera',\n 'picker.yourSeats': 'Tus asientos',\n 'picker.emptySeats': 'Toca asientos en el mapa',\n 'picker.emptySeatsWithGa': 'Toca asientos en el mapa · toca un área de pie para entradas de entrada general',\n 'picker.oneFewer': 'Uno menos',\n 'picker.oneMore': 'Uno más',\n 'picker.remove': 'Eliminar {label}',\n 'picker.ticketTierFor': 'Categoría de entrada para {label}',\n 'picker.total': 'Total: {amount}',\n 'picker.viewFromSeat': 'Vista desde el asiento {label}',\n 'picker.real360': 'REAL 360°',\n 'picker.preview': 'VISTA PREVIA',\n 'picker.open360': 'Abrir vista de 360°',\n 'picker.sightline': '≈ {rows} filas del escenario · {angle}° fuera del centro',\n 'picker.bookedDemo': '¡Reservado! (demostración)',\n 'picker.bookButton.one': 'Reservar {count} entrada — {amount}',\n 'picker.bookButton.other': 'Reservar {count} entradas — {amount}',\n 'picker.simulateCrowd': 'Simular multitud: {state}',\n 'picker.on': 'ACTIVADO',\n 'picker.off': 'DESACTIVADO',\n 'picker.panorama360': 'Foto de 360° del lugar',\n 'picker.illustrationCaption': 'ilustración · ≈ {m} m del escenario',\n};\n"],"mappings":";AAQO,IAAM,KAAW;AAAA;AAAA,EAEtB,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,iBAAiB;AAAA;AAAA,EAGjB,oBAAoB;AAAA,EACpB,0BAA0B;AAAA,EAC1B,oBAAoB;AAAA,EACpB,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,oBAAoB;AAAA,EACpB,sBAAsB;AAAA;AAAA,EAGtB,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,iBAAiB;AAAA;AAAA,EAGjB,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB,mBAAmB;AAAA,EACnB,gBAAgB;AAAA,EAChB,oBAAoB;AAAA,EACpB,wBAAwB;AAAA,EACxB,2BAA2B;AAAA,EAC3B,wBAAwB;AAAA,EACxB,0BAA0B;AAAA,EAC1B,6BAA6B;AAAA,EAC7B,0BAA0B;AAAA,EAC1B,6BAA6B;AAAA,EAC7B,8BAA8B;AAAA,EAC9B,iCAAiC;AAAA,EACjC,mCAAmC;AAAA,EACnC,mBAAmB;AAAA,EACnB,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,iBAAiB;AAAA,EACjB,qBAAqB;AAAA,EACrB,wBAAwB;AAAA,EACxB,4BAA4B;AAAA,EAC5B,cAAc;AAAA,EACd,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,2BAA2B;AAAA,EAC3B,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,wBAAwB;AAAA,EACxB,gBAAgB;AAAA,EAChB,uBAAuB;AAAA,EACvB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,yBAAyB;AAAA,EACzB,2BAA2B;AAAA,EAC3B,wBAAwB;AAAA,EACxB,aAAa;AAAA,EACb,cAAc;AAAA,EACd,sBAAsB;AAAA,EACtB,8BAA8B;AAChC;","names":[]}
@@ -11,7 +11,10 @@ var fr = {
11
11
  "picker.seatsHeld": "Si\xE8ges r\xE9serv\xE9s \u2014 {time}",
12
12
  "picker.holdExpired": "Votre r\xE9servation a expir\xE9 \u2014 les si\xE8ges ont \xE9t\xE9 lib\xE9r\xE9s. S\xE9lectionnez \xE0 nouveau.",
13
13
  "picker.seatTaken": "Le si\xE8ge {label} vient d'\xEAtre r\xE9serv\xE9 par un autre acheteur.",
14
- "picker.poweredBy": "Propuls\xE9 par SeatMap",
14
+ "picker.poweredBy": "Propuls\xE9 par SeatLayer",
15
+ "picker.testMode": "MODE TEST",
16
+ "picker.colorblind": "Couleurs adapt\xE9es au daltonisme",
17
+ "picker.orphanHint": "Cela laisse un si\xE8ge isol\xE9 \u2014 pensez \xE0 vous d\xE9caler d'un si\xE8ge.",
15
18
  "picker.seats.one": "{count} si\xE8ge",
16
19
  "picker.seats.other": "{count} si\xE8ges",
17
20
  // renderer (drawn on the Konva map — shared by the embed SDK)
@@ -67,4 +70,4 @@ var fr = {
67
70
  export {
68
71
  fr
69
72
  };
70
- //# sourceMappingURL=fr-IGRD2M4W.js.map
73
+ //# sourceMappingURL=fr-N7AAVATM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/i18n/locales/fr.ts"],"sourcesContent":["/**\n * French (fr) bundle — native French translation.\n * Follows the same key structure as en.ts; all placeholders and brand\n * names preserved.\n */\n\nimport type { Dict } from '../index';\n\nexport const fr: Dict = {\n // shared\n 'common.cancel': 'Annuler',\n 'common.close': 'Fermer',\n 'common.done': 'Terminé',\n 'common.copied': '✓ Copié',\n\n // buyer picker (extraction in progress — P4 of the port plan)\n 'picker.holdSeats': 'Réserver les sièges et valider',\n 'picker.completeBooking': 'Terminer la réservation',\n 'picker.seatsHeld': 'Sièges réservés — {time}',\n 'picker.holdExpired': 'Votre réservation a expiré — les sièges ont été libérés. Sélectionnez à nouveau.',\n 'picker.seatTaken': 'Le siège {label} vient d\\'être réservé par un autre acheteur.',\n 'picker.poweredBy': 'Propulsé par SeatLayer',\n 'picker.testMode': 'MODE TEST',\n 'picker.colorblind': 'Couleurs adaptées au daltonisme',\n 'picker.orphanHint': \"Cela laisse un siège isolé — pensez à vous décaler d'un siège.\",\n 'picker.seats.one': '{count} siège',\n 'picker.seats.other': '{count} sièges',\n\n // renderer (drawn on the Konva map — shared by the embed SDK)\n 'map.aria': 'Plan de salle. Utilisez les flèches pour naviguer entre les sièges, Entrée pour sélectionner.',\n 'map.seatsLeft': '{count} LIBRES',\n 'map.fromPrice': 'DÈS {price}',\n\n // buyer picker page (src/pages/PickerPage.tsx)\n 'picker.language': 'Langue',\n 'picker.zoomToFit': 'Ajuster le zoom',\n 'picker.viewMode': 'Mode d\\'affichage',\n 'picker.floor': 'Étage',\n 'picker.zoomLevel': 'Niveau de zoom',\n 'picker.rungTip.zones': 'Vue la plus large — blocs de zones (Étage / Parquet inférieur / Parquet supérieur)',\n 'picker.rungTip.sections': 'Blocs de sections — mélange de catégories + teinte de disponibilité',\n 'picker.rungTip.seats': 'Sièges individuels — les blocs se transforment en points',\n 'picker.rungLabel.zones': 'ZONES',\n 'picker.rungLabel.sections': 'SECTIONS',\n 'picker.rungLabel.seats': 'SIÈGES',\n 'picker.sectionSummaryAria': 'Résumé de la section {label}',\n 'picker.closeSectionSummary': 'Fermer le résumé de la section',\n 'picker.seatsLeftInSection.one': '{count} siège restant',\n 'picker.seatsLeftInSection.other': '{count} sièges restants',\n 'picker.overview': 'Vue d\\'ensemble',\n 'picker.tapSeatHint': 'Appuyez sur un siège pour vérifier sa vue',\n 'picker.chartSize': 'Taille du plan',\n 'picker.custom': 'Personnalisé',\n 'picker.categories': 'Catégories',\n 'picker.accessibility': 'Accessibilité',\n 'picker.showAnyAccessible': 'Afficher un siège accessible',\n 'picker.any': 'N\\'importe quel',\n 'picker.yourSeats': 'Vos sièges',\n 'picker.emptySeats': 'Appuyez sur les sièges sur le plan',\n 'picker.emptySeatsWithGa': 'Appuyez sur les sièges sur le plan · appuyez sur une zone debout pour les billets GA',\n 'picker.oneFewer': 'Un de moins',\n 'picker.oneMore': 'Un de plus',\n 'picker.remove': 'Supprimer {label}',\n 'picker.ticketTierFor': 'Catégorie de billet pour {label}',\n 'picker.total': 'Total : {amount}',\n 'picker.viewFromSeat': 'Vue depuis le siège {label}',\n 'picker.real360': 'VRAI 360°',\n 'picker.preview': 'APERÇU',\n 'picker.open360': 'Ouvrir la vue à 360°',\n 'picker.sightline': '≈ {rows} rangées de la scène · {angle}° décalé du centre',\n 'picker.bookedDemo': 'Réservé ! (démo)',\n 'picker.bookButton.one': 'Réserver {count} billet — {amount}',\n 'picker.bookButton.other': 'Réserver {count} billets — {amount}',\n 'picker.simulateCrowd': 'Simuler la foule : {state}',\n 'picker.on': 'ACTIVÉ',\n 'picker.off': 'DÉSACTIVÉ',\n 'picker.panorama360': 'Photo panoramique 360° du lieu',\n 'picker.illustrationCaption': 'illustration · ≈ {m} m de la scène',\n};\n"],"mappings":";AAQO,IAAM,KAAW;AAAA;AAAA,EAEtB,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,eAAe;AAAA,EACf,iBAAiB;AAAA;AAAA,EAGjB,oBAAoB;AAAA,EACpB,0BAA0B;AAAA,EAC1B,oBAAoB;AAAA,EACpB,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,mBAAmB;AAAA,EACnB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,oBAAoB;AAAA,EACpB,sBAAsB;AAAA;AAAA,EAGtB,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,iBAAiB;AAAA;AAAA,EAGjB,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB,mBAAmB;AAAA,EACnB,gBAAgB;AAAA,EAChB,oBAAoB;AAAA,EACpB,wBAAwB;AAAA,EACxB,2BAA2B;AAAA,EAC3B,wBAAwB;AAAA,EACxB,0BAA0B;AAAA,EAC1B,6BAA6B;AAAA,EAC7B,0BAA0B;AAAA,EAC1B,6BAA6B;AAAA,EAC7B,8BAA8B;AAAA,EAC9B,iCAAiC;AAAA,EACjC,mCAAmC;AAAA,EACnC,mBAAmB;AAAA,EACnB,sBAAsB;AAAA,EACtB,oBAAoB;AAAA,EACpB,iBAAiB;AAAA,EACjB,qBAAqB;AAAA,EACrB,wBAAwB;AAAA,EACxB,4BAA4B;AAAA,EAC5B,cAAc;AAAA,EACd,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,2BAA2B;AAAA,EAC3B,mBAAmB;AAAA,EACnB,kBAAkB;AAAA,EAClB,iBAAiB;AAAA,EACjB,wBAAwB;AAAA,EACxB,gBAAgB;AAAA,EAChB,uBAAuB;AAAA,EACvB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,oBAAoB;AAAA,EACpB,qBAAqB;AAAA,EACrB,yBAAyB;AAAA,EACzB,2BAA2B;AAAA,EAC3B,wBAAwB;AAAA,EACxB,aAAa;AAAA,EACb,cAAc;AAAA,EACd,sBAAsB;AAAA,EACtB,8BAA8B;AAChC;","names":[]}
package/dist/index.cjs CHANGED
@@ -41,7 +41,10 @@ var init_es = __esm({
41
41
  "picker.seatsHeld": "Asientos retenidos \u2014 {time}",
42
42
  "picker.holdExpired": "Tu retenci\xF3n expir\xF3 \u2014 los asientos fueron liberados. Elige de nuevo.",
43
43
  "picker.seatTaken": "El asiento {label} fue tomado por otro comprador.",
44
- "picker.poweredBy": "Con la tecnolog\xEDa de SeatMap",
44
+ "picker.poweredBy": "Con la tecnolog\xEDa de SeatLayer",
45
+ "picker.testMode": "MODO DE PRUEBA",
46
+ "picker.colorblind": "Colores aptos para daltonismo",
47
+ "picker.orphanHint": "Esto deja un asiento aislado \u2014 considera desplazarte un asiento.",
45
48
  "picker.seats.one": "{count} asiento",
46
49
  "picker.seats.other": "{count} asientos",
47
50
  // renderer (drawn on the Konva map — shared by the embed SDK)
@@ -118,7 +121,10 @@ var init_de = __esm({
118
121
  "picker.seatsHeld": "Pl\xE4tze reserviert \u2014 {time}",
119
122
  "picker.holdExpired": "Ihre Reservierung ist abgelaufen \u2014 die Pl\xE4tze wurden freigegeben. Bitte erneut ausw\xE4hlen.",
120
123
  "picker.seatTaken": "Platz {label} wurde gerade von einem anderen K\xE4ufer gebucht.",
121
- "picker.poweredBy": "Bereitgestellt von SeatMap",
124
+ "picker.poweredBy": "Bereitgestellt von SeatLayer",
125
+ "picker.testMode": "TESTMODUS",
126
+ "picker.colorblind": "Farbenblind-freundliche Farben",
127
+ "picker.orphanHint": "Dadurch bleibt ein einzelner Platz \xFCbrig \u2014 r\xFCcken Sie ggf. einen Platz weiter.",
122
128
  "picker.seats.one": "{count} Platz",
123
129
  "picker.seats.other": "{count} Pl\xE4tze",
124
130
  // renderer (drawn on the Konva map — shared by the embed SDK)
@@ -195,7 +201,10 @@ var init_fr = __esm({
195
201
  "picker.seatsHeld": "Si\xE8ges r\xE9serv\xE9s \u2014 {time}",
196
202
  "picker.holdExpired": "Votre r\xE9servation a expir\xE9 \u2014 les si\xE8ges ont \xE9t\xE9 lib\xE9r\xE9s. S\xE9lectionnez \xE0 nouveau.",
197
203
  "picker.seatTaken": "Le si\xE8ge {label} vient d'\xEAtre r\xE9serv\xE9 par un autre acheteur.",
198
- "picker.poweredBy": "Propuls\xE9 par SeatMap",
204
+ "picker.poweredBy": "Propuls\xE9 par SeatLayer",
205
+ "picker.testMode": "MODE TEST",
206
+ "picker.colorblind": "Couleurs adapt\xE9es au daltonisme",
207
+ "picker.orphanHint": "Cela laisse un si\xE8ge isol\xE9 \u2014 pensez \xE0 vous d\xE9caler d'un si\xE8ge.",
199
208
  "picker.seats.one": "{count} si\xE8ge",
200
209
  "picker.seats.other": "{count} si\xE8ges",
201
210
  // renderer (drawn on the Konva map — shared by the embed SDK)
@@ -257,6 +266,8 @@ __export(index_exports, {
257
266
  ACCESSIBILITY_TYPES: () => ACCESSIBILITY_TYPES,
258
267
  CHART_STORAGE_KEY: () => CHART_STORAGE_KEY,
259
268
  DEFAULT_CURRENCY: () => DEFAULT_CURRENCY,
269
+ MAX_EVENT_INVENTORY: () => MAX_EVENT_INVENTORY,
270
+ MAX_GA_CAPACITY: () => MAX_GA_CAPACITY,
260
271
  PickerController: () => PickerController,
261
272
  SUPPORTED_LOCALES: () => SUPPORTED_LOCALES,
262
273
  SeatmapRenderer: () => SeatmapRenderer,
@@ -277,8 +288,12 @@ __export(index_exports, {
277
288
  floorsOf: () => floorsOf,
278
289
  formatDate: () => formatDate,
279
290
  formatMoney: () => formatMoney,
291
+ gaAreasOf: () => gaAreasOf,
292
+ gaUnitLabel: () => gaUnitLabel,
293
+ gaUnitLabels: () => gaUnitLabels,
280
294
  getLocale: () => getLocale,
281
295
  hiddenObjectIds: () => hiddenObjectIds,
296
+ isGaUnitLabel: () => isGaUnitLabel,
282
297
  isSectionHidden: () => isSectionHidden,
283
298
  layerOf: () => layerOf,
284
299
  loadLocale: () => loadLocale,
@@ -660,16 +675,35 @@ function chartBounds(doc) {
660
675
  };
661
676
  }
662
677
 
678
+ // src/core/ga.ts
679
+ var PREFIX = "__sl_ga__";
680
+ var MAX_GA_CAPACITY = 1e5;
681
+ var MAX_EVENT_INVENTORY = 1e5;
682
+ function gaUnitLabel(areaId, index) {
683
+ return `${PREFIX}${encodeURIComponent(areaId)}__${index + 1}`;
684
+ }
685
+ function gaUnitLabels(area) {
686
+ const capacity = Math.min(MAX_GA_CAPACITY, Math.max(0, Math.floor(area.capacity)));
687
+ return Array.from({ length: capacity }, (_, i) => gaUnitLabel(area.id, i));
688
+ }
689
+ function gaAreasOf(doc) {
690
+ return allObjects(doc).filter((o) => o.type === "gaArea");
691
+ }
692
+ function isGaUnitLabel(label) {
693
+ return label.startsWith(PREFIX);
694
+ }
695
+
663
696
  // src/core/sections.ts
664
697
  var UNGROUPED_ID = "__ungrouped__";
665
698
  function objectSeatLabels(o) {
666
699
  if (o.type === "row") return expandRow(o).map((s) => s.label);
667
700
  if (o.type === "table") return expandTable(o).map((s) => s.label);
668
701
  if (o.type === "booth") return expandBooth(o).map((s) => s.label);
702
+ if (o.type === "gaArea") return gaUnitLabels(o);
669
703
  return [];
670
704
  }
671
705
  function isSeatObject(o) {
672
- return o.type === "row" || o.type === "table" || o.type === "booth";
706
+ return o.type === "row" || o.type === "table" || o.type === "booth" || o.type === "gaArea";
673
707
  }
674
708
  function computeSections(doc) {
675
709
  const objs = allObjects(doc);
@@ -796,7 +830,10 @@ var en = {
796
830
  "picker.seatsHeld": "Seats held \u2014 {time}",
797
831
  "picker.holdExpired": "Your hold expired \u2014 the seats were released. Pick again.",
798
832
  "picker.seatTaken": "Seat {label} was just taken by another buyer.",
799
- "picker.poweredBy": "Powered by SeatMap",
833
+ "picker.poweredBy": "Powered by SeatLayer",
834
+ "picker.testMode": "TEST MODE",
835
+ "picker.colorblind": "Colorblind-friendly colors",
836
+ "picker.orphanHint": "This leaves a single seat stranded \u2014 consider shifting one seat over.",
800
837
  "picker.seats.one": "{count} seat",
801
838
  "picker.seats.other": "{count} seats",
802
839
  // renderer (drawn on the Konva map — shared by the embed SDK)
@@ -913,6 +950,7 @@ var ZONE_SUB_PX = 12;
913
950
  var HELD_FILL = "#6b7280";
914
951
  var TAKEN_FILL = "#374151";
915
952
  var NFS_STROKE = "#4b5563";
953
+ var CB_PALETTE = ["#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7"];
916
954
  var ACCESS_RING = {
917
955
  wheelchair: "#3b82f6",
918
956
  companion: "#8b5cf6",
@@ -1015,6 +1053,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
1015
1053
  this.statusById = /* @__PURE__ */ new Map();
1016
1054
  this.catColor = /* @__PURE__ */ new Map();
1017
1055
  this.theme = {};
1056
+ /** Colorblind-safe mode (Okabe-Ito hues + hollow booked seats). */
1057
+ this.colorblind = false;
1058
+ /** Category order from the doc — the stable index into the CB palette. */
1059
+ this.catOrder = [];
1018
1060
  this.selection = /* @__PURE__ */ new Set();
1019
1061
  this.selectionRings = /* @__PURE__ */ new Map();
1020
1062
  this.focusedId = null;
@@ -1265,6 +1307,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
1265
1307
  this.hoverRing.radius(this.seatR + 2);
1266
1308
  this.catColor.clear();
1267
1309
  this.catPrice.clear();
1310
+ this.catOrder = doc.categories.map((c) => c.key);
1268
1311
  for (const c of doc.categories) {
1269
1312
  this.catColor.set(c.key, c.color);
1270
1313
  if (typeof c.price === "number") this.catPrice.set(c.key, c.price);
@@ -1788,12 +1831,18 @@ var _SeatmapRenderer = class _SeatmapRenderer {
1788
1831
  this.hasBoothText = true;
1789
1832
  target.add(t2);
1790
1833
  }
1834
+ /** The category's display color — Okabe-Ito hue when colorblind-safe is on. */
1835
+ seatBaseColor(categoryKey) {
1836
+ if (!this.colorblind) return this.catColor.get(categoryKey) ?? "#6e7bff";
1837
+ const idx = this.catOrder.indexOf(categoryKey);
1838
+ return CB_PALETTE[(idx >= 0 ? idx : 0) % CB_PALETTE.length];
1839
+ }
1791
1840
  /** Apply fill/stroke/opacity for a seat's current status + selection. */
1792
1841
  paintSeat(c, id) {
1793
1842
  const seat = this.seatById.get(id);
1794
1843
  const status = this.statusById.get(id) ?? "free";
1795
1844
  const selected = this.selection.has(id);
1796
- const base = this.catColor.get(seat.categoryKey) ?? "#6e7bff";
1845
+ const base = this.seatBaseColor(seat.categoryKey);
1797
1846
  c.dash([]);
1798
1847
  c.strokeWidth(0);
1799
1848
  c.stroke("");
@@ -1806,8 +1855,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
1806
1855
  c.fill(HELD_FILL);
1807
1856
  break;
1808
1857
  case "booked":
1809
- c.fill(TAKEN_FILL);
1810
- c.opacity(0.45);
1858
+ if (this.colorblind) {
1859
+ c.fill("rgba(0,0,0,0)");
1860
+ c.stroke(TAKEN_FILL);
1861
+ c.strokeWidth(1.5);
1862
+ c.opacity(0.9);
1863
+ } else {
1864
+ c.fill(TAKEN_FILL);
1865
+ c.opacity(0.45);
1866
+ }
1811
1867
  break;
1812
1868
  case "not_for_sale":
1813
1869
  c.fill(TAKEN_FILL);
@@ -1829,6 +1885,24 @@ var _SeatmapRenderer = class _SeatmapRenderer {
1829
1885
  }
1830
1886
  }
1831
1887
  }
1888
+ /**
1889
+ * Colorblind-safe mode: swap category hues for the Okabe-Ito palette and
1890
+ * render booked seats hollow. Off restores the exact default rendering.
1891
+ */
1892
+ setColorblindSafe(on) {
1893
+ if (on === this.colorblind) return;
1894
+ this.colorblind = on;
1895
+ for (const seat of this.seats) {
1896
+ const c = this.circleById.get(seat.id);
1897
+ if (c) this.paintSeat(c, seat.id);
1898
+ }
1899
+ if (this.cached) {
1900
+ this.seatLayer.clearCache();
1901
+ this.cacheSeatLayer();
1902
+ } else {
1903
+ this.seatLayer.batchDraw();
1904
+ }
1905
+ }
1832
1906
  /**
1833
1907
  * Dim the seats of these section/zone ids (organizer manager use) so held-back
1834
1908
  * inventory is visually distinct on the canvas without hiding it. `null`/empty
@@ -2767,6 +2841,38 @@ function createRenderer(container, opts) {
2767
2841
  return new SeatmapRenderer(container, opts);
2768
2842
  }
2769
2843
 
2844
+ // src/core/orphans.ts
2845
+ function seatIndex(id) {
2846
+ const n = Number(id.slice(id.lastIndexOf(":") + 1));
2847
+ return Number.isFinite(n) ? n : -1;
2848
+ }
2849
+ function strandedSingles(seats, statusOf, selectedIds) {
2850
+ const rows = /* @__PURE__ */ new Map();
2851
+ for (const s of seats) {
2852
+ if (s.kind === "booth") continue;
2853
+ const list = rows.get(s.rowId);
2854
+ if (list) list.push(s);
2855
+ else rows.set(s.rowId, [s]);
2856
+ }
2857
+ const unavailable = (s) => selectedIds.has(s.id) || statusOf(s.id) !== "free";
2858
+ const out = [];
2859
+ for (const list of rows.values()) {
2860
+ if (list.length < 3) continue;
2861
+ list.sort((a, b) => seatIndex(a.id) - seatIndex(b.id));
2862
+ for (let i = 1; i < list.length - 1; i++) {
2863
+ const seat = list[i];
2864
+ if (unavailable(seat)) continue;
2865
+ const left = list[i - 1];
2866
+ const right = list[i + 1];
2867
+ if (seatIndex(seat.id) - seatIndex(left.id) !== 1 || seatIndex(right.id) - seatIndex(seat.id) !== 1) continue;
2868
+ if (!unavailable(left) || !unavailable(right)) continue;
2869
+ if (!selectedIds.has(left.id) && !selectedIds.has(right.id)) continue;
2870
+ out.push(seat);
2871
+ }
2872
+ }
2873
+ return out;
2874
+ }
2875
+
2770
2876
  // src/picker/PickerController.ts
2771
2877
  var DEFAULT_MAX_SELECTION = 10;
2772
2878
  var MAX_BACKOFF_MS = 15e3;
@@ -2800,7 +2906,11 @@ var PickerController = class {
2800
2906
  this.closed = false;
2801
2907
  // hold state
2802
2908
  this.hold_ = null;
2909
+ this.liveStatuses = /* @__PURE__ */ new Map();
2910
+ this.currency = "USD";
2803
2911
  this.expiryTimer = null;
2912
+ /** Whether the last emitted hint was non-null (avoids clearing repeatedly). */
2913
+ this.hintShown = false;
2804
2914
  this.opts = options;
2805
2915
  this.api = options.transport;
2806
2916
  this.key = options.eventKey;
@@ -2857,6 +2967,7 @@ var PickerController = class {
2857
2967
  this.allIds.push(s.id);
2858
2968
  }
2859
2969
  const currency = res.event.currency ?? this.opts.currency;
2970
+ this.currency = currency ?? "USD";
2860
2971
  const renderer = createRenderer(host, {
2861
2972
  maxSelection: this.maxSelection,
2862
2973
  confirmSelection: this.opts.confirmSelection,
@@ -2896,6 +3007,7 @@ var PickerController = class {
2896
3007
  return null;
2897
3008
  }
2898
3009
  renderer.setChart(this.visibleDoc());
3010
+ if (this.opts.colorblindSafe) renderer.setColorblindSafe?.(true);
2899
3011
  if (seats) this.applySeatsMap(seats);
2900
3012
  this.connect();
2901
3013
  return {
@@ -2904,7 +3016,9 @@ var PickerController = class {
2904
3016
  eventName: res.event.name,
2905
3017
  venue: res.event.venue,
2906
3018
  startsAt: res.event.startsAt,
2907
- currency
3019
+ currency,
3020
+ // 'test' ⇒ sandbox event: hosts render a TEST MODE ribbon (Phase 11).
3021
+ mode: res.event.mode ?? "live"
2908
3022
  };
2909
3023
  }
2910
3024
  // ---- selection ------------------------------------------------------------
@@ -2927,21 +3041,72 @@ var PickerController = class {
2927
3041
  * re-throws so the caller can choose the banner copy. Returns null only when
2928
3042
  * there's nothing to hold.
2929
3043
  */
2930
- async hold(labelsArg) {
3044
+ async hold(labelsArg, ttlMs) {
2931
3045
  const r = this.renderer;
2932
3046
  if (!r) return null;
2933
3047
  const labels = labelsArg ?? r.getSelection().map((s) => s.label);
2934
- if (!labels.length) return null;
2935
- if (this.hold_ && this.holdCovers(labels)) return this.hold_;
3048
+ const existingGA = (this.hold_?.items ?? []).filter((item) => item.objectType === "ga");
3049
+ const combinedLabels = [.../* @__PURE__ */ new Set([...existingGA.map((item) => item.label), ...labels])];
3050
+ if (!combinedLabels.length) return null;
3051
+ if (this.hold_ && this.holdCovers(combinedLabels)) return this.hold_;
2936
3052
  try {
2937
- const result = await this.api.hold(this.key, labels);
2938
- this.setHold({ holdId: result.holdId, labels: [...labels], expiresAt: result.expiresAt });
3053
+ const selections = combinedLabels.map((label) => {
3054
+ const ga = existingGA.find((item) => item.label === label);
3055
+ if (ga) return { label, tierId: ga.tierId };
3056
+ const seat = this.labelToSeat.get(label);
3057
+ const resolved = seat ? this.toSeat(seat) : null;
3058
+ return { label, ...resolved?.tierId ? { tierId: resolved.tierId } : {} };
3059
+ });
3060
+ const result = await this.api.hold(this.key, selections, ttlMs, this.hold_?.holdId);
3061
+ this.setHold({ holdId: result.holdId, labels: combinedLabels, expiresAt: result.expiresAt, items: result.items });
2939
3062
  return this.hold_;
2940
3063
  } catch (err) {
2941
3064
  this.handle409Conflicts(err);
2942
3065
  throw err;
2943
3066
  }
2944
3067
  }
3068
+ /** Public GA inventory derived from the live synthetic-unit status stream. */
3069
+ getGAAreas() {
3070
+ const doc = this.visibleDoc();
3071
+ if (!doc) return [];
3072
+ return gaAreasOf(doc).map((area) => {
3073
+ const category = doc.categories.find((candidate) => candidate.key === area.categoryKey);
3074
+ return {
3075
+ id: area.id,
3076
+ label: area.label,
3077
+ capacity: Math.max(0, Math.floor(area.capacity)),
3078
+ available: gaUnitLabels(area).filter((label) => (this.liveStatuses.get(label) ?? "free") === "free").length,
3079
+ categoryKey: area.categoryKey,
3080
+ tiers: category?.tiers,
3081
+ price: category?.tiers?.[0]?.price ?? category?.price ?? 0,
3082
+ currency: this.currency
3083
+ };
3084
+ });
3085
+ }
3086
+ /** Select a quantity from one GA area and hold it atomically. */
3087
+ async holdGA(areaId, qty, options = {}) {
3088
+ const doc = this.visibleDoc();
3089
+ if (!doc || !Number.isFinite(qty) || qty < 1) return null;
3090
+ const area = gaAreasOf(doc).find((candidate) => candidate.id === areaId);
3091
+ if (!area) return null;
3092
+ const labels = gaUnitLabels(area).filter((label) => !this.hold_?.labels.includes(label) && (this.liveStatuses.get(label) ?? "free") === "free").slice(0, Math.floor(qty));
3093
+ if (labels.length !== Math.floor(qty)) return null;
3094
+ const selections = /* @__PURE__ */ new Map();
3095
+ for (const item of this.hold_?.items ?? []) selections.set(item.label, { label: item.label, tierId: item.tierId });
3096
+ if (this.hold_ && !this.hold_?.items?.length) {
3097
+ for (const seat of this.hold_.seats) selections.set(seat.label, { label: seat.label, ...seat.tierId ? { tierId: seat.tierId } : {} });
3098
+ }
3099
+ for (const seat of this.renderer?.getSelection() ?? []) {
3100
+ const resolved = this.labelToSeat.get(seat.label);
3101
+ const chosen = resolved ? this.toSeat(resolved) : null;
3102
+ selections.set(seat.label, { label: seat.label, ...chosen?.tierId ? { tierId: chosen.tierId } : {} });
3103
+ }
3104
+ for (const label of labels) selections.set(label, { label, ...options.tierId ? { tierId: options.tierId } : {} });
3105
+ const combined = [...selections.values()];
3106
+ const result = await this.api.hold(this.key, combined, options.ttlMs, this.hold_?.holdId);
3107
+ this.setHold({ holdId: result.holdId, labels: combined.map((s) => s.label), expiresAt: result.expiresAt, items: result.items });
3108
+ return this.hold_;
3109
+ }
2945
3110
  /** Server-picks `qty` best free seats and holds them atomically. Throws on failure. */
2946
3111
  async bestAvailable(qty, categoryKey) {
2947
3112
  const r = this.renderer;
@@ -2952,7 +3117,7 @@ var PickerController = class {
2952
3117
  r.clearSelection();
2953
3118
  const ids = result.labels.map((l) => this.labelToId.get(l)).filter((v) => !!v);
2954
3119
  if (ids.length) r.setStatus(ids, "held");
2955
- this.setHold({ holdId: result.holdId, labels: [...result.labels], expiresAt: result.expiresAt });
3120
+ this.setHold({ holdId: result.holdId, labels: [...result.labels], expiresAt: result.expiresAt, items: result.items });
2956
3121
  const seats = result.labels.map((l) => this.labelToSeat.get(l)).filter((s) => !!s).map((s) => this.toSeat(s));
2957
3122
  this.opts.onSelectionChange?.(seats);
2958
3123
  return this.hold_;
@@ -2981,9 +3146,19 @@ var PickerController = class {
2981
3146
  if (this.hold_ && this.holdCovers(labels)) {
2982
3147
  holdId = this.hold_.holdId;
2983
3148
  } else {
2984
- const h = await this.api.hold(this.key, labels);
3149
+ const replaceHoldId = this.hold_?.holdId;
3150
+ const h = await this.api.hold(
3151
+ this.key,
3152
+ labels.map((label) => {
3153
+ const seat = this.labelToSeat.get(label);
3154
+ const resolved = seat ? this.toSeat(seat) : null;
3155
+ return { label, ...resolved?.tierId ? { tierId: resolved.tierId } : {} };
3156
+ }),
3157
+ void 0,
3158
+ replaceHoldId
3159
+ );
2985
3160
  holdId = h.holdId;
2986
- this.setHold({ holdId, labels: [...labels], expiresAt: h.expiresAt });
3161
+ this.setHold({ holdId, labels: [...labels], expiresAt: h.expiresAt, items: h.items });
2987
3162
  }
2988
3163
  await this.api.book(this.key, labels, holdId, bookingRef);
2989
3164
  } catch (err) {
@@ -3267,6 +3442,31 @@ var PickerController = class {
3267
3442
  }
3268
3443
  emitSelectionChange() {
3269
3444
  this.opts.onSelectionChange?.(this.getSelection());
3445
+ this.emitOrphanHint();
3446
+ }
3447
+ /**
3448
+ * Orphan-seat advice on manual selection: when the buyer's current picks
3449
+ * strand one (or more) single free seats between unavailable same-row
3450
+ * neighbors, surface a localized, non-blocking hint. Cleared (null) as soon
3451
+ * as the selection stops stranding anyone.
3452
+ */
3453
+ emitOrphanHint() {
3454
+ if (!this.opts.onHint) return;
3455
+ const r = this.renderer;
3456
+ if (!r) return;
3457
+ const selected = new Set(r.getSelection().map((s) => s.id));
3458
+ const stranded = selected.size ? strandedSingles(this.seatById.values(), (id) => r.getStatus(id), selected) : [];
3459
+ if (stranded.length > 0) {
3460
+ this.hintShown = true;
3461
+ this.opts.onHint(t("picker.orphanHint"));
3462
+ } else if (this.hintShown) {
3463
+ this.hintShown = false;
3464
+ this.opts.onHint(null);
3465
+ }
3466
+ }
3467
+ /** Toggle colorblind-safe rendering at runtime (see PickerOptions.colorblindSafe). */
3468
+ setColorblindSafe(on) {
3469
+ this.renderer?.setColorblindSafe?.(on);
3270
3470
  }
3271
3471
  emitError(err) {
3272
3472
  if (this.opts.onError) this.opts.onError(err);
@@ -3274,7 +3474,13 @@ var PickerController = class {
3274
3474
  }
3275
3475
  holdCovers(labels) {
3276
3476
  const h = this.hold_;
3277
- return !!h && h.labels.length === labels.length && labels.every((l) => h.labels.includes(l));
3477
+ if (!h || h.labels.length !== labels.length || !labels.every((l) => h.labels.includes(l))) return false;
3478
+ const tiers = new Map((h.items ?? []).map((item) => [item.label, item.tierId]));
3479
+ return labels.every((label) => {
3480
+ const seat = this.labelToSeat.get(label);
3481
+ const currentTier = seat ? this.toSeat(seat).tierId ?? null : null;
3482
+ return !tiers.has(label) || tiers.get(label) === currentTier;
3483
+ });
3278
3484
  }
3279
3485
  handle409Conflicts(err) {
3280
3486
  const { status, conflicts } = errInfo(err);
@@ -3294,7 +3500,7 @@ var PickerController = class {
3294
3500
  this.hold_ = full;
3295
3501
  if (this.expiryTimer) clearTimeout(this.expiryTimer);
3296
3502
  const ms = Math.max(0, full.expiresAt - Date.now());
3297
- this.expiryTimer = setTimeout(() => this.onHoldExpired(), ms);
3503
+ this.expiryTimer = setTimeout(() => void this.expireActiveHold(), ms);
3298
3504
  this.opts.onHold?.(full);
3299
3505
  }
3300
3506
  clearHold() {
@@ -3304,18 +3510,31 @@ var PickerController = class {
3304
3510
  this.expiryTimer = null;
3305
3511
  }
3306
3512
  }
3307
- onHoldExpired() {
3513
+ async expireActiveHold() {
3308
3514
  const hold = this.hold_;
3309
- this.clearHold();
3310
- if (hold) {
3311
- const ids = hold.labels.map((l) => this.labelToId.get(l)).filter((v) => !!v);
3312
- if (ids.length) this.renderer?.setStatus(ids, "free");
3515
+ if (!hold) return;
3516
+ try {
3517
+ const snap = await this.api.objects(this.key);
3518
+ if (this.hold_?.holdId !== hold.holdId) return;
3519
+ this.applySeatsMap(snap.seats);
3520
+ } catch {
3521
+ if (this.hold_?.holdId === hold.holdId) {
3522
+ this.expiryTimer = setTimeout(() => void this.expireActiveHold(), 2e3);
3523
+ }
3524
+ return;
3313
3525
  }
3526
+ if (this.hold_?.holdId !== hold.holdId) return;
3527
+ if (hold.labels.some((label) => this.liveStatuses.get(label) === "held")) {
3528
+ this.expiryTimer = setTimeout(() => void this.expireActiveHold(), 1e3);
3529
+ return;
3530
+ }
3531
+ this.clearHold();
3314
3532
  this.opts.onHoldExpired?.();
3315
3533
  }
3316
3534
  applySeatsMap(seats) {
3317
3535
  const r = this.renderer;
3318
3536
  if (!r) return;
3537
+ this.liveStatuses = new Map(Object.entries(seats));
3319
3538
  if (this.allIds.length) r.setStatus(this.allIds, "free");
3320
3539
  const byStatus = { free: [], held: [], booked: [], not_for_sale: [] };
3321
3540
  for (const [label, st] of Object.entries(seats)) {
@@ -3326,6 +3545,10 @@ var PickerController = class {
3326
3545
  if (byStatus[st].length) r.setStatus(byStatus[st], st);
3327
3546
  });
3328
3547
  this.opts.onStatusChange?.();
3548
+ this.clearBookedHoldIfSettled();
3549
+ }
3550
+ clearBookedHoldIfSettled() {
3551
+ if (this.hold_?.labels.every((label) => this.liveStatuses.get(label) === "booked")) this.clearHold();
3329
3552
  }
3330
3553
  async resnapshot() {
3331
3554
  try {
@@ -3368,6 +3591,7 @@ var PickerController = class {
3368
3591
  this.applySeatsMap(m.seats);
3369
3592
  } else if (Array.isArray(m.changes)) {
3370
3593
  for (const ch of m.changes) {
3594
+ this.liveStatuses.set(ch.label, ch.status);
3371
3595
  const id = this.labelToId.get(ch.label);
3372
3596
  if (!id) continue;
3373
3597
  const next = mapStatus(ch.status);
@@ -3376,6 +3600,7 @@ var PickerController = class {
3376
3600
  }
3377
3601
  r.setStatus([id], next);
3378
3602
  }
3603
+ this.clearBookedHoldIfSettled();
3379
3604
  this.opts.onStatusChange?.();
3380
3605
  }
3381
3606
  };
@@ -3429,6 +3654,8 @@ async function loadLocale(code) {
3429
3654
  ACCESSIBILITY_TYPES,
3430
3655
  CHART_STORAGE_KEY,
3431
3656
  DEFAULT_CURRENCY,
3657
+ MAX_EVENT_INVENTORY,
3658
+ MAX_GA_CAPACITY,
3432
3659
  PickerController,
3433
3660
  SUPPORTED_LOCALES,
3434
3661
  SeatmapRenderer,
@@ -3449,8 +3676,12 @@ async function loadLocale(code) {
3449
3676
  floorsOf,
3450
3677
  formatDate,
3451
3678
  formatMoney,
3679
+ gaAreasOf,
3680
+ gaUnitLabel,
3681
+ gaUnitLabels,
3452
3682
  getLocale,
3453
3683
  hiddenObjectIds,
3684
+ isGaUnitLabel,
3454
3685
  isSectionHidden,
3455
3686
  layerOf,
3456
3687
  loadLocale,