@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.cjs CHANGED
@@ -500,24 +500,26 @@ var CSS = `
500
500
  .sl-body{display:flex;flex:1;min-height:0}
501
501
  .sl-map{position:relative;flex:1;min-width:0}
502
502
  .sl-map-host{position:absolute;inset:0}
503
- .sl-side{width:300px;flex:none;border-left:1px solid var(--sl-line);display:flex;flex-direction:column;min-height:0;overflow-y:auto}
503
+ .sl-side{width:300px;flex:none;border-left:1px solid var(--sl-line);display:flex;flex-direction:column;min-height:0;overflow:hidden}
504
504
 
505
505
  /* narrow (container < 640px): map-first \u2014 the map claims ~80-85% of the
506
506
  container and the side panel becomes a PEEKING bottom sheet (AXS/Ticketmaster
507
507
  mobile pattern). data-sheet on the root: "peek" (default: grab handle + one
508
- summary line) / "open" (\u226450%, swipe up or auto-expand on first selection).
508
+ summary line) / "open" (room for rows + checkout, swipe up to open).
509
509
  Swipe handling lives on the sheet head ONLY \u2014 never the map host, so the
510
510
  map's raw-pointer gesture pipeline is untouched. */
511
511
  .sl-picker[data-layout="narrow"] .sl-body{flex-direction:column}
512
512
  .sl-picker[data-layout="narrow"] .sl-map{min-height:0;flex:1}
513
513
  .sl-picker[data-layout="narrow"] .sl-side{width:100%;border-left:0;border-top:1px solid var(--sl-line);
514
- flex:none;height:50%;transition:height .3s cubic-bezier(.2,.8,.2,1)}
514
+ flex:none;height:min(72%,480px);overflow:hidden;transition:height .3s cubic-bezier(.2,.8,.2,1);overscroll-behavior:contain}
515
+ .sl-picker[data-layout="narrow"][data-sheet="open"][data-has-selection="false"] .sl-side{height:min(252px,52%)}
515
516
  .sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-side{height:86px;overflow:hidden}
516
517
  .sl-picker[data-layout="narrow"][data-sheet="peek"] .sl-side > :not(.sl-sheet-head){display:none}
517
- .sl-picker[data-layout="narrow"] .sl-tray{flex:none}
518
- .sl-picker[data-layout="narrow"] .sl-foot{position:sticky;bottom:0;background:var(--sl-bg)}
518
+ .sl-picker[data-layout="narrow"] .sl-tray{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}
519
+ .sl-picker[data-layout="narrow"] .sl-foot{position:static;background:var(--sl-bg)}
520
+ .sl-picker[data-layout="narrow"] .sl-foot.empty{display:none}
519
521
  .sl-picker[data-layout="narrow"] .sl-sheet-head{order:0}
520
- .sl-picker[data-layout="narrow"] .sl-seats-sec{order:1}
522
+ .sl-picker[data-layout="narrow"] .sl-seats-sec{display:none}
521
523
  .sl-picker[data-layout="narrow"] .sl-tray{order:2}
522
524
  .sl-picker[data-layout="narrow"] .sl-filtersec{order:3}
523
525
  .sl-picker[data-layout="narrow"] .sl-filters{order:4}
@@ -525,13 +527,21 @@ var CSS = `
525
527
  .sl-picker[data-layout="narrow"] .sl-pricef{order:6}
526
528
  .sl-picker[data-layout="narrow"] .sl-prices{order:7}
527
529
  .sl-picker[data-layout="narrow"] .sl-foot{order:8}
530
+ .sl-picker[data-layout="narrow"] .sl-tray-hint,
531
+ .sl-picker[data-layout="narrow"] .sl-filtersec,
532
+ .sl-picker[data-layout="narrow"] .sl-filters,
533
+ .sl-picker[data-layout="narrow"] .sl-prices-sec,
534
+ .sl-picker[data-layout="narrow"] .sl-prices{display:none!important}
535
+ .sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filtersec,
536
+ .sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filters,
537
+ .sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-prices-sec{display:none}
528
538
  /* touch chrome: pinch-zoom exists \u2014 hide +/\u2212 on the sheet layout (keep fit) */
529
539
  .sl-picker[data-layout="narrow"] .sl-zoom [data-ref="zin"],
530
540
  .sl-picker[data-layout="narrow"] .sl-zoom [data-ref="zout"]{display:none}
531
541
 
532
542
  /* bottom-sheet head: grab handle + one-line summary (narrow only). The WHOLE
533
543
  head is the tap/swipe toggle target (min 44px), so it reads as one control. */
