@seatlayer/js 0.51.0 → 0.52.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.cjs CHANGED
@@ -2524,6 +2524,9 @@ var BuyerAssetObjectUrls = class {
2524
2524
  }
2525
2525
  };
2526
2526
 
2527
+ // src/SeatPicker.ts
2528
+ var import_minimapGeometry = require("@seatlayer/core/picker/minimapGeometry");
2529
+
2527
2530
  // src/offerAvailability.ts
2528
2531
  var SALE_STATES = [
2529
2532
  "on-sale",
@@ -2663,6 +2666,12 @@ function tfStatic(key, fallback) {
2663
2666
  const v = (0, import_core2.t)(key);
2664
2667
  return v === key ? fallback : v;
2665
2668
  }
2669
+ var chartsLoadedThisPage = /* @__PURE__ */ new Set();
2670
+ function markChartLoaded(eventKey) {
2671
+ const seen = chartsLoadedThisPage.has(eventKey);
2672
+ chartsLoadedThisPage.add(eventKey);
2673
+ return seen;
2674
+ }
2666
2675
  function pointInPolygon(x, y, poly) {
2667
2676
  let inside = false;
2668
2677
  for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
@@ -2765,9 +2774,13 @@ var CSS2 = (
2765
2774
  the WIDGET's meanings, not an org's brand: premium gold, environment
2766
2775
  warning, danger, success. Hosts can still override them with CSS. */
2767
2776
  --sl-premium:#e8c15a;--sl-premium-deep:#c9a24b;--sl-premium-ink:#1c1608;
2768
- --sl-warn:#d8a425;--sl-danger:#e5484d;--sl-success:#22a06b}
2777
+ --sl-premium-text:color-mix(in srgb,var(--sl-premium-deep) 48%,var(--sl-text));
2778
+ --sl-warn:#d8a425;--sl-warn-text:color-mix(in srgb,var(--sl-warn) 48%,var(--sl-text));
2779
+ --sl-danger:#e5484d;--sl-success:#22a06b}
2769
2780
  .sl-picker *{box-sizing:border-box;margin:0;padding:0}
2770
- .sl-picker button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
2781
+ /* Keep the reset intentionally specificity-free. Standalone map and panel
2782
+ controls must be able to establish their own token surfaces in either theme. */
2783
+ .sl-picker :where(button){font:inherit;color:inherit;background:none;border:0;cursor:pointer}
2771
2784
 
2772
2785
  /* header */
2773
2786
  .sl-head{display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--sl-line);flex:none}
@@ -2963,11 +2976,11 @@ var CSS2 = (
2963
2976
  .sl-offer{display:none;margin:12px 14px 2px;padding:12px;border:1px solid color-mix(in srgb,var(--sl-accent) 34%,var(--sl-line));
2964
2977
  border-radius:12px;background:color-mix(in srgb,var(--sl-accent) 8%,var(--sl-surface));color:var(--sl-text)}
2965
2978
  .sl-offer.has{display:block}.sl-offer-main{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
2966
- .sl-offer-copy{min-width:0}.sl-offer-kicker{display:block;font-size:10px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-accent)}
2979
+ .sl-offer-copy{min-width:0}.sl-offer-kicker{display:block;font-size:10px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-accent-text)}
2967
2980
  .sl-offer-name{display:block;margin-top:2px;font-size:13px;font-weight:800;line-height:1.3}.sl-offer-line{display:block;margin-top:3px;font-size:11px;line-height:1.35;color:var(--sl-muted)}
2968
2981
  .sl-offer-info{position:relative;flex:none}.sl-offer-info>summary{list-style:none;width:25px;height:25px;border:1px solid var(--sl-line);border-radius:999px;
2969
2982
  display:grid;place-items:center;cursor:pointer;font-size:12px;font-weight:850;color:var(--sl-text);background:var(--sl-surface)}
2970
- .sl-offer-info>summary::-webkit-details-marker{display:none}.sl-offer-info[open]>summary{border-color:var(--sl-accent);color:var(--sl-accent)}
2983
+ .sl-offer-info>summary::-webkit-details-marker{display:none}.sl-offer-info[open]>summary{border-color:var(--sl-accent);color:var(--sl-accent-text)}
2971
2984
  .sl-offer-detail{margin-top:10px;padding-top:9px;border-top:1px solid var(--sl-line);font-size:10.5px;line-height:1.45;color:var(--sl-muted)}
2972
2985
  .sl-sec{padding:14px 14px 4px;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
2973
2986
  .sl-prices-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
@@ -2985,8 +2998,8 @@ var CSS2 = (
2985
2998
  padding:0 6px;margin:0 -6px;border-radius:8px;cursor:pointer;transition:background .15s}
2986
2999
  .sl-price-row:hover,.sl-price-row:focus-visible{background:color-mix(in srgb,var(--sl-line) 40%,transparent)}
2987
3000
  .sl-price-row.sl-active{background:color-mix(in srgb,var(--sl-accent) 9%,transparent)}
2988
- .sl-price-was{margin-left:auto;color:var(--sl-muted);font-size:10px;text-decoration:line-through}.sl-price-offer{display:block;color:var(--sl-accent);font-size:10px;font-weight:750}
2989
- .sl-price-row.sl-active .sl-price-label{color:var(--sl-accent)}
3001
+ .sl-price-was{margin-left:auto;color:var(--sl-muted);font-size:10px;text-decoration:line-through}.sl-price-offer{display:block;color:var(--sl-accent-text);font-size:10px;font-weight:750}
3002
+ .sl-price-row.sl-active .sl-price-label{color:var(--sl-accent-text)}
2990
3003
  .sl-dot{width:9px;height:9px;border-radius:50%;flex:none}
2991
3004
  .sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
2992
3005
  .sl-price-left{font-size:11px;color:var(--sl-muted);font-variant-numeric:tabular-nums}
@@ -3021,7 +3034,7 @@ var CSS2 = (
3021
3034
  box-shadow:inset 3px 0 0 color-mix(in srgb,var(--sl-accent) 72%,transparent)}
3022
3035
  .sl-ticket-state{width:17px;height:17px;border-radius:999px;flex:none;display:flex;align-items:center;justify-content:center;
3023
3036
  background:var(--sl-accent);color:var(--sl-accent-ink)}
3024
- .sl-ticket-state.held{background:color-mix(in srgb,var(--sl-accent) 18%,var(--sl-surface));color:var(--sl-accent)}
3037
+ .sl-ticket-state.held{background:color-mix(in srgb,var(--sl-accent) 18%,var(--sl-surface));color:var(--sl-accent-text)}
3025
3038
  .sl-ticket-state svg{width:10px;height:10px;stroke:currentColor;stroke-width:2.6;fill:none;stroke-linecap:round;stroke-linejoin:round}
3026
3039
  .sl-chip-main{min-width:0;padding:8px 10px 8px 11px;display:flex;flex-direction:column;justify-content:center;gap:5px}
3027
3040
  .sl-chip-id{display:flex;gap:12px;min-width:0}
@@ -3064,7 +3077,7 @@ var CSS2 = (
3064
3077
  .sl-ga-qty span{min-width:16px;text-align:center;font-weight:800;font-variant-numeric:tabular-nums}
3065
3078
 
3066
3079
  /* footer */
3067
- .sl-foot{position:relative;z-index:2;padding:12px 16px 14px;border-top:1px solid var(--sl-line);flex:none;
3080
+ .sl-foot{position:relative;z-index:2;padding:10px 16px 8px;border-top:1px solid var(--sl-line);flex:none;
3068
3081
  background:var(--sl-bg);box-shadow:0 -10px 24px -22px rgba(0,0,0,.72)}
3069
3082
  .sl-hold-note{display:none;align-items:center;gap:7px;margin-bottom:8px;padding:7px 8px;border-radius:var(--sl-r-sm);
3070
3083
  border:1px solid var(--sl-line);background:color-mix(in srgb,var(--sl-accent) 7%,var(--sl-surface));
@@ -3078,14 +3091,13 @@ var CSS2 = (
3078
3091
  .sl-hold-note>span{flex:1;min-width:0}
3079
3092
  .sl-hold-change{flex:none;min-height:30px;padding:5px 8px;border-radius:8px;border:1px solid var(--sl-line);
3080
3093
  color:var(--sl-text);font-size:10.5px;font-weight:750;white-space:nowrap}
3081
- .sl-hold-change:hover,.sl-hold-change:focus-visible{border-color:var(--sl-accent);color:var(--sl-accent)}
3094
+ .sl-hold-change:hover,.sl-hold-change:focus-visible{border-color:var(--sl-accent);color:var(--sl-accent-text)}
3082
3095
  .sl-hold-change:disabled{opacity:.58;cursor:wait}
3083
3096
  .sl-total{display:flex;justify-content:space-between;align-items:center;font-size:13px;margin-bottom:10px}
3084
3097
  .sl-total b{font-size:17px;font-variant-numeric:tabular-nums}
3085
3098
  .sl-value-pop{animation:slValuePop .32s cubic-bezier(.2,.8,.2,1)}
3086
- /* Primary checkout CTA. Scoped under .sl-picker so it OUTWEIGHS the
3087
- '.sl-picker button' reset (0,1,1) \u2014 an unscoped '.sl-cta' (0,1,0) loses to it
3088
- and the button renders as plain text with no accent fill. */
3099
+ /* Primary checkout CTA. The extra root scope documents that this is widget
3100
+ chrome and keeps the contract resilient when embedded beside host styles. */
3089
3101
  .sl-picker .sl-cta{display:flex;align-items:center;justify-content:center;width:100%;min-height:44px;
3090
3102
  padding:12px 16px;border-radius:var(--sl-r-sm);font-weight:800;font-size:14px;line-height:1.1;
3091
3103
  background:var(--sl-accent);color:var(--sl-accent-ink);
@@ -3096,8 +3108,8 @@ var CSS2 = (
3096
3108
  .sl-cta-spin,.sl-ba-spin{width:14px;height:14px;border-radius:50%;border:2px solid currentColor;border-right-color:transparent;
3097
3109
  animation:slspin .7s linear infinite;flex:none}
3098
3110
  /* Disabled ("Select seats"): quieter, but still a full-width button shape. */
3099
- .sl-picker .sl-cta:disabled{background:var(--sl-surface);color:var(--sl-muted);opacity:1;
3100
- cursor:not-allowed;filter:none;transform:none}
3111
+ .sl-picker .sl-cta:disabled{background:color-mix(in srgb,var(--sl-bg) 35%,var(--sl-surface));color:var(--sl-muted);
3112
+ box-shadow:inset 0 0 0 1px var(--sl-line);opacity:1;cursor:not-allowed;filter:none;transform:none}
3101
3113
 
3102
3114
  /* Chrome anchor regions (Feature 6) \u2014 every interactive map overlay is APPENDED
3103
3115
  INTO one of these positioned flex containers and flows/stacks within it, so no
@@ -3127,7 +3139,7 @@ var CSS2 = (
3127
3139
  in the same region, so no displacement hack is needed. */
3128
3140
  .sl-testbadge{display:inline-flex;align-items:center;padding:5px 10px;border-radius:7px;pointer-events:none;
3129
3141
  font-size:10px;font-weight:850;letter-spacing:.13em;line-height:1.2;text-transform:uppercase;white-space:nowrap;
3130
- color:var(--sl-warn);background:color-mix(in srgb,var(--sl-warn) 13%,var(--sl-surface));
3142
+ color:var(--sl-warn-text);background:color-mix(in srgb,var(--sl-warn) 13%,var(--sl-surface));
3131
3143
  border:1px dashed color-mix(in srgb,var(--sl-warn) 55%,transparent)}
3132
3144
 
3133
3145
  /* zoom column (flows within the bottom-right region) */
@@ -3194,7 +3206,7 @@ var CSS2 = (
3194
3206
  .sl-extend-btn{flex:none;padding:8px 14px;border-radius:999px;font-weight:800;font-size:12.5px;
3195
3207
  background:var(--sl-accent);color:var(--sl-accent-ink);transition:filter .15s,opacity .15s}
3196
3208
  .sl-extend-btn:hover{filter:brightness(1.08)}
3197
- .sl-extend-btn:disabled{opacity:.5;cursor:not-allowed}
3209
+ .sl-extend-btn:disabled{opacity:.62;cursor:not-allowed}
3198
3210
 
3199
3211
  /* booked confirmation overlay (covers the widget once the held seats are sold) */
3200
3212
  .sl-booked{position:absolute;inset:0;z-index:11;display:flex;flex-direction:column;align-items:center;
@@ -3221,7 +3233,7 @@ var CSS2 = (
3221
3233
  justify-content:center;text-align:center;gap:8px;padding:24px;
3222
3234
  background:color-mix(in srgb,var(--sl-bg) 82%,transparent);backdrop-filter:blur(4px)}
3223
3235
  .sl-soldout.on{display:flex}
3224
- .sl-soldout-eyebrow{font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--sl-accent);font-weight:800}
3236
+ .sl-soldout-eyebrow{font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--sl-accent-text);font-weight:800}
3225
3237
  .sl-soldout-title{font-size:32px;font-weight:800;color:var(--sl-text);line-height:1.05}
3226
3238
  .sl-soldout-copy{max-width:360px;font-size:13px;color:var(--sl-muted);line-height:1.5}
3227
3239
 
@@ -3237,9 +3249,9 @@ var CSS2 = (
3237
3249
  /* "Powered by SeatLayer" attribution badge (side-panel foot) \u2014 the canonical
3238
3250
  Layered Rows mark + wordmark. Hidden when the host opts out or the org's paid
3239
3251
  theme sets hideBadge. */
3240
- .sl-powered{display:flex;align-items:center;justify-content:center;gap:6px;margin-top:10px;
3241
- font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--sl-text);opacity:.72;
3242
- text-decoration:none;padding:6px 10px;border-radius:999px;width:fit-content;margin-inline:auto;
3252
+ .sl-powered{display:flex;align-items:center;justify-content:center;gap:5px;margin-top:2px;
3253
+ font-size:11px;font-weight:600;letter-spacing:.02em;color:var(--sl-text);opacity:.72;
3254
+ text-decoration:none;padding:2px 8px;border-radius:999px;width:fit-content;margin-inline:auto;
3243
3255
  transition:opacity .15s ease,background-color .15s ease}
3244
3256
  .sl-powered:hover{opacity:1;background:color-mix(in srgb,var(--sl-text) 8%,transparent)}
3245
3257
  .sl-powered:focus-visible{opacity:1;outline:2px solid var(--sl-accent);outline-offset:2px}
@@ -3277,10 +3289,22 @@ var CSS2 = (
3277
3289
  .sl-confirm-field:first-child .sl-confirm-value{font-size:13.5px;line-height:1.25;white-space:normal;
3278
3290
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
3279
3291
  .sl-confirm-cat{display:flex;align-items:center;gap:8px;padding:10px 12px;background:color-mix(in srgb,var(--sl-cat) 76%,var(--sl-surface))}
3280
- .sl-confirm-cat .sl-dot{border:2px solid rgba(255,255,255,.78);width:11px;height:11px}
3281
- .sl-confirm-cat-name{font-size:13.5px;font-weight:800;color:#fff;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
3282
- .sl-confirm-price{font-size:17px;font-weight:850;color:#fff;font-variant-numeric:tabular-nums}
3292
+ .sl-confirm-cat .sl-dot{border:2px solid color-mix(in srgb,var(--sl-cat-ink) 78%,transparent);width:11px;height:11px}
3293
+ .sl-confirm-cat-name{font-size:13.5px;font-weight:800;color:var(--sl-cat-ink);flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
3294
+ .sl-confirm-price{font-size:17px;font-weight:850;color:var(--sl-cat-ink);font-variant-numeric:tabular-nums}
3283
3295
  .sl-confirm-body{padding:11px 12px 12px}
3296
+ .sl-confirm-tiers{display:grid;gap:6px;margin:0 0 9px;padding:0;border:0}
3297
+ .sl-confirm-tiers legend{margin:0 0 6px;padding:0;color:var(--sl-muted);font-size:10px;font-weight:800;letter-spacing:.12em;text-transform:uppercase}
3298
+ .sl-confirm-tier{display:flex;align-items:center;gap:9px;width:100%;min-height:40px;padding:8px 10px;border:1px solid var(--sl-line);
3299
+ border-radius:9px;color:var(--sl-text);background:color-mix(in srgb,var(--sl-bg) 72%,var(--sl-surface));text-align:left}
3300
+ .sl-confirm-tier:hover{border-color:color-mix(in srgb,var(--sl-accent) 58%,var(--sl-line))}
3301
+ .sl-confirm-tier[aria-pressed="true"]{border-color:var(--sl-accent);background:color-mix(in srgb,var(--sl-accent) 13%,var(--sl-surface));
3302
+ box-shadow:inset 3px 0 0 var(--sl-accent)}
3303
+ .sl-confirm-tier-copy{min-width:0;flex:1;display:grid;gap:2px}
3304
+ .sl-confirm-tier-name{min-width:0;font-size:12.5px;font-weight:800}
3305
+ .sl-confirm-tier-note,.sl-tier-note{color:var(--sl-muted);font-size:10.5px;line-height:1.3}
3306
+ .sl-confirm-tier[aria-pressed="true"] .sl-confirm-tier-note{color:var(--sl-text)}
3307
+ .sl-confirm-tier-price{font-size:13px;font-weight:850;font-variant-numeric:tabular-nums}
3284
3308
  .sl-confirm-row{display:flex;gap:8px;margin-top:10px}
3285
3309
  .sl-confirm-row button{flex:1;min-height:44px;padding:9px 12px;border-radius:9px;font-weight:800;font-size:13px}
3286
3310
  .sl-confirm-add{background:var(--sl-accent)!important;color:var(--sl-accent-ink)!important;display:flex;align-items:center;justify-content:center;gap:7px}
@@ -3298,6 +3322,7 @@ var CSS2 = (
3298
3322
  .sl-picker[data-layout="narrow"] .sl-confirm-cat-name{font-size:12.5px}
3299
3323
  .sl-picker[data-layout="narrow"] .sl-confirm-price{font-size:15px}
3300
3324
  .sl-picker[data-layout="narrow"] .sl-confirm-body{padding:9px 10px 10px}
3325
+ .sl-picker[data-layout="narrow"] .sl-confirm-tier{min-height:38px;padding:7px 9px}
3301
3326
  .sl-picker[data-layout="narrow"] .sl-confirm-thumbwrap{height:62px;margin-bottom:6px}
3302
3327
  .sl-picker[data-layout="narrow"] .sl-confirm-3d{margin-top:7px;padding:6px}
3303
3328
  .sl-picker[data-layout="narrow"] .sl-confirm-row{margin-top:8px}
@@ -3321,7 +3346,7 @@ var CSS2 = (
3321
3346
  .sl-table-stepper{display:grid;grid-template-columns:48px 1fr 48px;align-items:center;border:1px solid var(--sl-line);
3322
3347
  border-radius:12px;overflow:hidden;background:var(--sl-bg)}
3323
3348
  .sl-table-stepper button{height:48px;font-size:24px;font-weight:700;background:color-mix(in srgb,var(--sl-line) 34%,transparent)!important}
3324
- .sl-table-stepper button:disabled{opacity:.38;cursor:not-allowed}
3349
+ .sl-table-stepper button:disabled{opacity:.58;cursor:not-allowed}
3325
3350
  .sl-table-stepper output{text-align:center;font-size:19px;font-weight:850;font-variant-numeric:tabular-nums}
3326
3351
  .sl-table-range{margin-top:7px;color:var(--sl-muted);font-size:11px;text-align:center}
3327
3352
  .sl-table-actions{display:flex;gap:9px;margin-top:17px}.sl-table-actions button{flex:1;min-height:46px;border-radius:10px;font-weight:800}
@@ -3363,14 +3388,14 @@ var CSS2 = (
3363
3388
  .sl-ba::after{content:'\u2726';position:absolute;right:10px;top:3px;color:color-mix(in srgb,var(--sl-accent) 20%,transparent);font-size:42px;line-height:1}
3364
3389
  .sl-ba-title,.sl-ba-copy,.sl-ba select,.sl-ba-qty,.sl-ba-go{position:relative;z-index:1}
3365
3390
  .sl-ba-title{grid-column:1/-1;display:flex;align-items:center;gap:7px;font-size:13px;font-weight:850}
3366
- .sl-ba-title .spark{color:var(--sl-accent);font-size:16px}
3391
+ .sl-ba-title .spark{color:var(--sl-accent-text);font-size:16px}
3367
3392
  .sl-ba-copy{grid-column:1/-1;margin:-4px 0 2px 23px;color:var(--sl-muted);font-size:10.5px;line-height:1.35}
3368
3393
  .sl-ba-copy .narrow{display:none}
3369
3394
  /* "\u2605 Best seats" premium quick-pick \u2014 gold accent echoing the \u2605 Premium pill on
3370
3395
  the confirm popover; deliberately distinct from the accent-toned qty/go. */
3371
3396
  .sl-ba-premium{position:relative;z-index:1;grid-column:1/-1;justify-self:start;display:inline-flex;align-items:center;gap:6px;
3372
3397
  padding:6px 12px;border-radius:999px;font-size:11px;font-weight:800;letter-spacing:.02em;cursor:pointer;
3373
- color:var(--sl-premium-deep);background:color-mix(in srgb,var(--sl-premium) 10%,var(--sl-surface));
3398
+ color:var(--sl-premium-text);background:color-mix(in srgb,var(--sl-premium) 10%,var(--sl-surface));
3374
3399
  border:1px solid color-mix(in srgb,var(--sl-premium) 34%,var(--sl-line));transition:filter .15s,background .15s,color .15s}
3375
3400
  .sl-ba-premium .star{font-size:12px;line-height:1;color:var(--sl-premium)}
3376
3401
  .sl-ba-premium:hover{filter:brightness(1.05)}
@@ -3423,7 +3448,7 @@ var CSS2 = (
3423
3448
  back out of a reopened card. */
3424
3449
  .sl-ba-reopen{align-self:flex-start;display:inline-flex;align-items:center;gap:6px;min-height:32px;padding:5px 12px;margin-top:2px;
3425
3450
  border:1px dashed color-mix(in srgb,var(--sl-accent) 40%,var(--sl-line));border-radius:999px;
3426
- color:var(--sl-accent);font-size:11.5px;font-weight:750;transition:border-color .15s,background .15s}
3451
+ color:var(--sl-accent-text);font-size:11.5px;font-weight:750;transition:border-color .15s,background .15s}
3427
3452
  .sl-ba-reopen:hover,.sl-ba-reopen:focus-visible{background:color-mix(in srgb,var(--sl-accent) 8%,transparent)}
3428
3453
  .sl-ba-back{grid-column:1/-1;justify-self:start;min-height:30px;padding:3px 0;color:var(--sl-muted);
3429
3454
  font-size:11px;font-weight:700;text-decoration:underline;text-underline-offset:3px;transition:color .15s}
@@ -3545,7 +3570,7 @@ var CSS2 = (
3545
3570
  border:1px solid rgba(150,165,205,.35);backdrop-filter:blur(6px);cursor:pointer}
3546
3571
  .sl-view3d-nav button:hover,.sl-view3d-nav button:focus-visible{color:#eef1f8;border-color:rgba(190,205,240,.6)}
3547
3572
  .sl-view3d-nav button[aria-pressed="true"]{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
3548
- .sl-view3d-nav button:disabled{opacity:.45;cursor:not-allowed}
3573
+ .sl-view3d-nav button:disabled{opacity:.6;cursor:not-allowed}
3549
3574
  .sl-view3d-nav-toggle{display:none!important}
3550
3575
  .sl-view3d-nav select{min-height:38px;max-width:100%;padding:7px 34px 7px 12px;
3551
3576
  border-radius:999px;font:700 11.5px/1 inherit;color:#eef1f8;background:rgba(12,18,32,.86);
@@ -3593,7 +3618,7 @@ var CSS2 = (
3593
3618
  .sl-confirm-confidence strong,.sl-confirm-confidence small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
3594
3619
  .sl-confirm-confidence strong{font-size:11px}.sl-confirm-confidence small{margin-top:2px;color:var(--sl-muted);font-size:9.5px}
3595
3620
  .sl-confirm-confidence em{display:none;font-style:normal}
3596
- .sl-confirm-confidence>b{flex:none;font-size:11px;color:var(--sl-accent)}
3621
+ .sl-confirm-confidence>b{flex:none;font-size:11px;color:var(--sl-accent-text)}
3597
3622
  .sl-confirm-confidence:hover,.sl-confirm-confidence:focus-visible{border-color:var(--sl-accent)}
3598
3623
  /* The production 3D decision dock is deliberately denser than the 2D popup:
3599
3624
  the venue remains the main content and the two inspection actions share one
@@ -3675,7 +3700,7 @@ var CSS2 = (
3675
3700
  .sl-view3d-compare-actions{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;margin-top:12px}
3676
3701
  .sl-view3d-compare-actions button{min-height:44px;border-radius:9px;border:1px solid var(--sl-line);font-size:12px;font-weight:800}
3677
3702
  .sl-view3d-compare-actions .select{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
3678
- .sl-view3d-compare-actions button:disabled{opacity:.5;cursor:not-allowed}
3703
+ .sl-view3d-compare-actions button:disabled{opacity:.62;cursor:not-allowed}
3679
3704
  .sl-view3d-passport-shell{position:absolute;inset:0;z-index:40;display:grid;place-items:center;padding:16px}
3680
3705
  .sl-view3d-passport-scrim{position:absolute;inset:0;background:rgba(3,6,12,.8);backdrop-filter:blur(6px)}
3681
3706
  .sl-view3d-passport{position:relative;width:min(540px,100%);max-height:min(700px,calc(100% - 20px));overflow:auto;
@@ -3716,14 +3741,17 @@ var CSS2 = (
3716
3741
  .sl-confirm-3d:hover,.sl-confirm-3d:focus-visible{border-color:var(--sl-accent);background:color-mix(in srgb,var(--sl-accent) 20%,transparent)}
3717
3742
  .sl-confirm-3d svg{width:15px;height:15px;stroke:currentColor;stroke-width:1.9;fill:none;stroke-linecap:round;stroke-linejoin:round}
3718
3743
 
3719
- /* multi-floor switcher (flows within the left-rail region) */
3720
- .sl-floors{display:none;flex-direction:column;gap:6px;max-width:100%}
3744
+ /* One compact multi-floor selector (flows within the left-rail region). */
3745
+ .sl-floors{display:none;align-items:center;gap:6px;max-width:min(260px,100%);padding:5px;border-radius:999px;
3746
+ background:color-mix(in srgb,var(--sl-surface) 92%,transparent);border:1px solid var(--sl-line);
3747
+ box-shadow:var(--sl-shadow);backdrop-filter:blur(8px)}
3721
3748
  .sl-floors.on{display:flex}
3722
- .sl-floors button{padding:7px 13px;border-radius:999px;font-size:12px;font-weight:700;background:var(--sl-surface);
3723
- border:1px solid var(--sl-line);color:var(--sl-muted);white-space:nowrap;max-width:100%;overflow:hidden;
3724
- text-overflow:ellipsis;transition:color .15s,border-color .15s}
3725
- .sl-floors button:hover{color:var(--sl-text)}
3726
- .sl-floors button.on{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
3749
+ .sl-floors select{min-width:0;max-width:210px;min-height:34px;padding:6px 30px 6px 10px;border:0;border-radius:999px;
3750
+ font:700 12px/1 inherit;color:var(--sl-text);background:var(--sl-surface);cursor:pointer}
3751
+ .sl-floors select:focus-visible,.sl-floor-info:focus-visible{outline:2px solid var(--sl-accent);outline-offset:2px}
3752
+ .sl-floor-info{display:inline-grid;place-items:center;flex:0 0 30px;width:30px;height:30px;border-radius:999px;
3753
+ border:1px solid var(--sl-line);color:var(--sl-muted);background:transparent;font:800 13px/1 inherit;cursor:help}
3754
+ .sl-floor-info:hover{color:var(--sl-text);border-color:var(--sl-accent)}
3727
3755
 
3728
3756
  /* tapped-section summary card \u2014 docks INSIDE the top-center anchor region on
3729
3757
  wide (flows below the rung pills, never over them, never floating over the
@@ -3758,8 +3786,14 @@ var CSS2 = (
3758
3786
  .sl-seccard-mix-dot{width:8px;height:8px;border-radius:50%;flex:none}
3759
3787
  .sl-seccard-mix-price{font-weight:700;color:var(--sl-text)}
3760
3788
  .sl-seccard-foot{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:10px}
3761
- .sl-seccard-overview{font-size:12px;font-weight:800;color:var(--sl-accent)}
3789
+ .sl-seccard-overview{font-size:12px;font-weight:800;color:var(--sl-accent-text)}
3762
3790
  .sl-seccard-hint{font-size:10.5px;color:var(--sl-muted)}
3791
+ .sl-seccard-nav{display:inline-flex;align-items:center;gap:4px;margin-left:auto}
3792
+ .sl-seccard-nav button{width:28px;height:28px;border:1px solid var(--sl-line);border-radius:999px;color:var(--sl-text);
3793
+ background:color-mix(in srgb,var(--sl-surface) 88%,var(--sl-bg));font-size:14px;font-weight:850;line-height:1}
3794
+ .sl-seccard-nav button:hover,.sl-seccard-nav button:focus-visible{border-color:var(--sl-accent);color:var(--sl-accent-text)}
3795
+ .sl-seccard.mini .sl-seccard-nav,.sl-seccard.strip .sl-seccard-nav{margin-left:2px}
3796
+ .sl-seccard.mini .sl-seccard-nav button,.sl-seccard.strip .sl-seccard-nav button{width:24px;height:24px;font-size:12px}
3763
3797
 
3764
3798
  /* view-from-seat button on the confirm popover */
3765
3799
  /* Eager sightline preview inside the confirm card */
@@ -3812,10 +3846,29 @@ var CSS2 = (
3812
3846
  white-space:nowrap;pointer-events:none;max-width:90%;overflow:hidden;text-overflow:ellipsis}
3813
3847
 
3814
3848
  /* F3 minimap \u2014 venue overview + live viewport rect (flows in the bottom-left region) */
3815
- .sl-minimap{border:1px solid var(--sl-line);border-radius:9px;
3816
- overflow:hidden;background:var(--sl-surface);box-shadow:0 12px 34px -14px rgba(0,0,0,.55);line-height:0;cursor:pointer}
3849
+ .sl-minimap{border:1px solid var(--sl-line);border-radius:9px;overflow:hidden;background:var(--sl-surface);
3850
+ box-shadow:0 12px 34px -14px rgba(0,0,0,.55);line-height:0;touch-action:none}
3851
+ .sl-minimap-bar{min-height:24px;padding:3px 5px 3px 8px;display:flex;align-items:center;gap:6px;border-bottom:1px solid var(--sl-line);
3852
+ color:var(--sl-muted);font-size:9px;font-weight:800;line-height:1.2;letter-spacing:.06em;text-transform:uppercase}
3853
+ .sl-minimap-label{min-width:0;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
3854
+ .sl-minimap-key{display:inline-flex;align-items:center;gap:3px;flex:none}
3855
+ .sl-minimap-key i{width:6px;height:6px;border-radius:999px;background:var(--sl-success)}
3856
+ .sl-minimap-key i.selected{background:var(--sl-accent)}
3857
+ .sl-minimap-key i.inactive{background:var(--sl-muted);opacity:.45}
3858
+ .sl-minimap-key i.focused{background:transparent;border:1.5px solid var(--sl-accent);width:7px;height:7px}
3859
+ .sl-minimap-close{display:none;width:24px;height:24px;border-radius:6px;color:var(--sl-muted);font-size:15px;line-height:1}
3817
3860
  .sl-minimap canvas{display:block}
3861
+ .sl-minimap canvas:focus-visible{outline:2px solid var(--sl-accent);outline-offset:-3px}
3862
+ .sl-minimap canvas.sl-dragging{cursor:grabbing}
3863
+ .sl-minimap-toggle{display:none;width:42px;height:42px;border:1px solid var(--sl-line);border-radius:10px;background:var(--sl-surface);
3864
+ color:var(--sl-text);box-shadow:0 10px 28px -14px rgba(0,0,0,.65);align-items:center;justify-content:center}
3865
+ .sl-minimap-toggle svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linejoin:round}
3818
3866
  .sl-picker[data-layout="narrow"] .sl-minimap{display:none}
3867
+ .sl-picker[data-layout="narrow"] .sl-minimap-toggle{display:flex}
3868
+ .sl-picker[data-layout="narrow"][data-minimap-open="true"] .sl-minimap{display:block}
3869
+ .sl-picker[data-layout="narrow"][data-minimap-open="true"] .sl-minimap-toggle{display:none}
3870
+ .sl-picker[data-layout="narrow"] .sl-minimap canvas{width:min(168px,42vw)!important;height:auto!important}
3871
+ .sl-picker[data-layout="narrow"] .sl-minimap-close{display:grid;place-items:center}
3819
3872
 
3820
3873
  /* F4 legend reflection: rows + counts for out-of-band categories read muted */
3821
3874
  /* Out-of-band rows are exactly the rows a buyer reads while deciding whether
@@ -3893,17 +3946,88 @@ function formatWhen(startsAt, timezone, locale) {
3893
3946
  }
3894
3947
  return at.toLocaleString(locale, options);
3895
3948
  }
3896
- function resolveTokens(chart, host) {
3949
+ function parseThemeColor(value) {
3950
+ if (!value) return null;
3951
+ const color = value.trim().toLowerCase();
3952
+ if (color === "white") return { r: 255, g: 255, b: 255 };
3953
+ if (color === "black") return { r: 0, g: 0, b: 0 };
3954
+ const hex = /^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(color)?.[1];
3955
+ if (hex) {
3956
+ const expanded = hex.length <= 4 ? [...hex].map((digit) => `${digit}${digit}`).join("") : hex;
3957
+ return {
3958
+ r: Number.parseInt(expanded.slice(0, 2), 16),
3959
+ g: Number.parseInt(expanded.slice(2, 4), 16),
3960
+ b: Number.parseInt(expanded.slice(4, 6), 16)
3961
+ };
3962
+ }
3963
+ const rgb = /^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:\s*[,/]\s*[\d.]+%?)?\s*\)$/i.exec(color);
3964
+ if (!rgb) return null;
3965
+ return {
3966
+ r: Math.max(0, Math.min(255, Number(rgb[1]))),
3967
+ g: Math.max(0, Math.min(255, Number(rgb[2]))),
3968
+ b: Math.max(0, Math.min(255, Number(rgb[3])))
3969
+ };
3970
+ }
3971
+ function relativeLuminance(color) {
3972
+ const channel = (value) => {
3973
+ const normalized = value / 255;
3974
+ return normalized <= 0.04045 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4;
3975
+ };
3976
+ return 0.2126 * channel(color.r) + 0.7152 * channel(color.g) + 0.0722 * channel(color.b);
3977
+ }
3978
+ function themeContrast(foreground, background) {
3979
+ const fg = parseThemeColor(foreground);
3980
+ const bg = parseThemeColor(background);
3981
+ if (!fg || !bg) return null;
3982
+ const lighter = Math.max(relativeLuminance(fg), relativeLuminance(bg));
3983
+ const darker = Math.min(relativeLuminance(fg), relativeLuminance(bg));
3984
+ return (lighter + 0.05) / (darker + 0.05);
3985
+ }
3986
+ function highestContrastInk(background, dark = "#172033", light = "#eef1f8") {
3987
+ return (themeContrast(dark, background) ?? 0) >= (themeContrast(light, background) ?? 0) ? dark : light;
3988
+ }
3989
+ function rgbHex(color) {
3990
+ const channel = (value) => Math.round(value).toString(16).padStart(2, "0");
3991
+ return `#${channel(color.r)}${channel(color.g)}${channel(color.b)}`;
3992
+ }
3993
+ function mixThemeColors(first, second, firstWeight) {
3994
+ const a = parseThemeColor(first);
3995
+ const b = parseThemeColor(second);
3996
+ if (!a || !b) return null;
3997
+ const weight = Math.max(0, Math.min(1, firstWeight));
3998
+ return rgbHex({
3999
+ r: a.r * weight + b.r * (1 - weight),
4000
+ g: a.g * weight + b.g * (1 - weight),
4001
+ b: a.b * weight + b.b * (1 - weight)
4002
+ });
4003
+ }
4004
+ function readableAccentText(accent, text, background, surface) {
4005
+ if ((themeContrast(accent, background) ?? 4.5) >= 4.5 && (themeContrast(accent, surface) ?? 4.5) >= 4.5) return accent;
4006
+ if (!parseThemeColor(accent) || !parseThemeColor(text)) return text;
4007
+ for (let textWeight = 0.15; textWeight <= 1; textWeight += 0.05) {
4008
+ const candidate = mixThemeColors(accent, text, 1 - textWeight);
4009
+ if (candidate && (themeContrast(candidate, background) ?? 0) >= 4.5 && (themeContrast(candidate, surface) ?? 0) >= 4.5) return candidate;
4010
+ }
4011
+ return text;
4012
+ }
4013
+ function resolvePickerTokens(chart, host) {
4014
+ const background = host?.background ?? chart?.background ?? "#0f1522";
4015
+ const isLight = parseThemeColor(background) ? highestContrastInk(background) === "#172033" : false;
4016
+ const surface = host?.surface ?? (isLight ? "#ffffff" : "#1a2234");
3897
4017
  const accent = host?.accent ?? chart?.accent ?? "#f4b740";
3898
- const accentInk = host?.accentInk ?? chart?.accentInk ?? "#1a1200";
4018
+ const chartAccentInk = chart?.accentInk;
4019
+ const accentInk = host?.accentInk ?? (chartAccentInk && (themeContrast(chartAccentInk, accent) ?? 4.5) >= 4.5 ? chartAccentInk : highestContrastInk(accent, "#1a1200", "#ffffff"));
4020
+ const chartText = chart?.textColor;
4021
+ const text = host?.text ?? (chartText && (themeContrast(chartText, background) ?? 4.5) >= 4.5 && (themeContrast(chartText, surface) ?? 4.5) >= 4.5 ? chartText : highestContrastInk(background));
3899
4022
  return {
3900
4023
  "--sl-accent": accent,
3901
4024
  "--sl-accent-ink": accentInk,
3902
- "--sl-bg": host?.background ?? chart?.background ?? "#0f1522",
3903
- "--sl-surface": host?.surface ?? "#1a2234",
3904
- "--sl-text": host?.text ?? chart?.textColor ?? "#eef1f8",
3905
- "--sl-muted": host?.muted ?? "#8b93a7",
3906
- "--sl-line": host?.line ?? "rgba(139,147,167,.22)",
4025
+ "--sl-accent-text": readableAccentText(accent, text, background, surface),
4026
+ "--sl-bg": background,
4027
+ "--sl-surface": surface,
4028
+ "--sl-text": text,
4029
+ "--sl-muted": host?.muted ?? (isLight ? "#667085" : "#a5aec2"),
4030
+ "--sl-line": host?.line ?? (isLight ? "rgba(23,32,51,.16)" : "rgba(165,174,194,.24)"),
3907
4031
  "--sl-font": host?.fontFamily ?? chart?.fontFamily ?? "-apple-system,BlinkMacSystemFont,'Segoe UI',Inter,sans-serif",
3908
4032
  "--sl-radius": `${host?.radius ?? 14}px`
3909
4033
  };
@@ -4035,7 +4159,13 @@ var SeatPicker = class _SeatPicker {
4035
4159
  // F3 minimap
4036
4160
  this.miniCanvas = null;
4037
4161
  this.miniBase = null;
4162
+ this.miniWrap = null;
4163
+ this.miniToggle = null;
4164
+ this.miniLabel = null;
4038
4165
  this.miniTf = null;
4166
+ this.miniDrag = null;
4167
+ this.miniSuppressClick = false;
4168
+ this.pickerLayoutSize = { width: 0, height: 0 };
4039
4169
  // F4 price-band filter — active band's category keys (null = all prices)
4040
4170
  this.priceBandKeys = null;
4041
4171
  this.focusedCatKey = null;
@@ -4136,6 +4266,7 @@ var SeatPicker = class _SeatPicker {
4136
4266
  mapTheme: options.theme?.map ?? null,
4137
4267
  onSelectionChange: () => {
4138
4268
  this.syncTray();
4269
+ this.refreshMinimap();
4139
4270
  if (this.committedSelection().length) this.collapseSectionCard();
4140
4271
  this.syncSelectionTo3d();
4141
4272
  },
@@ -4195,6 +4326,12 @@ var SeatPicker = class _SeatPicker {
4195
4326
  },
4196
4327
  // Tapped-section glide-in → surface (or clear) the section-summary card.
4197
4328
  onSectionFocus: (summary) => this.showSectionCard(summary),
4329
+ onDeckTap: () => {
4330
+ this.showSectionCard(null);
4331
+ this.syncFloors();
4332
+ this.syncRung();
4333
+ this.refreshMinimap();
4334
+ },
4198
4335
  onFocusSeat: (seat) => this.announceSeat(seat),
4199
4336
  onSeatHover: (d) => this.updateTooltip(d),
4200
4337
  onHint: (m) => {
@@ -4412,7 +4549,7 @@ var SeatPicker = class _SeatPicker {
4412
4549
  window.removeEventListener("keydown", this.fsEscHandler);
4413
4550
  this.fsEscHandler = null;
4414
4551
  }
4415
- requestAnimationFrame(() => this.controller.zoomToFit());
4552
+ requestAnimationFrame(() => this.controller.refitCurrentView());
4416
4553
  }
4417
4554
  setFsFallback(on) {
4418
4555
  if (this.fsFallback === on) return;
@@ -4428,7 +4565,7 @@ var SeatPicker = class _SeatPicker {
4428
4565
  window.removeEventListener("keydown", this.fsEscHandler);
4429
4566
  this.fsEscHandler = null;
4430
4567
  }
4431
- requestAnimationFrame(() => this.controller.zoomToFit());
4568
+ requestAnimationFrame(() => this.controller.refitCurrentView());
4432
4569
  }
4433
4570
  /**
4434
4571
  * Close the picker. In modal mode (SeatPicker.open()) this dismisses the
@@ -4568,11 +4705,17 @@ var SeatPicker = class _SeatPicker {
4568
4705
  }
4569
4706
  async render() {
4570
4707
  if (this.rendered) return this;
4708
+ const renderStartedAt = performance.now();
4709
+ const renderProfile = {};
4710
+ const duration = (startedAt) => Math.round((performance.now() - startedAt) * 100) / 100;
4571
4711
  this.rendered = true;
4572
4712
  ensureStyle2();
4713
+ const localeStartedAt = performance.now();
4573
4714
  await (0, import_core2.loadLocale)(this.opts.locale);
4715
+ renderProfile.loadLocale = duration(localeStartedAt);
4574
4716
  if (this.opts.messages) (0, import_core2.setStringOverrides)(this.opts.messages);
4575
4717
  if (this.checkoutMode === "hosted") this.paymentOptions = this.pubApi.paymentOptions(this.opts.event);
4718
+ const shellStartedAt = performance.now();
4576
4719
  const mount = resolveContainer3(this.opts.container);
4577
4720
  const root = document.createElement("div");
4578
4721
  root.className = "sl-picker";
@@ -4596,7 +4739,7 @@ var SeatPicker = class _SeatPicker {
4596
4739
  this.syncTray();
4597
4740
  }
4598
4741
  });
4599
- Object.entries(resolveTokens(void 0, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
4742
+ Object.entries(resolvePickerTokens(void 0, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
4600
4743
  root.innerHTML = `
4601
4744
  <div class="sl-head">
4602
4745
  <div class="sl-logo" data-ref="logo"></div>
@@ -4675,14 +4818,20 @@ var SeatPicker = class _SeatPicker {
4675
4818
  const applyLayout = () => {
4676
4819
  const w = root.clientWidth;
4677
4820
  if (w <= 0) return;
4821
+ const h = root.clientHeight;
4822
+ const sizeChanged = w !== this.pickerLayoutSize.width || h !== this.pickerLayoutSize.height;
4823
+ this.pickerLayoutSize = { width: w, height: h };
4678
4824
  this.reportFramedHeight();
4679
4825
  const next = w < 640 ? "narrow" : "wide";
4680
- const density = root.clientHeight < 560 ? "compact" : "comfortable";
4681
- if (root.dataset.layout === next && root.dataset.density === density) return;
4682
- root.dataset.layout = next;
4683
- root.dataset.density = density;
4684
- if (next === "narrow" && !root.dataset.sheet) root.dataset.sheet = "peek";
4685
- this.dockLayoutChrome();
4826
+ const density = h < 560 ? "compact" : "comfortable";
4827
+ const layoutChanged = root.dataset.layout !== next || root.dataset.density !== density;
4828
+ if (layoutChanged) {
4829
+ root.dataset.layout = next;
4830
+ root.dataset.density = density;
4831
+ if (next === "narrow" && !root.dataset.sheet) root.dataset.sheet = "peek";
4832
+ this.dockLayoutChrome();
4833
+ }
4834
+ if (sizeChanged && this.miniBase) this.refreshMinimap();
4686
4835
  };
4687
4836
  this.ro = new ResizeObserver(applyLayout);
4688
4837
  this.ro.observe(root);
@@ -4695,7 +4844,7 @@ var SeatPicker = class _SeatPicker {
4695
4844
  this.fsChangeHandler = () => {
4696
4845
  if (!document.fullscreenElement) this.setFsFallback(false);
4697
4846
  this.syncFullscreenButtons();
4698
- requestAnimationFrame(() => this.controller.zoomToFit());
4847
+ requestAnimationFrame(() => this.controller.refitCurrentView());
4699
4848
  };
4700
4849
  document.addEventListener("fullscreenchange", this.fsChangeHandler);
4701
4850
  const head = this.els.sheetHead;
@@ -4797,7 +4946,10 @@ var SeatPicker = class _SeatPicker {
4797
4946
  const canvasHost = document.createElement("div");
4798
4947
  canvasHost.style.cssText = "position:absolute;inset:0";
4799
4948
  this.mapHost.appendChild(canvasHost);
4949
+ renderProfile.mountBuyerShell = duration(shellStartedAt);
4950
+ const chartStartedAt = performance.now();
4800
4951
  const info = await this.controller.render(canvasHost);
4952
+ renderProfile.loadChartAndStatuses = duration(chartStartedAt);
4801
4953
  if (this.destroyed) return this;
4802
4954
  if (!info) {
4803
4955
  this.els.boot.innerHTML = `<div class="sl-boot-title">${this.tf("picker.mapDidNotLoad", "The seat map didn\u2019t load")}</div><div>${this.tf("picker.checkConnection", "Check your connection and try again.")}</div><button type="button" class="sl-boot-retry">${this.tf("picker.accessRetry", "Try again")}</button>`;
@@ -4809,7 +4961,9 @@ var SeatPicker = class _SeatPicker {
4809
4961
  });
4810
4962
  return this;
4811
4963
  }
4964
+ Object.assign(renderProfile, info.performanceProfile);
4812
4965
  this.els.boot.remove();
4966
+ const controlsStartedAt = performance.now();
4813
4967
  this.startRealtime();
4814
4968
  this.salesClosed = !!info.salesClosed || !!this.opts.readOnly;
4815
4969
  root.dataset.eventMode = info.mode === "test" ? "test" : "live";
@@ -4825,7 +4979,7 @@ var SeatPicker = class _SeatPicker {
4825
4979
  this.regions["top-left"].appendChild(badge);
4826
4980
  }
4827
4981
  const chartTheme = this.controller.doc?.theme;
4828
- Object.entries(resolveTokens(chartTheme, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
4982
+ Object.entries(resolvePickerTokens(chartTheme, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
4829
4983
  this.currency = info.currency ?? this.opts.currency ?? "USD";
4830
4984
  this.eventTimezone = info.timezone ?? null;
4831
4985
  const logoUrl = this.opts.theme?.logoUrl ?? chartTheme?.logoUrl ?? info.posterUrl ?? null;
@@ -4933,12 +5087,34 @@ var SeatPicker = class _SeatPicker {
4933
5087
  this.buildPanelToggle();
4934
5088
  if (this.opts.panelCollapsed) this.setPanelCollapsed(true);
4935
5089
  this.dockLayoutChrome();
5090
+ renderProfile.buildBuyerControls = duration(controlsStartedAt);
5091
+ const holdStartedAt = performance.now();
4936
5092
  await this.restoreRememberedHold();
5093
+ renderProfile.restoreHold = duration(holdStartedAt);
4937
5094
  if (this.destroyed) return this;
4938
5095
  if (this.salesClosed) this.applySalesClosed();
4939
5096
  this.syncPrices();
4940
5097
  this.syncTray();
4941
5098
  this.resumeHostedOrder();
5099
+ const doc = this.controller.doc;
5100
+ this.emitAnalytics("chart_rendered", {
5101
+ renderingTimeMillis: duration(renderStartedAt),
5102
+ performanceProfile: renderProfile,
5103
+ seatCount: this.allSeats().length,
5104
+ objectCount: doc?.objects.length ?? 0,
5105
+ sectionCount: doc?.objects.filter((object) => object.type === "section").length ?? 0,
5106
+ view: this.buyerView,
5107
+ // Dimensions a host needs to read the timings above as anything other
5108
+ // than one undifferentiated number: WHICH chart, HOW big, WHICH data
5109
+ // path, and whether the browser had already warmed for this chart.
5110
+ // The event key is the chart's identity here — an event owns exactly one
5111
+ // chart — and the name is the same string already drawn in the header.
5112
+ eventKey: this.opts.event,
5113
+ chartName: doc?.name,
5114
+ floorCount: this.controller.getFloors().length,
5115
+ transport: this.opts.transport ? "custom" : "pubapi",
5116
+ load: markChartLoaded(this.opts.event) ? "repeat" : "cold"
5117
+ });
4942
5118
  return this;
4943
5119
  }
4944
5120
  /**
@@ -5000,7 +5176,8 @@ var SeatPicker = class _SeatPicker {
5000
5176
  const collapsed = this.sideCollapsed;
5001
5177
  btn.setAttribute("aria-expanded", String(!collapsed));
5002
5178
  btn.setAttribute("aria-label", collapsed ? this.tf("picker.showTicketPanel", "Show the ticket panel") : this.tf("picker.hideTicketPanel", "Hide the ticket panel"));
5003
- btn.innerHTML = collapsed ? `<svg viewBox="0 0 24 24"><path d="M15 6l-6 6 6 6"/></svg><span>${this.tf("picker.tickets", "Tickets")}</span>` : `<svg viewBox="0 0 24 24"><path d="M9 6l6 6-6 6"/></svg><span>${this.tf("picker.hidePanel", "Hide panel")}</span>`;
5179
+ btn.title = collapsed ? this.tf("picker.showTicketPanel", "Show the ticket panel") : this.tf("picker.hideTicketPanel", "Hide the ticket panel");
5180
+ btn.innerHTML = collapsed ? `<svg viewBox="0 0 24 24"><path d="M15 6l-6 6 6 6"/></svg><span>${this.tf("picker.tickets", "Tickets")}</span>` : `<svg viewBox="0 0 24 24"><path d="M9 6l6 6-6 6"/></svg>`;
5004
5181
  }
5005
5182
  /**
5006
5183
  * Collapse or reopen the wide-layout ticket panel in place (host-drivable —
@@ -5013,7 +5190,7 @@ var SeatPicker = class _SeatPicker {
5013
5190
  if (this.destroyed) return;
5014
5191
  this.sideCollapsed = collapsed;
5015
5192
  this.applyPanelCollapsed();
5016
- this.scheduleMotion(() => this.controller.zoomToFit(), 340);
5193
+ this.scheduleMotion(() => this.controller.refitCurrentView(), 340);
5017
5194
  }
5018
5195
  /** Idempotent DOM apply, also run on every layout flip (inert must lift on narrow). */
5019
5196
  applyPanelCollapsed() {
@@ -5408,10 +5585,77 @@ var SeatPicker = class _SeatPicker {
5408
5585
  buildMinimap() {
5409
5586
  const vp = this.controller.getViewport();
5410
5587
  if (!vp || !this.els.map) return;
5411
- const b = vp.bounds;
5412
- if (!(b.width > 0 && b.height > 0)) return;
5413
- const MAXW = 158;
5414
- const MAXH = 118;
5588
+ const host = this.regions["bottom-left"] ?? this.els.map;
5589
+ const toggle = document.createElement("button");
5590
+ toggle.type = "button";
5591
+ toggle.className = "sl-minimap-toggle";
5592
+ toggle.setAttribute("aria-label", this.tf("picker.openVenueMap", "Open venue overview map"));
5593
+ toggle.setAttribute("aria-expanded", "false");
5594
+ toggle.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 6l5-2 8 3 5-2v13l-5 2-8-3-5 2zM8 4v13M16 7v13"/></svg>';
5595
+ host.appendChild(toggle);
5596
+ this.miniToggle = toggle;
5597
+ const wrap = document.createElement("div");
5598
+ wrap.className = "sl-minimap";
5599
+ wrap.setAttribute("role", "group");
5600
+ const bar = document.createElement("div");
5601
+ bar.className = "sl-minimap-bar";
5602
+ const label = document.createElement("span");
5603
+ label.className = "sl-minimap-label";
5604
+ const key = document.createElement("span");
5605
+ key.className = "sl-minimap-key";
5606
+ key.setAttribute("aria-label", this.tf("picker.minimapStateKey", "Focused, selected, available, and inactive sections"));
5607
+ key.innerHTML = '<i class="focused" title="Focused"></i><i class="selected" title="Selected"></i><i title="Available"></i><i class="inactive" title="Inactive"></i>';
5608
+ const close = document.createElement("button");
5609
+ close.type = "button";
5610
+ close.className = "sl-minimap-close";
5611
+ close.setAttribute("aria-label", this.tf("picker.closeVenueMap", "Close venue overview map"));
5612
+ close.textContent = "\xD7";
5613
+ bar.append(label, key, close);
5614
+ const canvas = document.createElement("canvas");
5615
+ canvas.tabIndex = 0;
5616
+ canvas.setAttribute("role", "application");
5617
+ canvas.setAttribute("aria-label", this.tf("picker.venueMapInstructions", "Venue overview map. Drag the viewport, click a section, or use arrow keys to pan."));
5618
+ wrap.append(bar, canvas);
5619
+ host.appendChild(wrap);
5620
+ this.miniWrap = wrap;
5621
+ this.miniLabel = label;
5622
+ this.miniCanvas = canvas;
5623
+ const base = document.createElement("canvas");
5624
+ this.miniBase = base;
5625
+ toggle.addEventListener("click", () => this.setMinimapOpen(true));
5626
+ close.addEventListener("click", () => this.setMinimapOpen(false));
5627
+ canvas.addEventListener("click", (event) => {
5628
+ if (this.miniSuppressClick) {
5629
+ this.miniSuppressClick = false;
5630
+ return;
5631
+ }
5632
+ this.minimapJump(event);
5633
+ });
5634
+ canvas.addEventListener("pointerdown", (event) => this.minimapPointerDown(event));
5635
+ canvas.addEventListener("pointermove", (event) => this.minimapPointerMove(event));
5636
+ canvas.addEventListener("pointerup", (event) => this.minimapPointerUp(event));
5637
+ canvas.addEventListener("pointercancel", (event) => this.minimapPointerUp(event));
5638
+ canvas.addEventListener("keydown", (event) => this.minimapKeydown(event));
5639
+ this.layoutMinimap();
5640
+ this.syncMinimapLabel();
5641
+ this.drawMinimapStatic();
5642
+ this.drawMinimapRect();
5643
+ }
5644
+ setMinimapOpen(open) {
5645
+ this.root?.setAttribute("data-minimap-open", String(open));
5646
+ this.miniToggle?.setAttribute("aria-expanded", String(open));
5647
+ if (open) this.miniCanvas?.focus();
5648
+ else this.miniToggle?.focus();
5649
+ }
5650
+ /** Re-fit the thumbnail whenever the active floor or complete overview
5651
+ * changes bounds. This is presentation-only; the source chart is untouched. */
5652
+ layoutMinimap() {
5653
+ const canvas = this.miniCanvas;
5654
+ const base = this.miniBase;
5655
+ const b = this.controller.getViewport()?.bounds;
5656
+ if (!canvas || !base || !b || !(b.width > 0 && b.height > 0)) return;
5657
+ const MAXW = 196;
5658
+ const MAXH = 146;
5415
5659
  const PAD = 6;
5416
5660
  const aspect = b.width / Math.max(1, b.height);
5417
5661
  let w = MAXW;
@@ -5423,32 +5667,40 @@ var SeatPicker = class _SeatPicker {
5423
5667
  w = Math.max(64, w);
5424
5668
  h = Math.max(48, h);
5425
5669
  const dpr = Math.min(2, window.devicePixelRatio || 1);
5426
- const wrap = document.createElement("div");
5427
- wrap.className = "sl-minimap";
5428
- wrap.setAttribute("aria-hidden", "true");
5429
- const canvas = document.createElement("canvas");
5430
5670
  canvas.width = Math.round(w * dpr);
5431
5671
  canvas.height = Math.round(h * dpr);
5432
5672
  canvas.style.width = `${w}px`;
5433
5673
  canvas.style.height = `${h}px`;
5434
- wrap.appendChild(canvas);
5435
- (this.regions["bottom-left"] ?? this.els.map).appendChild(wrap);
5436
- this.miniCanvas = canvas;
5437
- const scale = Math.min((w - PAD * 2) / Math.max(1, b.width), (h - PAD * 2) / Math.max(1, b.height)) * dpr;
5438
- const offX = (w * dpr - b.width * scale) / 2 - b.x * scale;
5439
- const offY = (h * dpr - b.height * scale) / 2 - b.y * scale;
5440
- this.miniTf = { scale, offX, offY, dpr };
5441
- const base = document.createElement("canvas");
5442
5674
  base.width = canvas.width;
5443
5675
  base.height = canvas.height;
5444
- this.miniBase = base;
5445
- wrap.addEventListener("click", (e) => this.minimapJump(e));
5446
- this.drawMinimapStatic();
5447
- this.drawMinimapRect();
5676
+ this.miniTf = (0, import_minimapGeometry.createMinimapTransform)(b, canvas.width, canvas.height, dpr, PAD);
5677
+ }
5678
+ syncMinimapLabel() {
5679
+ if (!this.miniLabel || !this.miniWrap) return;
5680
+ const floors = this.controller.getFloors();
5681
+ const floor = this.controller.isFloorOverview() ? this.tf("picker.allFloors", "All floors") : floors.find((candidate) => candidate.id === this.controller.getActiveFloorId())?.name ?? this.tf("picker.venueOverview", "Venue overview");
5682
+ const sections = this.controller.getMinimapSnapshot().sections;
5683
+ const focused = sections.find((section) => section.active);
5684
+ const focusedLabel = focused?.label ?? this.lastSection?.label;
5685
+ const label = focusedLabel ? `${floor} \xB7 ${focusedLabel}` : floor;
5686
+ this.miniLabel.textContent = label;
5687
+ const selected = sections.filter((section) => section.state === "selected").length;
5688
+ const available = sections.filter((section) => section.state === "available").length;
5689
+ const inactive = sections.filter((section) => section.state === "inactive").length;
5690
+ this.miniWrap.setAttribute(
5691
+ "aria-label",
5692
+ sections.length ? `${label}. ${selected} selected, ${available} available, ${inactive} inactive sections.` : label
5693
+ );
5694
+ this.miniCanvas?.setAttribute(
5695
+ "aria-label",
5696
+ sections.length ? `${label}. ${selected} selected, ${available} available, ${inactive} inactive sections. ${this.tf("picker.venueMapInstructions", "The outlined region is the visible map area. Drag it, click a section, or use arrow keys to pan.")}` : `${label}. ${this.tf("picker.venueMapInstructions", "The outlined region is the visible map area. Drag it, click elsewhere, or use arrow keys to pan.")}`
5697
+ );
5448
5698
  }
5449
5699
  /** Repaint the static overview + rect (floor switch, live open/close). */
5450
5700
  refreshMinimap() {
5451
5701
  if (!this.miniBase) return;
5702
+ this.layoutMinimap();
5703
+ this.syncMinimapLabel();
5452
5704
  this.drawMinimapStatic();
5453
5705
  this.drawMinimapRect();
5454
5706
  }
@@ -5456,8 +5708,7 @@ var SeatPicker = class _SeatPicker {
5456
5708
  drawMinimapStatic() {
5457
5709
  const base = this.miniBase;
5458
5710
  const tf = this.miniTf;
5459
- const doc = this.controller.doc;
5460
- if (!base || !tf || !doc) return;
5711
+ if (!base || !tf) return;
5461
5712
  const ctx = base.getContext("2d");
5462
5713
  if (!ctx) return;
5463
5714
  ctx.clearRect(0, 0, base.width, base.height);
@@ -5466,34 +5717,42 @@ var SeatPicker = class _SeatPicker {
5466
5717
  const line = this.cssVar("--sl-line") || "rgba(139,147,167,.5)";
5467
5718
  const muted = this.cssVar("--sl-muted") || "#8b93a7";
5468
5719
  const accent = this.cssVar("--sl-accent") || "#6e7bff";
5469
- const zoneColor = new Map((doc.zones ?? []).map((z) => [z.id, z.color]));
5470
- let drewSection = false;
5471
- for (const o of this.activeFloorObjects()) {
5472
- if (o.type !== "section" || !o.outline || o.outline.length < 3) continue;
5473
- drewSection = true;
5474
- const closed = this.controller.isSectionClosed(o.id);
5475
- const fill = closed ? muted : o.color ?? (o.zone && zoneColor.get(o.zone)) ?? accent;
5720
+ const available = this.cssVar("--sl-success") || "#22a06b";
5721
+ const snapshot = this.controller.getMinimapSnapshot();
5722
+ for (const section of snapshot.sections) {
5723
+ if (section.outline.length < 3) continue;
5476
5724
  ctx.beginPath();
5477
- o.outline.forEach((p, i) => i === 0 ? ctx.moveTo(fx(p.x), fy(p.y)) : ctx.lineTo(fx(p.x), fy(p.y)));
5725
+ section.outline.forEach((point, index) => index === 0 ? ctx.moveTo(fx(point.x), fy(point.y)) : ctx.lineTo(fx(point.x), fy(point.y)));
5478
5726
  ctx.closePath();
5479
- ctx.globalAlpha = closed ? 0.26 : 0.42;
5480
- ctx.fillStyle = fill;
5727
+ ctx.globalAlpha = section.state === "inactive" ? 0.18 : section.state === "selected" ? 0.72 : 0.34;
5728
+ ctx.fillStyle = section.state === "inactive" ? muted : section.state === "selected" ? accent : available;
5481
5729
  ctx.fill();
5482
- ctx.globalAlpha = 0.85;
5730
+ ctx.globalAlpha = 0.82;
5483
5731
  ctx.lineWidth = Math.max(1, tf.dpr);
5484
5732
  ctx.strokeStyle = line;
5485
5733
  ctx.stroke();
5734
+ if (section.active) {
5735
+ ctx.globalAlpha = 1;
5736
+ ctx.lineJoin = "round";
5737
+ ctx.lineWidth = Math.max(4, tf.dpr * 3.5);
5738
+ ctx.strokeStyle = this.cssVar("--sl-surface") || "#fff";
5739
+ ctx.stroke();
5740
+ ctx.lineWidth = Math.max(2, tf.dpr * 1.75);
5741
+ ctx.strokeStyle = accent;
5742
+ ctx.stroke();
5743
+ }
5486
5744
  }
5487
5745
  ctx.globalAlpha = 1;
5488
- if (!drewSection) {
5746
+ if (!snapshot.sections.length) {
5489
5747
  const r = Math.max(1, tf.dpr);
5490
- for (const seat of (0, import_core2.expandChart)(doc)) {
5491
- const cat = doc.categories.find((c) => c.key === seat.categoryKey);
5492
- ctx.fillStyle = cat?.color ?? accent;
5748
+ for (const seat of snapshot.seats) {
5749
+ ctx.globalAlpha = seat.state === "inactive" ? 0.22 : 0.78;
5750
+ ctx.fillStyle = seat.state === "inactive" ? muted : seat.state === "selected" ? accent : available;
5493
5751
  ctx.beginPath();
5494
5752
  ctx.arc(fx(seat.x), fy(seat.y), r, 0, Math.PI * 2);
5495
5753
  ctx.fill();
5496
5754
  }
5755
+ ctx.globalAlpha = 1;
5497
5756
  }
5498
5757
  }
5499
5758
  /** Blit the base overview, then stroke the current viewport rectangle on top. */
@@ -5508,41 +5767,106 @@ var SeatPicker = class _SeatPicker {
5508
5767
  ctx.drawImage(base, 0, 0);
5509
5768
  const vp = this.controller.getViewport();
5510
5769
  if (!vp) return;
5511
- const v = vp.visible;
5512
- const x = v.x * tf.scale + tf.offX;
5513
- const y = v.y * tf.scale + tf.offY;
5514
- const w = v.width * tf.scale;
5515
- const h = v.height * tf.scale;
5516
- const accent = this.cssVar("--sl-accent") || "#f4b740";
5770
+ const plan = (0, import_minimapGeometry.minimapViewportPlan)(vp.visible, tf, canvas.width, canvas.height, Math.max(2, tf.dpr * 2));
5771
+ const ink = this.cssVar("--sl-text") || "#12151c";
5772
+ const surface = this.cssVar("--sl-surface") || "#fff";
5517
5773
  ctx.save();
5518
- ctx.globalAlpha = 0.14;
5519
- ctx.fillStyle = accent;
5520
- ctx.fillRect(x, y, w, h);
5774
+ ctx.globalAlpha = 0.1;
5775
+ ctx.fillStyle = ink;
5776
+ ctx.fillRect(plan.raw.x, plan.raw.y, plan.raw.width, plan.raw.height);
5777
+ ctx.globalAlpha = 0.9;
5778
+ ctx.lineJoin = "round";
5779
+ ctx.lineWidth = Math.max(4, tf.dpr * 3.25);
5780
+ ctx.strokeStyle = surface;
5781
+ ctx.strokeRect(plan.raw.x, plan.raw.y, plan.raw.width, plan.raw.height);
5521
5782
  ctx.globalAlpha = 1;
5522
- ctx.lineWidth = Math.max(1.5, tf.dpr * 1.5);
5523
- ctx.strokeStyle = accent;
5524
- ctx.strokeRect(x, y, w, h);
5783
+ ctx.lineWidth = Math.max(2, tf.dpr * 1.75);
5784
+ ctx.strokeStyle = ink;
5785
+ ctx.strokeRect(plan.raw.x, plan.raw.y, plan.raw.width, plan.raw.height);
5786
+ if (plan.clipped && Object.values(plan.clippedEdges).some(Boolean)) {
5787
+ ctx.setLineDash([Math.max(3, tf.dpr * 3), Math.max(2, tf.dpr * 2)]);
5788
+ ctx.strokeRect(plan.clipped.x, plan.clipped.y, plan.clipped.width, plan.clipped.height);
5789
+ }
5525
5790
  ctx.restore();
5526
5791
  }
5527
- /** Minimap click → focus the section under the point (or overview on a miss). */
5528
- minimapJump(e) {
5792
+ minimapWorldPoint(event) {
5529
5793
  const canvas = this.miniCanvas;
5530
5794
  const tf = this.miniTf;
5531
- if (!canvas || !tf) return;
5532
- const r = canvas.getBoundingClientRect();
5533
- const px = (e.clientX - r.left) * (canvas.width / r.width);
5534
- const py = (e.clientY - r.top) * (canvas.height / r.height);
5535
- const wx = (px - tf.offX) / tf.scale;
5536
- const wy = (py - tf.offY) / tf.scale;
5537
- for (const o of this.activeFloorObjects()) {
5538
- if (o.type !== "section" || !o.outline || o.outline.length < 3) continue;
5539
- if (this.controller.isSectionClosed(o.id)) continue;
5540
- if (pointInPolygon(wx, wy, o.outline)) {
5541
- this.controller.focusSection(o.id);
5795
+ if (!canvas || !tf) return null;
5796
+ const rect = canvas.getBoundingClientRect();
5797
+ const px = (event.clientX - rect.left) * (canvas.width / Math.max(1, rect.width));
5798
+ const py = (event.clientY - rect.top) * (canvas.height / Math.max(1, rect.height));
5799
+ return { ...(0, import_minimapGeometry.minimapPointToWorld)({ x: px, y: py }, tf), px, py };
5800
+ }
5801
+ minimapPointerDown(event) {
5802
+ const point = this.minimapWorldPoint(event);
5803
+ const visible = this.controller.getViewport()?.visible;
5804
+ if (!point || !visible) return;
5805
+ const inside = point.x >= visible.x && point.x <= visible.x + visible.width && point.y >= visible.y && point.y <= visible.y + visible.height;
5806
+ if (!inside) return;
5807
+ this.miniDrag = {
5808
+ pointerId: event.pointerId,
5809
+ offsetX: point.x - (visible.x + visible.width / 2),
5810
+ offsetY: point.y - (visible.y + visible.height / 2),
5811
+ startX: event.clientX,
5812
+ startY: event.clientY,
5813
+ moved: false
5814
+ };
5815
+ this.miniCanvas?.setPointerCapture(event.pointerId);
5816
+ this.miniCanvas?.classList.add("sl-dragging");
5817
+ event.preventDefault();
5818
+ }
5819
+ minimapPointerMove(event) {
5820
+ const drag = this.miniDrag;
5821
+ if (!drag || drag.pointerId !== event.pointerId) return;
5822
+ const point = this.minimapWorldPoint(event);
5823
+ if (!point) return;
5824
+ if (!drag.moved && Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) > 2) {
5825
+ drag.moved = true;
5826
+ this.controller.clearSectionFocus();
5827
+ }
5828
+ this.controller.panToWorld({ x: point.x - drag.offsetX, y: point.y - drag.offsetY });
5829
+ event.preventDefault();
5830
+ }
5831
+ minimapPointerUp(event) {
5832
+ const drag = this.miniDrag;
5833
+ if (!drag || drag.pointerId !== event.pointerId) return;
5834
+ this.miniSuppressClick = drag.moved;
5835
+ this.miniDrag = null;
5836
+ this.miniCanvas?.classList.remove("sl-dragging");
5837
+ if (this.miniCanvas?.hasPointerCapture(event.pointerId)) this.miniCanvas.releasePointerCapture(event.pointerId);
5838
+ }
5839
+ minimapKeydown(event) {
5840
+ const visible = this.controller.getViewport()?.visible;
5841
+ if (!visible) return;
5842
+ if (event.key === "Home") {
5843
+ this.controller.overview();
5844
+ event.preventDefault();
5845
+ return;
5846
+ }
5847
+ const dx = event.key === "ArrowLeft" ? -visible.width * 0.2 : event.key === "ArrowRight" ? visible.width * 0.2 : 0;
5848
+ const dy = event.key === "ArrowUp" ? -visible.height * 0.2 : event.key === "ArrowDown" ? visible.height * 0.2 : 0;
5849
+ if (!dx && !dy) return;
5850
+ this.controller.clearSectionFocus();
5851
+ this.controller.panToWorld({
5852
+ x: visible.x + visible.width / 2 + dx,
5853
+ y: visible.y + visible.height / 2 + dy
5854
+ });
5855
+ event.preventDefault();
5856
+ }
5857
+ /** Minimap click → focus an available section, otherwise pan there. */
5858
+ minimapJump(e) {
5859
+ const point = this.minimapWorldPoint(e);
5860
+ if (!point) return;
5861
+ for (const section of this.controller.getMinimapSnapshot().sections) {
5862
+ if (section.state === "inactive" || section.outline.length < 3) continue;
5863
+ if (pointInPolygon(point.x, point.y, section.outline)) {
5864
+ this.controller.focusSection(section.id);
5542
5865
  return;
5543
5866
  }
5544
5867
  }
5545
- this.controller.overview();
5868
+ this.controller.clearSectionFocus();
5869
+ this.controller.panToWorld(point);
5546
5870
  }
5547
5871
  // ---- F4 price-band filter -------------------------------------------------
5548
5872
  /** Effective display price of a category: host pricing override → first tier → base. */
@@ -5551,32 +5875,39 @@ var SeatPicker = class _SeatPicker {
5551
5875
  if (chart === void 0 || !c.key) return chart;
5552
5876
  return this.paidPrice(c.key, c.tiers?.[0]?.id ?? null, chart);
5553
5877
  }
5554
- /** Derive price bands: one chip per distinct price (≤5), else quantile ranges. */
5878
+ /** Complete buyer-visible price range, including every ticket tier and
5879
+ * tier-specific host override. */
5880
+ catPriceRange(c) {
5881
+ const prices = c.tiers?.length ? c.tiers.map((tier) => this.paidPrice(c.key, tier.id ?? null, tier.price)) : c.price == null ? [] : [this.paidPrice(c.key, null, c.price)];
5882
+ return prices.length ? { min: Math.min(...prices), max: Math.max(...prices) } : void 0;
5883
+ }
5884
+ /** Derive category price bands without hiding the upper end of tier ranges. */
5555
5885
  priceBands() {
5556
5886
  const doc = this.controller.doc;
5557
5887
  if (!doc) return [];
5558
- const priced = doc.categories.map((c) => ({ key: c.key, price: this.catPrice(c) })).filter((x) => x.price != null);
5888
+ const priced = doc.categories.map((c) => ({ key: c.key, range: this.catPriceRange(c) })).filter((x) => x.range != null);
5559
5889
  if (!priced.length) return [];
5560
- const distinct = [...new Set(priced.map((p) => p.price))].sort((a, b) => a - b);
5890
+ const distinct = [...new Map(priced.map((item) => [`${item.range.min}:${item.range.max}`, item.range])).values()].sort((a, b) => a.min - b.min || a.max - b.max);
5561
5891
  if (distinct.length <= 5) {
5562
- return distinct.map((price) => ({
5563
- id: `p${price}`,
5564
- label: this.money(price),
5565
- keys: priced.filter((p) => p.price === price).map((p) => p.key),
5566
- min: price,
5567
- max: price
5892
+ return distinct.map((range) => ({
5893
+ id: `p${range.min}-${range.max}`,
5894
+ label: range.min === range.max ? this.money(range.min) : `${this.money(range.min)}\u2013${this.money(range.max)}`,
5895
+ keys: priced.filter((item) => item.range.min === range.min && item.range.max === range.max).map((item) => item.key),
5896
+ min: range.min,
5897
+ max: range.max
5568
5898
  }));
5569
5899
  }
5570
- const chunk = Math.ceil(distinct.length / 4);
5900
+ const ordered = [...priced].sort((a, b) => a.range.min - b.range.min || a.range.max - b.range.max);
5901
+ const chunk = Math.ceil(ordered.length / 4);
5571
5902
  const bands = [];
5572
- for (let i = 0; i < distinct.length; i += chunk) {
5573
- const slice = distinct.slice(i, i + chunk);
5574
- const lo = slice[0];
5575
- const hi = slice[slice.length - 1];
5903
+ for (let i = 0; i < ordered.length; i += chunk) {
5904
+ const slice = ordered.slice(i, i + chunk);
5905
+ const lo = Math.min(...slice.map((item) => item.range.min));
5906
+ const hi = Math.max(...slice.map((item) => item.range.max));
5576
5907
  bands.push({
5577
5908
  id: `b${i}`,
5578
5909
  label: lo === hi ? this.money(lo) : `${this.money(lo)}\u2013${this.money(hi)}`,
5579
- keys: priced.filter((p) => p.price >= lo && p.price <= hi).map((p) => p.key),
5910
+ keys: slice.map((item) => item.key),
5580
5911
  min: lo,
5581
5912
  max: hi
5582
5913
  });
@@ -5631,7 +5962,13 @@ var SeatPicker = class _SeatPicker {
5631
5962
  this.syncProjection();
5632
5963
  }
5633
5964
  if (hasSections) {
5634
- const RUNGS = ["zones", "sections", "seats"];
5965
+ const allObjects = [
5966
+ ...doc.objects,
5967
+ ...(doc.floors ?? []).flatMap((floor) => floor.objects)
5968
+ ];
5969
+ const authoredZoneIds = new Set((doc.zones ?? []).map((zone) => zone.id));
5970
+ const hasZones = allObjects.some((object) => object.type === "section" && typeof object.zone === "string" && authoredZoneIds.has(object.zone));
5971
+ const RUNGS = hasZones ? ["zones", "sections", "seats"] : ["sections", "seats"];
5635
5972
  const pills = document.createElement("div");
5636
5973
  pills.className = "sl-rungs on";
5637
5974
  pills.setAttribute("role", "group");
@@ -5664,18 +6001,38 @@ var SeatPicker = class _SeatPicker {
5664
6001
  const floors = this.controller.getFloors();
5665
6002
  const rail = document.createElement("div");
5666
6003
  rail.className = "sl-floors on";
5667
- rail.setAttribute("role", "group");
5668
- rail.setAttribute("aria-label", (0, import_core2.t)("picker.floor"));
5669
- rail.innerHTML = floors.map((f) => `<button type="button" data-floor="${f.id}">${f.name}</button>`).join("");
5670
- rail.querySelectorAll("button").forEach((btn) => {
5671
- btn.addEventListener("click", () => {
5672
- this.controller.setFloor(btn.dataset.floor);
5673
- this.showSectionCard(null);
5674
- this.syncFloors();
5675
- this.syncRung();
5676
- this.refreshMinimap();
5677
- });
6004
+ const select = document.createElement("select");
6005
+ select.setAttribute("aria-label", this.tf("picker.chooseLevel", "Choose level"));
6006
+ const overview = document.createElement("option");
6007
+ overview.value = "";
6008
+ overview.textContent = this.tf("picker.allFloors", "All floors");
6009
+ select.appendChild(overview);
6010
+ for (const floor of floors) {
6011
+ const option = document.createElement("option");
6012
+ option.value = floor.id;
6013
+ option.textContent = floor.name;
6014
+ select.appendChild(option);
6015
+ }
6016
+ select.addEventListener("change", () => {
6017
+ if (select.value === "") this.controller.setFloorOverview(true);
6018
+ else this.controller.setFloor(select.value);
6019
+ this.showSectionCard(null);
6020
+ this.syncFloors();
6021
+ this.syncRung();
6022
+ this.refreshMinimap();
5678
6023
  });
6024
+ rail.appendChild(select);
6025
+ const info = document.createElement("button");
6026
+ info.type = "button";
6027
+ info.className = "sl-floor-info";
6028
+ info.textContent = "i";
6029
+ const explanation = this.tf(
6030
+ "picker.floorHelp",
6031
+ "All floors shows the complete venue. Choose one level to inspect and select its seats."
6032
+ );
6033
+ info.setAttribute("aria-label", explanation);
6034
+ info.title = explanation;
6035
+ rail.appendChild(info);
5679
6036
  this.regions["left-rail"].appendChild(rail);
5680
6037
  this.floorsEl = rail;
5681
6038
  this.syncFloors();
@@ -5721,19 +6078,59 @@ var SeatPicker = class _SeatPicker {
5721
6078
  syncFloors() {
5722
6079
  if (!this.floorsEl) return;
5723
6080
  const active = this.controller.getActiveFloorId();
5724
- this.floorsEl.querySelectorAll("button").forEach((btn) => {
5725
- btn.classList.toggle("on", btn.dataset.floor === active);
5726
- });
6081
+ const overview = this.controller.isFloorOverview();
6082
+ const select = this.floorsEl.querySelector("select");
6083
+ if (select) select.value = overview ? "" : active;
5727
6084
  }
5728
6085
  /** Show (or clear, on null) the tapped-section summary card. */
5729
6086
  showSectionCard(summary) {
5730
6087
  this.lastSection = summary;
5731
6088
  this.secCardEl?.remove();
5732
6089
  this.secCardEl = null;
5733
- if (!summary) return;
6090
+ if (!summary) {
6091
+ this.refreshMinimap();
6092
+ return;
6093
+ }
5734
6094
  this.secCardCollapsed = this.controller.getRung() === "seats";
5735
6095
  this.secCardShownAt = Date.now();
5736
6096
  this.renderSectionCard(summary);
6097
+ this.refreshMinimap();
6098
+ }
6099
+ /**
6100
+ * Open sections next to the focused one, in authored order.
6101
+ *
6102
+ * This is intentionally a circular ring: arena sections are usually authored
6103
+ * clockwise, so moving past the last section should reach the first without
6104
+ * forcing a buyer back through the overview level. Closed or empty sections
6105
+ * are skipped because focusing one cannot produce a useful buyer card.
6106
+ */
6107
+ sectionNeighbours(sectionId) {
6108
+ const sections = this.activeFloorObjects().filter((object) => object.type === "section" && !this.controller.isSectionClosed(object.id) && this.controller.getSectionSeatCount(object.id) > 0);
6109
+ if (sections.length < 2) return null;
6110
+ const index = sections.findIndex((section) => section.id === sectionId);
6111
+ if (index < 0) return null;
6112
+ return {
6113
+ previous: sections[(index - 1 + sections.length) % sections.length],
6114
+ next: sections[(index + 1) % sections.length]
6115
+ };
6116
+ }
6117
+ /** Previous/next controls shared by expanded, mini, and phone section cards. */
6118
+ sectionNavigationHtml(summary) {
6119
+ const neighbours = this.sectionNeighbours(summary.id);
6120
+ if (!neighbours) return "";
6121
+ const previousLabel = neighbours.previous.label || neighbours.previous.id;
6122
+ const nextLabel = neighbours.next.label || neighbours.next.id;
6123
+ return `<span class="sl-seccard-nav" aria-label="${this.tf("picker.browseSections", "Browse sections")}"><button type="button" data-section-nav="${escapeOption(neighbours.previous.id)}" aria-label="${this.tf("picker.previousSection", "Previous section")}: ${escapeOption(previousLabel)}" title="${escapeOption(previousLabel)}">\u2190</button><button type="button" data-section-nav="${escapeOption(neighbours.next.id)}" aria-label="${this.tf("picker.nextSection", "Next section")}: ${escapeOption(nextLabel)}" title="${escapeOption(nextLabel)}">\u2192</button></span>`;
6124
+ }
6125
+ wireSectionNavigation(card) {
6126
+ card.querySelectorAll("[data-section-nav]").forEach((button) => {
6127
+ button.addEventListener("click", (event) => {
6128
+ event.preventDefault();
6129
+ event.stopPropagation();
6130
+ const sectionId = button.dataset.sectionNav;
6131
+ if (sectionId) this.controller.focusSection(sectionId);
6132
+ });
6133
+ });
5737
6134
  }
5738
6135
  /**
5739
6136
  * Render the section card in the form the layout + state want: expanded card
@@ -5747,26 +6144,32 @@ var SeatPicker = class _SeatPicker {
5747
6144
  this.lastSection = null;
5748
6145
  return;
5749
6146
  }
5750
- const paid = summary.categories.length ? summary.categories.map((c) => this.paidPrice(c.key, null, c.price)) : [summary.priceMin, summary.priceMax];
5751
- const paidMin = Math.min(...paid);
5752
- const paidMax = Math.max(...paid);
6147
+ const paidRanges = summary.categories.length ? summary.categories.map((item) => {
6148
+ const category = this.controller.doc?.categories.find((candidate) => candidate.key === item.key);
6149
+ return category ? this.catPriceRange(category) : { min: item.priceMin, max: item.priceMax };
6150
+ }).filter((range) => range != null) : [{ min: summary.priceMin, max: summary.priceMax }];
6151
+ const paidMin = Math.min(...paidRanges.map((range) => range.min));
6152
+ const paidMax = Math.max(...paidRanges.map((range) => range.max));
5753
6153
  const priceLabel = paidMin === paidMax ? this.money(paidMin) : `${this.money(paidMin)}\u2013${this.money(paidMax)}`;
5754
6154
  const leftLabel = (0, import_core2.tCount)("picker.seatsLeftInSection", summary.seatsLeft);
5755
6155
  const xBtn = `<button type="button" class="sl-seccard-x" aria-label="${(0, import_core2.t)("picker.closeSectionSummary")}">\u2715</button>`;
6156
+ const sectionNav = this.sectionNavigationHtml(summary);
5756
6157
  const card = document.createElement("div");
5757
6158
  const narrow = this.root?.dataset.layout === "narrow";
5758
6159
  if (narrow) {
5759
6160
  card.className = "sl-seccard strip on";
5760
6161
  card.setAttribute("role", "status");
5761
6162
  card.setAttribute("aria-label", (0, import_core2.t)("picker.sectionSummaryAria", { label: summary.label }));
5762
- card.innerHTML = `<span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span><span class="sl-seccard-left">${leftLabel}</span>` + (summary.categories.length ? `<span class="sl-seccard-price">${priceLabel}</span>` : "") + xBtn;
6163
+ card.innerHTML = `<span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span><span class="sl-seccard-left">${leftLabel}</span>` + (summary.categories.length ? `<span class="sl-seccard-price">${priceLabel}</span>` : "") + sectionNav + xBtn;
6164
+ this.wireSectionNavigation(card);
5763
6165
  card.querySelector(".sl-seccard-x").addEventListener("click", () => this.controller.overview());
5764
6166
  (this.els.sheetHead ?? this.els.side ?? this.els.map).appendChild(card);
5765
6167
  } else if (this.secCardCollapsed) {
5766
6168
  card.className = "sl-seccard mini on";
5767
6169
  card.setAttribute("role", "button");
5768
6170
  card.setAttribute("aria-label", (0, import_core2.t)("picker.sectionSummaryAria", { label: summary.label }));
5769
- card.innerHTML = `<span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span><span class="sl-seccard-left">${leftLabel}</span>` + xBtn;
6171
+ card.innerHTML = `<span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span><span class="sl-seccard-left">${leftLabel}</span>` + sectionNav + xBtn;
6172
+ this.wireSectionNavigation(card);
5770
6173
  card.addEventListener("click", (e) => {
5771
6174
  if (e.target.closest(".sl-seccard-x")) return;
5772
6175
  this.secCardCollapsed = false;
@@ -5781,9 +6184,13 @@ var SeatPicker = class _SeatPicker {
5781
6184
  card.setAttribute("aria-label", (0, import_core2.t)("picker.sectionSummaryAria", { label: summary.label }));
5782
6185
  const mix = summary.categories.map((c) => {
5783
6186
  const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
5784
- return `<span class="sl-seccard-mix-item${dim ? " sl-dim" : ""}"><span class="sl-seccard-mix-dot" style="background:${c.color}"></span>${c.label} <span class="sl-seccard-mix-price">${this.money(this.paidPrice(c.key, null, c.price))}</span></span>`;
6187
+ const category = this.controller.doc?.categories.find((candidate) => candidate.key === c.key);
6188
+ const range = category ? this.catPriceRange(category) : { min: c.priceMin, max: c.priceMax };
6189
+ const rangeLabel = range && range.min !== range.max ? `${this.money(range.min)}\u2013${this.money(range.max)}` : this.money(range?.min ?? c.price);
6190
+ return `<span class="sl-seccard-mix-item${dim ? " sl-dim" : ""}"><span class="sl-seccard-mix-dot" style="background:${c.color}"></span>${c.label} <span class="sl-seccard-mix-price">${rangeLabel}</span></span>`;
5785
6191
  }).join("");
5786
- card.innerHTML = `<div class="sl-seccard-head"><span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span>` + (summary.categories.length ? `<span class="sl-seccard-price">${priceLabel}</span>` : "") + xBtn + `</div><div class="sl-seccard-zone">${summary.zoneLabel ? `${summary.zoneLabel} \xB7 ` : ""}<span class="sl-seccard-left">${leftLabel}</span></div>` + (summary.entrance ? `<div class="sl-seccard-entrance">${(0, import_core2.t)("picker.entrance")} ${String(summary.entrance).replace(/[&<>"]/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[ch])}</div>` : "") + (mix ? `<div class="sl-seccard-mix">${mix}</div>` : "") + `<div class="sl-seccard-foot"><button type="button" class="sl-seccard-overview">\u2190 ${(0, import_core2.t)("picker.overview")}</button><span class="sl-seccard-hint">${(0, import_core2.t)("picker.tapSeatHint")}</span></div>`;
6192
+ card.innerHTML = `<div class="sl-seccard-head"><span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span>` + (summary.categories.length ? `<span class="sl-seccard-price">${priceLabel}</span>` : "") + xBtn + `</div><div class="sl-seccard-zone">${summary.zoneLabel ? `${summary.zoneLabel} \xB7 ` : ""}<span class="sl-seccard-left">${leftLabel}</span></div>` + (summary.entrance ? `<div class="sl-seccard-entrance">${(0, import_core2.t)("picker.entrance")} ${String(summary.entrance).replace(/[&<>"]/g, (ch) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[ch])}</div>` : "") + (mix ? `<div class="sl-seccard-mix">${mix}</div>` : "") + `<div class="sl-seccard-foot"><button type="button" class="sl-seccard-overview">\u2190 ${(0, import_core2.t)("picker.overview")}</button>` + sectionNav + `<span class="sl-seccard-hint">${(0, import_core2.t)("picker.tapSeatHint")}</span></div>`;
6193
+ this.wireSectionNavigation(card);
5787
6194
  card.querySelector(".sl-seccard-x").addEventListener("click", () => this.controller.overview());
5788
6195
  card.querySelector(".sl-seccard-overview").addEventListener("click", () => this.controller.overview());
5789
6196
  (this.regions["top-center"] ?? this.els.map).appendChild(card);
@@ -5848,13 +6255,14 @@ var SeatPicker = class _SeatPicker {
5848
6255
  const cat = this.controller.doc?.categories.find((c) => c.key === seat.categoryKey);
5849
6256
  const status = this.controller.getStatus(seat.id) ?? "free";
5850
6257
  const statusText = status === "free" ? this.tf("picker.statusAvailable", "available") : status === "held" ? this.tf("picker.statusOnHold", "on hold") : this.tf("picker.statusTaken2", "taken");
5851
- const price = cat ? this.catPrice(cat) : void 0;
6258
+ const priceRange = cat ? this.catPriceRange(cat) : void 0;
6259
+ const priceText = priceRange ? priceRange.min === priceRange.max ? this.money(priceRange.min) : `${this.money(priceRange.min)}\u2013${this.money(priceRange.max)}` : void 0;
5852
6260
  const table = this.controller.tableSelection(seat.id);
5853
6261
  const details = table ?? this.controller.seatDetails(seat.id);
5854
6262
  const typeWord = this.rowTypeWord(details);
5855
6263
  const buyerName = details?.rowLabel ?? details?.displayLabel ?? seat.displayLabel ?? seat.label;
5856
6264
  const identity = table ? `${typeWord} ${buyerName}, ${table.bookingMode === "variable" ? (0, import_core2.t)("picker.minToMaxGuests", { min: table.minOccupancy, max: table.maxOccupancy }) : (0, import_core2.t)("picker.capacityGuests", { count: table.capacity })}` : details?.objectType === "booth" ? `${typeWord} ${buyerName}` : details?.objectType === "table" ? `${typeWord} ${buyerName}, ${(0, import_core2.t)("picker.seatNumberLower", { label: details.seatNumber ?? seat.label })}` : (0, import_core2.t)("picker.seatLabel", { label: details?.displayLabel ?? seat.displayLabel ?? seat.label });
5857
- this.srEl.textContent = `${identity}, ${cat?.label ?? seat.categoryKey}${price != null ? `, ${this.money(price)}` : ""}, ${statusText}`;
6265
+ this.srEl.textContent = `${identity}, ${cat?.label ?? seat.categoryKey}${priceText ? `, ${priceText}` : ""}, ${statusText}`;
5858
6266
  }
5859
6267
  // ---- atomic table confirmation / guest quantity ---------------------------
5860
6268
  showTableDialog(table, held, returnFocus) {
@@ -6021,12 +6429,22 @@ var SeatPicker = class _SeatPicker {
6021
6429
  details?.rowLabel || details?.objectType === "booth" ? `<div class="sl-confirm-field"><span class="sl-confirm-key">${safe(this.rowTypeWord(details))}</span><span class="sl-confirm-value">${safe(this.rowShort(details) ?? details?.displayLabel ?? seat.displayLabel ?? seat.label)}</span></div>` : "",
6022
6430
  details?.objectType !== "booth" ? `<div class="sl-confirm-field"><span class="sl-confirm-key">${this.tf("picker.seat", "Seat")}</span><span class="sl-confirm-value">${safe(details?.seatNumber ?? details?.displayLabel ?? seat.displayLabel ?? seat.label)}</span></div>` : ""
6023
6431
  ].filter(Boolean).join("");
6432
+ const tiers = details?.tiers ?? cat?.tiers ?? [];
6433
+ const selectedTierId = details?.tierId ?? tiers[0]?.id;
6434
+ const tierGuidance = (tier) => tier.buyerMessage?.trim() || (tier.restriction === "companion" ? this.tf("picker.companionRequiresWheelchair", "Requires the adjacent wheelchair place") : void 0);
6435
+ const tierChoices = tiers.length > 1 ? `<fieldset class="sl-confirm-tiers"><legend>${this.tf("picker.ticketType", "Ticket type")}</legend>` + tiers.map((tier) => {
6436
+ const guidance = tierGuidance(tier);
6437
+ return `<button type="button" class="sl-confirm-tier" data-confirm-tier="${safe(tier.id)}" aria-pressed="${tier.id === selectedTierId}"><span class="sl-confirm-tier-copy"><span class="sl-confirm-tier-name">${safe(tier.name)}</span>` + (guidance ? `<small class="sl-confirm-tier-note">${safe(guidance)}</small>` : "") + `</span><span class="sl-confirm-tier-price">${this.money(this.paidPrice(seat.categoryKey, tier.id, tier.price))}</span></button>`;
6438
+ }).join("") + `</fieldset>` : tiers[0] && tierGuidance(tiers[0]) ? `<p class="sl-confirm-tier-note">${safe(tierGuidance(tiers[0]))}</p>` : "";
6024
6439
  const el2 = document.createElement("div");
6025
6440
  el2.className = "sl-confirm";
6026
6441
  el2.setAttribute("role", "dialog");
6027
6442
  el2.setAttribute("aria-label", (0, import_core2.t)("picker.confirmSeatLabel", { label: seat.label }));
6028
- el2.style.setProperty("--sl-cat", cat?.color ?? "#6e7bff");
6029
- el2.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">${(0, import_core2.t)("common.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>${this.tf("picker.select", "Select")}</button></div></div>`;
6443
+ const categoryColor = cat?.color ?? "#6e7bff";
6444
+ const categoryBand = mixThemeColors(categoryColor, this.cssVar("--sl-surface") || "#1a2234", 0.76) ?? categoryColor;
6445
+ el2.style.setProperty("--sl-cat", categoryColor);
6446
+ el2.style.setProperty("--sl-cat-ink", highestContrastInk(categoryBand, "#172033", "#ffffff"));
6447
+ el2.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">` + tierChoices + 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">${(0, import_core2.t)("common.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>${this.tf("picker.select", "Select")}</button></div></div>`;
6030
6448
  this.els.map.appendChild(el2);
6031
6449
  this.confirmEl = el2;
6032
6450
  const thumb = el2.querySelector(".sl-confirm-thumb");
@@ -6042,6 +6460,22 @@ var SeatPicker = class _SeatPicker {
6042
6460
  this.openSeatConfidencePassport(seat, event.currentTarget instanceof HTMLElement ? event.currentTarget : null);
6043
6461
  });
6044
6462
  el2.querySelector(".sl-confirm-compare")?.addEventListener("click", () => this.saveView3dComparisonSeat(seat));
6463
+ el2.querySelectorAll("[data-confirm-tier]").forEach((button) => {
6464
+ button.addEventListener("click", () => {
6465
+ const tierId = button.dataset.confirmTier;
6466
+ const tier = tiers.find((candidate) => candidate.id === tierId);
6467
+ if (!tier) return;
6468
+ this.controller.setSeatTier(seat.id, tier.id);
6469
+ el2.querySelectorAll("[data-confirm-tier]").forEach((candidate) => {
6470
+ candidate.setAttribute("aria-pressed", String(candidate === button));
6471
+ });
6472
+ const priceElement = el2.querySelector(".sl-confirm-price");
6473
+ if (priceElement) {
6474
+ priceElement.textContent = this.money(this.paidPrice(seat.categoryKey, tier.id, tier.price));
6475
+ }
6476
+ this.reanchorConfirm();
6477
+ });
6478
+ });
6045
6479
  el2.querySelector(".sl-confirm-3d")?.addEventListener("click", () => {
6046
6480
  if (this.buyerView === "venue3d") {
6047
6481
  this.dismissConfirm();
@@ -6418,21 +6852,23 @@ var SeatPicker = class _SeatPicker {
6418
6852
  const doc = this.controller.doc;
6419
6853
  if (!doc || !this.els.prices) return;
6420
6854
  const left = this.controller.categoryAvailability();
6421
- this.narrateAvailability(doc.categories, left);
6422
- this.syncSoldout(doc.categories, left);
6855
+ const sellable = doc.categories.filter((c) => !c.notForSale);
6856
+ this.narrateAvailability(sellable, left);
6857
+ this.syncSoldout(sellable, left);
6423
6858
  const PRICE_LIMIT = 5;
6424
- const overflow = doc.categories.length - PRICE_LIMIT;
6859
+ const overflow = sellable.length - PRICE_LIMIT;
6425
6860
  const collapsed = overflow > 1 && !this.pricesExpanded;
6426
- const shown = collapsed ? doc.categories.slice(0, PRICE_LIMIT) : doc.categories;
6861
+ const shown = collapsed ? sellable.slice(0, PRICE_LIMIT) : sellable;
6427
6862
  this.els.prices.classList.toggle("sl-expanded", overflow > 1 && this.pricesExpanded);
6428
6863
  this.els.prices.innerHTML = shown.map((c) => {
6429
6864
  const price = this.catPrice(c);
6865
+ const range = this.catPriceRange(c);
6430
6866
  const offer = this.offerPrice(c.key);
6431
6867
  const previous = offer?.previousPrice != null && offer.previousPrice > offer.price ? offer.previousPrice : null;
6432
6868
  const active = this.focusedCatKey === c.key;
6433
6869
  const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
6434
- return `<div class="sl-price-row${dim ? " sl-dim" : ""}${active ? " sl-active" : ""}" data-cat="${escapeOption(c.key)}" role="button" tabindex="0" aria-pressed="${active}" title="${escapeOption(active ? this.tf("picker.showAllSeats", "Show all seats") : (0, import_core2.t)("picker.showLabelSeatsOnMap", { label: c.label }))}"><span class="sl-dot" style="background:${escapeOption(c.color)}"></span><span class="sl-price-label">${escapeOption(c.label)}` + (offer?.offerName ? `<small class="sl-price-offer">${escapeOption(offer.offerName)}</small>` : "") + `</span><span class="sl-price-left">${(0, import_core2.tCount)("picker.leftCount", left[c.key] ?? 0)}</span>` + (previous != null ? `<span class="sl-price-was">${escapeOption(this.money(previous))}</span>` : "") + (price != null ? `<span class="sl-price-amt">${this.money(price)}</span>` : "") + `</div>`;
6435
- }).join("") + (overflow > 1 ? `<button type="button" class="sl-price-more" aria-expanded="${!collapsed}">` + (collapsed ? (0, import_core2.t)("picker.showAllTicketTypes", { count: doc.categories.length }) : this.tf("picker.showFewer", "Show fewer")) + `</button>` : "") + `<div class="sl-status-key" aria-label="${this.tf("picker.seatStatusLegend", "Seat status legend")}"><span class="sl-status-item"><i class="sl-status-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="5" y="10" width="14" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></i>${this.tf("picker.temporarilyHeld", "Temporarily held")}</span><span class="sl-status-item"><i class="sl-status-icon sold" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M7 17L17 7"/></svg></i>${this.tf("picker.sold", "Sold")}</span></div>`;
6870
+ return `<div class="sl-price-row${dim ? " sl-dim" : ""}${active ? " sl-active" : ""}" data-cat="${escapeOption(c.key)}" role="button" tabindex="0" aria-pressed="${active}" title="${escapeOption(active ? this.tf("picker.showAllSeats", "Show all seats") : (0, import_core2.t)("picker.showLabelSeatsOnMap", { label: c.label }))}"><span class="sl-dot" style="background:${escapeOption(c.color)}"></span><span class="sl-price-label">${escapeOption(c.label)}` + (offer?.offerName ? `<small class="sl-price-offer">${escapeOption(offer.offerName)}</small>` : "") + `</span><span class="sl-price-left">${(0, import_core2.tCount)("picker.leftCount", left[c.key] ?? 0)}</span>` + (previous != null ? `<span class="sl-price-was">${escapeOption(this.money(previous))}</span>` : "") + (range != null ? `<span class="sl-price-amt">${range.min === range.max ? this.money(range.min) : `${this.money(range.min)}\u2013${this.money(range.max)}`}</span>` : price != null ? `<span class="sl-price-amt">${this.money(price)}</span>` : "") + `</div>`;
6871
+ }).join("") + (overflow > 1 ? `<button type="button" class="sl-price-more" aria-expanded="${!collapsed}">` + (collapsed ? (0, import_core2.t)("picker.showAllTicketTypes", { count: sellable.length }) : this.tf("picker.showFewer", "Show fewer")) + `</button>` : "") + `<div class="sl-status-key" aria-label="${this.tf("picker.seatStatusLegend", "Seat status legend")}"><span class="sl-status-item"><i class="sl-status-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="5" y="10" width="14" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></i>${this.tf("picker.temporarilyHeld", "Temporarily held")}</span><span class="sl-status-item"><i class="sl-status-icon sold" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M7 17L17 7"/></svg></i>${this.tf("picker.sold", "Sold")}</span></div>`;
6436
6872
  this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
6437
6873
  row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
6438
6874
  row.addEventListener("mouseleave", () => this.controller.getRenderer()?.setCategoryHighlight?.(null));
@@ -6536,7 +6972,7 @@ var SeatPicker = class _SeatPicker {
6536
6972
  const noPicks = !seats.length && !heldItems.length && !this.pendingGACount();
6537
6973
  if (noPicks) this.baReopen = false;
6538
6974
  if (!this.salesClosed && !this.hold && (noPicks || this.baReopen || this.bestAvailableBusy || this.bestAvailableConfirm)) {
6539
- const cats = this.controller.doc?.categories ?? [];
6975
+ const cats = (this.controller.doc?.categories ?? []).filter((c) => !c.notForSale);
6540
6976
  const zones = this.controller.getBestAvailableZones();
6541
6977
  if (this.baZone && !zones.some((zone) => zone.id === this.baZone)) this.baZone = "";
6542
6978
  parts.push(this.bestAvailableConfirm ? `<div class="sl-ba" role="alert"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>${this.tf("picker.replaceCurrentChoices", "Replace your current choices?")}</div><div class="sl-ba-replace"><b>${(0, import_core2.t)("picker.willFindSeatsTogether", { count: this.baQty })}</b><span>${this.tf("picker.manualTicketsRemovedNote", "Your manually selected tickets will be removed only after a new group is secured.")}</span></div><div class="sl-ba-actions"><button type="button" data-ba-cancel>${this.tf("picker.keepMine", "Keep mine")}</button><button type="button" class="replace" data-ba-replace>${this.tf("picker.findNewSeats", "Find new seats")}</button></div></div>` : `<div class="sl-ba"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>${this.tf("picker.findBestSeatsTogether", "Find the best seats together")}</div><div class="sl-ba-copy"><span class="wide">${this.tf("picker.willChooseClosestGroup", "We\u2019ll choose the closest available group for you.")}</span><span class="narrow">${this.tf("picker.closestGroupChosenInstantly", "Closest available group, chosen instantly.")}</span></div>` + // Premium quick-pick — present only when the chart actually has premium
@@ -6592,8 +7028,10 @@ var SeatPicker = class _SeatPicker {
6592
7028
  nextTrayKeys.add(itemKey);
6593
7029
  const cat = this.controller.doc?.categories.find((c) => c.key === s.categoryKey);
6594
7030
  const tierSelect = s.tiers && s.tiers.length ? `<select class="tier" data-tier="${s.id}" aria-label="${(0, import_core2.t)("picker.ticketTierFor", { label: s.label })}">` + s.tiers.map((ti) => `<option value="${ti.id}"${ti.id === s.tierId ? " selected" : ""}>${ti.name} \xB7 ${this.money(this.paidPrice(s.categoryKey, ti.id, ti.price))}</option>`).join("") + `</select>` : "";
7031
+ const chosenTier = s.tiers?.find((tier) => tier.id === s.tierId) ?? s.tiers?.[0];
7032
+ const tierNote = chosenTier?.buyerMessage?.trim() || (chosenTier?.restriction === "companion" ? this.tf("picker.companionRequiresWheelchair", "Requires the adjacent wheelchair place") : "");
6595
7033
  parts.push(
6596
- `<div class="sl-chip${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-seat="${s.id}" data-locate="${s.id}"><div class="sl-chip-main">` + idGrid(s.id, s.label, s.objectType, s.quantity ?? 1, s.objectId, s) + `<div class="sl-chip-sub"><span class="sl-ticket-state" aria-label="${this.tf("picker.selected", "Selected")}" title="${this.tf("picker.selected", "Selected")}"><svg viewBox="0 0 24 24"><path d="M5 12l4 4L19 6"/></svg></span><span class="cat">${cat?.label ?? s.categoryKey}</span>` + (s.objectType === "table" && s.bookingMode === "variable" ? `<button type="button" class="sl-table-edit" data-table-edit="${encodeURIComponent(s.label)}">${(0, import_core2.t)("picker.guestsCountEdit", { count: s.quantity ?? 1 })}</button>` : "") + `${this.wheelchairChipMarker(s.wheelchairSpaceType)}${this.commercialChipMarker(s.commercial)}${tierSelect}<span class="amt">${this.money(this.paidPrice(s.categoryKey, s.tierId ?? null, s.price) * (s.quantity ?? 1))}</span></div></div>` + iconRail((0, import_core2.t)("picker.removeSeatLabel", { label: s.label }), canView && s.objectType !== "table" ? s.label : null) + `</div>`
7034
+ `<div class="sl-chip${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-seat="${s.id}" data-locate="${s.id}"><div class="sl-chip-main">` + idGrid(s.id, s.label, s.objectType, s.quantity ?? 1, s.objectId, s) + `<div class="sl-chip-sub"><span class="sl-ticket-state" aria-label="${this.tf("picker.selected", "Selected")}" title="${this.tf("picker.selected", "Selected")}"><svg viewBox="0 0 24 24"><path d="M5 12l4 4L19 6"/></svg></span><span class="cat">${cat?.label ?? s.categoryKey}</span>` + (s.objectType === "table" && s.bookingMode === "variable" ? `<button type="button" class="sl-table-edit" data-table-edit="${encodeURIComponent(s.label)}">${(0, import_core2.t)("picker.guestsCountEdit", { count: s.quantity ?? 1 })}</button>` : "") + `${this.wheelchairChipMarker(s.wheelchairSpaceType)}${this.commercialChipMarker(s.commercial)}${tierSelect}<span class="amt">${this.money(this.paidPrice(s.categoryKey, s.tierId ?? null, s.price) * (s.quantity ?? 1))}</span></div>` + (tierNote ? `<small class="sl-tier-note">${escapeOption(tierNote)}</small>` : "") + `</div>` + iconRail((0, import_core2.t)("picker.removeSeatLabel", { label: s.label }), canView && s.objectType !== "table" ? s.label : null) + `</div>`
6597
7035
  );
6598
7036
  }
6599
7037
  for (const area of gaAreas) {
@@ -6906,7 +7344,7 @@ var SeatPicker = class _SeatPicker {
6906
7344
  const problem = err;
6907
7345
  const labels = (problem.conflicts ?? []).map((conflict) => conflict.label).filter(Boolean).slice(0, 3);
6908
7346
  if (problem.reason === "event_closed") this.setSalesClosed(true);
6909
- const message = problem.reason === "event_closed" ? this.tf("picker.seatSalesClosedForEvent", "Seat sales have closed for this event.") : labels.length ? labels.length === 1 ? (0, import_core2.t)("picker.labelsNoLongerAvailable.one", { labels: labels.join(", ") }) : (0, import_core2.t)("picker.labelsNoLongerAvailable.other", { labels: labels.join(", ") }) : this.tf("picker.seatsJustTaken", "One or more seats were just taken. Please pick again.");
7347
+ const message = problem.reason === "event_closed" ? this.tf("picker.seatSalesClosedForEvent", "Seat sales have closed for this event.") : problem.reason === "companion_pair_required" ? this.tf("picker.companionPairRequired", "Select the adjacent wheelchair place with each companion ticket.") : problem.reason === "ticket_option_not_applicable" ? this.tf("picker.ticketOptionNotApplicable", "That ticket choice is not available for the selected seat.") : labels.length ? labels.length === 1 ? (0, import_core2.t)("picker.labelsNoLongerAvailable.one", { labels: labels.join(", ") }) : (0, import_core2.t)("picker.labelsNoLongerAvailable.other", { labels: labels.join(", ") }) : this.tf("picker.seatsJustTaken", "One or more seats were just taken. Please pick again.");
6910
7348
  this.toast(message, "error");
6911
7349
  this.setCtaPhase("idle");
6912
7350
  } finally {
@@ -7235,13 +7673,14 @@ var SeatPicker = class _SeatPicker {
7235
7673
  const isBooth = details.objectType === "booth";
7236
7674
  const hasLoc = details.sectionLabel || details.rowLabel || details.seatNumber;
7237
7675
  const grid = isGroupedTable ? `<div class="sl-tip-grid"><div class="sl-tip-field"><span class="sl-tip-key">${esc(this.rowTypeWord(details))}</span><span class="sl-tip-val">${esc(details.rowLabel ?? details.displayLabel ?? details.label)}</span></div><div class="sl-tip-field"><span class="sl-tip-key">${this.tf("picker.guests", "Guests")}</span><span class="sl-tip-val">${details.bookingMode === "variable" ? `${details.minOccupancy}\u2013${details.maxOccupancy}` : details.capacity}</span></div></div>` : isBooth ? `<div class="sl-tip-grid">` + (details.sectionLabel ? `<div class="sl-tip-field"><span class="sl-tip-key">${this.tf("picker.section", "Section")}</span><span class="sl-tip-val">${esc(details.sectionLabel)}</span></div>` : "") + `<div class="sl-tip-field"><span class="sl-tip-key">${esc(this.rowTypeWord(details))}</span><span class="sl-tip-val">${esc(details.rowLabel ?? details.displayLabel ?? details.label)}</span></div></div>` : hasLoc ? `<div class="sl-tip-grid">` + (details.sectionLabel ? `<div class="sl-tip-field"><span class="sl-tip-key">${this.tf("picker.section", "Section")}</span><span class="sl-tip-val">${esc(details.sectionLabel)}</span></div>` : "") + (details.rowLabel ? `<div class="sl-tip-field"><span class="sl-tip-key">${esc(this.rowTypeWord(details))}</span><span class="sl-tip-val">${esc(this.rowShort(details))}</span></div>` : "") + (details.seatNumber ? `<div class="sl-tip-field"><span class="sl-tip-key">${this.tf("picker.seat", "Seat")}</span><span class="sl-tip-val">${esc(details.seatNumber)}</span></div>` : "") + `</div>` : `<div class="sl-tip-grid one"><div class="sl-tip-field"><span class="sl-tip-key">${this.tf("picker.seat", "Seat")}</span><span class="sl-tip-val">${esc(details.displayLabel ?? details.label)}</span></div></div>`;
7238
- const statusLine = details.status === "free" ? "" : `<div class="sl-tip-status">${details.status === "held" ? (0, import_core2.t)("map.statusHeld") : (0, import_core2.t)("map.statusTaken")}</div>`;
7676
+ const notForSale = details.status === "not_for_sale";
7677
+ const statusLine = details.status === "free" ? "" : `<div class="sl-tip-status">${notForSale ? this.tf("picker.notAvailable", "Not available") : details.status === "held" ? (0, import_core2.t)("map.statusHeld") : (0, import_core2.t)("map.statusTaken")}</div>`;
7239
7678
  const limited = this.limitedViewLabel(details.commercial);
7240
7679
  const cxLine = limited ? `<div class="sl-tip-cx"><span class="g" aria-hidden="true">\u25D0</span>${esc(limited)}</div>` : "";
7241
7680
  const wheelchair = this.wheelchairProvisionLabel(details.wheelchairSpaceType);
7242
7681
  const wheelchairLine = wheelchair ? `<div class="sl-tip-cx"><span class="g" aria-hidden="true">\u267F</span>${esc(wheelchair)}</div>` : "";
7243
7682
  this.tipEl.style.setProperty("--sl-cat", details.categoryColor);
7244
- this.tipEl.innerHTML = grid + `<div class="sl-tip-cat"><span class="sl-tip-dot" style="background:${details.categoryColor}"></span><span class="sl-tip-name">${esc(details.categoryLabel)}</span><span class="sl-tip-amt">${price}</span></div>` + wheelchairLine + cxLine + statusLine;
7683
+ this.tipEl.innerHTML = grid + `<div class="sl-tip-cat"><span class="sl-tip-dot" style="background:${details.categoryColor}"></span><span class="sl-tip-name">${esc(details.categoryLabel)}</span>` + (notForSale ? "" : `<span class="sl-tip-amt">${price}</span>`) + `</div>` + wheelchairLine + cxLine + statusLine;
7245
7684
  this.tipEl.style.display = "block";
7246
7685
  this.placeTooltip();
7247
7686
  }
@@ -7464,13 +7903,16 @@ var SeatPicker = class _SeatPicker {
7464
7903
  sourceLabel: this.tf("picker.chartDerivedModel", "Chart-derived model")
7465
7904
  };
7466
7905
  }
7467
- /** Route the module's decoupled analytics into the host callback, tagged buyer. */
7468
- emit3dAnalytics(event, props) {
7906
+ /** Route decoupled analytics into the host callback, tagged buyer. */
7907
+ emitAnalytics(event, props) {
7469
7908
  try {
7470
7909
  this.opts.onAnalytics?.(event, { ...props, surface: "buyer" });
7471
7910
  } catch {
7472
7911
  }
7473
7912
  }
7913
+ emit3dAnalytics(event, props) {
7914
+ this.emitAnalytics(event, props);
7915
+ }
7474
7916
  /** A 3D seat tap runs the SAME selection path as a 2D tap: toggle through the
7475
7917
  * controller, then raise the shared confirm card (bottom-sheeted in 3D). */
7476
7918
  onView3dSeatPick(seatId) {
@@ -7489,9 +7931,9 @@ var SeatPicker = class _SeatPicker {
7489
7931
  this.syncSelectionTo3d();
7490
7932
  return;
7491
7933
  }
7492
- const visualState = this.seatState3dFor(seat);
7493
- if (visualState !== "available") {
7494
- this.showUnavailable3dSeat(seat, visualState);
7934
+ const status = this.controller.getStatus(seat.id);
7935
+ if (status === "held" || status === "booked" || status === "not_for_sale") {
7936
+ this.showUnavailable3dSeat(seat, this.seatState3dFor(seat), status);
7495
7937
  this.syncSelectionTo3d();
7496
7938
  return;
7497
7939
  }
@@ -7513,14 +7955,14 @@ var SeatPicker = class _SeatPicker {
7513
7955
  else this.syncTray();
7514
7956
  this.syncSelectionTo3d();
7515
7957
  }
7516
- /** Explain a visible-but-unselectable 3D seat without entering the booking
7517
- * flow. Category colour remains visible in the venue; this card names the
7518
- * availability state explicitly so yellow never has to carry both meanings. */
7519
- showUnavailable3dSeat(seat, visualState) {
7958
+ /** Explain a 3D seat that INVENTORY refuses — never one the buyer's own
7959
+ * filters merely dimmed, which stays buyable exactly as it is in 2D. Category
7960
+ * colour remains visible in the venue; this card names the availability state
7961
+ * explicitly so yellow never has to carry both meanings. */
7962
+ showUnavailable3dSeat(seat, visualState, status) {
7520
7963
  const overlay = this.view3dEl;
7521
7964
  if (!overlay) return;
7522
7965
  this.dismissUnavailable3dSeat();
7523
- const status = this.controller.getStatus(seat.id);
7524
7966
  const details = this.controller.seatDetails(seat.id);
7525
7967
  const identity = details?.displayLabel ?? seat.displayLabel ?? seat.label;
7526
7968
  const section = details?.sectionLabel;
@@ -7532,12 +7974,9 @@ var SeatPicker = class _SeatPicker {
7532
7974
  } : status === "booked" ? {
7533
7975
  title: this.tf("picker.sold", "Sold"),
7534
7976
  message: this.tf("picker.soldSeatExplanation", "This seat has already been booked.")
7535
- } : status === "not_for_sale" ? {
7977
+ } : {
7536
7978
  title: this.tf("picker.notForSale", "Not for sale"),
7537
7979
  message: this.tf("picker.notForSaleExplanation", "This seat is not included in the current sale.")
7538
- } : {
7539
- title: this.tf("picker.filteredSeat", "Unavailable with current filters"),
7540
- message: this.tf("picker.filteredSeatExplanation", "Change the active price or view filters to make this seat selectable.")
7541
7980
  };
7542
7981
  const card = document.createElement("div");
7543
7982
  card.className = "sl-view3d-unavailable";
@@ -7915,7 +8354,7 @@ var SeatPicker = class _SeatPicker {
7915
8354
  const floors = handle.floors();
7916
8355
  const floorLabels = new Set(floors.map((floor) => floor.label.trim().toLocaleLowerCase()));
7917
8356
  const zones = handle.zones().filter((zone) => zone.seatCount > 0 && !floorLabels.has(zone.label.trim().toLocaleLowerCase()));
7918
- const allSections = handle.sections().filter((s) => s.seatCount > 0);
8357
+ const allSections = handle.sections().filter((section) => section.seatCount > 0 && !floorLabels.has(section.label.trim().toLocaleLowerCase()));
7919
8358
  const sections = zones.length > 1 ? [] : allSections;
7920
8359
  const wantFloors = floors.length > 1;
7921
8360
  const wantZones = zones.length > 1;
@@ -7942,16 +8381,24 @@ var SeatPicker = class _SeatPicker {
7942
8381
  });
7943
8382
  setFinderOpen(false);
7944
8383
  nav.appendChild(finderToggle);
8384
+ let syncAreasForFloor = (_index) => void 0;
8385
+ let syncPressedFloor = (_index) => void 0;
7945
8386
  if (wantFloors) {
7946
8387
  const row = document.createElement("div");
7947
8388
  row.setAttribute("role", "group");
7948
8389
  row.setAttribute("aria-label", this.tf("picker.levels", "Levels"));
7949
8390
  const pills = [];
8391
+ syncPressedFloor = (index) => {
8392
+ pills.forEach((pill) => {
8393
+ pill.setAttribute("aria-pressed", String(
8394
+ (pill.dataset.floor === "" ? null : Number(pill.dataset.floor)) === index
8395
+ ));
8396
+ });
8397
+ };
7950
8398
  const select = (index) => {
7951
8399
  if (!handle.focusFloor(index)) return;
7952
- pills.forEach((p) => {
7953
- p.setAttribute("aria-pressed", String((p.dataset.floor === "" ? null : Number(p.dataset.floor)) === index));
7954
- });
8400
+ syncPressedFloor(index);
8401
+ syncAreasForFloor(index);
7955
8402
  };
7956
8403
  const add = (label, index) => {
7957
8404
  const b = document.createElement("button");
@@ -7976,39 +8423,55 @@ var SeatPicker = class _SeatPicker {
7976
8423
  row.setAttribute("aria-label", this.tf("picker.areas", "Areas"));
7977
8424
  const entries = wantZones ? zones.map((z) => ({ id: z.id, label: z.label || z.id, go: () => {
7978
8425
  handle.focusZone(z.id);
7979
- } })) : sections.map((s) => ({ id: s.id, label: s.label || s.id, go: () => {
7980
- handle.focusSection(s.id);
7981
- } }));
7982
- if (!wantZones && entries.length > MAX_3D_SECTION_PILLS) {
7983
- const select = document.createElement("select");
7984
- select.setAttribute("aria-label", this.tf("picker.jumpToSection", "Jump to section"));
7985
- const placeholder = document.createElement("option");
7986
- placeholder.value = "";
7987
- placeholder.textContent = this.tf("picker.jumpToSection", "Jump to section");
7988
- select.appendChild(placeholder);
7989
- for (const entry of entries) {
7990
- const option = document.createElement("option");
7991
- option.value = entry.id;
7992
- option.textContent = entry.label;
7993
- select.appendChild(option);
8426
+ } })) : sections.map((s) => ({
8427
+ id: s.id,
8428
+ label: s.label || s.id,
8429
+ floorIndex: s.floorIndex,
8430
+ go: () => {
8431
+ handle.focusSection(s.id);
8432
+ if (Number.isFinite(s.floorIndex)) syncPressedFloor(s.floorIndex);
7994
8433
  }
7995
- select.addEventListener("change", () => {
7996
- entries.find((entry) => entry.id === select.value)?.go();
7997
- });
7998
- row.appendChild(select);
7999
- } else {
8000
- for (const e of entries) {
8001
- const b = document.createElement("button");
8002
- b.type = "button";
8003
- b.textContent = e.label;
8004
- b.addEventListener("click", () => {
8005
- e.go();
8434
+ }));
8435
+ const renderEntries = (floorIndex) => {
8436
+ const visible = wantZones || floorIndex === null ? entries : entries.filter((entry) => entry.floorIndex === void 0 || entry.floorIndex === floorIndex);
8437
+ row.replaceChildren();
8438
+ if (!visible.length) {
8439
+ row.remove();
8440
+ return;
8441
+ }
8442
+ if (!row.isConnected) nav.appendChild(row);
8443
+ if (!wantZones && visible.length > MAX_3D_SECTION_PILLS) {
8444
+ const select = document.createElement("select");
8445
+ select.setAttribute("aria-label", this.tf("picker.jumpToSection", "Jump to section"));
8446
+ const placeholder = document.createElement("option");
8447
+ placeholder.value = "";
8448
+ placeholder.textContent = this.tf("picker.jumpToSection", "Jump to section");
8449
+ select.appendChild(placeholder);
8450
+ for (const entry of visible) {
8451
+ const option = document.createElement("option");
8452
+ option.value = entry.id;
8453
+ option.textContent = entry.label;
8454
+ select.appendChild(option);
8455
+ }
8456
+ select.addEventListener("change", () => {
8457
+ visible.find((entry) => entry.id === select.value)?.go();
8458
+ });
8459
+ row.appendChild(select);
8460
+ return;
8461
+ }
8462
+ for (const entry of visible) {
8463
+ const button = document.createElement("button");
8464
+ button.type = "button";
8465
+ button.textContent = entry.label;
8466
+ button.addEventListener("click", () => {
8467
+ entry.go();
8006
8468
  setFinderOpen(false);
8007
8469
  });
8008
- row.appendChild(b);
8470
+ row.appendChild(button);
8009
8471
  }
8010
- }
8011
- nav.appendChild(row);
8472
+ };
8473
+ syncAreasForFloor = renderEntries;
8474
+ renderEntries(null);
8012
8475
  }
8013
8476
  overlay.appendChild(nav);
8014
8477
  }
@@ -8207,7 +8670,7 @@ var SeatPicker = class _SeatPicker {
8207
8670
  } catch (err) {
8208
8671
  this.opts.onError?.(err);
8209
8672
  const reason = err?.reason;
8210
- const message = reason === "not_enough_together" ? (0, import_core2.t)("picker.couldNotFindSeatsTogether", { count: qty }) : reason === "sold_out" ? this.tf("picker.ticketTypeSoldOut", "That ticket type is sold out. Try another ticket type.") : reason === "event_closed" ? this.tf("picker.seatSalesClosedForEvent", "Seat sales have closed for this event.") : this.tf("picker.seatsNoLongerAvailableTryAnother", "Those seats are no longer available. Try another quantity or ticket type.");
8673
+ const message = reason === "not_enough_together" ? (0, import_core2.t)("picker.couldNotFindSeatsTogether", { count: qty }) : reason === "companion_pair_required" ? this.tf("picker.companionPairRequired", "Select the adjacent wheelchair place with each companion ticket.") : reason === "ticket_option_not_applicable" ? this.tf("picker.ticketOptionNotApplicable", "That ticket choice is not available for the selected seat.") : reason === "sold_out" ? this.tf("picker.ticketTypeSoldOut", "That ticket type is sold out. Try another ticket type.") : reason === "event_closed" ? this.tf("picker.seatSalesClosedForEvent", "Seat sales have closed for this event.") : this.tf("picker.seatsNoLongerAvailableTryAnother", "Those seats are no longer available. Try another quantity or ticket type.");
8211
8674
  this.toast(message, "error");
8212
8675
  return null;
8213
8676
  } finally {