@seatlayer/js 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -564,12 +564,8 @@ declare class SeatPicker {
564
564
  private catPrice;
565
565
  /** Derive price bands: one chip per distinct price (≤5), else quantile ranges. */
566
566
  private priceBands;
567
- /**
568
- * Build the price-band chip row in the side panel, directly under the "Prices"
569
- * header and above the legend it dims. Living in the panel (not a map overlay)
570
- * keeps it clear of the top-center rung pills and top-left a11y chips, and it
571
- * rides the bottom sheet on narrow layouts. Skipped when there's <2 bands.
572
- */
567
+ /** Build the compact price selector in the panel header. Choosing a band both
568
+ * filters availability and smoothly frames the matching seats on the map. */
573
569
  private buildPriceFilter;
574
570
  /** Build the rung pills (charts with sections) and floor switcher (>1 floor). */
575
571
  private buildArenaChrome;
package/dist/index.d.ts CHANGED
@@ -564,12 +564,8 @@ declare class SeatPicker {
564
564
  private catPrice;
565
565
  /** Derive price bands: one chip per distinct price (≤5), else quantile ranges. */
566
566
  private priceBands;
567
- /**
568
- * Build the price-band chip row in the side panel, directly under the "Prices"
569
- * header and above the legend it dims. Living in the panel (not a map overlay)
570
- * keeps it clear of the top-center rung pills and top-left a11y chips, and it
571
- * rides the bottom sheet on narrow layouts. Skipped when there's <2 bands.
572
- */
567
+ /** Build the compact price selector in the panel header. Choosing a band both
568
+ * filters availability and smoothly frames the matching seats on the map. */
573
569
  private buildPriceFilter;
574
570
  /** Build the rung pills (charts with sections) and floor switcher (>1 floor). */
575
571
  private buildArenaChrome;
package/dist/index.js CHANGED
@@ -481,19 +481,21 @@ var CSS = `
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,70 @@ 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-price-row{display:flex;align-items:center;gap:7px;min-height:28px;font-size:12px}
546
563
  .sl-dot{width:9px;height:9px;border-radius:50%;flex:none}
547
564
  .sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
548
565
  .sl-price-left{font-size:11px;color:var(--sl-muted);font-variant-numeric:tabular-nums}
549
566
  .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}
567
+ .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
568
  .sl-status-item{display:inline-flex;align-items:center;gap:6px}
552
569
  .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}
570
+ color:#fff;background:#6b7280;line-height:1}
571
+ .sl-status-icon svg{width:10px;height:10px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
572
+ .sl-status-icon.sold{background:#8b93a0}
573
+ .sl-status-icon.sold svg{width:11px;height:11px;stroke-width:2.4}
555
574
 
556
575
  /* tray */
557
- .sl-tray{flex:1;padding:10px 16px;display:flex;flex-direction:column;gap:8px;min-height:0}
576
+ .sl-seats-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
577
+ .sl-seat-summary{font-size:10px;letter-spacing:0;text-transform:none;white-space:nowrap}
578
+ .sl-tray{flex:1;padding:10px 14px;display:flex;flex-direction:column;gap:7px;min-height:0}
558
579
  .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}
580
+ .sl-chip{position:relative;display:grid;grid-template-columns:24px minmax(0,1fr) auto 30px;align-items:center;gap:8px;
581
+ min-height:53px;padding:7px 7px 7px 9px;border:1px solid var(--sl-line);border-radius:var(--sl-r-sm);
582
+ background:var(--sl-surface);font-size:13px;transform-origin:center;transition:border-color .15s,background .15s}
583
+ .sl-chip:hover{border-color:color-mix(in srgb,var(--sl-accent) 38%,var(--sl-line))}
561
584
  .sl-chip.sl-enter{animation:slChipIn .38s cubic-bezier(.2,.8,.2,1) both}
562
585
  .sl-chip.sl-leave{pointer-events:none;animation:slChipOut .16s ease-in both}
563
586
  .sl-chip.sl-held{border-color:var(--sl-line);background:color-mix(in srgb,var(--sl-accent) 7%,var(--sl-surface));
564
587
  box-shadow:inset 3px 0 0 color-mix(in srgb,var(--sl-accent) 72%,transparent)}
565
- .sl-chip b{font-weight:800}
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}
567
- .sl-chip .amt{font-weight:700;font-variant-numeric:tabular-nums}
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)}
588
+ .sl-ticket-state{width:23px;height:23px;border-radius:999px;display:flex;align-items:center;justify-content:center;
589
+ background:var(--sl-accent);color:var(--sl-accent-ink)}
590
+ .sl-ticket-state.held{background:color-mix(in srgb,var(--sl-accent) 18%,var(--sl-surface));color:var(--sl-accent)}
591
+ .sl-ticket-state svg{width:13px;height:13px;stroke:currentColor;stroke-width:2.4;fill:none;stroke-linecap:round;stroke-linejoin:round}
592
+ .sl-chip-main{min-width:0}
593
+ .sl-chip b{display:block;font-weight:800;min-width:max-content;white-space:nowrap}
594
+ .sl-chip-sub{display:flex;align-items:center;gap:5px;min-width:0;margin-top:2px}
595
+ .sl-chip .cat{color:var(--sl-muted);font-size:10.5px;flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
596
+ .sl-chip .amt{font-weight:700;font-variant-numeric:tabular-nums;flex:none;white-space:nowrap}
597
+ .sl-chip .rm{width:29px;height:29px;border-radius:8px;flex:none;display:flex;align-items:center;justify-content:center;color:var(--sl-muted)}
598
+ .sl-chip .rm:hover,.sl-chip .rm:focus-visible{color:var(--sl-accent);background:color-mix(in srgb,var(--sl-accent) 10%,transparent)}
573
599
  .sl-chip .rm svg{width:11px;height:11px;stroke:currentColor;stroke-width:2.4;fill:none;stroke-linecap:round}
574
600
 
575
601
  /* GA rows */
@@ -733,29 +759,43 @@ var CSS = `
733
759
  .sl-picker[data-layout="narrow"] .sl-confirm{left:50%!important;top:auto!important;bottom:14px;width:min(342px,calc(100% - 24px));
734
760
  transform:translateX(-50%);animation:slConfirmMobileIn .24s cubic-bezier(.2,.8,.2,1) both}
735
761
 
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}
762
+ /* Best available is a first-class shortcut, not an anonymous utility row. */
763
+ .sl-ba{position:relative;overflow:hidden;display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:7px;
764
+ padding:13px;border:1px solid color-mix(in srgb,var(--sl-accent) 34%,var(--sl-line));border-radius:13px;
765
+ 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)))}
766
+ .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}
767
+ .sl-ba-title,.sl-ba-copy,.sl-ba select,.sl-ba-qty,.sl-ba-go{position:relative;z-index:1}
768
+ .sl-ba-title{grid-column:1/-1;display:flex;align-items:center;gap:7px;font-size:13px;font-weight:850}
769
+ .sl-ba-title .spark{color:var(--sl-accent);font-size:16px}
770
+ .sl-ba-copy{grid-column:1/-1;margin:-4px 0 2px 23px;color:var(--sl-muted);font-size:10.5px;line-height:1.35}
771
+ .sl-ba-copy .narrow{display:none}
772
+ .sl-ba select{background:var(--sl-surface);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:8px;
773
+ font:inherit;font-size:11px;padding:7px 8px;min-width:0;width:100%;max-width:none}
774
+ .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)}
775
+ .sl-ba-qty button{width:25px;height:25px;border-radius:7px;background:color-mix(in srgb,var(--sl-line) 35%,transparent);border:0;
776
+ font-size:14px;font-weight:800;display:flex;align-items:center;justify-content:center}
743
777
  .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}