534
- .sl-sheet-head{display:none;flex-direction:column;justify-content:center;padding:6px 16px 8px;min-height:44px;
544
+ .sl-sheet-head{display:none;flex-direction:column;justify-content:center;padding:6px 12px 8px;min-height:56px;
535
545
  cursor:pointer;touch-action:none;user-select:none;-webkit-user-select:none;flex:none}
536
546
  .sl-picker[data-layout="narrow"] .sl-sheet-head{display:flex}
537
547
  .sl-sheet-grab{width:36px;height:4px;border-radius:999px;background:var(--sl-muted);opacity:.55;margin:2px auto 7px}
@@ -546,54 +556,72 @@ var CSS = `
546
556
  /* state chevron: points UP while peeking, rotates to point DOWN when open.
547
557
  Base keeps an explicit rotate(0) \u2014 transitioning to/from a bare 'none' leaves
548
558
  the value stuck in some engines, so both endpoints must be real transforms. */
549
- .sl-sheet-chevron{flex:none;display:flex;align-items:center;justify-content:center;color:var(--sl-muted);
550
- transform:rotate(0deg);transition:transform .22s ease}
551
- .sl-sheet-chevron svg{width:18px;height:18px;stroke:currentColor;stroke-width:2.4;fill:none;
559
+ .sl-sheet-toggle{width:44px;height:44px;margin:-8px -8px -8px 0;border-radius:999px;flex:none;display:flex;
560
+ align-items:center;justify-content:center;color:var(--sl-muted);transition:color .15s,background .15s}
561
+ .sl-sheet-toggle:hover,.sl-sheet-toggle:focus-visible{color:var(--sl-text);background:color-mix(in srgb,var(--sl-line) 44%,transparent)}
562
+ .sl-sheet-toggle svg{width:21px;height:21px;stroke:currentColor;stroke-width:2.4;fill:none;
552
563
  stroke-linecap:round;stroke-linejoin:round}
553
- .sl-picker[data-sheet="open"] .sl-sheet-chevron{transform:rotate(180deg)}
564
+ .sl-sheet-toggle svg{transform:rotate(0deg);transition:transform .24s cubic-bezier(.2,.8,.2,1)}
565
+ .sl-picker[data-sheet="open"] .sl-sheet-toggle svg{transform:rotate(180deg)}
554
566
 
555
567
  /* consolidated Filters row inside the sheet (a11y chips + colorblind toggle
556
568
  dock here on narrow; they live on the map / zoom column on wide) */
557
569
  .sl-filtersec{display:none}
558
570
  .sl-filters{display:none;gap:6px;flex-wrap:wrap;align-items:center;padding:2px 16px 10px}
559
- .sl-picker[data-layout="narrow"] .sl-filtersec.has{display:block}
560
- .sl-picker[data-layout="narrow"] .sl-filters.has{display:flex}
571
+ .sl-picker[data-layout="narrow"] .sl-filtersec.has,
572
+ .sl-picker[data-layout="narrow"] .sl-filters.has{display:none}
573
+ .sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filtersec.has,
574
+ .sl-picker[data-layout="narrow"][data-has-selection="true"] .sl-filters.has{display:none}
561
575
  .sl-cbbtn{width:32px;height:32px;border-radius:999px;background:var(--sl-surface);border:1px solid var(--sl-line);
562
576
  color:var(--sl-text);display:flex;align-items:center;justify-content:center;transition:border-color .15s}
563
577
  .sl-cbbtn:hover{border-color:var(--sl-muted)}
564
578
  .sl-cbbtn svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
565
579
 
566
- /* price panel */
567
- .sl-sec{padding:14px 16px 4px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
568
- .sl-prices{padding:4px 16px 10px;border-bottom:1px solid var(--sl-line)}
569
- .sl-price-row{display:flex;align-items:center;gap:8px;padding:5px 0;font-size:13px}
580
+ /* price panel \u2014 one compact filter control replaces the wrapping price-chip row. */
581
+ .sl-sec{padding:14px 14px 4px;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
582
+ .sl-prices-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
583
+ .sl-price-select{min-height:32px;max-width:130px;padding:5px 28px 5px 9px;border:1px solid var(--sl-line);border-radius:9px;
584
+ background:var(--sl-surface);color:var(--sl-text);font:inherit;font-size:11px;font-weight:750;letter-spacing:0;text-transform:none}
585
+ .sl-prices{padding:5px 14px 10px;border-bottom:1px solid var(--sl-line)}
586
+ .sl-prices-sec,.sl-prices,.sl-seats-sec{flex:none}
587
+ .sl-price-row{display:flex;align-items:center;gap:7px;min-height:28px;font-size:12px}
570
588
  .sl-dot{width:9px;height:9px;border-radius:50%;flex:none}
571
589
  .sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
572
590
  .sl-price-left{font-size:11px;color:var(--sl-muted);font-variant-numeric:tabular-nums}
573
591
  .sl-price-amt{font-weight:800;font-variant-numeric:tabular-nums}
574
- .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}
592
+ .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}
575
593
  .sl-status-item{display:inline-flex;align-items:center;gap:6px}
576
594
  .sl-status-icon{width:17px;height:17px;border-radius:999px;display:inline-flex;align-items:center;justify-content:center;
577
- color:#fff;background:#6b7280;font-size:9px;font-weight:900;line-height:1}
578
- .sl-status-icon.sold{background:#374151;font-size:14px}
595
+ color:#fff;background:#6b7280;line-height:1}
596
+ .sl-status-icon svg{width:10px;height:10px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
597
+ .sl-status-icon.sold{background:#8b93a0}
598
+ .sl-status-icon.sold svg{width:11px;height:11px;stroke-width:2.4}
579
599
 
580
600
  /* tray */
581
- .sl-tray{flex:1;padding:10px 16px;display:flex;flex-direction:column;gap:8px;min-height:0}
601
+ .sl-seats-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
602
+ .sl-seat-summary{font-size:10px;letter-spacing:0;text-transform:none;white-space:nowrap}
603
+ .sl-tray{flex:1;padding:10px 14px 14px;display:flex;flex-direction:column;gap:7px;min-height:0;overflow-y:auto;
604
+ overscroll-behavior:contain;scrollbar-gutter:stable}
582
605
  .sl-tray-hint{font-size:12.5px;color:var(--sl-muted);line-height:1.5}
583
- .sl-chip{display:flex;align-items:center;gap:9px;padding:9px 11px;border:1px solid var(--sl-line);
584
- border-radius:var(--sl-r-sm);background:var(--sl-surface);font-size:13px;transform-origin:center}
606
+ .sl-chip{position:relative;display:grid;grid-template-columns:24px minmax(0,1fr) auto 30px;align-items:center;gap:8px;
607
+ min-height:53px;padding:7px 7px 7px 9px;border:1px solid var(--sl-line);border-radius:var(--sl-r-sm);
608
+ background:var(--sl-surface);font-size:13px;transform-origin:center;transition:border-color .15s,background .15s}
609
+ .sl-chip:hover{border-color:color-mix(in srgb,var(--sl-accent) 38%,var(--sl-line))}
585
610
  .sl-chip.sl-enter{animation:slChipIn .38s cubic-bezier(.2,.8,.2,1) both}
586
611
  .sl-chip.sl-leave{pointer-events:none;animation:slChipOut .16s ease-in both}
587
612
  .sl-chip.sl-held{border-color:var(--sl-line);background:color-mix(in srgb,var(--sl-accent) 7%,var(--sl-surface));
588
613
  box-shadow:inset 3px 0 0 color-mix(in srgb,var(--sl-accent) 72%,transparent)}
589
- .sl-chip b{font-weight:800;flex:none;min-width:max-content;white-space:nowrap}
590
- .sl-chip .cat{color:var(--sl-muted);font-size:11.5px;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
614
+ .sl-ticket-state{width:23px;height:23px;border-radius:999px;display:flex;align-items:center;justify-content:center;
615
+ background:var(--sl-accent);color:var(--sl-accent-ink)}
616
+ .sl-ticket-state.held{background:color-mix(in srgb,var(--sl-accent) 18%,var(--sl-surface));color:var(--sl-accent)}
617
+ .sl-ticket-state svg{width:13px;height:13px;stroke:currentColor;stroke-width:2.4;fill:none;stroke-linecap:round;stroke-linejoin:round}
618
+ .sl-chip-main{min-width:0}
619
+ .sl-chip b{display:block;font-weight:800;min-width:max-content;white-space:nowrap}
620
+ .sl-chip-sub{display:flex;align-items:center;gap:5px;min-width:0;margin-top:2px}
621
+ .sl-chip .cat{color:var(--sl-muted);font-size:10.5px;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
591
622
  .sl-chip .amt{font-weight:700;font-variant-numeric:tabular-nums;flex:none;white-space:nowrap}
592
- .sl-chip-state{flex:none;padding:3px 6px;border-radius:999px;background:var(--sl-accent);color:var(--sl-accent-ink);
593
- font-size:8.5px;line-height:1;font-weight:900;letter-spacing:.08em;text-transform:uppercase}
594
- .sl-chip-state.sl-pending{background:transparent;color:var(--sl-muted);border:1px solid var(--sl-line)}
595
- .sl-chip .rm{width:22px;height:22px;border-radius:999px;flex:none;display:flex;align-items:center;justify-content:center;color:var(--sl-muted)}
596
- .sl-chip .rm:hover{color:var(--sl-text)}
623
+ .sl-chip .rm{width:29px;height:29px;border-radius:8px;flex:none;display:flex;align-items:center;justify-content:center;color:var(--sl-muted)}
624
+ .sl-chip .rm:hover,.sl-chip .rm:focus-visible{color:var(--sl-accent);background:color-mix(in srgb,var(--sl-accent) 10%,transparent)}
597
625
  .sl-chip .rm svg{width:11px;height:11px;stroke:currentColor;stroke-width:2.4;fill:none;stroke-linecap:round}
598
626
 
599
627
  /* GA rows */
@@ -608,16 +636,22 @@ var CSS = `
608
636
  .sl-ga-qty span{min-width:16px;text-align:center;font-weight:800;font-variant-numeric:tabular-nums}
609
637
 
610
638
  /* footer */
611
- .sl-foot{padding:12px 16px 14px;border-top:1px solid var(--sl-line);flex:none}
612
- .sl-hold-note{display:none;align-items:center;gap:9px;margin-bottom:10px;padding:9px 10px;border-radius:var(--sl-r-sm);
639
+ .sl-foot{position:relative;z-index:2;padding:12px 16px 14px;border-top:1px solid var(--sl-line);flex:none;
640
+ background:var(--sl-bg);box-shadow:0 -10px 24px -22px rgba(0,0,0,.72)}
641
+ .sl-hold-note{display:none;align-items:center;gap:7px;margin-bottom:8px;padding:7px 8px;border-radius:var(--sl-r-sm);
613
642
  border:1px solid var(--sl-line);background:color-mix(in srgb,var(--sl-accent) 7%,var(--sl-surface));
614
643
  box-shadow:inset 3px 0 0 color-mix(in srgb,var(--sl-accent) 72%,transparent);
615
644
  font-size:11.5px;line-height:1.35;color:var(--sl-muted)}
616
645
  .sl-hold-note.on{display:flex;animation:slNoticeIn .38s cubic-bezier(.2,.8,.2,1) both}
617
646
  .sl-hold-note svg{width:16px;height:16px;flex:none;stroke:var(--sl-accent);stroke-width:2.4;fill:none;
618
647
  stroke-linecap:round;stroke-linejoin:round}
619
- .sl-hold-note b{display:block;color:var(--sl-text);font-size:12px}
620
- .sl-hold-copy{display:block}
648
+ .sl-hold-note b{display:block;color:var(--sl-text);font-size:11.5px;white-space:nowrap}
649
+ .sl-hold-copy{display:block;white-space:nowrap;font-size:10.5px}
650
+ .sl-hold-note>span{flex:1;min-width:0}
651
+ .sl-hold-change{flex:none;min-height:30px;padding:5px 8px;border-radius:8px;border:1px solid var(--sl-line);
652
+ color:var(--sl-text);font-size:10.5px;font-weight:750;white-space:nowrap}
653
+ .sl-hold-change:hover,.sl-hold-change:focus-visible{border-color:var(--sl-accent);color:var(--sl-accent)}
654
+ .sl-hold-change:disabled{opacity:.58;cursor:wait}
621
655
  .sl-total{display:flex;justify-content:space-between;align-items:center;font-size:13px;margin-bottom:10px}
622
656
  .sl-total b{font-size:17px;font-variant-numeric:tabular-nums}
623
657
  .sl-value-pop{animation:slValuePop .32s cubic-bezier(.2,.8,.2,1)}
@@ -675,6 +709,9 @@ var CSS = `
675
709
  font-size:12.5px;font-weight:600;opacity:0;pointer-events:none;transition:opacity .22s,transform .22s;white-space:nowrap;
676
710
  overflow:hidden;text-overflow:ellipsis}
677
711
  .sl-toast.on{opacity:1;transform:translateY(0) scale(1)}
712
+ .sl-toast.has-action{pointer-events:auto;display:flex;align-items:center;gap:12px;padding-right:8px}
713
+ .sl-toast-action{min-height:30px;padding:5px 10px;border-radius:999px;background:var(--sl-accent);color:var(--sl-accent-ink);
714
+ font:inherit;font-weight:800}
678
715
  .sl-toast[data-tone="error"]{border-color:#ef4444}
679
716
  .sl-toast[data-tone="warning"]{border-color:var(--sl-accent)}
680
717
  .sl-toast[data-tone="success"]{border-color:#22c55e}
@@ -757,29 +794,49 @@ var CSS = `
757
794
  .sl-picker[data-layout="narrow"] .sl-confirm{left:50%!important;top:auto!important;bottom:14px;width:min(342px,calc(100% - 24px));
758
795
  transform:translateX(-50%);animation:slConfirmMobileIn .24s cubic-bezier(.2,.8,.2,1) both}
759
796
 
760
- /* best-available row */
761
- .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)}
762
- .sl-ba select{background:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:7px;
763
- font:inherit;font-size:12px;padding:7px 8px;min-width:0;width:100%;max-width:none}
764
- .sl-ba-qty{display:flex;align-items:center;gap:7px}
765
- .sl-ba-qty button{width:24px;height:24px;border-radius:999px;background:var(--sl-surface);border:1px solid var(--sl-line);
766
- font-size:14px;font-weight:700;display:flex;align-items:center;justify-content:center}
797
+ /* Best available is a first-class shortcut, not an anonymous utility row. */
798
+ .sl-ba{position:relative;overflow:hidden;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:7px;
799
+ padding:13px;border:1px solid color-mix(in srgb,var(--sl-accent) 34%,var(--sl-line));border-radius:13px;
800
+ 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)))}
801
+ .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}
802
+ .sl-ba-title,.sl-ba-copy,.sl-ba select,.sl-ba-qty,.sl-ba-go{position:relative;z-index:1}
803
+ .sl-ba-title{grid-column:1/-1;display:flex;align-items:center;gap:7px;font-size:13px;font-weight:850}
804
+ .sl-ba-title .spark{color:var(--sl-accent);font-size:16px}
805
+ .sl-ba-copy{grid-column:1/-1;margin:-4px 0 2px 23px;color:var(--sl-muted);font-size:10.5px;line-height:1.35}
806
+ .sl-ba-copy .narrow{display:none}
807
+ .sl-ba select{background:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:8px;
808
+ font:inherit;font-size:11px;padding:7px 8px;min-width:0;width:100%;max-width:none}
809
+ .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)}
810
+ .sl-ba-qty button{width:25px;height:25px;border-radius:7px;background:color-mix(in srgb,var(--sl-line) 35%,transparent);border:0;
811
+ font-size:14px;font-weight:800;display:flex;align-items:center;justify-content:center}
767
812
  .sl-ba-qty span{min-width:14px;text-align:center;font-weight:800}
