@seatlayer/js 0.15.1 → 0.16.1

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
@@ -476,24 +476,26 @@ var CSS = `
476
476
  .sl-body{display:flex;flex:1;min-height:0}
477
477
  .sl-map{position:relative;flex:1;min-width:0}
478
478
  .sl-map-host{position:absolute;inset:0}
479
- .sl-side{width:300px;flex:none;border-left:1px solid var(--sl-line);display:flex;flex-direction:column;min-height:0;overflow-y:auto}
479
+ .sl-side{width:300px;flex:none;border-left:1px solid var(--sl-line);display:flex;flex-direction:column;min-height:0;overflow:hidden}
480
480
 
481
481
  /* narrow (container < 640px): map-first \u2014 the map claims ~80-85% of the
482
482
  container and the side panel becomes a PEEKING bottom sheet (AXS/Ticketmaster
483
483
  mobile pattern). data-sheet on the root: "peek" (default: grab handle + one
484
- summary line) / "open" (\u226450%, swipe up or auto-expand on first selection).
484
+ summary line) / "open" (room for rows + checkout, swipe up to open).
485
485
  Swipe handling lives on the sheet head ONLY \u2014 never the map host, so the
486
486
  map's raw-pointer gesture pipeline is untouched. */
487
487
  .sl-picker[data-layout="narrow"] .sl-body{flex-direction:column}
488
488
  .sl-picker[data-layout="narrow"] .sl-map{min-height:0;flex:1}
489
489
  .sl-picker[data-layout="narrow"] .sl-side{width:100%;border-left:0;border-top:1px solid var(--sl-line);
490
- flex:none;height:50%;transition:height .3s cubic-bezier(.2,.8,.2,1)}
490
+ flex:none;height:min(72%,480px);overflow:hidden;transition:height .3s cubic-bezier(.2,.8,.2,1);overscroll-behavior:contain}
491
+ .sl-picker[data-layout="narrow"][data-sheet="open"][data-has-selection="false"] .sl-side{height:min(252px,52%)}
491
492
  .sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-side{height:86px;overflow:hidden}
492
493
  .sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-side > :not(.sl-sheet-head){display:none}
493
- .sl-picker[data-layout="narrow"] .sl-tray{flex:none}
494
- .sl-picker[data-layout="narrow"] .sl-foot{position:sticky;bottom:0;background:var(--sl-bg)}
494
+ .sl-picker[data-layout="narrow"] .sl-tray{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}
495
+ .sl-picker[data-layout="narrow"] .sl-foot{position:static;background:var(--sl-bg)}
496
+ .sl-picker[data-layout="narrow"] .sl-foot.empty{display:none}
495
497
  .sl-picker[data-layout="narrow"] .sl-sheet-head{order:0}
496
- .sl-picker[data-layout="narrow"] .sl-seats-sec{order:1}
498
+ .sl-picker[data-layout="narrow"] .sl-seats-sec{display:none}
497
499
  .sl-picker[data-layout="narrow"] .sl-tray{order:2}
498
500
  .sl-picker[data-layout="narrow"] .sl-filtersec{order:3}
499
501
  .sl-picker[data-layout="narrow"] .sl-filters{order:4}
@@ -501,13 +503,21 @@ var CSS = `
501
503
  .sl-picker[data-layout="narrow"] .sl-pricef{order:6}
502
504
  .sl-picker[data-layout="narrow"] .sl-prices{order:7}
503
505
  .sl-picker[data-layout="narrow"] .sl-foot{order:8}
506
+ .sl-picker[data-layout="narrow"] .sl-tray-hint,
507
+ .sl-picker[data-layout="narrow"] .sl-filtersec,
508
+ .sl-picker[data-layout="narrow"] .sl-filters,
509
+ .sl-picker[data-layout="narrow"] .sl-prices-sec,
510
+ .sl-picker[data-layout="narrow"] .sl-prices{display:none!important}
511
+ .sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filtersec,
512
+ .sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filters,
513
+ .sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-prices-sec{display:none}
504
514
  /* touch chrome: pinch-zoom exists \u2014 hide +/\u2212 on the sheet layout (keep fit) */
505
515
  .sl-picker[data-layout="narrow"] .sl-zoom [data-ref="zin"],
506
516
  .sl-picker[data-layout="narrow"] .sl-zoom [data-ref="zout"]{display:none}
507
517
 
508
518
  /* bottom-sheet head: grab handle + one-line summary (narrow only). The WHOLE
509
519
  head is the tap/swipe toggle target (min 44px), so it reads as one control. */