778
+ .sl-picker .sl-ba-go{grid-column:1/-1;width:100%;min-height:37px;padding:7px 12px;border-radius:9px;background:var(--sl-accent);
779
+ 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;
780
+ box-shadow:0 8px 18px color-mix(in srgb,var(--sl-accent) 18%,transparent)}
781
+ .sl-picker .sl-ba-go:hover{filter:brightness(1.06)}
782
+ .sl-picker .sl-ba-go:disabled{opacity:.62;cursor:wait}
783
+ .sl-picker[data-layout="narrow"] .sl-ba{padding:11px}
784
+ .sl-picker[data-layout="narrow"] .sl-ba-copy .wide{display:none}
785
+ .sl-picker[data-layout="narrow"] .sl-ba-copy .narrow{display:inline}
748
786
 
749
787
  /* screen-reader live region */
750
788
  .sl-sr{position:absolute;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
751
789
 
752
790
  /* 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}
791
+ .sl-chip .tier{background:var(--sl-bg);color:var(--sl-text);border:1px solid var(--sl-line);border-radius:6px;
792
+ font:inherit;font-size:10px;padding:2px 4px;min-width:0;max-width:100%;cursor:pointer}
793
+ .sl-chip .view{width:20px;height:20px;border-radius:999px;flex:none;display:flex;align-items:center;justify-content:center;
794
+ color:var(--sl-muted);opacity:.36;transition:color .15s,opacity .15s}
757
795
  .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}
796
+ .sl-chip:hover .view,.sl-chip .view:focus-visible{opacity:1;color:var(--sl-text)}
797
+ .sl-chip .view svg{width:12px;height:12px;stroke:currentColor;stroke-width:2;fill:none;stroke-linecap:round;stroke-linejoin:round}
798
+ @media(pointer:coarse){.sl-chip .view{opacity:.58}}
759
799
 
760
800
  /* arena: LOD rung pills (flow within the top-center region) */
761
801
  .sl-rungs{display:none;background:var(--sl-surface);border:1px solid var(--sl-line);border-radius:999px;padding:3px}
@@ -842,10 +882,6 @@ var CSS = `
842
882
  .sl-minimap canvas{display:block}
843
883
  .sl-picker[data-layout="narrow"] .sl-minimap{display:none}
844
884
 
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
885
  /* F4 legend reflection: rows + counts for out-of-band categories read muted */
850
886
  .sl-price-row.sl-dim{opacity:.4}
851
887
  .sl-seccard-mix-item.sl-dim{opacity:.4}
@@ -1141,18 +1177,18 @@ var SeatPicker = class _SeatPicker {
1141
1177
  <div class="sl-sheet-grab"></div>
1142
1178
  <div class="sl-sheet-bar">
1143
1179
  <div class="sl-sheet-peek" data-ref="peek"></div>
1144
- <span class="sl-sheet-chevron" aria-hidden="true">
1180
+ <button type="button" class="sl-sheet-toggle" data-ref="sheetToggle" aria-label="Open ticket panel" aria-expanded="false">
1145
1181
  <svg viewBox="0 0 24 24"><path d="M6 15l6-6 6 6"/></svg>
1146
- </span>
1182
+ </button>
1147
1183
  </div>
1148
1184
  </div>
1149
1185
  <div class="sl-sec sl-filtersec" data-ref="filtersSec">Filters</div>
1150
1186
  <div class="sl-filters" data-ref="filters"></div>
1151
- <div class="sl-sec sl-prices-sec" data-ref="pricesSec">Prices</div>
1187
+ <div class="sl-sec sl-prices-sec" data-ref="pricesSec"><span>Ticket prices</span></div>
1152
1188
  <div class="sl-prices" data-ref="prices"></div>
1153
- <div class="sl-sec sl-seats-sec">Your seats</div>
1189
+ <div class="sl-sec sl-seats-sec"><span>Your seats</span><span class="sl-seat-summary" data-ref="seatSummary"></span></div>
1154
1190
  <div class="sl-tray" data-ref="tray"></div>
1155
- <div class="sl-foot">
1191
+ <div class="sl-foot" data-ref="foot">
1156
1192
  <div class="sl-hold-note" data-ref="holdNote" role="status" aria-live="polite">
1157
1193
  <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M20 6L9 17l-5-5"/></svg>
1158
1194
  <span><b data-ref="holdTitle">Seats secured</b><span class="sl-hold-copy" data-ref="holdCopy">Checkout timer is running.</span></span>
@@ -1184,9 +1220,17 @@ var SeatPicker = class _SeatPicker {
1184
1220
  this.els.zfit.addEventListener("click", () => this.controller.zoomToFit());
1185
1221
  const head = this.els.sheetHead;
1186
1222
  if (head) {
1223
+ const toggle = this.els.sheetToggle;
1187
1224
  const setSheet = (open) => {
1188
1225
  root.dataset.sheet = open ? "open" : "peek";
1226
+ toggle?.setAttribute("aria-expanded", String(open));
1227
+ toggle?.setAttribute("aria-label", open ? "Collapse ticket panel" : "Open ticket panel");
1189
1228
  };
1229
+ setSheet(root.dataset.sheet === "open");
1230
+ toggle?.addEventListener("click", (e) => {
1231
+ e.stopPropagation();
1232
+ setSheet(root.dataset.sheet !== "open");
1233
+ });
1190
1234
  let startY = 0;
1191
1235
  let swiped = false;
1192
1236
  let tracking = false;
@@ -1209,7 +1253,7 @@ var SeatPicker = class _SeatPicker {
1209
1253
  });
1210
1254
  head.addEventListener("pointerup", (e) => {
1211
1255
  if (tracking && !swiped && Math.abs(e.clientY - startY) < 6) {
1212
- if (!e.target.closest(".sl-seccard")) setSheet(root.dataset.sheet !== "open");
1256
+ if (!e.target.closest(".sl-seccard,.sl-sheet-toggle")) setSheet(root.dataset.sheet !== "open");
1213
1257
  }
1214
1258
  tracking = false;
1215
1259
  head.releasePointerCapture?.(e.pointerId);
@@ -1728,39 +1772,26 @@ var SeatPicker = class _SeatPicker {
1728
1772
  }
1729
1773
  return bands;
1730
1774
  }
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
- */
1775
+ /** Build the compact price selector in the panel header. Choosing a band both
1776
+ * filters availability and smoothly frames the matching seats on the map. */
1737
1777
  buildPriceFilter() {
1738
1778
  if (!this.els.prices || !this.els.pricesSec) return;
1739
1779
  const bands = this.priceBands();
1740
1780
  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
- });
1781
+ const select = document.createElement("select");
1782
+ select.className = "sl-price-select";
1783
+ select.setAttribute("aria-label", "Filter and focus seats by price");
1784
+ select.innerHTML = `<option value="all">All prices</option>` + bands.map((band) => `<option value="${band.id}">${band.label}</option>`).join("");
1785
+ this.els.pricesSec.appendChild(select);
1786
+ this.priceFilterEl = select;
1787
+ select.addEventListener("change", () => {
1788
+ const band = bands.find((candidate) => candidate.id === select.value);
1789
+ const keys = band?.keys ?? null;
1790
+ this.priceBandKeys = keys ? new Set(keys) : null;
1791
+ this.controller.setCategoryFilter(keys);
1792
+ this.controller.focusCategoryFilter(keys);
1793
+ this.syncPrices();
1794
+ if (this.lastSection) this.showSectionCard(this.lastSection);
1764
1795
  });
1765
1796
  }
1766
1797
  // ---- arena / multi-floor chrome -------------------------------------------
@@ -2160,7 +2191,7 @@ var SeatPicker = class _SeatPicker {
2160
2191
  const price = c.tiers?.length ? c.tiers[0].price : c.price;
2161
2192
  const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
2162
2193
  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>`;
2194
+ }).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
2195
  this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
