@seatlayer/js 0.50.2 → 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",
@@ -2659,6 +2662,16 @@ var DEFAULT_MAX_SELECTION2 = 10;
2659
2662
  var EXTEND_PROMPT_MS = 6e4;
2660
2663
  var MAX_3D_SEATS_DEFAULT = 6e4;
2661
2664
  var MAX_3D_SECTION_PILLS = 12;
2665
+ function tfStatic(key, fallback) {
2666
+ const v = (0, import_core2.t)(key);
2667
+ return v === key ? fallback : v;
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
+ }
2662
2675
  function pointInPolygon(x, y, poly) {
2663
2676
  let inside = false;
2664
2677
  for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
@@ -2761,9 +2774,13 @@ var CSS2 = (
2761
2774
  the WIDGET's meanings, not an org's brand: premium gold, environment
2762
2775
  warning, danger, success. Hosts can still override them with CSS. */
2763
2776
  --sl-premium:#e8c15a;--sl-premium-deep:#c9a24b;--sl-premium-ink:#1c1608;
2764
- --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}
2765
2780
  .sl-picker *{box-sizing:border-box;margin:0;padding:0}
2766
- .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}
2767
2784
 
2768
2785
  /* header */
2769
2786
  .sl-head{display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--sl-line);flex:none}
@@ -2833,11 +2850,30 @@ var CSS2 = (
2833
2850
  .sl-picker[data-layout="narrow"] .sl-map{min-height:0;flex:1}
2834
2851
  .sl-picker[data-layout="narrow"] .sl-side{width:100%;border-left:0;border-top:1px solid var(--sl-line);
2835
2852
  flex:none;height:min(72%,480px);overflow:hidden;transition:height .3s cubic-bezier(.2,.8,.2,1);overscroll-behavior:contain}
2836
- .sl-picker[data-layout="narrow"][data-sheet="open"][data-has-selection="false"] .sl-side{height:min(252px,52%)}
2853
+ .sl-picker[data-layout="narrow"][data-sheet="open"][data-has-selection="false"] .sl-side{height:min(380px,64%)}
2854
+ /* With the price list expanded the sheet takes the with-selection height so
2855
+ the tray (cart + best-seats) is never crushed under the fixed-size list. */
2856
+ .sl-picker[data-layout="narrow"][data-sheet="open"][data-prices-open="true"] .sl-side{height:min(480px,74%)}
2837
2857
  .sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-side{height:76px;overflow:hidden}
2838
2858
  .sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-side > :not(.sl-sheet-head){display:none}
2839
- .sl-picker[data-layout="narrow"] .sl-tray{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}
2859
+ .sl-picker[data-layout="narrow"] .sl-tray{flex:1;min-height:96px;overflow-y:auto;overscroll-behavior:contain}
2860
+ .sl-picker[data-layout="narrow"][data-prices-open="true"] .sl-tray{min-height:48px}
2861
+ .sl-picker[data-layout="narrow"] .sl-prices{max-height:150px;overflow-y:auto;overscroll-behavior:contain}
2840
2862
  .sl-picker[data-layout="narrow"] .sl-foot{position:static;background:var(--sl-bg)}
2863
+ /* NARROW FOOT SAYS EVERYTHING ONCE (owner report: with a hold the footer ate
2864
+ ~190px and the ticket list got a sliver). The sheet head already carries
2865
+ "N tickets \xB7 $total" and the header pill already ticks the clock \u2014 so the
2866
+ total row hides, the secured note collapses to one compact line, and the
2867
+ badge tucks in. The tray gets the pixels; a thin scrollbar shows there is
2868
+ more to scroll. */
2869
+ .sl-picker[data-layout="narrow"] .sl-total{display:none}
2870
+ .sl-picker[data-layout="narrow"] .sl-foot{padding:8px 12px 8px}
2871
+ .sl-picker[data-layout="narrow"] .sl-hold-note{margin-bottom:6px;padding:5px 9px}
2872
+ .sl-picker[data-layout="narrow"] .sl-hold-note svg{display:none}
2873
+ .sl-picker[data-layout="narrow"] .sl-hold-note b{display:inline;margin-right:6px;font-size:11px}
2874
+ .sl-picker[data-layout="narrow"] .sl-hold-copy{display:inline;white-space:normal;font-size:10.5px}
2875
+ .sl-picker[data-layout="narrow"] .sl-powered{margin-top:4px;padding:2px 8px;font-size:11px}
2876
+ .sl-picker[data-layout="narrow"] .sl-tray{scrollbar-width:thin}
2841
2877
  .sl-picker[data-layout="narrow"] .sl-foot.empty{display:none}
2842
2878
  .sl-picker[data-layout="narrow"] .sl-sheet-head{order:0}
2843
2879
  .sl-picker[data-layout="narrow"] .sl-seats-sec{display:none}
@@ -2845,6 +2881,7 @@ var CSS2 = (
2845
2881
  .sl-picker[data-layout="narrow"] .sl-filtersec{order:3}
2846
2882
  .sl-picker[data-layout="narrow"] .sl-filters{order:4}
2847
2883
  .sl-picker[data-layout="narrow"] .sl-prices-sec{order:5}
2884
+ .sl-picker[data-layout="narrow"] .sl-prices-hint{order:6}
2848
2885
  .sl-picker[data-layout="narrow"] .sl-pricef{order:6}
2849
2886
  .sl-picker[data-layout="narrow"] .sl-prices{order:7}
2850
2887
  .sl-picker[data-layout="narrow"] .sl-foot{order:8}
@@ -2852,6 +2889,7 @@ var CSS2 = (
2852
2889
  .sl-picker[data-layout="narrow"] .sl-filtersec,
2853
2890
  .sl-picker[data-layout="narrow"] .sl-filters,
2854
2891
  .sl-picker[data-layout="narrow"] .sl-prices-sec,
2892
+ .sl-picker[data-layout="narrow"] .sl-prices-hint,
2855
2893
  .sl-picker[data-layout="narrow"] .sl-prices{display:none!important}
2856
2894
  .sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filtersec,
2857
2895
  .sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filters,
@@ -2869,7 +2907,13 @@ var CSS2 = (
2869
2907
  head is the tap/swipe toggle target (min 44px), so it reads as one control. */
2870
2908
  .sl-sheet-head{display:none;flex-direction:column;justify-content:center;padding:6px 12px 8px;min-height:56px;
2871
2909
  cursor:pointer;touch-action:none;user-select:none;-webkit-user-select:none;flex:none}
2872
- .sl-picker[data-layout="narrow"] .sl-sheet-head{display:flex;min-height:64px;padding:4px 10px 6px}
2910
+ .sl-picker[data-layout="narrow"] .sl-sheet-head{display:flex;min-height:52px;padding:3px 10px 5px}
2911
+ /* OPEN sheet: the head is context, not controls \u2014 the pill it carries at peek
2912
+ ("Best seats"/"Continue") duplicates the card and CTA right below it, and
2913
+ every saved pixel is tray room (owner call 2026-08-14). */
2914
+ .sl-picker[data-layout="narrow"][data-sheet="open"] .sl-sheet-head{min-height:42px}
2915
+ .sl-picker[data-layout="narrow"][data-sheet="open"] .sl-sheet-bar{min-height:32px}
2916
+ .sl-picker[data-layout="narrow"][data-sheet="open"] .sl-sheet-peek .go{display:none}
2873
2917
  .sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-sheet-head{height:100%}
2874
2918
  .sl-sheet-grab{width:36px;height:4px;border-radius:999px;background:var(--sl-muted);opacity:.55;margin:1px auto 5px}
2875
2919
  .sl-sheet-bar{display:flex;align-items:center;gap:8px;min-height:44px}
@@ -2910,9 +2954,19 @@ var CSS2 = (
2910
2954
  sheet brings it back, exactly like the filters row above. The tray hint comes
2911
2955
  back with it: the first-time buyer on a phone needs the instruction more than
2912
2956
  the desktop buyer, not less. */
2913
- .sl-picker[data-layout="narrow"][data-sheet="open"] .sl-prices-sec{display:flex!important}
2914
- .sl-picker[data-layout="narrow"][data-sheet="open"] .sl-prices{display:flex!important}
2957
+ .sl-picker[data-layout="narrow"][data-sheet="open"][data-prices-open="true"] .sl-prices-sec{display:flex!important}
2958
+ /* MOBILE SPACE BELONGS TO THE CART (owner call 2026-08-14): the price list is
2959
+ one tap away behind the "Ticket prices" row, not a permanent tenant. Zone
2960
+ flags and section taps already answer "what does it cost" in context. */
2961
+ .sl-picker[data-layout="narrow"][data-sheet="open"][data-prices-open="true"] .sl-prices-hint{display:block!important}
2962
+ .sl-picker[data-layout="narrow"][data-sheet="open"][data-prices-open="true"] .sl-prices{display:flex!important}
2915
2963
  .sl-picker[data-layout="narrow"][data-sheet="open"] .sl-tray-hint{display:block!important}
2964
+ /* The row reads as the toggle it is on narrow: chevron + pointer. */
2965
+ .sl-picker[data-layout="narrow"] .sl-prices-sec{cursor:pointer}
2966
+ .sl-picker[data-layout="narrow"] .sl-prices-sec::after{content:'';width:8px;height:8px;flex:none;margin-left:auto;
2967
+ border-right:2px solid var(--sl-muted);border-bottom:2px solid var(--sl-muted);
2968
+ transform:rotate(45deg);transition:transform .2s cubic-bezier(.2,.8,.2,1)}
2969
+ .sl-picker[data-layout="narrow"][data-prices-open="true"] .sl-prices-sec::after{transform:rotate(-135deg)}
2916
2970
  .sl-cbbtn{width:32px;height:32px;border-radius:999px;background:var(--sl-surface);border:1px solid var(--sl-line);
2917
2971
  color:var(--sl-text);display:flex;align-items:center;justify-content:center;transition:border-color .15s}
2918
2972
  .sl-cbbtn:hover{border-color:var(--sl-muted)}
@@ -2922,24 +2976,30 @@ var CSS2 = (
2922
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));
2923
2977
  border-radius:12px;background:color-mix(in srgb,var(--sl-accent) 8%,var(--sl-surface));color:var(--sl-text)}
2924
2978
  .sl-offer.has{display:block}.sl-offer-main{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
2925
- .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)}
2926
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)}
2927
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;
2928
2982
  display:grid;place-items:center;cursor:pointer;font-size:12px;font-weight:850;color:var(--sl-text);background:var(--sl-surface)}
2929
- .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)}
2930
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)}
2931
2985
  .sl-sec{padding:14px 14px 4px;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
2932
2986
  .sl-prices-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
2933
2987
  .sl-price-select{min-height:32px;max-width:130px;padding:5px 28px 5px 9px;border:1px solid var(--sl-line);border-radius:9px;
2934
- background:var(--sl-surface);color:var(--sl-text);font:inherit;font-size:11px;font-weight:750;letter-spacing:0;text-transform:none}
2988
+ background-color:var(--sl-surface);color:var(--sl-text);font:inherit;font-size:11px;font-weight:750;letter-spacing:0;text-transform:none}
2989
+ /* One sentence that teaches the whole colour code \u2014 a first-time buyer sees
2990
+ "Head Tables \xB7 $165" and coloured dots with nothing connecting them. Gone
2991
+ the moment they have seats (they've learned it) and while sales are closed. */
2992
+ .sl-prices-hint{padding:0 14px 7px;font-size:11px;line-height:1.45;color:var(--sl-muted)}
2993
+ .sl-picker[data-has-selection="true"] .sl-prices-hint,
2994
+ .sl-picker[data-sales-closed="true"] .sl-prices-hint{display:none}
2935
2995
  .sl-prices{display:flex;flex-direction:column;padding:4px 14px 8px;border-bottom:1px solid var(--sl-line)}
2936
2996
  .sl-prices-sec,.sl-prices,.sl-seats-sec{flex:none}
2937
2997
  .sl-price-row{display:flex;align-items:center;gap:7px;min-height:28px;font-size:12px;
2938
2998
  padding:0 6px;margin:0 -6px;border-radius:8px;cursor:pointer;transition:background .15s}
2939
2999
  .sl-price-row:hover,.sl-price-row:focus-visible{background:color-mix(in srgb,var(--sl-line) 40%,transparent)}
2940
3000
  .sl-price-row.sl-active{background:color-mix(in srgb,var(--sl-accent) 9%,transparent)}
2941
- .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}
2942
- .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)}
2943
3003
  .sl-dot{width:9px;height:9px;border-radius:50%;flex:none}
2944
3004
  .sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
2945
3005
  .sl-price-left{font-size:11px;color:var(--sl-muted);font-variant-numeric:tabular-nums}
@@ -2974,7 +3034,7 @@ var CSS2 = (
2974
3034
  box-shadow:inset 3px 0 0 color-mix(in srgb,var(--sl-accent) 72%,transparent)}
2975
3035
  .sl-ticket-state{width:17px;height:17px;border-radius:999px;flex:none;display:flex;align-items:center;justify-content:center;
2976
3036
  background:var(--sl-accent);color:var(--sl-accent-ink)}
2977
- .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)}
2978
3038
  .sl-ticket-state svg{width:10px;height:10px;stroke:currentColor;stroke-width:2.6;fill:none;stroke-linecap:round;stroke-linejoin:round}
2979
3039
  .sl-chip-main{min-width:0;padding:8px 10px 8px 11px;display:flex;flex-direction:column;justify-content:center;gap:5px}
2980
3040
  .sl-chip-id{display:flex;gap:12px;min-width:0}
@@ -3017,7 +3077,7 @@ var CSS2 = (
3017
3077
  .sl-ga-qty span{min-width:16px;text-align:center;font-weight:800;font-variant-numeric:tabular-nums}
3018
3078
 
3019
3079
  /* footer */
3020
- .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;
3021
3081
  background:var(--sl-bg);box-shadow:0 -10px 24px -22px rgba(0,0,0,.72)}
3022
3082
  .sl-hold-note{display:none;align-items:center;gap:7px;margin-bottom:8px;padding:7px 8px;border-radius:var(--sl-r-sm);
3023
3083
  border:1px solid var(--sl-line);background:color-mix(in srgb,var(--sl-accent) 7%,var(--sl-surface));
@@ -3031,14 +3091,13 @@ var CSS2 = (
3031
3091
  .sl-hold-note>span{flex:1;min-width:0}
3032
3092
  .sl-hold-change{flex:none;min-height:30px;padding:5px 8px;border-radius:8px;border:1px solid var(--sl-line);
3033
3093
  color:var(--sl-text);font-size:10.5px;font-weight:750;white-space:nowrap}
3034
- .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)}
3035
3095
  .sl-hold-change:disabled{opacity:.58;cursor:wait}
3036
3096
  .sl-total{display:flex;justify-content:space-between;align-items:center;font-size:13px;margin-bottom:10px}
3037
3097
  .sl-total b{font-size:17px;font-variant-numeric:tabular-nums}
3038
3098
  .sl-value-pop{animation:slValuePop .32s cubic-bezier(.2,.8,.2,1)}
3039
- /* Primary checkout CTA. Scoped under .sl-picker so it OUTWEIGHS the
3040
- '.sl-picker button' reset (0,1,1) \u2014 an unscoped '.sl-cta' (0,1,0) loses to it
3041
- 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. */
3042
3101
  .sl-picker .sl-cta{display:flex;align-items:center;justify-content:center;width:100%;min-height:44px;
3043
3102
  padding:12px 16px;border-radius:var(--sl-r-sm);font-weight:800;font-size:14px;line-height:1.1;
3044
3103
  background:var(--sl-accent);color:var(--sl-accent-ink);
@@ -3049,8 +3108,8 @@ var CSS2 = (
3049
3108
  .sl-cta-spin,.sl-ba-spin{width:14px;height:14px;border-radius:50%;border:2px solid currentColor;border-right-color:transparent;
3050
3109
  animation:slspin .7s linear infinite;flex:none}
3051
3110
  /* Disabled ("Select seats"): quieter, but still a full-width button shape. */
3052
- .sl-picker .sl-cta:disabled{background:var(--sl-surface);color:var(--sl-muted);opacity:1;
3053
- 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}
3054
3113
 
3055
3114
  /* Chrome anchor regions (Feature 6) \u2014 every interactive map overlay is APPENDED
3056
3115
  INTO one of these positioned flex containers and flows/stacks within it, so no
@@ -3080,7 +3139,7 @@ var CSS2 = (
3080
3139
  in the same region, so no displacement hack is needed. */
3081
3140
  .sl-testbadge{display:inline-flex;align-items:center;padding:5px 10px;border-radius:7px;pointer-events:none;
3082
3141
  font-size:10px;font-weight:850;letter-spacing:.13em;line-height:1.2;text-transform:uppercase;white-space:nowrap;
3083
- 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));
3084
3143
  border:1px dashed color-mix(in srgb,var(--sl-warn) 55%,transparent)}
3085
3144
 
3086
3145
  /* zoom column (flows within the bottom-right region) */
@@ -3091,11 +3150,11 @@ var CSS2 = (
3091
3150
  color:var(--sl-text);font-size:17px;font-weight:700;display:flex;align-items:center;justify-content:center;transition:border-color .15s}
3092
3151
  .sl-zoom button:hover{border-color:var(--sl-muted)}
3093
3152
  .sl-zoom svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
3094
- /* Full-screen promotion on phones: the capability existed (native + iOS overlay
3095
- + framed-host paths) but hid as the 4th unlabelled glyph in a corner stack.
3096
- On narrow layouts dockLayoutChrome moves the SAME button into the top-right
3097
- region beside Map|3D and this class dresses it as a labelled pill \u2014 the one
3098
- accelerator with the highest payoff on a small screen gets words. */
3153
+ /* Full-screen promotion: the capability existed (native + iOS overlay +
3154
+ framed-host paths) but hid as the 4th unlabelled glyph in a corner stack \u2014
3155
+ even the owner could not pick it out. dockLayoutChrome docks the SAME
3156
+ button into the top-right region beside Map|3D on EVERY layout and this
3157
+ class dresses it as a labelled pill. */
3099
3158
  .sl-zfs-lbl{display:none}