510
- .sl-sheet-head{display:none;flex-direction:column;justify-content:center;padding:6px 16px 8px;min-height:44px;
520
+ .sl-sheet-head{display:none;flex-direction:column;justify-content:center;padding:6px 12px 8px;min-height:56px;
511
521
  cursor:pointer;touch-action:none;user-select:none;-webkit-user-select:none;flex:none}
512
522
  .sl-picker[data-layout="narrow"] .sl-sheet-head{display:flex}
513
523
  .sl-sheet-grab{width:36px;height:4px;border-radius:999px;background:var(--sl-muted);opacity:.55;margin:2px auto 7px}
@@ -522,54 +532,72 @@ var CSS = `
522
532
  /* state chevron: points UP while peeking, rotates to point DOWN when open.
523
533
  Base keeps an explicit rotate(0) \u2014 transitioning to/from a bare 'none' leaves
524
534
  the value stuck in some engines, so both endpoints must be real transforms. */
525
- .sl-sheet-chevron{flex:none;display:flex;align-items:center;justify-content:center;color:var(--sl-muted);
526
- transform:rotate(0deg);transition:transform .22s ease}
527
- .sl-sheet-chevron svg{width:18px;height:18px;stroke:currentColor;stroke-width:2.4;fill:none;
535
+ .sl-sheet-toggle{width:44px;height:44px;margin:-8px -8px -8px 0;border-radius:999px;flex:none;display:flex;
536
+ align-items:center;justify-content:center;color:var(--sl-muted);transition:color .15s,background .15s}
537
+ .sl-sheet-toggle:hover,.sl-sheet-toggle:focus-visible{color:var(--sl-text);background:color-mix(in srgb,var(--sl-line) 44%,transparent)}
538
+ .sl-sheet-toggle svg{width:21px;height:21px;stroke:currentColor;stroke-width:2.4;fill:none;
528
539
  stroke-linecap:round;stroke-linejoin:round}
529
- .sl-picker[data-sheet="open"] .sl-sheet-chevron{transform:rotate(180deg)}
540
+ .sl-sheet-toggle svg{transform:rotate(0deg);transition:transform .24s cubic-bezier(.2,.8,.2,1)}
541
+ .sl-picker[data-sheet="open"] .sl-sheet-toggle svg{transform:rotate(180deg)}
530
542
 
531
543
  /* consolidated Filters row inside the sheet (a11y chips + colorblind toggle
532
544
  dock here on narrow; they live on the map / zoom column on wide) */
533
545
  .sl-filtersec{display:none}
534
546
  .sl-filters{display:none;gap:6px;flex-wrap:wrap;align-items:center;padding:2px 16px 10px}
535
- .sl-picker[data-layout="narrow"] .sl-filtersec.has{display:block}
536
- .sl-picker[data-layout="narrow"] .sl-filters.has{display:flex}
547
+ .sl-picker[data-layout="narrow"] .sl-filtersec.has,
548
+ .sl-picker[data-layout="narrow"] .sl-filters.has{display:none}
549
+ .sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filtersec.has,
550
+ .sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filters.has{display:none}
537
551
  .sl-cbbtn{width:32px;height:32px;border-radius:999px;background:var(--sl-surface);border:1px solid var(--sl-line);
538
552
  color:var(--sl-text);display:flex;align-items:center;justify-content:center;transition:border-color .15s}
539
553
  .sl-cbbtn:hover{border-color:var(--sl-muted)}
540
554
  .sl-cbbtn svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
541
555
 
542
- /* price panel */
543
- .sl-sec{padding:14px 16px 4px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
544
- .sl-prices{padding:4px 16px 10px;border-bottom:1px solid var(--sl-line)}
545
- .sl-price-row{display:flex;align-items:center;gap:8px;padding:5px 0;font-size:13px}
556
+ /* price panel \u2014 one compact filter control replaces the wrapping price-chip row. */
557
+ .sl-sec{padding:14px 14px 4px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
558
+ .sl-prices-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
559
+ .sl-price-select{min-height:32px;max-width:130px;padding:5px 28px 5px 9px;border:1px solid var(--sl-line);border-radius:9px;
560
+ background:var(--sl-surface);color:var(--sl-text);font:inherit;font-size:11px;font-weight:750;letter-spacing:0;text-transform:none}
561
+ .sl-prices{padding:5px 14px 10px;border-bottom:1px solid var(--sl-line)}
562
+ .sl-prices-sec,.sl-prices,.sl-seats-sec{flex:none}
563
+ .sl-price-row{display:flex;align-items:center;gap:7px;min-height:28px;font-size:12px}
546
564
  .sl-dot{width:9px;height:9px;border-radius:50%;flex:none}
547
565
  .sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
548
566
  .sl-price-left{font-size:11px;color:var(--sl-muted);font-variant-numeric:tabular-nums}
549
567
  .sl-price-amt{font-weight:800;font-variant-numeric:tabular-nums}
550
- .sl-status-key{display:flex;gap:12px;flex-wrap:wrap;padding:8px 16px 12px;border-bottom:1px solid var(--sl-line);color:var(--sl-muted);font-size:11px}
568
+ .sl-status-key{display:flex;gap:13px;flex-wrap:wrap;padding:8px 0 2px;margin-top:5px;border-top:1px solid var(--sl-line);color:var(--sl-muted);font-size:10.5px}
551
569
  .sl-status-item{display:inline-flex;align-items:center;gap:6px}
552
570
  .sl-status-icon{width:17px;height:17px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;
553
- color:#fff;background:#6b7280;font-size:9px;font-weight:900;line-height:1}
554
- .sl-status-icon.sold{background:#374151;font-size:14px}
571
+ color:#fff;background:#6b7280;line-height:1}
572
+ .sl-status-icon svg{width:10px;height:10px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
573
+ .sl-status-icon.sold{background:#8b93a0}
574
+ .sl-status-icon.sold svg{width:11px;height:11px;stroke-width:2.4}
555
575
 
556
576
  /* tray */
557
- .sl-tray{flex:1;padding:10px 16px;display:flex;flex-direction:column;gap:8px;min-height:0}
577
+ .sl-seats-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
578
+ .sl-seat-summary{font-size:10px;letter-spacing:0;text-transform:none;white-space:nowrap}
579
+ .sl-tray{flex:1;padding:10px 14px 14px;display:flex;flex-direction:column;gap:7px;min-height:0;overflow-y:auto;
580
+ overscroll-behavior:contain;scrollbar-gutter:stable}
558
581
  .sl-tray-hint{font-size:12.5px;color:var(--sl-muted);line-height:1.5}
559
- .sl-chip{display:flex;align-items:center;gap:9px;padding:9px 11px;border:1px solid var(--sl-line);
560
- border-radius:var(--sl-r-sm);background:var(--sl-surface);font-size:13px;transform-origin:center}
582
+ .sl-chip{position:relative;display:grid;grid-template-columns:24px minmax(0,1fr) auto 30px;align-items:center;gap:8px;
583
+ min-height:53px;padding:7px 7px 7px 9px;border:1px solid var(--sl-line);border-radius:var(--sl-r-sm);
584
+ background:var(--sl-surface);font-size:13px;transform-origin:center;transition:border-color .15s,background .15s}
585
+ .sl-chip:hover{border-color:color-mix(in srgb,var(--sl-accent) 38%,var(--sl-line))}
561
586
  .sl-chip.sl-enter{animation:slChipIn .38s cubic-bezier(.2,.8,.2,1) both}
562
587
  .sl-chip.sl-leave{pointer-events:none;animation:slChipOut .16s ease-in both}
563
588
  .sl-chip.sl-held{border-color:var(--sl-line);background:color-mix(in srgb,var(--sl-accent) 7%,var(--sl-surface));
564
589
  box-shadow:inset 3px 0 0 color-mix(in srgb,var(--sl-accent) 72%,transparent)}
565
- .sl-chip b{font-weight:800;flex:none;min-width:max-content;white-space:nowrap}
566
- .sl-chip .cat{color:var(--sl-muted);font-size:11.5px;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
590
+ .sl-ticket-state{width:23px;height:23px;border-radius:999px;display:flex;align-items:center;justify-content:center;
591
+ background:var(--sl-accent);color:var(--sl-accent-ink)}
592
+ .sl-ticket-state.held{background:color-mix(in srgb,var(--sl-accent) 18%,var(--sl-surface));color:var(--sl-accent)}
593
+ .sl-ticket-state svg{width:13px;height:13px;stroke:currentColor;stroke-width:2.4;fill:none;stroke-linecap:round;stroke-linejoin:round}
594
+ .sl-chip-main{min-width:0}
595
+ .sl-chip b{display:block;font-weight:800;min-width:max-content;white-space:nowrap}
596
+ .sl-chip-sub{display:flex;align-items:center;gap:5px;min-width:0;margin-top:2px}
597
+ .sl-chip .cat{color:var(--sl-muted);font-size:10.5px;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
567
598
  .sl-chip .amt{font-weight:700;font-variant-numeric:tabular-nums;flex:none;white-space:nowrap}
568
- .sl-chip-state{flex:none;padding:3px 6px;border-radius:999px;background:var(--sl-accent);color:var(--sl-accent-ink);
569
- font-size:8.5px;line-height:1;font-weight:900;letter-spacing:.08em;text-transform:uppercase}
570
- .sl-chip-state.sl-pending{background:transparent;color:var(--sl-muted);border:1px solid var(--sl-line)}
571
- .sl-chip .rm{width:22px;height:22px;border-radius:999px;flex:none;display:flex;align-items:center;justify-content:center;color:var(--sl-muted)}
572
- .sl-chip .rm:hover{color:var(--sl-text)}
599
+ .sl-chip .rm{width:29px;height:29px;border-radius:8px;flex:none;display:flex;align-items:center;justify-content:center;color:var(--sl-muted)}
600
+ .sl-chip .rm:hover,.sl-chip .rm:focus-visible{color:var(--sl-accent);background:color-mix(in srgb,var(--sl-accent) 10%,transparent)}
573
601
  .sl-chip .rm svg{width:11px;height:11px;stroke:currentColor;stroke-width:2.4;fill:none;stroke-linecap:round}
574
602
 
575
603
  /* GA rows */
@@ -584,16 +612,22 @@ var CSS = `
584
612
  .sl-ga-qty span{min-width:16px;text-align:center;font-weight:800;font-variant-numeric:tabular-nums}
585
613
 
586
614
  /* footer */
587
- .sl-foot{padding:12px 16px 14px;border-top:1px solid var(--sl-line);flex:none}
588
- .sl-hold-note{display:none;align-items:center;gap:9px;margin-bottom:10px;padding:9px 10px;border-radius:var(--sl-r-sm);
615
+ .sl-foot{position:relative;z-index:2;padding:12px 16px 14px;border-top:1px solid var(--sl-line);flex:none;
616
+ background:var(--sl-bg);box-shadow:0 -10px 24px -22px rgba(0,0,0,.72)}
617
+ .sl-hold-note{display:none;align-items:center;gap:7px;margin-bottom:8px;padding:7px 8px;border-radius:var(--sl-r-sm);
589
618
  border:1px solid var(--sl-line);background:color-mix(in srgb,var(--sl-accent) 7%,var(--sl-surface));
590
619
  box-shadow:inset 3px 0 0 color-mix(in srgb,var(--sl-accent) 72%,transparent);
591
620
  font-size:11.5px;line-height:1.35;color:var(--sl-muted)}
592
621
  .sl-hold-note.on{display:flex;animation:slNoticeIn .38s cubic-bezier(.2,.8,.2,1) both}
593
622
  .sl-hold-note svg{width:16px;height:16px;flex:none;stroke:var(--sl-accent);stroke-width:2.4;fill:none;
594
623
  stroke-linecap:round;stroke-linejoin:round}
595
- .sl-hold-note b{display:block;color:var(--sl-text);font-size:12px}
596
- .sl-hold-copy{display:block}
624
+ .sl-hold-note b{display:block;color:var(--sl-text);font-size:11.5px;white-space:nowrap}
625
+ .sl-hold-copy{display:block;white-space:nowrap;font-size:10.5px}
626
+ .sl-hold-note>span{flex:1;min-width:0}
627
+ .sl-hold-change{flex:none;min-height:30px;padding:5px 8px;border-radius:8px;border:1px solid var(--sl-line);
628
+ color:var(--sl-text);font-size:10.5px;font-weight:750;white-space:nowrap}
629
+ .sl-hold-change:hover,.sl-hold-change:focus-visible{border-color:var(--sl-accent);color:var(--sl-accent)}
630
+ .sl-hold-change:disabled{opacity:.58;cursor:wait}
597
631
  .sl-total{display:flex;justify-content:space-between;align-items:center;font-size:13px;margin-bottom:10px}
598
632
  .sl-total b{font-size:17px;font-variant-numeric:tabular-nums}
599
633
  .sl-value-pop{animation:slValuePop .32s cubic-bezier(.2,.8,.2,1)}
@@ -651,6 +685,9 @@ var CSS = `
651
685
  font-size:12.5px;font-weight:600;opacity:0;pointer-events:none;transition:opacity .22s,transform .22s;white-space:nowrap;
652
686
  overflow:hidden;text-overflow:ellipsis}
653
687
  .sl-toast.on{opacity:1;transform:translateY(0) scale(1)}
688
+ .sl-toast.has-action{pointer-events:auto;display:flex;align-items:center;gap:12px;padding-right:8px}
689
+ .sl-toast-action{min-height:30px;padding:5px 10px;border-radius:999px;background:var(--sl-accent);color:var(--sl-accent-ink);
690
+ font:inherit;font-weight:800}
654
691
  .sl-toast[data-tone="error"]{border-color:#ef4444}
655
692
  .sl-toast[data-tone="warning"]{border-color:var(--sl-accent)}
656
693
  .sl-toast[data-tone="success"]{border-color:#22c55e}
@@ -733,29 +770,49 @@ var CSS = `
733
770
  .sl-picker[data-layout="narrow"] .sl-confirm{left:50%!important;top:auto!important;bottom:14px;width:min(342px,calc(100% - 24px));
734
771
  transform:translateX(-50%);animation:slConfirmMobileIn .24s cubic-bezier(.2,.8,.2,1) both}
735
772
 
736
- /* best-available row */
737
- .sl-ba{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:8px;padding:9px 11px;border:1px solid var(--sl-line);border-radius:var(--sl-r-sm)}
738
- .sl-ba select{background:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:7px;
739
- font:inherit;font-size:12px;padding:7px 8px;min-width:0;width:100%;max-width:none}
740
- .sl-ba-qty{display:flex;align-items:center;gap:7px}
741
- .sl-ba-qty button{width:24px;height:24px;border-radius:999px;background:var(--sl-surface);border:1px solid var(--sl-line);
742
- font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:center}
773
+ /* Best available is a first-class shortcut, not an anonymous utility row. */
774
+ .sl-ba{position:relative;overflow:hidden;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:7px;
775
+ padding:13px;border:1px solid color-mix(in srgb,var(--sl-accent) 34%,var(--sl-line));border-radius:13px;
776
+ background:linear-gradient(135deg,color-mix(in srgb,var(--sl-accent) 5%,var(--sl-surface)),color-mix(in srgb,var(--sl-accent) 11%,var(--sl-surface)))}
777
+ .sl-ba::after{content:'\u2726';position:absolute;right:10px;top:3px;color:color-mix(in srgb,var(--sl-accent) 20%,transparent);font-size:42px;line-height:1}
778
+ .sl-ba-title,.sl-ba-copy,.sl-ba select,.sl-ba-qty,.sl-ba-go{position:relative;z-index:1}
779
+ .sl-ba-title{grid-column:1/-1;display:flex;align-items:center;gap:7px;font-size:13px;font-weight:850}
780
+ .sl-ba-title .spark{color:var(--sl-accent);font-size:16px}
781
+ .sl-ba-copy{grid-column:1/-1;margin:-4px 0 2px 23px;color:var(--sl-muted);font-size:10.5px;line-height:1.35}
782
+ .sl-ba-copy .narrow{display:none}
783
+ .sl-ba select{background:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:8px;
784
+ font:inherit;font-size:11px;padding:7px 8px;min-width:0;width:100%;max-width:none}
785
+ .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)}
786
+ .sl-ba-qty button{width:25px;height:25px;border-radius:7px;background:color-mix(in srgb,var(--sl-line) 35%,transparent);border:0;
787
+ font-size:14px;font-weight:800;display:flex;align-items:center;justify-content:center}
743
788
  .sl-ba-qty span{min-width:14px;text-align:center;font-weight:800}