2165
2196
  row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
2166
2197
  row.addEventListener("mouseleave", () => this.controller.getRenderer()?.setCategoryHighlight?.(null));
@@ -2189,14 +2220,21 @@ var SeatPicker = class _SeatPicker {
2189
2220
  } else if (!seats.length && !heldItems.length) {
2190
2221
  parts.push(`<div class="sl-tray-hint">Tap a seat on the map \u2014 or grab standing tickets below.</div>`);
2191
2222
  }
2223
+ if (!this.hold) {
2224
+ const cats = this.controller.doc?.categories ?? [];
2225
+ parts.push(
2226
+ `<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>`
2227
+ );
2228
+ }
2192
2229
  for (const item of heldItems) {
2193
2230
  const itemKey = `held:${item.label}`;
2194
2231
  nextTrayKeys.add(itemKey);
2195
2232
  const cat = this.controller.doc?.categories.find((c) => c.key === item.categoryKey);
2196
2233
  const tierName = item.tierId ? cat?.tiers?.find((ti) => ti.id === item.tierId)?.name : void 0;
2197
2234
  const canView2 = this.seatViewEnabled() && item.objectType !== "ga" && !!this.controller.seatByLabel(item.label);
2235
+ 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
2236
  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">Held by you</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>`
2237
+ `<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
2238
  );
2201
2239
  }
2202
2240
  const heldLabels = new Set(heldItems.map((item) => item.label));
@@ -2208,7 +2246,7 @@ var SeatPicker = class _SeatPicker {
2208
2246
  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
2247
  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
2248
  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>`
2249
+ `<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
2250
  );
2213
2251
  }
2214
2252
  for (const area of gaAreas) {
@@ -2217,12 +2255,6 @@ var SeatPicker = class _SeatPicker {
2217
2255
  `<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
2256
  );