3100
3159
  .sl-fs-pill.sl-fs-pill{display:inline-flex;align-items:center;justify-content:center;width:auto;min-height:40px;height:auto;
3101
3160
  padding:0 13px;gap:6px;border-radius:999px;font-size:11px;font-weight:800;
@@ -3103,6 +3162,16 @@ var CSS2 = (
3103
3162
  .sl-fs-pill.sl-fs-pill:hover{border-color:var(--sl-muted)}
3104
3163
  .sl-fs-pill svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
3105
3164
  .sl-fs-pill .sl-zfs-lbl{display:inline;letter-spacing:.02em;white-space:nowrap}
3165
+ /* In the header, icons carry it (owner call): square Full screen glyph, and
3166
+ on narrow a price-tag toggle that opens the sheet with the list expanded. */
3167
+ .sl-head .sl-fs-pill{min-height:34px;min-width:34px;width:34px;padding:0;flex:none}
3168
+ .sl-head .sl-fs-pill .sl-zfs-lbl{display:none}
3169
+ .sl-prices-btn{display:none;align-items:center;justify-content:center;width:34px;min-height:34px;flex:none;
3170
+ border-radius:999px;background:var(--sl-surface);border:1px solid var(--sl-line);color:var(--sl-text);transition:border-color .15s}
3171
+ .sl-prices-btn:hover{border-color:var(--sl-muted)}
3172
+ .sl-prices-btn svg{width:15px;height:15px;stroke:currentColor;stroke-width:1.8;fill:none;stroke-linecap:round;stroke-linejoin:round}
3173
+ .sl-prices-btn[aria-pressed="true"]{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
3174
+ .sl-picker[data-layout="narrow"] .sl-prices-btn{display:inline-flex}
3106
3175
 
3107
3176
  /* toast + boot states (toast flows in the bottom-center region) */
3108
3177
  .sl-toast{transform:translateY(6px) scale(.98);max-width:100%;
@@ -3137,7 +3206,7 @@ var CSS2 = (
3137
3206
  .sl-extend-btn{flex:none;padding:8px 14px;border-radius:999px;font-weight:800;font-size:12.5px;
3138
3207
  background:var(--sl-accent);color:var(--sl-accent-ink);transition:filter .15s,opacity .15s}
3139
3208
  .sl-extend-btn:hover{filter:brightness(1.08)}
3140
- .sl-extend-btn:disabled{opacity:.5;cursor:not-allowed}
3209
+ .sl-extend-btn:disabled{opacity:.62;cursor:not-allowed}
3141
3210
 
3142
3211
  /* booked confirmation overlay (covers the widget once the held seats are sold) */
3143
3212
  .sl-booked{position:absolute;inset:0;z-index:11;display:flex;flex-direction:column;align-items:center;
@@ -3164,7 +3233,7 @@ var CSS2 = (
3164
3233
  justify-content:center;text-align:center;gap:8px;padding:24px;
3165
3234
  background:color-mix(in srgb,var(--sl-bg) 82%,transparent);backdrop-filter:blur(4px)}
3166
3235
  .sl-soldout.on{display:flex}
3167
- .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}
3168
3237
  .sl-soldout-title{font-size:32px;font-weight:800;color:var(--sl-text);line-height:1.05}
3169
3238
  .sl-soldout-copy{max-width:360px;font-size:13px;color:var(--sl-muted);line-height:1.5}
3170
3239
 
@@ -3180,9 +3249,9 @@ var CSS2 = (
3180
3249
  /* "Powered by SeatLayer" attribution badge (side-panel foot) \u2014 the canonical
3181
3250
  Layered Rows mark + wordmark. Hidden when the host opts out or the org's paid
3182
3251
  theme sets hideBadge. */
3183
- .sl-powered{display:flex;align-items:center;justify-content:center;gap:6px;margin-top:10px;
3184
- font-size:12px;font-weight:600;letter-spacing:.02em;color:var(--sl-text);opacity:.72;
3185
- 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;
3186
3255
  transition:opacity .15s ease,background-color .15s ease}
3187
3256
  .sl-powered:hover{opacity:1;background:color-mix(in srgb,var(--sl-text) 8%,transparent)}
3188
3257
  .sl-powered:focus-visible{opacity:1;outline:2px solid var(--sl-accent);outline-offset:2px}
@@ -3220,18 +3289,44 @@ var CSS2 = (
3220
3289
  .sl-confirm-field:first-child .sl-confirm-value{font-size:13.5px;line-height:1.25;white-space:normal;
3221
3290
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
3222
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))}
3223
- .sl-confirm-cat .sl-dot{border:2px solid rgba(255,255,255,.78);width:11px;height:11px}
3224
- .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}
3225
- .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}
3226
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}
3227
3308
  .sl-confirm-row{display:flex;gap:8px;margin-top:10px}
3228
3309
  .sl-confirm-row button{flex:1;min-height:44px;padding:9px 12px;border-radius:9px;font-weight:800;font-size:13px}
3229
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}
3230
3311
  .sl-confirm-add svg{width:16px;height:16px;stroke:currentColor;stroke-width:2.8;fill:none;stroke-linecap:round;stroke-linejoin:round}
3231
3312
  .sl-confirm-cancel{background:color-mix(in srgb,var(--sl-line) 44%,transparent)!important;border:1px solid var(--sl-line)!important;color:var(--sl-muted)!important}
3232
3313
  .sl-confirm-cancel:hover{color:var(--sl-text)}
3233
- .sl-picker[data-layout="narrow"] .sl-confirm{left:50%!important;top:auto!important;bottom:14px;width:min(342px,calc(100% - 24px));
3314
+ .sl-picker[data-layout="narrow"] .sl-confirm{left:50%!important;top:auto!important;bottom:14px;width:min(310px,calc(100% - 24px));
3234
3315
  transform:translateX(-50%);animation:slConfirmMobileIn .24s cubic-bezier(.2,.8,.2,1) both}
3316
+ /* Phone-size confirm card (owner call 2026-08-14): same content \u2014 identity,
3317
+ price band, view photo, 3D link \u2014 on a tighter grid. The values stay large
3318
+ enough to double-check against the map; buttons keep tap-size. */
3319
+ .sl-picker[data-layout="narrow"] .sl-confirm-field{padding:9px 9px 8px}
3320
+ .sl-picker[data-layout="narrow"] .sl-confirm-value{font-size:15px}
3321
+ .sl-picker[data-layout="narrow"] .sl-confirm-cat{padding:8px 10px}
3322
+ .sl-picker[data-layout="narrow"] .sl-confirm-cat-name{font-size:12.5px}
3323
+ .sl-picker[data-layout="narrow"] .sl-confirm-price{font-size:15px}
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}
3326
+ .sl-picker[data-layout="narrow"] .sl-confirm-thumbwrap{height:62px;margin-bottom:6px}
3327
+ .sl-picker[data-layout="narrow"] .sl-confirm-3d{margin-top:7px;padding:6px}
3328
+ .sl-picker[data-layout="narrow"] .sl-confirm-row{margin-top:8px}
3329
+ .sl-picker[data-layout="narrow"] .sl-confirm-row button{min-height:42px}
3235
3330
 
3236
3331
  /* Atomic whole/variable-table chooser. Lives inside the widget so the same
3237
3332
  accessible dialog works in inline, modal, desktop and 390px mobile hosts. */
@@ -3251,7 +3346,7 @@ var CSS2 = (
3251
3346
  .sl-table-stepper{display:grid;grid-template-columns:48px 1fr 48px;align-items:center;border:1px solid var(--sl-line);
3252
3347
  border-radius:12px;overflow:hidden;background:var(--sl-bg)}
3253
3348
  .sl-table-stepper button{height:48px;font-size:24px;font-weight:700;background:color-mix(in srgb,var(--sl-line) 34%,transparent)!important}
3254
- .sl-table-stepper button:disabled{opacity:.38;cursor:not-allowed}
3349
+ .sl-table-stepper button:disabled{opacity:.58;cursor:not-allowed}
3255
3350
  .sl-table-stepper output{text-align:center;font-size:19px;font-weight:850;font-variant-numeric:tabular-nums}
3256
3351
  .sl-table-range{margin-top:7px;color:var(--sl-muted);font-size:11px;text-align:center}
3257
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}
@@ -3293,22 +3388,25 @@ var CSS2 = (
3293
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}
3294
3389
  .sl-ba-title,.sl-ba-copy,.sl-ba select,.sl-ba-qty,.sl-ba-go{position:relative;z-index:1}
3295
3390
  .sl-ba-title{grid-column:1/-1;display:flex;align-items:center;gap:7px;font-size:13px;font-weight:850}
3296
- .sl-ba-title .spark{color:var(--sl-accent);font-size:16px}
3391
+ .sl-ba-title .spark{color:var(--sl-accent-text);font-size:16px}
3297
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}
3298
3393
  .sl-ba-copy .narrow{display:none}
3299
3394
  /* "\u2605 Best seats" premium quick-pick \u2014 gold accent echoing the \u2605 Premium pill on
3300
3395
  the confirm popover; deliberately distinct from the accent-toned qty/go. */
3301
3396
  .sl-ba-premium{position:relative;z-index:1;grid-column:1/-1;justify-self:start;display:inline-flex;align-items:center;gap:6px;
3302
3397
  padding:6px 12px;border-radius:999px;font-size:11px;font-weight:800;letter-spacing:.02em;cursor:pointer;
3303
- 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));
3304
3399
  border:1px solid color-mix(in srgb,var(--sl-premium) 34%,var(--sl-line));transition:filter .15s,background .15s,color .15s}
3305
3400
  .sl-ba-premium .star{font-size:12px;line-height:1;color:var(--sl-premium)}
3306
3401
  .sl-ba-premium:hover{filter:brightness(1.05)}
3307
3402
  .sl-ba-premium.on{color:var(--sl-premium-ink);background:linear-gradient(135deg,#f0cf6b,#e0b23f);border-color:transparent;
3308
3403
  box-shadow:0 6px 16px color-mix(in srgb,var(--sl-premium) 26%,transparent)}
3309
3404
  .sl-ba-premium.on .star{color:#5a4410}
3310
- .sl-ba select{background:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:8px;
3311
- font:inherit;font-size:11px;padding:7px 8px;min-width:0;width:100%;max-width:none}
3405
+ .sl-ba select,.sl-price-select{appearance:none;-webkit-appearance:none;
3406
+ background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23949ca9' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
3407
+ background-repeat:no-repeat;background-position:right 10px center}
3408
+ .sl-ba select{background-color:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:8px;
3409
+ font:inherit;font-size:11px;padding:7px 26px 7px 8px;min-width:0;width:100%;max-width:none}
3312
3410
  .sl-ba-qty{display:flex;align-items:center;gap:7px;padding:3px;border:1px solid var(--sl-line);border-radius:9px;background:var(--sl-surface)}
3313
3411
  .sl-ba-qty button{width:25px;height:25px;border-radius:7px;background:color-mix(in srgb,var(--sl-line) 35%,transparent);border:0;
3314
3412
  font-size:14px;font-weight:800;display:flex;align-items:center;justify-content:center}
@@ -3338,9 +3436,23 @@ var CSS2 = (
3338
3436
  .sl-picker[data-layout="narrow"] .sl-ba-copy{display:none}
3339
3437
  .sl-picker[data-layout="narrow"] .sl-ba-copy .wide{display:none}
3340
3438
  .sl-picker[data-layout="narrow"] .sl-ba-copy .narrow{display:inline}
3341
- .sl-picker[data-layout="narrow"] .sl-ba select{min-height:40px}
3439
+ .sl-picker[data-layout="narrow"] .sl-ba{grid-template-columns:auto minmax(0,1fr)}
3440
+ .sl-picker[data-layout="narrow"] .sl-ba select{grid-column:1/-1;min-height:38px}
3342
3441
  .sl-picker[data-layout="narrow"] .sl-ba-qty button{width:30px;height:30px}
3343
- .sl-picker[data-layout="narrow"] .sl-ba-go{min-height:40px}
3442
+ /* Quantity and Go share ONE row: a full-width row holding only "\u2212 2 +" was
3443
+ dead space, and the Find button still gets the lion's share. */
3444
+ .sl-picker[data-layout="narrow"] .sl-ba-qty{grid-column:1}
3445
+ .sl-picker[data-layout="narrow"] .sl-ba-go{grid-column:2;min-height:40px}
3446
+ /* Re-entry to best-available once the cart has seats (the card yields its
3447
+ space to chips, but the accelerator must stay reachable) + the quiet way
3448
+ back out of a reopened card. */
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;
3450
+ border:1px dashed color-mix(in srgb,var(--sl-accent) 40%,var(--sl-line));border-radius:999px;
3451
+ color:var(--sl-accent-text);font-size:11.5px;font-weight:750;transition:border-color .15s,background .15s}
3452
+ .sl-ba-reopen:hover,.sl-ba-reopen:focus-visible{background:color-mix(in srgb,var(--sl-accent) 8%,transparent)}
3453
+ .sl-ba-back{grid-column:1/-1;justify-self:start;min-height:30px;padding:3px 0;color:var(--sl-muted);
3454
+ font-size:11px;font-weight:700;text-decoration:underline;text-underline-offset:3px;transition:color .15s}
3455
+ .sl-ba-back:hover,.sl-ba-back:focus-visible{color:var(--sl-text)}
3344
3456
 
3345
3457
  /* Sales-closed is a designed state, not a set of disables: one statement in
3346
3458
  the tray where the buying flow was, carrying the event's own date so the
@@ -3359,9 +3471,9 @@ var CSS2 = (
3359
3471
  (the cart chip's remove/view rail) instead get real height on narrow. */
3360
3472
  @media (pointer:coarse){
3361
3473
  .sl-zoom button,.sl-cbbtn,.sl-close,.sl-ga-qty button,.sl-ba-qty button,.sl-price-more,
3362
- .sl-hold-change,.sl-seccard-x,.sl-fs-pill,.sl-side-toggle{position:relative}
3474
+ .sl-hold-change,.sl-seccard-x,.sl-fs-pill,.sl-side-toggle,.sl-ba-reopen{position:relative}
3363
3475
  .sl-zoom button::after,.sl-cbbtn::after,.sl-close::after,.sl-ga-qty button::after,.sl-ba-qty button::after,
3364
- .sl-price-more::after,.sl-hold-change::after,.sl-seccard-x::after,.sl-fs-pill::after,.sl-side-toggle::after{
3476
+ .sl-price-more::after,.sl-hold-change::after,.sl-seccard-x::after,.sl-fs-pill::after,.sl-side-toggle::after,.sl-ba-reopen::after{
3365
3477
  content:'';position:absolute;top:50%;left:50%;width:max(100%,44px);height:max(100%,44px);
3366
3478
  transform:translate(-50%,-50%)}
3367
3479
  /* The chip rail stacks remove over view in ~53px \u2014 pseudo-targets would
@@ -3458,18 +3570,12 @@ var CSS2 = (
3458
3570
  border:1px solid rgba(150,165,205,.35);backdrop-filter:blur(6px);cursor:pointer}
3459
3571
  .sl-view3d-nav button:hover,.sl-view3d-nav button:focus-visible{color:#eef1f8;border-color:rgba(190,205,240,.6)}
3460
3572
  .sl-view3d-nav button[aria-pressed="true"]{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
3461
- .sl-view3d-nav button:disabled{opacity:.45;cursor:not-allowed}
3573
+ .sl-view3d-nav button:disabled{opacity:.6;cursor:not-allowed}
3462
3574
  .sl-view3d-nav-toggle{display:none!important}
3463
3575
  .sl-view3d-nav select{min-height:38px;max-width:100%;padding:7px 34px 7px 12px;
3464
3576
  border-radius:999px;font:700 11.5px/1 inherit;color:#eef1f8;background:rgba(12,18,32,.86);
3465
3577
  border:1px solid rgba(150,165,205,.45);backdrop-filter:blur(6px);cursor:pointer}
3466
3578
  .sl-view3d-nav select:focus-visible{outline:2px solid var(--sl-accent);outline-offset:2px}
3467
- .sl-view3d-nav .sl-view3d-locator{display:grid;grid-template-columns:minmax(150px,1.25fr) minmax(110px,.8fr) minmax(105px,.7fr) auto;
3468
- width:min(720px,100%);overflow:visible}
3469
- .sl-view3d-nav.is-seat-focused .sl-view3d-locator{display:none}
3470
- .sl-view3d-locator select{width:100%;min-width:0}
3471
- .sl-picker[data-layout="narrow"] .sl-view3d-nav .sl-view3d-locator{display:flex;width:100%;overflow-x:auto}
3472
- .sl-picker[data-layout="narrow"] .sl-view3d-locator select{flex:0 0 155px}
3473
3579
  /* On phones the library owns the bottom edge for seat/panorama/overview actions.
3474
3580
  Keep venue navigation in a separate top rail so those control families never
3475
3581
  stack over one another. */
@@ -3479,9 +3585,6 @@ var CSS2 = (
3479
3585
  .sl-picker[data-layout="narrow"] .sl-view3d-nav.is-open{left:12px;top:68px;max-width:calc(100% - 24px);padding:8px;
3480
3586
  border:1px solid rgba(150,165,205,.35);border-radius:14px;background:rgba(8,12,22,.9);backdrop-filter:blur(10px)}
3481
3587
  .sl-picker[data-layout="narrow"] .sl-view3d-nav.is-open>div{display:flex}
3482
- .sl-picker[data-layout="narrow"] .sl-view3d-nav.is-open .sl-view3d-locator{display:grid;grid-template-columns:1fr 1fr;width:100%;overflow:visible}
3483
- .sl-picker[data-layout="narrow"] .sl-view3d-nav.is-open .sl-view3d-locator select{width:100%;min-width:0;flex:auto}
3484
- .sl-picker[data-layout="narrow"] .sl-view3d-nav.is-open .sl-view3d-locator button{width:100%}
3485
3588
  /* Seat-eye is a decision state, not another venue-navigation state. Once the
3486
3589
  buyer arrives, clear the floor/area/locator rails and the module's duplicate
3487
3590
  Overview action. The picker-owned Back button becomes the one predictable
@@ -3515,7 +3618,7 @@ var CSS2 = (
3515
3618
  .sl-confirm-confidence strong,.sl-confirm-confidence small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
3516
3619
  .sl-confirm-confidence strong{font-size:11px}.sl-confirm-confidence small{margin-top:2px;color:var(--sl-muted);font-size:9.5px}
3517
3620
  .sl-confirm-confidence em{display:none;font-style:normal}
3518
- .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)}
3519
3622
  .sl-confirm-confidence:hover,.sl-confirm-confidence:focus-visible{border-color:var(--sl-accent)}
3520
3623
  /* The production 3D decision dock is deliberately denser than the 2D popup:
3521
3624
  the venue remains the main content and the two inspection actions share one
@@ -3597,7 +3700,7 @@ var CSS2 = (
3597
3700
  .sl-view3d-compare-actions{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;margin-top:12px}
3598
3701
  .sl-view3d-compare-actions button{min-height:44px;border-radius:9px;border:1px solid var(--sl-line);font-size:12px;font-weight:800}
3599
3702
  .sl-view3d-compare-actions .select{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
3600
- .sl-view3d-compare-actions button:disabled{opacity:.5;cursor:not-allowed}
3703
+ .sl-view3d-compare-actions button:disabled{opacity:.62;cursor:not-allowed}
3601
3704
  .sl-view3d-passport-shell{position:absolute;inset:0;z-index:40;display:grid;place-items:center;padding:16px}
3602
3705
  .sl-view3d-passport-scrim{position:absolute;inset:0;background:rgba(3,6,12,.8);backdrop-filter:blur(6px)}
3603
3706
  .sl-view3d-passport{position:relative;width:min(540px,100%);max-height:min(700px,calc(100% - 20px));overflow:auto;
@@ -3638,14 +3741,17 @@ var CSS2 = (
3638
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)}
3639
3742
  .sl-confirm-3d svg{width:15px;height:15px;stroke:currentColor;stroke-width:1.9;fill:none;stroke-linecap:round;stroke-linejoin:round}
3640
3743
 
3641
- /* multi-floor switcher (flows within the left-rail region) */
3642
- .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)}
3643
3748
  .sl-floors.on{display:flex}
3644
- .sl-floors button{padding:7px 13px;border-radius:999px;font-size:12px;font-weight:700;background:var(--sl-surface);
3645
- border:1px solid var(--sl-line);color:var(--sl-muted);white-space:nowrap;max-width:100%;overflow:hidden;
3646
- text-overflow:ellipsis;transition:color .15s,border-color .15s}
3647
- .sl-floors button:hover{color:var(--sl-text)}
3648
- .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)}
3649
3755
 
3650
3756
  /* tapped-section summary card \u2014 docks INSIDE the top-center anchor region on
3651
3757
  wide (flows below the rung pills, never over them, never floating over the
@@ -3680,8 +3786,14 @@ var CSS2 = (
3680
3786
  .sl-seccard-mix-dot{width:8px;height:8px;border-radius:50%;flex:none}
3681
3787
  .sl-seccard-mix-price{font-weight:700;color:var(--sl-text)}
3682
3788
  .sl-seccard-foot{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:10px}
3683
- .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)}
3684
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}
3685
3797
 
3686
3798
  /* view-from-seat button on the confirm popover */
3687
3799
  /* Eager sightline preview inside the confirm card */
@@ -3734,10 +3846,29 @@ var CSS2 = (
3734
3846
  white-space:nowrap;pointer-events:none;max-width:90%;overflow:hidden;text-overflow:ellipsis}
3735
3847
 
3736
3848
  /* F3 minimap \u2014 venue overview + live viewport rect (flows in the bottom-left region) */
3737
- .sl-minimap{border:1px solid var(--sl-line);border-radius:9px;
3738
- 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}
3739
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}
3740
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}
3741
3872
 
3742
3873
  /* F4 legend reflection: rows + counts for out-of-band categories read muted */
3743
3874
  /* Out-of-band rows are exactly the rows a buyer reads while deciding whether
@@ -3815,17 +3946,88 @@ function formatWhen(startsAt, timezone, locale) {
3815
3946
  }
3816
3947
  return at.toLocaleString(locale, options);
3817
3948
  }
3818
- 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");
3819
4017
  const accent = host?.accent ?? chart?.accent ?? "#f4b740";
3820
- 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));
3821
4022
  return {
3822
4023
  "--sl-accent": accent,
3823
4024
  "--sl-accent-ink": accentInk,
3824
- "--sl-bg": host?.background ?? chart?.background ?? "#0f1522",
3825
- "--sl-surface": host?.surface ?? "#1a2234",
3826
- "--sl-text": host?.text ?? chart?.textColor ?? "#eef1f8",
3827
- "--sl-muted": host?.muted ?? "#8b93a7",
3828
- "--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)"),
3829
4031
  "--sl-font": host?.fontFamily ?? chart?.fontFamily ?? "-apple-system,BlinkMacSystemFont,'Segoe UI',Inter,sans-serif",
3830
4032
  "--sl-radius": `${host?.radius ?? 14}px`
3831
4033
  };
@@ -3907,6 +4109,15 @@ var SeatPicker = class _SeatPicker {
3907
4109
  this.baZone = "";
3908
4110
  /** "★ Best seats" premium quick-pick toggle — biases best-available to premium seats. */
3909
4111
  this.baPremium = false;
4112
+ /**
4113
+ * The buyer re-opened the best-seats form with seats already in the cart
4114
+ * ("I picked one, actually find us three together"). The card normally
4115
+ * yields this space to the ticket chips the moment anything is selected —
4116
+ * which removed the accelerator at exactly the moment a one-handed phone
4117
+ * buyer wants it. Cleared when the cart empties, a hold lands, or the
4118
+ * buyer backs out.
4119
+ */
4120
+ this.baReopen = false;
3910
4121
  this.bestAvailableConfirm = false;
3911
4122
  this.releasingHold = false;