744
- .sl-ba-go{grid-column:1/-1;width:100%;min-height:38px;padding:7px 12px;border-radius:9px;border:1px solid var(--sl-line);font-weight:800;font-size:12px;
745
- transition:border-color .15s,opacity .15s;display:flex;align-items:center;justify-content:center;gap:6px}
746
- .sl-ba-go:hover{border-color:var(--sl-muted)}
747
- .sl-ba-go:disabled{opacity:.62;cursor:wait}
789
+ .sl-picker .sl-ba-go{grid-column:1/-1;width:100%;min-height:37px;padding:7px 12px;border-radius:9px;background:var(--sl-accent);
790
+ color:var(--sl-accent-ink);font-weight:800;font-size:12px;transition:filter .15s,opacity .15s;display:flex;align-items:center;justify-content:center;gap:6px;
791
+ box-shadow:0 8px 18px color-mix(in srgb,var(--sl-accent) 18%,transparent)}
792
+ .sl-picker .sl-ba-go:hover{filter:brightness(1.06)}
793
+ .sl-picker .sl-ba-go:disabled{opacity:.62;cursor:wait}
794
+ .sl-ba-replace{grid-column:1/-1;padding:3px 0 1px}
795
+ .sl-ba-replace b{display:block;font-size:12.5px}
796
+ .sl-ba-replace span{display:block;margin-top:3px;color:var(--sl-muted);font-size:10.5px;line-height:1.35}
797
+ .sl-ba-actions{grid-column:1/-1;display:grid;grid-template-columns:1fr 1fr;gap:7px}
798
+ .sl-ba-actions button{min-height:36px;border-radius:9px;border:1px solid var(--sl-line);font-size:11.5px;font-weight:800}
799
+ .sl-ba-actions .replace{border-color:var(--sl-accent);background:var(--sl-accent);color:var(--sl-accent-ink)}
800
+ .sl-picker[data-layout="narrow"] .sl-ba{padding:11px}
801
+ .sl-picker[data-layout="narrow"] .sl-ba-copy .wide{display:none}
802
+ .sl-picker[data-layout="narrow"] .sl-ba-copy .narrow{display:inline}
748
803
 
