@seatlayer/js 0.48.1 → 0.49.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.
package/dist/index.js CHANGED
@@ -1,38 +1,3 @@
1
- import {
2
- SeatManager
3
- } from "./chunk-Q5QT3YLA.js";
4
- import {
5
- ACCESS_LINK_DEFAULTS,
6
- ChannelsMode,
7
- PUBLIC_CHANNEL_ID,
8
- PUBLIC_CHANNEL_NAME,
9
- accessIntentDescription,
10
- accessIntentLabel,
11
- accessLine,
12
- accessLinkBadge,
13
- accessLinkErrorCopy,
14
- accessLinkIsLive,
15
- accessLinkPolicyLines,
16
- bucketRows,
17
- bucketRowsHtml,
18
- dropReviewRows,
19
- intentForbidsCopy,
20
- intentSwitchBlockedCopy,
21
- isPublicChannelId,
22
- markerLetter,
23
- markerOf,
24
- mutationCount,
25
- needsMoveConfirmation,
26
- planAssignment,
27
- retryAfterCopy,
28
- selectionSources,
29
- stateBadge,
30
- suggestMarker
31
- } from "./chunk-KNMZQZXR.js";
32
- import {
33
- ManageApi,
34
- ManageApiError
35
- } from "./chunk-H4OJF6LE.js";
36
1
  import {
37
2
  __privateAdd,
38
3
  __privateGet,
@@ -1448,6 +1413,12 @@ var EmbeddedDesigner = class {
1448
1413
  */
1449
1414
  this.autoRecoverUsed = false;
1450
1415
  this.phase = "loading";
1416
+ /**
1417
+ * Set only after an identity-checked `ready`. Unlike `phase`, this remains true
1418
+ * if a later fatal error renders the error card, so no subsequent callback can
1419
+ * shed the chart/workspace identity the live session already established.
1420
+ */
1421
+ this.identityEstablished = false;
1451
1422
  this.restoreContainerPosition = null;
1452
1423
  // Host-side fullscreen pin: saved state we restore on `off`/Escape/destroy.
1453
1424
  this.pinned = false;
@@ -1514,14 +1485,21 @@ var EmbeddedDesigner = class {
1514
1485
  expiresAt: typeof data.expiresAt === "number" ? data.expiresAt : void 0,
1515
1486
  code: typeof data.code === "string" ? data.code : void 0,
1516
1487
  message: typeof data.message === "string" ? data.message : void 0,
1517
- meta: data.meta
1488
+ meta: data.meta,
1489
+ fatal: typeof data.fatal === "boolean" ? data.fatal : void 0,
1490
+ action: typeof data.action === "string" ? data.action : void 0
1518
1491
  };
1519
- if (this.options.expectedChartId && message.chartId && message.chartId !== this.options.expectedChartId || this.options.expectedWorkspaceId && message.workspaceId && message.workspaceId !== this.options.expectedWorkspaceId) {
1492
+ const identityRequired = this.identityEstablished || message.type === "seatlayer.designer.ready" || message.type === "seatlayer.designer.saved" || message.type === "seatlayer.designer.published" || message.type === "seatlayer.designer.close";
1493
+ const chartMismatch = this.options.expectedChartId !== void 0 && message.chartId !== this.options.expectedChartId && (identityRequired || message.chartId !== void 0);
1494
+ const workspaceMismatch = this.options.expectedWorkspaceId !== void 0 && message.workspaceId !== this.options.expectedWorkspaceId && (identityRequired || message.workspaceId !== void 0);
1495
+ if (chartMismatch || workspaceMismatch) {
1520
1496
  this.showError("mismatch");
1521
1497
  return;
1522
1498
  }
1523
1499
  switch (message.type) {
1524
1500
  case "seatlayer.designer.ready":
1501
+ this.identityEstablished = true;
1502
+ this.sessionExpiresAt = message.expiresAt;
1525
1503
  this.phase = "ready";
1526
1504
  this.clearTimeoutTimer();
1527
1505
  this.removeOverlay();
@@ -1603,6 +1581,25 @@ var EmbeddedDesigner = class {
1603
1581
  getIframe() {
1604
1582
  return this.frame;
1605
1583
  }
1584
+ /** Update iframe sizing without replacing the live Designer session. */
1585
+ setSizing(height, minHeight) {
1586
+ this.options = { ...this.options, height, minHeight };
1587
+ if (!this.frame) return;
1588
+ this.stopFill();
1589
+ this.lastAutoHeight = "";
1590
+ if (this.fillEnabled()) {
1591
+ if (!this.pinned) this.setFrameHeight("100%");
1592
+ this.startFill();
1593
+ } else if (!this.pinned) {
1594
+ this.setFrameHeight(`${height}px`);
1595
+ }
1596
+ }
1597
+ /** Update renewal/expiry-recovery policy without replacing the iframe. */
1598
+ setRelaunchPolicy(onRequestRelaunch, autoRenewSession) {
1599
+ this.options = { ...this.options, onRequestRelaunch, autoRenewSession };
1600
+ this.clearRenewTimer();
1601
+ if (this.phase === "ready") this.scheduleRenewal(this.sessionExpiresAt);
1602
+ }
1606
1603
  destroy() {
1607
1604
  window.removeEventListener("message", this.handleMessage);
1608
1605
  this.stopFill();
@@ -1617,6 +1614,8 @@ var EmbeddedDesigner = class {
1617
1614
  this.fillMode = null;
1618
1615
  this.designerOrigin = "";
1619
1616
  this.phase = "loading";
1617
+ this.identityEstablished = false;
1618
+ this.sessionExpiresAt = void 0;
1620
1619
  this.lastAutoHeight = "";
1621
1620
  }
1622
1621
  loadingStateEnabled() {
@@ -1767,6 +1766,7 @@ var EmbeddedDesigner = class {
1767
1766
  else this.frame.setAttribute("style", this.frameStyleBeforeFs);
1768
1767
  if (this.fillEnabled()) this.applyFill();
1769
1768
  else if (this.autoResizeEnabled() && this.lastAutoHeight) this.setFrameHeight(this.lastAutoHeight);
1769
+ else if (typeof this.options.height === "number") this.setFrameHeight(`${this.options.height}px`);
1770
1770
  }
1771
1771
  this.frameStyleBeforeFs = null;
1772
1772
  if (this.docOverflowBeforeFs !== null) {
@@ -2031,6 +2031,7 @@ import {
2031
2031
  tCount
2032
2032
  } from "@seatlayer/core";
2033
2033
  import { isAuthoredSeatView, seatViewDisclosure } from "@seatlayer/core/view3d/crossfade/panorama";
2034
+ import { seatConfidenceDisclosure } from "@seatlayer/core/core/seatConfidence";
2034
2035
  import {
2035
2036
  browserPanoramaConstraints,
2036
2037
  loadPanoramaImage,
@@ -2328,7 +2329,7 @@ async function loadHostedCheckout() {
2328
2329
  cdnChunkUrl("seatlayer-checkout.mjs")
2329
2330
  );
2330
2331
  }
2331
- return import("./hostedCheckout-F6C6VCCG.js");
2332
+ return import("./hostedCheckout-GVS6UKYA.js");
2332
2333
  }
2333
2334
  function paymentsOffReason(reason) {
2334
2335
  return reason === "unavailable_for_event" || reason === "payments_off_for_event" ? reason : "not_configured";
@@ -2699,10 +2700,9 @@ var CSS = (
2699
2700
  .sl-booked.on .sl-booked-title{animation-delay:.22s}
2700
2701
  .sl-booked.on .sl-booked-sub{animation-delay:.3s}
2701
2702
 
2702
- /* sold-out overlay \u2014 every SEATED category's live availability is 0. Centered
2703
- over the map; a stub (disabled) "Join waitlist" button, exactly like the page.
2704
- Suppressed when GA areas exist (GA capacity isn't seat-counted). Clears live
2705
- the moment WS frees a seat up. */
2703
+ /* sold-out overlay \u2014 every SEATED category's live availability is 0. This is
2704
+ informational only: no waitlist workflow exists. Suppressed when GA areas
2705
+ exist (GA capacity isn't seat-counted). Clears live when WS frees a seat. */
2706
2706
  .sl-soldout{position:absolute;inset:0;z-index:10;display:none;flex-direction:column;align-items:center;
2707
2707
  justify-content:center;text-align:center;gap:8px;padding:24px;
2708
2708
  background:color-mix(in srgb,var(--sl-bg) 82%,transparent);backdrop-filter:blur(4px)}
@@ -2710,9 +2710,6 @@ var CSS = (
2710
2710
  .sl-soldout-eyebrow{font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--sl-accent);font-weight:800}
2711
2711
  .sl-soldout-title{font-size:32px;font-weight:800;color:var(--sl-text);line-height:1.05}
2712
2712
  .sl-soldout-copy{max-width:360px;font-size:13px;color:var(--sl-muted);line-height:1.5}
2713
- .sl-picker .sl-soldout-btn{margin-top:10px;min-height:40px;padding:10px 18px;border-radius:var(--sl-r-sm);
2714
- background:var(--sl-surface);color:var(--sl-muted);border:1px solid var(--sl-line);font-weight:800;font-size:13px;
2715
- cursor:not-allowed;opacity:.85}
2716
2713
 
2717
2714
  /* sales-closed pill (header) \u2014 persistent read-only state when the event's sales
2718
2715
  window is closed at load or closes live mid-session. Neutral (not accent) so it
@@ -2914,6 +2911,12 @@ var CSS = (
2914
2911
  Map|3D toggle and the seat confirm both stay usable over it. */
2915
2912
  .sl-view3d{position:absolute;inset:0;z-index:4;opacity:0;touch-action:none;
2916
2913
  transition:opacity .3s ease;background:radial-gradient(120% 120% at 50% 0%,#191f28 0%,#0d1014 70%)}
2914
+ .sl-view3d.has-comparison,.sl-view3d.has-passport{z-index:20}
2915
+ /* Confirm mode normally disables the entire GL sibling. Modal surfaces live
2916
+ inside that sibling, so explicitly restore pointer input only while one is
2917
+ open; their own inert contract keeps the venue underneath unavailable. */
2918
+ .sl-picker[data-confirming="true"] .sl-view3d.has-comparison,
2919
+ .sl-picker[data-confirming="true"] .sl-view3d.has-passport{pointer-events:auto}
2917
2920
  .sl-view3d canvas{display:block;width:100%;height:100%}
2918
2921
  .sl-view3d canvas:focus-visible{outline:2px solid var(--sl-accent);outline-offset:-3px}
2919
2922
  .sl-view3d-loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:10px;
@@ -2922,10 +2925,13 @@ var CSS = (
2922
2925
  border:2px solid rgba(217,226,242,.28);border-top-color:#d9e2f2;animation:slSpin .8s linear infinite}
2923
2926
  [data-view3d=on] .sl-chips,[data-view3d=on] .sl-rungs{display:none}
2924
2927
  .sl-view3d-back{position:absolute;top:12px;left:12px;z-index:2;display:inline-flex;align-items:center;gap:6px;
2925
- padding:7px 13px 7px 9px;border-radius:999px;font-size:11px;font-weight:800;letter-spacing:.03em;
2928
+ min-height:44px;padding:7px 13px 7px 9px;border-radius:999px;font-size:11px;font-weight:800;letter-spacing:.03em;
2926
2929
  color:#e6edf3;background:rgba(10,14,20,.62);border:1px solid rgba(255,255,255,.22);backdrop-filter:blur(6px)}
2927
2930
  .sl-view3d-back:hover,.sl-view3d-back:focus-visible{background:rgba(16,22,30,.82);border-color:rgba(255,255,255,.4)}
2928
2931
  .sl-view3d-back svg{width:15px;height:15px;stroke:currentColor;stroke-width:2.4;fill:none;stroke-linecap:round;stroke-linejoin:round}
2932
+ /* Map|3D already exits an overview to the 2D map. Reserve Back for the one
2933
+ state where it adds meaning: returning from an exact seat to the venue. */
2934
+ .sl-view3d:not(.is-seat-focused) .sl-view3d-back{display:none}
2929
2935
  .sl-view3d-fs{position:absolute;top:12px;right:12px;z-index:2;min-width:44px;min-height:44px;padding:8px 12px;
2930
2936
  border-radius:999px;font-size:16px;color:#e6edf3;background:rgba(10,14,20,.62);
2931
2937
  border:1px solid rgba(255,255,255,.22);backdrop-filter:blur(6px)}
@@ -2959,6 +2965,7 @@ var CSS = (
2959
2965
  .sl-view3d-nav button:hover,.sl-view3d-nav button:focus-visible{color:#eef1f8;border-color:rgba(190,205,240,.6)}
2960
2966
  .sl-view3d-nav button[aria-pressed="true"]{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
2961
2967
  .sl-view3d-nav button:disabled{opacity:.45;cursor:not-allowed}
2968
+ .sl-view3d-nav-toggle{display:none!important}
2962
2969
  .sl-view3d-nav select{min-height:38px;max-width:100%;padding:7px 34px 7px 12px;
2963
2970
  border-radius:999px;font:700 11.5px/1 inherit;color:#eef1f8;background:rgba(12,18,32,.86);
2964
2971
  border:1px solid rgba(150,165,205,.45);backdrop-filter:blur(6px);cursor:pointer}
@@ -2972,7 +2979,22 @@ var CSS = (
2972
2979
  /* On phones the library owns the bottom edge for seat/panorama/overview actions.
2973
2980
  Keep venue navigation in a separate top rail so those control families never
2974
2981
  stack over one another. */
2975
- .sl-picker[data-layout="narrow"] .sl-view3d-nav{top:68px;bottom:auto;max-width:calc(100% - 24px)}
2982
+ .sl-picker[data-layout="narrow"] .sl-view3d-nav{left:120px;top:12px;bottom:auto;max-width:calc(100% - 132px)}
2983
+ .sl-picker[data-layout="narrow"] .sl-view3d-nav-toggle{display:inline-flex!important;align-items:center;pointer-events:auto;min-height:44px}
2984
+ .sl-picker[data-layout="narrow"] .sl-view3d-nav:not(.is-open)>div{display:none!important}
2985
+ .sl-picker[data-layout="narrow"] .sl-view3d-nav.is-open{left:12px;top:68px;max-width:calc(100% - 24px);padding:8px;
2986
+ border:1px solid rgba(150,165,205,.35);border-radius:14px;background:rgba(8,12,22,.9);backdrop-filter:blur(10px)}
2987
+ .sl-picker[data-layout="narrow"] .sl-view3d-nav.is-open>div{display:flex}
2988
+ .sl-picker[data-layout="narrow"] .sl-view3d-nav.is-open .sl-view3d-locator{display:grid;grid-template-columns:1fr 1fr;width:100%;overflow:visible}
2989
+ .sl-picker[data-layout="narrow"] .sl-view3d-nav.is-open .sl-view3d-locator select{width:100%;min-width:0;flex:auto}
2990
+ .sl-picker[data-layout="narrow"] .sl-view3d-nav.is-open .sl-view3d-locator button{width:100%}
2991
+ /* Seat-eye is a decision state, not another venue-navigation state. Once the
2992
+ buyer arrives, clear the floor/area/locator rails and the module's duplicate
2993
+ Overview action. The picker-owned Back button becomes the one predictable
2994
+ escape: seat -> venue -> 2D map. */
2995
+ .sl-view3d.is-seat-focused .sl-view3d-nav,
2996
+ .sl-view3d.is-seat-focused .sl-3d-overview-control,
2997
+ .sl-view3d.is-seat-focused .sl-view3d-compare-saved{display:none!important}
2976
2998
  /* While immersed, the 2D-only chrome is meaningless \u2014 hide it, keep Map|3D. */
2977
2999
  .sl-picker[data-view3d="on"] .sl-rungs,
2978
3000
  .sl-picker[data-view3d="on"] .sl-floors,
@@ -2983,6 +3005,130 @@ var CSS = (
2983
3005
  .sl-picker[data-view3d="on"] .sl-confirm{left:50%!important;top:auto!important;bottom:16px;
2984
3006
  transform:translateX(-50%);width:min(342px,calc(100% - 24px))}
2985
3007
  .sl-picker[data-view3d="on"] .sl-confirm[data-placement]{transform:translateX(-50%)}
3008
+ .sl-confirm-inspect-row{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:9px}
3009
+ .sl-confirm-inspect-row .sl-confirm-3d{margin-top:0;min-height:44px}
3010
+ .sl-confirm-compare{min-height:44px;padding:8px;border-radius:8px;border:1px solid var(--sl-line);
3011
+ display:flex;align-items:center;justify-content:center;gap:7px;font-size:12px;font-weight:800;
3012
+ color:var(--sl-text);background:transparent}
3013
+ .sl-confirm-compare:hover,.sl-confirm-compare:focus-visible{border-color:var(--sl-accent);
3014
+ background:color-mix(in srgb,var(--sl-accent) 10%,transparent)}
3015
+ .sl-confirm-compare:disabled{opacity:.62;cursor:default}
3016
+ .sl-confirm-confidence{width:100%;min-height:44px;margin-top:8px;padding:8px 10px;border-radius:9px;
3017
+ border:1px solid color-mix(in srgb,var(--sl-accent) 35%,var(--sl-line));display:flex;align-items:center;
3018
+ justify-content:space-between;gap:10px;text-align:left;color:var(--sl-text);
3019
+ background:color-mix(in srgb,var(--sl-accent) 7%,var(--sl-surface))}
3020
+ .sl-confirm-confidence>span{min-width:0}
3021
+ .sl-confirm-confidence strong,.sl-confirm-confidence small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
3022
+ .sl-confirm-confidence strong{font-size:11px}.sl-confirm-confidence small{margin-top:2px;color:var(--sl-muted);font-size:9.5px}
3023
+ .sl-confirm-confidence em{display:none;font-style:normal}
3024
+ .sl-confirm-confidence>b{flex:none;font-size:11px;color:var(--sl-accent)}
3025
+ .sl-confirm-confidence:hover,.sl-confirm-confidence:focus-visible{border-color:var(--sl-accent)}
3026
+ /* The production 3D decision dock is deliberately denser than the 2D popup:
3027
+ the venue remains the main content and the two inspection actions share one
3028
+ row. Truth-bearing accessibility/restriction copy is never hidden. */
3029
+ .sl-picker[data-view3d="on"][data-layout="narrow"] .sl-confirm{bottom:10px}
3030
+ .sl-picker[data-view3d="on"][data-layout="narrow"] .sl-confirm-field{padding:8px 9px 7px}
3031
+ .sl-picker[data-view3d="on"][data-layout="narrow"] .sl-confirm-value{font-size:14px;margin-top:2px}
3032
+ .sl-picker[data-view3d="on"][data-layout="narrow"] .sl-confirm-field:first-child .sl-confirm-value{font-size:12px}
3033
+ .sl-picker[data-view3d="on"][data-layout="narrow"] .sl-confirm-cat{padding:7px 10px}
3034
+ .sl-picker[data-view3d="on"][data-layout="narrow"] .sl-confirm-price{font-size:15px}
3035
+ .sl-picker[data-view3d="on"][data-layout="narrow"] .sl-confirm-body{padding:8px 10px 9px}
3036
+ .sl-picker[data-view3d="on"][data-layout="narrow"] .sl-confirm-row{margin-top:7px}
3037
+ /* A short embedded/mobile picker cannot afford a full decision sheet over a
3038
+ 285px map. Keep three 44px action rows and move all disclosure into the
3039
+ passport instead of hiding it without a route back. */
3040
+ .sl-picker[data-view3d="on"][data-layout="narrow"][data-density="compact"] .sl-confirm{bottom:6px}
3041
+ .sl-picker[data-view3d="on"][data-layout="narrow"][data-density="compact"] .sl-confirm-grid,
3042
+ .sl-picker[data-view3d="on"][data-layout="narrow"][data-density="compact"] .sl-confirm-cat,
3043
+ .sl-picker[data-view3d="on"][data-layout="narrow"][data-density="compact"] .sl-confirm-body>.sl-cx{display:none}
3044
+ .sl-picker[data-view3d="on"][data-layout="narrow"][data-density="compact"] .sl-confirm-body{padding:6px 8px 7px}
3045
+ .sl-picker[data-view3d="on"][data-layout="narrow"][data-density="compact"] .sl-confirm-confidence{margin-top:0}
3046
+ .sl-picker[data-view3d="on"][data-layout="narrow"][data-density="compact"] .sl-confirm-confidence em{display:block;margin-bottom:2px;
3047
+ color:var(--sl-text);font-size:12px;font-weight:850;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
3048
+ .sl-picker[data-view3d="on"][data-layout="narrow"][data-density="compact"] .sl-confirm-confidence strong{font-size:9.5px}
3049
+ .sl-picker[data-view3d="on"][data-layout="narrow"][data-density="compact"] .sl-confirm-confidence small{display:none}
3050
+ .sl-picker[data-view3d="on"][data-layout="narrow"][data-density="compact"] .sl-confirm-inspect-row{margin-top:5px}
3051
+ .sl-picker[data-view3d="on"][data-layout="narrow"][data-density="compact"] .sl-confirm-row{margin-top:5px}
3052
+
3053
+ /* Saved comparison lives in the top journey row, away from checkout, arrival
3054
+ controls and the bottom-right privacy position. */
3055
+ .sl-view3d-compare-saved{position:absolute;top:12px;left:136px;z-index:5;display:flex;align-items:stretch;
3056
+ max-width:180px;min-height:38px;border-radius:999px;overflow:hidden;color:#e6edf3;
3057
+ background:rgba(10,14,20,.72);border:1px solid rgba(255,255,255,.22);backdrop-filter:blur(6px)}
3058
+ .sl-view3d-compare-saved button{min-width:0;padding:7px 10px;color:inherit;font-size:11px;font-weight:800;white-space:nowrap}
3059
+ .sl-view3d-compare-saved .main{overflow:hidden;text-overflow:ellipsis}
3060
+ .sl-view3d-compare-saved .clear{width:34px;padding:7px;border-left:1px solid rgba(255,255,255,.18)}
3061
+ .sl-view3d-compare-saved button:hover,.sl-view3d-compare-saved button:focus-visible{background:rgba(255,255,255,.1)}
3062
+ .sl-picker[data-layout="narrow"] .sl-view3d-compare-saved{left:126px;max-width:calc(100% - 194px);min-height:44px}
3063
+
3064
+ /* An unavailable seat is still inspectable. This compact, non-modal status
3065
+ card explains the exact chair the buyer touched without selecting it or
3066
+ obscuring the venue with the full purchase confirmation sheet. */
3067
+ .sl-view3d-unavailable{position:absolute;left:50%;bottom:18px;z-index:9;display:grid;
3068
+ grid-template-columns:minmax(0,1fr) auto;gap:4px 14px;width:min(330px,calc(100% - 24px));
3069
+ padding:13px 14px;border:1px solid rgba(255,255,255,.22);border-radius:14px;
3070
+ color:#eef3fb;background:rgba(10,14,22,.94);box-shadow:0 18px 48px rgba(0,0,0,.48);
3071
+ backdrop-filter:blur(10px);transform:translateX(-50%)}
3072
+ .sl-view3d-unavailable[data-state="held"]{border-color:rgba(242,168,56,.7)}
3073
+ .sl-view3d-unavailable[data-state="sold"],.sl-view3d-unavailable[data-state="dimmed"]{border-color:rgba(160,170,188,.48)}
3074
+ .sl-view3d-unavailable-copy{min-width:0}
3075
+ .sl-view3d-unavailable-eyebrow{display:block;font-size:9px;line-height:1.2;letter-spacing:.13em;
3076
+ text-transform:uppercase;color:#aab7cc;font-weight:850}
3077
+ .sl-view3d-unavailable strong{display:block;margin-top:3px;font-size:17px;line-height:1.2}
3078
+ .sl-view3d-unavailable p{grid-column:1/-1;margin:4px 0 0;color:#b9c4d7;font-size:11px;line-height:1.4}
3079
+ .sl-view3d-unavailable button{align-self:start;min-width:44px;min-height:44px;margin:-5px -6px 0 0;
3080
+ border-radius:999px;color:#eef3fb;font-size:18px;border:1px solid rgba(255,255,255,.18)}
3081
+ .sl-view3d-unavailable button:hover,.sl-view3d-unavailable button:focus-visible{background:rgba(255,255,255,.1)}
3082
+ .sl-picker[data-layout="narrow"] .sl-view3d-unavailable{bottom:10px;padding-bottom:max(13px,env(safe-area-inset-bottom))}
3083
+
3084
+ .sl-view3d-compare-shell{position:absolute;inset:0;z-index:30;display:grid;place-items:center;padding:16px}
3085
+ .sl-view3d-compare-scrim{position:absolute;inset:0;background:rgba(3,6,12,.74);backdrop-filter:blur(5px)}
3086
+ .sl-view3d-compare{position:relative;width:min(720px,100%);max-height:min(680px,calc(100% - 20px));overflow:auto;
3087
+ border:1px solid rgba(160,177,214,.34);border-radius:18px;background:var(--sl-bg);color:var(--sl-text);
3088
+ box-shadow:0 28px 90px rgba(0,0,0,.55);padding:18px}
3089
+ .sl-view3d-compare>header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}
3090
+ .sl-view3d-compare>header span{display:block;font-size:9px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:850}
3091
+ .sl-view3d-compare>header strong{display:block;margin-top:4px;font-size:20px}
3092
+ .sl-view3d-compare>header button{min-width:44px;min-height:44px;border-radius:999px;border:1px solid var(--sl-line);color:var(--sl-text)}
3093
+ .sl-view3d-compare-note{margin:12px 0;color:var(--sl-muted);font-size:12px;line-height:1.45}
3094
+ .sl-view3d-compare-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
3095
+ .sl-view3d-compare article{min-width:0;padding:14px;border:1px solid var(--sl-line);border-radius:13px;background:var(--sl-surface)}
3096
+ .sl-view3d-compare article>span{font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--sl-muted);font-weight:850}
3097
+ .sl-view3d-compare article>strong{display:block;margin-top:3px;font-size:20px}
3098
+ .sl-view3d-compare article>small{display:block;margin-top:3px;color:var(--sl-muted)}
3099
+ .sl-view3d-compare dl{margin:12px 0 0}
3100
+ .sl-view3d-compare dl div{display:grid;grid-template-columns:minmax(90px,.8fr) minmax(0,1.2fr);gap:10px;padding:8px 0;border-top:1px solid var(--sl-line)}
3101
+ .sl-view3d-compare dt{font-size:10.5px;color:var(--sl-muted)}
3102
+ .sl-view3d-compare dd{margin:0;text-align:right;font-size:11px;font-weight:750;overflow-wrap:anywhere}
3103
+ .sl-view3d-compare-actions{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;margin-top:12px}
3104
+ .sl-view3d-compare-actions button{min-height:44px;border-radius:9px;border:1px solid var(--sl-line);font-size:12px;font-weight:800}
3105
+ .sl-view3d-compare-actions .select{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
3106
+ .sl-view3d-compare-actions button:disabled{opacity:.5;cursor:not-allowed}
3107
+ .sl-view3d-passport-shell{position:absolute;inset:0;z-index:40;display:grid;place-items:center;padding:16px}
3108
+ .sl-view3d-passport-scrim{position:absolute;inset:0;background:rgba(3,6,12,.8);backdrop-filter:blur(6px)}
3109
+ .sl-view3d-passport{position:relative;width:min(540px,100%);max-height:min(700px,calc(100% - 20px));overflow:auto;
3110
+ padding:18px;border:1px solid rgba(160,177,214,.38);border-radius:18px;background:var(--sl-bg);color:var(--sl-text);
3111
+ box-shadow:0 28px 90px rgba(0,0,0,.6)}
3112
+ .sl-view3d-passport>header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}
3113
+ .sl-view3d-passport>header span{display:block;font-size:9px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:850}
3114
+ .sl-view3d-passport>header strong{display:block;margin-top:4px;font-size:20px}
3115
+ .sl-view3d-passport>header button{min-width:44px;min-height:44px;border:1px solid var(--sl-line);border-radius:999px;color:var(--sl-text)}
3116
+ .sl-view3d-passport-summary{margin:14px 0;padding:12px;border-radius:12px;background:color-mix(in srgb,var(--sl-accent) 9%,var(--sl-surface));
3117
+ border:1px solid color-mix(in srgb,var(--sl-accent) 30%,var(--sl-line))}
3118
+ .sl-view3d-passport-summary strong{display:block;font-size:15px}.sl-view3d-passport-summary span{display:block;margin-top:4px;font-size:11px;color:var(--sl-muted)}
3119
+ .sl-view3d-passport dl{margin:0}.sl-view3d-passport dl div{display:grid;grid-template-columns:minmax(105px,.75fr) minmax(0,1.25fr);
3120
+ gap:12px;padding:9px 0;border-top:1px solid var(--sl-line)}
3121
+ .sl-view3d-passport dt{font-size:10.5px;color:var(--sl-muted)}.sl-view3d-passport dd{margin:0;text-align:right;font-size:11px;font-weight:750;overflow-wrap:anywhere}
3122
+ .sl-view3d-passport h4{margin:14px 0 6px;font-size:11px}.sl-view3d-passport ul{margin:0;padding-left:18px;color:var(--sl-muted);font-size:10.5px;line-height:1.5}
3123
+ .sl-view3d-passport-note{margin:14px 0 0;color:var(--sl-muted);font-size:10.5px;line-height:1.45}
3124
+ @media(max-width:640px){
3125
+ .sl-view3d-compare-shell{padding:max(10px,env(safe-area-inset-top)) 10px max(10px,env(safe-area-inset-bottom))}
3126
+ .sl-view3d-compare{width:100%;max-height:100%;padding:14px 14px max(86px,calc(14px + env(safe-area-inset-bottom)));border-radius:15px}
3127
+ .sl-view3d-compare-grid{grid-template-columns:1fr}
3128
+ .sl-view3d-compare article{padding:12px}
3129
+ .sl-view3d-passport-shell{padding:max(10px,env(safe-area-inset-top)) 10px max(10px,env(safe-area-inset-bottom))}
3130
+ .sl-view3d-passport{width:100%;max-height:100%;padding:14px 14px max(24px,calc(14px + env(safe-area-inset-bottom)));border-radius:15px}
3131
+ }
2986
3132
 
2987
3133
  /* Plain "View from here" action shown when no real photo exists (the synthetic
2988
3134
  thumb is suppressed at card size \u2014 full-screen is where it earns its keep). */
@@ -3282,6 +3428,16 @@ var SeatPicker = class _SeatPicker {
3282
3428
  this.view3dGen = 0;
3283
3429
  /** Seat whose 2D confirm card launched "See it in 3D"; re-shown on return. */
3284
3430
  this.view3dReturnSeat = null;
3431
+ /** Current premium 3D journey depth. `null` is the venue; a seat id is the
3432
+ * fixed seat-eye state. It lets Back unwind one step before leaving 3D. */
3433
+ this.view3dTargetSeatId = null;
3434
+ /** Inspection-only comparison. These ids never represent cart selection. */
3435
+ this.view3dCompareSeatIds = [];
3436
+ this.view3dCompareChip = null;
3437
+ this.view3dCompareEl = null;
3438
+ this.view3dCompareCleanup = null;
3439
+ this.view3dPassportEl = null;
3440
+ this.view3dPassportCleanup = null;
3285
3441
  this.floorsEl = null;
3286
3442
  this.secCardEl = null;
3287
3443
  this.viewEl = null;
@@ -3692,24 +3848,73 @@ var SeatPicker = class _SeatPicker {
3692
3848
  /** Mount the full picker as a document-level modal. Resolves after render. */
3693
3849
  static async open(options) {
3694
3850
  ensureStyle();
3851
+ const priorFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
3695
3852
  const scrim = document.createElement("div");
3696
3853
  scrim.className = "sl-modal-scrim";
3697
3854
  const frame = document.createElement("div");
3698
3855
  frame.className = "sl-modal-frame";
3856
+ frame.setAttribute("role", "dialog");
3857
+ frame.setAttribute("aria-modal", "true");
3858
+ frame.setAttribute("aria-label", "Seat selection");
3859
+ frame.tabIndex = -1;
3699
3860
  scrim.appendChild(frame);
3700
3861
  document.body.appendChild(scrim);
3701
3862
  const prevOverflow = document.body.style.overflow;
3702
3863
  document.body.style.overflow = "hidden";
3703
3864
  const picker = new _SeatPicker({ ...options, container: frame });
3704
3865
  picker.modalScrim = scrim;
3705
- picker.prevFocus = document.activeElement;
3866
+ picker.prevFocus = priorFocus;
3867
+ const focusableSelector = [
3868
+ "a[href]",
3869
+ "area[href]",
3870
+ "button",
3871
+ "input",
3872
+ "select",
3873
+ "textarea",
3874
+ "iframe",
3875
+ "object",
3876
+ "embed",
3877
+ "summary",
3878
+ "audio[controls]",
3879
+ "video[controls]",
3880
+ '[contenteditable]:not([contenteditable="false"])',
3881
+ "[tabindex]"
3882
+ ].join(",");
3883
+ const activeDialog = () => {
3884
+ const nested = [...frame.querySelectorAll('[role="dialog"][aria-modal="true"]')].filter((dialog) => dialog.isConnected && !dialog.closest('[hidden], [aria-hidden="true"], [inert]'));
3885
+ return nested[nested.length - 1] ?? frame;
3886
+ };
3887
+ const hiddenWithin = (element, scope) => {
3888
+ let current = element;
3889
+ while (current) {
3890
+ const style = window.getComputedStyle(current);
3891
+ if (current.hidden || current.getAttribute("aria-hidden") === "true" || current.hasAttribute("inert") || style.display === "none" || style.visibility === "hidden" || style.visibility === "collapse") return true;
3892
+ if (current === scope) return false;
3893
+ current = current.parentElement;
3894
+ }
3895
+ return true;
3896
+ };
3897
+ const tabbableWithin = (scope) => [...scope.querySelectorAll(focusableSelector)].filter((element) => element.tabIndex >= 0 && !element.matches(":disabled") && !hiddenWithin(element, scope));
3898
+ const focusEdge = (scope, backwards) => {
3899
+ const tabbable = tabbableWithin(scope);
3900
+ const target = backwards ? tabbable[tabbable.length - 1] : tabbable[0];
3901
+ if (target) target.focus({ preventScroll: true });
3902
+ else {
3903
+ if (!scope.hasAttribute("tabindex")) scope.tabIndex = -1;
3904
+ scope.focus({ preventScroll: true });
3905
+ }
3906
+ };
3706
3907
  let closing = false;
3707
3908
  const close = () => {
3708
3909
  if (closing) return;
3709
3910
  closing = true;
3710
3911
  document.body.style.overflow = prevOverflow;
3711
- scrim.style.opacity = "0";
3712
- scrim.style.pointerEvents = "none";
3912
+ if (picker.escHandler) document.removeEventListener("keydown", picker.escHandler);
3913
+ scrim.remove();
3914
+ picker.modalScrim = null;
3915
+ const restoreTarget = picker.prevFocus;
3916
+ picker.prevFocus = null;
3917
+ if (restoreTarget?.isConnected) restoreTarget.focus({ preventScroll: true });
3713
3918
  const finish = () => {
3714
3919
  picker.destroy();
3715
3920
  options.onClose?.();
@@ -3722,6 +3927,28 @@ var SeatPicker = class _SeatPicker {
3722
3927
  if (e.target === scrim) close();
3723
3928
  });
3724
3929
  picker.escHandler = (e) => {
3930
+ if (e.key === "Tab") {
3931
+ if (e.defaultPrevented) return;
3932
+ const scope = activeDialog();
3933
+ const tabbable = tabbableWithin(scope);
3934
+ const first = tabbable[0];
3935
+ const last = tabbable[tabbable.length - 1];
3936
+ const active = document.activeElement;
3937
+ if (!first || !last) {
3938
+ e.preventDefault();
3939
+ focusEdge(scope, e.shiftKey);
3940
+ } else if (active === scope || !active || !scope.contains(active)) {
3941
+ e.preventDefault();
3942
+ (e.shiftKey ? last : first).focus({ preventScroll: true });
3943
+ } else if (e.shiftKey && active === first) {
3944
+ e.preventDefault();
3945
+ last.focus({ preventScroll: true });
3946
+ } else if (!e.shiftKey && active === last) {
3947
+ e.preventDefault();
3948
+ first.focus({ preventScroll: true });
3949
+ }
3950
+ return;
3951
+ }
3725
3952
  if (e.key !== "Escape") return;
3726
3953
  if (picker.tableDialog) {
3727
3954
  e.preventDefault();
@@ -3734,6 +3961,7 @@ var SeatPicker = class _SeatPicker {
3734
3961
  picker.bestAvailableConfirm = false;
3735
3962
  picker.syncTray();
3736
3963
  } else {
3964
+ e.preventDefault();
3737
3965
  close();
3738
3966
  }
3739
3967
  };
@@ -3741,6 +3969,7 @@ var SeatPicker = class _SeatPicker {
3741
3969
  await picker.render();
3742
3970
  picker.els.close?.classList.add("on");
3743
3971
  picker.els.close?.addEventListener("click", close);
3972
+ focusEdge(activeDialog(), false);
3744
3973
  return picker;
3745
3974
  }
3746
3975
  async render() {
@@ -3852,8 +4081,10 @@ var SeatPicker = class _SeatPicker {
3852
4081
  if (w <= 0) return;
3853
4082
  this.reportFramedHeight();
3854
4083
  const next = w < 640 ? "narrow" : "wide";
3855
- if (root.dataset.layout === next) return;
4084
+ const density = root.clientHeight < 560 ? "compact" : "comfortable";
4085
+ if (root.dataset.layout === next && root.dataset.density === density) return;
3856
4086
  root.dataset.layout = next;
4087
+ root.dataset.density = density;
3857
4088
  if (next === "narrow" && !root.dataset.sheet) root.dataset.sheet = "peek";
3858
4089
  this.dockLayoutChrome();
3859
4090
  };
@@ -4158,14 +4389,14 @@ var SeatPicker = class _SeatPicker {
4158
4389
  const v = t2(key);
4159
4390
  return v === key ? fallback : v;
4160
4391
  }
4161
- /** Sold-out overlay — centered over the map, disabled waitlist stub (Gap 2). */
4392
+ /** Sold-out overlay — an informational state with no unavailable action. */
4162
4393
  buildSoldoutOverlay() {
4163
4394
  if (!this.els.map) return;
4164
4395
  const el = document.createElement("div");
4165
4396
  el.className = "sl-soldout";
4166
4397
  el.setAttribute("role", "status");
4167
4398
  const name = (this.controller.doc?.theme?.brandName ?? this.opts.theme?.brandName ?? this.els.name?.textContent ?? this.tf("picker.soldOutEyebrow", "This event")).toUpperCase();
4168
- el.innerHTML = `<div class="sl-soldout-eyebrow">${name}</div><div class="sl-soldout-title">${this.tf("picker.soldOutTitle", "Sold out")}</div><p class="sl-soldout-copy">${this.tf("picker.soldOutCopy", "Every seat is gone. Join the waitlist and we\u2019ll email you if seats are released.")}</p><button type="button" class="sl-soldout-btn" disabled>${this.tf("picker.waitlist", "Join waitlist")}</button>`;
4399
+ el.innerHTML = `<div class="sl-soldout-eyebrow">${name}</div><div class="sl-soldout-title">${this.tf("picker.soldOutTitle", "Sold out")}</div><p class="sl-soldout-copy">${this.tf("picker.soldOutCopy", "No reserved seats are currently available for this event.")}</p>`;
4169
4400
  this.els.map.appendChild(el);
4170
4401
  this.soldoutEl = el;
4171
4402
  }
@@ -5082,7 +5313,7 @@ var SeatPicker = class _SeatPicker {
5082
5313
  el.setAttribute("aria-modal", "true");
5083
5314
  el.setAttribute("aria-label", `Confirm seat ${seat.label}`);
5084
5315
  el.style.setProperty("--sl-cat", cat?.color ?? "#6e7bff");
5085
- el.innerHTML = `<div class="sl-confirm-grid">` + identityFields + `</div><div class="sl-confirm-cat"><span class="sl-dot" style="background:${cat?.color ?? "#6e7bff"}"></span><span class="sl-confirm-cat-name">${safe(details?.categoryLabel ?? cat?.label ?? seat.categoryKey)}</span>` + (price != null ? `<span class="sl-confirm-price">${this.money(price)}</span>` : "") + `</div><div class="sl-confirm-body">` + this.wheelchairConfirmHtml(details?.wheelchairSpaceType) + this.commercialConfirmHtml(seat.commercial) + (this.seatViewEnabled() ? this.confirmThumbHtml(seat) : "") + this.see3dConfirmHtml() + `<div class="sl-confirm-row"><button type="button" class="sl-confirm-cancel">Cancel</button><button type="button" class="sl-confirm-add"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4 4L19 7"/></svg>Select</button></div></div>`;
5316
+ el.innerHTML = `<div class="sl-confirm-grid">` + identityFields + `</div><div class="sl-confirm-cat"><span class="sl-dot" style="background:${cat?.color ?? "#6e7bff"}"></span><span class="sl-confirm-cat-name">${safe(details?.categoryLabel ?? cat?.label ?? seat.categoryKey)}</span>` + (price != null ? `<span class="sl-confirm-price">${this.money(price)}</span>` : "") + `</div><div class="sl-confirm-body">` + this.wheelchairConfirmHtml(details?.wheelchairSpaceType) + this.commercialConfirmHtml(seat.commercial) + (this.seatViewEnabled() && this.buyerView !== "venue3d" ? this.confirmThumbHtml(seat) : "") + (this.buyerView === "venue3d" ? `${this.seatConfidenceConfirmHtml(seat, `${details?.displayLabel ?? seat.displayLabel ?? seat.label} \xB7 ${price == null ? this.tf("picker.priceNotSupplied", "Price not supplied") : this.money(price)}`)}<div class="sl-confirm-inspect-row">${this.view3dCompareConfirmHtml(seat)}${this.see3dConfirmHtml()}</div>` : this.see3dConfirmHtml()) + `<div class="sl-confirm-row"><button type="button" class="sl-confirm-cancel">Cancel</button><button type="button" class="sl-confirm-add"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4 4L19 7"/></svg>Select</button></div></div>`;
5086
5317
  this.els.map.appendChild(el);
5087
5318
  this.confirmEl = el;
5088
5319
  const thumb = el.querySelector(".sl-confirm-thumb");
@@ -5094,6 +5325,10 @@ var SeatPicker = class _SeatPicker {
5094
5325
  }
5095
5326
  this.reanchorConfirm();
5096
5327
  el.querySelector(".sl-confirm-view")?.addEventListener("click", () => void this.openSeatView(seat));
5328
+ el.querySelector(".sl-confirm-confidence")?.addEventListener("click", (event) => {
5329
+ this.openSeatConfidencePassport(seat, event.currentTarget instanceof HTMLElement ? event.currentTarget : null);
5330
+ });
5331
+ el.querySelector(".sl-confirm-compare")?.addEventListener("click", () => this.saveView3dComparisonSeat(seat));
5097
5332
  el.querySelector(".sl-confirm-3d")?.addEventListener("click", () => {
5098
5333
  if (this.buyerView === "venue3d") {
5099
5334
  this.dismissConfirm();
@@ -5137,12 +5372,14 @@ var SeatPicker = class _SeatPicker {
5137
5372
  this.dismissConfirm();
5138
5373
  this.collapseSectionCard();
5139
5374
  this.syncTray();
5375
+ this.syncSelectionTo3d();
5140
5376
  }
5141
5377
  cancelConfirm() {
5142
5378
  const seat = this.confirmSeat;
5143
5379
  if (!seat) return;
5144
5380
  this.controller.deselect([seat.id]);
5145
5381
  if (this.confirmSeat) this.dismissConfirm();
5382
+ this.syncSelectionTo3d();
5146
5383
  this.root?.focus({ preventScroll: true });
5147
5384
  }
5148
5385
  closeConfirm() {
@@ -6439,16 +6676,13 @@ var SeatPicker = class _SeatPicker {
6439
6676
  return null;
6440
6677
  }
6441
6678
  }
6442
- const doc = this.controller.doc;
6443
- if (!doc) return null;
6444
- const activeId = this.controller.getActiveFloorId();
6445
- const focal = seat.focalPoint ?? doc.floors?.find((f) => f.id === activeId)?.focalPoint ?? doc.focalPoint ?? { x: 0, y: 0 };
6446
- try {
6447
- const { generateSeatPanorama } = await loadPanorama();
6448
- return { url: generateSeatPanorama(seat, focal, this.allSeats()).url, generated: true };
6449
- } catch {
6450
- return null;
6451
- }
6679
+ return {
6680
+ url: "",
6681
+ generated: true,
6682
+ mediaKind: "model",
6683
+ coverage: "exact-seat",
6684
+ sourceLabel: this.tf("picker.chartDerivedModel", "Chart-derived model")
6685
+ };
6452
6686
  }
6453
6687
  /** Route the module's decoupled analytics into the host callback, tagged buyer. */
6454
6688
  emit3dAnalytics(event, props) {
@@ -6472,8 +6706,16 @@ var SeatPicker = class _SeatPicker {
6472
6706
  if (already) {
6473
6707
  this.controller.deselect([seatId]);
6474
6708
  this.dismissConfirm();
6709
+ this.syncSelectionTo3d();
6475
6710
  return;
6476
6711
  }
6712
+ const visualState = this.seatState3dFor(seat);
6713
+ if (visualState !== "available") {
6714
+ this.showUnavailable3dSeat(seat, visualState);
6715
+ this.syncSelectionTo3d();
6716
+ return;
6717
+ }
6718
+ this.dismissUnavailable3dSeat();
6477
6719
  const added = this.controller.select([seatId]);
6478
6720
  if (!added.length) {
6479
6721
  this.syncSelectionTo3d();
@@ -6489,6 +6731,391 @@ var SeatPicker = class _SeatPicker {
6489
6731
  }
6490
6732
  if (this.opts.confirmSelection !== false) this.showConfirm(seat);
6491
6733
  else this.syncTray();
6734
+ this.syncSelectionTo3d();
6735
+ }
6736
+ /** Explain a visible-but-unselectable 3D seat without entering the booking
6737
+ * flow. Category colour remains visible in the venue; this card names the
6738
+ * availability state explicitly so yellow never has to carry both meanings. */
6739
+ showUnavailable3dSeat(seat, visualState) {
6740
+ const overlay = this.view3dEl;
6741
+ if (!overlay) return;
6742
+ this.dismissUnavailable3dSeat();
6743
+ const status = this.controller.getStatus(seat.id);
6744
+ const details = this.controller.seatDetails(seat.id);
6745
+ const identity = details?.displayLabel ?? seat.displayLabel ?? seat.label;
6746
+ const section = details?.sectionLabel;
6747
+ const row = this.rowShort(details);
6748
+ const location2 = [section, row ? `Row ${row}` : null, identity].filter(Boolean).join(" \xB7 ");
6749
+ const copy = status === "held" ? {
6750
+ title: this.tf("picker.temporarilyHeld", "Temporarily held"),
6751
+ message: this.tf("picker.heldSeatExplanation", "Another buyer is holding this seat. It may become available again.")
6752
+ } : status === "booked" ? {
6753
+ title: this.tf("picker.sold", "Sold"),
6754
+ message: this.tf("picker.soldSeatExplanation", "This seat has already been booked.")
6755
+ } : status === "not_for_sale" ? {
6756
+ title: this.tf("picker.notForSale", "Not for sale"),
6757
+ message: this.tf("picker.notForSaleExplanation", "This seat is not included in the current sale.")
6758
+ } : {
6759
+ title: this.tf("picker.filteredSeat", "Unavailable with current filters"),
6760
+ message: this.tf("picker.filteredSeatExplanation", "Change the active price or view filters to make this seat selectable.")
6761
+ };
6762
+ const card = document.createElement("div");
6763
+ card.className = "sl-view3d-unavailable";
6764
+ card.dataset.state = visualState;
6765
+ card.setAttribute("role", "status");
6766
+ card.setAttribute("aria-live", "polite");
6767
+ const eyebrow = document.createElement("span");
6768
+ eyebrow.className = "sl-view3d-unavailable-eyebrow";
6769
+ eyebrow.textContent = location2 || identity;
6770
+ const title = document.createElement("strong");
6771
+ title.textContent = copy.title;
6772
+ const content = document.createElement("div");
6773
+ content.className = "sl-view3d-unavailable-copy";
6774
+ content.append(eyebrow, title);
6775
+ const close = document.createElement("button");
6776
+ close.type = "button";
6777
+ close.setAttribute("aria-label", this.tf("picker.closeSeatStatus", "Close seat status"));
6778
+ close.textContent = "\xD7";
6779
+ const message = document.createElement("p");
6780
+ message.textContent = copy.message;
6781
+ card.append(content, close, message);
6782
+ close.addEventListener("click", () => card.remove());
6783
+ overlay.appendChild(card);
6784
+ this.announceSeat(seat);
6785
+ }
6786
+ dismissUnavailable3dSeat() {
6787
+ this.view3dEl?.querySelector(".sl-view3d-unavailable")?.remove();
6788
+ }
6789
+ /** Comparison belongs to inspection, never selection. The confirm candidate
6790
+ * remains excluded from checkout until Select; saving it releases that
6791
+ * candidate before any comparison state is created. */
6792
+ view3dCompareConfirmHtml(seat) {
6793
+ if (this.buyerView !== "venue3d") return "";
6794
+ const saved = this.view3dCompareSeatIds;
6795
+ const included = saved.includes(seat.id);
6796
+ const label = included ? saved.length > 1 ? this.tf("picker.openComparison", "Open comparison") : this.tf("picker.savedForComparison", "Saved for comparison") : saved.length ? this.tf("picker.compareWithSaved", "Compare with saved") : this.tf("picker.saveToCompare", "Save to compare");
6797
+ return `<button type="button" class="sl-confirm-compare"${included && saved.length === 1 ? " disabled" : ""}><span aria-hidden="true">\u21C4</span><span>${this.escCx(label)}</span></button>`;
6798
+ }
6799
+ seatConfidenceConfirmHtml(seat, compactSummary) {
6800
+ if (this.buyerView !== "venue3d") return "";
6801
+ const disclosure = seatConfidenceDisclosure(seat.confidenceEvidence);
6802
+ const detail = disclosure.modeledTarget ?? disclosure.reality;
6803
+ return `<button type="button" class="sl-confirm-confidence" aria-label="Open seat confidence passport for ${this.escCx(seat.displayLabel ?? seat.label)}"><span><em>${this.escCx(compactSummary)}</em><strong>${this.escCx(disclosure.headline)}</strong><small>${this.escCx(detail)}</small></span><b>Passport</b></button>`;
6804
+ }
6805
+ saveView3dComparisonSeat(seat) {
6806
+ if (this.buyerView !== "venue3d") return;
6807
+ const previous = this.view3dCompareSeatIds;
6808
+ if (!previous.includes(seat.id)) {
6809
+ this.view3dCompareSeatIds = previous.length === 0 ? [seat.id] : [previous[0], seat.id];
6810
+ }
6811
+ if (this.confirmSeat?.id === seat.id) {
6812
+ this.controller.deselect([seat.id]);
6813
+ this.dismissConfirm();
6814
+ this.syncSelectionTo3d();
6815
+ this.syncTray();
6816
+ }
6817
+ this.syncView3dCompareChip();
6818
+ this.emit3dAnalytics("3d_comparison_saved", {
6819
+ seatId: seat.id,
6820
+ count: this.view3dCompareSeatIds.length
6821
+ });
6822
+ if (this.view3dCompareSeatIds.length > 1) this.openView3dComparison();
6823
+ else this.toast(this.tf("picker.chooseAnotherToCompare", "Seat saved. Choose another seat to compare."), "success");
6824
+ }
6825
+ clearView3dComparison() {
6826
+ this.closeView3dComparison(false);
6827
+ this.view3dCompareSeatIds = [];
6828
+ this.view3dCompareChip?.remove();
6829
+ this.view3dCompareChip = null;
6830
+ this.emit3dAnalytics("3d_comparison_cleared");
6831
+ }
6832
+ syncView3dCompareChip() {
6833
+ const overlay = this.view3dEl;
6834
+ if (!overlay || this.view3dCompareSeatIds.length === 0) {
6835
+ this.view3dCompareChip?.remove();
6836
+ this.view3dCompareChip = null;
6837
+ return;
6838
+ }
6839
+ let chip = this.view3dCompareChip;
6840
+ if (!chip) {
6841
+ chip = document.createElement("div");
6842
+ chip.className = "sl-view3d-compare-saved";
6843
+ chip.setAttribute("role", "group");
6844
+ chip.setAttribute("aria-label", "Saved seat comparison");
6845
+ const main2 = document.createElement("button");
6846
+ main2.type = "button";
6847
+ main2.className = "main";
6848
+ main2.addEventListener("click", () => {
6849
+ if (this.view3dCompareSeatIds.length > 1) this.openView3dComparison();
6850
+ else this.toast(this.tf("picker.chooseAnotherToCompare", "Choose another seat to compare."), "neutral");
6851
+ });
6852
+ const clear = document.createElement("button");
6853
+ clear.type = "button";
6854
+ clear.className = "clear";
6855
+ clear.textContent = "\xD7";
6856
+ clear.setAttribute("aria-label", "Clear saved seat comparison");
6857
+ clear.addEventListener("click", () => this.clearView3dComparison());
6858
+ chip.append(main2, clear);
6859
+ overlay.appendChild(chip);
6860
+ this.view3dCompareChip = chip;
6861
+ }
6862
+ const count = this.view3dCompareSeatIds.length;
6863
+ const main = chip.querySelector(".main");
6864
+ if (main) {
6865
+ main.textContent = count > 1 ? `Compare ${count}` : "1 seat saved";
6866
+ main.setAttribute("aria-label", count > 1 ? `Open comparison of ${count} seats` : "One seat saved; choose another to compare");
6867
+ }
6868
+ }
6869
+ view3dComparisonSnapshot(seatId) {
6870
+ const seat = this.allSeats().find((candidate) => candidate.id === seatId);
6871
+ if (!seat) return null;
6872
+ const details = this.controller.seatDetails(seat.id);
6873
+ const cat = this.controller.doc?.categories.find((candidate) => candidate.key === seat.categoryKey);
6874
+ const chartPrice = details?.price ?? (cat?.tiers?.length ? cat.tiers[0].price : cat?.price);
6875
+ const price = chartPrice != null ? this.paidPrice(seat.categoryKey, details?.tierId ?? cat?.tiers?.[0]?.id ?? null, chartPrice) : void 0;
6876
+ const status = this.controller.getStatus(seat.id);
6877
+ const availability = status === "held" ? this.tf("picker.temporarilyHeld", "Temporarily held") : status === "booked" ? this.tf("picker.sold", "Sold") : status === "not_for_sale" ? this.tf("picker.notForSale", "Not for sale") : this.tf("picker.available", "Available");
6878
+ const viewSource = seat.viewUrl ? seatViewDisclosure({
6879
+ url: seat.viewUrl,
6880
+ ...seat.viewMeta?.coverage ? { coverage: seat.viewMeta.coverage } : {},
6881
+ ...seat.viewMeta?.capturedAt ? { capturedAt: seat.viewMeta.capturedAt } : {},
6882
+ ...seat.viewMeta?.sourceLabel ? { sourceLabel: seat.viewMeta.sourceLabel } : {}
6883
+ }) : this.tf("picker.chartDerivedSeatEye", "Live 3D \xB7 chart-derived seat-eye \xB7 not surveyed");
6884
+ const limited = this.limitedViewLabel(seat.commercial) || this.tf("picker.noAuthoredRestriction", "No organizer-authored restriction");
6885
+ const accessibility = details?.wheelchairSpaceType ? `${this.wheelchairProvisionLabel(details.wheelchairSpaceType)} \xB7 metadata, not access certification` : this.tf("picker.noAccessibilityMetadata", "No accessibility metadata supplied");
6886
+ const confidence = seatConfidenceDisclosure(seat.confidenceEvidence);
6887
+ return {
6888
+ seat,
6889
+ label: details?.displayLabel ?? seat.displayLabel ?? seat.label,
6890
+ section: details?.sectionLabel ?? seat.sectionId ?? "\u2014",
6891
+ row: this.rowShort(details) ?? details?.rowLabel ?? "\u2014",
6892
+ category: details?.categoryLabel ?? cat?.label ?? seat.categoryKey,
6893
+ price: price == null ? this.tf("picker.priceNotSupplied", "Not supplied") : this.money(price),
6894
+ availability,
6895
+ selectable: status == null || status === "free",
6896
+ viewSource,
6897
+ limited,
6898
+ accessibility,
6899
+ confidence
6900
+ };
6901
+ }
6902
+ openSeatConfidencePassport(seat, returnFocus = null) {
6903
+ const overlay = this.view3dEl;
6904
+ if (!overlay) return;
6905
+ this.closeSeatConfidencePassport(false);
6906
+ const disclosure = seatConfidenceDisclosure(seat.confidenceEvidence);
6907
+ const evidence = seat.confidenceEvidence;
6908
+ const details = this.controller.seatDetails(seat.id);
6909
+ const safe = (value) => this.escCx(value);
6910
+ const limitations = disclosure.limitations.length ? `<h4>Known limits</h4><ul>${disclosure.limitations.map((item) => `<li>${safe(item)}</li>`).join("")}</ul>` : "";
6911
+ const modeledTarget = disclosure.modeledTarget ? `<div><dt>Modeled target</dt><dd>${safe(disclosure.modeledTarget)}</dd></div>` : "";
6912
+ const evidenceRows = evidence ? `<div><dt>Evidence ID</dt><dd>${safe(evidence.evidenceId)}</dd></div><div><dt>Model version</dt><dd>${safe(evidence.modelVersion)}</dd></div><div><dt>Event configuration</dt><dd>${safe(evidence.eventConfigurationId ?? "Not configuration-specific")}</dd></div><div><dt>Approval</dt><dd>${safe(evidence.approvedByRole ?? "No external approval supplied")}</dd></div>` + (evidence.validUntil ? `<div><dt>Valid until</dt><dd>${safe(evidence.validUntil.slice(0, 10))}</dd></div>` : "") : `<div><dt>Evidence ID</dt><dd>None supplied</dd></div>`;
6913
+ const restriction = this.limitedViewLabel(seat.commercial) || this.tf("picker.noAuthoredRestriction", "No organizer-authored restriction");
6914
+ const commercialRows = `<div><dt>View restriction</dt><dd>${safe(restriction)}</dd></div>` + (seat.commercial?.note ? `<div><dt>Organizer note</dt><dd>${safe(seat.commercial.note)}</dd></div>` : "");
6915
+ const shell = document.createElement("div");
6916
+ shell.className = "sl-view3d-passport-shell";
6917
+ shell.innerHTML = `<div class="sl-view3d-passport-scrim" aria-hidden="true"></div><section class="sl-view3d-passport" role="dialog" aria-modal="true" aria-labelledby="sl-view3d-passport-title"><header><div><span>Seat confidence passport</span><strong id="sl-view3d-passport-title">${safe(details?.displayLabel ?? seat.displayLabel ?? seat.label)}</strong></div><button type="button" data-close aria-label="Close seat confidence passport">\xD7</button></header><div class="sl-view3d-passport-summary"><strong>${safe(disclosure.headline)}</strong><span>${safe(disclosure.coverage)} \xB7 ${safe(disclosure.freshness)}</span></div><dl><div><dt>Model status</dt><dd>${safe(disclosure.model)}</dd></div><div><dt>Reality evidence</dt><dd>${safe(disclosure.reality)}</dd></div><div><dt>Source</dt><dd>${safe(disclosure.provenance)}</dd></div>` + commercialRows + modeledTarget + evidenceRows + `</dl>${limitations}<p class="sl-view3d-passport-note">This passport describes supplied evidence and known limits. It does not guarantee that every temporary obstruction or real-world condition is knowable before the event build.</p></section>`;
6918
+ const background = [.../* @__PURE__ */ new Set([
6919
+ ...overlay.children,
6920
+ ...[...this.els.map.children].filter((element) => element !== overlay)
6921
+ ])].filter((element) => element instanceof HTMLElement);
6922
+ const prior = background.map((element) => ({
6923
+ element,
6924
+ inert: element.inert,
6925
+ ariaHidden: element.getAttribute("aria-hidden")
6926
+ }));
6927
+ for (const element of background) {
6928
+ element.inert = true;
6929
+ element.setAttribute("aria-hidden", "true");
6930
+ }
6931
+ overlay.appendChild(shell);
6932
+ overlay.classList.add("has-passport");
6933
+ this.view3dPassportEl = shell;
6934
+ const dialog = shell.querySelector(".sl-view3d-passport");
6935
+ const controls = () => [...dialog.querySelectorAll('button:not(:disabled),[href],[tabindex]:not([tabindex="-1"])')];
6936
+ const onKey = (event) => {
6937
+ if (event.key === "Escape") {
6938
+ event.preventDefault();
6939
+ event.stopPropagation();
6940
+ event.stopImmediatePropagation();
6941
+ this.closeSeatConfidencePassport();
6942
+ return;
6943
+ }
6944
+ if (event.key !== "Tab") return;
6945
+ const focusable = controls();
6946
+ if (!focusable.length) return;
6947
+ const first = focusable[0];
6948
+ const last = focusable[focusable.length - 1];
6949
+ if (event.shiftKey && document.activeElement === first) {
6950
+ event.preventDefault();
6951
+ last.focus();
6952
+ } else if (!event.shiftKey && document.activeElement === last) {
6953
+ event.preventDefault();
6954
+ first.focus();
6955
+ }
6956
+ };
6957
+ window.addEventListener("keydown", onKey, true);
6958
+ this.view3dPassportCleanup = () => {
6959
+ window.removeEventListener("keydown", onKey, true);
6960
+ for (const state of prior) {
6961
+ state.element.inert = state.inert;
6962
+ if (state.ariaHidden === null) state.element.removeAttribute("aria-hidden");
6963
+ else state.element.setAttribute("aria-hidden", state.ariaHidden);
6964
+ }
6965
+ const returnCandidate = returnFocus?.isConnected ? returnFocus : this.confirmEl?.querySelector(".sl-confirm-confidence") ?? this.view3dCompareEl?.querySelector("[data-passport-seat]") ?? null;
6966
+ const returnSurface = returnCandidate?.closest(".sl-confirm,.sl-view3d-compare");
6967
+ if (returnSurface?.isConnected) {
6968
+ returnSurface.inert = false;
6969
+ returnSurface.removeAttribute("aria-hidden");
6970
+ }
6971
+ shell.remove();
6972
+ overlay.classList.remove("has-passport");
6973
+ if (this.view3dPassportEl === shell) this.view3dPassportEl = null;
6974
+ const fallback = returnCandidate ?? this.view3dCompareEl?.querySelector("[data-passport-seat]") ?? this.confirmEl?.querySelector(".sl-confirm-confidence") ?? this.view3dCompareChip?.querySelector(".main");
6975
+ (returnFocus?.isConnected ? returnFocus : fallback)?.focus();
6976
+ };
6977
+ shell.addEventListener("click", (event) => {
6978
+ const target = event.target instanceof HTMLElement ? event.target : null;
6979
+ if (target?.closest("[data-close]") || target?.classList.contains("sl-view3d-passport-scrim")) {
6980
+ this.closeSeatConfidencePassport();
6981
+ }
6982
+ });
6983
+ requestAnimationFrame(() => controls()[0]?.focus());
6984
+ this.emit3dAnalytics("3d_confidence_passport_opened", {
6985
+ seatId: seat.id,
6986
+ evidenceId: evidence?.evidenceId ?? null,
6987
+ eventConfigurationId: evidence?.eventConfigurationId ?? null,
6988
+ modelLevel: evidence?.modelLevel ?? "unverified",
6989
+ realityLevel: evidence?.realityLevel ?? "none"
6990
+ });
6991
+ }
6992
+ closeSeatConfidencePassport(restoreFocus = true) {
6993
+ const cleanup = this.view3dPassportCleanup;
6994
+ this.view3dPassportCleanup = null;
6995
+ if (!cleanup) {
6996
+ this.view3dPassportEl?.remove();
6997
+ this.view3dPassportEl = null;
6998
+ this.view3dEl?.classList.remove("has-passport");
6999
+ return;
7000
+ }
7001
+ if (!restoreFocus) (document.activeElement instanceof HTMLElement ? document.activeElement : null)?.blur();
7002
+ cleanup();
7003
+ if (!restoreFocus) this.root?.focus({ preventScroll: true });
7004
+ }
7005
+ openView3dComparison() {
7006
+ const overlay = this.view3dEl;
7007
+ if (!overlay || this.view3dCompareSeatIds.length < 2) return;
7008
+ this.closeView3dComparison(false);
7009
+ const snapshots = this.view3dCompareSeatIds.map((seatId) => this.view3dComparisonSnapshot(seatId)).filter((value) => !!value);
7010
+ if (snapshots.length < 2) {
7011
+ this.clearView3dComparison();
7012
+ return;
7013
+ }
7014
+ const safe = (value) => this.escCx(value);
7015
+ const shell = document.createElement("div");
7016
+ shell.className = "sl-view3d-compare-shell";
7017
+ const cards = snapshots.map((snapshot, index) => `<article><span>Seat ${index === 0 ? "A" : "B"}</span><strong>${safe(snapshot.label)}</strong><small>Section ${safe(snapshot.section)} \xB7 Row ${safe(snapshot.row)}</small><dl><div><dt>Current price</dt><dd>${safe(snapshot.price)}</dd></div><div><dt>Ticket type</dt><dd>${safe(snapshot.category)}</dd></div><div><dt>Availability</dt><dd>${safe(snapshot.availability)}</dd></div><div><dt>View source</dt><dd>${safe(snapshot.viewSource)}</dd></div><div><dt>View restriction</dt><dd>${safe(snapshot.limited)}</dd></div><div><dt>Seat confidence</dt><dd>${safe(snapshot.confidence.headline)}</dd></div><div><dt>Reality check</dt><dd>${safe(snapshot.confidence.reality)}</dd></div><div><dt>Accessibility</dt><dd>${safe(snapshot.accessibility)}</dd></div></dl><div class="sl-view3d-compare-actions"><button type="button" data-passport-seat="${safe(snapshot.seat.id)}">Passport</button><button type="button" data-view-seat="${safe(snapshot.seat.id)}">View seat</button><button type="button" class="select" data-select-seat="${safe(snapshot.seat.id)}"${snapshot.selectable ? "" : " disabled"}>Select seat</button></div></article>`).join("");
7018
+ shell.innerHTML = `<div class="sl-view3d-compare-scrim" aria-hidden="true"></div><section class="sl-view3d-compare" role="dialog" aria-modal="true" aria-labelledby="sl-view3d-compare-title"><header><div><span>Seat inspection</span><strong id="sl-view3d-compare-title">Compare disclosed attributes</strong></div><button type="button" data-close aria-label="Close seat comparison">\xD7</button></header><p class="sl-view3d-compare-note">Current price and availability come from this picker. Modeled views are chart-derived unless organizer media is labeled; SeatLayer does not invent why a seat has its price.</p><div class="sl-view3d-compare-grid">${cards}</div></section>`;
7019
+ const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
7020
+ const background = [...overlay.children].filter((element) => element instanceof HTMLElement);
7021
+ const prior = background.map((element) => ({
7022
+ element,
7023
+ inert: element.inert,
7024
+ ariaHidden: element.getAttribute("aria-hidden")
7025
+ }));
7026
+ for (const element of background) {
7027
+ element.inert = true;
7028
+ element.setAttribute("aria-hidden", "true");
7029
+ }
7030
+ overlay.appendChild(shell);
7031
+ overlay.classList.add("has-comparison");
7032
+ this.view3dCompareEl = shell;
7033
+ const dialog = shell.querySelector(".sl-view3d-compare");
7034
+ const controls = () => [...dialog.querySelectorAll('button:not(:disabled),[href],[tabindex]:not([tabindex="-1"])')];
7035
+ const onKey = (event) => {
7036
+ if (event.key === "Escape") {
7037
+ event.preventDefault();
7038
+ this.closeView3dComparison();
7039
+ return;
7040
+ }
7041
+ if (event.key !== "Tab") return;
7042
+ const focusable = controls();
7043
+ if (!focusable.length) return;
7044
+ const first = focusable[0];
7045
+ const last = focusable[focusable.length - 1];
7046
+ if (event.shiftKey && document.activeElement === first) {
7047
+ event.preventDefault();
7048
+ last.focus();
7049
+ } else if (!event.shiftKey && document.activeElement === last) {
7050
+ event.preventDefault();
7051
+ first.focus();
7052
+ }
7053
+ };
7054
+ window.addEventListener("keydown", onKey);
7055
+ this.view3dCompareCleanup = () => {
7056
+ window.removeEventListener("keydown", onKey);
7057
+ for (const state of prior) {
7058
+ state.element.inert = state.inert;
7059
+ if (state.ariaHidden === null) state.element.removeAttribute("aria-hidden");
7060
+ else state.element.setAttribute("aria-hidden", state.ariaHidden);
7061
+ }
7062
+ shell.remove();
7063
+ overlay.classList.remove("has-comparison");
7064
+ if (this.view3dCompareEl === shell) this.view3dCompareEl = null;
7065
+ if (previousFocus?.isConnected) previousFocus.focus();
7066
+ else this.view3dCompareChip?.querySelector(".main")?.focus();
7067
+ };
7068
+ shell.querySelector("[data-close]")?.addEventListener("click", () => this.closeView3dComparison());
7069
+ shell.querySelectorAll("[data-passport-seat]").forEach((button) => button.addEventListener("click", () => {
7070
+ const seatId = button.dataset.passportSeat;
7071
+ const seat = seatId ? this.allSeats().find((candidate) => candidate.id === seatId) : void 0;
7072
+ if (seat) this.openSeatConfidencePassport(seat, button);
7073
+ }));
7074
+ shell.querySelectorAll("[data-view-seat]").forEach((button) => button.addEventListener("click", () => {
7075
+ const seatId = button.dataset.viewSeat;
7076
+ this.closeView3dComparison(false);
7077
+ if (seatId) void this.view3dHandle?.flyToSeat(seatId);
7078
+ }));
7079
+ shell.querySelectorAll("[data-select-seat]").forEach((button) => button.addEventListener("click", () => {
7080
+ const seatId = button.dataset.selectSeat;
7081
+ if (seatId) this.selectComparedSeat(seatId);
7082
+ }));
7083
+ requestAnimationFrame(() => controls()[0]?.focus());
7084
+ this.emit3dAnalytics("3d_comparison_opened", { seatIds: this.view3dCompareSeatIds.slice() });
7085
+ }
7086
+ closeView3dComparison(restoreFocus = true) {
7087
+ const cleanup = this.view3dCompareCleanup;
7088
+ this.view3dCompareCleanup = null;
7089
+ if (!cleanup) {
7090
+ this.view3dCompareEl?.remove();
7091
+ this.view3dCompareEl = null;
7092
+ this.view3dEl?.classList.remove("has-comparison");
7093
+ return;
7094
+ }
7095
+ if (!restoreFocus) {
7096
+ const active = document.activeElement instanceof HTMLElement ? document.activeElement : null;
7097
+ active?.blur();
7098
+ }
7099
+ cleanup();
7100
+ if (!restoreFocus) this.root?.focus({ preventScroll: true });
7101
+ }
7102
+ selectComparedSeat(seatId) {
7103
+ if (this.salesClosed) {
7104
+ this.toast(this.tf("picker.salesClosedToast", "Sales are closed for this event."), "warning");
7105
+ return;
7106
+ }
7107
+ const seat = this.allSeats().find((candidate) => candidate.id === seatId);
7108
+ if (!seat) return;
7109
+ this.closeView3dComparison(false);
7110
+ const added = this.controller.select([seatId]);
7111
+ if (!added.length) {
7112
+ this.syncSelectionTo3d();
7113
+ this.toast(this.tf("picker.seatNoLongerAvailable", "That seat is no longer available."), "warning");
7114
+ return;
7115
+ }
7116
+ this.syncSelectionTo3d();
7117
+ this.showConfirm(seat);
7118
+ this.emit3dAnalytics("3d_comparison_selected", { seatId });
6492
7119
  }
6493
7120
  /**
6494
7121
  * The venue-navigation rail inside 3D: levels and areas.
@@ -6517,6 +7144,25 @@ var SeatPicker = class _SeatPicker {
6517
7144
  if (!wantFloors && !wantZones && !wantSections && !wantLocator) return;
6518
7145
  const nav = document.createElement("div");
6519
7146
  nav.className = "sl-view3d-nav";
7147
+ const finderToggle = document.createElement("button");
7148
+ finderToggle.type = "button";
7149
+ finderToggle.className = "sl-view3d-nav-toggle";
7150
+ finderToggle.setAttribute("aria-expanded", "false");
7151
+ const setFinderOpen = (open) => {
7152
+ nav.classList.toggle("is-open", open);
7153
+ finderToggle.setAttribute("aria-expanded", String(open));
7154
+ finderToggle.textContent = open ? "Close seat finder" : "Find a seat";
7155
+ };
7156
+ finderToggle.addEventListener("click", () => setFinderOpen(!nav.classList.contains("is-open")));
7157
+ nav.addEventListener("keydown", (event) => {
7158
+ if (event.key !== "Escape" || !nav.classList.contains("is-open")) return;
7159
+ event.preventDefault();
7160
+ event.stopPropagation();
7161
+ setFinderOpen(false);
7162
+ finderToggle.focus();
7163
+ });
7164
+ setFinderOpen(false);
7165
+ nav.appendChild(finderToggle);
6520
7166
  if (wantFloors) {
6521
7167
  const row = document.createElement("div");
6522
7168
  row.setAttribute("role", "group");
@@ -6534,7 +7180,10 @@ var SeatPicker = class _SeatPicker {
6534
7180
  b.textContent = label;
6535
7181
  b.dataset.floor = index === null ? "" : String(index);
6536
7182
  b.setAttribute("aria-pressed", String(index === null));
6537
- b.addEventListener("click", () => select(index));
7183
+ b.addEventListener("click", () => {
7184
+ select(index);
7185
+ setFinderOpen(false);
7186
+ });
6538
7187
  pills.push(b);
6539
7188
  row.appendChild(b);
6540
7189
  };
@@ -6573,7 +7222,10 @@ var SeatPicker = class _SeatPicker {
6573
7222
  const b = document.createElement("button");
6574
7223
  b.type = "button";
6575
7224
  b.textContent = e.label;
6576
- b.addEventListener("click", e.go);
7225
+ b.addEventListener("click", () => {
7226
+ e.go();
7227
+ setFinderOpen(false);
7228
+ });
6577
7229
  row.appendChild(b);
6578
7230
  }
6579
7231
  }
@@ -6592,7 +7244,7 @@ var SeatPicker = class _SeatPicker {
6592
7244
  seatSelect.setAttribute("aria-label", "Choose seat in 3D");
6593
7245
  const view = document.createElement("button");
6594
7246
  view.type = "button";
6595
- view.textContent = "View seat";
7247
+ view.textContent = "Inspect seat";
6596
7248
  view.disabled = true;
6597
7249
  const fill = (select, placeholder, entries) => {
6598
7250
  select.replaceChildren();
@@ -6648,11 +7300,13 @@ var SeatPicker = class _SeatPicker {
6648
7300
  seatSelect.addEventListener("change", () => {
6649
7301
  const seatId = seatSelect.value;
6650
7302
  view.disabled = !seatId;
6651
- handle.setSelection(seatId ? [seatId] : []);
6652
7303
  });
6653
7304
  view.addEventListener("click", () => {
6654
7305
  const seatId = seatSelect.value;
6655
- if (seatId) void handle.flyToSeat(seatId);
7306
+ if (seatId) {
7307
+ setFinderOpen(false);
7308
+ this.onView3dSeatPick(seatId);
7309
+ }
6656
7310
  });
6657
7311
  locator.append(sectionSelect, rowSelect, seatSelect, view);
6658
7312
  nav.appendChild(locator);
@@ -6664,6 +7318,7 @@ var SeatPicker = class _SeatPicker {
6664
7318
  const doc = this.controller.doc;
6665
7319
  if (!doc) return;
6666
7320
  this.buyerView = "venue3d";
7321
+ this.view3dTargetSeatId = null;
6667
7322
  this.opts.onBuyerViewChange?.({ view: "venue3d", ...flySeatId ? { seatId: flySeatId } : {} });
6668
7323
  this.root?.setAttribute("data-view3d", "on");
6669
7324
  this.dismissConfirm();
@@ -6676,7 +7331,22 @@ var SeatPicker = class _SeatPicker {
6676
7331
  back.type = "button";
6677
7332
  back.className = "sl-view3d-back";
6678
7333
  back.innerHTML = `<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M15 18l-6-6 6-6"/></svg><span>${this.tf("picker.backToMap", "Back to map")}</span>`;
6679
- back.addEventListener("click", () => this.exit3d());
7334
+ const backLabel = back.querySelector("span");
7335
+ const setJourneyTarget = (seatId) => {
7336
+ this.view3dTargetSeatId = seatId;
7337
+ const atSeat = !!seatId;
7338
+ overlay.classList.toggle("is-seat-focused", atSeat);
7339
+ const label = atSeat ? this.tf("picker.backToVenue", "Back to venue") : this.tf("picker.backToMap", "Back to map");
7340
+ if (backLabel) backLabel.textContent = label;
7341
+ back.setAttribute("aria-label", label);
7342
+ };
7343
+ back.addEventListener("click", () => {
7344
+ if (this.view3dTargetSeatId && this.view3dHandle) {
7345
+ this.view3dHandle.focusOverview();
7346
+ return;
7347
+ }
7348
+ this.exit3d();
7349
+ });
6680
7350
  overlay.appendChild(back);
6681
7351
  const fullscreen = document.createElement("button");
6682
7352
  fullscreen.type = "button";
@@ -6694,6 +7364,7 @@ var SeatPicker = class _SeatPicker {
6694
7364
  overlay.appendChild(loading);
6695
7365
  this.els.map.appendChild(overlay);
6696
7366
  this.view3dEl = overlay;
7367
+ this.syncView3dCompareChip();
6697
7368
  requestAnimationFrame(() => {
6698
7369
  overlay.style.opacity = "1";
6699
7370
  });
@@ -6709,7 +7380,13 @@ var SeatPicker = class _SeatPicker {
6709
7380
  // phone. The bounded portrait fit was validated against every UX-lab
6710
7381
  // fixture; keep editor previews strict while making buyer seats legible.
6711
7382
  portraitOverviewCrop: true,
7383
+ // Premium buyer mode lands at the modeled seated-eye and locks the
7384
+ // venue orbit there. Explicit look-around rotates at that same origin;
7385
+ // zooming cannot escape through the shell or reveal backstage geometry.
7386
+ arriveAtSeatEye: true,
7387
+ seatViewActionLabel: (seatId) => this.allSeats().find((seat) => seat.id === seatId)?.viewUrl ? this.tf("picker.openAuthored360", "Open venue 360\xB0") : this.tf("picker.lookAroundLive3d", "Look around in live 3D"),
6712
7388
  onSeatPick: (id) => this.onView3dSeatPick(id),
7389
+ onSeatInspect: (id) => this.onView3dSeatPick(id),
6713
7390
  onSectionFocusChange: (sectionId) => {
6714
7391
  const select = overlay.querySelector(
6715
7392
  'select[aria-label="Choose section in 3D"]'
@@ -6721,6 +7398,7 @@ var SeatPicker = class _SeatPicker {
6721
7398
  },
6722
7399
  onViewTargetChange: (seatId) => {
6723
7400
  overlay.querySelector(".sl-view3d-nav")?.classList.toggle("is-seat-focused", !!seatId);
7401
+ setJourneyTarget(seatId);
6724
7402
  this.opts.onBuyerViewChange?.({
6725
7403
  view: "venue3d",
6726
7404
  ...seatId ? { seatId } : {}
@@ -6761,8 +7439,13 @@ var SeatPicker = class _SeatPicker {
6761
7439
  if (this.buyerView !== "venue3d" && !this.view3dEl) return;
6762
7440
  this.view3dGen++;
6763
7441
  this.buyerView = "map";
7442
+ this.view3dTargetSeatId = null;
6764
7443
  this.opts.onBuyerViewChange?.({ view: "map" });
6765
7444
  this.root?.removeAttribute("data-view3d");
7445
+ this.closeSeatConfidencePassport(false);
7446
+ this.closeView3dComparison(false);
7447
+ this.view3dCompareChip?.remove();
7448
+ this.view3dCompareChip = null;
6766
7449
  try {
6767
7450
  this.view3dHandle?.dispose();
6768
7451
  } catch {
@@ -7048,7 +7731,8 @@ var SeatPicker = class _SeatPicker {
7048
7731
  if (this.modalScrim) {
7049
7732
  this.modalScrim.remove();
7050
7733
  this.modalScrim = null;
7051
- this.prevFocus?.focus?.();
7734
+ if (this.prevFocus?.isConnected) this.prevFocus.focus({ preventScroll: true });
7735
+ this.prevFocus = null;
7052
7736
  }
7053
7737
  }
7054
7738
  };
@@ -7139,46 +7823,17 @@ function attachPickerFrame(iframe, opts = {}) {
7139
7823
  };
7140
7824
  }
7141
7825
  export {
7142
- ACCESS_LINK_DEFAULTS,
7143
7826
  ApiError,
7144
7827
  BuyerAccessContext,
7145
7828
  BuyerAccessUnavailableError,
7146
7829
  BuyerRealtimeClient,
7147
- ChannelsMode,
7148
7830
  EmbeddedDesigner,
7149
- ManageApi,
7150
- ManageApiError,
7151
- PUBLIC_CHANNEL_ID,
7152
- PUBLIC_CHANNEL_NAME,
7153
- SeatManager,
7154
7831
  SeatPicker,
7155
7832
  SeatingChart,
7156
- accessIntentDescription,
7157
- accessIntentLabel,
7158
- accessLine,
7159
- accessLinkBadge,
7160
- accessLinkErrorCopy,
7161
- accessLinkIsLive,
7162
- accessLinkPolicyLines,
7163
7833
  attachPickerFrame,
7164
- bucketRows,
7165
- bucketRowsHtml,
7166
7834
  createBuyerAccessContext,
7167
7835
  createControllerSink,
7168
- dropReviewRows,
7169
- intentForbidsCopy,
7170
- intentSwitchBlockedCopy,
7171
- isPublicChannelId,
7172
- markerLetter,
7173
- markerOf,
7174
- mutationCount,
7175
- needsMoveConfirmation,
7176
7836
  parseTicketOfferAvailability,
7177
- planAssignment,
7178
- retryAfterCopy,
7179
- selectionSources,
7180
- stateBadge,
7181
- suggestMarker,
7182
7837
  ticketOfferPrices
7183
7838
  };
7184
7839
  //# sourceMappingURL=index.js.map