2219
2257
  }
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
2258
  this.els.tray.innerHTML = parts.join("");
2227
2259
  this.lastTrayKeys = nextTrayKeys;
2228
2260
  this.els.tray.querySelectorAll("[data-ba]").forEach((btn) => {
@@ -2284,6 +2316,11 @@ var SeatPicker = class _SeatPicker {
2284
2316
  const previousTotal = this.lastTrayTotal;
2285
2317
  this.els.count.textContent = count ? `${count} ${count === 1 ? "ticket" : "tickets"}` : "No seats selected";
2286
2318
  this.els.total.textContent = count ? this.money(total) : "";
2319
+ this.root?.setAttribute("data-has-selection", String(count > 0));
2320
+ this.els.foot?.classList.toggle("empty", count === 0);
2321
+ if (this.els.seatSummary) {
2322
+ this.els.seatSummary.textContent = count ? `${count} selected` : "";
2323
+ }
2287
2324
  this.syncCta(count, pendingCount);
2288
2325
  if (this.hold) {
2289
2326
  const securedCount = heldCount || this.hold.seats?.length || 0;
@@ -2302,7 +2339,7 @@ var SeatPicker = class _SeatPicker {
2302
2339
  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
2340
  } else {
2304
2341
  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>`;
2342
+ 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
2343
  }
2307
2344
  }
2308
2345
  this.lastTrayCount = count;