749
804
  /* screen-reader live region */
750
805
  .sl-sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
751
806
 
752
807
  /* per-seat ticket-tier select + view-from-seat button in tray chips */
753
- .sl-chip .tier{background:var(--sl-bg);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:7px;
754
- font:inherit;font-size:11px;padding:3px 5px;max-width:130px;cursor:pointer}
755
- .sl-chip .view{width:24px;height:24px;border-radius:999px;flex:none;display:flex;align-items:center;justify-content:center;
756
- color:var(--sl-muted);transition:color .15s}
808
+ .sl-chip .tier{background:var(--sl-bg);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:6px;
809
+ font:inherit;font-size:10px;padding:2px 4px;min-width:0;max-width:100%;cursor:pointer}
810
+ .sl-chip .view{width:20px;height:20px;border-radius:999px;flex:none;display:flex;align-items:center;justify-content:center;
811
+ color:var(--sl-muted);opacity:.36;transition:color .15s,opacity .15s}
757
812
  .sl-chip .view:hover{color:var(--sl-text)}
758
- .sl-chip .view svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
813
+ .sl-chip:hover .view,.sl-chip .view:focus-visible{opacity:1;color:var(--sl-text)}
814
+ .sl-chip .view svg{width:12px;height:12px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
815
+ @media(pointer:coarse){.sl-chip .view{opacity:.58}}
759
816
 
760
817
  /* arena: LOD rung pills (flow within the top-center region) */
761
818
  .sl-rungs{display:none;background:var(--sl-surface);border:1px solid var(--sl-line);border-radius:999px;padding:3px}
@@ -842,10 +899,6 @@ var CSS = `
842
899
  .sl-minimap canvas{display:block}
843
900
  .sl-picker[data-layout="narrow"] .sl-minimap{display:none}
844
901
 
845
- /* F4 price-band filter \u2014 chip row in the side panel, above the price legend it
846
- * dims (keeps clear of the map's top-center rungs / top-left a11y chips). */
847
- .sl-pricef{display:flex;gap:6px;flex-wrap:wrap;padding:2px 16px 10px}
848
- .sl-pricef .sl-chip-f{padding:6px 11px;font-size:11.5px}
849
902
  /* F4 legend reflection: rows + counts for out-of-band categories read muted */
850
903
  .sl-price-row.sl-dim{opacity:.4}
851
904
  .sl-seccard-mix-item.sl-dim{opacity:.4}
@@ -935,6 +988,8 @@ var SeatPicker = class _SeatPicker {
935
988
  this.a11yFilter = "all";
936
989
  this.baQty = 2;
937
990
  this.baCat = "";
991
+ this.bestAvailableConfirm = false;
992
+ this.releasingHold = false;
938
993
  // arena / multi-floor / seat-view chrome
939
994
  this.rungsEl = null;
940
995
  this.floorsEl = null;
@@ -982,10 +1037,11 @@ var SeatPicker = class _SeatPicker {
982
1037
  this.opts = { ...options, confirmSelection: options.confirmSelection ?? true };
983
1038
  this.apiBase = (options.apiBase ?? DEFAULT_API_BASE2).replace(/\/+$/, "");
984
1039
  this.api = new PubApi(this.apiBase);
1040
+ this.maxTickets = Math.max(1, Math.floor(options.maxSelection ?? DEFAULT_MAX_SELECTION2));
985
1041
  this.controller = new PickerController2({
986
1042
  transport: this.api,
987
1043
  eventKey: options.event,
988
- maxSelection: options.maxSelection ?? DEFAULT_MAX_SELECTION2,
1044
+ maxSelection: this.maxTickets,
989
1045
  currency: options.currency,
990
1046
  flashOnLiveChange: true,
991
1047
  colorblindSafe: options.colorblindSafe,
@@ -1020,6 +1076,9 @@ var SeatPicker = class _SeatPicker {
1020
1076
  onDeselect: (seat) => {
1021
1077
  if (this.confirmSeat?.id === seat.id) this.dismissConfirm();
1022
1078
  },
1079
+ onSelectionLimit: () => {
1080
+ this.toast(`You can select up to ${this.maxTickets} tickets for this order.`, "warning");
1081
+ },
1023
1082
  onViewChange: () => {
1024
1083
  this.reanchorConfirm();
1025
1084
  this.syncRung();
@@ -1082,6 +1141,10 @@ var SeatPicker = class _SeatPicker {
1082
1141
  if (picker.confirmSeat) {
1083
1142
  e.preventDefault();
1084
1143
  picker.cancelConfirm();
1144
+ } else if (picker.bestAvailableConfirm) {
1145
+ e.preventDefault();
1146
+ picker.bestAvailableConfirm = false;
1147
+ picker.syncTray();
1085
1148
  } else {
1086
1149
  close();
1087
1150
  }
@@ -1105,10 +1168,17 @@ var SeatPicker = class _SeatPicker {
1105
1168
  this.root = root;
1106
1169
  mount.appendChild(root);
1107
1170
  root.addEventListener("keydown", (e) => {
1108
- if (e.key !== "Escape" || !this.confirmSeat) return;
1109
- e.preventDefault();
1110
- e.stopPropagation();
1111
- this.cancelConfirm();
1171
+ if (e.key !== "Escape") return;
1172
+ if (this.confirmSeat) {
1173
+ e.preventDefault();
1174
+ e.stopPropagation();
1175
+ this.cancelConfirm();
1176
+ } else if (this.bestAvailableConfirm) {
1177
+ e.preventDefault();
1178
+ e.stopPropagation();
1179
+ this.bestAvailableConfirm = false;
1180
+ this.syncTray();
1181
+ }
1112
1182
  });
1113
1183
  Object.entries(resolveTokens(void 0, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
1114
1184
  root.innerHTML = `
@@ -1141,21 +1211,22 @@ var SeatPicker = class _SeatPicker {
1141
1211
  <div class="sl-sheet-grab"></div>
1142
1212
  <div class="sl-sheet-bar">
1143
1213
  <div class="sl-sheet-peek" data-ref="peek"></div>
1144
- <span class="sl-sheet-chevron" aria-hidden="true">
1214
+ <button type="button" class="sl-sheet-toggle" data-ref="sheetToggle" aria-label="Open ticket panel" aria-expanded="false">
1145
1215
  <svg viewBox="0 0 24 24"><path d="M6 15l6-6 6 6"/></svg>
1146
- </span>
1216
+ </button>
1147
1217
  </div>
1148
1218
  </div>
1149
1219
  <div class="sl-sec sl-filtersec" data-ref="filtersSec">Filters</div>
1150
1220
  <div class="sl-filters" data-ref="filters"></div>
1151
- <div class="sl-sec sl-prices-sec" data-ref="pricesSec">Prices</div>
1221
+ <div class="sl-sec sl-prices-sec" data-ref="pricesSec"><span>Ticket prices</span></div>
1152
1222
  <div class="sl-prices" data-ref="prices"></div>
1153
- <div class="sl-sec sl-seats-sec">Your seats</div>
1223
+ <div class="sl-sec sl-seats-sec"><span>Your seats</span><span class="sl-seat-summary" data-ref="seatSummary"></span></div>
1154
1224
  <div class="sl-tray" data-ref="tray"></div>
1155
- <div class="sl-foot">
1225
+ <div class="sl-foot" data-ref="foot">
1156
1226
  <div class="sl-hold-note" data-ref="holdNote" role="status" aria-live="polite">
1157
1227
  <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg>
1158
1228
  <span><b data-ref="holdTitle">Seats secured</b><span class="sl-hold-copy" data-ref="holdCopy">Checkout timer is running.</span></span>
1229
+ <button type="button" class="sl-hold-change" data-ref="holdChange" aria-label="Release held tickets and choose different seats">Change</button>
1159
1230
  </div>
1160
1231
  <div class="sl-total"><span data-ref="count"></span><b data-ref="total"></b></div>
1161
1232
  <button type="button" class="sl-cta" data-ref="cta" disabled></button>
@@ -1184,9 +1255,17 @@ var SeatPicker = class _SeatPicker {
1184
1255
  this.els.zfit.addEventListener("click", () => this.controller.zoomToFit());
1185
1256
  const head = this.els.sheetHead;
1186
1257
  if (head) {
1258
+ const toggle = this.els.sheetToggle;
1187
1259
  const setSheet = (open) => {
1188
1260
  root.dataset.sheet = open ? "open" : "peek";
1261
+ toggle?.setAttribute("aria-expanded", String(open));
1262
+ toggle?.setAttribute("aria-label", open ? "Collapse ticket panel" : "Open ticket panel");
1189
1263
  };
1264
+ setSheet(root.dataset.sheet === "open");
1265
+ toggle?.addEventListener("click", (e) => {
1266
+ e.stopPropagation();
1267
+ setSheet(root.dataset.sheet !== "open");
1268
+ });
1190
1269
  let startY = 0;
1191
1270
  let swiped = false;
1192
1271
  let tracking = false;
@@ -1209,7 +1288,7 @@ var SeatPicker = class _SeatPicker {
1209
1288
  });
1210
1289
  head.addEventListener("pointerup", (e) => {
1211
1290
  if (tracking && !swiped && Math.abs(e.clientY - startY) < 6) {
1212
- if (!e.target.closest(".sl-seccard")) setSheet(root.dataset.sheet !== "open");
1291
+ if (!e.target.closest(".sl-seccard,.sl-sheet-toggle")) setSheet(root.dataset.sheet !== "open");
1213
1292
  }
1214
1293
  tracking = false;
1215
1294
  head.releasePointerCapture?.(e.pointerId);
@@ -1225,6 +1304,7 @@ var SeatPicker = class _SeatPicker {
1225
1304
  if (this.tipEl && this.tipEl.style.display !== "none") this.placeTooltip();
1226
1305
  });
1227
1306
  this.els.cta.addEventListener("click", () => void this.handleCta());
1307
+ this.els.holdChange?.addEventListener("click", () => void this.handleChangeSeats());
1228
1308
  const canvasHost = document.createElement("div");
1229
1309
  canvasHost.style.cssText = "position:absolute;inset:0";
1230
1310
  this.mapHost.appendChild(canvasHost);
@@ -1431,15 +1511,51 @@ var SeatPicker = class _SeatPicker {
1431
1511
  const heldItems = this.hold?.items ?? [];
1432
1512
  const heldLabels = new Set(heldItems.map((item) => item.label));
1433
1513
  const pendingSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).length;
1514
+ return pendingSeats + this.pendingGACount();
1515
+ }
1516
+ heldGACounts() {
1434
1517
  const heldGA = /* @__PURE__ */ new Map();
1435
- for (const item of heldItems.filter((candidate) => candidate.objectType === "ga")) {
1518
+ for (const item of (this.hold?.items ?? []).filter((candidate) => candidate.objectType === "ga")) {
1436
1519
  heldGA.set(item.objectId, (heldGA.get(item.objectId) ?? 0) + (item.quantity ?? 1));
1437
1520
  }
1438
- const pendingGA = [...this.gaQty.entries()].reduce(
1521
+ return heldGA;
1522
+ }
1523
+ pendingGACount() {
1524
+ const heldGA = this.heldGACounts();
1525
+ return [...this.gaQty.entries()].reduce(
1439
1526
  (sum, [areaId, qty]) => sum + Math.max(0, qty - (heldGA.get(areaId) ?? 0)),
1440
1527
  0
1441
1528
  );
1442
- return pendingSeats + pendingGA;
1529
+ }
1530
+ heldTicketCount() {
1531
+ return (this.hold?.items ?? []).reduce((sum, item) => sum + (item.quantity ?? 1), 0);
1532
+ }
1533
+ totalTicketCount() {
1534
+ const heldLabels = new Set((this.hold?.items ?? []).map((item) => item.label));
1535
+ const freshSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).length;
1536
+ return this.heldTicketCount() + freshSeats + this.pendingGACount();
1537
+ }
1538
+ /** Held tickets and standing quantities consume the same order-wide cap. */
1539
+ updateSelectionCapacity() {
1540
+ const heldLabels = new Set((this.hold?.items ?? []).map((item) => item.label));
1541
+ const selectedHeld = this.committedSelection().filter((seat) => heldLabels.has(seat.label)).length;
1542
+ const remaining = Math.max(0, this.maxTickets - this.heldTicketCount() - this.pendingGACount());
1543
+ this.controller.setMaxSelection(selectedHeld + remaining);
1544
+ }
1545
+ canAddTicket() {
1546
+ if (this.totalTicketCount() < this.maxTickets) return true;
1547
+ this.toast(`You can select up to ${this.maxTickets} tickets for this order.`, "warning");
1548
+ return false;
1549
+ }
1550
+ pendingGATotal(gaAreas) {
1551
+ const heldGA = /* @__PURE__ */ new Map();
1552
+ for (const item of (this.hold?.items ?? []).filter((candidate) => candidate.objectType === "ga")) {
1553
+ heldGA.set(item.objectId, (heldGA.get(item.objectId) ?? 0) + (item.quantity ?? 1));
1554
+ }
1555
+ return gaAreas.reduce(
1556
+ (sum, area) => sum + area.price * Math.max(0, (this.gaQty.get(area.id) ?? 0) - (heldGA.get(area.id) ?? 0)),
1557
+ 0
1558
+ );
1443
1559
  }
1444
1560
  syncCta(count = this.lastTrayCount, pending = this.pendingSelectionCount()) {
1445
1561
  const cta = this.els.cta;
@@ -1728,39 +1844,26 @@ var SeatPicker = class _SeatPicker {
1728
1844
  }
1729
1845
  return bands;
1730
1846
  }
1731
- /**
1732
- * Build the price-band chip row in the side panel, directly under the "Prices"
1733
- * header and above the legend it dims. Living in the panel (not a map overlay)
1734
- * keeps it clear of the top-center rung pills and top-left a11y chips, and it
1735
- * rides the bottom sheet on narrow layouts. Skipped when there's <2 bands.
1736
- */
1847
+ /** Build the compact price selector in the panel header. Choosing a band both
1848
+ * filters availability and smoothly frames the matching seats on the map. */
1737
1849
  buildPriceFilter() {
1738
1850
  if (!this.els.prices || !this.els.pricesSec) return;
1739
1851
  const bands = this.priceBands();
1740
1852
  if (bands.length < 2) return;
1741
- const wrap = document.createElement("div");
1742
- wrap.className = "sl-pricef";
1743
- wrap.setAttribute("role", "group");
1744
- wrap.setAttribute("aria-label", "Filter seats by price");
1745
- const mk = (band, label, on) => `<button type="button" class="sl-chip-f${on ? " on" : ""}" data-band="${band}">${label}</button>`;
1746
- wrap.innerHTML = mk("all", "All prices", true) + bands.map((bd) => mk(bd.id, bd.label, false)).join("");
1747
- this.els.pricesSec.after(wrap);
1748
- this.priceFilterEl = wrap;
1749
- wrap.querySelectorAll("button").forEach((btn) => {
1750
- btn.addEventListener("click", () => {
1751
- wrap.querySelectorAll("button").forEach((b) => b.classList.toggle("on", b === btn));
1752
- const id = btn.dataset.band;
1753
- if (id === "all") {
1754
- this.priceBandKeys = null;
1755
- this.controller.setCategoryFilter(null);
1756
- } else {
1757
- const bd = bands.find((x) => x.id === id);
1758
- this.priceBandKeys = bd ? new Set(bd.keys) : null;
1759
- this.controller.setCategoryFilter(bd ? bd.keys : null);
1760
- }
1761
- this.syncPrices();
1762
- if (this.lastSection) this.showSectionCard(this.lastSection);
1763
- });
1853
+ const select = document.createElement("select");
1854
+ select.className = "sl-price-select";
1855
+ select.setAttribute("aria-label", "Filter and focus seats by price");
1856
+ select.innerHTML = `<option value="all">All prices</option>` + bands.map((band) => `<option value="${band.id}">${band.label}</option>`).join("");
1857
+ this.els.pricesSec.appendChild(select);
1858
+ this.priceFilterEl = select;
1859
+ select.addEventListener("change", () => {
1860
+ const band = bands.find((candidate) => candidate.id === select.value);
1861
+ const keys = band?.keys ?? null;
1862
+ this.priceBandKeys = keys ? new Set(keys) : null;
1863
+ this.controller.setCategoryFilter(keys);
1864
+ this.controller.focusCategoryFilter(keys);
1865
+ this.syncPrices();
1866
+ if (this.lastSection) this.showSectionCard(this.lastSection);
1764
1867
  });
1765
1868
  }
1766
1869
  // ---- arena / multi-floor chrome -------------------------------------------
@@ -2160,7 +2263,7 @@ var SeatPicker = class _SeatPicker {
2160
2263
  const price = c.tiers?.length ? c.tiers[0].price : c.price;
2161
2264
  const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
2162
2265
  return `<div class="sl-price-row${dim ? " sl-dim" : ""}" data-cat="${c.key}"><span class="sl-dot" style="background:${c.color}"></span><span class="sl-price-label">${c.label}</span><span class="sl-price-left">${left[c.key] ?? 0} left</span>` + (price != null ? `<span class="sl-price-amt">${this.money(price)}</span>` : "") + `</div>`;
2163
- }).join("") + `<div class="sl-status-key" aria-label="Seat status legend"><span class="sl-status-item"><i class="sl-status-icon">H</i>Held by another buyer</span><span class="sl-status-item"><i class="sl-status-icon sold">\xD7</i>Sold</span></div>`;
2266
+ }).join("") + `<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>`;
2164
2267
  this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
2165
2268
  row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
2166
2269
  row.addEventListener("mouseleave", () => this.controller.getRenderer()?.setCategoryHighlight?.(null));
@@ -2179,6 +2282,7 @@ var SeatPicker = class _SeatPicker {
2179
2282
  }
2180
2283
  syncTray() {
2181
2284
  if (!this.els.tray) return;
2285
+ this.updateSelectionCapacity();
2182
2286
  const seats = this.committedSelection();
2183
2287
  const gaAreas = this.controller.getGAAreas();
2184
2288
  const heldItems = this.hold?.items ?? [];
@@ -2189,14 +2293,19 @@ var SeatPicker = class _SeatPicker {
2189
2293
  } else if (!seats.length && !heldItems.length) {
2190
2294
  parts.push(`<div class="sl-tray-hint">Tap a seat on the map \u2014 or grab standing tickets below.</div>`);
2191
2295
  }
2296
+ if (!this.hold) {
2297
+ const cats = this.controller.doc?.categories ?? [];
2298
+ 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>` + (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>`) + `<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 ? " 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>`);
2299
+ }
2192
2300
  for (const item of heldItems) {
2193
2301
  const itemKey = `held:${item.label}`;
2194
2302
  nextTrayKeys.add(itemKey);
2195
2303
  const cat = this.controller.doc?.categories.find((c) => c.key === item.categoryKey);
2196
2304
  const tierName = item.tierId ? cat?.tiers?.find((ti) => ti.id === item.tierId)?.name : void 0;
2197
2305
  const canView2 = this.seatViewEnabled() && item.objectType !== "ga" && !!this.controller.seatByLabel(item.label);
2306
+ const viewBtn = canView2 ? `<button type="button" class="view" data-view-label="${item.label}" aria-label="${t2("picker.viewFromSeat", { label: item.label })}"><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>` : "";
2198
2307
  parts.push(
2199
- `<div class="sl-chip sl-held${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-held="${encodeURIComponent(item.label)}"><b>${item.label}</b><span class="cat">${cat?.label ?? item.categoryKey}${tierName ? ` \xB7 ${tierName}` : ""}</span><span class="sl-chip-state" aria-label="Held by you" title="Held by you">Held</span><span class="amt">${this.money(item.unitPrice * (item.quantity ?? 1))}</span>` + (canView2 ? `<button type="button" class="view" data-view-label="${item.label}" aria-label="${t2("picker.viewFromSeat", { label: item.label })}"><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>` : "") + `<button type="button" class="rm" aria-label="Remove held ticket ${item.label}"><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>`
2308
+ `<div class="sl-chip sl-held${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-held="${encodeURIComponent(item.label)}"><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><div class="sl-chip-main"><b>${item.label}</b><div class="sl-chip-sub"><span class="cat">${cat?.label ?? item.categoryKey}${tierName ? ` \xB7 ${tierName}` : ""}</span>${viewBtn}</div></div><span class="amt">${this.money(item.unitPrice * (item.quantity ?? 1))}</span><button type="button" class="rm" aria-label="Remove held ticket ${item.label}"><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>`
2200
2309
  );
2201
2310
  }
2202
2311
  const heldLabels = new Set(heldItems.map((item) => item.label));
@@ -2208,7 +2317,7 @@ var SeatPicker = class _SeatPicker {
2208
2317
  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(ti.price)}</option>`).join("") + `</select>` : "";
2209
2318
  const viewBtn = canView ? `<button type="button" class="view" data-view-label="${s.label}" aria-label="${t2("picker.viewFromSeat", { label: s.label })}"><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>` : "";
2210
2319
  parts.push(
2211
- `<div class="sl-chip${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-seat="${s.id}"><b>${s.label}</b><span class="cat">${cat?.label ?? s.categoryKey}</span>` + tierSelect + `<span class="sl-chip-state sl-pending">Selected</span><span class="amt">${this.money(s.price)}</span>` + viewBtn + `<button type="button" class="rm" aria-label="Remove ${s.label}"><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>`
2320
+ `<div class="sl-chip${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-seat="${s.id}"><span class="sl-ticket-state" aria-label="Selected" title="Selected"><svg viewBox="0 0 24 24"><path d="M5 12l4 4L19 6"/></svg></span><div class="sl-chip-main"><b>${s.label}</b><div class="sl-chip-sub"><span class="cat">${cat?.label ?? s.categoryKey}</span>${tierSelect}${viewBtn}</div></div><span class="amt">${this.money(s.price)}</span><button type="button" class="rm" aria-label="Remove ${s.label}"><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>`
2212
2321
  );
2213
2322
  }