768
- .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;
769
- transition:border-color .15s,opacity .15s;display:flex;align-items:center;justify-content:center;gap:6px}
770
- .sl-ba-go:hover{border-color:var(--sl-muted)}
771
- .sl-ba-go:disabled{opacity:.62;cursor:wait}
813
+ .sl-picker .sl-ba-go{grid-column:1/-1;width:100%;min-height:37px;padding:7px 12px;border-radius:9px;background:var(--sl-accent);
814
+ 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;
815
+ box-shadow:0 8px 18px color-mix(in srgb,var(--sl-accent) 18%,transparent)}
816
+ .sl-picker .sl-ba-go:hover{filter:brightness(1.06)}
817
+ .sl-picker .sl-ba-go:disabled{opacity:.62;cursor:wait}
818
+ .sl-ba-replace{grid-column:1/-1;padding:3px 0 1px}
819
+ .sl-ba-replace b{display:block;font-size:12.5px}
820
+ .sl-ba-replace span{display:block;margin-top:3px;color:var(--sl-muted);font-size:10.5px;line-height:1.35}
821
+ .sl-ba-actions{grid-column:1/-1;display:grid;grid-template-columns:1fr 1fr;gap:7px}
822
+ .sl-ba-actions button{min-height:36px;border-radius:9px;border:1px solid var(--sl-line);font-size:11.5px;font-weight:800}
823
+ .sl-ba-actions .replace{border-color:var(--sl-accent);background:var(--sl-accent);color:var(--sl-accent-ink)}
824
+ .sl-picker[data-layout="narrow"] .sl-ba{padding:11px}
825
+ .sl-picker[data-layout="narrow"] .sl-ba-copy .wide{display:none}
826
+ .sl-picker[data-layout="narrow"] .sl-ba-copy .narrow{display:inline}
772
827
 