3912
4123
  /** Event sales window is closed (read-only load state / live close). */
@@ -3948,7 +4159,13 @@ var SeatPicker = class _SeatPicker {
3948
4159
  // F3 minimap
3949
4160
  this.miniCanvas = null;
3950
4161
  this.miniBase = null;
4162
+ this.miniWrap = null;
4163
+ this.miniToggle = null;
4164
+ this.miniLabel = null;
3951
4165
  this.miniTf = null;
4166
+ this.miniDrag = null;
4167
+ this.miniSuppressClick = false;
4168
+ this.pickerLayoutSize = { width: 0, height: 0 };
3952
4169
  // F4 price-band filter — active band's category keys (null = all prices)
3953
4170
  this.priceBandKeys = null;
3954
4171
  this.focusedCatKey = null;
@@ -3984,6 +4201,7 @@ var SeatPicker = class _SeatPicker {
3984
4201
  /** Wide-layout ticket panel collapsed (map owns the full width). */
3985
4202
  this.sideCollapsed = false;
3986
4203
  this.sideToggleEl = null;
4204
+ this.pricesBtnEl = null;
3987
4205
  /** True once we've asked the host page to pin us fullscreen (framed, no native). */
3988
4206
  this.framedFs = false;
3989
4207
  /** Last height (px) posted to a host frame; dedupes redundant reports. */
@@ -4048,6 +4266,7 @@ var SeatPicker = class _SeatPicker {
4048
4266
  mapTheme: options.theme?.map ?? null,
4049
4267
  onSelectionChange: () => {
4050
4268
  this.syncTray();
4269
+ this.refreshMinimap();
4051
4270
  if (this.committedSelection().length) this.collapseSectionCard();
4052
4271
  this.syncSelectionTo3d();
4053
4272
  },
@@ -4096,7 +4315,7 @@ var SeatPicker = class _SeatPicker {
4096
4315
  if (this.tableDialog?.physicalSeatIds.includes(seat.id)) this.dismissTableDialog();
4097
4316
  },
4098
4317
  onSelectionLimit: () => {
4099
- this.toast(`You can select up to ${this.maxTickets} tickets for this order.`, "warning");
4318
+ this.toast((0, import_core2.t)("picker.maxTicketsForOrder", { count: this.maxTickets }), "warning");
4100
4319
  },
4101
4320
  onViewChange: () => {
4102
4321
  this.reanchorConfirm();
@@ -4107,6 +4326,12 @@ var SeatPicker = class _SeatPicker {
4107
4326
  },
4108
4327
  // Tapped-section glide-in → surface (or clear) the section-summary card.
4109
4328
  onSectionFocus: (summary) => this.showSectionCard(summary),
4329
+ onDeckTap: () => {
4330
+ this.showSectionCard(null);
4331
+ this.syncFloors();
4332
+ this.syncRung();
4333
+ this.refreshMinimap();
4334
+ },
4110
4335
  onFocusSeat: (seat) => this.announceSeat(seat),
4111
4336
  onSeatHover: (d) => this.updateTooltip(d),
4112
4337
  onHint: (m) => {
@@ -4212,8 +4437,8 @@ var SeatPicker = class _SeatPicker {
4212
4437
  return `<span class="sl-cx-mark" role="img" aria-label="${title}" title="${title}">\u25D0</span>`;
4213
4438
  }
4214
4439
  wheelchairProvisionLabel(type) {
4215
- if (type === "no-seat") return "Empty wheelchair space";
4216
- if (type === "seat-present") return "Accessible physical seat";
4440
+ if (type === "no-seat") return this.tf("picker.emptyWheelchairSpace", "Empty wheelchair space");
4441
+ if (type === "seat-present") return this.tf("picker.accessiblePhysicalSeat", "Accessible physical seat");
4217
4442
  return "";
4218
4443
  }
4219
4444
  wheelchairConfirmHtml(type) {
@@ -4294,9 +4519,9 @@ var SeatPicker = class _SeatPicker {
4294
4519
  const hideEventDetails = this.eventDetailsHidden && !active;
4295
4520
  this.root?.setAttribute("data-event-details-hidden", String(hideEventDetails));
4296
4521
  this.els.zfs?.setAttribute("aria-pressed", String(active));
4297
- this.els.zfs?.setAttribute("title", active ? "Exit full screen" : "Full screen");
4522
+ this.els.zfs?.setAttribute("title", active ? this.tf("picker.exitFullScreen", "Exit full screen") : this.tf("picker.fullScreen", "Full screen"));
4298
4523
  const zfsLabel = this.els.zfs?.querySelector(".sl-zfs-lbl");
4299
- if (zfsLabel) zfsLabel.textContent = active ? "Exit full screen" : "Full screen";
4524
+ if (zfsLabel) zfsLabel.textContent = active ? this.tf("picker.exitFullScreen", "Exit full screen") : this.tf("picker.fullScreen", "Full screen");
4300
4525
  this.view3dEl?.querySelector(".sl-view3d-fs")?.setAttribute("aria-pressed", String(active));
4301
4526
  }
4302
4527
  /**
@@ -4324,7 +4549,7 @@ var SeatPicker = class _SeatPicker {
4324
4549
  window.removeEventListener("keydown", this.fsEscHandler);
4325
4550
  this.fsEscHandler = null;
4326
4551
  }
4327
- requestAnimationFrame(() => this.controller.zoomToFit());
4552
+ requestAnimationFrame(() => this.controller.refitCurrentView());
4328
4553
  }
4329
4554
  setFsFallback(on) {
4330
4555
  if (this.fsFallback === on) return;
@@ -4340,7 +4565,7 @@ var SeatPicker = class _SeatPicker {
4340
4565
  window.removeEventListener("keydown", this.fsEscHandler);
4341
4566
  this.fsEscHandler = null;
4342
4567
  }
4343
- requestAnimationFrame(() => this.controller.zoomToFit());
4568
+ requestAnimationFrame(() => this.controller.refitCurrentView());
4344
4569
  }
4345
4570
  /**
4346
4571
  * Close the picker. In modal mode (SeatPicker.open()) this dismisses the
@@ -4361,7 +4586,7 @@ var SeatPicker = class _SeatPicker {
4361
4586
  frame.className = "sl-modal-frame";
4362
4587
  frame.setAttribute("role", "dialog");
4363
4588
  frame.setAttribute("aria-modal", "true");
4364
- frame.setAttribute("aria-label", "Seat selection");
4589
+ frame.setAttribute("aria-label", tfStatic("picker.seatSelection", "Seat selection"));
4365
4590
  frame.tabIndex = -1;
4366
4591
  scrim.appendChild(frame);
4367
4592
  document.body.appendChild(scrim);
@@ -4480,11 +4705,17 @@ var SeatPicker = class _SeatPicker {
4480
4705
  }
4481
4706
  async render() {
4482
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;
4483
4711
  this.rendered = true;
4484
4712
  ensureStyle2();
4713
+ const localeStartedAt = performance.now();
4485
4714
  await (0, import_core2.loadLocale)(this.opts.locale);
4715
+ renderProfile.loadLocale = duration(localeStartedAt);
4486
4716
  if (this.opts.messages) (0, import_core2.setStringOverrides)(this.opts.messages);
4487
4717
  if (this.checkoutMode === "hosted") this.paymentOptions = this.pubApi.paymentOptions(this.opts.event);
4718
+ const shellStartedAt = performance.now();
4488
4719
  const mount = resolveContainer3(this.opts.container);
4489
4720
  const root = document.createElement("div");
4490
4721
  root.className = "sl-picker";
@@ -4508,7 +4739,7 @@ var SeatPicker = class _SeatPicker {
4508
4739
  this.syncTray();
4509
4740
  }
4510
4741
  });
4511
- 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));
4512
4743
  root.innerHTML = `
4513
4744
  <div class="sl-head">
4514
4745
  <div class="sl-logo" data-ref="logo"></div>
@@ -4521,7 +4752,7 @@ var SeatPicker = class _SeatPicker {
4521
4752
  <svg viewBox="0 0 24 24" aria-hidden="true"><rect x="5" y="11" width="14" height="9" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>
4522
4753
  <span data-ref="closedPillText"></span>
4523
4754
  </span>
4524
- <button type="button" class="sl-close" data-ref="close" aria-label="Close">
4755
+ <button type="button" class="sl-close" data-ref="close" aria-label="${this.tf("picker.close", "Close")}">
4525
4756
  <svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
4526
4757
  </button>
4527
4758
  </div>
@@ -4529,17 +4760,17 @@ var SeatPicker = class _SeatPicker {
4529
4760
  <div class="sl-map">
4530
4761
  <div class="sl-map-host" data-ref="map"></div>
4531
4762
  <div class="sl-zoom" data-ref="zoom">
4532
- <button type="button" aria-label="Zoom in" title="Zoom in" data-ref="zin">+</button>
4533
- <button type="button" aria-label="Zoom out" title="Zoom out" data-ref="zout">\u2212</button>
4534
- <button type="button" aria-label="Fit to screen" title="Fit to screen" data-ref="zfit">
4763
+ <button type="button" aria-label="${this.tf("picker.zoomIn", "Zoom in")}" title="${this.tf("picker.zoomIn", "Zoom in")}" data-ref="zin">+</button>
4764
+ <button type="button" aria-label="${this.tf("picker.zoomOut", "Zoom out")}" title="${this.tf("picker.zoomOut", "Zoom out")}" data-ref="zout">\u2212</button>
4765
+ <button type="button" aria-label="${this.tf("picker.fitToScreen", "Fit to screen")}" title="${this.tf("picker.fitToScreen", "Fit to screen")}" data-ref="zfit">
4535
4766
  <svg viewBox="0 0 24 24"><path d="M8 3H5a2 2 0 0 0-2 2v3M16 3h3a2 2 0 0 1 2 2v3M8 21H5a2 2 0 0 1-2-2v-3M16 21h3a2 2 0 0 0 2-2v-3"/></svg>
4536
4767
  </button>
4537
- <button type="button" aria-label="Full screen" title="Full screen" aria-pressed="false" data-ref="zfs">
4768
+ <button type="button" aria-label="${this.tf("picker.fullScreen", "Full screen")}" title="${this.tf("picker.fullScreen", "Full screen")}" aria-pressed="false" data-ref="zfs">
4538
4769
  <svg viewBox="0 0 24 24"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>
4539
- <span class="sl-zfs-lbl" aria-hidden="true">Full screen</span>
4770
+ <span class="sl-zfs-lbl" aria-hidden="true">${this.tf("picker.fullScreen", "Full screen")}</span>
4540
4771
  </button>
4541
4772
  </div>
4542
- <div class="sl-boot" data-ref="boot"><span class="sl-boot-spin"></span>Loading seat map\u2026</div>
4773
+ <div class="sl-boot" data-ref="boot"><span class="sl-boot-spin"></span>${this.tf("picker.loadingSeatMap", "Loading seat map\u2026")}</div>
4543
4774
  <div class="sl-toast" data-ref="toast" role="status" aria-live="polite"></div>
4544
4775
  </div>
4545
4776
  <div class="sl-side" data-ref="side">
@@ -4547,24 +4778,25 @@ var SeatPicker = class _SeatPicker {
4547
4778
  <div class="sl-sheet-grab"></div>
4548
4779
  <div class="sl-sheet-bar">
4549
4780
  <div class="sl-sheet-peek" data-ref="peek"></div>
4550
- <button type="button" class="sl-sheet-toggle" data-ref="sheetToggle" aria-label="Open ticket panel" aria-expanded="false">
4781
+ <button type="button" class="sl-sheet-toggle" data-ref="sheetToggle" aria-label="${this.tf("picker.openTicketPanel", "Open ticket panel")}" aria-expanded="false">
4551
4782
  <svg viewBox="0 0 24 24"><path d="M6 15l6-6 6 6"/></svg>
4552
4783
  </button>
4553
4784
  </div>
4554
4785
  </div>
4555
- <div class="sl-sec sl-filtersec" data-ref="filtersSec">Filters</div>
4786
+ <div class="sl-sec sl-filtersec" data-ref="filtersSec">${this.tf("picker.filters", "Filters")}</div>
4556
4787
  <div class="sl-filters" data-ref="filters"></div>
4557
4788
  <div class="sl-offer" data-ref="offer" role="status" aria-live="polite"></div>
4558
- <div class="sl-sec sl-prices-sec" data-ref="pricesSec"><span>Ticket prices</span></div>
4789
+ <div class="sl-sec sl-prices-sec" data-ref="pricesSec"><span>${this.tf("picker.ticketPrices", "Ticket prices")}</span></div>
4790
+ <div class="sl-prices-hint" data-ref="pricesHint"></div>
4559
4791
  <div class="sl-prices" data-ref="prices"></div>
4560
- <div class="sl-live" data-ref="live" role="status" aria-live="polite"><span class="dot" aria-hidden="true"></span><span data-ref="liveText">Live availability \u2014 seats update in real time</span></div>
4561
- <div class="sl-sec sl-seats-sec"><span>Your seats</span><span class="sl-seat-summary" data-ref="seatSummary"></span></div>
4792
+ <div class="sl-live" data-ref="live" role="status" aria-live="polite"><span class="dot" aria-hidden="true"></span><span data-ref="liveText">${this.tf("picker.liveAvailability", "Live availability \u2014 seats update in real time")}</span></div>
4793
+ <div class="sl-sec sl-seats-sec"><span>${this.tf("picker.yourSeats", "Your seats")}</span><span class="sl-seat-summary" data-ref="seatSummary"></span></div>
4562
4794
  <div class="sl-tray" data-ref="tray"></div>
4563
4795
  <div class="sl-foot" data-ref="foot">
4564
4796
  <div class="sl-hold-note" data-ref="holdNote" role="status" aria-live="polite">
4565
4797
  <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg>
4566
- <span><b data-ref="holdTitle">Seats secured</b><span class="sl-hold-copy" data-ref="holdCopy">You won\u2019t be charged yet.</span></span>
4567
- <button type="button" class="sl-hold-change" data-ref="holdChange" aria-label="Release held tickets and choose different seats">Change</button>
4798
+ <span><b data-ref="holdTitle">${this.tf("picker.seatsSecured", "Seats secured")}</b><span class="sl-hold-copy" data-ref="holdCopy">${this.tf("picker.notChargedYet", "You won\u2019t be charged yet.")}</span></span>
4799
+ <button type="button" class="sl-hold-change" data-ref="holdChange" aria-label="${this.tf("picker.releaseHeldTickets", "Release held tickets and choose different seats")}">${this.tf("picker.change", "Change")}</button>
4568
4800
  </div>
4569
4801
  <div class="sl-total"><span data-ref="count"></span><b data-ref="total"></b></div>
4570
4802
  <button type="button" class="sl-cta" data-ref="cta" disabled></button>
@@ -4586,14 +4818,20 @@ var SeatPicker = class _SeatPicker {
4586
4818
  const applyLayout = () => {
4587
4819
  const w = root.clientWidth;
4588
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 };
4589
4824
  this.reportFramedHeight();
4590
4825
  const next = w < 640 ? "narrow" : "wide";
4591
- const density = root.clientHeight < 560 ? "compact" : "comfortable";
4592
- if (root.dataset.layout === next && root.dataset.density === density) return;
4593
- root.dataset.layout = next;
4594
- root.dataset.density = density;
4595
- if (next === "narrow" && !root.dataset.sheet) root.dataset.sheet = "peek";
4596
- 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();
4597
4835
  };
4598
4836
  this.ro = new ResizeObserver(applyLayout);
4599
4837
  this.ro.observe(root);
@@ -4606,7 +4844,7 @@ var SeatPicker = class _SeatPicker {
4606
4844
  this.fsChangeHandler = () => {
4607
4845
  if (!document.fullscreenElement) this.setFsFallback(false);
4608
4846
  this.syncFullscreenButtons();
4609
- requestAnimationFrame(() => this.controller.zoomToFit());
4847
+ requestAnimationFrame(() => this.controller.refitCurrentView());
4610
4848
  };
4611
4849
  document.addEventListener("fullscreenchange", this.fsChangeHandler);
4612
4850
  const head = this.els.sheetHead;
@@ -4615,7 +4853,7 @@ var SeatPicker = class _SeatPicker {
4615
4853
  const setSheet = (open) => {
4616
4854
  root.dataset.sheet = open ? "open" : "peek";
4617
4855
  toggle?.setAttribute("aria-expanded", String(open));
4618
- toggle?.setAttribute("aria-label", open ? "Collapse ticket panel" : "Open ticket panel");
4856
+ toggle?.setAttribute("aria-label", open ? this.tf("picker.collapseTicketPanel", "Collapse ticket panel") : this.tf("picker.openTicketPanel", "Open ticket panel"));
4619
4857
  };
4620
4858
  setSheet(root.dataset.sheet === "open");
4621
4859
  toggle?.addEventListener("click", (e) => {
@@ -4661,6 +4899,39 @@ var SeatPicker = class _SeatPicker {
4661
4899
  head.releasePointerCapture?.(e.pointerId);
4662
4900
  });
4663
4901
  }
4902
+ const togglePrices = () => {
4903
+ if (this.root?.dataset.layout !== "narrow") return;
4904
+ const open = this.root.getAttribute("data-prices-open") === "true";
4905
+ this.root.setAttribute("data-prices-open", String(!open));
4906
+ this.els.pricesSec?.setAttribute("aria-expanded", String(!open));
4907
+ this.pricesBtnEl?.setAttribute("aria-pressed", String(!open));
4908
+ if (!open && this.root.dataset.sheet !== "open") {
4909
+ this.root.dataset.sheet = "open";
4910
+ const sheetToggle = this.els.sheetToggle;
4911
+ sheetToggle?.setAttribute("aria-expanded", "true");
4912
+ sheetToggle?.setAttribute("aria-label", "Collapse ticket panel");
4913
+ }
4914
+ };
4915
+ this.els.pricesSec?.addEventListener("click", (e) => {
4916
+ if (e.target.closest("select,button")) return;
4917
+ togglePrices();
4918
+ });
4919
+ this.els.pricesSec?.addEventListener("keydown", (e) => {
4920
+ if (e.key !== "Enter" && e.key !== " ") return;
4921
+ if (e.target.closest("select,button")) return;
4922
+ e.preventDefault();
4923
+ togglePrices();
4924
+ });
4925
+ const pricesBtn = document.createElement("button");
4926
+ pricesBtn.type = "button";
4927
+ pricesBtn.className = "sl-prices-btn";
4928
+ pricesBtn.setAttribute("aria-label", this.tf("picker.ticketPrices", "Ticket prices"));
4929
+ pricesBtn.setAttribute("title", this.tf("picker.ticketPrices", "Ticket prices"));
4930
+ pricesBtn.setAttribute("aria-pressed", "false");
4931
+ pricesBtn.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20.6 13.4 13.4 20.6a2 2 0 0 1-2.8 0L3 13V5a2 2 0 0 1 2-2h8l7.6 7.6a2 2 0 0 1 0 2.8z"/><circle cx="7.5" cy="7.5" r="1.3"/></svg>';
4932
+ pricesBtn.addEventListener("click", () => togglePrices());
4933
+ this.els.headInfo?.parentElement?.insertBefore(pricesBtn, this.els.hold ?? null);
4934
+ this.pricesBtnEl = pricesBtn;
4664
4935
  this.tipEl = document.createElement("div");
4665
4936
  this.tipEl.setAttribute("role", "tooltip");
4666
4937
  this.tipEl.className = "sl-tip";
@@ -4675,10 +4946,13 @@ var SeatPicker = class _SeatPicker {
4675
4946
  const canvasHost = document.createElement("div");
4676
4947
  canvasHost.style.cssText = "position:absolute;inset:0";
4677
4948
  this.mapHost.appendChild(canvasHost);
4949
+ renderProfile.mountBuyerShell = duration(shellStartedAt);
4950
+ const chartStartedAt = performance.now();
4678
4951
  const info = await this.controller.render(canvasHost);
4952
+ renderProfile.loadChartAndStatuses = duration(chartStartedAt);
4679
4953
  if (this.destroyed) return this;
4680
4954
  if (!info) {
4681
- this.els.boot.innerHTML = '<div class="sl-boot-title">The seat map didn\u2019t load</div><div>Check your connection and try again.</div><button type="button" class="sl-boot-retry">Try again</button>';
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>`;
4682
4956
  this.els.boot.querySelector("button").addEventListener("click", () => {
4683
4957
  const container = this.opts.container;
4684
4958
  const opts = this.opts;
@@ -4687,7 +4961,9 @@ var SeatPicker = class _SeatPicker {
4687
4961
  });
4688
4962
  return this;
4689
4963
  }
4964
+ Object.assign(renderProfile, info.performanceProfile);
4690
4965
  this.els.boot.remove();
4966
+ const controlsStartedAt = performance.now();
4691
4967
  this.startRealtime();
4692
4968
  this.salesClosed = !!info.salesClosed || !!this.opts.readOnly;
4693
4969
  root.dataset.eventMode = info.mode === "test" ? "test" : "live";
@@ -4703,7 +4979,7 @@ var SeatPicker = class _SeatPicker {
4703
4979
  this.regions["top-left"].appendChild(badge);
4704
4980
  }
4705
4981
  const chartTheme = this.controller.doc?.theme;
4706
- 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));
4707
4983
  this.currency = info.currency ?? this.opts.currency ?? "USD";
4708
4984
  this.eventTimezone = info.timezone ?? null;
4709
4985
  const logoUrl = this.opts.theme?.logoUrl ?? chartTheme?.logoUrl ?? info.posterUrl ?? null;
@@ -4713,6 +4989,12 @@ var SeatPicker = class _SeatPicker {
4713
4989
  const when = info.startsAt ? formatWhen(info.startsAt, info.timezone ?? null, this.opts.locale) : "";
4714
4990
  this.eventWhenText = when || null;
4715
4991
  this.els.meta.textContent = [info.venue, when].filter(Boolean).join(" \xB7 ");
4992
+ if (this.els.pricesHint) {
4993
+ this.els.pricesHint.textContent = this.tf(
4994
+ "picker.priceHint",
4995
+ "Colours on the map are ticket types \u2014 tap one below to show just those seats."
4996
+ );
4997
+ }
4716
4998
  this.buildBadge(chartTheme);
4717
4999
  const present = /* @__PURE__ */ new Set();
4718
5000
  let hasLimitedView = false;
@@ -4739,7 +5021,7 @@ var SeatPicker = class _SeatPicker {
4739
5021
  const mk = (key, label) => `<button type="button" class="sl-chip-f${key === "all" ? " on" : ""}" data-a11y="1" data-f="${key}">${label}</button>`;
4740
5022
  chips.insertAdjacentHTML(
4741
5023
  "beforeend",
4742
- mk("all", "All seats") + import_core2.ACCESSIBILITY_TYPES.filter(({ key }) => present.has(key)).map(({ key, short, icon }) => mk(key, `${icon} ${short}`)).join("")
5024
+ mk("all", this.tf("picker.allSeats", "All seats")) + import_core2.ACCESSIBILITY_TYPES.filter(({ key }) => present.has(key)).map(({ key, short, icon }) => mk(key, `${icon} ${short}`)).join("")
4743
5025
  );
4744
5026
  const active = /* @__PURE__ */ new Set();
4745
5027
  const syncChips = () => {
@@ -4785,7 +5067,7 @@ var SeatPicker = class _SeatPicker {
4785
5067
  cb.type = "button";
4786
5068
  cb.className = "sl-cbbtn";
4787
5069
  this.cbEl = cb;
4788
- cb.setAttribute("aria-label", "Toggle colorblind-friendly colors");
5070
+ cb.setAttribute("aria-label", this.tf("picker.toggleColorblindColors", "Toggle colorblind-friendly colors"));
4789
5071
  cb.setAttribute("aria-pressed", String(this.cbSafe));
4790
5072
  cb.innerHTML = '<svg viewBox="0 0 24 24"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg>';
4791
5073
  this.els.zfit.parentElement.appendChild(cb);
@@ -4805,12 +5087,34 @@ var SeatPicker = class _SeatPicker {
4805
5087
  this.buildPanelToggle();
4806
5088
  if (this.opts.panelCollapsed) this.setPanelCollapsed(true);
4807
5089
  this.dockLayoutChrome();
5090
+ renderProfile.buildBuyerControls = duration(controlsStartedAt);
5091
+ const holdStartedAt = performance.now();
4808
5092
  await this.restoreRememberedHold();
5093
+ renderProfile.restoreHold = duration(holdStartedAt);
4809
5094
  if (this.destroyed) return this;
4810
5095
  if (this.salesClosed) this.applySalesClosed();
4811
5096
  this.syncPrices();
4812
5097
  this.syncTray();
4813
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
+ });
4814
5118
  return this;
4815
5119
  }
4816
5120
  /**
@@ -4871,8 +5175,9 @@ var SeatPicker = class _SeatPicker {
4871
5175
  if (!btn) return;
4872
5176
  const collapsed = this.sideCollapsed;
4873
5177
  btn.setAttribute("aria-expanded", String(!collapsed));
4874
- btn.setAttribute("aria-label", collapsed ? "Show the ticket panel" : "Hide the ticket panel");
4875
- btn.innerHTML = collapsed ? '<svg viewBox="0 0 24 24"><path d="M15 6l-6 6 6 6"/></svg><span>Tickets</span>' : '<svg viewBox="0 0 24 24"><path d="M9 6l6 6-6 6"/></svg><span>Hide panel</span>';
5178
+ btn.setAttribute("aria-label", collapsed ? this.tf("picker.showTicketPanel", "Show the ticket panel") : this.tf("picker.hideTicketPanel", "Hide the ticket panel"));
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>`;
4876
5181
  }
4877
5182
  /**
4878
5183
  * Collapse or reopen the wide-layout ticket panel in place (host-drivable —
@@ -4885,7 +5190,7 @@ var SeatPicker = class _SeatPicker {
4885
5190
  if (this.destroyed) return;
4886
5191
  this.sideCollapsed = collapsed;
4887
5192
  this.applyPanelCollapsed();
4888
- this.scheduleMotion(() => this.controller.zoomToFit(), 340);
5193
+ this.scheduleMotion(() => this.controller.refitCurrentView(), 340);
4889
5194
  }
4890
5195
  /** Idempotent DOM apply, also run on every layout flip (inert must lift on narrow). */
4891
5196
  applyPanelCollapsed() {
@@ -4898,13 +5203,24 @@ var SeatPicker = class _SeatPicker {
4898
5203
  const narrow = this.root?.dataset.layout === "narrow";
4899
5204
  this.applyPanelCollapsed();
4900
5205
  const zfs = this.els.zfs;
4901
- if (zfs) {
4902
- if (narrow && this.regions["top-right"]) {
4903
- zfs.classList.add("sl-fs-pill");
4904
- this.regions["top-right"].appendChild(zfs);
4905
- } else if (!narrow && this.els.zoom && zfs.parentElement !== this.els.zoom) {
4906
- zfs.classList.remove("sl-fs-pill");
4907
- this.els.zoom.appendChild(zfs);
5206
+ const head = this.els.headInfo?.parentElement;
5207
+ if (zfs && head && zfs.parentElement !== head) {
5208
+ zfs.classList.add("sl-fs-pill");
5209
+ head.insertBefore(zfs, this.els.hold ?? null);
5210
+ }
5211
+ const pricesSec = this.els.pricesSec;
5212
+ if (pricesSec) {
5213
+ if (narrow) {
5214
+ pricesSec.setAttribute("role", "button");
5215
+ pricesSec.tabIndex = 0;
5216
+ pricesSec.setAttribute(
5217
+ "aria-expanded",
5218
+ String(this.root?.getAttribute("data-prices-open") === "true")
5219
+ );
5220
+ } else {
5221
+ pricesSec.removeAttribute("role");
5222
+ pricesSec.removeAttribute("aria-expanded");
5223
+ pricesSec.tabIndex = -1;
4908
5224
  }
4909
5225
  }
4910
5226
  const filters = this.els.filters;
@@ -4931,7 +5247,7 @@ var SeatPicker = class _SeatPicker {
4931
5247
  this.extendEl = el2;
4932
5248
  this.els.extendTxt = el2.querySelector('[data-ref="extendTxt"]');
4933
5249
  this.els.extendBtn = el2.querySelector('[data-ref="extendBtn"]');
4934
- this.els.extendBtn.textContent = "Add time";
5250
+ this.els.extendBtn.textContent = this.tf("picker.addTime", "Add time");
4935
5251
  this.els.extendBtn.addEventListener("click", () => void this.handleExtend());
4936
5252
  }
4937
5253
  /** Success overlay + onBooked fire when the held seats settle to booked. */
@@ -4940,7 +5256,7 @@ var SeatPicker = class _SeatPicker {
4940
5256
  el2.className = "sl-booked";
4941
5257
  el2.setAttribute("role", "status");
4942
5258
  el2.setAttribute("aria-live", "polite");
4943
- el2.innerHTML = `<div class="sl-booked-badge"><svg viewBox="0 0 24 24"><path d="M20 6L9 17l-5-5"/></svg></div><div class="sl-booked-title">You're all set</div><div class="sl-booked-sub" data-ref="bookedSub"></div>`;
5259
+ el2.innerHTML = `<div class="sl-booked-badge"><svg viewBox="0 0 24 24"><path d="M20 6L9 17l-5-5"/></svg></div><div class="sl-booked-title">${this.tf("picker.allSetTitle", "You're all set")}</div><div class="sl-booked-sub" data-ref="bookedSub"></div>`;
4944
5260
  this.root.appendChild(el2);
4945
5261
  this.bookedEl = el2;
4946
5262
  this.els.bookedSub = el2.querySelector('[data-ref="bookedSub"]');
@@ -4951,8 +5267,7 @@ var SeatPicker = class _SeatPicker {
4951
5267
  * host `messages` override (which makes `t()` return the override, not the key).
4952
5268
  */
4953
5269
  tf(key, fallback) {
4954
- const v = (0, import_core2.t)(key);
4955
- return v === key ? fallback : v;
5270
+ return tfStatic(key, fallback);
4956
5271
  }
4957
5272
  /** Sold-out overlay — an informational state with no unavailable action. */
4958
5273
  buildSoldoutOverlay() {
@@ -5137,7 +5452,7 @@ var SeatPicker = class _SeatPicker {
5137
5452
  }
5138
5453
  canAddTicket() {
5139
5454
  if (this.totalTicketCount() < this.maxTickets) return true;
5140
- this.toast(`You can select up to ${this.maxTickets} tickets for this order.`, "warning");
5455
+ this.toast((0, import_core2.t)("picker.maxTicketsForOrder", { count: this.maxTickets }), "warning");
5141
5456
  return false;
5142
5457
  }
5143
5458
  pendingGATotal(gaAreas) {
@@ -5160,21 +5475,21 @@ var SeatPicker = class _SeatPicker {
5160
5475
  }
5161
5476
  if (this.confirmSeat || this.tableDialog && !this.tableDialogHeld) {
5162
5477
  cta.disabled = true;
5163
- cta.textContent = this.tableDialog ? "Confirm your table" : "Confirm or cancel this seat";
5478
+ cta.textContent = this.tableDialog ? this.tf("picker.confirmYourTable", "Confirm your table") : this.tf("picker.confirmOrCancelSeat", "Confirm or cancel this seat");
5164
5479
  return;
5165
5480
  }
5166
5481
  if (this.ctaPhase === "holding") {
5167
5482
  cta.disabled = true;
5168
- cta.innerHTML = '<span class="sl-cta-spin" aria-hidden="true"></span>Securing seats\u2026';
5483
+ cta.innerHTML = `<span class="sl-cta-spin" aria-hidden="true"></span>${this.tf("picker.securingSeats", "Securing your seats\u2026")}`;
5169
5484
  return;
5170
5485
  }
5171
5486
  if (this.ctaPhase === "checkout") {
5172
5487
  cta.disabled = true;
5173
- cta.innerHTML = '<span class="sl-cta-spin" aria-hidden="true"></span>Opening secure checkout\u2026';
5488
+ cta.innerHTML = `<span class="sl-cta-spin" aria-hidden="true"></span>${this.tf("picker.openingCheckout", "Opening secure checkout\u2026")}`;
5174
5489
  return;
5175
5490
  }
5176
5491
  cta.disabled = count === 0;
5177
- cta.textContent = this.hold ? pending ? `Secure ${pending} more & checkout` : "Continue to checkout" : count ? "Hold seats & checkout" : "Select seats";
5492
+ cta.textContent = this.hold ? pending ? (0, import_core2.t)("picker.secureMoreAndCheckout", { count: pending }) : this.tf("picker.continueToCheckout", "Continue to checkout") : count ? this.tf("picker.holdSeatsAndCheckout", "Hold seats & checkout") : this.tf("picker.selectSeats", "Select seats");
5178
5493
  }
5179
5494
  setCtaPhase(phase) {
5180
5495
  this.ctaPhase = phase;
@@ -5235,7 +5550,7 @@ var SeatPicker = class _SeatPicker {
5235
5550
  this.syncTray();
5236
5551
  this.emitHoldChange();
5237
5552
  this.opts.onHoldRestored?.(restored, restored.seats ?? [], this.buildHandoff(restored));
5238
- if (automatic) this.toast("Your held tickets have been restored.", "success");
5553
+ if (automatic) this.toast(this.tf("picker.heldTicketsRestored", "Your held tickets have been restored."), "success");
5239
5554
  return restored;
5240
5555
  } catch (error) {
5241
5556
  const status = error?.status;
@@ -5270,10 +5585,77 @@ var SeatPicker = class _SeatPicker {
5270
5585
  buildMinimap() {
5271
5586
  const vp = this.controller.getViewport();
5272
5587
  if (!vp || !this.els.map) return;
5273
- const b = vp.bounds;
5274
- if (!(b.width > 0 && b.height > 0)) return;
5275
- const MAXW = 158;
5276
- 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;
5277
5659
  const PAD = 6;
5278
5660
  const aspect = b.width / Math.max(1, b.height);
5279
5661
  let w = MAXW;
@@ -5285,32 +5667,40 @@ var SeatPicker = class _SeatPicker {
5285
5667
  w = Math.max(64, w);
5286
5668
  h = Math.max(48, h);
5287
5669
  const dpr = Math.min(2, window.devicePixelRatio || 1);
5288
- const wrap = document.createElement("div");
5289
- wrap.className = "sl-minimap";
5290
- wrap.setAttribute("aria-hidden", "true");
5291
- const canvas = document.createElement("canvas");
5292
5670
  canvas.width = Math.round(w * dpr);
5293
5671
  canvas.height = Math.round(h * dpr);
5294
5672
  canvas.style.width = `${w}px`;
5295
5673
  canvas.style.height = `${h}px`;
5296
- wrap.appendChild(canvas);
5297
- (this.regions["bottom-left"] ?? this.els.map).appendChild(wrap);
5298
- this.miniCanvas = canvas;
5299
- const scale = Math.min((w - PAD * 2) / Math.max(1, b.width), (h - PAD * 2) / Math.max(1, b.height)) * dpr;
5300
- const offX = (w * dpr - b.width * scale) / 2 - b.x * scale;
5301
- const offY = (h * dpr - b.height * scale) / 2 - b.y * scale;
5302
- this.miniTf = { scale, offX, offY, dpr };
5303
- const base = document.createElement("canvas");
5304
5674
  base.width = canvas.width;
5305
5675
  base.height = canvas.height;
5306
- this.miniBase = base;
5307
- wrap.addEventListener("click", (e) => this.minimapJump(e));
5308
- this.drawMinimapStatic();
5309
- 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
+ );
5310
5698
  }
5311
5699
  /** Repaint the static overview + rect (floor switch, live open/close). */
5312
5700
  refreshMinimap() {
5313
5701
  if (!this.miniBase) return;
5702
+ this.layoutMinimap();
5703
+ this.syncMinimapLabel();
5314
5704
  this.drawMinimapStatic();
5315
5705
  this.drawMinimapRect();
5316
5706
  }
@@ -5318,8 +5708,7 @@ var SeatPicker = class _SeatPicker {
5318
5708
  drawMinimapStatic() {
5319
5709
  const base = this.miniBase;
5320
5710
  const tf = this.miniTf;
5321
- const doc = this.controller.doc;
5322
- if (!base || !tf || !doc) return;
5711
+ if (!base || !tf) return;
5323
5712
  const ctx = base.getContext("2d");
5324
5713
  if (!ctx) return;
5325
5714
  ctx.clearRect(0, 0, base.width, base.height);
@@ -5328,34 +5717,42 @@ var SeatPicker = class _SeatPicker {
5328
5717
  const line = this.cssVar("--sl-line") || "rgba(139,147,167,.5)";
5329
5718
  const muted = this.cssVar("--sl-muted") || "#8b93a7";
5330
5719
  const accent = this.cssVar("--sl-accent") || "#6e7bff";
5331
- const zoneColor = new Map((doc.zones ?? []).map((z) => [z.id, z.color]));
5332
- let drewSection = false;
5333
- for (const o of this.activeFloorObjects()) {
5334
- if (o.type !== "section" || !o.outline || o.outline.length < 3) continue;
5335
- drewSection = true;
5336
- const closed = this.controller.isSectionClosed(o.id);
5337
- 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;
5338
5724
  ctx.beginPath();
5339
- 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)));
5340
5726
  ctx.closePath();
5341
- ctx.globalAlpha = closed ? 0.26 : 0.42;
5342
- 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;
5343
5729
  ctx.fill();
5344
- ctx.globalAlpha = 0.85;
5730
+ ctx.globalAlpha = 0.82;
5345
5731
  ctx.lineWidth = Math.max(1, tf.dpr);
5346
5732
  ctx.strokeStyle = line;
5347
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
+ }
5348
5744
  }
5349
5745
  ctx.globalAlpha = 1;
5350
- if (!drewSection) {
5746
+ if (!snapshot.sections.length) {
5351
5747
  const r = Math.max(1, tf.dpr);
5352
- for (const seat of (0, import_core2.expandChart)(doc)) {
5353
- const cat = doc.categories.find((c) => c.key === seat.categoryKey);
5354
- 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;
5355
5751
  ctx.beginPath();
5356
5752
  ctx.arc(fx(seat.x), fy(seat.y), r, 0, Math.PI * 2);
5357
5753
  ctx.fill();
5358
5754
  }
5755
+ ctx.globalAlpha = 1;
5359
5756
  }
5360
5757
  }
5361
5758
  /** Blit the base overview, then stroke the current viewport rectangle on top. */
@@ -5370,41 +5767,106 @@ var SeatPicker = class _SeatPicker {
5370
5767
  ctx.drawImage(base, 0, 0);
5371
5768
  const vp = this.controller.getViewport();
5372
5769
  if (!vp) return;
5373
- const v = vp.visible;
5374
- const x = v.x * tf.scale + tf.offX;
5375
- const y = v.y * tf.scale + tf.offY;
5376
- const w = v.width * tf.scale;
5377
- const h = v.height * tf.scale;
5378
- 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";
5379
5773
  ctx.save();
5380
- ctx.globalAlpha = 0.14;
5381
- ctx.fillStyle = accent;
5382
- 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);
5383
5782
  ctx.globalAlpha = 1;
5384
- ctx.lineWidth = Math.max(1.5, tf.dpr * 1.5);
5385
- ctx.strokeStyle = accent;
5386
- 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
+ }
5387
5790
  ctx.restore();
5388
5791
  }
5389
- /** Minimap click → focus the section under the point (or overview on a miss). */
5390
- minimapJump(e) {
5792
+ minimapWorldPoint(event) {
5391
5793
  const canvas = this.miniCanvas;
5392
5794
  const tf = this.miniTf;
5393
- if (!canvas || !tf) return;
5394
- const r = canvas.getBoundingClientRect();
5395
- const px = (e.clientX - r.left) * (canvas.width / r.width);
5396
- const py = (e.clientY - r.top) * (canvas.height / r.height);
5397
- const wx = (px - tf.offX) / tf.scale;
5398
- const wy = (py - tf.offY) / tf.scale;
5399
- for (const o of this.activeFloorObjects()) {
5400
- if (o.type !== "section" || !o.outline || o.outline.length < 3) continue;
5401
- if (this.controller.isSectionClosed(o.id)) continue;
5402
- if (pointInPolygon(wx, wy, o.outline)) {
5403
- 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);
5404
5865
  return;
5405
5866
  }
5406
5867
  }
5407
- this.controller.overview();
5868
+ this.controller.clearSectionFocus();
5869
+ this.controller.panToWorld(point);
5408
5870
  }
5409
5871
  // ---- F4 price-band filter -------------------------------------------------
5410
5872
  /** Effective display price of a category: host pricing override → first tier → base. */
@@ -5413,32 +5875,39 @@ var SeatPicker = class _SeatPicker {
5413
5875
  if (chart === void 0 || !c.key) return chart;
5414
5876
  return this.paidPrice(c.key, c.tiers?.[0]?.id ?? null, chart);
5415
5877
  }
5416
- /** 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. */
5417
5885
  priceBands() {
5418
5886
  const doc = this.controller.doc;
5419
5887
  if (!doc) return [];
5420
- 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);
5421
5889
  if (!priced.length) return [];
5422
- 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);
5423
5891
  if (distinct.length <= 5) {
5424
- return distinct.map((price) => ({
5425
- id: `p${price}`,
5426
- label: this.money(price),
5427
- keys: priced.filter((p) => p.price === price).map((p) => p.key),
5428
- min: price,
5429
- 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
5430
5898
  }));
5431
5899
  }
5432
- 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);
5433
5902
  const bands = [];
5434
- for (let i = 0; i < distinct.length; i += chunk) {
5435
- const slice = distinct.slice(i, i + chunk);
5436
- const lo = slice[0];
5437
- 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));
5438
5907
  bands.push({
5439
5908
  id: `b${i}`,
5440
5909
  label: lo === hi ? this.money(lo) : `${this.money(lo)}\u2013${this.money(hi)}`,
5441
- keys: priced.filter((p) => p.price >= lo && p.price <= hi).map((p) => p.key),
5910
+ keys: slice.map((item) => item.key),
5442
5911
  min: lo,
5443
5912
  max: hi
5444
5913
  });
@@ -5453,8 +5922,8 @@ var SeatPicker = class _SeatPicker {
5453
5922
  if (bands.length < 2) return;
5454
5923
  const select = document.createElement("select");
5455
5924
  select.className = "sl-price-select";
5456
- select.setAttribute("aria-label", "Filter and focus seats by price");
5457
- select.innerHTML = `<option value="all">All prices</option>` + bands.map((band) => `<option value="${band.id}">${band.label}</option>`).join("");
5925
+ select.setAttribute("aria-label", this.tf("picker.filterAndFocusByPrice", "Filter and focus seats by price"));
5926
+ select.innerHTML = `<option value="all">${this.tf("picker.allPrices", "All prices")}</option>` + bands.map((band) => `<option value="${band.id}">${band.label}</option>`).join("");
5458
5927
  this.els.pricesSec.appendChild(select);
5459
5928
  select.addEventListener("change", () => {
5460
5929
  const band = bands.find((candidate) => candidate.id === select.value);
@@ -5481,8 +5950,8 @@ var SeatPicker = class _SeatPicker {
5481
5950
  const projection = document.createElement("div");
5482
5951
  projection.className = "sl-projection";
5483
5952
  projection.setAttribute("role", "group");
5484
- projection.setAttribute("aria-label", "Venue view");
5485
- projection.innerHTML = '<button type="button" data-view="map" aria-pressed="true" title="Flat 2D map">Map</button><button type="button" data-view="venue3d" aria-pressed="false" title="Interactive 3D venue view">3D</button>';
5953
+ projection.setAttribute("aria-label", this.tf("picker.venueView", "Venue view"));
5954
+ projection.innerHTML = `<button type="button" data-view="map" aria-pressed="true" title="${this.tf("picker.flat2dMap", "Flat 2D map")}">${this.tf("picker.map", "Map")}</button><button type="button" data-view="venue3d" aria-pressed="false" title="${this.tf("picker.interactive3dVenueView", "Interactive 3D venue view")}">3D</button>`;
5486
5955
  projection.querySelectorAll("button").forEach((button) => {
5487
5956
  button.addEventListener("click", () => {
5488
5957
  this.setBuyerView(button.dataset.view);
@@ -5493,7 +5962,13 @@ var SeatPicker = class _SeatPicker {
5493
5962
  this.syncProjection();
5494
5963
  }
5495
5964
  if (hasSections) {
5496
- 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"];
5497
5972
  const pills = document.createElement("div");
5498
5973
  pills.className = "sl-rungs on";
5499
5974
  pills.setAttribute("role", "group");
@@ -5526,18 +6001,38 @@ var SeatPicker = class _SeatPicker {
5526
6001
  const floors = this.controller.getFloors();
5527
6002
  const rail = document.createElement("div");
5528
6003
  rail.className = "sl-floors on";
5529
- rail.setAttribute("role", "group");
5530
- rail.setAttribute("aria-label", (0, import_core2.t)("picker.floor"));
5531
- rail.innerHTML = floors.map((f) => `<button type="button" data-floor="${f.id}">${f.name}</button>`).join("");
5532
- rail.querySelectorAll("button").forEach((btn) => {
5533
- btn.addEventListener("click", () => {
5534
- this.controller.setFloor(btn.dataset.floor);
5535
- this.showSectionCard(null);
5536
- this.syncFloors();
5537
- this.syncRung();
5538
- this.refreshMinimap();
5539
- });
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();
5540
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);
5541
6036
  this.regions["left-rail"].appendChild(rail);
5542
6037
  this.floorsEl = rail;
5543
6038
  this.syncFloors();
@@ -5583,19 +6078,59 @@ var SeatPicker = class _SeatPicker {
5583
6078
  syncFloors() {
5584
6079
  if (!this.floorsEl) return;
5585
6080
  const active = this.controller.getActiveFloorId();
5586
- this.floorsEl.querySelectorAll("button").forEach((btn) => {
5587
- btn.classList.toggle("on", btn.dataset.floor === active);
5588
- });
6081
+ const overview = this.controller.isFloorOverview();
6082
+ const select = this.floorsEl.querySelector("select");
6083
+ if (select) select.value = overview ? "" : active;
5589
6084
  }
5590
6085
  /** Show (or clear, on null) the tapped-section summary card. */
5591
6086
  showSectionCard(summary) {
5592
6087
  this.lastSection = summary;
5593
6088
  this.secCardEl?.remove();
5594
6089
  this.secCardEl = null;
5595
- if (!summary) return;
6090
+ if (!summary) {
6091
+ this.refreshMinimap();
6092
+ return;
6093
+ }
5596
6094
  this.secCardCollapsed = this.controller.getRung() === "seats";
5597
6095
  this.secCardShownAt = Date.now();
5598
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
+ });
5599
6134
  }
5600
6135
  /**
5601
6136
  * Render the section card in the form the layout + state want: expanded card
@@ -5605,26 +6140,36 @@ var SeatPicker = class _SeatPicker {
5605
6140
  renderSectionCard(summary) {
5606
6141
  if (!this.els.map) return;
5607
6142
  this.secCardEl?.remove();
5608
- const paid = summary.categories.length ? summary.categories.map((c) => this.paidPrice(c.key, null, c.price)) : [summary.priceMin, summary.priceMax];
5609
- const paidMin = Math.min(...paid);
5610
- const paidMax = Math.max(...paid);
6143
+ if (this.root?.dataset.layout === "narrow" && (this.hold || this.committedSelection().length || this.pendingGACount())) {
6144
+ this.lastSection = null;
6145
+ return;
6146
+ }
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));
5611
6153
  const priceLabel = paidMin === paidMax ? this.money(paidMin) : `${this.money(paidMin)}\u2013${this.money(paidMax)}`;
5612
6154
  const leftLabel = (0, import_core2.tCount)("picker.seatsLeftInSection", summary.seatsLeft);
5613
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);
5614
6157
  const card = document.createElement("div");
5615
6158
  const narrow = this.root?.dataset.layout === "narrow";
5616
6159
  if (narrow) {
5617
6160
  card.className = "sl-seccard strip on";
5618
6161
  card.setAttribute("role", "status");
5619
6162
  card.setAttribute("aria-label", (0, import_core2.t)("picker.sectionSummaryAria", { label: summary.label }));
5620
- 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);
5621
6165
  card.querySelector(".sl-seccard-x").addEventListener("click", () => this.controller.overview());
5622
6166
  (this.els.sheetHead ?? this.els.side ?? this.els.map).appendChild(card);
5623
6167
  } else if (this.secCardCollapsed) {
5624
6168
  card.className = "sl-seccard mini on";
5625
6169
  card.setAttribute("role", "button");
5626
6170
  card.setAttribute("aria-label", (0, import_core2.t)("picker.sectionSummaryAria", { label: summary.label }));
5627
- 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);
5628
6173
  card.addEventListener("click", (e) => {
5629
6174
  if (e.target.closest(".sl-seccard-x")) return;
5630
6175
  this.secCardCollapsed = false;
@@ -5639,9 +6184,13 @@ var SeatPicker = class _SeatPicker {
5639
6184
  card.setAttribute("aria-label", (0, import_core2.t)("picker.sectionSummaryAria", { label: summary.label }));
5640
6185
  const mix = summary.categories.map((c) => {
5641
6186
  const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
5642
- 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>`;
5643
6191
  }).join("");
5644
- 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);
5645
6194
  card.querySelector(".sl-seccard-x").addEventListener("click", () => this.controller.overview());
5646
6195
  card.querySelector(".sl-seccard-overview").addEventListener("click", () => this.controller.overview());
5647
6196
  (this.regions["top-center"] ?? this.els.map).appendChild(card);
@@ -5705,14 +6254,15 @@ var SeatPicker = class _SeatPicker {
5705
6254
  }
5706
6255
  const cat = this.controller.doc?.categories.find((c) => c.key === seat.categoryKey);
5707
6256
  const status = this.controller.getStatus(seat.id) ?? "free";
5708
- const statusText = status === "free" ? "available" : status === "held" ? "on hold" : "taken";
5709
- const price = cat ? this.catPrice(cat) : void 0;
6257
+ const statusText = status === "free" ? this.tf("picker.statusAvailable", "available") : status === "held" ? this.tf("picker.statusOnHold", "on hold") : this.tf("picker.statusTaken2", "taken");
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;
5710
6260
  const table = this.controller.tableSelection(seat.id);
5711
6261
  const details = table ?? this.controller.seatDetails(seat.id);
5712
6262
  const typeWord = this.rowTypeWord(details);
5713
6263
  const buyerName = details?.rowLabel ?? details?.displayLabel ?? seat.displayLabel ?? seat.label;
5714
- const identity = table ? `${typeWord} ${buyerName}, ${table.bookingMode === "variable" ? `${table.minOccupancy} to ${table.maxOccupancy} guests` : `${table.capacity} guests`}` : details?.objectType === "booth" ? `${typeWord} ${buyerName}` : details?.objectType === "table" ? `${typeWord} ${buyerName}, seat ${details.seatNumber ?? seat.label}` : `Seat ${details?.displayLabel ?? seat.displayLabel ?? seat.label}`;
5715
- this.srEl.textContent = `${identity}, ${cat?.label ?? seat.categoryKey}${price != null ? `, ${this.money(price)}` : ""}, ${statusText}`;
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 });
6265
+ this.srEl.textContent = `${identity}, ${cat?.label ?? seat.categoryKey}${priceText ? `, ${priceText}` : ""}, ${statusText}`;
5716
6266
  }
5717
6267
  // ---- atomic table confirmation / guest quantity ---------------------------
5718
6268
  showTableDialog(table, held, returnFocus) {
@@ -5732,7 +6282,7 @@ var SeatPicker = class _SeatPicker {
5732
6282
  const typeWord = this.rowTypeWord(table);
5733
6283
  const el2 = document.createElement("div");
5734
6284
  el2.className = "sl-table-scrim";
5735
- el2.innerHTML = `<section class="sl-table-dialog" role="dialog" aria-modal="true" aria-labelledby="sl-table-title" aria-describedby="sl-table-copy"><div class="sl-table-head"><div class="sl-table-eyebrow">${esc(variable ? `Flexible party \xB7 ${typeWord}` : `Whole ${typeWord}`)}</div><h2 class="sl-table-title" id="sl-table-title">${esc(table.displayLabel ?? table.label)}</h2><p class="sl-table-copy" id="sl-table-copy">${variable ? `Choose how many guests will sit together. This table is held exclusively for your party.` : `All ${table.capacity} places are booked together as one exclusive table.`}</p></div><div class="sl-table-body"><div class="sl-table-summary"><span>${esc(cat?.label ?? table.categoryKey)}</span><b data-table-unit>${this.money(this.paidPrice(table.categoryKey, table.tierId ?? null, table.price))} per guest</b><span class="muted">Table capacity</span><span>${table.capacity} guests</span><span class="muted">Total</span><b data-table-total></b></div>` + (variable ? `<label class="sl-table-qtylabel" id="sl-table-qty-label">Number of guests</label><div class="sl-table-stepper" role="group" aria-labelledby="sl-table-qty-label"><button type="button" data-table-step="-1" aria-label="Fewer guests">\u2212</button><output aria-live="polite" data-table-qty>${table.quantity}</output><button type="button" data-table-step="1" aria-label="More guests">+</button></div><div class="sl-table-range">Choose ${table.minOccupancy}\u2013${table.maxOccupancy} guests</div>` : `<input type="hidden" data-table-qty value="${table.capacity}">`) + `<div class="sl-table-actions"><button type="button" class="sl-table-cancel">Cancel</button><button type="button" class="sl-table-confirm">${held ? "Update table" : variable ? "Select table" : "Select whole table"}</button></div></div></section>`;
6285
+ el2.innerHTML = `<section class="sl-table-dialog" role="dialog" aria-modal="true" aria-labelledby="sl-table-title" aria-describedby="sl-table-copy"><div class="sl-table-head"><div class="sl-table-eyebrow">${esc(variable ? (0, import_core2.t)("picker.flexiblePartyTypeWord", { typeWord }) : (0, import_core2.t)("picker.wholeTypeWord", { typeWord }))}</div><h2 class="sl-table-title" id="sl-table-title">${esc(table.displayLabel ?? table.label)}</h2><p class="sl-table-copy" id="sl-table-copy">${variable ? this.tf("picker.chooseGuestsCopy", "Choose how many guests will sit together. This table is held exclusively for your party.") : (0, import_core2.t)("picker.allPlacesBookedTogether", { count: table.capacity })}</p></div><div class="sl-table-body"><div class="sl-table-summary"><span>${esc(cat?.label ?? table.categoryKey)}</span><b data-table-unit>${(0, import_core2.t)("picker.perGuestPrice", { price: this.money(this.paidPrice(table.categoryKey, table.tierId ?? null, table.price)) })}</b><span class="muted">${this.tf("picker.tableCapacity", "Table capacity")}</span><span>${(0, import_core2.t)("picker.guestsCount", { count: table.capacity })}</span><span class="muted">${this.tf("picker.total", "Total")}</span><b data-table-total></b></div>` + (variable ? `<label class="sl-table-qtylabel" id="sl-table-qty-label">${this.tf("picker.numberOfGuests", "Number of guests")}</label><div class="sl-table-stepper" role="group" aria-labelledby="sl-table-qty-label"><button type="button" data-table-step="-1" aria-label="${this.tf("picker.fewerGuests", "Fewer guests")}">\u2212</button><output aria-live="polite" data-table-qty>${table.quantity}</output><button type="button" data-table-step="1" aria-label="${this.tf("picker.moreGuests", "More guests")}">+</button></div><div class="sl-table-range">${(0, import_core2.t)("picker.chooseMinMaxGuests", { min: table.minOccupancy, max: table.maxOccupancy })}</div>` : `<input type="hidden" data-table-qty value="${table.capacity}">`) + `<div class="sl-table-actions"><button type="button" class="sl-table-cancel">${(0, import_core2.t)("common.cancel")}</button><button type="button" class="sl-table-confirm">${held ? this.tf("picker.updateTable", "Update table") : variable ? this.tf("picker.selectTable", "Select table") : this.tf("picker.selectWholeTable", "Select whole table")}</button></div></div></section>`;
5736
6286
  this.root.appendChild(el2);
5737
6287
  this.tableDialogEl = el2;
5738
6288
  this.renderTableDialogState();
@@ -5791,17 +6341,17 @@ var SeatPicker = class _SeatPicker {
5791
6341
  const button = this.tableDialogEl?.querySelector(".sl-table-confirm");
5792
6342
  if (button) {
5793
6343
  button.disabled = true;
5794
- button.textContent = held ? "Updating\u2026" : "Selecting\u2026";
6344
+ button.textContent = held ? this.tf("picker.updatingEllipsis", "Updating\u2026") : this.tf("picker.selectingEllipsis", "Selecting\u2026");
5795
6345
  }
5796
6346
  if (held) {
5797
6347
  try {
5798
6348
  const updated = await this.controller.replaceTableQuantity(table.label, table.quantity, this.opts.holdTtlMs);
5799
6349
  if (!updated) {
5800
- this.toast("That guest count could not be secured. Your current table hold is unchanged.", "warning");
6350
+ this.toast(this.tf("picker.guestCountCouldNotBeSecured", "That guest count could not be secured. Your current table hold is unchanged."), "warning");
5801
6351
  this.renderTableDialogState();
5802
6352
  if (button) {
5803
6353
  button.disabled = false;
5804
- button.textContent = "Update table";
6354
+ button.textContent = this.tf("picker.updateTable", "Update table");
5805
6355
  }
5806
6356
  return;
5807
6357
  }
@@ -5815,13 +6365,13 @@ var SeatPicker = class _SeatPicker {
5815
6365
  this.dismissTableDialog();
5816
6366
  this.syncTray();
5817
6367
  this.emitHoldChange();
5818
- this.toast(`${table.label} updated for ${table.quantity} guests.`, "success");
6368
+ this.toast((0, import_core2.t)("picker.tableUpdatedForGuests", { label: table.label, count: table.quantity }), "success");
5819
6369
  } catch (error) {
5820
6370
  this.opts.onError?.(error);
5821
- this.toast("That guest count is no longer available. Your current hold is unchanged.", "error");
6371
+ this.toast(this.tf("picker.guestCountNoLongerAvailable", "That guest count is no longer available. Your current hold is unchanged."), "error");
5822
6372
  if (button) {
5823
6373
  button.disabled = false;
5824
- button.textContent = "Update table";
6374
+ button.textContent = this.tf("picker.updateTable", "Update table");
5825
6375
  }
5826
6376
  }
5827
6377
  return;
@@ -5829,7 +6379,7 @@ var SeatPicker = class _SeatPicker {
5829
6379
  if (!this.controller.setTableQuantity(table.label, table.quantity)) {
5830
6380
  if (button) {
5831
6381
  button.disabled = false;
5832
- button.textContent = table.bookingMode === "variable" ? "Select table" : "Select whole table";
6382
+ button.textContent = table.bookingMode === "variable" ? this.tf("picker.selectTable", "Select table") : this.tf("picker.selectWholeTable", "Select whole table");
5833
6383
  }
5834
6384
  return;
5835
6385
  }
@@ -5875,16 +6425,26 @@ var SeatPicker = class _SeatPicker {
5875
6425
  '"': "&quot;"
5876
6426
  })[char]);
5877
6427
  const identityFields = [
5878
- details?.sectionLabel ? `<div class="sl-confirm-field"><span class="sl-confirm-key">Section</span><span class="sl-confirm-value">${safe(details.sectionLabel)}</span></div>` : "",
6428
+ details?.sectionLabel ? `<div class="sl-confirm-field"><span class="sl-confirm-key">${this.tf("picker.section", "Section")}</span><span class="sl-confirm-value">${safe(details.sectionLabel)}</span></div>` : "",
5879
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>` : "",
5880
- details?.objectType !== "booth" ? `<div class="sl-confirm-field"><span class="sl-confirm-key">Seat</span><span class="sl-confirm-value">${safe(details?.seatNumber ?? details?.displayLabel ?? seat.displayLabel ?? seat.label)}</span></div>` : ""
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>` : ""
5881
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>` : "";
5882
6439
  const el2 = document.createElement("div");
5883
6440
  el2.className = "sl-confirm";
5884
6441
  el2.setAttribute("role", "dialog");
5885
- el2.setAttribute("aria-label", `Confirm seat ${seat.label}`);
5886
- el2.style.setProperty("--sl-cat", cat?.color ?? "#6e7bff");
5887
- 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">Cancel</button><button type="button" class="sl-confirm-add"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4 4L19 7"/></svg>Select</button></div></div>`;
6442
+ el2.setAttribute("aria-label", (0, import_core2.t)("picker.confirmSeatLabel", { label: seat.label }));
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>`;
5888
6448
  this.els.map.appendChild(el2);
5889
6449
  this.confirmEl = el2;
5890
6450
  const thumb = el2.querySelector(".sl-confirm-thumb");
@@ -5900,6 +6460,22 @@ var SeatPicker = class _SeatPicker {
5900
6460
  this.openSeatConfidencePassport(seat, event.currentTarget instanceof HTMLElement ? event.currentTarget : null);
5901
6461
  });
5902
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
+ });
5903
6479
  el2.querySelector(".sl-confirm-3d")?.addEventListener("click", () => {
5904
6480
  if (this.buyerView === "venue3d") {
5905
6481
  this.dismissConfirm();
@@ -6037,7 +6613,7 @@ var SeatPicker = class _SeatPicker {
6037
6613
  el2.className = "sl-view";
6038
6614
  el2.setAttribute("role", "dialog");
6039
6615
  el2.setAttribute("aria-label", (0, import_core2.t)("picker.viewFromSeat", { label: seat.label }));
6040
- el2.innerHTML = `<div class="sl-view-head"><span class="sl-view-title">${(0, import_core2.t)("picker.viewFromSeat", { label: seat.label })}</span><span class="sl-view-cap">${caption}</span><button type="button" class="sl-view-x" aria-label="Close"><svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button></div><div class="sl-view-pano"><span class="sl-view-badge">${real ? (0, import_core2.t)("picker.real360") : (0, import_core2.t)("picker.preview")}</span><span class="sl-view-hint">Drag to look around \xB7 scroll to zoom</span></div>`;
6616
+ el2.innerHTML = `<div class="sl-view-head"><span class="sl-view-title">${(0, import_core2.t)("picker.viewFromSeat", { label: seat.label })}</span><span class="sl-view-cap">${caption}</span><button type="button" class="sl-view-x" aria-label="${this.tf("picker.close", "Close")}"><svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button></div><div class="sl-view-pano"><span class="sl-view-badge">${real ? (0, import_core2.t)("picker.real360") : (0, import_core2.t)("picker.preview")}</span><span class="sl-view-hint">${this.tf("picker.dragToLookAround", "Drag to look around \xB7 scroll to zoom")}</span></div>`;
6041
6617
  this.root.appendChild(el2);
6042
6618
  this.viewEl = el2;
6043
6619
  const pano = el2.querySelector(".sl-view-pano");
@@ -6233,27 +6809,27 @@ var SeatPicker = class _SeatPicker {
6233
6809
  copy.className = "sl-offer-copy";
6234
6810
  const kicker = document.createElement("span");
6235
6811
  kicker.className = "sl-offer-kicker";
6236
- kicker.textContent = active ? "Current ticket offer" : "Upcoming ticket offer";
6812
+ kicker.textContent = active ? this.tf("picker.currentTicketOffer", "Current ticket offer") : this.tf("picker.upcomingTicketOffer", "Upcoming ticket offer");
6237
6813
  const name = document.createElement("strong");
6238
6814
  name.className = "sl-offer-name";
6239
- name.textContent = offer.name || (active ? "Current offer" : "Scheduled offer");
6815
+ name.textContent = offer.name || (active ? this.tf("picker.currentOffer", "Current offer") : this.tf("picker.scheduledOffer", "Scheduled offer"));
6240
6816
  const line = document.createElement("span");
6241
6817
  line.className = "sl-offer-line";
6242
6818
  const facts = [];
6243
6819
  if (active && availability.fromPrice != null) facts.push(this.money(availability.fromPrice / 100));
6244
- if (active && offer.remaining != null) facts.push(`${offer.remaining} available`);
6245
- if (active && offer.endsAt != null) facts.push(`until ${formatWhen(offer.endsAt, this.eventTimezone, this.opts.locale)}`);
6246
- if (upcoming?.startsAt != null) facts.push(`starts ${formatWhen(upcoming.startsAt, this.eventTimezone, this.opts.locale)}`);
6820
+ if (active && offer.remaining != null) facts.push((0, import_core2.t)("picker.offerRemainingAvailable", { count: offer.remaining }));
6821
+ if (active && offer.endsAt != null) facts.push((0, import_core2.t)("picker.offerUntil", { time: formatWhen(offer.endsAt, this.eventTimezone, this.opts.locale) }));
6822
+ if (upcoming?.startsAt != null) facts.push((0, import_core2.t)("picker.offerStarts", { time: formatWhen(upcoming.startsAt, this.eventTimezone, this.opts.locale) }));
6247
6823
  line.textContent = facts.join(" \xB7 ");
6248
6824
  copy.append(kicker, name, line);
6249
6825
  const info = document.createElement("details");
6250
6826
  info.className = "sl-offer-info";
6251
6827
  const summary = document.createElement("summary");
6252
- summary.setAttribute("aria-label", `How the ${name.textContent} offer works`);
6828
+ summary.setAttribute("aria-label", (0, import_core2.t)("picker.howOfferWorks", { name: name.textContent ?? "" }));
6253
6829
  summary.textContent = "i";
6254
6830
  const detail = document.createElement("div");
6255
6831
  detail.className = "sl-offer-detail";
6256
- detail.textContent = active ? `This price applies automatically to eligible seats. Tickets in active carts temporarily reduce the available quantity; released or expired holds return it. When the offer ends, the next matching offer or normal ticket price takes over.` : `Tickets are available at their normal price now. This scheduled offer will apply automatically to eligible seats when it starts.`;
6832
+ detail.textContent = active ? this.tf("picker.offerDetailActive", "This price applies automatically to eligible seats. Tickets in active carts temporarily reduce the available quantity; released or expired holds return it. When the offer ends, the next matching offer or normal ticket price takes over.") : this.tf("picker.offerDetailUpcoming", "Tickets are available at their normal price now. This scheduled offer will apply automatically to eligible seats when it starts.");
6257
6833
  info.append(summary, detail);
6258
6834
  main.append(copy, info);
6259
6835
  host.replaceChildren(main);
@@ -6276,21 +6852,23 @@ var SeatPicker = class _SeatPicker {
6276
6852
  const doc = this.controller.doc;
6277
6853
  if (!doc || !this.els.prices) return;
6278
6854
  const left = this.controller.categoryAvailability();
6279
- this.narrateAvailability(doc.categories, left);
6280
- this.syncSoldout(doc.categories, left);
6855
+ const sellable = doc.categories.filter((c) => !c.notForSale);
6856
+ this.narrateAvailability(sellable, left);
6857
+ this.syncSoldout(sellable, left);
6281
6858
  const PRICE_LIMIT = 5;
6282
- const overflow = doc.categories.length - PRICE_LIMIT;
6859
+ const overflow = sellable.length - PRICE_LIMIT;
6283
6860
  const collapsed = overflow > 1 && !this.pricesExpanded;
6284
- const shown = collapsed ? doc.categories.slice(0, PRICE_LIMIT) : doc.categories;
6861
+ const shown = collapsed ? sellable.slice(0, PRICE_LIMIT) : sellable;
6285
6862
  this.els.prices.classList.toggle("sl-expanded", overflow > 1 && this.pricesExpanded);
6286
6863
  this.els.prices.innerHTML = shown.map((c) => {
6287
6864
  const price = this.catPrice(c);
6865
+ const range = this.catPriceRange(c);
6288
6866
  const offer = this.offerPrice(c.key);
6289
6867
  const previous = offer?.previousPrice != null && offer.previousPrice > offer.price ? offer.previousPrice : null;
6290
6868
  const active = this.focusedCatKey === c.key;
6291
6869
  const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
6292
- 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 ? "Show all seats" : `Show ${c.label} seats on the map`)}"><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">${left[c.key] ?? 0} left</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>`;
6293
- }).join("") + (overflow > 1 ? `<button type="button" class="sl-price-more" aria-expanded="${!collapsed}">` + (collapsed ? `Show all ${doc.categories.length} ticket types` : "Show fewer") + `</button>` : "") + `<div class="sl-status-key" aria-label="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>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>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>`;
6294
6872
  this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
6295
6873
  row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
6296
6874
  row.addEventListener("mouseleave", () => this.controller.getRenderer()?.setCategoryHighlight?.(null));
@@ -6348,7 +6926,7 @@ var SeatPicker = class _SeatPicker {
6348
6926
  const now = left[cat.key] ?? 0;
6349
6927
  if (before === void 0 || now >= before) continue;
6350
6928
  const taken = before - now;
6351
- textEl.textContent = `${taken} seat${taken === 1 ? "" : "s"} just taken in ${cat.label} \xB7 ${now} left`;
6929
+ textEl.textContent = (0, import_core2.tCount)("picker.seatsJustTakenInCategory", taken, { label: cat.label, left: now });
6352
6930
  this.els.live?.classList.remove("on");
6353
6931
  void this.els.live?.offsetWidth;
6354
6932
  this.els.live?.classList.add("on");
@@ -6366,12 +6944,17 @@ var SeatPicker = class _SeatPicker {
6366
6944
  const gone = this.controller.getSelection().filter((s) => !ownLabels.has(s.label) && (this.controller.getStatus(s.id) ?? "free") !== "free");
6367
6945
  if (!gone.length) return;
6368
6946
  this.controller.deselect(gone.map((s) => s.id));
6369
- this.toast(`Seat ${gone[0].label} was just taken by another buyer.`, "error");
6947
+ this.toast((0, import_core2.t)("picker.seatJustTakenByAnother", { label: gone[0].label }), "error");
6370
6948
  }
6371
6949
  syncTray() {
6372
6950
  if (!this.els.tray) return;
6373
6951
  this.updateSelectionCapacity();
6374
6952
  const seats = this.committedSelection();
6953
+ if (this.root?.dataset.layout === "narrow" && this.secCardEl && (this.hold || seats.length || this.pendingGACount())) {
6954
+ this.secCardEl.remove();
6955
+ this.secCardEl = null;
6956
+ this.lastSection = null;
6957
+ }
6375
6958
  const gaAreas = this.controller.getGAAreas();
6376
6959
  const heldItems = this.hold?.items ?? [];
6377
6960
  const parts = [];
@@ -6382,18 +6965,22 @@ var SeatPicker = class _SeatPicker {
6382
6965
  `<div class="sl-closed-note" role="status"><b>${this.tf("picker.salesClosedPill", "Sales are closed")}</b><span>${this.tf("picker.salesClosedCopy", "Ticket sales for this event have ended.")}${closedWhen}</span></div>`
6383
6966
  );
6384
6967
  } else if (!seats.length && !heldItems.length && !gaAreas.length) {
6385
- parts.push(`<div class="sl-tray-hint">Tap a seat on the map, or let us pick the best available for you.</div>`);
6968
+ parts.push(`<div class="sl-tray-hint">${this.tf("picker.trayHintTapOrBest", "Tap a seat on the map, or let us pick the best available for you.")}</div>`);
6386
6969
  } else if (!seats.length && !heldItems.length) {
6387
- parts.push(`<div class="sl-tray-hint">Tap a seat on the map \u2014 or grab standing tickets below.</div>`);
6970
+ parts.push(`<div class="sl-tray-hint">${this.tf("picker.trayHintTapOrStanding", "Tap a seat on the map \u2014 or grab standing tickets below.")}</div>`);
6388
6971
  }
6389
6972
  const noPicks = !seats.length && !heldItems.length && !this.pendingGACount();
6390
- if (!this.salesClosed && !this.hold && (noPicks || this.bestAvailableBusy || this.bestAvailableConfirm)) {
6391
- const cats = this.controller.doc?.categories ?? [];
6973
+ if (noPicks) this.baReopen = false;
6974
+ if (!this.salesClosed && !this.hold && (noPicks || this.baReopen || this.bestAvailableBusy || this.bestAvailableConfirm)) {
6975
+ const cats = (this.controller.doc?.categories ?? []).filter((c) => !c.notForSale);
6392
6976
  const zones = this.controller.getBestAvailableZones();
6393
6977
  if (this.baZone && !zones.some((zone) => zone.id === this.baZone)) this.baZone = "";
6394
- parts.push(this.bestAvailableConfirm ? `<div class="sl-ba" role="alert"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>Replace your current choices?</div><div class="sl-ba-replace"><b>We\u2019ll find ${this.baQty} seats together.</b><span>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>Keep mine</button><button type="button" class="replace" data-ba-replace>Find new seats</button></div></div>` : `<div class="sl-ba"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>Find the best seats together</div><div class="sl-ba-copy"><span class="wide">We\u2019ll choose the closest available group for you.</span><span class="narrow">Closest available group, chosen instantly.</span></div>` + // Premium quick-pick — present only when the chart actually has premium
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
6395
6979
  // seats (same present-only philosophy as the a11y filter chips).
6396
- (this.controller.hasPremiumSeats() ? `<button type="button" class="sl-ba-premium${this.baPremium ? " on" : ""}" data-ba-premium aria-pressed="${this.baPremium ? "true" : "false"}"><span class="star" aria-hidden="true">\u2605</span>${this.tf("picker.bestSeatsPremium", "Best seats")}</button>` : "") + (cats.length > 1 ? `<select aria-label="Preferred ticket type" data-ba-cat><option value="">Any ticket type</option>` + cats.map((c) => `<option value="${c.key}"${this.baCat === c.key ? " selected" : ""}>${c.label}</option>`).join("") + `</select>` : `<span aria-hidden="true"></span>`) + (zones.length ? `<select aria-label="Preferred venue zone" data-ba-zone><option value="">Any venue zone</option>` + zones.map((zone) => `<option value="${escapeOption(zone.id)}"${this.baZone === zone.id ? " selected" : ""}>${escapeOption(zone.label)}</option>`).join("") + `</select>` : "") + `<div class="sl-ba-qty"><button type="button" data-ba="-1" aria-label="Fewer seats">\u2212</button><span>${this.baQty}</span><button type="button" data-ba="1" aria-label="More seats">+</button></div><button type="button" class="sl-ba-go${this.bestAvailableBusy ? " sl-busy" : ""}"${this.bestAvailableBusy ? " disabled" : ""}>` + (this.bestAvailableBusy ? `<span class="sl-ba-spin" aria-hidden="true"></span>Finding the best seats\u2026` : `Find ${this.baQty} best ${this.baQty === 1 ? "seat" : "seats"}`) + `</button></div>`);
6980
+ (this.controller.hasPremiumSeats() ? `<button type="button" class="sl-ba-premium${this.baPremium ? " on" : ""}" data-ba-premium aria-pressed="${this.baPremium ? "true" : "false"}"><span class="star" aria-hidden="true">\u2605</span>${this.tf("picker.bestSeatsPremium", "Best seats")}</button>` : "") + (cats.length > 1 ? `<select aria-label="${this.tf("picker.preferredTicketType", "Preferred ticket type")}" data-ba-cat><option value="">${this.tf("picker.anyTicketType", "Any ticket type")}</option>` + cats.map((c) => `<option value="${c.key}"${this.baCat === c.key ? " selected" : ""}>${c.label}</option>`).join("") + `</select>` : `<span aria-hidden="true"></span>`) + (zones.length ? `<select aria-label="${this.tf("picker.preferredVenueZone", "Preferred venue zone")}" data-ba-zone><option value="">${this.tf("picker.anyVenueZone", "Any venue zone")}</option>` + zones.map((zone) => `<option value="${escapeOption(zone.id)}"${this.baZone === zone.id ? " selected" : ""}>${escapeOption(zone.label)}</option>`).join("") + `</select>` : "") + `<div class="sl-ba-qty"><button type="button" data-ba="-1" aria-label="${this.tf("picker.fewerSeats", "Fewer seats")}">\u2212</button><span>${this.baQty}</span><button type="button" data-ba="1" aria-label="${this.tf("picker.moreSeats", "More seats")}">+</button></div><button type="button" class="sl-ba-go${this.bestAvailableBusy ? " sl-busy" : ""}"${this.bestAvailableBusy ? " disabled" : ""}>` + (this.bestAvailableBusy ? `<span class="sl-ba-spin" aria-hidden="true"></span>${this.tf("picker.findingBestSeats", "Finding the best seats\u2026")}` : (0, import_core2.tCount)("picker.findBestSeatsCount", this.baQty)) + `</button>` + // Reopened over an existing selection: the buyer needs the way back
6981
+ // stated as plainly as the way forward (Go routes through the
6982
+ // "Replace your current choices?" confirm, so nothing is lost yet).
6983
+ (this.baReopen ? `<button type="button" class="sl-ba-back" data-ba-close>${this.tf("picker.keepMyPicks", "Keep my picks")}</button>` : "") + `</div>`);
6397
6984
  }
6398
6985
  const idGrid = (seatId, label, objectType, quantity = 1, objectId, identity) => {
6399
6986
  const esc = (value) => String(value ?? "\u2014").replace(/[&<>"]/g, (char) => ({
@@ -6405,21 +6992,21 @@ var SeatPicker = class _SeatPicker {
6405
6992
  const d = seatId ? this.controller.seatDetails(seatId) : null;
6406
6993
  const area = objectType === "ga" ? gaAreas.find((candidate) => candidate.id === objectId) : void 0;
6407
6994
  const effectiveType = objectType === "ga" ? "ga" : d?.objectType ?? objectType;
6408
- const typeWord = identity?.displayType?.trim() || d?.displayType?.trim() || area?.displayType?.trim() || (effectiveType === "table" ? "Table" : effectiveType === "booth" ? "Booth" : effectiveType === "ga" ? "General admission" : "Row");
6995
+ const typeWord = identity?.displayType?.trim() || d?.displayType?.trim() || area?.displayType?.trim() || (effectiveType === "table" ? this.tf("picker.table", "Table") : effectiveType === "booth" ? this.tf("picker.booth", "Booth") : effectiveType === "ga" ? this.tf("picker.generalAdmission", "General admission") : this.tf("picker.row", "Row"));
6409
6996
  const buyerName = identity?.rowLabel ?? identity?.displayLabel ?? d?.rowLabel ?? d?.displayLabel ?? area?.displayLabel ?? area?.label ?? label;
6410
6997
  if (effectiveType === "table" && identity?.bookingMode) {
6411
- return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">${esc(typeWord)}</span><span class="val">${esc(buyerName)}</span></span><span class="fld mid"><span class="sl-chip-eb">Guests</span><span class="val">${quantity}</span></span></div>`;
6998
+ return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">${esc(typeWord)}</span><span class="val">${esc(buyerName)}</span></span><span class="fld mid"><span class="sl-chip-eb">${this.tf("picker.guests", "Guests")}</span><span class="val">${quantity}</span></span></div>`;
6412
6999
  }
6413
7000
  if (effectiveType === "ga") {
6414
- return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">${esc(typeWord)}</span><span class="val">${esc(buyerName)}</span></span>` + (quantity > 1 ? `<span class="fld mid"><span class="sl-chip-eb">Tickets</span><span class="val">${quantity}</span></span>` : "") + `</div>`;
7001
+ return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">${esc(typeWord)}</span><span class="val">${esc(buyerName)}</span></span>` + (quantity > 1 ? `<span class="fld mid"><span class="sl-chip-eb">${this.tf("picker.tickets", "Tickets")}</span><span class="val">${quantity}</span></span>` : "") + `</div>`;
6415
7002
  }
6416
7003
  if (effectiveType === "booth") {
6417
- return `<div class="sl-chip-id">` + (d?.sectionLabel ? `<span class="fld sec"><span class="sl-chip-eb">Section</span><span class="val">${esc(d.sectionLabel)}</span></span>` : "") + `<span class="fld mid"><span class="sl-chip-eb">${esc(typeWord)}</span><span class="val">${esc(buyerName)}</span></span></div>`;
7004
+ return `<div class="sl-chip-id">` + (d?.sectionLabel ? `<span class="fld sec"><span class="sl-chip-eb">${this.tf("picker.section", "Section")}</span><span class="val">${esc(d.sectionLabel)}</span></span>` : "") + `<span class="fld mid"><span class="sl-chip-eb">${esc(typeWord)}</span><span class="val">${esc(buyerName)}</span></span></div>`;
6418
7005
  }
6419
7006
  if (!d?.sectionLabel && !d?.rowLabel && !d?.seatNumber) {
6420
- return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">Seat</span><span class="val">${esc(buyerName)}</span></span></div>`;
7007
+ return `<div class="sl-chip-id"><span class="fld sec"><span class="sl-chip-eb">${this.tf("picker.seat", "Seat")}</span><span class="val">${esc(buyerName)}</span></span></div>`;
6421
7008
  }
6422
- return `<div class="sl-chip-id">` + (d.sectionLabel ? `<span class="fld sec"><span class="sl-chip-eb">Section</span><span class="val">${esc(d.sectionLabel)}</span></span>` : "") + (d.rowLabel ? `<span class="fld mid"><span class="sl-chip-eb">${esc(typeWord)}</span><span class="val">${esc(this.rowShort(d))}</span></span>` : "") + (d.seatNumber ? `<span class="fld mid"><span class="sl-chip-eb">Seat</span><span class="val">${esc(d.seatNumber)}</span></span>` : "") + `</div>`;
7009
+ return `<div class="sl-chip-id">` + (d.sectionLabel ? `<span class="fld sec"><span class="sl-chip-eb">${this.tf("picker.section", "Section")}</span><span class="val">${esc(d.sectionLabel)}</span></span>` : "") + (d.rowLabel ? `<span class="fld mid"><span class="sl-chip-eb">${esc(typeWord)}</span><span class="val">${esc(this.rowShort(d))}</span></span>` : "") + (d.seatNumber ? `<span class="fld mid"><span class="sl-chip-eb">${this.tf("picker.seat", "Seat")}</span><span class="val">${esc(d.seatNumber)}</span></span>` : "") + `</div>`;
6423
7010
  };
6424
7011
  const iconRail = (rmAria, viewLabel) => `<div class="sl-chip-rail"><button type="button" class="rm" aria-label="${rmAria}"><svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>` + (viewLabel ? `<button type="button" class="view" data-view-label="${viewLabel}" aria-label="${(0, import_core2.t)("picker.viewFromSeat", { label: viewLabel })}"><svg viewBox="0 0 24 24"><path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></svg></button>` : "") + `</div>`;
6425
7012
  for (const item of heldItems) {
@@ -6431,7 +7018,7 @@ var SeatPicker = class _SeatPicker {
6431
7018
  const table = item.objectType === "table" ? this.controller.tableSelection(item.label) : null;
6432
7019
  const canView2 = this.seatViewEnabled() && !!heldSeat && item.objectType !== "table";
6433
7020
  parts.push(
6434
- `<div class="sl-chip sl-held${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-held="${encodeURIComponent(item.label)}"${heldSeat ? ` data-locate="${heldSeat.id}"` : ""}><div class="sl-chip-main">` + idGrid(heldSeat?.id ?? null, item.label, item.objectType, item.quantity ?? 1, item.objectId, table ?? void 0) + `<div class="sl-chip-sub"><span class="sl-ticket-state held" aria-label="Held for you" title="Held for you"><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></span><span class="cat">${cat?.label ?? item.categoryKey}${tierName ? ` \xB7 ${tierName}` : ""}</span>` + (table?.bookingMode === "variable" ? `<button type="button" class="sl-table-edit" data-table-edit="${encodeURIComponent(item.label)}">${item.quantity ?? 1} guests \xB7 Edit</button>` : "") + this.wheelchairChipMarker(heldSeat?.wheelchairSpaceType) + this.commercialChipMarker(heldSeat?.commercial) + `<span class="amt">${this.money(this.paidPrice(item.categoryKey, item.tierId, item.unitPrice) * (item.quantity ?? 1))}</span></div></div>` + iconRail(`Remove held ticket ${item.label}`, canView2 ? item.label : null) + `</div>`
7021
+ `<div class="sl-chip sl-held${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-held="${encodeURIComponent(item.label)}"${heldSeat ? ` data-locate="${heldSeat.id}"` : ""}><div class="sl-chip-main">` + idGrid(heldSeat?.id ?? null, item.label, item.objectType, item.quantity ?? 1, item.objectId, table ?? void 0) + `<div class="sl-chip-sub"><span class="sl-ticket-state held" aria-label="${this.tf("picker.heldForYou", "Held for you")}" title="${this.tf("picker.heldForYou", "Held for you")}"><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></span><span class="cat">${cat?.label ?? item.categoryKey}${tierName ? ` \xB7 ${tierName}` : ""}</span>` + (table?.bookingMode === "variable" ? `<button type="button" class="sl-table-edit" data-table-edit="${encodeURIComponent(item.label)}">${(0, import_core2.t)("picker.guestsCountEdit", { count: item.quantity ?? 1 })}</button>` : "") + this.wheelchairChipMarker(heldSeat?.wheelchairSpaceType) + this.commercialChipMarker(heldSeat?.commercial) + `<span class="amt">${this.money(this.paidPrice(item.categoryKey, item.tierId, item.unitPrice) * (item.quantity ?? 1))}</span></div></div>` + iconRail((0, import_core2.t)("picker.removeHeldTicketLabel", { label: item.label }), canView2 ? item.label : null) + `</div>`
6435
7022
  );
6436
7023
  }
6437
7024
  const heldLabels = new Set(heldItems.map((item) => item.label));
@@ -6441,14 +7028,21 @@ var SeatPicker = class _SeatPicker {
6441
7028
  nextTrayKeys.add(itemKey);
6442
7029
  const cat = this.controller.doc?.categories.find((c) => c.key === s.categoryKey);
6443
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") : "");
6444
7033
  parts.push(
6445
- `<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="Selected" title="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)}">${s.quantity ?? 1} guests \xB7 Edit</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(`Remove ${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>`
6446
7035
  );
6447
7036
  }
6448
7037
  for (const area of gaAreas) {
6449
7038
  const qty = this.gaQty.get(area.id) ?? 0;
6450
7039
  parts.push(
6451
- `<div class="sl-ga" data-ga="${area.id}"><div class="sl-ga-info"><div class="sl-ga-name">${area.displayLabel ?? area.label}</div><div class="sl-ga-sub">${area.displayType ?? "General admission"} \xB7 ${this.money(this.paidPrice(area.categoryKey, null, area.price))} \xB7 ${area.available} left</div></div><div class="sl-ga-qty"><button type="button" data-d="-1" aria-label="Fewer">\u2212</button><span>${qty}</span><button type="button" data-d="1" aria-label="More">+</button></div></div>`
7040
+ `<div class="sl-ga" data-ga="${area.id}"><div class="sl-ga-info"><div class="sl-ga-name">${area.displayLabel ?? area.label}</div><div class="sl-ga-sub">${area.displayType ?? this.tf("picker.generalAdmission", "General admission")} \xB7 ${this.money(this.paidPrice(area.categoryKey, null, area.price))} \xB7 ${(0, import_core2.tCount)("picker.leftCount", area.available)}</div></div><div class="sl-ga-qty"><button type="button" data-d="-1" aria-label="${this.tf("picker.fewer", "Fewer")}">\u2212</button><span>${qty}</span><button type="button" data-d="1" aria-label="${this.tf("picker.more", "More")}">+</button></div></div>`
7041
+ );
7042
+ }
7043
+ if (!this.salesClosed && !this.hold && !noPicks && !this.baReopen && !this.bestAvailableBusy && !this.bestAvailableConfirm) {
7044
+ parts.push(
7045
+ `<button type="button" class="sl-ba-reopen" data-ba-reopen><span aria-hidden="true">\u2726</span>${this.tf("picker.findTogetherInstead", "Find seats together instead")}</button>`
6452
7046
  );
6453
7047
  }
6454
7048
  this.els.tray.innerHTML = parts.join("");
@@ -6483,6 +7077,16 @@ var SeatPicker = class _SeatPicker {
6483
7077
  this.syncTray();
6484
7078
  this.els.tray.querySelector(".sl-ba-go")?.focus();
6485
7079
  });
7080
+ this.els.tray.querySelector("[data-ba-reopen]")?.addEventListener("click", () => {
7081
+ this.baReopen = true;
7082
+ this.syncTray();
7083
+ this.els.tray.querySelector(".sl-ba-go")?.focus();
7084
+ });
7085
+ this.els.tray.querySelector("[data-ba-close]")?.addEventListener("click", () => {
7086
+ this.baReopen = false;
7087
+ this.syncTray();
7088
+ this.els.tray.querySelector("[data-ba-reopen]")?.focus();
7089
+ });
6486
7090
  this.els.tray.querySelector("[data-ba-replace]")?.addEventListener("click", () => {
6487
7091
  this.bestAvailableConfirm = false;
6488
7092
  void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium, zoneId: this.baZone || void 0 });
@@ -6495,15 +7099,15 @@ var SeatPicker = class _SeatPicker {
6495
7099
  return;
6496
7100
  }
6497
7101
  const id = chip.dataset.seat;
6498
- const label = this.controller.getSelection().find((sel) => sel.id === id)?.label ?? "Seat";
7102
+ const label = this.controller.getSelection().find((sel) => sel.id === id)?.label ?? this.tf("picker.seat", "Seat");
6499
7103
  const remove = () => {
6500
7104
  this.controller.deselect([id]);
6501
- this.toast(`${label} removed.`, "neutral", {
6502
- label: "Undo",
7105
+ this.toast((0, import_core2.t)("picker.labelRemoved", { label }), "neutral", {
7106
+ label: this.tf("picker.undo", "Undo"),
6503
7107
  onClick: () => {
6504
7108
  const restored = this.controller.select([id]);
6505
7109
  this.toast(
6506
- restored.length ? `${label} restored.` : `${label} is no longer available.`,
7110
+ restored.length ? (0, import_core2.t)("picker.labelRestored", { label }) : (0, import_core2.t)("picker.labelNoLongerAvailable", { label }),
6507
7111
  restored.length ? "success" : "warning"
6508
7112
  );
6509
7113
  }
@@ -6575,31 +7179,31 @@ var SeatPicker = class _SeatPicker {
6575
7179
  const pendingCount = this.pendingSelectionCount();
6576
7180
  const previousCount = this.lastTrayCount;
6577
7181
  const previousTotal = this.lastTrayTotal;
6578
- this.els.count.textContent = count ? `${count} ${count === 1 ? "ticket" : "tickets"}` : "No seats selected";
7182
+ this.els.count.textContent = count ? (0, import_core2.tCount)("picker.ticketsCount", count) : this.tf("picker.noSeatsSelected", "No seats selected");
6579
7183
  this.els.total.textContent = count ? this.money(total) : "";
6580
7184
  this.root?.setAttribute("data-has-selection", String(count > 0));
6581
7185
  this.root?.setAttribute(
6582
7186
  "data-ba-active",
6583
- String(this.bestAvailableConfirm || this.bestAvailableBusy)
7187
+ String(this.bestAvailableConfirm || this.bestAvailableBusy || this.baReopen)
6584
7188
  );
6585
7189
  this.els.foot?.classList.toggle("empty", count === 0);
6586
7190
  if (this.els.seatSummary) {
6587
- this.els.seatSummary.textContent = count ? `${count} selected` : "";
7191
+ this.els.seatSummary.textContent = count ? (0, import_core2.tCount)("picker.seatsSelectedCount", count) : "";
6588
7192
  }
6589
7193
  this.syncCta(count, pendingCount);
6590
7194
  if (this.hold) {
6591
7195
  const securedCount = heldCount || this.hold.seats?.length || 0;
6592
7196
  if (this.els.holdTitle) {
6593
- this.els.holdTitle.textContent = `${securedCount} secured`;
7197
+ this.els.holdTitle.textContent = (0, import_core2.t)("picker.securedCount", { count: securedCount });
6594
7198
  }
6595
7199
  if (this.els.holdCopy) {
6596
7200
  this.holdCopyPending = pendingCount > 0;
6597
- this.els.holdCopy.textContent = pendingCount ? `${pendingCount} more selected` : this.holdReassuranceText();
7201
+ this.els.holdCopy.textContent = pendingCount ? (0, import_core2.t)("picker.moreSelectedCount", { count: pendingCount }) : this.holdReassuranceText();
6598
7202
  }
6599
7203
  const change = this.els.holdChange;
6600
7204
  if (change) {
6601
7205
  change.disabled = this.releasingHold;
6602
- change.textContent = this.releasingHold ? "Releasing\u2026" : "Change";
7206
+ change.textContent = this.releasingHold ? this.tf("picker.releasingEllipsis", "Releasing\u2026") : this.tf("picker.change", "Change");
6603
7207
  }
6604
7208
  }
6605
7209
  if (count !== previousCount) this.animateOnce(this.els.count, "sl-value-pop", 380);
@@ -6624,21 +7228,21 @@ var SeatPicker = class _SeatPicker {
6624
7228
  renderPeek(count, total, pendingCount) {
6625
7229
  if (!this.els.peek) return;
6626
7230
  if (count && this.ctaPhase === "holding") {
6627
- this.els.peek.innerHTML = `<span>Securing your seats\u2026</span>`;
7231
+ this.els.peek.innerHTML = `<span>${this.tf("picker.securingSeats", "Securing your seats\u2026")}</span>`;
6628
7232
  return;
6629
7233
  }
6630
7234
  if (count && this.ctaPhase === "checkout") {
6631
- this.els.peek.innerHTML = `<span>\u2713 ${count} ${count === 1 ? "ticket" : "tickets"} secured \xB7 ${this.money(total)} \u2014 you won\u2019t be charged yet</span>`;
7235
+ this.els.peek.innerHTML = `<span>${(0, import_core2.t)("picker.peekSecured", { count, total: this.money(total) })}</span>`;
6632
7236
  return;
6633
7237
  }
6634
7238
  if (count) {
6635
- const holding = !!this.hold && !pendingCount;
6636
- this.els.peek.innerHTML = `<span>${count} ${count === 1 ? "ticket" : "tickets"} \xB7 ${this.money(total)}</span><button type="button" class="go sl-sheet-go" data-act="${holding ? "checkout" : "open"}">${this.hold ? pendingCount ? "Secure more" : "Continue" : "Review"}</button>`;
7239
+ const toCheckout = !!this.hold;
7240
+ this.els.peek.innerHTML = `<span>${(0, import_core2.tCount)("picker.peekTicketsTotal", count, { total: this.money(total) })}</span><button type="button" class="go sl-sheet-go" data-act="${toCheckout ? "checkout" : "open"}">${this.hold ? pendingCount ? this.tf("picker.secureMore", "Secure more") : this.tf("picker.continue", "Continue") : this.tf("picker.review", "Review")}</button>`;
6637
7241
  } else if (this.salesClosed) {
6638
7242
  this.els.peek.innerHTML = `<span>${this.tf("picker.salesClosedPill", "Sales are closed")}</span>`;
6639
7243
  } else {
6640
7244
  const prices = (this.controller.doc?.categories ?? []).map((c) => this.catPrice(c)).filter((p) => p != null);
6641
- this.els.peek.innerHTML = (prices.length ? `<span>From ${this.money(Math.min(...prices))}</span>` : "<span>Pick your seats</span>") + `<button type="button" class="go sl-sheet-go" data-act="open">\u2726 Best seats</button>`;
7245
+ this.els.peek.innerHTML = (prices.length ? `<span>${(0, import_core2.t)("picker.peekFromPrice", { price: this.money(Math.min(...prices)) })}</span>` : `<span>${this.tf("picker.pickYourSeats", "Pick your seats")}</span>`) + `<button type="button" class="go sl-sheet-go" data-act="open">${this.tf("picker.bestSeatsStar", "\u2726 Best seats")}</button>`;
6642
7246
  }
6643
7247
  }
6644
7248
  async removeHeldLabel(label, chip) {
@@ -6651,7 +7255,7 @@ var SeatPicker = class _SeatPicker {
6651
7255
  const preserveAcrossNavigation = this.handedOff;
6652
7256
  const released = await this.controller.releaseLabels([label]);
6653
7257
  if (!released) {
6654
- this.toast(`Couldn't remove ${label}. Your hold is unchanged.`, "error");
7258
+ this.toast((0, import_core2.t)("picker.couldNotRemoveLabel", { label }), "error");
6655
7259
  return false;
6656
7260
  }
6657
7261
  const remaining = this.controller.currentHold();
@@ -6667,7 +7271,7 @@ var SeatPicker = class _SeatPicker {
6667
7271
  }
6668
7272
  this.syncTray();
6669
7273
  this.emitHoldChange();
6670
- this.toast(`${label} removed from your hold.`, "success");
7274
+ this.toast((0, import_core2.t)("picker.labelRemovedFromHold", { label }), "success");
6671
7275
  return true;
6672
7276
  } finally {
6673
7277
  this.releasingLabels.delete(label);
@@ -6681,23 +7285,23 @@ var SeatPicker = class _SeatPicker {
6681
7285
  const button = this.els.holdChange;
6682
7286
  if (button) {
6683
7287
  button.disabled = true;
6684
- button.textContent = "Releasing\u2026";
7288
+ button.textContent = this.tf("picker.releasingEllipsis", "Releasing\u2026");
6685
7289
  }
6686
7290
  try {
6687
7291
  await this.release();
6688
- if (!this.hold) this.toast("Held tickets released. Choose your new seats.", "success");
7292
+ if (!this.hold) this.toast(this.tf("picker.heldTicketsReleased", "Held tickets released. Choose your new seats."), "success");
6689
7293
  } finally {
6690
7294
  this.releasingHold = false;
6691
7295
  if (button?.isConnected) {
6692
7296
  button.disabled = false;
6693
- button.textContent = "Change";
7297
+ button.textContent = this.tf("picker.change", "Change");
6694
7298
  }
6695
7299
  }
6696
7300
  }
6697
7301
  async handleCta() {
6698
7302
  if (this.salesClosed) return;
6699
7303
  if (this.totalTicketCount() > this.maxTickets) {
6700
- this.toast(`Remove tickets until your order has ${this.maxTickets} or fewer.`, "warning");
7304
+ this.toast((0, import_core2.t)("picker.removeTicketsUntilOrFewer", { count: this.maxTickets }), "warning");
6701
7305
  return;
6702
7306
  }
6703
7307
  const committed = this.committedSelection();
@@ -6723,7 +7327,7 @@ var SeatPicker = class _SeatPicker {
6723
7327
  hold = h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : hold;
6724
7328
  }
6725
7329
  if (!hold) {
6726
- this.toast("One or more seats were just taken. Please pick again.", "error");
7330
+ this.toast(this.tf("picker.seatsJustTaken", "One or more seats were just taken. Please pick again."), "error");
6727
7331
  this.setCtaPhase("idle");
6728
7332
  this.syncTray();
6729
7333
  return;
@@ -6740,7 +7344,7 @@ var SeatPicker = class _SeatPicker {
6740
7344
  const problem = err;
6741
7345
  const labels = (problem.conflicts ?? []).map((conflict) => conflict.label).filter(Boolean).slice(0, 3);
6742
7346
  if (problem.reason === "event_closed") this.setSalesClosed(true);
6743
- const message = problem.reason === "event_closed" ? "Seat sales have closed for this event." : labels.length ? `${labels.join(", ")} ${labels.length === 1 ? "is" : "are"} no longer available. Choose another ${labels.length === 1 ? "seat" : "group"}.` : "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.");
6744
7348
  this.toast(message, "error");
6745
7349
  this.setCtaPhase("idle");
6746
7350
  } finally {
@@ -6754,14 +7358,14 @@ var SeatPicker = class _SeatPicker {
6754
7358
  const ms = Math.max(0, this.holdExpiresAt - Date.now());
6755
7359
  const m = Math.floor(ms / 6e4);
6756
7360
  const s = String(Math.floor(ms % 6e4 / 1e3)).padStart(2, "0");
6757
- return `Yours for ${m}:${s} \u2014 you won\u2019t be charged yet.`;
7361
+ return (0, import_core2.t)("picker.holdReassurance", { time: `${m}:${s}` });
6758
7362
  }
6759
7363
  startHoldTimer(expiresAt) {
6760
7364
  this.stopHoldTimer();
6761
7365
  this.holdExpiresAt = expiresAt;
6762
7366
  if (this.hold) this.rememberHold(this.hold);
6763
7367
  const pill = this.els.hold;
6764
- pill.innerHTML = '<span class="sl-hold-dot" aria-hidden="true"></span><span>Held</span><span class="sl-hold-time" data-ref="holdTime"></span>';
7368
+ pill.innerHTML = `<span class="sl-hold-dot" aria-hidden="true"></span><span>${this.tf("picker.held", "Held")}</span><span class="sl-hold-time" data-ref="holdTime"></span>`;
6765
7369
  const time = pill.querySelector('[data-ref="holdTime"]');
6766
7370
  this.els.holdNote?.classList.add("on");
6767
7371
  const tick = () => {
@@ -6794,7 +7398,7 @@ var SeatPicker = class _SeatPicker {
6794
7398
  const totalSecs = Math.ceil(ms / 1e3);
6795
7399
  const m = Math.floor(totalSecs / 60);
6796
7400
  const s = String(totalSecs % 60).padStart(2, "0");
6797
- this.els.extendTxt.innerHTML = `Your seats are held for <b>${m}:${s}</b>. Need more time?`;
7401
+ this.els.extendTxt.innerHTML = (0, import_core2.t)("picker.seatsHeldForNeedMoreTime", { time: `${m}:${s}` });
6798
7402
  this.extendEl.classList.add("on");
6799
7403
  } else {
6800
7404
  this.extendEl.classList.remove("on");
@@ -6804,7 +7408,7 @@ var SeatPicker = class _SeatPicker {
6804
7408
  const btn = this.els.extendBtn;
6805
7409
  btn.disabled = true;
6806
7410
  const prev = btn.textContent;
6807
- btn.textContent = "Adding\u2026";
7411
+ btn.textContent = this.tf("picker.addingEllipsis", "Adding\u2026");
6808
7412
  try {
6809
7413
  const h = await this.controller.extendHold(this.opts.holdTtlMs);
6810
7414
  if (h) {
@@ -6813,13 +7417,13 @@ var SeatPicker = class _SeatPicker {
6813
7417
  this.extendEl?.classList.remove("on");
6814
7418
  this.rememberHold(this.hold);
6815
7419
  this.emitHoldChange();
6816
- this.toast("More time added \u2014 your seats are still held.", "success");
7420
+ this.toast(this.tf("picker.moreTimeAdded", "More time added \u2014 your seats are still held."), "success");
6817
7421
  } else {
6818
- this.toast("Couldn't add more time \u2014 please head to checkout now.", "warning");
7422
+ this.toast(this.tf("picker.couldNotAddMoreTime", "Couldn't add more time \u2014 please head to checkout now."), "warning");
6819
7423
  }
6820
7424
  } catch (err) {
6821
7425
  this.opts.onError?.(err);
6822
- this.toast("Couldn't add more time \u2014 please head to checkout now.", "warning");
7426
+ this.toast(this.tf("picker.couldNotAddMoreTime", "Couldn't add more time \u2014 please head to checkout now."), "warning");
6823
7427
  } finally {
6824
7428
  btn.disabled = false;
6825
7429
  btn.textContent = prev;
@@ -6846,7 +7450,7 @@ var SeatPicker = class _SeatPicker {
6846
7450
  this.forgetHold();
6847
7451
  const n = handoff.lineItems.reduce((sum, i) => sum + i.quantity, 0);
6848
7452
  if (this.els.bookedSub) {
6849
- this.els.bookedSub.innerHTML = `<span class="sl-booked-seats">${n} ${n === 1 ? "ticket" : "tickets"}</span> confirmed. A confirmation is on its way.`;
7453
+ this.els.bookedSub.innerHTML = `<span class="sl-booked-seats">${(0, import_core2.tCount)("picker.ticketsCount", n)}</span> ${this.tf("picker.confirmedAndOnWay", "confirmed. A confirmation is on its way.")}`;
6850
7454
  }
6851
7455
  this.bookedEl?.classList.add("on");
6852
7456
  this.opts.onBooked?.(handoff);
@@ -6928,7 +7532,7 @@ var SeatPicker = class _SeatPicker {
6928
7532
  ({ mountCheckout: mountCheckout2 } = await loadHostedCheckout());
6929
7533
  } catch (err) {
6930
7534
  this.opts.onError?.(err);
6931
- this.toast("Checkout could not be opened. Your seats are still held \u2014 please try again.", "error");
7535
+ this.toast(this.tf("picker.checkoutCouldNotBeOpened", "Checkout could not be opened. Your seats are still held \u2014 please try again."), "error");
6932
7536
  this.setCtaPhase("idle");
6933
7537
  return;
6934
7538
  }
@@ -7043,9 +7647,9 @@ var SeatPicker = class _SeatPicker {
7043
7647
  rowTypeWord(details) {
7044
7648
  const authored = details?.displayType?.trim() || details?.rowType?.trim();
7045
7649
  if (authored) return authored;
7046
- if (details?.objectType === "table") return "Table";
7047
- if (details?.objectType === "booth") return "Booth";
7048
- return "Row";
7650
+ if (details?.objectType === "table") return this.tf("picker.table", "Table");
7651
+ if (details?.objectType === "booth") return this.tf("picker.booth", "Booth");
7652
+ return this.tf("picker.row", "Row");
7049
7653
  }
7050
7654
  rowShort(details) {
7051
7655
  const row = details?.rowLabel;
@@ -7068,14 +7672,15 @@ var SeatPicker = class _SeatPicker {
7068
7672
  const isGroupedTable = details.objectType === "table" && !!details.bookingMode;
7069
7673
  const isBooth = details.objectType === "booth";
7070
7674
  const hasLoc = details.sectionLabel || details.rowLabel || details.seatNumber;
7071
- 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">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">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">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">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">Seat</span><span class="sl-tip-val">${esc(details.displayLabel ?? details.label)}</span></div></div>`;
7072
- 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>`;
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>`;
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>`;
7073
7678
  const limited = this.limitedViewLabel(details.commercial);
7074
7679
  const cxLine = limited ? `<div class="sl-tip-cx"><span class="g" aria-hidden="true">\u25D0</span>${esc(limited)}</div>` : "";
7075
7680
  const wheelchair = this.wheelchairProvisionLabel(details.wheelchairSpaceType);
7076
7681
  const wheelchairLine = wheelchair ? `<div class="sl-tip-cx"><span class="g" aria-hidden="true">\u267F</span>${esc(wheelchair)}</div>` : "";
7077
7682
  this.tipEl.style.setProperty("--sl-cat", details.categoryColor);
7078
- 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;
7079
7684
  this.tipEl.style.display = "block";
7080
7685
  this.placeTooltip();
7081
7686
  }
@@ -7298,13 +7903,16 @@ var SeatPicker = class _SeatPicker {
7298
7903
  sourceLabel: this.tf("picker.chartDerivedModel", "Chart-derived model")
7299
7904
  };
7300
7905
  }
7301
- /** Route the module's decoupled analytics into the host callback, tagged buyer. */
7302
- emit3dAnalytics(event, props) {
7906
+ /** Route decoupled analytics into the host callback, tagged buyer. */
7907
+ emitAnalytics(event, props) {
7303
7908
  try {
7304
7909
  this.opts.onAnalytics?.(event, { ...props, surface: "buyer" });
7305
7910
  } catch {
7306
7911
  }
7307
7912
  }
7913
+ emit3dAnalytics(event, props) {
7914
+ this.emitAnalytics(event, props);
7915
+ }
7308
7916
  /** A 3D seat tap runs the SAME selection path as a 2D tap: toggle through the
7309
7917
  * controller, then raise the shared confirm card (bottom-sheeted in 3D). */
7310
7918
  onView3dSeatPick(seatId) {
@@ -7323,9 +7931,9 @@ var SeatPicker = class _SeatPicker {
7323
7931
  this.syncSelectionTo3d();
7324
7932
  return;
7325
7933
  }
7326
- const visualState = this.seatState3dFor(seat);
7327
- if (visualState !== "available") {
7328
- 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);
7329
7937
  this.syncSelectionTo3d();
7330
7938
  return;
7331
7939
  }
@@ -7347,31 +7955,28 @@ var SeatPicker = class _SeatPicker {
7347
7955
  else this.syncTray();
7348
7956
  this.syncSelectionTo3d();
7349
7957
  }
7350
- /** Explain a visible-but-unselectable 3D seat without entering the booking
7351
- * flow. Category colour remains visible in the venue; this card names the
7352
- * availability state explicitly so yellow never has to carry both meanings. */
7353
- 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) {
7354
7963
  const overlay = this.view3dEl;
7355
7964
  if (!overlay) return;
7356
7965
  this.dismissUnavailable3dSeat();
7357
- const status = this.controller.getStatus(seat.id);
7358
7966
  const details = this.controller.seatDetails(seat.id);
7359
7967
  const identity = details?.displayLabel ?? seat.displayLabel ?? seat.label;
7360
7968
  const section = details?.sectionLabel;
7361
7969
  const row = this.rowShort(details);
7362
- const location2 = [section, row ? `Row ${row}` : null, identity].filter(Boolean).join(" \xB7 ");
7970
+ const location2 = [section, row ? (0, import_core2.t)("picker.rowLabel", { label: row }) : null, identity].filter(Boolean).join(" \xB7 ");
7363
7971
  const copy = status === "held" ? {
7364
7972
  title: this.tf("picker.temporarilyHeld", "Temporarily held"),
7365
7973
  message: this.tf("picker.heldSeatExplanation", "Another buyer is holding this seat. It may become available again.")
7366
7974
  } : status === "booked" ? {
7367
7975
  title: this.tf("picker.sold", "Sold"),
7368
7976
  message: this.tf("picker.soldSeatExplanation", "This seat has already been booked.")
7369
- } : status === "not_for_sale" ? {
7977
+ } : {
7370
7978
  title: this.tf("picker.notForSale", "Not for sale"),
7371
7979
  message: this.tf("picker.notForSaleExplanation", "This seat is not included in the current sale.")
7372
- } : {
7373
- title: this.tf("picker.filteredSeat", "Unavailable with current filters"),
7374
- message: this.tf("picker.filteredSeatExplanation", "Change the active price or view filters to make this seat selectable.")
7375
7980
  };
7376
7981
  const card = document.createElement("div");
7377
7982
  card.className = "sl-view3d-unavailable";
@@ -7455,7 +8060,7 @@ var SeatPicker = class _SeatPicker {
7455
8060
  chip = document.createElement("div");
7456
8061
  chip.className = "sl-view3d-compare-saved";
7457
8062
  chip.setAttribute("role", "group");
7458
- chip.setAttribute("aria-label", "Saved seat comparison");
8063
+ chip.setAttribute("aria-label", this.tf("picker.savedSeatComparison", "Saved seat comparison"));
7459
8064
  const main2 = document.createElement("button");
7460
8065
  main2.type = "button";
7461
8066
  main2.className = "main";
@@ -7467,7 +8072,7 @@ var SeatPicker = class _SeatPicker {
7467
8072
  clear.type = "button";
7468
8073
  clear.className = "clear";
7469
8074
  clear.textContent = "\xD7";
7470
- clear.setAttribute("aria-label", "Clear saved seat comparison");
8075
+ clear.setAttribute("aria-label", this.tf("picker.clearSavedSeatComparison", "Clear saved seat comparison"));
7471
8076
  clear.addEventListener("click", () => this.clearView3dComparison());
7472
8077
  chip.append(main2, clear);
7473
8078
  overlay.appendChild(chip);
@@ -7476,8 +8081,8 @@ var SeatPicker = class _SeatPicker {
7476
8081
  const count = this.view3dCompareSeatIds.length;
7477
8082
  const main = chip.querySelector(".main");
7478
8083
  if (main) {
7479
- main.textContent = count > 1 ? `Compare ${count}` : "1 seat saved";
7480
- main.setAttribute("aria-label", count > 1 ? `Open comparison of ${count} seats` : "One seat saved; choose another to compare");
8084
+ main.textContent = count > 1 ? (0, import_core2.t)("picker.compareCount", { count }) : this.tf("picker.oneSeatSaved", "1 seat saved");
8085
+ main.setAttribute("aria-label", count > 1 ? (0, import_core2.t)("picker.openComparisonOfSeats", { count }) : this.tf("picker.oneSeatSavedChooseAnother", "One seat saved; choose another to compare"));
7481
8086
  }
7482
8087
  }
7483
8088
  view3dComparisonSnapshot(seatId) {
@@ -7749,13 +8354,12 @@ var SeatPicker = class _SeatPicker {
7749
8354
  const floors = handle.floors();
7750
8355
  const floorLabels = new Set(floors.map((floor) => floor.label.trim().toLocaleLowerCase()));
7751
8356
  const zones = handle.zones().filter((zone) => zone.seatCount > 0 && !floorLabels.has(zone.label.trim().toLocaleLowerCase()));
7752
- 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()));
7753
8358
  const sections = zones.length > 1 ? [] : allSections;
7754
8359
  const wantFloors = floors.length > 1;
7755
8360
  const wantZones = zones.length > 1;
7756
8361
  const wantSections = sections.length > 1;
7757
- const wantLocator = allSections.length > 0 && handle.rows().length > 0;
7758
- if (!wantFloors && !wantZones && !wantSections && !wantLocator) return;
8362
+ if (!wantFloors && !wantZones && !wantSections) return;
7759
8363
  const nav = document.createElement("div");
7760
8364
  nav.className = "sl-view3d-nav";
7761
8365
  const finderToggle = document.createElement("button");
@@ -7765,7 +8369,7 @@ var SeatPicker = class _SeatPicker {
7765
8369
  const setFinderOpen = (open) => {
7766
8370
  nav.classList.toggle("is-open", open);
7767
8371
  finderToggle.setAttribute("aria-expanded", String(open));
7768
- finderToggle.textContent = open ? "Close seat finder" : "Find a seat";
8372
+ finderToggle.textContent = open ? this.tf("picker.closeVenueNav", "Close") : wantFloors && (wantZones || wantSections) ? this.tf("picker.levelsAndAreas", "Levels & areas") : wantFloors ? this.tf("picker.levels", "Levels") : this.tf("picker.areas", "Areas");
7769
8373
  };
7770
8374
  finderToggle.addEventListener("click", () => setFinderOpen(!nav.classList.contains("is-open")));
7771
8375
  nav.addEventListener("keydown", (event) => {
@@ -7777,16 +8381,24 @@ var SeatPicker = class _SeatPicker {
7777
8381
  });
7778
8382
  setFinderOpen(false);
7779
8383
  nav.appendChild(finderToggle);
8384
+ let syncAreasForFloor = (_index) => void 0;
8385
+ let syncPressedFloor = (_index) => void 0;
7780
8386
  if (wantFloors) {
7781
8387
  const row = document.createElement("div");
7782
8388
  row.setAttribute("role", "group");
7783
8389
  row.setAttribute("aria-label", this.tf("picker.levels", "Levels"));
7784
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
+ };
7785
8398
  const select = (index) => {
7786
8399
  if (!handle.focusFloor(index)) return;
7787
- pills.forEach((p) => {
7788
- p.setAttribute("aria-pressed", String((p.dataset.floor === "" ? null : Number(p.dataset.floor)) === index));
7789
- });
8400
+ syncPressedFloor(index);
8401
+ syncAreasForFloor(index);
7790
8402
  };
7791
8403
  const add = (label, index) => {
7792
8404
  const b = document.createElement("button");
@@ -7802,7 +8414,7 @@ var SeatPicker = class _SeatPicker {
7802
8414
  row.appendChild(b);
7803
8415
  };
7804
8416
  add(this.tf("picker.allLevels", "All levels"), null);
7805
- for (const f of floors) add(f.label || `Level ${f.index + 1}`, f.index);
8417
+ for (const f of floors) add(f.label || (0, import_core2.t)("picker.levelNumber", { number: f.index + 1 }), f.index);
7806
8418
  nav.appendChild(row);
7807
8419
  }
7808
8420
  if (wantZones || wantSections) {
@@ -7811,119 +8423,55 @@ var SeatPicker = class _SeatPicker {
7811
8423
  row.setAttribute("aria-label", this.tf("picker.areas", "Areas"));
7812
8424
  const entries = wantZones ? zones.map((z) => ({ id: z.id, label: z.label || z.id, go: () => {
7813
8425
  handle.focusZone(z.id);
7814
- } })) : sections.map((s) => ({ id: s.id, label: s.label || s.id, go: () => {
7815
- handle.focusSection(s.id);
7816
- } }));
7817
- if (!wantZones && entries.length > MAX_3D_SECTION_PILLS) {
7818
- const select = document.createElement("select");
7819
- select.setAttribute("aria-label", this.tf("picker.jumpToSection", "Jump to section"));
7820
- const placeholder = document.createElement("option");
7821
- placeholder.value = "";
7822
- placeholder.textContent = this.tf("picker.jumpToSection", "Jump to section");
7823
- select.appendChild(placeholder);
7824
- for (const entry of entries) {
7825
- const option = document.createElement("option");
7826
- option.value = entry.id;
7827
- option.textContent = entry.label;
7828
- 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);
7829
8433
  }
7830
- select.addEventListener("change", () => {
7831
- entries.find((entry) => entry.id === select.value)?.go();
7832
- });
7833
- row.appendChild(select);
7834
- } else {
7835
- for (const e of entries) {
7836
- const b = document.createElement("button");
7837
- b.type = "button";
7838
- b.textContent = e.label;
7839
- b.addEventListener("click", () => {
7840
- e.go();
7841
- setFinderOpen(false);
7842
- });
7843
- row.appendChild(b);
7844
- }
7845
- }
7846
- nav.appendChild(row);
7847
- }
7848
- if (wantLocator) {
7849
- const locator = document.createElement("div");
7850
- locator.className = "sl-view3d-locator";
7851
- locator.setAttribute("role", "group");
7852
- locator.setAttribute("aria-label", "Find an exact seat in 3D");
7853
- const sectionSelect = document.createElement("select");
7854
- sectionSelect.setAttribute("aria-label", "Choose section in 3D");
7855
- const rowSelect = document.createElement("select");
7856
- rowSelect.setAttribute("aria-label", "Choose row in 3D");
7857
- const seatSelect = document.createElement("select");
7858
- seatSelect.setAttribute("aria-label", "Choose seat in 3D");
7859
- const view = document.createElement("button");
7860
- view.type = "button";
7861
- view.textContent = "Inspect seat";
7862
- view.disabled = true;
7863
- const fill = (select, placeholder, entries) => {
7864
- select.replaceChildren();
7865
- const first = document.createElement("option");
7866
- first.value = "";
7867
- first.textContent = placeholder;
7868
- select.appendChild(first);
7869
- for (const entry of entries) {
7870
- const option = document.createElement("option");
7871
- option.value = entry.id;
7872
- option.textContent = entry.label;
7873
- select.appendChild(option);
7874
- }
7875
- select.value = "";
7876
- };
7877
- fill(sectionSelect, "1. Section", allSections.map((section) => ({
7878
- id: section.id,
7879
- label: `${section.label} \xB7 ${section.seatCount.toLocaleString()} seats`
7880
- })));
7881
- fill(rowSelect, "2. Row", []);
7882
- fill(seatSelect, "3. Seat", []);
7883
- rowSelect.disabled = true;
7884
- seatSelect.disabled = true;
7885
- sectionSelect.addEventListener("change", () => {
7886
- const sectionId = sectionSelect.value;
7887
- view.disabled = true;
7888
- fill(seatSelect, "3. Seat", []);
7889
- seatSelect.disabled = true;
7890
- if (!sectionId || !handle.focusSection(sectionId)) {
7891
- fill(rowSelect, "2. Row", []);
7892
- rowSelect.disabled = true;
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();
7893
8440
  return;
7894
8441
  }
7895
- const rows = handle.rows(sectionId);
7896
- fill(rowSelect, "2. Row", rows.map((row) => ({
7897
- id: row.id,
7898
- label: `${row.label} \xB7 ${row.seatCount} seats`
7899
- })));
7900
- rowSelect.disabled = rows.length === 0;
7901
- });
7902
- rowSelect.addEventListener("change", () => {
7903
- const rowId = rowSelect.value;
7904
- view.disabled = true;
7905
- if (!rowId || !handle.focusRow(rowId)) {
7906
- fill(seatSelect, "3. Seat", []);
7907
- seatSelect.disabled = true;
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);
7908
8460
  return;
7909
8461
  }
7910
- const seats = handle.seatsInRow(rowId);
7911
- fill(seatSelect, "3. Seat", seats);
7912
- seatSelect.disabled = seats.length === 0;
7913
- });
7914
- seatSelect.addEventListener("change", () => {
7915
- const seatId = seatSelect.value;
7916
- view.disabled = !seatId;
7917
- });
7918
- view.addEventListener("click", () => {
7919
- const seatId = seatSelect.value;
7920
- if (seatId) {
7921
- setFinderOpen(false);
7922
- this.onView3dSeatPick(seatId);
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();
8468
+ setFinderOpen(false);
8469
+ });
8470
+ row.appendChild(button);
7923
8471
  }
7924
- });
7925
- locator.append(sectionSelect, rowSelect, seatSelect, view);
7926
- nav.appendChild(locator);
8472
+ };
8473
+ syncAreasForFloor = renderEntries;
8474
+ renderEntries(null);
7927
8475
  }
7928
8476
  overlay.appendChild(nav);
7929
8477
  }
@@ -7940,7 +8488,7 @@ var SeatPicker = class _SeatPicker {
7940
8488
  const overlay = document.createElement("div");
7941
8489
  overlay.className = "sl-view3d";
7942
8490
  overlay.setAttribute("role", "group");
7943
- overlay.setAttribute("aria-label", "Interactive 3D venue view");
8491
+ overlay.setAttribute("aria-label", this.tf("picker.interactive3dVenueView", "Interactive 3D venue view"));
7944
8492
  const back = document.createElement("button");
7945
8493
  back.type = "button";
7946
8494
  back.className = "sl-view3d-back";
@@ -7966,7 +8514,7 @@ var SeatPicker = class _SeatPicker {
7966
8514
  fullscreen.type = "button";
7967
8515
  fullscreen.className = "sl-view3d-fs";
7968
8516
  fullscreen.textContent = "\u26F6";
7969
- fullscreen.setAttribute("aria-label", "Full screen");
8517
+ fullscreen.setAttribute("aria-label", this.tf("picker.fullScreen", "Full screen"));
7970
8518
  fullscreen.setAttribute("aria-pressed", String(!!document.fullscreenElement || this.fsFallback || this.framedFs));
7971
8519
  fullscreen.addEventListener("click", () => this.toggleFullscreen());
7972
8520
  overlay.appendChild(fullscreen);
@@ -8003,7 +8551,7 @@ var SeatPicker = class _SeatPicker {
8003
8551
  onSeatInspect: (id) => this.onView3dSeatPick(id),
8004
8552
  onSectionFocusChange: (sectionId) => {
8005
8553
  const select = overlay.querySelector(
8006
- 'select[aria-label="Choose section in 3D"]'
8554
+ 'select[data-locator="section"]'
8007
8555
  );
8008
8556
  const next = sectionId ?? "";
8009
8557
  if (!select || select.value === next) return;
@@ -8100,7 +8648,7 @@ var SeatPicker = class _SeatPicker {
8100
8648
  if (button) {
8101
8649
  button.disabled = true;
8102
8650
  button.classList.add("sl-busy");
8103
- button.innerHTML = '<span class="sl-ba-spin" aria-hidden="true"></span>Finding\u2026';
8651
+ button.innerHTML = `<span class="sl-ba-spin" aria-hidden="true"></span>${this.tf("picker.findingEllipsis", "Finding\u2026")}`;
8104
8652
  }
8105
8653
  try {
8106
8654
  const h = await this.controller.bestAvailable(qty, categoryKey, { ...opts, ttlMs: this.opts.holdTtlMs });
@@ -8122,7 +8670,7 @@ var SeatPicker = class _SeatPicker {
8122
8670
  } catch (err) {
8123
8671
  this.opts.onError?.(err);
8124
8672
  const reason = err?.reason;
8125
- const message = reason === "not_enough_together" ? `We couldn't find ${qty} seats together. Try fewer seats or another ticket type.` : reason === "sold_out" ? "That ticket type is sold out. Try another ticket type." : reason === "event_closed" ? "Seat sales have closed for this event." : "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.");
8126
8674
  this.toast(message, "error");
8127
8675
  return null;
8128
8676
  } finally {
@@ -8151,7 +8699,7 @@ var SeatPicker = class _SeatPicker {
8151
8699
  }
8152
8700
  }
8153
8701
  if (!released) {
8154
- this.toast("Couldn't release your tickets. Your hold is unchanged.", "error");
8702
+ this.toast(this.tf("picker.couldNotReleaseTickets", "Couldn't release your tickets. Your hold is unchanged."), "error");
8155
8703
  return;
8156
8704
  }
8157
8705
  this.hold = null;