2214
2323
  for (const area of gaAreas) {
@@ -2217,17 +2326,11 @@ var SeatPicker = class _SeatPicker {
2217
2326
  `<div class="sl-ga" data-ga="${area.id}"><div class="sl-ga-info"><div class="sl-ga-name">${area.label}</div><div class="sl-ga-sub">${this.money(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>`
2218
2327
  );
2219
2328
  }
2220
- if (!this.hold) {
2221
- const cats = this.controller.doc?.categories ?? [];
2222
- parts.push(
2223
- `<div class="sl-ba">` + (cats.length > 1 ? `<select aria-label="Category" data-ba-cat><option value="">Any tier</option>` + cats.map((c) => `<option value="${c.key}"${this.baCat === c.key ? " selected" : ""}>${c.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 ? " disabled" : ""}>` + (this.bestAvailableBusy ? `<span class="sl-ba-spin" aria-hidden="true"></span>Finding\u2026` : "Best available") + `</button></div>`
2224
- );
2225
- }
2226
2329
  this.els.tray.innerHTML = parts.join("");
2227
2330
  this.lastTrayKeys = nextTrayKeys;
2228
2331
  this.els.tray.querySelectorAll("[data-ba]").forEach((btn) => {
2229
2332
  btn.addEventListener("click", () => {
2230
- this.baQty = Math.max(1, Math.min(8, this.baQty + Number(btn.dataset.ba)));
2333
+ this.baQty = Math.max(1, Math.min(this.maxTickets, this.baQty + Number(btn.dataset.ba)));
2231
2334
  this.syncTray();
2232
2335
  });
2233
2336
  });
@@ -2235,6 +2338,21 @@ var SeatPicker = class _SeatPicker {
2235
2338
  this.baCat = e.target.value;
2236
2339
  });
2237
2340
  this.els.tray.querySelector(".sl-ba-go")?.addEventListener("click", () => {
2341
+ if (this.pendingSelectionCount() > 0) {
2342
+ this.bestAvailableConfirm = true;
2343
+ this.syncTray();
2344
+ this.els.tray.querySelector("[data-ba-replace]")?.focus();
2345
+ return;
2346
+ }
2347
+ void this.bestAvailable(this.baQty, this.baCat || void 0);
2348
+ });
2349
+ this.els.tray.querySelector("[data-ba-cancel]")?.addEventListener("click", () => {
2350
+ this.bestAvailableConfirm = false;
2351
+ this.syncTray();
2352
+ this.els.tray.querySelector(".sl-ba-go")?.focus();
2353
+ });
2354
+ this.els.tray.querySelector("[data-ba-replace]")?.addEventListener("click", () => {
2355
+ this.bestAvailableConfirm = false;
2238
2356
  void this.bestAvailable(this.baQty, this.baCat || void 0);
2239
2357
  });
2240
2358
  this.els.tray.querySelectorAll(".sl-chip .rm").forEach((btn) => {
@@ -2245,12 +2363,26 @@ var SeatPicker = class _SeatPicker {
2245
2363
  return;
2246
2364
  }
2247
2365
  const id = chip.dataset.seat;
2248
- if (this.reducedMotion()) {
2366
+ const label = chip.querySelector("b")?.textContent ?? "Seat";
2367
+ const remove = () => {
2249
2368
  this.controller.deselect([id]);
2369
+ this.toast(`${label} removed.`, "neutral", {
2370
+ label: "Undo",
2371
+ onClick: () => {
2372
+ const restored = this.controller.select([id]);
2373
+ this.toast(
2374
+ restored.length ? `${label} restored.` : `${label} is no longer available.`,
2375
+ restored.length ? "success" : "warning"
2376
+ );
2377
+ }
2378
+ });
2379
+ };
2380
+ if (this.reducedMotion()) {
2381
+ remove();
2250
2382
  return;
2251
2383
  }
2252
2384
  chip.classList.add("sl-leave");
2253
- this.scheduleMotion(() => this.controller.deselect([id]), 150);
2385
+ this.scheduleMotion(remove, 150);
2254
2386
  });
2255
2387
  });
