@seatlayer/js 0.50.1 → 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). */
@@ -3568,9 +3655,12 @@ var SeatPicker = class _SeatPicker {
3568
3655
  this.fsEscHandler = null;
3569
3656
  /** Host-level event chrome owns the duplicate identity outside full screen. */
3570
3657
  this.eventDetailsHidden = false;
3658
+ /** True while the hold note reads "N more selected" — the tick must not overwrite it. */
3659
+ this.holdCopyPending = false;
3571
3660
  /** Wide-layout ticket panel collapsed (map owns the full width). */
3572
3661
  this.sideCollapsed = false;
3573
3662
  this.sideToggleEl = null;
3663
+ this.pricesBtnEl = null;
3574
3664
  /** True once we've asked the host page to pin us fullscreen (framed, no native). */
3575
3665
  this.framedFs = false;
3576
3666
  /** Last height (px) posted to a host frame; dedupes redundant reports. */
@@ -3683,7 +3773,7 @@ var SeatPicker = class _SeatPicker {
3683
3773
  if (this.tableDialog?.physicalSeatIds.includes(seat.id)) this.dismissTableDialog();
3684
3774
  },
3685
3775
  onSelectionLimit: () => {
3686
- this.toast(`You can select up to ${this.maxTickets} tickets for this order.`, "warning");
3776
+ this.toast(t2("picker.maxTicketsForOrder", { count: this.maxTickets }), "warning");
3687
3777
  },
3688
3778
  onViewChange: () => {
3689
3779
  this.reanchorConfirm();
@@ -3799,8 +3889,8 @@ var SeatPicker = class _SeatPicker {
3799
3889
  return `<span class="sl-cx-mark" role="img" aria-label="${title}" title="${title}">\u25D0</span>`;
3800
3890
  }
3801
3891
  wheelchairProvisionLabel(type) {
3802
- if (type === "no-seat") return "Empty wheelchair space";
3803
- 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");
3804
3894
  return "";
3805
3895
  }
3806
3896
  wheelchairConfirmHtml(type) {
@@ -3881,9 +3971,9 @@ var SeatPicker = class _SeatPicker {
3881
3971
  const hideEventDetails = this.eventDetailsHidden && !active;
3882
3972
  this.root?.setAttribute("data-event-details-hidden", String(hideEventDetails));
3883
3973
  this.els.zfs?.setAttribute("aria-pressed", String(active));
3884
- 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"));
3885
3975
  const zfsLabel = this.els.zfs?.querySelector(".sl-zfs-lbl");
3886
- 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");
3887
3977
  this.view3dEl?.querySelector(".sl-view3d-fs")?.setAttribute("aria-pressed", String(active));
3888
3978
  }
3889
3979
  /**
@@ -3948,7 +4038,7 @@ var SeatPicker = class _SeatPicker {
3948
4038
  frame.className = "sl-modal-frame";
3949
4039
  frame.setAttribute("role", "dialog");
3950
4040
  frame.setAttribute("aria-modal", "true");
3951
- frame.setAttribute("aria-label", "Seat selection");
4041
+ frame.setAttribute("aria-label", tfStatic("picker.seatSelection", "Seat selection"));
3952
4042
  frame.tabIndex = -1;
3953
4043
  scrim.appendChild(frame);
3954
4044
  document.body.appendChild(scrim);
@@ -4108,7 +4198,7 @@ var SeatPicker = class _SeatPicker {
4108
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>
4109
4199
  <span data-ref="closedPillText"></span>
4110
4200
  </span>
4111
- <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")}">
4112
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>
4113
4203
  </button>
4114
4204
  </div>
@@ -4116,17 +4206,17 @@ var SeatPicker = class _SeatPicker {
4116
4206
  <div class="sl-map">
4117
4207
  <div class="sl-map-host" data-ref="map"></div>
4118
4208
  <div class="sl-zoom" data-ref="zoom">
4119
- <button type="button" aria-label="Zoom in" title="Zoom in" data-ref="zin">+</button>
4120
- <button type="button" aria-label="Zoom out" title="Zoom out" data-ref="zout">\u2212</button>
4121
- <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">
4122
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>
4123
4213
  </button>
4124
- <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">
4125
4215
  <svg viewBox="0 0 24 24"><path d="M15 3h6v6M9 21H3v-6M21 3l-7 7M3 21l7-7"/></svg>
4126
- <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>
4127
4217
  </button>
4128
4218
  </div>
4129
- <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>
4130
4220
  <div class="sl-toast" data-ref="toast" role="status" aria-live="polite"></div>
4131
4221
  </div>
4132
4222
  <div class="sl-side" data-ref="side">
@@ -4134,24 +4224,25 @@ var SeatPicker = class _SeatPicker {
4134
4224
  <div class="sl-sheet-grab"></div>
4135
4225
  <div class="sl-sheet-bar">
4136
4226
  <div class="sl-sheet-peek" data-ref="peek"></div>
4137
- <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">
4138
4228
  <svg viewBox="0 0 24 24"><path d="M6 15l6-6 6 6"/></svg>
4139
4229
  </button>
4140
4230
  </div>
4141
4231
  </div>
4142
- <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>
4143
4233
  <div class="sl-filters" data-ref="filters"></div>
4144
4234
  <div class="sl-offer" data-ref="offer" role="status" aria-live="polite"></div>
4145
- <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>
4146
4237
  <div class="sl-prices" data-ref="prices"></div>
4147
- <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>
4148
- <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>
4149
4240
  <div class="sl-tray" data-ref="tray"></div>
4150
4241
  <div class="sl-foot" data-ref="foot">
4151
4242
  <div class="sl-hold-note" data-ref="holdNote" role="status" aria-live="polite">
4152
4243
  <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg>
4153
- <span><b data-ref="holdTitle">Seats secured</b><span class="sl-hold-copy" data-ref="holdCopy">Checkout timer is running.</span></span>
4154
- <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>
4155
4246
  </div>
4156
4247
  <div class="sl-total"><span data-ref="count"></span><b data-ref="total"></b></div>
4157
4248
  <button type="button" class="sl-cta" data-ref="cta" disabled></button>
@@ -4202,7 +4293,7 @@ var SeatPicker = class _SeatPicker {
4202
4293
  const setSheet = (open) => {
4203
4294
  root.dataset.sheet = open ? "open" : "peek";
4204
4295
  toggle?.setAttribute("aria-expanded", String(open));
4205
- 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"));
4206
4297
  };
4207
4298
  setSheet(root.dataset.sheet === "open");
4208
4299
  toggle?.addEventListener("click", (e) => {
@@ -4248,6 +4339,39 @@ var SeatPicker = class _SeatPicker {
4248
4339
  head.releasePointerCapture?.(e.pointerId);
4249
4340
  });
4250
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;
4251
4375
  this.tipEl = document.createElement("div");
4252
4376
  this.tipEl.setAttribute("role", "tooltip");
4253
4377
  this.tipEl.className = "sl-tip";
@@ -4265,7 +4389,7 @@ var SeatPicker = class _SeatPicker {
4265
4389
  const info = await this.controller.render(canvasHost);
4266
4390
  if (this.destroyed) return this;
4267
4391
  if (!info) {
4268
- 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>`;
4269
4393
  this.els.boot.querySelector("button").addEventListener("click", () => {
4270
4394
  const container = this.opts.container;
4271
4395
  const opts = this.opts;
@@ -4300,6 +4424,12 @@ var SeatPicker = class _SeatPicker {
4300
4424
  const when = info.startsAt ? formatWhen(info.startsAt, info.timezone ?? null, this.opts.locale) : "";
4301
4425
  this.eventWhenText = when || null;
4302
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
+ }
4303
4433
  this.buildBadge(chartTheme);
4304
4434
  const present = /* @__PURE__ */ new Set();
4305
4435
  let hasLimitedView = false;
@@ -4326,7 +4456,7 @@ var SeatPicker = class _SeatPicker {
4326
4456
  const mk = (key, label) => `<button type="button" class="sl-chip-f${key === "all" ? " on" : ""}" data-a11y="1" data-f="${key}">${label}</button>`;
4327
4457
  chips.insertAdjacentHTML(
4328
4458
  "beforeend",
4329
- 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("")
4330
4460
  );
4331
4461
  const active = /* @__PURE__ */ new Set();
4332
4462
  const syncChips = () => {
@@ -4372,7 +4502,7 @@ var SeatPicker = class _SeatPicker {
4372
4502
  cb.type = "button";
4373
4503
  cb.className = "sl-cbbtn";
4374
4504
  this.cbEl = cb;
4375
- cb.setAttribute("aria-label", "Toggle colorblind-friendly colors");
4505
+ cb.setAttribute("aria-label", this.tf("picker.toggleColorblindColors", "Toggle colorblind-friendly colors"));
4376
4506
  cb.setAttribute("aria-pressed", String(this.cbSafe));
4377
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>';
4378
4508
  this.els.zfit.parentElement.appendChild(cb);
@@ -4458,8 +4588,8 @@ var SeatPicker = class _SeatPicker {
4458
4588
  if (!btn) return;
4459
4589
  const collapsed = this.sideCollapsed;
4460
4590
  btn.setAttribute("aria-expanded", String(!collapsed));
4461
- btn.setAttribute("aria-label", collapsed ? "Show the ticket panel" : "Hide the ticket panel");
4462
- 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>`;
4463
4593
  }
4464
4594
  /**
4465
4595
  * Collapse or reopen the wide-layout ticket panel in place (host-drivable —
@@ -4485,13 +4615,24 @@ var SeatPicker = class _SeatPicker {
4485
4615
  const narrow = this.root?.dataset.layout === "narrow";
4486
4616
  this.applyPanelCollapsed();
4487
4617
  const zfs = this.els.zfs;
4488
- if (zfs) {
4489
- if (narrow && this.regions["top-right"]) {
4490
- zfs.classList.add("sl-fs-pill");
4491
- this.regions["top-right"].appendChild(zfs);
4492
- } else if (!narrow && this.els.zoom && zfs.parentElement !== this.els.zoom) {
4493
- zfs.classList.remove("sl-fs-pill");
4494
- 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;
4495
4636
  }
4496
4637
  }
4497
4638
  const filters = this.els.filters;
@@ -4518,7 +4659,7 @@ var SeatPicker = class _SeatPicker {
4518
4659
  this.extendEl = el;
4519
4660
  this.els.extendTxt = el.querySelector('[data-ref="extendTxt"]');
4520
4661
  this.els.extendBtn = el.querySelector('[data-ref="extendBtn"]');
4521
- this.els.extendBtn.textContent = "Add time";
4662
+ this.els.extendBtn.textContent = this.tf("picker.addTime", "Add time");
4522
4663
  this.els.extendBtn.addEventListener("click", () => void this.handleExtend());
4523
4664
  }
4524
4665
  /** Success overlay + onBooked fire when the held seats settle to booked. */
@@ -4527,7 +4668,7 @@ var SeatPicker = class _SeatPicker {
4527
4668
  el.className = "sl-booked";
4528
4669
  el.setAttribute("role", "status");
4529
4670
  el.setAttribute("aria-live", "polite");
4530
- 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>`;
4531
4672
  this.root.appendChild(el);
4532
4673
  this.bookedEl = el;
4533
4674
  this.els.bookedSub = el.querySelector('[data-ref="bookedSub"]');
@@ -4538,8 +4679,7 @@ var SeatPicker = class _SeatPicker {
4538
4679
  * host `messages` override (which makes `t()` return the override, not the key).
4539
4680
  */
4540
4681
  tf(key, fallback) {
4541
- const v = t2(key);
4542
- return v === key ? fallback : v;
4682
+ return tfStatic(key, fallback);
4543
4683
  }
4544
4684
  /** Sold-out overlay — an informational state with no unavailable action. */
4545
4685
  buildSoldoutOverlay() {
@@ -4724,7 +4864,7 @@ var SeatPicker = class _SeatPicker {
4724
4864
  }
4725
4865
  canAddTicket() {
4726
4866
  if (this.totalTicketCount() < this.maxTickets) return true;
4727
- this.toast(`You can select up to ${this.maxTickets} tickets for this order.`, "warning");
4867
+ this.toast(t2("picker.maxTicketsForOrder", { count: this.maxTickets }), "warning");
4728
4868
  return false;
4729
4869
  }
4730
4870
  pendingGATotal(gaAreas) {
@@ -4747,30 +4887,32 @@ var SeatPicker = class _SeatPicker {
4747
4887
  }
4748
4888
  if (this.confirmSeat || this.tableDialog && !this.tableDialogHeld) {
4749
4889
  cta.disabled = true;
4750
- 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");
4751
4891
  return;
4752
4892
  }
4753
4893
  if (this.ctaPhase === "holding") {
4754
4894
  cta.disabled = true;
4755
- 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")}`;
4756
4896
  return;
4757
4897
  }
4758
4898
  if (this.ctaPhase === "checkout") {
4759
4899
  cta.disabled = true;
4760
- cta.innerHTML = '<span class="sl-cta-spin" aria-hidden="true"></span>Opening checkout\u2026';
4900
+ cta.innerHTML = `<span class="sl-cta-spin" aria-hidden="true"></span>${this.tf("picker.openingCheckout", "Opening secure checkout\u2026")}`;
4761
4901
  return;
4762
4902
  }
4763
4903
  cta.disabled = count === 0;
4764
- 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");
4765
4905
  }
4766
4906
  setCtaPhase(phase) {
4767
4907
  this.ctaPhase = phase;
4768
4908
  this.syncCta();
4909
+ this.renderPeek(this.lastTrayCount, this.lastTrayTotal, this.pendingSelectionCount());
4769
4910
  if (phase === "checkout") {
4770
4911
  this.scheduleMotion(() => {
4771
4912
  if (this.ctaPhase !== "checkout") return;
4772
4913
  this.ctaPhase = "idle";
4773
4914
  this.syncCta();
4915
+ this.renderPeek(this.lastTrayCount, this.lastTrayTotal, this.pendingSelectionCount());
4774
4916
  }, 1100);
4775
4917
  }
4776
4918
  }
@@ -4820,7 +4962,7 @@ var SeatPicker = class _SeatPicker {
4820
4962
  this.syncTray();
4821
4963
  this.emitHoldChange();
4822
4964
  this.opts.onHoldRestored?.(restored, restored.seats ?? [], this.buildHandoff(restored));
4823
- 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");
4824
4966
  return restored;
4825
4967
  } catch (error) {
4826
4968
  const status = error?.status;
@@ -5038,8 +5180,8 @@ var SeatPicker = class _SeatPicker {
5038
5180
  if (bands.length < 2) return;
5039
5181
  const select = document.createElement("select");
5040
5182
  select.className = "sl-price-select";
5041
- select.setAttribute("aria-label", "Filter and focus seats by price");
5042
- 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("");
5043
5185
  this.els.pricesSec.appendChild(select);
5044
5186
  select.addEventListener("change", () => {
5045
5187
  const band = bands.find((candidate) => candidate.id === select.value);
@@ -5066,8 +5208,8 @@ var SeatPicker = class _SeatPicker {
5066
5208
  const projection = document.createElement("div");
5067
5209
  projection.className = "sl-projection";
5068
5210
  projection.setAttribute("role", "group");
5069
- projection.setAttribute("aria-label", "Venue view");
5070
- 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>`;
5071
5213
  projection.querySelectorAll("button").forEach((button) => {
5072
5214
  button.addEventListener("click", () => {
5073
5215
  this.setBuyerView(button.dataset.view);
@@ -5190,6 +5332,10 @@ var SeatPicker = class _SeatPicker {
5190
5332
  renderSectionCard(summary) {
5191
5333
  if (!this.els.map) return;
5192
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
+ }
5193
5339
  const paid = summary.categories.length ? summary.categories.map((c) => this.paidPrice(c.key, null, c.price)) : [summary.priceMin, summary.priceMax];
5194
5340
  const paidMin = Math.min(...paid);
5195
5341
  const paidMax = Math.max(...paid);
@@ -5290,13 +5436,13 @@ var SeatPicker = class _SeatPicker {
5290
5436
  }
5291
5437
  const cat = this.controller.doc?.categories.find((c) => c.key === seat.categoryKey);
5292
5438
  const status = this.controller.getStatus(seat.id) ?? "free";
5293
- 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");
5294
5440
  const price = cat ? this.catPrice(cat) : void 0;
5295
5441
  const table = this.controller.tableSelection(seat.id);
5296
5442
  const details = table ?? this.controller.seatDetails(seat.id);
5297
5443
  const typeWord = this.rowTypeWord(details);
5298
5444
  const buyerName = details?.rowLabel ?? details?.displayLabel ?? seat.displayLabel ?? seat.label;
5299
- 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 });
5300
5446
  this.srEl.textContent = `${identity}, ${cat?.label ?? seat.categoryKey}${price != null ? `, ${this.money(price)}` : ""}, ${statusText}`;
5301
5447
  }
5302
5448
  // ---- atomic table confirmation / guest quantity ---------------------------
@@ -5317,7 +5463,7 @@ var SeatPicker = class _SeatPicker {
5317
5463
  const typeWord = this.rowTypeWord(table);
5318
5464
  const el = document.createElement("div");
5319
5465
  el.className = "sl-table-scrim";
5320
- 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>`;
5321
5467
  this.root.appendChild(el);
5322
5468
  this.tableDialogEl = el;
5323
5469
  this.renderTableDialogState();
@@ -5376,17 +5522,17 @@ var SeatPicker = class _SeatPicker {
5376
5522
  const button = this.tableDialogEl?.querySelector(".sl-table-confirm");
5377
5523
  if (button) {
5378
5524
  button.disabled = true;
5379
- button.textContent = held ? "Updating\u2026" : "Selecting\u2026";
5525
+ button.textContent = held ? this.tf("picker.updatingEllipsis", "Updating\u2026") : this.tf("picker.selectingEllipsis", "Selecting\u2026");
5380
5526
  }
5381
5527
  if (held) {
5382
5528
  try {
5383
5529
  const updated = await this.controller.replaceTableQuantity(table.label, table.quantity, this.opts.holdTtlMs);
5384
5530
  if (!updated) {
5385
- 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");
5386
5532
  this.renderTableDialogState();
5387
5533
  if (button) {
5388
5534
  button.disabled = false;
5389
- button.textContent = "Update table";
5535
+ button.textContent = this.tf("picker.updateTable", "Update table");
5390
5536
  }
5391
5537
  return;
5392
5538
  }
@@ -5400,13 +5546,13 @@ var SeatPicker = class _SeatPicker {
5400
5546
  this.dismissTableDialog();
5401
5547
  this.syncTray();
5402
5548
  this.emitHoldChange();
5403
- this.toast(`${table.label} updated for ${table.quantity} guests.`, "success");
5549
+ this.toast(t2("picker.tableUpdatedForGuests", { label: table.label, count: table.quantity }), "success");
5404
5550
  } catch (error) {
5405
5551
  this.opts.onError?.(error);
5406
- 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");
5407
5553
  if (button) {
5408
5554
  button.disabled = false;
5409
- button.textContent = "Update table";
5555
+ button.textContent = this.tf("picker.updateTable", "Update table");
5410
5556
  }
5411
5557
  }
5412
5558
  return;
@@ -5414,7 +5560,7 @@ var SeatPicker = class _SeatPicker {
5414
5560
  if (!this.controller.setTableQuantity(table.label, table.quantity)) {
5415
5561
  if (button) {
5416
5562
  button.disabled = false;
5417
- 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");
5418
5564
  }
5419
5565
  return;
5420
5566
  }
@@ -5460,16 +5606,16 @@ var SeatPicker = class _SeatPicker {
5460
5606
  '"': "&quot;"
5461
5607
  })[char]);
5462
5608
  const identityFields = [
5463
- 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>` : "",
5464
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>` : "",
5465
- 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>` : ""
5466
5612
  ].filter(Boolean).join("");
5467
5613
  const el = document.createElement("div");
5468
5614
  el.className = "sl-confirm";
5469
5615
  el.setAttribute("role", "dialog");
5470
- el.setAttribute("aria-label", `Confirm seat ${seat.label}`);
5616
+ el.setAttribute("aria-label", t2("picker.confirmSeatLabel", { label: seat.label }));
5471
5617
  el.style.setProperty("--sl-cat", cat?.color ?? "#6e7bff");
5472
- 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>`;
5473
5619
  this.els.map.appendChild(el);
5474
5620
  this.confirmEl = el;
5475
5621
  const thumb = el.querySelector(".sl-confirm-thumb");
@@ -5622,7 +5768,7 @@ var SeatPicker = class _SeatPicker {
5622
5768
  el.className = "sl-view";
5623
5769
  el.setAttribute("role", "dialog");
5624
5770
  el.setAttribute("aria-label", t2("picker.viewFromSeat", { label: seat.label }));
5625
- 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>`;
5626
5772
  this.root.appendChild(el);
5627
5773
  this.viewEl = el;
5628
5774
  const pano = el.querySelector(".sl-view-pano");
@@ -5818,27 +5964,27 @@ var SeatPicker = class _SeatPicker {
5818
5964
  copy.className = "sl-offer-copy";
5819
5965
  const kicker = document.createElement("span");
5820
5966
  kicker.className = "sl-offer-kicker";
5821
- 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");
5822
5968
  const name = document.createElement("strong");
5823
5969
  name.className = "sl-offer-name";
5824
- 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"));
5825
5971
  const line = document.createElement("span");
5826
5972
  line.className = "sl-offer-line";
5827
5973
  const facts = [];
5828
5974
  if (active && availability.fromPrice != null) facts.push(this.money(availability.fromPrice / 100));
5829
- if (active && offer.remaining != null) facts.push(`${offer.remaining} available`);
5830
- if (active && offer.endsAt != null) facts.push(`until ${formatWhen(offer.endsAt, this.eventTimezone, this.opts.locale)}`);
5831
- 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) }));
5832
5978
  line.textContent = facts.join(" \xB7 ");
5833
5979
  copy.append(kicker, name, line);
5834
5980
  const info = document.createElement("details");
5835
5981
  info.className = "sl-offer-info";
5836
5982
  const summary = document.createElement("summary");
5837
- summary.setAttribute("aria-label", `How the ${name.textContent} offer works`);
5983
+ summary.setAttribute("aria-label", t2("picker.howOfferWorks", { name: name.textContent ?? "" }));
5838
5984
  summary.textContent = "i";
5839
5985
  const detail = document.createElement("div");
5840
5986
  detail.className = "sl-offer-detail";
5841
- 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.");
5842
5988
  info.append(summary, detail);
5843
5989
  main.append(copy, info);
5844
5990
  host.replaceChildren(main);
@@ -5874,8 +6020,8 @@ var SeatPicker = class _SeatPicker {
5874
6020
  const previous = offer?.previousPrice != null && offer.previousPrice > offer.price ? offer.previousPrice : null;
5875
6021
  const active = this.focusedCatKey === c.key;
5876
6022
  const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
5877
- 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>`;
5878
- }).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>`;
5879
6025
  this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
5880
6026
  row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
5881
6027
  row.addEventListener("mouseleave", () => this.controller.getRenderer()?.setCategoryHighlight?.(null));
@@ -5933,7 +6079,7 @@ var SeatPicker = class _SeatPicker {
5933
6079
  const now = left[cat.key] ?? 0;
5934
6080
  if (before === void 0 || now >= before) continue;
5935
6081
  const taken = before - now;
5936
- 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 });
5937
6083
  this.els.live?.classList.remove("on");
5938
6084
  void this.els.live?.offsetWidth;
5939
6085
  this.els.live?.classList.add("on");
@@ -5951,12 +6097,17 @@ var SeatPicker = class _SeatPicker {
5951
6097
  const gone = this.controller.getSelection().filter((s) => !ownLabels.has(s.label) && (this.controller.getStatus(s.id) ?? "free") !== "free");
5952
6098
  if (!gone.length) return;
5953
6099
  this.controller.deselect(gone.map((s) => s.id));
5954
- this.toast(`Seat ${gone[0].label} was just taken by another buyer.`, "error");
6100
+ this.toast(t2("picker.seatJustTakenByAnother", { label: gone[0].label }), "error");
5955
6101
  }
5956
6102
  syncTray() {
5957
6103
  if (!this.els.tray) return;
5958
6104
  this.updateSelectionCapacity();
5959
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
+ }
5960
6111
  const gaAreas = this.controller.getGAAreas();
5961
6112
  const heldItems = this.hold?.items ?? [];
5962
6113
  const parts = [];
@@ -5967,18 +6118,22 @@ var SeatPicker = class _SeatPicker {
5967
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>`
5968
6119
  );
5969
6120
  } else if (!seats.length && !heldItems.length && !gaAreas.length) {
5970
- 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>`);
5971
6122
  } else if (!seats.length && !heldItems.length) {
5972
- 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>`);
5973
6124
  }
5974
6125
  const noPicks = !seats.length && !heldItems.length && !this.pendingGACount();
5975
- 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)) {
5976
6128
  const cats = this.controller.doc?.categories ?? [];
5977
6129
  const zones = this.controller.getBestAvailableZones();
5978
6130
  if (this.baZone && !zones.some((zone) => zone.id === this.baZone)) this.baZone = "";
5979
- 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
5980
6132
  // seats (same present-only philosophy as the a11y filter chips).
5981
- (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>`);
5982
6137
  }
5983
6138
  const idGrid = (seatId, label, objectType, quantity = 1, objectId, identity) => {
5984
6139
  const esc = (value) => String(value ?? "\u2014").replace(/[&<>"]/g, (char) => ({
@@ -5990,21 +6145,21 @@ var SeatPicker = class _SeatPicker {
5990
6145
  const d = seatId ? this.controller.seatDetails(seatId) : null;
5991
6146
  const area = objectType === "ga" ? gaAreas.find((candidate) => candidate.id === objectId) : void 0;
5992
6147
  const effectiveType = objectType === "ga" ? "ga" : d?.objectType ?? objectType;
5993
- 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"));
5994
6149
  const buyerName = identity?.rowLabel ?? identity?.displayLabel ?? d?.rowLabel ?? d?.displayLabel ?? area?.displayLabel ?? area?.label ?? label;
5995
6150
  if (effectiveType === "table" && identity?.bookingMode) {
5996
- 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>`;
5997
6152
  }
5998
6153
  if (effectiveType === "ga") {
5999
- 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>`;
6000
6155
  }
6001
6156
  if (effectiveType === "booth") {
6002
- 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>`;
6003
6158
  }
6004
6159
  if (!d?.sectionLabel && !d?.rowLabel && !d?.seatNumber) {
6005
- 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>`;
6006
6161
  }
6007
- 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>`;
6008
6163
  };
6009
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>`;
6010
6165
  for (const item of heldItems) {
@@ -6016,7 +6171,7 @@ var SeatPicker = class _SeatPicker {
6016
6171
  const table = item.objectType === "table" ? this.controller.tableSelection(item.label) : null;
6017
6172
  const canView2 = this.seatViewEnabled() && !!heldSeat && item.objectType !== "table";
6018
6173
  parts.push(
6019
- `<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>`
6020
6175
  );
6021
6176
  }
6022
6177
  const heldLabels = new Set(heldItems.map((item) => item.label));
@@ -6027,13 +6182,18 @@ var SeatPicker = class _SeatPicker {
6027
6182
  const cat = this.controller.doc?.categories.find((c) => c.key === s.categoryKey);
6028
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>` : "";
6029
6184
  parts.push(
6030
- `<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>`
6031
6186
  );
6032
6187
  }
6033
6188
  for (const area of gaAreas) {
6034
6189
  const qty = this.gaQty.get(area.id) ?? 0;
6035
6190
  parts.push(
6036
- `<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>`
6037
6197
  );
6038
6198
  }
6039
6199
  this.els.tray.innerHTML = parts.join("");
@@ -6068,6 +6228,16 @@ var SeatPicker = class _SeatPicker {
6068
6228
  this.syncTray();
6069
6229
  this.els.tray.querySelector(".sl-ba-go")?.focus();
6070
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
+ });
6071
6241
  this.els.tray.querySelector("[data-ba-replace]")?.addEventListener("click", () => {
6072
6242
  this.bestAvailableConfirm = false;
6073
6243
  void this.bestAvailable(this.baQty, this.baCat || void 0, { preferPremium: this.baPremium, zoneId: this.baZone || void 0 });
@@ -6080,15 +6250,15 @@ var SeatPicker = class _SeatPicker {
6080
6250
  return;
6081
6251
  }
6082
6252
  const id = chip.dataset.seat;
6083
- 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");
6084
6254
  const remove = () => {
6085
6255
  this.controller.deselect([id]);
6086
- this.toast(`${label} removed.`, "neutral", {
6087
- label: "Undo",
6256
+ this.toast(t2("picker.labelRemoved", { label }), "neutral", {
6257
+ label: this.tf("picker.undo", "Undo"),
6088
6258
  onClick: () => {
6089
6259
  const restored = this.controller.select([id]);
6090
6260
  this.toast(
6091
- restored.length ? `${label} restored.` : `${label} is no longer available.`,
6261
+ restored.length ? t2("picker.labelRestored", { label }) : t2("picker.labelNoLongerAvailable", { label }),
6092
6262
  restored.length ? "success" : "warning"
6093
6263
  );
6094
6264
  }
@@ -6160,30 +6330,31 @@ var SeatPicker = class _SeatPicker {
6160
6330
  const pendingCount = this.pendingSelectionCount();
6161
6331
  const previousCount = this.lastTrayCount;
6162
6332
  const previousTotal = this.lastTrayTotal;
6163
- 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");
6164
6334
  this.els.total.textContent = count ? this.money(total) : "";
6165
6335
  this.root?.setAttribute("data-has-selection", String(count > 0));
6166
6336
  this.root?.setAttribute(
6167
6337
  "data-ba-active",
6168
- String(this.bestAvailableConfirm || this.bestAvailableBusy)
6338
+ String(this.bestAvailableConfirm || this.bestAvailableBusy || this.baReopen)
6169
6339
  );
6170
6340
  this.els.foot?.classList.toggle("empty", count === 0);
6171
6341
  if (this.els.seatSummary) {
6172
- this.els.seatSummary.textContent = count ? `${count} selected` : "";
6342
+ this.els.seatSummary.textContent = count ? tCount("picker.seatsSelectedCount", count) : "";
6173
6343
  }
6174
6344
  this.syncCta(count, pendingCount);
6175
6345
  if (this.hold) {
6176
6346
  const securedCount = heldCount || this.hold.seats?.length || 0;
6177
6347
  if (this.els.holdTitle) {
6178
- this.els.holdTitle.textContent = `${securedCount} secured`;
6348
+ this.els.holdTitle.textContent = t2("picker.securedCount", { count: securedCount });
6179
6349
  }
6180
6350
  if (this.els.holdCopy) {
6181
- this.els.holdCopy.textContent = pendingCount ? `${pendingCount} more selected` : "Checkout timer running";
6351
+ this.holdCopyPending = pendingCount > 0;
6352
+ this.els.holdCopy.textContent = pendingCount ? t2("picker.moreSelectedCount", { count: pendingCount }) : this.holdReassuranceText();
6182
6353
  }
6183
6354
  const change = this.els.holdChange;
6184
6355
  if (change) {
6185
6356
  change.disabled = this.releasingHold;
6186
- change.textContent = this.releasingHold ? "Releasing\u2026" : "Change";
6357
+ change.textContent = this.releasingHold ? this.tf("picker.releasingEllipsis", "Releasing\u2026") : this.tf("picker.change", "Change");
6187
6358
  }
6188
6359
  }
6189
6360
  if (count !== previousCount) this.animateOnce(this.els.count, "sl-value-pop", 380);
@@ -6192,21 +6363,39 @@ var SeatPicker = class _SeatPicker {
6192
6363
  this.animateOnce(this.els.cta, "sl-ready", 520);
6193
6364
  if (this.sideCollapsed && this.root?.dataset.layout !== "narrow") this.setPanelCollapsed(false);
6194
6365
  }
6195
- if (this.els.peek) {
6196
- if (count) {
6197
- const holding = !!this.hold && !pendingCount;
6198
- 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>`;
6199
- } else if (this.salesClosed) {
6200
- this.els.peek.innerHTML = `<span>${this.tf("picker.salesClosedPill", "Sales are closed")}</span>`;
6201
- } else {
6202
- const prices = (this.controller.doc?.categories ?? []).map((c) => this.catPrice(c)).filter((p) => p != null);
6203
- 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>`;
6204
- }
6205
- }
6366
+ this.renderPeek(count, total, pendingCount);
6206
6367
  this.lastTrayCount = count;
6207
6368
  this.lastTrayTotal = total;
6208
6369
  this.opts.onSelectionChange?.(seats);
6209
6370
  }
6371
+ /**
6372
+ * The bottom-sheet's collapsed one-liner. Called from syncTray on every
6373
+ * repaint AND from setCtaPhase: while the widget is securing seats or
6374
+ * opening checkout, the peek is the phone buyer's ONLY line of sight, and a
6375
+ * pill still reading "Continue" at the moment money is about to move is the
6376
+ * emotional trapdoor the audit flagged. The phase lines restate what is
6377
+ * secured and promise no charge yet.
6378
+ */
6379
+ renderPeek(count, total, pendingCount) {
6380
+ if (!this.els.peek) return;
6381
+ if (count && this.ctaPhase === "holding") {
6382
+ this.els.peek.innerHTML = `<span>${this.tf("picker.securingSeats", "Securing your seats\u2026")}</span>`;
6383
+ return;
6384
+ }
6385
+ if (count && this.ctaPhase === "checkout") {
6386
+ this.els.peek.innerHTML = `<span>${t2("picker.peekSecured", { count, total: this.money(total) })}</span>`;
6387
+ return;
6388
+ }
6389
+ if (count) {
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>`;
6392
+ } else if (this.salesClosed) {
6393
+ this.els.peek.innerHTML = `<span>${this.tf("picker.salesClosedPill", "Sales are closed")}</span>`;
6394
+ } else {
6395
+ const prices = (this.controller.doc?.categories ?? []).map((c) => this.catPrice(c)).filter((p) => p != null);
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>`;
6397
+ }
6398
+ }
6210
6399
  async removeHeldLabel(label, chip) {
6211
6400
  if (!label || this.releasingLabels.has(label)) return false;
6212
6401
  this.releasingLabels.add(label);
@@ -6217,7 +6406,7 @@ var SeatPicker = class _SeatPicker {
6217
6406
  const preserveAcrossNavigation = this.handedOff;
6218
6407
  const released = await this.controller.releaseLabels([label]);
6219
6408
  if (!released) {
6220
- this.toast(`Couldn't remove ${label}. Your hold is unchanged.`, "error");
6409
+ this.toast(t2("picker.couldNotRemoveLabel", { label }), "error");
6221
6410
  return false;
6222
6411
  }
6223
6412
  const remaining = this.controller.currentHold();
@@ -6233,7 +6422,7 @@ var SeatPicker = class _SeatPicker {
6233
6422
  }
6234
6423
  this.syncTray();
6235
6424
  this.emitHoldChange();
6236
- this.toast(`${label} removed from your hold.`, "success");
6425
+ this.toast(t2("picker.labelRemovedFromHold", { label }), "success");
6237
6426
  return true;
6238
6427
  } finally {
6239
6428
  this.releasingLabels.delete(label);
@@ -6247,23 +6436,23 @@ var SeatPicker = class _SeatPicker {
6247
6436
  const button = this.els.holdChange;
6248
6437
  if (button) {
6249
6438
  button.disabled = true;
6250
- button.textContent = "Releasing\u2026";
6439
+ button.textContent = this.tf("picker.releasingEllipsis", "Releasing\u2026");
6251
6440
  }
6252
6441
  try {
6253
6442
  await this.release();
6254
- 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");
6255
6444
  } finally {
6256
6445
  this.releasingHold = false;
6257
6446
  if (button?.isConnected) {
6258
6447
  button.disabled = false;
6259
- button.textContent = "Change";
6448
+ button.textContent = this.tf("picker.change", "Change");
6260
6449
  }
6261
6450
  }
6262
6451
  }
6263
6452
  async handleCta() {
6264
6453
  if (this.salesClosed) return;
6265
6454
  if (this.totalTicketCount() > this.maxTickets) {
6266
- this.toast(`Remove tickets until your order has ${this.maxTickets} or fewer.`, "warning");
6455
+ this.toast(t2("picker.removeTicketsUntilOrFewer", { count: this.maxTickets }), "warning");
6267
6456
  return;
6268
6457
  }
6269
6458
  const committed = this.committedSelection();
@@ -6289,7 +6478,7 @@ var SeatPicker = class _SeatPicker {
6289
6478
  hold = h ? { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items } : hold;
6290
6479
  }
6291
6480
  if (!hold) {
6292
- 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");
6293
6482
  this.setCtaPhase("idle");
6294
6483
  this.syncTray();
6295
6484
  return;
@@ -6306,7 +6495,7 @@ var SeatPicker = class _SeatPicker {
6306
6495
  const problem = err;
6307
6496
  const labels = (problem.conflicts ?? []).map((conflict) => conflict.label).filter(Boolean).slice(0, 3);
6308
6497
  if (problem.reason === "event_closed") this.setSalesClosed(true);
6309
- 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.");
6310
6499
  this.toast(message, "error");
6311
6500
  this.setCtaPhase("idle");
6312
6501
  } finally {
@@ -6315,12 +6504,19 @@ var SeatPicker = class _SeatPicker {
6315
6504
  this.syncTray();
6316
6505
  }
6317
6506
  }
6507
+ /** "Yours for m:ss — you won't be charged yet." — the hold note's promise. */
6508
+ holdReassuranceText() {
6509
+ const ms = Math.max(0, this.holdExpiresAt - Date.now());
6510
+ const m = Math.floor(ms / 6e4);
6511
+ const s = String(Math.floor(ms % 6e4 / 1e3)).padStart(2, "0");
6512
+ return t2("picker.holdReassurance", { time: `${m}:${s}` });
6513
+ }
6318
6514
  startHoldTimer(expiresAt) {
6319
6515
  this.stopHoldTimer();
6320
6516
  this.holdExpiresAt = expiresAt;
6321
6517
  if (this.hold) this.rememberHold(this.hold);
6322
6518
  const pill = this.els.hold;
6323
- 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>`;
6324
6520
  const time = pill.querySelector('[data-ref="holdTime"]');
6325
6521
  this.els.holdNote?.classList.add("on");
6326
6522
  const tick = () => {
@@ -6328,6 +6524,9 @@ var SeatPicker = class _SeatPicker {
6328
6524
  const m = Math.floor(ms / 6e4);
6329
6525
  const s = String(Math.floor(ms % 6e4 / 1e3)).padStart(2, "0");
6330
6526
  if (time) time.textContent = `${m}:${s}`;
6527
+ if (!this.holdCopyPending && this.els.holdCopy) {
6528
+ this.els.holdCopy.textContent = this.holdReassuranceText();
6529
+ }
6331
6530
  pill.classList.add("on");
6332
6531
  pill.classList.toggle("is-expiring", ms > 0 && ms <= EXTEND_PROMPT_MS);
6333
6532
  this.setExtendPrompt(ms > 0 && ms <= EXTEND_PROMPT_MS, ms);
@@ -6347,8 +6546,10 @@ var SeatPicker = class _SeatPicker {
6347
6546
  setExtendPrompt(show, ms) {
6348
6547
  if (!this.extendEl) return;
6349
6548
  if (show && this.controller.currentHold() && !this.bookedShown) {
6350
- const secs = Math.ceil(ms / 1e3);
6351
- this.els.extendTxt.innerHTML = `Your seats are held for <b>0:${String(secs).padStart(2, "0")}</b>. Need more time?`;
6549
+ const totalSecs = Math.ceil(ms / 1e3);
6550
+ const m = Math.floor(totalSecs / 60);
6551
+ const s = String(totalSecs % 60).padStart(2, "0");
6552
+ this.els.extendTxt.innerHTML = t2("picker.seatsHeldForNeedMoreTime", { time: `${m}:${s}` });
6352
6553
  this.extendEl.classList.add("on");
6353
6554
  } else {
6354
6555
  this.extendEl.classList.remove("on");
@@ -6358,7 +6559,7 @@ var SeatPicker = class _SeatPicker {
6358
6559
  const btn = this.els.extendBtn;
6359
6560
  btn.disabled = true;
6360
6561
  const prev = btn.textContent;
6361
- btn.textContent = "Adding\u2026";
6562
+ btn.textContent = this.tf("picker.addingEllipsis", "Adding\u2026");
6362
6563
  try {
6363
6564
  const h = await this.controller.extendHold(this.opts.holdTtlMs);
6364
6565
  if (h) {
@@ -6367,13 +6568,13 @@ var SeatPicker = class _SeatPicker {
6367
6568
  this.extendEl?.classList.remove("on");
6368
6569
  this.rememberHold(this.hold);
6369
6570
  this.emitHoldChange();
6370
- 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");
6371
6572
  } else {
6372
- 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");
6373
6574
  }
6374
6575
  } catch (err) {
6375
6576
  this.opts.onError?.(err);
6376
- 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");
6377
6578
  } finally {
6378
6579
  btn.disabled = false;
6379
6580
  btn.textContent = prev;
@@ -6400,7 +6601,7 @@ var SeatPicker = class _SeatPicker {
6400
6601
  this.forgetHold();
6401
6602
  const n = handoff.lineItems.reduce((sum, i) => sum + i.quantity, 0);
6402
6603
  if (this.els.bookedSub) {
6403
- 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.")}`;
6404
6605
  }
6405
6606
  this.bookedEl?.classList.add("on");
6406
6607
  this.opts.onBooked?.(handoff);
@@ -6482,7 +6683,7 @@ var SeatPicker = class _SeatPicker {
6482
6683
  ({ mountCheckout } = await loadHostedCheckout());
6483
6684
  } catch (err) {
6484
6685
  this.opts.onError?.(err);
6485
- 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");
6486
6687
  this.setCtaPhase("idle");
6487
6688
  return;
6488
6689
  }
@@ -6597,9 +6798,9 @@ var SeatPicker = class _SeatPicker {
6597
6798
  rowTypeWord(details) {
6598
6799
  const authored = details?.displayType?.trim() || details?.rowType?.trim();
6599
6800
  if (authored) return authored;
6600
- if (details?.objectType === "table") return "Table";
6601
- if (details?.objectType === "booth") return "Booth";
6602
- 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");
6603
6804
  }
6604
6805
  rowShort(details) {
6605
6806
  const row = details?.rowLabel;
@@ -6622,7 +6823,7 @@ var SeatPicker = class _SeatPicker {
6622
6823
  const isGroupedTable = details.objectType === "table" && !!details.bookingMode;
6623
6824
  const isBooth = details.objectType === "booth";
6624
6825
  const hasLoc = details.sectionLabel || details.rowLabel || details.seatNumber;
6625
- 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>`;
6626
6827
  const statusLine = details.status === "free" ? "" : `<div class="sl-tip-status">${details.status === "held" ? t2("map.statusHeld") : t2("map.statusTaken")}</div>`;
6627
6828
  const limited = this.limitedViewLabel(details.commercial);
6628
6829
  const cxLine = limited ? `<div class="sl-tip-cx"><span class="g" aria-hidden="true">\u25D0</span>${esc(limited)}</div>` : "";
@@ -6913,7 +7114,7 @@ var SeatPicker = class _SeatPicker {
6913
7114
  const identity = details?.displayLabel ?? seat.displayLabel ?? seat.label;
6914
7115
  const section = details?.sectionLabel;
6915
7116
  const row = this.rowShort(details);
6916
- 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 ");
6917
7118
  const copy = status === "held" ? {
6918
7119
  title: this.tf("picker.temporarilyHeld", "Temporarily held"),
6919
7120
  message: this.tf("picker.heldSeatExplanation", "Another buyer is holding this seat. It may become available again.")
@@ -7009,7 +7210,7 @@ var SeatPicker = class _SeatPicker {
7009
7210
  chip = document.createElement("div");
7010
7211
  chip.className = "sl-view3d-compare-saved";
7011
7212
  chip.setAttribute("role", "group");
7012
- chip.setAttribute("aria-label", "Saved seat comparison");
7213
+ chip.setAttribute("aria-label", this.tf("picker.savedSeatComparison", "Saved seat comparison"));
7013
7214
  const main2 = document.createElement("button");
7014
7215
  main2.type = "button";
7015
7216
  main2.className = "main";
@@ -7021,7 +7222,7 @@ var SeatPicker = class _SeatPicker {
7021
7222
  clear.type = "button";
7022
7223
  clear.className = "clear";
7023
7224
  clear.textContent = "\xD7";
7024
- clear.setAttribute("aria-label", "Clear saved seat comparison");
7225
+ clear.setAttribute("aria-label", this.tf("picker.clearSavedSeatComparison", "Clear saved seat comparison"));
7025
7226
  clear.addEventListener("click", () => this.clearView3dComparison());
7026
7227
  chip.append(main2, clear);
7027
7228
  overlay.appendChild(chip);
@@ -7030,8 +7231,8 @@ var SeatPicker = class _SeatPicker {
7030
7231
  const count = this.view3dCompareSeatIds.length;
7031
7232
  const main = chip.querySelector(".main");
7032
7233
  if (main) {
7033
- main.textContent = count > 1 ? `Compare ${count}` : "1 seat saved";
7034
- 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"));
7035
7236
  }
7036
7237
  }
7037
7238
  view3dComparisonSnapshot(seatId) {
@@ -7308,8 +7509,7 @@ var SeatPicker = class _SeatPicker {
7308
7509
  const wantFloors = floors.length > 1;
7309
7510
  const wantZones = zones.length > 1;
7310
7511
  const wantSections = sections.length > 1;
7311
- const wantLocator = allSections.length > 0 && handle.rows().length > 0;
7312
- if (!wantFloors && !wantZones && !wantSections && !wantLocator) return;
7512
+ if (!wantFloors && !wantZones && !wantSections) return;
7313
7513
  const nav = document.createElement("div");
7314
7514
  nav.className = "sl-view3d-nav";
7315
7515
  const finderToggle = document.createElement("button");
@@ -7319,7 +7519,7 @@ var SeatPicker = class _SeatPicker {
7319
7519
  const setFinderOpen = (open) => {
7320
7520
  nav.classList.toggle("is-open", open);
7321
7521
  finderToggle.setAttribute("aria-expanded", String(open));
7322
- 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");
7323
7523
  };
7324
7524
  finderToggle.addEventListener("click", () => setFinderOpen(!nav.classList.contains("is-open")));
7325
7525
  nav.addEventListener("keydown", (event) => {
@@ -7356,7 +7556,7 @@ var SeatPicker = class _SeatPicker {
7356
7556
  row.appendChild(b);
7357
7557
  };
7358
7558
  add(this.tf("picker.allLevels", "All levels"), null);
7359
- 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);
7360
7560
  nav.appendChild(row);
7361
7561
  }
7362
7562
  if (wantZones || wantSections) {
@@ -7399,86 +7599,6 @@ var SeatPicker = class _SeatPicker {
7399
7599
  }
7400
7600
  nav.appendChild(row);
7401
7601
  }
7402
- if (wantLocator) {
7403
- const locator = document.createElement("div");
7404
- locator.className = "sl-view3d-locator";
7405
- locator.setAttribute("role", "group");
7406
- locator.setAttribute("aria-label", "Find an exact seat in 3D");
7407
- const sectionSelect = document.createElement("select");
7408
- sectionSelect.setAttribute("aria-label", "Choose section in 3D");
7409
- const rowSelect = document.createElement("select");
7410
- rowSelect.setAttribute("aria-label", "Choose row in 3D");
7411
- const seatSelect = document.createElement("select");
7412
- seatSelect.setAttribute("aria-label", "Choose seat in 3D");
7413
- const view = document.createElement("button");
7414
- view.type = "button";
7415
- view.textContent = "Inspect seat";
7416
- view.disabled = true;
7417
- const fill = (select, placeholder, entries) => {
7418
- select.replaceChildren();
7419
- const first = document.createElement("option");
7420
- first.value = "";
7421
- first.textContent = placeholder;
7422
- select.appendChild(first);
7423
- for (const entry of entries) {
7424
- const option = document.createElement("option");
7425
- option.value = entry.id;
7426
- option.textContent = entry.label;
7427
- select.appendChild(option);
7428
- }
7429
- select.value = "";
7430
- };
7431
- fill(sectionSelect, "1. Section", allSections.map((section) => ({
7432
- id: section.id,
7433
- label: `${section.label} \xB7 ${section.seatCount.toLocaleString()} seats`
7434
- })));
7435
- fill(rowSelect, "2. Row", []);
7436
- fill(seatSelect, "3. Seat", []);
7437
- rowSelect.disabled = true;
7438
- seatSelect.disabled = true;
7439
- sectionSelect.addEventListener("change", () => {
7440
- const sectionId = sectionSelect.value;
7441
- view.disabled = true;
7442
- fill(seatSelect, "3. Seat", []);
7443
- seatSelect.disabled = true;
7444
- if (!sectionId || !handle.focusSection(sectionId)) {
7445
- fill(rowSelect, "2. Row", []);
7446
- rowSelect.disabled = true;
7447
- return;
7448
- }
7449
- const rows = handle.rows(sectionId);
7450
- fill(rowSelect, "2. Row", rows.map((row) => ({
7451
- id: row.id,
7452
- label: `${row.label} \xB7 ${row.seatCount} seats`
7453
- })));
7454
- rowSelect.disabled = rows.length === 0;
7455
- });
7456
- rowSelect.addEventListener("change", () => {
7457
- const rowId = rowSelect.value;
7458
- view.disabled = true;
7459
- if (!rowId || !handle.focusRow(rowId)) {
7460
- fill(seatSelect, "3. Seat", []);
7461
- seatSelect.disabled = true;
7462
- return;
7463
- }
7464
- const seats = handle.seatsInRow(rowId);
7465
- fill(seatSelect, "3. Seat", seats);
7466
- seatSelect.disabled = seats.length === 0;
7467
- });
7468
- seatSelect.addEventListener("change", () => {
7469
- const seatId = seatSelect.value;
7470
- view.disabled = !seatId;
7471
- });
7472
- view.addEventListener("click", () => {
7473
- const seatId = seatSelect.value;
7474
- if (seatId) {
7475
- setFinderOpen(false);
7476
- this.onView3dSeatPick(seatId);
7477
- }
7478
- });
7479
- locator.append(sectionSelect, rowSelect, seatSelect, view);
7480
- nav.appendChild(locator);
7481
- }
7482
7602
  overlay.appendChild(nav);
7483
7603
  }
7484
7604
  async enter3d(flySeatId) {
@@ -7494,7 +7614,7 @@ var SeatPicker = class _SeatPicker {
7494
7614
  const overlay = document.createElement("div");
7495
7615
  overlay.className = "sl-view3d";
7496
7616
  overlay.setAttribute("role", "group");
7497
- overlay.setAttribute("aria-label", "Interactive 3D venue view");
7617
+ overlay.setAttribute("aria-label", this.tf("picker.interactive3dVenueView", "Interactive 3D venue view"));
7498
7618
  const back = document.createElement("button");
7499
7619
  back.type = "button";
7500
7620
  back.className = "sl-view3d-back";
@@ -7520,7 +7640,7 @@ var SeatPicker = class _SeatPicker {
7520
7640
  fullscreen.type = "button";
7521
7641
  fullscreen.className = "sl-view3d-fs";
7522
7642
  fullscreen.textContent = "\u26F6";
7523
- fullscreen.setAttribute("aria-label", "Full screen");
7643
+ fullscreen.setAttribute("aria-label", this.tf("picker.fullScreen", "Full screen"));
7524
7644
  fullscreen.setAttribute("aria-pressed", String(!!document.fullscreenElement || this.fsFallback || this.framedFs));
7525
7645
  fullscreen.addEventListener("click", () => this.toggleFullscreen());
7526
7646
  overlay.appendChild(fullscreen);
@@ -7557,7 +7677,7 @@ var SeatPicker = class _SeatPicker {
7557
7677
  onSeatInspect: (id) => this.onView3dSeatPick(id),
7558
7678
  onSectionFocusChange: (sectionId) => {
7559
7679
  const select = overlay.querySelector(
7560
- 'select[aria-label="Choose section in 3D"]'
7680
+ 'select[data-locator="section"]'
7561
7681
  );
7562
7682
  const next = sectionId ?? "";
7563
7683
  if (!select || select.value === next) return;
@@ -7654,7 +7774,7 @@ var SeatPicker = class _SeatPicker {
7654
7774
  if (button) {
7655
7775
  button.disabled = true;
7656
7776
  button.classList.add("sl-busy");
7657
- 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")}`;
7658
7778
  }
7659
7779
  try {
7660
7780
  const h = await this.controller.bestAvailable(qty, categoryKey, { ...opts, ttlMs: this.opts.holdTtlMs });
@@ -7676,7 +7796,7 @@ var SeatPicker = class _SeatPicker {
7676
7796
  } catch (err) {
7677
7797
  this.opts.onError?.(err);
7678
7798
  const reason = err?.reason;
7679
- 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.");
7680
7800
  this.toast(message, "error");
7681
7801
  return null;
7682
7802
  } finally {
@@ -7705,7 +7825,7 @@ var SeatPicker = class _SeatPicker {
7705
7825
  }
7706
7826
  }
7707
7827
  if (!released) {
7708
- 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");
7709
7829
  return;
7710
7830
  }
7711
7831
  this.hold = null;