@seatlayer/js 0.50.2 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2248,6 +2248,10 @@ var DEFAULT_MAX_SELECTION2 = 10;
2248
2248
  var EXTEND_PROMPT_MS = 6e4;
2249
2249
  var MAX_3D_SEATS_DEFAULT = 6e4;
2250
2250
  var MAX_3D_SECTION_PILLS = 12;
2251
+ function tfStatic(key, fallback) {
2252
+ const v = t2(key);
2253
+ return v === key ? fallback : v;
2254
+ }
2251
2255
  function pointInPolygon(x, y, poly) {
2252
2256
  let inside = false;
2253
2257
  for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
@@ -2422,11 +2426,30 @@ var CSS = (
2422
2426
  .sl-picker[data-layout="narrow"] .sl-map{min-height:0;flex:1}
2423
2427
  .sl-picker[data-layout="narrow"] .sl-side{width:100%;border-left:0;border-top:1px solid var(--sl-line);
2424
2428
  flex:none;height:min(72%,480px);overflow:hidden;transition:height .3s cubic-bezier(.2,.8,.2,1);overscroll-behavior:contain}
2425
- .sl-picker[data-layout="narrow"][data-sheet="open"][data-has-selection="false"] .sl-side{height:min(252px,52%)}
2429
+ .sl-picker[data-layout="narrow"][data-sheet="open"][data-has-selection="false"] .sl-side{height:min(380px,64%)}
2430
+ /* With the price list expanded the sheet takes the with-selection height so
2431
+ the tray (cart + best-seats) is never crushed under the fixed-size list. */
2432
+ .sl-picker[data-layout="narrow"][data-sheet="open"][data-prices-open="true"] .sl-side{height:min(480px,74%)}
2426
2433
  .sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-side{height:76px;overflow:hidden}
2427
2434
  .sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-side > :not(.sl-sheet-head){display:none}
2428
- .sl-picker[data-layout="narrow"] .sl-tray{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}
2435
+ .sl-picker[data-layout="narrow"] .sl-tray{flex:1;min-height:96px;overflow-y:auto;overscroll-behavior:contain}
2436
+ .sl-picker[data-layout="narrow"][data-prices-open="true"] .sl-tray{min-height:48px}
2437
+ .sl-picker[data-layout="narrow"] .sl-prices{max-height:150px;overflow-y:auto;overscroll-behavior:contain}
2429
2438
  .sl-picker[data-layout="narrow"] .sl-foot{position:static;background:var(--sl-bg)}
2439
+ /* NARROW FOOT SAYS EVERYTHING ONCE (owner report: with a hold the footer ate
2440
+ ~190px and the ticket list got a sliver). The sheet head already carries
2441
+ "N tickets \xB7 $total" and the header pill already ticks the clock \u2014 so the
2442
+ total row hides, the secured note collapses to one compact line, and the
2443
+ badge tucks in. The tray gets the pixels; a thin scrollbar shows there is
2444
+ more to scroll. */
2445
+ .sl-picker[data-layout="narrow"] .sl-total{display:none}
2446
+ .sl-picker[data-layout="narrow"] .sl-foot{padding:8px 12px 8px}
2447
+ .sl-picker[data-layout="narrow"] .sl-hold-note{margin-bottom:6px;padding:5px 9px}
2448
+ .sl-picker[data-layout="narrow"] .sl-hold-note svg{display:none}
2449
+ .sl-picker[data-layout="narrow"] .sl-hold-note b{display:inline;margin-right:6px;font-size:11px}
2450
+ .sl-picker[data-layout="narrow"] .sl-hold-copy{display:inline;white-space:normal;font-size:10.5px}
2451
+ .sl-picker[data-layout="narrow"] .sl-powered{margin-top:4px;padding:2px 8px;font-size:11px}
2452
+ .sl-picker[data-layout="narrow"] .sl-tray{scrollbar-width:thin}
2430
2453
  .sl-picker[data-layout="narrow"] .sl-foot.empty{display:none}
2431
2454
  .sl-picker[data-layout="narrow"] .sl-sheet-head{order:0}
2432
2455
  .sl-picker[data-layout="narrow"] .sl-seats-sec{display:none}
@@ -2434,6 +2457,7 @@ var CSS = (
2434
2457
  .sl-picker[data-layout="narrow"] .sl-filtersec{order:3}
2435
2458
  .sl-picker[data-layout="narrow"] .sl-filters{order:4}
2436
2459
  .sl-picker[data-layout="narrow"] .sl-prices-sec{order:5}
2460
+ .sl-picker[data-layout="narrow"] .sl-prices-hint{order:6}
2437
2461
  .sl-picker[data-layout="narrow"] .sl-pricef{order:6}
2438
2462
  .sl-picker[data-layout="narrow"] .sl-prices{order:7}
2439
2463
  .sl-picker[data-layout="narrow"] .sl-foot{order:8}
@@ -2441,6 +2465,7 @@ var CSS = (
2441
2465
  .sl-picker[data-layout="narrow"] .sl-filtersec,
2442
2466
  .sl-picker[data-layout="narrow"] .sl-filters,
2443
2467
  .sl-picker[data-layout="narrow"] .sl-prices-sec,
2468
+ .sl-picker[data-layout="narrow"] .sl-prices-hint,
2444
2469
  .sl-picker[data-layout="narrow"] .sl-prices{display:none!important}
2445
2470
  .sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filtersec,
2446
2471
  .sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filters,
@@ -2458,7 +2483,13 @@ var CSS = (
2458
2483
  head is the tap/swipe toggle target (min 44px), so it reads as one control. */
2459
2484
  .sl-sheet-head{display:none;flex-direction:column;justify-content:center;padding:6px 12px 8px;min-height:56px;
2460
2485
  cursor:pointer;touch-action:none;user-select:none;-webkit-user-select:none;flex:none}
2461
- .sl-picker[data-layout="narrow"] .sl-sheet-head{display:flex;min-height:64px;padding:4px 10px 6px}
2486
+ .sl-picker[data-layout="narrow"] .sl-sheet-head{display:flex;min-height:52px;padding:3px 10px 5px}
2487
+ /* OPEN sheet: the head is context, not controls \u2014 the pill it carries at peek
2488
+ ("Best seats"/"Continue") duplicates the card and CTA right below it, and
2489
+ every saved pixel is tray room (owner call 2026-08-14). */
2490
+ .sl-picker[data-layout="narrow"][data-sheet="open"] .sl-sheet-head{min-height:42px}
2491
+ .sl-picker[data-layout="narrow"][data-sheet="open"] .sl-sheet-bar{min-height:32px}
2492
+ .sl-picker[data-layout="narrow"][data-sheet="open"] .sl-sheet-peek .go{display:none}
2462
2493
  .sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-sheet-head{height:100%}
2463
2494
  .sl-sheet-grab{width:36px;height:4px;border-radius:999px;background:var(--sl-muted);opacity:.55;margin:1px auto 5px}
2464
2495
  .sl-sheet-bar{display:flex;align-items:center;gap:8px;min-height:44px}
@@ -2499,9 +2530,19 @@ var CSS = (
2499
2530
  sheet brings it back, exactly like the filters row above. The tray hint comes
2500
2531
  back with it: the first-time buyer on a phone needs the instruction more than
2501
2532
  the desktop buyer, not less. */
2502
- .sl-picker[data-layout="narrow"][data-sheet="open"] .sl-prices-sec{display:flex!important}
2503
- .sl-picker[data-layout="narrow"][data-sheet="open"] .sl-prices{display:flex!important}
2533
+ .sl-picker[data-layout="narrow"][data-sheet="open"][data-prices-open="true"] .sl-prices-sec{display:flex!important}
2534
+ /* MOBILE SPACE BELONGS TO THE CART (owner call 2026-08-14): the price list is
2535
+ one tap away behind the "Ticket prices" row, not a permanent tenant. Zone
2536
+ flags and section taps already answer "what does it cost" in context. */
2537
+ .sl-picker[data-layout="narrow"][data-sheet="open"][data-prices-open="true"] .sl-prices-hint{display:block!important}
2538
+ .sl-picker[data-layout="narrow"][data-sheet="open"][data-prices-open="true"] .sl-prices{display:flex!important}
2504
2539
  .sl-picker[data-layout="narrow"][data-sheet="open"] .sl-tray-hint{display:block!important}
2540
+ /* The row reads as the toggle it is on narrow: chevron + pointer. */
2541
+ .sl-picker[data-layout="narrow"] .sl-prices-sec{cursor:pointer}
2542
+ .sl-picker[data-layout="narrow"] .sl-prices-sec::after{content:'';width:8px;height:8px;flex:none;margin-left:auto;
2543
+ border-right:2px solid var(--sl-muted);border-bottom:2px solid var(--sl-muted);
2544
+ transform:rotate(45deg);transition:transform .2s cubic-bezier(.2,.8,.2,1)}
2545
+ .sl-picker[data-layout="narrow"][data-prices-open="true"] .sl-prices-sec::after{transform:rotate(-135deg)}
2505
2546
  .sl-cbbtn{width:32px;height:32px;border-radius:999px;background:var(--sl-surface);border:1px solid var(--sl-line);
2506
2547
  color:var(--sl-text);display:flex;align-items:center;justify-content:center;transition:border-color .15s}
2507
2548
  .sl-cbbtn:hover{border-color:var(--sl-muted)}
@@ -2520,7 +2561,13 @@ var CSS = (
2520
2561
  .sl-sec{padding:14px 14px 4px;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
2521
2562
  .sl-prices-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
2522
2563
  .sl-price-select{min-height:32px;max-width:130px;padding:5px 28px 5px 9px;border:1px solid var(--sl-line);border-radius:9px;
2523
- background:var(--sl-surface);color:var(--sl-text);font:inherit;font-size:11px;font-weight:750;letter-spacing:0;text-transform:none}
2564
+ background-color:var(--sl-surface);color:var(--sl-text);font:inherit;font-size:11px;font-weight:750;letter-spacing:0;text-transform:none}
2565
+ /* One sentence that teaches the whole colour code \u2014 a first-time buyer sees
2566
+ "Head Tables \xB7 $165" and coloured dots with nothing connecting them. Gone
2567
+ the moment they have seats (they've learned it) and while sales are closed. */
2568
+ .sl-prices-hint{padding:0 14px 7px;font-size:11px;line-height:1.45;color:var(--sl-muted)}
2569
+ .sl-picker[data-has-selection="true"] .sl-prices-hint,
2570
+ .sl-picker[data-sales-closed="true"] .sl-prices-hint{display:none}
2524
2571
  .sl-prices{display:flex;flex-direction:column;padding:4px 14px 8px;border-bottom:1px solid var(--sl-line)}
2525
2572
  .sl-prices-sec,.sl-prices,.sl-seats-sec{flex:none}
2526
2573
  .sl-price-row{display:flex;align-items:center;gap:7px;min-height:28px;font-size:12px;
@@ -2680,11 +2727,11 @@ var CSS = (
2680
2727
  color:var(--sl-text);font-size:17px;font-weight:700;display:flex;align-items:center;justify-content:center;transition:border-color .15s}
2681
2728
  .sl-zoom button:hover{border-color:var(--sl-muted)}
2682
2729
  .sl-zoom svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
2683
- /* Full-screen promotion on phones: the capability existed (native + iOS overlay
2684
- + framed-host paths) but hid as the 4th unlabelled glyph in a corner stack.
2685
- On narrow layouts dockLayoutChrome moves the SAME button into the top-right
2686
- region beside Map|3D and this class dresses it as a labelled pill \u2014 the one
2687
- accelerator with the highest payoff on a small screen gets words. */
2730
+ /* Full-screen promotion: the capability existed (native + iOS overlay +
2731
+ framed-host paths) but hid as the 4th unlabelled glyph in a corner stack \u2014
2732
+ even the owner could not pick it out. dockLayoutChrome docks the SAME
2733
+ button into the top-right region beside Map|3D on EVERY layout and this
2734
+ class dresses it as a labelled pill. */
2688
2735
  .sl-zfs-lbl{display:none}
2689
2736
  .sl-fs-pill.sl-fs-pill{display:inline-flex;align-items:center;justify-content:center;width:auto;min-height:40px;height:auto;
2690
2737
  padding:0 13px;gap:6px;border-radius:999px;font-size:11px;font-weight:800;
@@ -2692,6 +2739,16 @@ var CSS = (
2692
2739
  .sl-fs-pill.sl-fs-pill:hover{border-color:var(--sl-muted)}
2693
2740
  .sl-fs-pill svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
2694
2741
  .sl-fs-pill .sl-zfs-lbl{display:inline;letter-spacing:.02em;white-space:nowrap}
2742
+ /* In the header, icons carry it (owner call): square Full screen glyph, and
2743
+ on narrow a price-tag toggle that opens the sheet with the list expanded. */
2744
+ .sl-head .sl-fs-pill{min-height:34px;min-width:34px;width:34px;padding:0;flex:none}
2745
+ .sl-head .sl-fs-pill .sl-zfs-lbl{display:none}
2746
+ .sl-prices-btn{display:none;align-items:center;justify-content:center;width:34px;min-height:34px;flex:none;
2747
+ border-radius:999px;background:var(--sl-surface);border:1px solid var(--sl-line);color:var(--sl-text);transition:border-color .15s}
2748
+ .sl-prices-btn:hover{border-color:var(--sl-muted)}
2749
+ .sl-prices-btn svg{width:15px;height:15px;stroke:currentColor;stroke-width:1.8;fill:none;stroke-linecap:round;stroke-linejoin:round}
2750
+ .sl-prices-btn[aria-pressed="true"]{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
2751
+ .sl-picker[data-layout="narrow"] .sl-prices-btn{display:inline-flex}
2695
2752
 
2696
2753
  /* toast + boot states (toast flows in the bottom-center region) */
2697
2754
  .sl-toast{transform:translateY(6px) scale(.98);max-width:100%;
@@ -2819,8 +2876,21 @@ var CSS = (
2819
2876
  .sl-confirm-add svg{width:16px;height:16px;stroke:currentColor;stroke-width:2.8;fill:none;stroke-linecap:round;stroke-linejoin:round}
2820
2877
  .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}
2821
2878
  .sl-confirm-cancel:hover{color:var(--sl-text)}
2822
- .sl-picker[data-layout="narrow"] .sl-confirm{left:50%!important;top:auto!important;bottom:14px;width:min(342px,calc(100% - 24px));
2879
+ .sl-picker[data-layout="narrow"] .sl-confirm{left:50%!important;top:auto!important;bottom:14px;width:min(310px,calc(100% - 24px));
2823
2880
  transform:translateX(-50%);animation:slConfirmMobileIn .24s cubic-bezier(.2,.8,.2,1) both}
2881
+ /* Phone-size confirm card (owner call 2026-08-14): same content \u2014 identity,
2882
+ price band, view photo, 3D link \u2014 on a tighter grid. The values stay large
2883
+ enough to double-check against the map; buttons keep tap-size. */
2884
+ .sl-picker[data-layout="narrow"] .sl-confirm-field{padding:9px 9px 8px}
2885
+ .sl-picker[data-layout="narrow"] .sl-confirm-value{font-size:15px}
2886
+ .sl-picker[data-layout="narrow"] .sl-confirm-cat{padding:8px 10px}
2887
+ .sl-picker[data-layout="narrow"] .sl-confirm-cat-name{font-size:12.5px}
2888
+ .sl-picker[data-layout="narrow"] .sl-confirm-price{font-size:15px}
2889
+ .sl-picker[data-layout="narrow"] .sl-confirm-body{padding:9px 10px 10px}
2890
+ .sl-picker[data-layout="narrow"] .sl-confirm-thumbwrap{height:62px;margin-bottom:6px}
2891
+ .sl-picker[data-layout="narrow"] .sl-confirm-3d{margin-top:7px;padding:6px}
2892
+ .sl-picker[data-layout="narrow"] .sl-confirm-row{margin-top:8px}
2893
+ .sl-picker[data-layout="narrow"] .sl-confirm-row button{min-height:42px}
2824
2894
 
2825
2895
  /* Atomic whole/variable-table chooser. Lives inside the widget so the same
2826
2896
  accessible dialog works in inline, modal, desktop and 390px mobile hosts. */
@@ -2896,8 +2966,11 @@ var CSS = (
2896
2966
  .sl-ba-premium.on{color:var(--sl-premium-ink);background:linear-gradient(135deg,#f0cf6b,#e0b23f);border-color:transparent;
2897
2967
  box-shadow:0 6px 16px color-mix(in srgb,var(--sl-premium) 26%,transparent)}
2898
2968
  .sl-ba-premium.on .star{color:#5a4410}
2899
- .sl-ba select{background:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:8px;
2900
- font:inherit;font-size:11px;padding:7px 8px;min-width:0;width:100%;max-width:none}
2969
+ .sl-ba select,.sl-price-select{appearance:none;-webkit-appearance:none;
2970
+ 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");
2971
+ background-repeat:no-repeat;background-position:right 10px center}
2972
+ .sl-ba select{background-color:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:8px;
2973
+ font:inherit;font-size:11px;padding:7px 26px 7px 8px;min-width:0;width:100%;max-width:none}
2901
2974
  .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)}
2902
2975
  .sl-ba-qty button{width:25px;height:25px;border-radius:7px;background:color-mix(in srgb,var(--sl-line) 35%,transparent);border:0;
2903
2976
  font-size:14px;font-weight:800;display:flex;align-items:center;justify-content:center}
@@ -2927,9 +3000,23 @@ var CSS = (
2927
3000
  .sl-picker[data-layout="narrow"] .sl-ba-copy{display:none}
2928
3001
  .sl-picker[data-layout="narrow"] .sl-ba-copy .wide{display:none}
2929
3002
  .sl-picker[data-layout="narrow"] .sl-ba-copy .narrow{display:inline}
2930
- .sl-picker[data-layout="narrow"] .sl-ba select{min-height:40px}
3003
+ .sl-picker[data-layout="narrow"] .sl-ba{grid-template-columns:auto minmax(0,1fr)}
3004
+ .sl-picker[data-layout="narrow"] .sl-ba select{grid-column:1/-1;min-height:38px}
2931
3005
  .sl-picker[data-layout="narrow"] .sl-ba-qty button{width:30px;height:30px}
2932
- .sl-picker[data-layout="narrow"] .sl-ba-go{min-height:40px}
3006
+ /* Quantity and Go share ONE row: a full-width row holding only "\u2212 2 +" was
3007
+ dead space, and the Find button still gets the lion's share. */
3008
+ .sl-picker[data-layout="narrow"] .sl-ba-qty{grid-column:1}
3009
+ .sl-picker[data-layout="narrow"] .sl-ba-go{grid-column:2;min-height:40px}
3010
+ /* Re-entry to best-available once the cart has seats (the card yields its
3011
+ space to chips, but the accelerator must stay reachable) + the quiet way
3012
+ back out of a reopened card. */
3013
+ .sl-ba-reopen{align-self:flex-start;display:inline-flex;align-items:center;gap:6px;min-height:32px;padding:5px 12px;margin-top:2px;
3014
+ border:1px dashed color-mix(in srgb,var(--sl-accent) 40%,var(--sl-line));border-radius:999px;
3015
+ color:var(--sl-accent);font-size:11.5px;font-weight:750;transition:border-color .15s,background .15s}
3016
+ .sl-ba-reopen:hover,.sl-ba-reopen:focus-visible{background:color-mix(in srgb,var(--sl-accent) 8%,transparent)}
3017
+ .sl-ba-back{grid-column:1/-1;justify-self:start;min-height:30px;padding:3px 0;color:var(--sl-muted);
3018
+ font-size:11px;font-weight:700;text-decoration:underline;text-underline-offset:3px;transition:color .15s}
3019
+ .sl-ba-back:hover,.sl-ba-back:focus-visible{color:var(--sl-text)}
2933
3020
 
2934
3021
  /* Sales-closed is a designed state, not a set of disables: one statement in
2935
3022
  the tray where the buying flow was, carrying the event's own date so the
@@ -2948,9 +3035,9 @@ var CSS = (
2948
3035
  (the cart chip's remove/view rail) instead get real height on narrow. */
2949
3036
  @media (pointer:coarse){
2950
3037
  .sl-zoom button,.sl-cbbtn,.sl-close,.sl-ga-qty button,.sl-ba-qty button,.sl-price-more,
2951
- .sl-hold-change,.sl-seccard-x,.sl-fs-pill,.sl-side-toggle{position:relative}
3038
+ .sl-hold-change,.sl-seccard-x,.sl-fs-pill,.sl-side-toggle,.sl-ba-reopen{position:relative}
2952
3039
  .sl-zoom button::after,.sl-cbbtn::after,.sl-close::after,.sl-ga-qty button::after,.sl-ba-qty button::after,
2953
- .sl-price-more::after,.sl-hold-change::after,.sl-seccard-x::after,.sl-fs-pill::after,.sl-side-toggle::after{
3040
+ .sl-price-more::after,.sl-hold-change::after,.sl-seccard-x::after,.sl-fs-pill::after,.sl-side-toggle::after,.sl-ba-reopen::after{
2954
3041
  content:'';position:absolute;top:50%;left:50%;width:max(100%,44px);height:max(100%,44px);
2955
3042
  transform:translate(-50%,-50%)}
2956
3043
  /* The chip rail stacks remove over view in ~53px \u2014 pseudo-targets would
@@ -3053,12 +3140,6 @@ var CSS = (
3053
3140
  border-radius:999px;font:700 11.5px/1 inherit;color:#eef1f8;background:rgba(12,18,32,.86);
3054
3141
  border:1px solid rgba(150,165,205,.45);backdrop-filter:blur(6px);cursor:pointer}
3055
3142
  .sl-view3d-nav select:focus-visible{outline:2px solid var(--sl-accent);outline-offset:2px}
3056
- .sl-view3d-nav .sl-view3d-locator{display:grid;grid-template-columns:minmax(150px,1.25fr) minmax(110px,.8fr) minmax(105px,.7fr) auto;
3057
- width:min(720px,100%);overflow:visible}
3058
- .sl-view3d-nav.is-seat-focused .sl-view3d-locator{display:none}
3059
- .sl-view3d-locator select{width:100%;min-width:0}
3060
- .sl-picker[data-layout="narrow"] .sl-view3d-nav .sl-view3d-locator{display:flex;width:100%;overflow-x:auto}
3061
- .sl-picker[data-layout="narrow"] .sl-view3d-locator select{flex:0 0 155px}
3062
3143
  /* On phones the library owns the bottom edge for seat/panorama/overview actions.
3063
3144
  Keep venue navigation in a separate top rail so those control families never
3064
3145
  stack over one another. */
@@ -3068,9 +3149,6 @@ var CSS = (
3068
3149
  .sl-picker[data-layout="narrow"] .sl-view3d-nav.is-open{left:12px;top:68px;max-width:calc(100% - 24px);padding:8px;
3069
3150
  border:1px solid rgba(150,165,205,.35);border-radius:14px;background:rgba(8,12,22,.9);backdrop-filter:blur(10px)}
3070
3151
  .sl-picker[data-layout="narrow"] .sl-view3d-nav.is-open>div{display:flex}
3071
- .sl-picker[data-layout="narrow"] .sl-view3d-nav.is-open .sl-view3d-locator{display:grid;grid-template-columns:1fr 1fr;width:100%;overflow:visible}
3072
- .sl-picker[data-layout="narrow"] .sl-view3d-nav.is-open .sl-view3d-locator select{width:100%;min-width:0;flex:auto}
3073
- .sl-picker[data-layout="narrow"] .sl-view3d-nav.is-open .sl-view3d-locator button{width:100%}
3074
3152
  /* Seat-eye is a decision state, not another venue-navigation state. Once the
3075
3153
  buyer arrives, clear the floor/area/locator rails and the module's duplicate
3076
3154
  Overview action. The picker-owned Back button becomes the one predictable
@@ -3496,6 +3574,15 @@ var SeatPicker = class _SeatPicker {
3496
3574
  this.baZone = "";
3497
3575
  /** "★ Best seats" premium quick-pick toggle — biases best-available to premium seats. */
3498
3576
  this.baPremium = false;
3577
+ /**
3578
+ * The buyer re-opened the best-seats form with seats already in the cart
3579
+ * ("I picked one, actually find us three together"). The card normally
3580
+ * yields this space to the ticket chips the moment anything is selected —
3581
+ * which removed the accelerator at exactly the moment a one-handed phone
3582
+ * buyer wants it. Cleared when the cart empties, a hold lands, or the
3583
+ * buyer backs out.
3584
+ */
3585
+ this.baReopen = false;
3499
3586
  this.bestAvailableConfirm = false;
3500
3587
  this.releasingHold = false;
3501
3588
  /** Event sales window is closed (read-only load state / live close). */
@@ -3573,6 +3660,7 @@ var SeatPicker = class _SeatPicker {
3573
3660
  /** Wide-layout ticket panel collapsed (map owns the full width). */
3574
3661
  this.sideCollapsed = false;
3575
3662
  this.sideToggleEl = null;
3663
+ this.pricesBtnEl = null;
3576
3664
  /** True once we've asked the host page to pin us fullscreen (framed, no native). */
3577
3665
  this.framedFs = false;
3578
3666
  /** Last height (px) posted to a host frame; dedupes redundant reports. */
@@ -3685,7 +3773,7 @@ var SeatPicker = class _SeatPicker {
3685
3773
  if (this.tableDialog?.physicalSeatIds.includes(seat.id)) this.dismissTableDialog();
3686
3774
  },
3687
3775
  onSelectionLimit: () => {
3688
- this.toast(`You can select up to ${this.maxTickets} tickets for this order.`, "warning");
3776
+ this.toast(t2("picker.maxTicketsForOrder", { count: this.maxTickets }), "warning");
3689
3777
  },
3690
3778
  onViewChange: () => {
3691
3779
  this.reanchorConfirm();
@@ -3801,8 +3889,8 @@ var SeatPicker = class _SeatPicker {
3801
3889
  return `<span class="sl-cx-mark" role="img" aria-label="${title}" title="${title}">\u25D0</span>`;
3802
3890
  }
3803
3891
  wheelchairProvisionLabel(type) {
3804
- if (type === "no-seat") return "Empty wheelchair space";
3805
- if (type === "seat-present") return "Accessible physical seat";
3892
+ if (type === "no-seat") return this.tf("picker.emptyWheelchairSpace", "Empty wheelchair space");
3893
+ if (type === "seat-present") return this.tf("picker.accessiblePhysicalSeat", "Accessible physical seat");
3806
3894
  return "";
3807
3895
  }
3808
3896
  wheelchairConfirmHtml(type) {
@@ -3883,9 +3971,9 @@ var SeatPicker = class _SeatPicker {
3883
3971
  const hideEventDetails = this.eventDetailsHidden && !active;
3884
3972
  this.root?.setAttribute("data-event-details-hidden", String(hideEventDetails));
3885
3973
  this.els.zfs?.setAttribute("aria-pressed", String(active));
3886
- this.els.zfs?.setAttribute("title", active ? "Exit full screen" : "Full screen");
3974
+ this.els.zfs?.setAttribute("title", active ? this.tf("picker.exitFullScreen", "Exit full screen") : this.tf("picker.fullScreen", "Full screen"));
3887
3975
  const zfsLabel = this.els.zfs?.querySelector(".sl-zfs-lbl");
3888
- if (zfsLabel) zfsLabel.textContent = active ? "Exit full screen" : "Full screen";
3976
+ if (zfsLabel) zfsLabel.textContent = active ? this.tf("picker.exitFullScreen", "Exit full screen") : this.tf("picker.fullScreen", "Full screen");
3889
3977
  this.view3dEl?.querySelector(".sl-view3d-fs")?.setAttribute("aria-pressed", String(active));
3890
3978
  }
3891
3979
  /**
@@ -3950,7 +4038,7 @@ var SeatPicker = class _SeatPicker {
3950
4038
  frame.className = "sl-modal-frame";
3951
4039
  frame.setAttribute("role", "dialog");
3952
4040
  frame.setAttribute("aria-modal", "true");
3953
- frame.setAttribute("aria-label", "Seat selection");
4041
+ frame.setAttribute("aria-label", tfStatic("picker.seatSelection", "Seat selection"));
3954
4042
  frame.tabIndex = -1;
3955
4043
  scrim.appendChild(frame);
3956
4044
  document.body.appendChild(scrim);
@@ -4110,7 +4198,7 @@ var SeatPicker = class _SeatPicker {
4110
4198
  <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>
4111
4199
  <span data-ref="closedPillText"></span>
4112
4200
  </span>
4113
- <button type="button" class="sl-close" data-ref="close" aria-label="Close">
4201
+ <button type="button" class="sl-close" data-ref="close" aria-label="${this.tf("picker.close", "Close")}">
4114
4202
  <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>
4115
4203
  </button>
4116
4204
  </div>
@@ -4118,17 +4206,17 @@ var SeatPicker = class _SeatPicker {
4118
4206
  <div class="sl-map">
4119
4207
  <div class="sl-map-host" data-ref="map"></div>
4120
4208
  <div class="sl-zoom" data-ref="zoom">
4121
- <button type="button" aria-label="Zoom in" title="Zoom in" data-ref="zin">+</button>
4122
- <button type="button" aria-label="Zoom out" title="Zoom out" data-ref="zout">\u2212</button>
4123
- <button type="button" aria-label="Fit to screen" title="Fit to screen" data-ref="zfit">
4209
+ <button type="button" aria-label="${this.tf("picker.zoomIn", "Zoom in")}" title="${this.tf("picker.zoomIn", "Zoom in")}" data-ref="zin">+</button>
4210
+ <button type="button" aria-label="${this.tf("picker.zoomOut", "Zoom out")}" title="${this.tf("picker.zoomOut", "Zoom out")}" data-ref="zout">\u2212</button>
4211
+ <button type="button" aria-label="${this.tf("picker.fitToScreen", "Fit to screen")}" title="${this.tf("picker.fitToScreen", "Fit to screen")}" data-ref="zfit">
4124
4212
  <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>
4125
4213
  </button>
4126
- <button type="button" aria-label="Full screen" title="Full screen" aria-pressed="false" data-ref="zfs">
4214
+ <button type="button" aria-label="${this.tf("picker.fullScreen", "Full screen")}" title="${this.tf("picker.fullScreen", "Full screen")}" aria-pressed="false" data-ref="zfs">
4127
4215
  <svg viewBox="0 0 24 24"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>
4128
- <span class="sl-zfs-lbl" aria-hidden="true">Full screen</span>
4216
+ <span class="sl-zfs-lbl" aria-hidden="true">${this.tf("picker.fullScreen", "Full screen")}</span>
4129
4217
  </button>
4130
4218
  </div>
4131
- <div class="sl-boot" data-ref="boot"><span class="sl-boot-spin"></span>Loading seat map\u2026</div>
4219
+ <div class="sl-boot" data-ref="boot"><span class="sl-boot-spin"></span>${this.tf("picker.loadingSeatMap", "Loading seat map\u2026")}</div>
4132
4220
  <div class="sl-toast" data-ref="toast" role="status" aria-live="polite"></div>
4133
4221
  </div>
4134
4222
  <div class="sl-side" data-ref="side">
@@ -4136,24 +4224,25 @@ var SeatPicker = class _SeatPicker {
4136
4224
  <div class="sl-sheet-grab"></div>
4137
4225
  <div class="sl-sheet-bar">
4138
4226
  <div class="sl-sheet-peek" data-ref="peek"></div>
4139
- <button type="button" class="sl-sheet-toggle" data-ref="sheetToggle" aria-label="Open ticket panel" aria-expanded="false">
4227
+ <button type="button" class="sl-sheet-toggle" data-ref="sheetToggle" aria-label="${this.tf("picker.openTicketPanel", "Open ticket panel")}" aria-expanded="false">
4140
4228
  <svg viewBox="0 0 24 24"><path d="M6 15l6-6 6 6"/></svg>
4141
4229
  </button>
4142
4230
  </div>
4143
4231
  </div>
4144
- <div class="sl-sec sl-filtersec" data-ref="filtersSec">Filters</div>
4232
+ <div class="sl-sec sl-filtersec" data-ref="filtersSec">${this.tf("picker.filters", "Filters")}</div>
4145
4233
  <div class="sl-filters" data-ref="filters"></div>
4146
4234
  <div class="sl-offer" data-ref="offer" role="status" aria-live="polite"></div>
4147
- <div class="sl-sec sl-prices-sec" data-ref="pricesSec"><span>Ticket prices</span></div>
4235
+ <div class="sl-sec sl-prices-sec" data-ref="pricesSec"><span>${this.tf("picker.ticketPrices", "Ticket prices")}</span></div>
4236
+ <div class="sl-prices-hint" data-ref="pricesHint"></div>
4148
4237
  <div class="sl-prices" data-ref="prices"></div>
4149
- <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>
4150
- <div class="sl-sec sl-seats-sec"><span>Your seats</span><span class="sl-seat-summary" data-ref="seatSummary"></span></div>
4238
+ <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>
4239
+ <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>
4151
4240
  <div class="sl-tray" data-ref="tray"></div>
4152
4241
  <div class="sl-foot" data-ref="foot">
4153
4242
  <div class="sl-hold-note" data-ref="holdNote" role="status" aria-live="polite">
4154
4243
  <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg>
4155
- <span><b data-ref="holdTitle">Seats secured</b><span class="sl-hold-copy" data-ref="holdCopy">You won\u2019t be charged yet.</span></span>
4156
- <button type="button" class="sl-hold-change" data-ref="holdChange" aria-label="Release held tickets and choose different seats">Change</button>
4244
+ <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>
4245
+ <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>
4157
4246
  </div>
4158
4247
  <div class="sl-total"><span data-ref="count"></span><b data-ref="total"></b></div>
4159
4248
  <button type="button" class="sl-cta" data-ref="cta" disabled></button>
@@ -4204,7 +4293,7 @@ var SeatPicker = class _SeatPicker {
4204
4293
  const setSheet = (open) => {
4205
4294
  root.dataset.sheet = open ? "open" : "peek";
4206
4295
  toggle?.setAttribute("aria-expanded", String(open));
4207
- toggle?.setAttribute("aria-label", open ? "Collapse ticket panel" : "Open ticket panel");
4296
+ toggle?.setAttribute("aria-label", open ? this.tf("picker.collapseTicketPanel", "Collapse ticket panel") : this.tf("picker.openTicketPanel", "Open ticket panel"));
4208
4297
  };
4209
4298
  setSheet(root.dataset.sheet === "open");
4210
4299
  toggle?.addEventListener("click", (e) => {
@@ -4250,6 +4339,39 @@ var SeatPicker = class _SeatPicker {
4250
4339
  head.releasePointerCapture?.(e.pointerId);
4251
4340
  });
4252
4341
  }
4342
+ const togglePrices = () => {
4343
+ if (this.root?.dataset.layout !== "narrow") return;
4344
+ const open = this.root.getAttribute("data-prices-open") === "true";
4345
+ this.root.setAttribute("data-prices-open", String(!open));
4346
+ this.els.pricesSec?.setAttribute("aria-expanded", String(!open));
4347
+ this.pricesBtnEl?.setAttribute("aria-pressed", String(!open));
4348
+ if (!open && this.root.dataset.sheet !== "open") {
4349
+ this.root.dataset.sheet = "open";
4350
+ const sheetToggle = this.els.sheetToggle;
4351
+ sheetToggle?.setAttribute("aria-expanded", "true");
4352
+ sheetToggle?.setAttribute("aria-label", "Collapse ticket panel");
4353
+ }
4354
+ };
4355
+ this.els.pricesSec?.addEventListener("click", (e) => {
4356
+ if (e.target.closest("select,button")) return;
4357
+ togglePrices();
4358
+ });
4359
+ this.els.pricesSec?.addEventListener("keydown", (e) => {
4360
+ if (e.key !== "Enter" && e.key !== " ") return;
4361
+ if (e.target.closest("select,button")) return;
4362
+ e.preventDefault();
4363
+ togglePrices();
4364
+ });
4365
+ const pricesBtn = document.createElement("button");
4366
+ pricesBtn.type = "button";
4367
+ pricesBtn.className = "sl-prices-btn";
4368
+ pricesBtn.setAttribute("aria-label", this.tf("picker.ticketPrices", "Ticket prices"));
4369
+ pricesBtn.setAttribute("title", this.tf("picker.ticketPrices", "Ticket prices"));
4370
+ pricesBtn.setAttribute("aria-pressed", "false");
4371
+ 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>';
4372
+ pricesBtn.addEventListener("click", () => togglePrices());
4373
+ this.els.headInfo?.parentElement?.insertBefore(pricesBtn, this.els.hold ?? null);
4374
+ this.pricesBtnEl = pricesBtn;
4253
4375
  this.tipEl = document.createElement("div");
4254
4376
  this.tipEl.setAttribute("role", "tooltip");
4255
4377
  this.tipEl.className = "sl-tip";
@@ -4267,7 +4389,7 @@ var SeatPicker = class _SeatPicker {
4267
4389
  const info = await this.controller.render(canvasHost);
4268
4390
  if (this.destroyed) return this;
4269
4391
  if (!info) {
4270
- 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>';
4392
+ 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>`;
4271
4393
  this.els.boot.querySelector("button").addEventListener("click", () => {
4272
4394
  const container = this.opts.container;
4273
4395
  const opts = this.opts;
@@ -4302,6 +4424,12 @@ var SeatPicker = class _SeatPicker {
4302
4424
  const when = info.startsAt ? formatWhen(info.startsAt, info.timezone ?? null, this.opts.locale) : "";
4303
4425
  this.eventWhenText = when || null;
4304
4426
  this.els.meta.textContent = [info.venue, when].filter(Boolean).join(" \xB7 ");
4427
+ if (this.els.pricesHint) {
4428
+ this.els.pricesHint.textContent = this.tf(
4429
+ "picker.priceHint",
4430
+ "Colours on the map are ticket types \u2014 tap one below to show just those seats."
4431
+ );
4432
+ }
4305
4433
  this.buildBadge(chartTheme);
4306
4434
  const present = /* @__PURE__ */ new Set();
4307
4435
  let hasLimitedView = false;
@@ -4328,7 +4456,7 @@ var SeatPicker = class _SeatPicker {
4328
4456
  const mk = (key, label) => `<button type="button" class="sl-chip-f${key === "all" ? " on" : ""}" data-a11y="1" data-f="${key}">${label}</button>`;
4329
4457
  chips.insertAdjacentHTML(
4330
4458
  "beforeend",
4331
- mk("all", "All seats") + ACCESSIBILITY_TYPES.filter(({ key }) => present.has(key)).map(({ key, short, icon }) => mk(key, `${icon} ${short}`)).join("")
4459
+ mk("all", this.tf("picker.allSeats", "All seats")) + ACCESSIBILITY_TYPES.filter(({ key }) => present.has(key)).map(({ key, short, icon }) => mk(key, `${icon} ${short}`)).join("")
4332
4460
  );
4333
4461
  const active = /* @__PURE__ */ new Set();
4334
4462
  const syncChips = () => {
@@ -4374,7 +4502,7 @@ var SeatPicker = class _SeatPicker {
4374
4502
  cb.type = "button";
4375
4503
  cb.className = "sl-cbbtn";
4376
4504
  this.cbEl = cb;
4377
- cb.setAttribute("aria-label", "Toggle colorblind-friendly colors");
4505
+ cb.setAttribute("aria-label", this.tf("picker.toggleColorblindColors", "Toggle colorblind-friendly colors"));
4378
4506
  cb.setAttribute("aria-pressed", String(this.cbSafe));
4379
4507
  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>';
4380
4508
  this.els.zfit.parentElement.appendChild(cb);
@@ -4460,8 +4588,8 @@ var SeatPicker = class _SeatPicker {
4460
4588
  if (!btn) return;
4461
4589
  const collapsed = this.sideCollapsed;
4462
4590
  btn.setAttribute("aria-expanded", String(!collapsed));
4463
- btn.setAttribute("aria-label", collapsed ? "Show the ticket panel" : "Hide the ticket panel");
4464
- 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>';
4591
+ btn.setAttribute("aria-label", collapsed ? this.tf("picker.showTicketPanel", "Show the ticket panel") : this.tf("picker.hideTicketPanel", "Hide the ticket panel"));
4592
+ btn.innerHTML = collapsed ? `<svg viewBox="0 0 24 24"><path d="M15 6l-6 6 6 6"/></svg><span>${this.tf("picker.tickets", "Tickets")}</span>` : `<svg viewBox="0 0 24 24"><path d="M9 6l6 6-6 6"/></svg><span>${this.tf("picker.hidePanel", "Hide panel")}</span>`;
4465
4593
  }
4466
4594
  /**
4467
4595
  * Collapse or reopen the wide-layout ticket panel in place (host-drivable —
@@ -4487,13 +4615,24 @@ var SeatPicker = class _SeatPicker {
4487
4615
  const narrow = this.root?.dataset.layout === "narrow";
4488
4616
  this.applyPanelCollapsed();
4489
4617
  const zfs = this.els.zfs;
4490
- if (zfs) {
4491
- if (narrow && this.regions["top-right"]) {
4492
- zfs.classList.add("sl-fs-pill");
4493
- this.regions["top-right"].appendChild(zfs);
4494
- } else if (!narrow && this.els.zoom && zfs.parentElement !== this.els.zoom) {
4495
- zfs.classList.remove("sl-fs-pill");
4496
- this.els.zoom.appendChild(zfs);
4618
+ const head = this.els.headInfo?.parentElement;
4619
+ if (zfs && head && zfs.parentElement !== head) {
4620
+ zfs.classList.add("sl-fs-pill");
4621
+ head.insertBefore(zfs, this.els.hold ?? null);
4622
+ }
4623
+ const pricesSec = this.els.pricesSec;
4624
+ if (pricesSec) {
4625
+ if (narrow) {
4626
+ pricesSec.setAttribute("role", "button");
4627
+ pricesSec.tabIndex = 0;
4628
+ pricesSec.setAttribute(
4629
+ "aria-expanded",
4630
+ String(this.root?.getAttribute("data-prices-open") === "true")
4631
+ );
4632
+ } else {
4633
+ pricesSec.removeAttribute("role");
4634
+ pricesSec.removeAttribute("aria-expanded");
4635
+ pricesSec.tabIndex = -1;
4497
4636
  }
4498
4637
  }
4499
4638
  const filters = this.els.filters;
@@ -4520,7 +4659,7 @@ var SeatPicker = class _SeatPicker {
4520
4659
  this.extendEl = el;
4521
4660
  this.els.extendTxt = el.querySelector('[data-ref="extendTxt"]');
4522
4661
  this.els.extendBtn = el.querySelector('[data-ref="extendBtn"]');
4523
- this.els.extendBtn.textContent = "Add time";
4662
+ this.els.extendBtn.textContent = this.tf("picker.addTime", "Add time");
4524
4663
  this.els.extendBtn.addEventListener("click", () => void this.handleExtend());
4525
4664
  }
4526
4665
  /** Success overlay + onBooked fire when the held seats settle to booked. */
@@ -4529,7 +4668,7 @@ var SeatPicker = class _SeatPicker {
4529
4668
  el.className = "sl-booked";
4530
4669
  el.setAttribute("role", "status");
4531
4670
  el.setAttribute("aria-live", "polite");
4532
- el.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>`;
4671
+ el.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>`;
4533
4672
  this.root.appendChild(el);
4534
4673
  this.bookedEl = el;
4535
4674
  this.els.bookedSub = el.querySelector('[data-ref="bookedSub"]');
@@ -4540,8 +4679,7 @@ var SeatPicker = class _SeatPicker {
4540
4679
  * host `messages` override (which makes `t()` return the override, not the key).
4541
4680
  */
4542
4681
  tf(key, fallback) {
4543
- const v = t2(key);
4544
- return v === key ? fallback : v;
4682
+ return tfStatic(key, fallback);
4545
4683
  }
4546
4684
  /** Sold-out overlay — an informational state with no unavailable action. */
4547
4685
  buildSoldoutOverlay() {
@@ -4726,7 +4864,7 @@ var SeatPicker = class _SeatPicker {
4726
4864
  }
4727
4865
  canAddTicket() {
4728
4866
  if (this.totalTicketCount() < this.maxTickets) return true;
4729
- this.toast(`You can select up to ${this.maxTickets} tickets for this order.`, "warning");
4867
+ this.toast(t2("picker.maxTicketsForOrder", { count: this.maxTickets }), "warning");
4730
4868
  return false;
4731
4869
  }
4732
4870
  pendingGATotal(gaAreas) {
@@ -4749,21 +4887,21 @@ var SeatPicker = class _SeatPicker {
4749
4887
  }
4750
4888
  if (this.confirmSeat || this.tableDialog && !this.tableDialogHeld) {
4751
4889
  cta.disabled = true;
4752
- cta.textContent = this.tableDialog ? "Confirm your table" : "Confirm or cancel this seat";
4890
+ cta.textContent = this.tableDialog ? this.tf("picker.confirmYourTable", "Confirm your table") : this.tf("picker.confirmOrCancelSeat", "Confirm or cancel this seat");
4753
4891
  return;
4754
4892
  }
4755
4893
  if (this.ctaPhase === "holding") {
4756
4894
  cta.disabled = true;
4757
- cta.innerHTML = '<span class="sl-cta-spin" aria-hidden="true"></span>Securing seats\u2026';
4895
+ cta.innerHTML = `<span class="sl-cta-spin" aria-hidden="true"></span>${this.tf("picker.securingSeats", "Securing your seats\u2026")}`;
4758
4896
  return;
4759
4897
  }
4760
4898
  if (this.ctaPhase === "checkout") {
4761
4899
  cta.disabled = true;
4762
- cta.innerHTML = '<span class="sl-cta-spin" aria-hidden="true"></span>Opening secure checkout\u2026';
4900
+ cta.innerHTML = `<span class="sl-cta-spin" aria-hidden="true"></span>${this.tf("picker.openingCheckout", "Opening secure checkout\u2026")}`;
4763
4901
  return;
4764
4902
  }
4765
4903
  cta.disabled = count === 0;
4766
- cta.textContent = this.hold ? pending ? `Secure ${pending} more & checkout` : "Continue to checkout" : count ? "Hold seats & checkout" : "Select seats";
4904
+ cta.textContent = this.hold ? pending ? t2("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");
4767
4905
  }
4768
4906
  setCtaPhase(phase) {
4769
4907
  this.ctaPhase = phase;
@@ -4824,7 +4962,7 @@ var SeatPicker = class _SeatPicker {
4824
4962
  this.syncTray();
4825
4963
  this.emitHoldChange();
4826
4964
  this.opts.onHoldRestored?.(restored, restored.seats ?? [], this.buildHandoff(restored));
4827
- if (automatic) this.toast("Your held tickets have been restored.", "success");
4965
+ if (automatic) this.toast(this.tf("picker.heldTicketsRestored", "Your held tickets have been restored."), "success");
4828
4966
  return restored;
4829
4967
  } catch (error) {
4830
4968
  const status = error?.status;
@@ -5042,8 +5180,8 @@ var SeatPicker = class _SeatPicker {
5042
5180
  if (bands.length < 2) return;
5043
5181
  const select = document.createElement("select");
5044
5182
  select.className = "sl-price-select";
5045
- select.setAttribute("aria-label", "Filter and focus seats by price");
5046
- select.innerHTML = `<option value="all">All prices</option>` + bands.map((band) => `<option value="${band.id}">${band.label}</option>`).join("");
5183
+ select.setAttribute("aria-label", this.tf("picker.filterAndFocusByPrice", "Filter and focus seats by price"));
5184
+ select.innerHTML = `<option value="all">${this.tf("picker.allPrices", "All prices")}</option>` + bands.map((band) => `<option value="${band.id}">${band.label}</option>`).join("");
5047
5185
  this.els.pricesSec.appendChild(select);
5048
5186
  select.addEventListener("change", () => {
5049
5187
  const band = bands.find((candidate) => candidate.id === select.value);
@@ -5070,8 +5208,8 @@ var SeatPicker = class _SeatPicker {
5070
5208
  const projection = document.createElement("div");
5071
5209
  projection.className = "sl-projection";
5072
5210
  projection.setAttribute("role", "group");
5073
- projection.setAttribute("aria-label", "Venue view");
5074
- 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>';
5211
+ projection.setAttribute("aria-label", this.tf("picker.venueView", "Venue view"));
5212
+ 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>`;
5075
5213
  projection.querySelectorAll("button").forEach((button) => {
5076
5214
  button.addEventListener("click", () => {
5077
5215
  this.setBuyerView(button.dataset.view);
@@ -5194,6 +5332,10 @@ var SeatPicker = class _SeatPicker {
5194
5332
  renderSectionCard(summary) {
5195
5333
  if (!this.els.map) return;
5196
5334
  this.secCardEl?.remove();
5335
+ if (this.root?.dataset.layout === "narrow" && (this.hold || this.committedSelection().length || this.pendingGACount())) {
5336
+ this.lastSection = null;
5337
+ return;
5338
+ }
5197
5339
  const paid = summary.categories.length ? summary.categories.map((c) => this.paidPrice(c.key, null, c.price)) : [summary.priceMin, summary.priceMax];
5198
5340
  const paidMin = Math.min(...paid);
5199
5341
  const paidMax = Math.max(...paid);
@@ -5294,13 +5436,13 @@ var SeatPicker = class _SeatPicker {
5294
5436
  }
5295
5437
  const cat = this.controller.doc?.categories.find((c) => c.key === seat.categoryKey);
5296
5438
  const status = this.controller.getStatus(seat.id) ?? "free";
5297
- const statusText = status === "free" ? "available" : status === "held" ? "on hold" : "taken";
5439
+ const statusText = status === "free" ? this.tf("picker.statusAvailable", "available") : status === "held" ? this.tf("picker.statusOnHold", "on hold") : this.tf("picker.statusTaken2", "taken");
5298
5440
  const price = cat ? this.catPrice(cat) : void 0;
5299
5441
  const table = this.controller.tableSelection(seat.id);
5300
5442
  const details = table ?? this.controller.seatDetails(seat.id);
5301
5443
  const typeWord = this.rowTypeWord(details);
5302
5444
  const buyerName = details?.rowLabel ?? details?.displayLabel ?? seat.displayLabel ?? seat.label;
5303
- 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}`;
5445
+ const identity = table ? `${typeWord} ${buyerName}, ${table.bookingMode === "variable" ? t2("picker.minToMaxGuests", { min: table.minOccupancy, max: table.maxOccupancy }) : t2("picker.capacityGuests", { count: table.capacity })}` : details?.objectType === "booth" ? `${typeWord} ${buyerName}` : details?.objectType === "table" ? `${typeWord} ${buyerName}, ${t2("picker.seatNumberLower", { label: details.seatNumber ?? seat.label })}` : t2("picker.seatLabel", { label: details?.displayLabel ?? seat.displayLabel ?? seat.label });
5304
5446
  this.srEl.textContent = `${identity}, ${cat?.label ?? seat.categoryKey}${price != null ? `, ${this.money(price)}` : ""}, ${statusText}`;
5305
5447
  }
5306
5448
  // ---- atomic table confirmation / guest quantity ---------------------------
@@ -5321,7 +5463,7 @@ var SeatPicker = class _SeatPicker {
5321
5463
  const typeWord = this.rowTypeWord(table);
5322
5464
  const el = document.createElement("div");
5323
5465
  el.className = "sl-table-scrim";
5324
- el.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>`;
5466
+ el.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 ? t2("picker.flexiblePartyTypeWord", { typeWord }) : t2("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.") : t2("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>${t2("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>${t2("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">${t2("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">${t2("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>`;
5325
5467
  this.root.appendChild(el);
5326
5468
  this.tableDialogEl = el;
5327
5469
  this.renderTableDialogState();
@@ -5380,17 +5522,17 @@ var SeatPicker = class _SeatPicker {
5380
5522
  const button = this.tableDialogEl?.querySelector(".sl-table-confirm");
5381
5523
  if (button) {
5382
5524
  button.disabled = true;
5383
- button.textContent = held ? "Updating\u2026" : "Selecting\u2026";
5525
+ button.textContent = held ? this.tf("picker.updatingEllipsis", "Updating\u2026") : this.tf("picker.selectingEllipsis", "Selecting\u2026");
5384
5526
  }
5385
5527
  if (held) {
5386
5528
  try {
5387
5529
  const updated = await this.controller.replaceTableQuantity(table.label, table.quantity, this.opts.holdTtlMs);
5388
5530
  if (!updated) {
5389
- this.toast("That guest count could not be secured. Your current table hold is unchanged.", "warning");
5531
+ this.toast(this.tf("picker.guestCountCouldNotBeSecured", "That guest count could not be secured. Your current table hold is unchanged."), "warning");
5390
5532
  this.renderTableDialogState();
5391
5533
  if (button) {
5392
5534
  button.disabled = false;
5393
- button.textContent = "Update table";
5535
+ button.textContent = this.tf("picker.updateTable", "Update table");
5394
5536
  }
5395
5537
  return;
5396
5538
  }
@@ -5404,13 +5546,13 @@ var SeatPicker = class _SeatPicker {
5404
5546
  this.dismissTableDialog();
5405
5547
  this.syncTray();
5406
5548
  this.emitHoldChange();
5407
- this.toast(`${table.label} updated for ${table.quantity} guests.`, "success");
5549
+ this.toast(t2("picker.tableUpdatedForGuests", { label: table.label, count: table.quantity }), "success");
5408
5550
  } catch (error) {
5409
5551
  this.opts.onError?.(error);
5410
- this.toast("That guest count is no longer available. Your current hold is unchanged.", "error");
5552
+ this.toast(this.tf("picker.guestCountNoLongerAvailable", "That guest count is no longer available. Your current hold is unchanged."), "error");
5411
5553
  if (button) {
5412
5554
  button.disabled = false;
5413
- button.textContent = "Update table";
5555
+ button.textContent = this.tf("picker.updateTable", "Update table");
5414
5556
  }
5415
5557
  }
5416
5558
  return;
@@ -5418,7 +5560,7 @@ var SeatPicker = class _SeatPicker {
5418
5560
  if (!this.controller.setTableQuantity(table.label, table.quantity)) {
5419
5561
  if (button) {
5420
5562
  button.disabled = false;
5421
- button.textContent = table.bookingMode === "variable" ? "Select table" : "Select whole table";
5563
+ button.textContent = table.bookingMode === "variable" ? this.tf("picker.selectTable", "Select table") : this.tf("picker.selectWholeTable", "Select whole table");
5422
5564
  }
5423
5565
  return;
5424
5566
  }
@@ -5464,16 +5606,16 @@ var SeatPicker = class _SeatPicker {
5464
5606
  '"': "&quot;"
5465
5607
  })[char]);
5466
5608
  const identityFields = [
5467
- details?.sectionLabel ? `<div class="sl-confirm-field"><span class="sl-confirm-key">Section</span><span class="sl-confirm-value">${safe(details.sectionLabel)}</span></div>` : "",
5609
+ 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>` : "",
5468
5610
  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>` : "",
5469
- 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>` : ""
5611
+ 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>` : ""
5470
5612
  ].filter(Boolean).join("");
5471
5613
  const el = document.createElement("div");
5472
5614
  el.className = "sl-confirm";
5473
5615
  el.setAttribute("role", "dialog");
5474
- el.setAttribute("aria-label", `Confirm seat ${seat.label}`);
5616
+ el.setAttribute("aria-label", t2("picker.confirmSeatLabel", { label: seat.label }));
5475
5617
  el.style.setProperty("--sl-cat", cat?.color ?? "#6e7bff");
5476
- el.innerHTML = `<div class="sl-confirm-grid">` + identityFields + `</div><div class="sl-confirm-cat"><span class="sl-dot" style="background:${cat?.color ?? "#6e7bff"}"></span><span class="sl-confirm-cat-name">${safe(details?.categoryLabel ?? cat?.label ?? seat.categoryKey)}</span>` + (price != null ? `<span class="sl-confirm-price">${this.money(price)}</span>` : "") + `</div><div class="sl-confirm-body">` + this.wheelchairConfirmHtml(details?.wheelchairSpaceType) + this.commercialConfirmHtml(seat.commercial) + (this.seatViewEnabled() && this.buyerView !== "venue3d" ? this.confirmThumbHtml(seat) : "") + (this.buyerView === "venue3d" ? `${this.seatConfidenceConfirmHtml(seat, `${details?.displayLabel ?? seat.displayLabel ?? seat.label} \xB7 ${price == null ? this.tf("picker.priceNotSupplied", "Price not supplied") : this.money(price)}`)}<div class="sl-confirm-inspect-row">${this.view3dCompareConfirmHtml(seat)}${this.see3dConfirmHtml()}</div>` : this.see3dConfirmHtml()) + `<div class="sl-confirm-row"><button type="button" class="sl-confirm-cancel">Cancel</button><button type="button" class="sl-confirm-add"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4 4L19 7"/></svg>Select</button></div></div>`;
5618
+ el.innerHTML = `<div class="sl-confirm-grid">` + identityFields + `</div><div class="sl-confirm-cat"><span class="sl-dot" style="background:${cat?.color ?? "#6e7bff"}"></span><span class="sl-confirm-cat-name">${safe(details?.categoryLabel ?? cat?.label ?? seat.categoryKey)}</span>` + (price != null ? `<span class="sl-confirm-price">${this.money(price)}</span>` : "") + `</div><div class="sl-confirm-body">` + this.wheelchairConfirmHtml(details?.wheelchairSpaceType) + this.commercialConfirmHtml(seat.commercial) + (this.seatViewEnabled() && this.buyerView !== "venue3d" ? this.confirmThumbHtml(seat) : "") + (this.buyerView === "venue3d" ? `${this.seatConfidenceConfirmHtml(seat, `${details?.displayLabel ?? seat.displayLabel ?? seat.label} \xB7 ${price == null ? this.tf("picker.priceNotSupplied", "Price not supplied") : this.money(price)}`)}<div class="sl-confirm-inspect-row">${this.view3dCompareConfirmHtml(seat)}${this.see3dConfirmHtml()}</div>` : this.see3dConfirmHtml()) + `<div class="sl-confirm-row"><button type="button" class="sl-confirm-cancel">${t2("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>`;
5477
5619
  this.els.map.appendChild(el);
5478
5620
  this.confirmEl = el;
5479
5621
  const thumb = el.querySelector(".sl-confirm-thumb");
@@ -5626,7 +5768,7 @@ var SeatPicker = class _SeatPicker {
5626
5768
  el.className = "sl-view";
5627
5769
  el.setAttribute("role", "dialog");
5628
5770
  el.setAttribute("aria-label", t2("picker.viewFromSeat", { label: seat.label }));
5629
- el.innerHTML = `<div class="sl-view-head"><span class="sl-view-title">${t2("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 ? t2("picker.real360") : t2("picker.preview")}</span><span class="sl-view-hint">Drag to look around \xB7 scroll to zoom</span></div>`;
5771
+ el.innerHTML = `<div class="sl-view-head"><span class="sl-view-title">${t2("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 ? t2("picker.real360") : t2("picker.preview")}</span><span class="sl-view-hint">${this.tf("picker.dragToLookAround", "Drag to look around \xB7 scroll to zoom")}</span></div>`;
5630
5772
  this.root.appendChild(el);
5631
5773
  this.viewEl = el;
5632
5774
  const pano = el.querySelector(".sl-view-pano");
@@ -5822,27 +5964,27 @@ var SeatPicker = class _SeatPicker {
5822
5964
  copy.className = "sl-offer-copy";
5823
5965
  const kicker = document.createElement("span");
5824
5966
  kicker.className = "sl-offer-kicker";
5825
- kicker.textContent = active ? "Current ticket offer" : "Upcoming ticket offer";
5967
+ kicker.textContent = active ? this.tf("picker.currentTicketOffer", "Current ticket offer") : this.tf("picker.upcomingTicketOffer", "Upcoming ticket offer");
5826
5968
  const name = document.createElement("strong");
5827
5969
  name.className = "sl-offer-name";
5828
- name.textContent = offer.name || (active ? "Current offer" : "Scheduled offer");
5970
+ name.textContent = offer.name || (active ? this.tf("picker.currentOffer", "Current offer") : this.tf("picker.scheduledOffer", "Scheduled offer"));
5829
5971
  const line = document.createElement("span");
5830
5972
  line.className = "sl-offer-line";
5831
5973
  const facts = [];
5832
5974
  if (active && availability.fromPrice != null) facts.push(this.money(availability.fromPrice / 100));
5833
- if (active && offer.remaining != null) facts.push(`${offer.remaining} available`);
5834
- if (active && offer.endsAt != null) facts.push(`until ${formatWhen(offer.endsAt, this.eventTimezone, this.opts.locale)}`);
5835
- if (upcoming?.startsAt != null) facts.push(`starts ${formatWhen(upcoming.startsAt, this.eventTimezone, this.opts.locale)}`);
5975
+ if (active && offer.remaining != null) facts.push(t2("picker.offerRemainingAvailable", { count: offer.remaining }));
5976
+ if (active && offer.endsAt != null) facts.push(t2("picker.offerUntil", { time: formatWhen(offer.endsAt, this.eventTimezone, this.opts.locale) }));
5977
+ if (upcoming?.startsAt != null) facts.push(t2("picker.offerStarts", { time: formatWhen(upcoming.startsAt, this.eventTimezone, this.opts.locale) }));
5836
5978
  line.textContent = facts.join(" \xB7 ");
5837
5979
  copy.append(kicker, name, line);
5838
5980
  const info = document.createElement("details");
5839
5981
  info.className = "sl-offer-info";
5840
5982
  const summary = document.createElement("summary");
5841
- summary.setAttribute("aria-label", `How the ${name.textContent} offer works`);
5983
+ summary.setAttribute("aria-label", t2("picker.howOfferWorks", { name: name.textContent ?? "" }));
5842
5984
  summary.textContent = "i";
5843
5985
  const detail = document.createElement("div");
5844
5986
  detail.className = "sl-offer-detail";
5845
- 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.`;
5987
+ 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.");
5846
5988
  info.append(summary, detail);
5847
5989
  main.append(copy, info);
5848
5990
  host.replaceChildren(main);
@@ -5878,8 +6020,8 @@ var SeatPicker = class _SeatPicker {
5878
6020
  const previous = offer?.previousPrice != null && offer.previousPrice > offer.price ? offer.previousPrice : null;
5879
6021
  const active = this.focusedCatKey === c.key;
5880
6022
  const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
5881
- 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>`;
5882
- }).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>`;
6023
+ 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") : t2("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">${tCount("picker.leftCount", left[c.key] ?? 0)}</span>` + (previous != null ? `<span class="sl-price-was">${escapeOption(this.money(previous))}</span>` : "") + (price != null ? `<span class="sl-price-amt">${this.money(price)}</span>` : "") + `</div>`;
6024
+ }).join("") + (overflow > 1 ? `<button type="button" class="sl-price-more" aria-expanded="${!collapsed}">` + (collapsed ? t2("picker.showAllTicketTypes", { count: doc.categories.length }) : this.tf("picker.showFewer", "Show fewer")) + `</button>` : "") + `<div class="sl-status-key" aria-label="${this.tf("picker.seatStatusLegend", "Seat status legend")}"><span class="sl-status-item"><i class="sl-status-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="5" y="10" width="14" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></i>${this.tf("picker.temporarilyHeld", "Temporarily held")}</span><span class="sl-status-item"><i class="sl-status-icon sold" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M7 17L17 7"/></svg></i>${this.tf("picker.sold", "Sold")}</span></div>`;
5883
6025
  this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
5884
6026
  row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
5885
6027
  row.addEventListener("mouseleave", () => this.controller.getRenderer()?.setCategoryHighlight?.(null));
@@ -5937,7 +6079,7 @@ var SeatPicker = class _SeatPicker {
5937
6079
  const now = left[cat.key] ?? 0;
5938
6080
  if (before === void 0 || now >= before) continue;
5939
6081
  const taken = before - now;
5940
- textEl.textContent = `${taken} seat${taken === 1 ? "" : "s"} just taken in ${cat.label} \xB7 ${now} left`;
6082
+ textEl.textContent = tCount("picker.seatsJustTakenInCategory", taken, { label: cat.label, left: now });
5941
6083
  this.els.live?.classList.remove("on");
5942
6084
  void this.els.live?.offsetWidth;
5943
6085
  this.els.live?.classList.add("on");
@@ -5955,12 +6097,17 @@ var SeatPicker = class _SeatPicker {
5955
6097
  const gone = this.controller.getSelection().filter((s) => !ownLabels.has(s.label) && (this.controller.getStatus(s.id) ?? "free") !== "free");
5956
6098
  if (!gone.length) return;
5957
6099
  this.controller.deselect(gone.map((s) => s.id));
5958
- this.toast(`Seat ${gone[0].label} was just taken by another buyer.`, "error");
6100
+ this.toast(t2("picker.seatJustTakenByAnother", { label: gone[0].label }), "error");
5959
6101
  }
5960
6102
  syncTray() {
5961
6103
  if (!this.els.tray) return;
5962
6104
  this.updateSelectionCapacity();
5963
6105
  const seats = this.committedSelection();
6106
+ if (this.root?.dataset.layout === "narrow" && this.secCardEl && (this.hold || seats.length || this.pendingGACount())) {
6107
+ this.secCardEl.remove();
6108
+ this.secCardEl = null;
6109
+ this.lastSection = null;
6110
+ }
5964
6111
  const gaAreas = this.controller.getGAAreas();
5965
6112
  const heldItems = this.hold?.items ?? [];
5966
6113
  const parts = [];
@@ -5971,18 +6118,22 @@ var SeatPicker = class _SeatPicker {
5971
6118
  `<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>`
5972
6119
  );
5973
6120
  } else if (!seats.length && !heldItems.length && !gaAreas.length) {
5974
- parts.push(`<div class="sl-tray-hint">Tap a seat on the map, or let us pick the best available for you.</div>`);
6121
+ 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>`);
5975
6122
  } else if (!seats.length && !heldItems.length) {
5976
- parts.push(`<div class="sl-tray-hint">Tap a seat on the map \u2014 or grab standing tickets below.</div>`);
6123
+ parts.push(`<div class="sl-tray-hint">${this.tf("picker.trayHintTapOrStanding", "Tap a seat on the map \u2014 or grab standing tickets below.")}</div>`);
5977
6124
  }
5978
6125
  const noPicks = !seats.length && !heldItems.length && !this.pendingGACount();
5979
- if (!this.salesClosed && !this.hold && (noPicks || this.bestAvailableBusy || this.bestAvailableConfirm)) {
6126
+ if (noPicks) this.baReopen = false;
6127
+ if (!this.salesClosed && !this.hold && (noPicks || this.baReopen || this.bestAvailableBusy || this.bestAvailableConfirm)) {
5980
6128
  const cats = this.controller.doc?.categories ?? [];
5981
6129
  const zones = this.controller.getBestAvailableZones();
5982
6130
  if (this.baZone && !zones.some((zone) => zone.id === this.baZone)) this.baZone = "";
5983
- 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
6131
+ 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>${t2("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
5984
6132
  // seats (same present-only philosophy as the a11y filter chips).
5985
- (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>`);
6133
+ (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")}` : tCount("picker.findBestSeatsCount", this.baQty)) + `</button>` + // Reopened over an existing selection: the buyer needs the way back
6134
+ // stated as plainly as the way forward (Go routes through the
6135
+ // "Replace your current choices?" confirm, so nothing is lost yet).
6136
+ (this.baReopen ? `<button type="button" class="sl-ba-back" data-ba-close>${this.tf("picker.keepMyPicks", "Keep my picks")}</button>` : "") + `</div>`);
5986
6137
  }
5987
6138
  const idGrid = (seatId, label, objectType, quantity = 1, objectId, identity) => {
5988
6139
  const esc = (value) => String(value ?? "\u2014").replace(/[&<>"]/g, (char) => ({
@@ -5994,21 +6145,21 @@ var SeatPicker = class _SeatPicker {
5994
6145
  const d = seatId ? this.controller.seatDetails(seatId) : null;
5995
6146
  const area = objectType === "ga" ? gaAreas.find((candidate) => candidate.id === objectId) : void 0;
5996
6147
  const effectiveType = objectType === "ga" ? "ga" : d?.objectType ?? objectType;
5997
- const typeWord = identity?.displayType?.trim() || d?.displayType?.trim() || area?.displayType?.trim() || (effectiveType === "table" ? "Table" : effectiveType === "booth" ? "Booth" : effectiveType === "ga" ? "General admission" : "Row");
6148
+ 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"));
5998
6149
  const buyerName = identity?.rowLabel ?? identity?.displayLabel ?? d?.rowLabel ?? d?.displayLabel ?? area?.displayLabel ?? area?.label ?? label;
5999
6150
  if (effectiveType === "table" && identity?.bookingMode) {
6000
- 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>`;
6151
+ 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>`;
6001
6152
  }
6002
6153
  if (effectiveType === "ga") {
6003
- 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>`;
6154
+ 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>`;
6004
6155
  }
6005
6156
  if (effectiveType === "booth") {
6006
- 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>`;
6157
+ 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>`;
6007
6158
  }
6008
6159
  if (!d?.sectionLabel && !d?.rowLabel && !d?.seatNumber) {
6009
- 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>`;
6160
+ 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>`;
6010
6161
  }
6011
- 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>`;
6162
+ 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>`;
6012
6163
  };
6013
6164
  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="${t2("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>`;
6014
6165
  for (const item of heldItems) {
@@ -6020,7 +6171,7 @@ var SeatPicker = class _SeatPicker {
6020
6171
  const table = item.objectType === "table" ? this.controller.tableSelection(item.label) : null;
6021
6172
  const canView2 = this.seatViewEnabled() && !!heldSeat && item.objectType !== "table";
6022
6173
  parts.push(
6023
- `<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>`
6174
+ `<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)}">${t2("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(t2("picker.removeHeldTicketLabel", { label: item.label }), canView2 ? item.label : null) + `</div>`
6024
6175
  );
6025
6176
  }
6026
6177
  const heldLabels = new Set(heldItems.map((item) => item.label));
@@ -6031,13 +6182,18 @@ var SeatPicker = class _SeatPicker {
6031
6182
  const cat = this.controller.doc?.categories.find((c) => c.key === s.categoryKey);
6032
6183
  const tierSelect = s.tiers && s.tiers.length ? `<select class="tier" data-tier="${s.id}" aria-label="${t2("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>` : "";
6033
6184
  parts.push(
6034
- `<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>`
6185
+ `<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)}">${t2("picker.guestsCountEdit", { count: s.quantity ?? 1 })}</button>` : "") + `${this.wheelchairChipMarker(s.wheelchairSpaceType)}${this.commercialChipMarker(s.commercial)}${tierSelect}<span class="amt">${this.money(this.paidPrice(s.categoryKey, s.tierId ?? null, s.price) * (s.quantity ?? 1))}</span></div></div>` + iconRail(t2("picker.removeSeatLabel", { label: s.label }), canView && s.objectType !== "table" ? s.label : null) + `</div>`
6035
6186
  );
6036
6187
  }
6037
6188
  for (const area of gaAreas) {
6038
6189
  const qty = this.gaQty.get(area.id) ?? 0;
6039
6190
  parts.push(
6040
- `<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>`
6191
+ `<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 ${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>`
6192
+ );
6193
+ }
6194
+ if (!this.salesClosed && !this.hold && !noPicks && !this.baReopen && !this.bestAvailableBusy && !this.bestAvailableConfirm) {
6195
+ parts.push(
6196
+ `<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>`
6041
6197
  );
6042
6198
  }
6043
6199
  this.els.tray.innerHTML = parts.join("");
@@ -6072,6 +6228,16 @@ var SeatPicker = class _SeatPicker {
6072
6228
  this.syncTray();
6073
6229
  this.els.tray.querySelector(".sl-ba-go")?.focus();
6074
6230
  });
6231
+ this.els.tray.querySelector("[data-ba-reopen]")?.addEventListener("click", () => {
6232
+ this.baReopen = true;
6233
+ this.syncTray();
6234
+ this.els.tray.querySelector(".sl-ba-go")?.focus();
6235
+ });
6236
+ this.els.tray.querySelector("[data-ba-close]")?.addEventListener("click", () => {
6237
+ this.baReopen = false;
6238
+ this.syncTray();
6239
+ this.els.tray.querySelector("[data-ba-reopen]")?.focus();
6240
+ });
6075
6241
  this.els.tray.querySelector("[data-ba-replace]")?.addEventListener("click", () => {
6076
6242
  this.bestAvailableConfirm = false;
6077
6243
  void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium, zoneId: this.baZone || void 0 });
@@ -6084,15 +6250,15 @@ var SeatPicker = class _SeatPicker {
6084
6250
  return;
6085
6251
  }
6086
6252
  const id = chip.dataset.seat;
6087
- const label = this.controller.getSelection().find((sel) => sel.id === id)?.label ?? "Seat";
6253
+ const label = this.controller.getSelection().find((sel) => sel.id === id)?.label ?? this.tf("picker.seat", "Seat");
6088
6254
  const remove = () => {
6089
6255
  this.controller.deselect([id]);
6090
- this.toast(`${label} removed.`, "neutral", {
6091
- label: "Undo",
6256
+ this.toast(t2("picker.labelRemoved", { label }), "neutral", {
6257
+ label: this.tf("picker.undo", "Undo"),
6092
6258
  onClick: () => {
6093
6259
  const restored = this.controller.select([id]);
6094
6260
  this.toast(
6095
- restored.length ? `${label} restored.` : `${label} is no longer available.`,
6261
+ restored.length ? t2("picker.labelRestored", { label }) : t2("picker.labelNoLongerAvailable", { label }),
6096
6262
  restored.length ? "success" : "warning"
6097
6263
  );
6098
6264
  }
@@ -6164,31 +6330,31 @@ var SeatPicker = class _SeatPicker {
6164
6330
  const pendingCount = this.pendingSelectionCount();
6165
6331
  const previousCount = this.lastTrayCount;
6166
6332
  const previousTotal = this.lastTrayTotal;
6167
- this.els.count.textContent = count ? `${count} ${count === 1 ? "ticket" : "tickets"}` : "No seats selected";
6333
+ this.els.count.textContent = count ? tCount("picker.ticketsCount", count) : this.tf("picker.noSeatsSelected", "No seats selected");
6168
6334
  this.els.total.textContent = count ? this.money(total) : "";
6169
6335
  this.root?.setAttribute("data-has-selection", String(count > 0));
6170
6336
  this.root?.setAttribute(
6171
6337
  "data-ba-active",
6172
- String(this.bestAvailableConfirm || this.bestAvailableBusy)
6338
+ String(this.bestAvailableConfirm || this.bestAvailableBusy || this.baReopen)
6173
6339
  );
6174
6340
  this.els.foot?.classList.toggle("empty", count === 0);
6175
6341
  if (this.els.seatSummary) {
6176
- this.els.seatSummary.textContent = count ? `${count} selected` : "";
6342
+ this.els.seatSummary.textContent = count ? tCount("picker.seatsSelectedCount", count) : "";
6177
6343
  }
6178
6344
  this.syncCta(count, pendingCount);
6179
6345
  if (this.hold) {
6180
6346
  const securedCount = heldCount || this.hold.seats?.length || 0;
6181
6347
  if (this.els.holdTitle) {
6182
- this.els.holdTitle.textContent = `${securedCount} secured`;
6348
+ this.els.holdTitle.textContent = t2("picker.securedCount", { count: securedCount });
6183
6349
  }
6184
6350
  if (this.els.holdCopy) {
6185
6351
  this.holdCopyPending = pendingCount > 0;
6186
- this.els.holdCopy.textContent = pendingCount ? `${pendingCount} more selected` : this.holdReassuranceText();
6352
+ this.els.holdCopy.textContent = pendingCount ? t2("picker.moreSelectedCount", { count: pendingCount }) : this.holdReassuranceText();
6187
6353
  }
6188
6354
  const change = this.els.holdChange;
6189
6355
  if (change) {
6190
6356
  change.disabled = this.releasingHold;
6191
- change.textContent = this.releasingHold ? "Releasing\u2026" : "Change";
6357
+ change.textContent = this.releasingHold ? this.tf("picker.releasingEllipsis", "Releasing\u2026") : this.tf("picker.change", "Change");
6192
6358
  }
6193
6359
  }
6194
6360
  if (count !== previousCount) this.animateOnce(this.els.count, "sl-value-pop", 380);
@@ -6213,21 +6379,21 @@ var SeatPicker = class _SeatPicker {
6213
6379
  renderPeek(count, total, pendingCount) {
6214
6380
  if (!this.els.peek) return;
6215
6381
  if (count && this.ctaPhase === "holding") {
6216
- this.els.peek.innerHTML = `<span>Securing your seats\u2026</span>`;
6382
+ this.els.peek.innerHTML = `<span>${this.tf("picker.securingSeats", "Securing your seats\u2026")}</span>`;
6217
6383
  return;
6218
6384
  }
6219
6385
  if (count && this.ctaPhase === "checkout") {
6220
- this.els.peek.innerHTML = `<span>\u2713 ${count} ${count === 1 ? "ticket" : "tickets"} secured \xB7 ${this.money(total)} \u2014 you won\u2019t be charged yet</span>`;
6386
+ this.els.peek.innerHTML = `<span>${t2("picker.peekSecured", { count, total: this.money(total) })}</span>`;
6221
6387
  return;
6222
6388
  }
6223
6389
  if (count) {
6224
- const holding = !!this.hold && !pendingCount;
6225
- 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>`;
6390
+ const toCheckout = !!this.hold;
6391
+ this.els.peek.innerHTML = `<span>${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>`;
6226
6392
  } else if (this.salesClosed) {
6227
6393
  this.els.peek.innerHTML = `<span>${this.tf("picker.salesClosedPill", "Sales are closed")}</span>`;
6228
6394
  } else {
6229
6395
  const prices = (this.controller.doc?.categories ?? []).map((c) => this.catPrice(c)).filter((p) => p != null);
6230
- 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>`;
6396
+ this.els.peek.innerHTML = (prices.length ? `<span>${t2("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>`;
6231
6397
  }
6232
6398
  }
6233
6399
  async removeHeldLabel(label, chip) {
@@ -6240,7 +6406,7 @@ var SeatPicker = class _SeatPicker {
6240
6406
  const preserveAcrossNavigation = this.handedOff;
6241
6407
  const released = await this.controller.releaseLabels([label]);
6242
6408
  if (!released) {
6243
- this.toast(`Couldn't remove ${label}. Your hold is unchanged.`, "error");
6409
+ this.toast(t2("picker.couldNotRemoveLabel", { label }), "error");
6244
6410
  return false;
6245
6411
  }
6246
6412
  const remaining = this.controller.currentHold();
@@ -6256,7 +6422,7 @@ var SeatPicker = class _SeatPicker {
6256
6422
  }
6257
6423
  this.syncTray();
6258
6424
  this.emitHoldChange();
6259
- this.toast(`${label} removed from your hold.`, "success");
6425
+ this.toast(t2("picker.labelRemovedFromHold", { label }), "success");
6260
6426
  return true;
6261
6427
  } finally {
6262
6428
  this.releasingLabels.delete(label);
@@ -6270,23 +6436,23 @@ var SeatPicker = class _SeatPicker {
6270
6436
  const button = this.els.holdChange;
6271
6437
  if (button) {
6272
6438
  button.disabled = true;
6273
- button.textContent = "Releasing\u2026";
6439
+ button.textContent = this.tf("picker.releasingEllipsis", "Releasing\u2026");
6274
6440
  }
6275
6441
  try {
6276
6442
  await this.release();
6277
- if (!this.hold) this.toast("Held tickets released. Choose your new seats.", "success");
6443
+ if (!this.hold) this.toast(this.tf("picker.heldTicketsReleased", "Held tickets released. Choose your new seats."), "success");
6278
6444
  } finally {
6279
6445
  this.releasingHold = false;
6280
6446
  if (button?.isConnected) {
6281
6447
  button.disabled = false;
6282
- button.textContent = "Change";
6448
+ button.textContent = this.tf("picker.change", "Change");
6283
6449
  }
6284
6450
  }
6285
6451
  }
6286
6452
  async handleCta() {
6287
6453
  if (this.salesClosed) return;
6288
6454
  if (this.totalTicketCount() > this.maxTickets) {
6289
- this.toast(`Remove tickets until your order has ${this.maxTickets} or fewer.`, "warning");
6455
+ this.toast(t2("picker.removeTicketsUntilOrFewer", { count: this.maxTickets }), "warning");
6290
6456
  return;
6291
6457
  }
6292
6458
  const committed = this.committedSelection();
@@ -6312,7 +6478,7 @@ var SeatPicker = class _SeatPicker {
6312
6478
  hold = h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : hold;
6313
6479
  }
6314
6480
  if (!hold) {
6315
- this.toast("One or more seats were just taken. Please pick again.", "error");
6481
+ this.toast(this.tf("picker.seatsJustTaken", "One or more seats were just taken. Please pick again."), "error");
6316
6482
  this.setCtaPhase("idle");
6317
6483
  this.syncTray();
6318
6484
  return;
@@ -6329,7 +6495,7 @@ var SeatPicker = class _SeatPicker {
6329
6495
  const problem = err;
6330
6496
  const labels = (problem.conflicts ?? []).map((conflict) => conflict.label).filter(Boolean).slice(0, 3);
6331
6497
  if (problem.reason === "event_closed") this.setSalesClosed(true);
6332
- 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.";
6498
+ const message = problem.reason === "event_closed" ? this.tf("picker.seatSalesClosedForEvent", "Seat sales have closed for this event.") : labels.length ? labels.length === 1 ? t2("picker.labelsNoLongerAvailable.one", { labels: labels.join(", ") }) : t2("picker.labelsNoLongerAvailable.other", { labels: labels.join(", ") }) : this.tf("picker.seatsJustTaken", "One or more seats were just taken. Please pick again.");
6333
6499
  this.toast(message, "error");
6334
6500
  this.setCtaPhase("idle");
6335
6501
  } finally {
@@ -6343,14 +6509,14 @@ var SeatPicker = class _SeatPicker {
6343
6509
  const ms = Math.max(0, this.holdExpiresAt - Date.now());
6344
6510
  const m = Math.floor(ms / 6e4);
6345
6511
  const s = String(Math.floor(ms % 6e4 / 1e3)).padStart(2, "0");
6346
- return `Yours for ${m}:${s} \u2014 you won\u2019t be charged yet.`;
6512
+ return t2("picker.holdReassurance", { time: `${m}:${s}` });
6347
6513
  }
6348
6514
  startHoldTimer(expiresAt) {
6349
6515
  this.stopHoldTimer();
6350
6516
  this.holdExpiresAt = expiresAt;
6351
6517
  if (this.hold) this.rememberHold(this.hold);
6352
6518
  const pill = this.els.hold;
6353
- pill.innerHTML = '<span class="sl-hold-dot" aria-hidden="true"></span><span>Held</span><span class="sl-hold-time" data-ref="holdTime"></span>';
6519
+ 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>`;
6354
6520
  const time = pill.querySelector('[data-ref="holdTime"]');
6355
6521
  this.els.holdNote?.classList.add("on");
6356
6522
  const tick = () => {
@@ -6383,7 +6549,7 @@ var SeatPicker = class _SeatPicker {
6383
6549
  const totalSecs = Math.ceil(ms / 1e3);
6384
6550
  const m = Math.floor(totalSecs / 60);
6385
6551
  const s = String(totalSecs % 60).padStart(2, "0");
6386
- this.els.extendTxt.innerHTML = `Your seats are held for <b>${m}:${s}</b>. Need more time?`;
6552
+ this.els.extendTxt.innerHTML = t2("picker.seatsHeldForNeedMoreTime", { time: `${m}:${s}` });
6387
6553
  this.extendEl.classList.add("on");
6388
6554
  } else {
6389
6555
  this.extendEl.classList.remove("on");
@@ -6393,7 +6559,7 @@ var SeatPicker = class _SeatPicker {
6393
6559
  const btn = this.els.extendBtn;
6394
6560
  btn.disabled = true;
6395
6561
  const prev = btn.textContent;
6396
- btn.textContent = "Adding\u2026";
6562
+ btn.textContent = this.tf("picker.addingEllipsis", "Adding\u2026");
6397
6563
  try {
6398
6564
  const h = await this.controller.extendHold(this.opts.holdTtlMs);
6399
6565
  if (h) {
@@ -6402,13 +6568,13 @@ var SeatPicker = class _SeatPicker {
6402
6568
  this.extendEl?.classList.remove("on");
6403
6569
  this.rememberHold(this.hold);
6404
6570
  this.emitHoldChange();
6405
- this.toast("More time added \u2014 your seats are still held.", "success");
6571
+ this.toast(this.tf("picker.moreTimeAdded", "More time added \u2014 your seats are still held."), "success");
6406
6572
  } else {
6407
- this.toast("Couldn't add more time \u2014 please head to checkout now.", "warning");
6573
+ this.toast(this.tf("picker.couldNotAddMoreTime", "Couldn't add more time \u2014 please head to checkout now."), "warning");
6408
6574
  }
6409
6575
  } catch (err) {
6410
6576
  this.opts.onError?.(err);
6411
- this.toast("Couldn't add more time \u2014 please head to checkout now.", "warning");
6577
+ this.toast(this.tf("picker.couldNotAddMoreTime", "Couldn't add more time \u2014 please head to checkout now."), "warning");
6412
6578
  } finally {
6413
6579
  btn.disabled = false;
6414
6580
  btn.textContent = prev;
@@ -6435,7 +6601,7 @@ var SeatPicker = class _SeatPicker {
6435
6601
  this.forgetHold();
6436
6602
  const n = handoff.lineItems.reduce((sum, i) => sum + i.quantity, 0);
6437
6603
  if (this.els.bookedSub) {
6438
- this.els.bookedSub.innerHTML = `<span class="sl-booked-seats">${n} ${n === 1 ? "ticket" : "tickets"}</span> confirmed. A confirmation is on its way.`;
6604
+ this.els.bookedSub.innerHTML = `<span class="sl-booked-seats">${tCount("picker.ticketsCount", n)}</span> ${this.tf("picker.confirmedAndOnWay", "confirmed. A confirmation is on its way.")}`;
6439
6605
  }
6440
6606
  this.bookedEl?.classList.add("on");
6441
6607
  this.opts.onBooked?.(handoff);
@@ -6517,7 +6683,7 @@ var SeatPicker = class _SeatPicker {
6517
6683
  ({ mountCheckout } = await loadHostedCheckout());
6518
6684
  } catch (err) {
6519
6685
  this.opts.onError?.(err);
6520
- this.toast("Checkout could not be opened. Your seats are still held \u2014 please try again.", "error");
6686
+ this.toast(this.tf("picker.checkoutCouldNotBeOpened", "Checkout could not be opened. Your seats are still held \u2014 please try again."), "error");
6521
6687
  this.setCtaPhase("idle");
6522
6688
  return;
6523
6689
  }
@@ -6632,9 +6798,9 @@ var SeatPicker = class _SeatPicker {
6632
6798
  rowTypeWord(details) {
6633
6799
  const authored = details?.displayType?.trim() || details?.rowType?.trim();
6634
6800
  if (authored) return authored;
6635
- if (details?.objectType === "table") return "Table";
6636
- if (details?.objectType === "booth") return "Booth";
6637
- return "Row";
6801
+ if (details?.objectType === "table") return this.tf("picker.table", "Table");
6802
+ if (details?.objectType === "booth") return this.tf("picker.booth", "Booth");
6803
+ return this.tf("picker.row", "Row");
6638
6804
  }
6639
6805
  rowShort(details) {
6640
6806
  const row = details?.rowLabel;
@@ -6657,7 +6823,7 @@ var SeatPicker = class _SeatPicker {
6657
6823
  const isGroupedTable = details.objectType === "table" && !!details.bookingMode;
6658
6824
  const isBooth = details.objectType === "booth";
6659
6825
  const hasLoc = details.sectionLabel || details.rowLabel || details.seatNumber;
6660
- 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>`;
6826
+ 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>`;
6661
6827
  const statusLine = details.status === "free" ? "" : `<div class="sl-tip-status">${details.status === "held" ? t2("map.statusHeld") : t2("map.statusTaken")}</div>`;
6662
6828
  const limited = this.limitedViewLabel(details.commercial);
6663
6829
  const cxLine = limited ? `<div class="sl-tip-cx"><span class="g" aria-hidden="true">\u25D0</span>${esc(limited)}</div>` : "";
@@ -6948,7 +7114,7 @@ var SeatPicker = class _SeatPicker {
6948
7114
  const identity = details?.displayLabel ?? seat.displayLabel ?? seat.label;
6949
7115
  const section = details?.sectionLabel;
6950
7116
  const row = this.rowShort(details);
6951
- const location2 = [section, row ? `Row ${row}` : null, identity].filter(Boolean).join(" \xB7 ");
7117
+ const location2 = [section, row ? t2("picker.rowLabel", { label: row }) : null, identity].filter(Boolean).join(" \xB7 ");
6952
7118
  const copy = status === "held" ? {
6953
7119
  title: this.tf("picker.temporarilyHeld", "Temporarily held"),
6954
7120
  message: this.tf("picker.heldSeatExplanation", "Another buyer is holding this seat. It may become available again.")
@@ -7044,7 +7210,7 @@ var SeatPicker = class _SeatPicker {
7044
7210
  chip = document.createElement("div");
7045
7211
  chip.className = "sl-view3d-compare-saved";
7046
7212
  chip.setAttribute("role", "group");
7047
- chip.setAttribute("aria-label", "Saved seat comparison");
7213
+ chip.setAttribute("aria-label", this.tf("picker.savedSeatComparison", "Saved seat comparison"));
7048
7214
  const main2 = document.createElement("button");
7049
7215
  main2.type = "button";
7050
7216
  main2.className = "main";
@@ -7056,7 +7222,7 @@ var SeatPicker = class _SeatPicker {
7056
7222
  clear.type = "button";
7057
7223
  clear.className = "clear";
7058
7224
  clear.textContent = "\xD7";
7059
- clear.setAttribute("aria-label", "Clear saved seat comparison");
7225
+ clear.setAttribute("aria-label", this.tf("picker.clearSavedSeatComparison", "Clear saved seat comparison"));
7060
7226
  clear.addEventListener("click", () => this.clearView3dComparison());
7061
7227
  chip.append(main2, clear);
7062
7228
  overlay.appendChild(chip);
@@ -7065,8 +7231,8 @@ var SeatPicker = class _SeatPicker {
7065
7231
  const count = this.view3dCompareSeatIds.length;
7066
7232
  const main = chip.querySelector(".main");
7067
7233
  if (main) {
7068
- main.textContent = count > 1 ? `Compare ${count}` : "1 seat saved";
7069
- main.setAttribute("aria-label", count > 1 ? `Open comparison of ${count} seats` : "One seat saved; choose another to compare");
7234
+ main.textContent = count > 1 ? t2("picker.compareCount", { count }) : this.tf("picker.oneSeatSaved", "1 seat saved");
7235
+ main.setAttribute("aria-label", count > 1 ? t2("picker.openComparisonOfSeats", { count }) : this.tf("picker.oneSeatSavedChooseAnother", "One seat saved; choose another to compare"));
7070
7236
  }
7071
7237
  }
7072
7238
  view3dComparisonSnapshot(seatId) {
@@ -7343,8 +7509,7 @@ var SeatPicker = class _SeatPicker {
7343
7509
  const wantFloors = floors.length > 1;
7344
7510
  const wantZones = zones.length > 1;
7345
7511
  const wantSections = sections.length > 1;
7346
- const wantLocator = allSections.length > 0 && handle.rows().length > 0;
7347
- if (!wantFloors && !wantZones && !wantSections && !wantLocator) return;
7512
+ if (!wantFloors && !wantZones && !wantSections) return;
7348
7513
  const nav = document.createElement("div");
7349
7514
  nav.className = "sl-view3d-nav";
7350
7515
  const finderToggle = document.createElement("button");
@@ -7354,7 +7519,7 @@ var SeatPicker = class _SeatPicker {
7354
7519
  const setFinderOpen = (open) => {
7355
7520
  nav.classList.toggle("is-open", open);
7356
7521
  finderToggle.setAttribute("aria-expanded", String(open));
7357
- finderToggle.textContent = open ? "Close seat finder" : "Find a seat";
7522
+ 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");
7358
7523
  };
7359
7524
  finderToggle.addEventListener("click", () => setFinderOpen(!nav.classList.contains("is-open")));
7360
7525
  nav.addEventListener("keydown", (event) => {
@@ -7391,7 +7556,7 @@ var SeatPicker = class _SeatPicker {
7391
7556
  row.appendChild(b);
7392
7557
  };
7393
7558
  add(this.tf("picker.allLevels", "All levels"), null);
7394
- for (const f of floors) add(f.label || `Level ${f.index + 1}`, f.index);
7559
+ for (const f of floors) add(f.label || t2("picker.levelNumber", { number: f.index + 1 }), f.index);
7395
7560
  nav.appendChild(row);
7396
7561
  }
7397
7562
  if (wantZones || wantSections) {
@@ -7434,86 +7599,6 @@ var SeatPicker = class _SeatPicker {
7434
7599
  }
7435
7600
  nav.appendChild(row);
7436
7601
  }
7437
- if (wantLocator) {
7438
- const locator = document.createElement("div");
7439
- locator.className = "sl-view3d-locator";
7440
- locator.setAttribute("role", "group");
7441
- locator.setAttribute("aria-label", "Find an exact seat in 3D");
7442
- const sectionSelect = document.createElement("select");
7443
- sectionSelect.setAttribute("aria-label", "Choose section in 3D");
7444
- const rowSelect = document.createElement("select");
7445
- rowSelect.setAttribute("aria-label", "Choose row in 3D");
7446
- const seatSelect = document.createElement("select");
7447
- seatSelect.setAttribute("aria-label", "Choose seat in 3D");
7448
- const view = document.createElement("button");
7449
- view.type = "button";
7450
- view.textContent = "Inspect seat";
7451
- view.disabled = true;
7452
- const fill = (select, placeholder, entries) => {
7453
- select.replaceChildren();
7454
- const first = document.createElement("option");
7455
- first.value = "";
7456
- first.textContent = placeholder;
7457
- select.appendChild(first);
7458
- for (const entry of entries) {
7459
- const option = document.createElement("option");
7460
- option.value = entry.id;
7461
- option.textContent = entry.label;
7462
- select.appendChild(option);
7463
- }
7464
- select.value = "";
7465
- };
7466
- fill(sectionSelect, "1. Section", allSections.map((section) => ({
7467
- id: section.id,
7468
- label: `${section.label} \xB7 ${section.seatCount.toLocaleString()} seats`
7469
- })));
7470
- fill(rowSelect, "2. Row", []);
7471
- fill(seatSelect, "3. Seat", []);
7472
- rowSelect.disabled = true;
7473
- seatSelect.disabled = true;
7474
- sectionSelect.addEventListener("change", () => {
7475
- const sectionId = sectionSelect.value;
7476
- view.disabled = true;
7477
- fill(seatSelect, "3. Seat", []);
7478
- seatSelect.disabled = true;
7479
- if (!sectionId || !handle.focusSection(sectionId)) {
7480
- fill(rowSelect, "2. Row", []);
7481
- rowSelect.disabled = true;
7482
- return;
7483
- }
7484
- const rows = handle.rows(sectionId);
7485
- fill(rowSelect, "2. Row", rows.map((row) => ({
7486
- id: row.id,
7487
- label: `${row.label} \xB7 ${row.seatCount} seats`
7488
- })));
7489
- rowSelect.disabled = rows.length === 0;
7490
- });
7491
- rowSelect.addEventListener("change", () => {
7492
- const rowId = rowSelect.value;
7493
- view.disabled = true;
7494
- if (!rowId || !handle.focusRow(rowId)) {
7495
- fill(seatSelect, "3. Seat", []);
7496
- seatSelect.disabled = true;
7497
- return;
7498
- }
7499
- const seats = handle.seatsInRow(rowId);
7500
- fill(seatSelect, "3. Seat", seats);
7501
- seatSelect.disabled = seats.length === 0;
7502
- });
7503
- seatSelect.addEventListener("change", () => {
7504
- const seatId = seatSelect.value;
7505
- view.disabled = !seatId;
7506
- });
7507
- view.addEventListener("click", () => {
7508
- const seatId = seatSelect.value;
7509
- if (seatId) {
7510
- setFinderOpen(false);
7511
- this.onView3dSeatPick(seatId);
7512
- }
7513
- });
7514
- locator.append(sectionSelect, rowSelect, seatSelect, view);
7515
- nav.appendChild(locator);
7516
- }
7517
7602
  overlay.appendChild(nav);
7518
7603
  }
7519
7604
  async enter3d(flySeatId) {
@@ -7529,7 +7614,7 @@ var SeatPicker = class _SeatPicker {
7529
7614
  const overlay = document.createElement("div");
7530
7615
  overlay.className = "sl-view3d";
7531
7616
  overlay.setAttribute("role", "group");
7532
- overlay.setAttribute("aria-label", "Interactive 3D venue view");
7617
+ overlay.setAttribute("aria-label", this.tf("picker.interactive3dVenueView", "Interactive 3D venue view"));
7533
7618
  const back = document.createElement("button");
7534
7619
  back.type = "button";
7535
7620
  back.className = "sl-view3d-back";
@@ -7555,7 +7640,7 @@ var SeatPicker = class _SeatPicker {
7555
7640
  fullscreen.type = "button";
7556
7641
  fullscreen.className = "sl-view3d-fs";
7557
7642
  fullscreen.textContent = "\u26F6";
7558
- fullscreen.setAttribute("aria-label", "Full screen");
7643
+ fullscreen.setAttribute("aria-label", this.tf("picker.fullScreen", "Full screen"));
7559
7644
  fullscreen.setAttribute("aria-pressed", String(!!document.fullscreenElement || this.fsFallback || this.framedFs));
7560
7645
  fullscreen.addEventListener("click", () => this.toggleFullscreen());
7561
7646
  overlay.appendChild(fullscreen);
@@ -7592,7 +7677,7 @@ var SeatPicker = class _SeatPicker {
7592
7677
  onSeatInspect: (id) => this.onView3dSeatPick(id),
7593
7678
  onSectionFocusChange: (sectionId) => {
7594
7679
  const select = overlay.querySelector(
7595
- 'select[aria-label="Choose section in 3D"]'
7680
+ 'select[data-locator="section"]'
7596
7681
  );
7597
7682
  const next = sectionId ?? "";
7598
7683
  if (!select || select.value === next) return;
@@ -7689,7 +7774,7 @@ var SeatPicker = class _SeatPicker {
7689
7774
  if (button) {
7690
7775
  button.disabled = true;
7691
7776
  button.classList.add("sl-busy");
7692
- button.innerHTML = '<span class="sl-ba-spin" aria-hidden="true"></span>Finding\u2026';
7777
+ button.innerHTML = `<span class="sl-ba-spin" aria-hidden="true"></span>${this.tf("picker.findingEllipsis", "Finding\u2026")}`;
7693
7778
  }
7694
7779
  try {
7695
7780
  const h = await this.controller.bestAvailable(qty, categoryKey, { ...opts, ttlMs: this.opts.holdTtlMs });
@@ -7711,7 +7796,7 @@ var SeatPicker = class _SeatPicker {
7711
7796
  } catch (err) {
7712
7797
  this.opts.onError?.(err);
7713
7798
  const reason = err?.reason;
7714
- 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.";
7799
+ const message = reason === "not_enough_together" ? t2("picker.couldNotFindSeatsTogether", { count: qty }) : reason === "sold_out" ? this.tf("picker.ticketTypeSoldOut", "That ticket type is sold out. Try another ticket type.") : reason === "event_closed" ? this.tf("picker.seatSalesClosedForEvent", "Seat sales have closed for this event.") : this.tf("picker.seatsNoLongerAvailableTryAnother", "Those seats are no longer available. Try another quantity or ticket type.");
7715
7800
  this.toast(message, "error");
7716
7801
  return null;
7717
7802
  } finally {
@@ -7740,7 +7825,7 @@ var SeatPicker = class _SeatPicker {
7740
7825
  }
7741
7826
  }
7742
7827
  if (!released) {
7743
- this.toast("Couldn't release your tickets. Your hold is unchanged.", "error");
7828
+ this.toast(this.tf("picker.couldNotReleaseTickets", "Couldn't release your tickets. Your hold is unchanged."), "error");
7744
7829
  return;
7745
7830
  }
7746
7831
  this.hold = null;