2256
2388
  this.els.tray.querySelectorAll(".sl-chip .tier").forEach((sel) => {
@@ -2267,13 +2399,15 @@ var SeatPicker = class _SeatPicker {
2267
2399
  const areaEl = btn.closest(".sl-ga");
2268
2400
  const id = areaEl.dataset.ga;
2269
2401
  const area = gaAreas.find((a) => a.id === id);
2270
- const next = Math.max(0, Math.min(area?.available ?? 0, (this.gaQty.get(id) ?? 0) + Number(btn.dataset.d)));
2402
+ const delta = Number(btn.dataset.d);
2403
+ if (delta > 0 && !this.canAddTicket()) return;
2404
+ const next = Math.max(0, Math.min(area?.available ?? 0, (this.gaQty.get(id) ?? 0) + delta));
2271
2405
  this.gaQty.set(id, next);
2272
2406
  this.syncTray();
2273
2407
  });
2274
2408
  });
2275
- const gaTotal = gaAreas.reduce((sum, a) => sum + a.price * (this.gaQty.get(a.id) ?? 0), 0);
2276
- const gaCount = [...this.gaQty.values()].reduce((a, b) => a + b, 0);
2409
+ const gaTotal = this.pendingGATotal(gaAreas);
2410
+ const gaCount = this.pendingGACount();
2277
2411
  const heldTotal = heldItems.reduce((sum, item) => sum + item.unitPrice * (item.quantity ?? 1), 0);