773
828
  /* screen-reader live region */
774
829
  .sl-sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
775
830
 
776
831
  /* per-seat ticket-tier select + view-from-seat button in tray chips */
777
- .sl-chip .tier{background:var(--sl-bg);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:7px;
778
- font:inherit;font-size:11px;padding:3px 5px;max-width:130px;cursor:pointer}
779
- .sl-chip .view{width:24px;height:24px;border-radius:999px;flex:none;display:flex;align-items:center;justify-content:center;
780
- color:var(--sl-muted);transition:color .15s}
832
+ .sl-chip .tier{background:var(--sl-bg);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:6px;
833
+ font:inherit;font-size:10px;padding:2px 4px;min-width:0;max-width:100%;cursor:pointer}
834
+ .sl-chip .view{width:20px;height:20px;border-radius:999px;flex:none;display:flex;align-items:center;justify-content:center;
835
+ color:var(--sl-muted);opacity:.36;transition:color .15s,opacity .15s}
781
836
  .sl-chip .view:hover{color:var(--sl-text)}
782
- .sl-chip .view svg{width:14px;height:14px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
837
+ .sl-chip:hover .view,.sl-chip .view:focus-visible{opacity:1;color:var(--sl-text)}
838
+ .sl-chip .view svg{width:12px;height:12px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
839
+ @media(pointer:coarse){.sl-chip .view{opacity:.58}}
783
840
 
784
841
  /* arena: LOD rung pills (flow within the top-center region) */
785
842
  .sl-rungs{display:none;background:var(--sl-surface);border:1px solid var(--sl-line);border-radius:999px;padding:3px}
@@ -866,10 +923,6 @@ var CSS = `
866
923
  .sl-minimap canvas{display:block}
867
924
  .sl-picker[data-layout="narrow"] .sl-minimap{display:none}
868
925
 
869
- /* F4 price-band filter \u2014 chip row in the side panel, above the price legend it
870
- * dims (keeps clear of the map's top-center rungs / top-left a11y chips). */
871
- .sl-pricef{display:flex;gap:6px;flex-wrap:wrap;padding:2px 16px 10px}
872
- .sl-pricef .sl-chip-f{padding:6px 11px;font-size:11.5px}
873
926
  /* F4 legend reflection: rows + counts for out-of-band categories read muted */
874
927
  .sl-price-row.sl-dim{opacity:.4}
875
928
  .sl-seccard-mix-item.sl-dim{opacity:.4}
@@ -959,6 +1012,8 @@ var SeatPicker = class _SeatPicker {
959
1012
  this.a11yFilter = "all";
960
1013
  this.baQty = 2;
961
1014
  this.baCat = "";
1015
+ this.bestAvailableConfirm = false;
1016
+ this.releasingHold = false;
962
1017
  // arena / multi-floor / seat-view chrome
963
1018
  this.rungsEl = null;
964
1019
  this.floorsEl = null;
@@ -1006,10 +1061,11 @@ var SeatPicker = class _SeatPicker {
1006
1061
  this.opts = { ...options, confirmSelection: options.confirmSelection ?? true };
1007
1062
  this.apiBase = (options.apiBase ?? DEFAULT_API_BASE2).replace(/\/+$/, "");
1008
1063
  this.api = new PubApi(this.apiBase);
1064
+ this.maxTickets = Math.max(1, Math.floor(options.maxSelection ?? DEFAULT_MAX_SELECTION2));
1009
1065
  this.controller = new import_core2.PickerController({
1010
1066
  transport: this.api,
1011
1067
  eventKey: options.event,
1012
- maxSelection: options.maxSelection ?? DEFAULT_MAX_SELECTION2,
1068
+ maxSelection: this.maxTickets,
1013
1069
  currency: options.currency,
1014
1070
  flashOnLiveChange: true,
1015
1071
  colorblindSafe: options.colorblindSafe,
@@ -1044,6 +1100,9 @@ var SeatPicker = class _SeatPicker {
1044
1100
  onDeselect: (seat) => {
1045
1101
  if (this.confirmSeat?.id === seat.id) this.dismissConfirm();
1046
1102
  },
1103
+ onSelectionLimit: () => {
1104
+ this.toast(`You can select up to ${this.maxTickets} tickets for this order.`, "warning");
1105
+ },
1047
1106
  onViewChange: () => {
1048
1107
  this.reanchorConfirm();
1049
1108
  this.syncRung();
@@ -1106,6 +1165,10 @@ var SeatPicker = class _SeatPicker {
1106
1165
  if (picker.confirmSeat) {
1107
1166
  e.preventDefault();
1108
1167
  picker.cancelConfirm();
1168
+ } else if (picker.bestAvailableConfirm) {
1169
+ e.preventDefault();
1170
+ picker.bestAvailableConfirm = false;
1171
+ picker.syncTray();
1109
1172
  } else {
1110
1173
  close();
1111
1174
  }
@@ -1129,10 +1192,17 @@ var SeatPicker = class _SeatPicker {
1129
1192
  this.root = root;
1130
1193
  mount.appendChild(root);
1131
1194
  root.addEventListener("keydown", (e) => {
1132
- if (e.key !== "Escape" || !this.confirmSeat) return;
1133
- e.preventDefault();
1134
- e.stopPropagation();
1135
- this.cancelConfirm();
1195
+ if (e.key !== "Escape") return;
1196
+ if (this.confirmSeat) {
1197
+ e.preventDefault();
1198
+ e.stopPropagation();
1199
+ this.cancelConfirm();
1200
+ } else if (this.bestAvailableConfirm) {
1201
+ e.preventDefault();
1202
+ e.stopPropagation();
1203
+ this.bestAvailableConfirm = false;
1204
+ this.syncTray();
1205
+ }
1136
1206
  });
1137
1207
  Object.entries(resolveTokens(void 0, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
1138
1208
  root.innerHTML = `
@@ -1165,21 +1235,22 @@ var SeatPicker = class _SeatPicker {
1165
1235
  <div class="sl-sheet-grab"></div>
1166
1236
  <div class="sl-sheet-bar">
1167
1237
  <div class="sl-sheet-peek" data-ref="peek"></div>
1168
- <span class="sl-sheet-chevron" aria-hidden="true">
1238
+ <button type="button" class="sl-sheet-toggle" data-ref="sheetToggle" aria-label="Open ticket panel" aria-expanded="false">
1169
1239
  <svg viewBox="0 0 24 24"><path d="M6 15l6-6 6 6"/></svg>
1170
- </span>
1240
+ </button>
1171
1241
  </div>
1172
1242
  </div>
1173
1243
  <div class="sl-sec sl-filtersec" data-ref="filtersSec">Filters</div>
1174
1244
  <div class="sl-filters" data-ref="filters"></div>
1175
- <div class="sl-sec sl-prices-sec" data-ref="pricesSec">Prices</div>
1245
+ <div class="sl-sec sl-prices-sec" data-ref="pricesSec"><span>Ticket prices</span></div>
1176
1246
  <div class="sl-prices" data-ref="prices"></div>
1177
- <div class="sl-sec sl-seats-sec">Your seats</div>
1247
+ <div class="sl-sec sl-seats-sec"><span>Your seats</span><span class="sl-seat-summary" data-ref="seatSummary"></span></div>
1178
1248
  <div class="sl-tray" data-ref="tray"></div>
1179
- <div class="sl-foot">
1249
+ <div class="sl-foot" data-ref="foot">
1180
1250
  <div class="sl-hold-note" data-ref="holdNote" role="status" aria-live="polite">
1181
1251
  <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg>
1182
1252
  <span><b data-ref="holdTitle">Seats secured</b><span class="sl-hold-copy" data-ref="holdCopy">Checkout timer is running.</span></span>
1253
+ <button type="button" class="sl-hold-change" data-ref="holdChange" aria-label="Release held tickets and choose different seats">Change</button>
1183
1254
  </div>
1184
1255
  <div class="sl-total"><span data-ref="count"></span><b data-ref="total"></b></div>
1185
1256
  <button type="button" class="sl-cta" data-ref="cta" disabled></button>
@@ -1208,9 +1279,17 @@ var SeatPicker = class _SeatPicker {
1208
1279
  this.els.zfit.addEventListener("click", () => this.controller.zoomToFit());
1209
1280
  const head = this.els.sheetHead;
1210
1281
  if (head) {
1282
+ const toggle = this.els.sheetToggle;
1211
1283
  const setSheet = (open) => {
1212
1284
  root.dataset.sheet = open ? "open" : "peek";
1285
+ toggle?.setAttribute("aria-expanded", String(open));
1286
+ toggle?.setAttribute("aria-label", open ? "Collapse ticket panel" : "Open ticket panel");
1213
1287
  };
1288
+ setSheet(root.dataset.sheet === "open");
1289
+ toggle?.addEventListener("click", (e) => {
1290
+ e.stopPropagation();
1291
+ setSheet(root.dataset.sheet !== "open");
1292
+ });
1214
1293
  let startY = 0;
1215
1294
  let swiped = false;
1216
1295
  let tracking = false;
@@ -1233,7 +1312,7 @@ var SeatPicker = class _SeatPicker {
1233
1312
  });
1234
1313
  head.addEventListener("pointerup", (e) => {
1235
1314
  if (tracking && !swiped && Math.abs(e.clientY - startY) < 6) {
1236
- if (!e.target.closest(".sl-seccard")) setSheet(root.dataset.sheet !== "open");
1315
+ if (!e.target.closest(".sl-seccard,.sl-sheet-toggle")) setSheet(root.dataset.sheet !== "open");
1237
1316
  }
1238
1317
  tracking = false;
1239
1318
  head.releasePointerCapture?.(e.pointerId);
@@ -1249,6 +1328,7 @@ var SeatPicker = class _SeatPicker {
1249
1328
  if (this.tipEl && this.tipEl.style.display !== "none") this.placeTooltip();
1250
1329
  });
1251
1330
  this.els.cta.addEventListener("click", () => void this.handleCta());
1331
+ this.els.holdChange?.addEventListener("click", () => void this.handleChangeSeats());
1252
1332
  const canvasHost = document.createElement("div");
1253
1333
  canvasHost.style.cssText = "position:absolute;inset:0";
1254
1334
  this.mapHost.appendChild(canvasHost);
@@ -1455,15 +1535,51 @@ var SeatPicker = class _SeatPicker {
1455
1535
  const heldItems = this.hold?.items ?? [];
1456
1536
  const heldLabels = new Set(heldItems.map((item) => item.label));
1457
1537
  const pendingSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).length;
1538
+ return pendingSeats + this.pendingGACount();
1539
+ }
1540
+ heldGACounts() {
1458
1541
  const heldGA = /* @__PURE__ */ new Map();
1459
- for (const item of heldItems.filter((candidate) => candidate.objectType === "ga")) {
1542
+ for (const item of (this.hold?.items ?? []).filter((candidate) => candidate.objectType === "ga")) {
1460
1543
  heldGA.set(item.objectId, (heldGA.get(item.objectId) ?? 0) + (item.quantity ?? 1));
1461
1544
  }
1462
- const pendingGA = [...this.gaQty.entries()].reduce(
1545
+ return heldGA;
1546
+ }
1547
+ pendingGACount() {
1548
+ const heldGA = this.heldGACounts();
1549
+ return [...this.gaQty.entries()].reduce(
1463
1550
  (sum, [areaId, qty]) => sum + Math.max(0, qty - (heldGA.get(areaId) ?? 0)),
1464
1551
  0
1465
1552
  );
1466
- return pendingSeats + pendingGA;
1553
+ }
1554
+ heldTicketCount() {
1555
+ return (this.hold?.items ?? []).reduce((sum, item) => sum + (item.quantity ?? 1), 0);
1556
+ }
1557
+ totalTicketCount() {
1558
+ const heldLabels = new Set((this.hold?.items ?? []).map((item) => item.label));
1559
+ const freshSeats = this.committedSelection().filter((seat) => !heldLabels.has(seat.label)).length;
1560
+ return this.heldTicketCount() + freshSeats + this.pendingGACount();
1561
+ }
1562
+ /** Held tickets and standing quantities consume the same order-wide cap. */
1563
+ updateSelectionCapacity() {
1564
+ const heldLabels = new Set((this.hold?.items ?? []).map((item) => item.label));
1565
+ const selectedHeld = this.committedSelection().filter((seat) => heldLabels.has(seat.label)).length;
1566
+ const remaining = Math.max(0, this.maxTickets - this.heldTicketCount() - this.pendingGACount());
1567
+ this.controller.setMaxSelection(selectedHeld + remaining);
1568
+ }
1569
+ canAddTicket() {
1570
+ if (this.totalTicketCount() < this.maxTickets) return true;
1571
+ this.toast(`You can select up to ${this.maxTickets} tickets for this order.`, "warning");
1572
+ return false;
1573
+ }
1574
+ pendingGATotal(gaAreas) {
1575
+ const heldGA = /* @__PURE__ */ new Map();
1576
+ for (const item of (this.hold?.items ?? []).filter((candidate) => candidate.objectType === "ga")) {
1577
+ heldGA.set(item.objectId, (heldGA.get(item.objectId) ?? 0) + (item.quantity ?? 1));
1578
+ }
1579
+ return gaAreas.reduce(
1580
+ (sum, area) => sum + area.price * Math.max(0, (this.gaQty.get(area.id) ?? 0) - (heldGA.get(area.id) ?? 0)),
1581
+ 0
1582
+ );
1467
1583
  }
1468
1584
  syncCta(count = this.lastTrayCount, pending = this.pendingSelectionCount()) {
1469
1585
  const cta = this.els.cta;
@@ -1752,39 +1868,26 @@ var SeatPicker = class _SeatPicker {
1752
1868
  }
1753
1869
  return bands;
1754
1870
  }
1755
- /**
1756
- * Build the price-band chip row in the side panel, directly under the "Prices"
1757
- * header and above the legend it dims. Living in the panel (not a map overlay)
1758
- * keeps it clear of the top-center rung pills and top-left a11y chips, and it
1759
- * rides the bottom sheet on narrow layouts. Skipped when there's <2 bands.
1760
- */
1871
+ /** Build the compact price selector in the panel header. Choosing a band both
1872
+ * filters availability and smoothly frames the matching seats on the map. */
1761
1873
  buildPriceFilter() {
1762
1874
  if (!this.els.prices || !this.els.pricesSec) return;
1763
1875
  const bands = this.priceBands();
1764
1876
  if (bands.length < 2) return;
1765
- const wrap = document.createElement("div");
1766
- wrap.className = "sl-pricef";
1767
- wrap.setAttribute("role", "group");
1768
- wrap.setAttribute("aria-label", "Filter seats by price");
1769
- const mk = (band, label, on) => `<button type="button" class="sl-chip-f${on ? " on" : ""}" data-band="${band}">${label}</button>`;
1770
- wrap.innerHTML = mk("all", "All prices", true) + bands.map((bd) => mk(bd.id, bd.label, false)).join("");
1771
- this.els.pricesSec.after(wrap);
1772
- this.priceFilterEl = wrap;
1773
- wrap.querySelectorAll("button").forEach((btn) => {
1774
- btn.addEventListener("click", () => {
1775
- wrap.querySelectorAll("button").forEach((b) => b.classList.toggle("on", b === btn));
1776
- const id = btn.dataset.band;
1777
- if (id === "all") {
1778
- this.priceBandKeys = null;
1779
- this.controller.setCategoryFilter(null);
1780
- } else {
1781
- const bd = bands.find((x) => x.id === id);
1782
- this.priceBandKeys = bd ? new Set(bd.keys) : null;
1783
- this.controller.setCategoryFilter(bd ? bd.keys : null);
1784
- }
1785
- this.syncPrices();
1786
- if (this.lastSection) this.showSectionCard(this.lastSection);
1787
- });
1877
+ const select = document.createElement("select");
1878
+ select.className = "sl-price-select";
1879
+ select.setAttribute("aria-label", "Filter and focus seats by price");
1880
+ select.innerHTML = `<option value="all">All prices</option>` + bands.map((band) => `<option value="${band.id}">${band.label}</option>`).join("");
1881
+ this.els.pricesSec.appendChild(select);
1882
+ this.priceFilterEl = select;
1883
+ select.addEventListener("change", () => {
1884
+ const band = bands.find((candidate) => candidate.id === select.value);
1885
+ const keys = band?.keys ?? null;
1886
+ this.priceBandKeys = keys ? new Set(keys) : null;
1887
+ this.controller.setCategoryFilter(keys);
1888
+ this.controller.focusCategoryFilter(keys);
1889
+ this.syncPrices();
1890
+ if (this.lastSection) this.showSectionCard(this.lastSection);
1788
1891
  });
1789
1892
  }
1790
1893
  // ---- arena / multi-floor chrome -------------------------------------------
@@ -2184,7 +2287,7 @@ var SeatPicker = class _SeatPicker {
2184
2287
  const price = c.tiers?.length ? c.tiers[0].price : c.price;
2185
2288
  const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
2186
2289
  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>`;
2187
- }).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>`;
2290
+ }).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>`;
2188
2291
  this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
2189
2292
  row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
2190
2293
  row.addEventListener("mouseleave", () => this.controller.getRenderer()?.setCategoryHighlight?.(null));
@@ -2203,6 +2306,7 @@ var SeatPicker = class _SeatPicker {
2203
2306
  }
2204
2307
  syncTray() {
2205
2308
  if (!this.els.tray) return;
2309
+ this.updateSelectionCapacity();
2206
2310
  const seats = this.committedSelection();
2207
2311
  const gaAreas = this.controller.getGAAreas();
2208
2312
  const heldItems = this.hold?.items ?? [];
@@ -2213,14 +2317,19 @@ var SeatPicker = class _SeatPicker {
2213
2317
  } else if (!seats.length && !heldItems.length) {
2214
2318
  parts.push(`<div class="sl-tray-hint">Tap a seat on the map \u2014 or grab standing tickets below.</div>`);
2215
2319
  }
2320
+ if (!this.hold) {
2321
+ const cats = this.controller.doc?.categories ?? [];
2322
+ 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>`);
2323
+ }
2216
2324
  for (const item of heldItems) {
2217
2325
  const itemKey = `held:${item.label}`;
2218
2326
  nextTrayKeys.add(itemKey);
2219
2327
  const cat = this.controller.doc?.categories.find((c) => c.key === item.categoryKey);
2220
2328
  const tierName = item.tierId ? cat?.tiers?.find((ti) => ti.id === item.tierId)?.name : void 0;
2221
2329
  const canView2 = this.seatViewEnabled() && item.objectType !== "ga" && !!this.controller.seatByLabel(item.label);
2330
+ const viewBtn = canView2 ? `<button type="button" class="view" data-view-label="${item.label}" aria-label="${(0, import_core2.t)("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>` : "";
2222
2331
  parts.push(
2223
- `<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="${(0, import_core2.t)("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>`
2332
+ `<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>`
2224
2333
  );
2225
2334
  }
2226
2335
  const heldLabels = new Set(heldItems.map((item) => item.label));
@@ -2232,7 +2341,7 @@ var SeatPicker = class _SeatPicker {
2232
2341
  const tierSelect = s.tiers && s.tiers.length ? `<select class="tier" data-tier="${s.id}" aria-label="${(0, import_core2.t)("picker.ticketTierFor", { label: s.label })}">` + s.tiers.map((ti) => `<option value="${ti.id}"${ti.id === s.tierId ? " selected" : ""}>${ti.name} \xB7 ${this.money(ti.price)}</option>`).join("") + `</select>` : "";
2233
2342
  const viewBtn = canView ? `<button type="button" class="view" data-view-label="${s.label}" aria-label="${(0, import_core2.t)("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>` : "";
2234
2343
  parts.push(
2235
- `<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>`
2344
+ `<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>`
2236
2345
  );
2237
2346
  }
2238
2347
  for (const area of gaAreas) {
@@ -2241,17 +2350,11 @@ var SeatPicker = class _SeatPicker {
2241
2350
  `<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>`
2242
2351
  );
2243
2352
  }
2244
- if (!this.hold) {
2245
- const cats = this.controller.doc?.categories ?? [];
2246
- parts.push(
2247
- `<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>`
2248
- );
2249
- }
2250
2353
  this.els.tray.innerHTML = parts.join("");
2251
2354
  this.lastTrayKeys = nextTrayKeys;
2252
2355
  this.els.tray.querySelectorAll("[data-ba]").forEach((btn) => {
2253
2356
  btn.addEventListener("click", () => {
2254
- this.baQty = Math.max(1, Math.min(8, this.baQty + Number(btn.dataset.ba)));
2357
+ this.baQty = Math.max(1, Math.min(this.maxTickets, this.baQty + Number(btn.dataset.ba)));
2255
2358
  this.syncTray();
2256
2359
  });
2257
2360
  });
@@ -2259,6 +2362,21 @@ var SeatPicker = class _SeatPicker {
2259
2362
  this.baCat = e.target.value;
2260
2363
  });
2261
2364
  this.els.tray.querySelector(".sl-ba-go")?.addEventListener("click", () => {
2365
+ if (this.pendingSelectionCount() > 0) {
2366
+ this.bestAvailableConfirm = true;
2367
+ this.syncTray();
2368
+ this.els.tray.querySelector("[data-ba-replace]")?.focus();
2369
+ return;
2370
+ }
2371
+ void this.bestAvailable(this.baQty, this.baCat || void 0);
2372
+ });
2373
+ this.els.tray.querySelector("[data-ba-cancel]")?.addEventListener("click", () => {
2374
+ this.bestAvailableConfirm = false;
2375
+ this.syncTray();
2376
+ this.els.tray.querySelector(".sl-ba-go")?.focus();
2377
+ });
2378
+ this.els.tray.querySelector("[data-ba-replace]")?.addEventListener("click", () => {
2379
+ this.bestAvailableConfirm = false;
2262
2380
  void this.bestAvailable(this.baQty, this.baCat || void 0);
2263
2381
  });
2264
2382
  this.els.tray.querySelectorAll(".sl-chip .rm").forEach((btn) => {
@@ -2269,12 +2387,26 @@ var SeatPicker = class _SeatPicker {
2269
2387
  return;
2270
2388
  }
2271
2389
  const id = chip.dataset.seat;
2272
- if (this.reducedMotion()) {
2390
+ const label = chip.querySelector("b")?.textContent ?? "Seat";
2391
+ const remove = () => {
2273
2392
  this.controller.deselect([id]);
2393
+ this.toast(`${label} removed.`, "neutral", {
2394
+ label: "Undo",
2395
+ onClick: () => {
2396
+ const restored = this.controller.select([id]);
2397
+ this.toast(
2398
+ restored.length ? `${label} restored.` : `${label} is no longer available.`,
2399
+ restored.length ? "success" : "warning"
2400
+ );
2401
+ }
2402
+ });
2403
+ };
2404
+ if (this.reducedMotion()) {
2405
+ remove();
2274
2406
  return;
2275
2407
  }
2276
2408
  chip.classList.add("sl-leave");
2277
- this.scheduleMotion(() => this.controller.deselect([id]), 150);
2409
+ this.scheduleMotion(remove, 150);
2278
2410
  });
2279
2411
  });
2280
2412
  this.els.tray.querySelectorAll(".sl-chip .tier").forEach((sel) => {
@@ -2291,13 +2423,15 @@ var SeatPicker = class _SeatPicker {
2291
2423
  const areaEl = btn.closest(".sl-ga");
2292
2424
  const id = areaEl.dataset.ga;
2293
2425
  const area = gaAreas.find((a) => a.id === id);
2294
- const next = Math.max(0, Math.min(area?.available ?? 0, (this.gaQty.get(id) ?? 0) + Number(btn.dataset.d)));
2426
+ const delta = Number(btn.dataset.d);
2427
+ if (delta > 0 && !this.canAddTicket()) return;
2428
+ const next = Math.max(0, Math.min(area?.available ?? 0, (this.gaQty.get(id) ?? 0) + delta));
2295
2429
  this.gaQty.set(id, next);
2296
2430
  this.syncTray();
2297
2431
  });
2298
2432
  });
2299
- const gaTotal = gaAreas.reduce((sum, a) => sum + a.price * (this.gaQty.get(a.id) ?? 0), 0);
2300
- const gaCount = [...this.gaQty.values()].reduce((a, b) => a + b, 0);
2433
+ const gaTotal = this.pendingGATotal(gaAreas);
2434
+ const gaCount = this.pendingGACount();
2301
2435
  const heldTotal = heldItems.reduce((sum, item) => sum + item.unitPrice * (item.quantity ?? 1), 0);
2302
2436
  const heldCount = heldItems.reduce((sum, item) => sum + (item.quantity ?? 1), 0);
2303
2437
  const freshSeats = seats.filter((seat) => !heldLabels.has(seat.label));
@@ -2308,14 +2442,24 @@ var SeatPicker = class _SeatPicker {
2308
2442
  const previousTotal = this.lastTrayTotal;
2309
2443
  this.els.count.textContent = count ? `${count} ${count === 1 ? "ticket" : "tickets"}` : "No seats selected";
2310
2444
  this.els.total.textContent = count ? this.money(total) : "";
2445
+ this.root?.setAttribute("data-has-selection", String(count > 0));
2446
+ this.els.foot?.classList.toggle("empty", count === 0);
2447
+ if (this.els.seatSummary) {
2448
+ this.els.seatSummary.textContent = count ? `${count} selected` : "";
2449
+ }
2311
2450
  this.syncCta(count, pendingCount);
2312
2451
  if (this.hold) {
2313
2452
  const securedCount = heldCount || this.hold.seats?.length || 0;
2314
2453
  if (this.els.holdTitle) {
2315
- this.els.holdTitle.textContent = `${securedCount} ${securedCount === 1 ? "seat" : "seats"} secured`;
2454
+ this.els.holdTitle.textContent = `${securedCount} secured`;
2316
2455
  }
2317
2456
  if (this.els.holdCopy) {
2318
- this.els.holdCopy.textContent = pendingCount ? `${pendingCount} more selected \u2014 secure before checkout.` : "Checkout timer is running.";
2457
+ this.els.holdCopy.textContent = pendingCount ? `${pendingCount} more selected` : "Checkout timer running";
2458
+ }
2459
+ const change = this.els.holdChange;
2460
+ if (change) {
2461
+ change.disabled = this.releasingHold;
2462
+ change.textContent = this.releasingHold ? "Releasing\u2026" : "Change";
2319
2463
  }
2320
2464
  }
2321
2465
  if (count !== previousCount) this.animateOnce(this.els.count, "sl-value-pop", 380);
@@ -2326,7 +2470,7 @@ var SeatPicker = class _SeatPicker {
2326
2470
  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>`;
2327
2471
  } else {
2328
2472
  const prices = (this.controller.doc?.categories ?? []).map((c) => this.catPrice(c)).filter((p) => p != null);
2329
- 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>`;
2473
+ 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>`;
2330
2474
  }
2331
2475
  }
2332
2476
  this.lastTrayCount = count;
@@ -2367,7 +2511,30 @@ var SeatPicker = class _SeatPicker {
2367
2511
  if (button?.isConnected) button.disabled = false;
2368
2512
  }
2369
2513
  }
2514
+ async handleChangeSeats() {
2515
+ if (!this.hold || this.releasingHold) return;
2516
+ this.releasingHold = true;
2517
+ const button = this.els.holdChange;
2518
+ if (button) {
2519
+ button.disabled = true;
2520
+ button.textContent = "Releasing\u2026";
2521
+ }
2522
+ try {
2523
+ await this.release();
2524
+ if (!this.hold) this.toast("Held tickets released. Choose your new seats.", "success");
2525
+ } finally {
2526
+ this.releasingHold = false;
2527
+ if (button?.isConnected) {
2528
+ button.disabled = false;
2529
+ button.textContent = "Change";
2530
+ }
2531
+ }
2532
+ }
2370
2533
  async handleCta() {
2534
+ if (this.totalTicketCount() > this.maxTickets) {
2535
+ this.toast(`Remove tickets until your order has ${this.maxTickets} or fewer.`, "warning");
2536
+ return;
2537
+ }
2371
2538
  const committed = this.committedSelection();
2372
2539
  if (this.hold && !committed.some((s) => !(this.hold.items ?? []).some((i) => i.label === s.label))) {
2373
2540
  const seats = this.hold.seats ?? committed;
@@ -2405,7 +2572,10 @@ var SeatPicker = class _SeatPicker {
2405
2572
  this.opts.onCheckout?.(hold, hold.seats ?? chosenSeats, this.buildHandoff(hold));
2406
2573
  } catch (err) {
2407
2574
  this.opts.onError?.(err);
2408
- this.toast("One or more seats were just taken. Please pick again.", "error");
2575
+ const problem = err;
2576
+ const labels = (problem.conflicts ?? []).map((conflict) => conflict.label).filter(Boolean).slice(0, 3);
2577
+ 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.";
2578
+ this.toast(message, "error");
2409
2579
  this.setCtaPhase("idle");
2410
2580
  } finally {
2411
2581
  this.holdingLabels.clear();
@@ -2528,15 +2698,28 @@ var SeatPicker = class _SeatPicker {
2528
2698
  hold ? this.buildHandoff(hold) : null
2529
2699
  );
2530
2700
  }
2531
- toast(msg, tone = "neutral") {
2701
+ toast(msg, tone = "neutral", action) {
2532
2702
  const el = this.els.toast;
2533
2703
  if (!el) return;
2534
- el.textContent = msg;
2704
+ el.replaceChildren();
2705
+ const copy = document.createElement("span");
2706
+ copy.textContent = msg;
2707
+ el.appendChild(copy);
2708
+ el.classList.toggle("has-action", !!action);
2709
+ if (action) {
2710
+ const button = document.createElement("button");
2711
+ button.type = "button";
2712
+ button.className = "sl-toast-action";
2713
+ button.textContent = action.label;
2714
+ button.addEventListener("click", action.onClick, { once: true });
2715
+ el.appendChild(button);
2716
+ }
2535
2717
  el.dataset.tone = tone;
2536
2718
  el.classList.add("on");
2537
2719
  if (this.toastTimer) clearTimeout(this.toastTimer);
2538
2720
  this.toastTimer = setTimeout(() => {
2539
2721
  el.classList.remove("on");
2722
+ el.classList.remove("has-action");
2540
2723
  el.dataset.tone = "neutral";
2541
2724
  }, 4200);
2542
2725
  }
@@ -2586,7 +2769,9 @@ var SeatPicker = class _SeatPicker {
2586
2769
  }
2587
2770
  async bestAvailable(qty, categoryKey) {
2588
2771
  if (this.bestAvailableBusy) return null;
2772
+ qty = Math.max(1, Math.min(this.maxTickets, Math.floor(qty)));
2589
2773
  if (this.confirmSeat) this.cancelConfirm();
2774
+ this.bestAvailableConfirm = false;
2590
2775
  this.bestAvailableBusy = true;
2591
2776
  const button = this.els.tray?.querySelector(".sl-ba-go");
2592
2777
  if (button) {
@@ -2599,6 +2784,7 @@ var SeatPicker = class _SeatPicker {
2599
2784
  this.hold = { holdId: h.holdId, expiresAt: h.expiresAt, seats: h.seats, items: h.items };
2600
2785
  this.handedOff = false;
2601
2786
  this.bookedShown = false;
2787
+ this.gaQty.clear();
2602
2788
  this.startHoldTimer(h.expiresAt);
2603
2789
  this.flashHeldSeats(this.hold);
2604
2790
  this.syncTray();
@@ -2608,14 +2794,13 @@ var SeatPicker = class _SeatPicker {
2608
2794
  return null;
2609
2795
  } catch (err) {
2610
2796
  this.opts.onError?.(err);
2611
- this.toast("Those seats are no longer available. Try another quantity or category.", "error");
2797
+ const reason = err?.reason;
2798
+ 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.";
2799
+ this.toast(message, "error");
2612
2800
  return null;
2613
2801
  } finally {
2614
2802
  this.bestAvailableBusy = false;
2615
- if (!this.hold && button?.isConnected) {
2616
- button.disabled = false;
2617
- button.textContent = "Best available";
2618
- }
2803
+ this.syncTray();
2619
2804
  }
2620
2805
  }
2621
2806
  async release() {