2278
2412
  const heldCount = heldItems.reduce((sum, item) => sum + (item.quantity ?? 1), 0);
2279
2413
  const freshSeats = seats.filter((seat) => !heldLabels.has(seat.label));
@@ -2284,14 +2418,24 @@ var SeatPicker = class _SeatPicker {
2284
2418
  const previousTotal = this.lastTrayTotal;
2285
2419
  this.els.count.textContent = count ? `${count} ${count === 1 ? "ticket" : "tickets"}` : "No seats selected";
2286
2420
  this.els.total.textContent = count ? this.money(total) : "";
2421
+ this.root?.setAttribute("data-has-selection", String(count > 0));
2422
+ this.els.foot?.classList.toggle("empty", count === 0);
2423
+ if (this.els.seatSummary) {
2424
+ this.els.seatSummary.textContent = count ? `${count} selected` : "";
2425
+ }
2287
2426
  this.syncCta(count, pendingCount);
2288
2427
  if (this.hold) {
2289
2428
  const securedCount = heldCount || this.hold.seats?.length || 0;
2290
2429
  if (this.els.holdTitle) {
2291
- this.els.holdTitle.textContent = `${securedCount} ${securedCount === 1 ? "seat" : "seats"} secured`;
2430
+ this.els.holdTitle.textContent = `${securedCount} secured`;
2292
2431
  }
2293
2432
  if (this.els.holdCopy) {
2294
- this.els.holdCopy.textContent = pendingCount ? `${pendingCount} more selected \u2014 secure before checkout.` : "Checkout timer is running.";
2433
+ this.els.holdCopy.textContent = pendingCount ? `${pendingCount} more selected` : "Checkout timer running";
2434
+ }
2435
+ const change = this.els.holdChange;
2436
+ if (change) {
2437
+ change.disabled = this.releasingHold;
2438
+ change.textContent = this.releasingHold ? "Releasing\u2026" : "Change";
2295
2439
  }
2296
2440
  }
2297
2441
  if (count !== previousCount) this.animateOnce(this.els.count, "sl-value-pop", 380);
@@ -2302,7 +2446,7 @@ var SeatPicker = class _SeatPicker {
2302
2446
  this.els.peek.innerHTML = `<span>${count} ${count === 1 ? "ticket" : "tickets"} \xB7 ${this.money(total)}</span><span class="go">${this.hold ? pendingCount ? "Secure more" : "Continue" : "Review"}</span>`;
2303
2447
  } else {
2304
2448
  const prices = (this.controller.doc?.categories ?? []).map((c) => this.catPrice(c)).filter((p) => p != null);
2305
- this.els.peek.innerHTML = (prices.length ? `<span>From ${this.money(Math.min(...prices))}</span>` : "<span>Pick your seats</span>") + `<span class="sub">\xB7 Best available</span>`;
2449
+ this.els.peek.innerHTML = (prices.length ? `<span>From ${this.money(Math.min(...prices))}</span>` : "<span>Pick your seats</span>") + `<span class="go">\u2726 Best seats</span>`;
2306
2450
  }
2307
2451
  }
2308
2452
  this.lastTrayCount = count;
@@ -2343,7 +2487,30 @@ var SeatPicker = class _SeatPicker {
2343
2487
  if (button?.isConnected) button.disabled = false;
2344
2488
  }
2345
2489
  }
2490
+ async handleChangeSeats() {
2491
+ if (!this.hold || this.releasingHold) return;
2492
+ this.releasingHold = true;
2493
+ const button = this.els.holdChange;
2494
+ if (button) {
2495
+ button.disabled = true;
2496
+ button.textContent = "Releasing\u2026";
2497
+ }
2498
+ try {
2499
+ await this.release();
2500
+ if (!this.hold) this.toast("Held tickets released. Choose your new seats.", "success");
2501
+ } finally {
2502
+ this.releasingHold = false;
2503
+ if (button?.isConnected) {
2504
+ button.disabled = false;
2505
+ button.textContent = "Change";
2506
+ }
2507
+ }
2508
+ }
2346
2509
  async handleCta() {
2510
+ if (this.totalTicketCount() > this.maxTickets) {
2511
+ this.toast(`Remove tickets until your order has ${this.maxTickets} or fewer.`, "warning");
2512
+ return;
2513
+ }
2347
2514
  const committed = this.committedSelection();
2348
2515
  if (this.hold && !committed.some((s) => !(this.hold.items ?? []).some((i) => i.label === s.label))) {
2349
2516
  const seats = this.hold.seats ?? committed;
@@ -2381,7 +2548,10 @@ var SeatPicker = class _SeatPicker {
2381
2548
  this.opts.onCheckout?.(hold, hold.seats ?? chosenSeats, this.buildHandoff(hold));
2382
2549
  } catch (err) {
2383
2550
  this.opts.onError?.(err);
2384
- this.toast("One or more seats were just taken. Please pick again.", "error");
2551
+ const problem = err;
2552
+ const labels = (problem.conflicts ?? []).map((conflict) => conflict.label).filter(Boolean).slice(0, 3);
2553
+ 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.";
2554
+ this.toast(message, "error");
2385
2555
  this.setCtaPhase("idle");
2386
2556
  } finally {
2387
2557
  this.holdingLabels.clear();
@@ -2504,15 +2674,28 @@ var SeatPicker = class _SeatPicker {
2504
2674
  hold ? this.buildHandoff(hold) : null
2505
2675
  );
2506
2676
  }
2507
- toast(msg, tone = "neutral") {
2677
+ toast(msg, tone = "neutral", action) {
2508
2678
  const el = this.els.toast;
2509
2679
  if (!el) return;
2510
- el.textContent = msg;
2680
+ el.replaceChildren();
2681
+ const copy = document.createElement("span");
2682
+ copy.textContent = msg;
2683
+ el.appendChild(copy);
2684
+ el.classList.toggle("has-action", !!action);
2685
+ if (action) {
2686
+ const button = document.createElement("button");
2687
+ button.type = "button";
2688
+ button.className = "sl-toast-action";
2689
+ button.textContent = action.label;
2690
+ button.addEventListener("click", action.onClick, { once: true });
2691
+ el.appendChild(button);
2692
+ }
2511
2693
  el.dataset.tone = tone;
2512
2694
  el.classList.add("on");
2513
2695
  if (this.toastTimer) clearTimeout(this.toastTimer);
2514
2696
  this.toastTimer = setTimeout(() => {
2515
2697
  el.classList.remove("on");
2698
+ el.classList.remove("has-action");
2516
2699
  el.dataset.tone = "neutral";
2517
2700
  }, 4200);
2518
2701
  }
@@ -2562,7 +2745,9 @@ var SeatPicker = class _SeatPicker {
2562
2745
  }
2563
2746
  async bestAvailable(qty, categoryKey) {
2564
2747
  if (this.bestAvailableBusy) return null;
2748
+ qty = Math.max(1, Math.min(this.maxTickets, Math.floor(qty)));
2565
2749
  if (this.confirmSeat) this.cancelConfirm();
2750
+ this.bestAvailableConfirm = false;
2566
2751
  this.bestAvailableBusy = true;
2567
2752
  const button = this.els.tray?.querySelector(".sl-ba-go");
2568
2753
  if (button) {
@@ -2575,6 +2760,7 @@ var SeatPicker = class _SeatPicker {
2575
2760
  this.hold = { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items };
2576
2761
  this.handedOff = false;
2577
2762
  this.bookedShown = false;
2763
+ this.gaQty.clear();
2578
2764
  this.startHoldTimer(h.expiresAt);
2579
2765
  this.flashHeldSeats(this.hold);
2580
2766
  this.syncTray();
@@ -2584,14 +2770,13 @@ var SeatPicker = class _SeatPicker {
2584
2770
  return null;
2585
2771
  } catch (err) {
2586
2772
  this.opts.onError?.(err);
2587
- this.toast("Those seats are no longer available. Try another quantity or category.", "error");
2773
+ const reason = err?.reason;
2774
+ 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.";
2775
+ this.toast(message, "error");
2588
2776
  return null;
2589
2777
  } finally {
2590
2778
  this.bestAvailableBusy = false;
2591
- if (!this.hold && button?.isConnected) {
2592
- button.disabled = false;
2593
- button.textContent = "Best available";
2594
- }
2779
+ this.syncTray();
2595
2780
  }
2596
2781
  }
2597
2782
  async release() {