@seatlayer/js 0.51.0 → 0.52.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.cjs +687 -224
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -8
- package/dist/index.d.ts +43 -8
- package/dist/index.js +691 -224
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -2114,6 +2114,13 @@ var BuyerAssetObjectUrls = class {
|
|
|
2114
2114
|
}
|
|
2115
2115
|
};
|
|
2116
2116
|
|
|
2117
|
+
// src/SeatPicker.ts
|
|
2118
|
+
import {
|
|
2119
|
+
createMinimapTransform,
|
|
2120
|
+
minimapPointToWorld,
|
|
2121
|
+
minimapViewportPlan
|
|
2122
|
+
} from "@seatlayer/core/picker/minimapGeometry";
|
|
2123
|
+
|
|
2117
2124
|
// src/offerAvailability.ts
|
|
2118
2125
|
var SALE_STATES = [
|
|
2119
2126
|
"on-sale",
|
|
@@ -2252,6 +2259,12 @@ function tfStatic(key, fallback) {
|
|
|
2252
2259
|
const v = t2(key);
|
|
2253
2260
|
return v === key ? fallback : v;
|
|
2254
2261
|
}
|
|
2262
|
+
var chartsLoadedThisPage = /* @__PURE__ */ new Set();
|
|
2263
|
+
function markChartLoaded(eventKey) {
|
|
2264
|
+
const seen = chartsLoadedThisPage.has(eventKey);
|
|
2265
|
+
chartsLoadedThisPage.add(eventKey);
|
|
2266
|
+
return seen;
|
|
2267
|
+
}
|
|
2255
2268
|
function pointInPolygon(x, y, poly) {
|
|
2256
2269
|
let inside = false;
|
|
2257
2270
|
for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
|
@@ -2354,9 +2367,13 @@ var CSS = (
|
|
|
2354
2367
|
the WIDGET's meanings, not an org's brand: premium gold, environment
|
|
2355
2368
|
warning, danger, success. Hosts can still override them with CSS. */
|
|
2356
2369
|
--sl-premium:#e8c15a;--sl-premium-deep:#c9a24b;--sl-premium-ink:#1c1608;
|
|
2357
|
-
--sl-
|
|
2370
|
+
--sl-premium-text:color-mix(in srgb,var(--sl-premium-deep) 48%,var(--sl-text));
|
|
2371
|
+
--sl-warn:#d8a425;--sl-warn-text:color-mix(in srgb,var(--sl-warn) 48%,var(--sl-text));
|
|
2372
|
+
--sl-danger:#e5484d;--sl-success:#22a06b}
|
|
2358
2373
|
.sl-picker *{box-sizing:border-box;margin:0;padding:0}
|
|
2359
|
-
.
|
|
2374
|
+
/* Keep the reset intentionally specificity-free. Standalone map and panel
|
|
2375
|
+
controls must be able to establish their own token surfaces in either theme. */
|
|
2376
|
+
.sl-picker :where(button){font:inherit;color:inherit;background:none;border:0;cursor:pointer}
|
|
2360
2377
|
|
|
2361
2378
|
/* header */
|
|
2362
2379
|
.sl-head{display:flex;align-items:center;gap:12px;padding:12px 16px;border-bottom:1px solid var(--sl-line);flex:none}
|
|
@@ -2552,11 +2569,11 @@ var CSS = (
|
|
|
2552
2569
|
.sl-offer{display:none;margin:12px 14px 2px;padding:12px;border:1px solid color-mix(in srgb,var(--sl-accent) 34%,var(--sl-line));
|
|
2553
2570
|
border-radius:12px;background:color-mix(in srgb,var(--sl-accent) 8%,var(--sl-surface));color:var(--sl-text)}
|
|
2554
2571
|
.sl-offer.has{display:block}.sl-offer-main{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}
|
|
2555
|
-
.sl-offer-copy{min-width:0}.sl-offer-kicker{display:block;font-size:10px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-accent)}
|
|
2572
|
+
.sl-offer-copy{min-width:0}.sl-offer-kicker{display:block;font-size:10px;font-weight:800;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-accent-text)}
|
|
2556
2573
|
.sl-offer-name{display:block;margin-top:2px;font-size:13px;font-weight:800;line-height:1.3}.sl-offer-line{display:block;margin-top:3px;font-size:11px;line-height:1.35;color:var(--sl-muted)}
|
|
2557
2574
|
.sl-offer-info{position:relative;flex:none}.sl-offer-info>summary{list-style:none;width:25px;height:25px;border:1px solid var(--sl-line);border-radius:999px;
|
|
2558
2575
|
display:grid;place-items:center;cursor:pointer;font-size:12px;font-weight:850;color:var(--sl-text);background:var(--sl-surface)}
|
|
2559
|
-
.sl-offer-info>summary::-webkit-details-marker{display:none}.sl-offer-info[open]>summary{border-color:var(--sl-accent);color:var(--sl-accent)}
|
|
2576
|
+
.sl-offer-info>summary::-webkit-details-marker{display:none}.sl-offer-info[open]>summary{border-color:var(--sl-accent);color:var(--sl-accent-text)}
|
|
2560
2577
|
.sl-offer-detail{margin-top:10px;padding-top:9px;border-top:1px solid var(--sl-line);font-size:10.5px;line-height:1.45;color:var(--sl-muted)}
|
|
2561
2578
|
.sl-sec{padding:14px 14px 4px;font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--sl-muted);font-weight:700}
|
|
2562
2579
|
.sl-prices-sec{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:13px}
|
|
@@ -2574,8 +2591,8 @@ var CSS = (
|
|
|
2574
2591
|
padding:0 6px;margin:0 -6px;border-radius:8px;cursor:pointer;transition:background .15s}
|
|
2575
2592
|
.sl-price-row:hover,.sl-price-row:focus-visible{background:color-mix(in srgb,var(--sl-line) 40%,transparent)}
|
|
2576
2593
|
.sl-price-row.sl-active{background:color-mix(in srgb,var(--sl-accent) 9%,transparent)}
|
|
2577
|
-
.sl-price-was{margin-left:auto;color:var(--sl-muted);font-size:10px;text-decoration:line-through}.sl-price-offer{display:block;color:var(--sl-accent);font-size:10px;font-weight:750}
|
|
2578
|
-
.sl-price-row.sl-active .sl-price-label{color:var(--sl-accent)}
|
|
2594
|
+
.sl-price-was{margin-left:auto;color:var(--sl-muted);font-size:10px;text-decoration:line-through}.sl-price-offer{display:block;color:var(--sl-accent-text);font-size:10px;font-weight:750}
|
|
2595
|
+
.sl-price-row.sl-active .sl-price-label{color:var(--sl-accent-text)}
|
|
2579
2596
|
.sl-dot{width:9px;height:9px;border-radius:50%;flex:none}
|
|
2580
2597
|
.sl-price-label{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
|
|
2581
2598
|
.sl-price-left{font-size:11px;color:var(--sl-muted);font-variant-numeric:tabular-nums}
|
|
@@ -2610,7 +2627,7 @@ var CSS = (
|
|
|
2610
2627
|
box-shadow:inset 3px 0 0 color-mix(in srgb,var(--sl-accent) 72%,transparent)}
|
|
2611
2628
|
.sl-ticket-state{width:17px;height:17px;border-radius:999px;flex:none;display:flex;align-items:center;justify-content:center;
|
|
2612
2629
|
background:var(--sl-accent);color:var(--sl-accent-ink)}
|
|
2613
|
-
.sl-ticket-state.held{background:color-mix(in srgb,var(--sl-accent) 18%,var(--sl-surface));color:var(--sl-accent)}
|
|
2630
|
+
.sl-ticket-state.held{background:color-mix(in srgb,var(--sl-accent) 18%,var(--sl-surface));color:var(--sl-accent-text)}
|
|
2614
2631
|
.sl-ticket-state svg{width:10px;height:10px;stroke:currentColor;stroke-width:2.6;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
2615
2632
|
.sl-chip-main{min-width:0;padding:8px 10px 8px 11px;display:flex;flex-direction:column;justify-content:center;gap:5px}
|
|
2616
2633
|
.sl-chip-id{display:flex;gap:12px;min-width:0}
|
|
@@ -2653,7 +2670,7 @@ var CSS = (
|
|
|
2653
2670
|
.sl-ga-qty span{min-width:16px;text-align:center;font-weight:800;font-variant-numeric:tabular-nums}
|
|
2654
2671
|
|
|
2655
2672
|
/* footer */
|
|
2656
|
-
.sl-foot{position:relative;z-index:2;padding:
|
|
2673
|
+
.sl-foot{position:relative;z-index:2;padding:10px 16px 8px;border-top:1px solid var(--sl-line);flex:none;
|
|
2657
2674
|
background:var(--sl-bg);box-shadow:0 -10px 24px -22px rgba(0,0,0,.72)}
|
|
2658
2675
|
.sl-hold-note{display:none;align-items:center;gap:7px;margin-bottom:8px;padding:7px 8px;border-radius:var(--sl-r-sm);
|
|
2659
2676
|
border:1px solid var(--sl-line);background:color-mix(in srgb,var(--sl-accent) 7%,var(--sl-surface));
|
|
@@ -2667,14 +2684,13 @@ var CSS = (
|
|
|
2667
2684
|
.sl-hold-note>span{flex:1;min-width:0}
|
|
2668
2685
|
.sl-hold-change{flex:none;min-height:30px;padding:5px 8px;border-radius:8px;border:1px solid var(--sl-line);
|
|
2669
2686
|
color:var(--sl-text);font-size:10.5px;font-weight:750;white-space:nowrap}
|
|
2670
|
-
.sl-hold-change:hover,.sl-hold-change:focus-visible{border-color:var(--sl-accent);color:var(--sl-accent)}
|
|
2687
|
+
.sl-hold-change:hover,.sl-hold-change:focus-visible{border-color:var(--sl-accent);color:var(--sl-accent-text)}
|
|
2671
2688
|
.sl-hold-change:disabled{opacity:.58;cursor:wait}
|
|
2672
2689
|
.sl-total{display:flex;justify-content:space-between;align-items:center;font-size:13px;margin-bottom:10px}
|
|
2673
2690
|
.sl-total b{font-size:17px;font-variant-numeric:tabular-nums}
|
|
2674
2691
|
.sl-value-pop{animation:slValuePop .32s cubic-bezier(.2,.8,.2,1)}
|
|
2675
|
-
/* Primary checkout CTA.
|
|
2676
|
-
|
|
2677
|
-
and the button renders as plain text with no accent fill. */
|
|
2692
|
+
/* Primary checkout CTA. The extra root scope documents that this is widget
|
|
2693
|
+
chrome and keeps the contract resilient when embedded beside host styles. */
|
|
2678
2694
|
.sl-picker .sl-cta{display:flex;align-items:center;justify-content:center;width:100%;min-height:44px;
|
|
2679
2695
|
padding:12px 16px;border-radius:var(--sl-r-sm);font-weight:800;font-size:14px;line-height:1.1;
|
|
2680
2696
|
background:var(--sl-accent);color:var(--sl-accent-ink);
|
|
@@ -2685,8 +2701,8 @@ var CSS = (
|
|
|
2685
2701
|
.sl-cta-spin,.sl-ba-spin{width:14px;height:14px;border-radius:50%;border:2px solid currentColor;border-right-color:transparent;
|
|
2686
2702
|
animation:slspin .7s linear infinite;flex:none}
|
|
2687
2703
|
/* Disabled ("Select seats"): quieter, but still a full-width button shape. */
|
|
2688
|
-
.sl-picker .sl-cta:disabled{background:var(--sl-surface);color:var(--sl-muted);
|
|
2689
|
-
cursor:not-allowed;filter:none;transform:none}
|
|
2704
|
+
.sl-picker .sl-cta:disabled{background:color-mix(in srgb,var(--sl-bg) 35%,var(--sl-surface));color:var(--sl-muted);
|
|
2705
|
+
box-shadow:inset 0 0 0 1px var(--sl-line);opacity:1;cursor:not-allowed;filter:none;transform:none}
|
|
2690
2706
|
|
|
2691
2707
|
/* Chrome anchor regions (Feature 6) \u2014 every interactive map overlay is APPENDED
|
|
2692
2708
|
INTO one of these positioned flex containers and flows/stacks within it, so no
|
|
@@ -2716,7 +2732,7 @@ var CSS = (
|
|
|
2716
2732
|
in the same region, so no displacement hack is needed. */
|
|
2717
2733
|
.sl-testbadge{display:inline-flex;align-items:center;padding:5px 10px;border-radius:7px;pointer-events:none;
|
|
2718
2734
|
font-size:10px;font-weight:850;letter-spacing:.13em;line-height:1.2;text-transform:uppercase;white-space:nowrap;
|
|
2719
|
-
color:var(--sl-warn);background:color-mix(in srgb,var(--sl-warn) 13%,var(--sl-surface));
|
|
2735
|
+
color:var(--sl-warn-text);background:color-mix(in srgb,var(--sl-warn) 13%,var(--sl-surface));
|
|
2720
2736
|
border:1px dashed color-mix(in srgb,var(--sl-warn) 55%,transparent)}
|
|
2721
2737
|
|
|
2722
2738
|
/* zoom column (flows within the bottom-right region) */
|
|
@@ -2783,7 +2799,7 @@ var CSS = (
|
|
|
2783
2799
|
.sl-extend-btn{flex:none;padding:8px 14px;border-radius:999px;font-weight:800;font-size:12.5px;
|
|
2784
2800
|
background:var(--sl-accent);color:var(--sl-accent-ink);transition:filter .15s,opacity .15s}
|
|
2785
2801
|
.sl-extend-btn:hover{filter:brightness(1.08)}
|
|
2786
|
-
.sl-extend-btn:disabled{opacity:.
|
|
2802
|
+
.sl-extend-btn:disabled{opacity:.62;cursor:not-allowed}
|
|
2787
2803
|
|
|
2788
2804
|
/* booked confirmation overlay (covers the widget once the held seats are sold) */
|
|
2789
2805
|
.sl-booked{position:absolute;inset:0;z-index:11;display:flex;flex-direction:column;align-items:center;
|
|
@@ -2810,7 +2826,7 @@ var CSS = (
|
|
|
2810
2826
|
justify-content:center;text-align:center;gap:8px;padding:24px;
|
|
2811
2827
|
background:color-mix(in srgb,var(--sl-bg) 82%,transparent);backdrop-filter:blur(4px)}
|
|
2812
2828
|
.sl-soldout.on{display:flex}
|
|
2813
|
-
.sl-soldout-eyebrow{font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--sl-accent);font-weight:800}
|
|
2829
|
+
.sl-soldout-eyebrow{font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--sl-accent-text);font-weight:800}
|
|
2814
2830
|
.sl-soldout-title{font-size:32px;font-weight:800;color:var(--sl-text);line-height:1.05}
|
|
2815
2831
|
.sl-soldout-copy{max-width:360px;font-size:13px;color:var(--sl-muted);line-height:1.5}
|
|
2816
2832
|
|
|
@@ -2826,9 +2842,9 @@ var CSS = (
|
|
|
2826
2842
|
/* "Powered by SeatLayer" attribution badge (side-panel foot) \u2014 the canonical
|
|
2827
2843
|
Layered Rows mark + wordmark. Hidden when the host opts out or the org's paid
|
|
2828
2844
|
theme sets hideBadge. */
|
|
2829
|
-
.sl-powered{display:flex;align-items:center;justify-content:center;gap:
|
|
2830
|
-
font-size:
|
|
2831
|
-
text-decoration:none;padding:
|
|
2845
|
+
.sl-powered{display:flex;align-items:center;justify-content:center;gap:5px;margin-top:2px;
|
|
2846
|
+
font-size:11px;font-weight:600;letter-spacing:.02em;color:var(--sl-text);opacity:.72;
|
|
2847
|
+
text-decoration:none;padding:2px 8px;border-radius:999px;width:fit-content;margin-inline:auto;
|
|
2832
2848
|
transition:opacity .15s ease,background-color .15s ease}
|
|
2833
2849
|
.sl-powered:hover{opacity:1;background:color-mix(in srgb,var(--sl-text) 8%,transparent)}
|
|
2834
2850
|
.sl-powered:focus-visible{opacity:1;outline:2px solid var(--sl-accent);outline-offset:2px}
|
|
@@ -2866,10 +2882,22 @@ var CSS = (
|
|
|
2866
2882
|
.sl-confirm-field:first-child .sl-confirm-value{font-size:13.5px;line-height:1.25;white-space:normal;
|
|
2867
2883
|
display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
|
|
2868
2884
|
.sl-confirm-cat{display:flex;align-items:center;gap:8px;padding:10px 12px;background:color-mix(in srgb,var(--sl-cat) 76%,var(--sl-surface))}
|
|
2869
|
-
.sl-confirm-cat .sl-dot{border:2px solid
|
|
2870
|
-
.sl-confirm-cat-name{font-size:13.5px;font-weight:800;color
|
|
2871
|
-
.sl-confirm-price{font-size:17px;font-weight:850;color
|
|
2885
|
+
.sl-confirm-cat .sl-dot{border:2px solid color-mix(in srgb,var(--sl-cat-ink) 78%,transparent);width:11px;height:11px}
|
|
2886
|
+
.sl-confirm-cat-name{font-size:13.5px;font-weight:800;color:var(--sl-cat-ink);flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
2887
|
+
.sl-confirm-price{font-size:17px;font-weight:850;color:var(--sl-cat-ink);font-variant-numeric:tabular-nums}
|
|
2872
2888
|
.sl-confirm-body{padding:11px 12px 12px}
|
|
2889
|
+
.sl-confirm-tiers{display:grid;gap:6px;margin:0 0 9px;padding:0;border:0}
|
|
2890
|
+
.sl-confirm-tiers legend{margin:0 0 6px;padding:0;color:var(--sl-muted);font-size:10px;font-weight:800;letter-spacing:.12em;text-transform:uppercase}
|
|
2891
|
+
.sl-confirm-tier{display:flex;align-items:center;gap:9px;width:100%;min-height:40px;padding:8px 10px;border:1px solid var(--sl-line);
|
|
2892
|
+
border-radius:9px;color:var(--sl-text);background:color-mix(in srgb,var(--sl-bg) 72%,var(--sl-surface));text-align:left}
|
|
2893
|
+
.sl-confirm-tier:hover{border-color:color-mix(in srgb,var(--sl-accent) 58%,var(--sl-line))}
|
|
2894
|
+
.sl-confirm-tier[aria-pressed="true"]{border-color:var(--sl-accent);background:color-mix(in srgb,var(--sl-accent) 13%,var(--sl-surface));
|
|
2895
|
+
box-shadow:inset 3px 0 0 var(--sl-accent)}
|
|
2896
|
+
.sl-confirm-tier-copy{min-width:0;flex:1;display:grid;gap:2px}
|
|
2897
|
+
.sl-confirm-tier-name{min-width:0;font-size:12.5px;font-weight:800}
|
|
2898
|
+
.sl-confirm-tier-note,.sl-tier-note{color:var(--sl-muted);font-size:10.5px;line-height:1.3}
|
|
2899
|
+
.sl-confirm-tier[aria-pressed="true"] .sl-confirm-tier-note{color:var(--sl-text)}
|
|
2900
|
+
.sl-confirm-tier-price{font-size:13px;font-weight:850;font-variant-numeric:tabular-nums}
|
|
2873
2901
|
.sl-confirm-row{display:flex;gap:8px;margin-top:10px}
|
|
2874
2902
|
.sl-confirm-row button{flex:1;min-height:44px;padding:9px 12px;border-radius:9px;font-weight:800;font-size:13px}
|
|
2875
2903
|
.sl-confirm-add{background:var(--sl-accent)!important;color:var(--sl-accent-ink)!important;display:flex;align-items:center;justify-content:center;gap:7px}
|
|
@@ -2887,6 +2915,7 @@ var CSS = (
|
|
|
2887
2915
|
.sl-picker[data-layout="narrow"] .sl-confirm-cat-name{font-size:12.5px}
|
|
2888
2916
|
.sl-picker[data-layout="narrow"] .sl-confirm-price{font-size:15px}
|
|
2889
2917
|
.sl-picker[data-layout="narrow"] .sl-confirm-body{padding:9px 10px 10px}
|
|
2918
|
+
.sl-picker[data-layout="narrow"] .sl-confirm-tier{min-height:38px;padding:7px 9px}
|
|
2890
2919
|
.sl-picker[data-layout="narrow"] .sl-confirm-thumbwrap{height:62px;margin-bottom:6px}
|
|
2891
2920
|
.sl-picker[data-layout="narrow"] .sl-confirm-3d{margin-top:7px;padding:6px}
|
|
2892
2921
|
.sl-picker[data-layout="narrow"] .sl-confirm-row{margin-top:8px}
|
|
@@ -2910,7 +2939,7 @@ var CSS = (
|
|
|
2910
2939
|
.sl-table-stepper{display:grid;grid-template-columns:48px 1fr 48px;align-items:center;border:1px solid var(--sl-line);
|
|
2911
2940
|
border-radius:12px;overflow:hidden;background:var(--sl-bg)}
|
|
2912
2941
|
.sl-table-stepper button{height:48px;font-size:24px;font-weight:700;background:color-mix(in srgb,var(--sl-line) 34%,transparent)!important}
|
|
2913
|
-
.sl-table-stepper button:disabled{opacity:.
|
|
2942
|
+
.sl-table-stepper button:disabled{opacity:.58;cursor:not-allowed}
|
|
2914
2943
|
.sl-table-stepper output{text-align:center;font-size:19px;font-weight:850;font-variant-numeric:tabular-nums}
|
|
2915
2944
|
.sl-table-range{margin-top:7px;color:var(--sl-muted);font-size:11px;text-align:center}
|
|
2916
2945
|
.sl-table-actions{display:flex;gap:9px;margin-top:17px}.sl-table-actions button{flex:1;min-height:46px;border-radius:10px;font-weight:800}
|
|
@@ -2952,14 +2981,14 @@ var CSS = (
|
|
|
2952
2981
|
.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}
|
|
2953
2982
|
.sl-ba-title,.sl-ba-copy,.sl-ba select,.sl-ba-qty,.sl-ba-go{position:relative;z-index:1}
|
|
2954
2983
|
.sl-ba-title{grid-column:1/-1;display:flex;align-items:center;gap:7px;font-size:13px;font-weight:850}
|
|
2955
|
-
.sl-ba-title .spark{color:var(--sl-accent);font-size:16px}
|
|
2984
|
+
.sl-ba-title .spark{color:var(--sl-accent-text);font-size:16px}
|
|
2956
2985
|
.sl-ba-copy{grid-column:1/-1;margin:-4px 0 2px 23px;color:var(--sl-muted);font-size:10.5px;line-height:1.35}
|
|
2957
2986
|
.sl-ba-copy .narrow{display:none}
|
|
2958
2987
|
/* "\u2605 Best seats" premium quick-pick \u2014 gold accent echoing the \u2605 Premium pill on
|
|
2959
2988
|
the confirm popover; deliberately distinct from the accent-toned qty/go. */
|
|
2960
2989
|
.sl-ba-premium{position:relative;z-index:1;grid-column:1/-1;justify-self:start;display:inline-flex;align-items:center;gap:6px;
|
|
2961
2990
|
padding:6px 12px;border-radius:999px;font-size:11px;font-weight:800;letter-spacing:.02em;cursor:pointer;
|
|
2962
|
-
color:var(--sl-premium-
|
|
2991
|
+
color:var(--sl-premium-text);background:color-mix(in srgb,var(--sl-premium) 10%,var(--sl-surface));
|
|
2963
2992
|
border:1px solid color-mix(in srgb,var(--sl-premium) 34%,var(--sl-line));transition:filter .15s,background .15s,color .15s}
|
|
2964
2993
|
.sl-ba-premium .star{font-size:12px;line-height:1;color:var(--sl-premium)}
|
|
2965
2994
|
.sl-ba-premium:hover{filter:brightness(1.05)}
|
|
@@ -3012,7 +3041,7 @@ var CSS = (
|
|
|
3012
3041
|
back out of a reopened card. */
|
|
3013
3042
|
.sl-ba-reopen{align-self:flex-start;display:inline-flex;align-items:center;gap:6px;min-height:32px;padding:5px 12px;margin-top:2px;
|
|
3014
3043
|
border:1px dashed color-mix(in srgb,var(--sl-accent) 40%,var(--sl-line));border-radius:999px;
|
|
3015
|
-
color:var(--sl-accent);font-size:11.5px;font-weight:750;transition:border-color .15s,background .15s}
|
|
3044
|
+
color:var(--sl-accent-text);font-size:11.5px;font-weight:750;transition:border-color .15s,background .15s}
|
|
3016
3045
|
.sl-ba-reopen:hover,.sl-ba-reopen:focus-visible{background:color-mix(in srgb,var(--sl-accent) 8%,transparent)}
|
|
3017
3046
|
.sl-ba-back{grid-column:1/-1;justify-self:start;min-height:30px;padding:3px 0;color:var(--sl-muted);
|
|
3018
3047
|
font-size:11px;font-weight:700;text-decoration:underline;text-underline-offset:3px;transition:color .15s}
|
|
@@ -3134,7 +3163,7 @@ var CSS = (
|
|
|
3134
3163
|
border:1px solid rgba(150,165,205,.35);backdrop-filter:blur(6px);cursor:pointer}
|
|
3135
3164
|
.sl-view3d-nav button:hover,.sl-view3d-nav button:focus-visible{color:#eef1f8;border-color:rgba(190,205,240,.6)}
|
|
3136
3165
|
.sl-view3d-nav button[aria-pressed="true"]{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
|
|
3137
|
-
.sl-view3d-nav button:disabled{opacity:.
|
|
3166
|
+
.sl-view3d-nav button:disabled{opacity:.6;cursor:not-allowed}
|
|
3138
3167
|
.sl-view3d-nav-toggle{display:none!important}
|
|
3139
3168
|
.sl-view3d-nav select{min-height:38px;max-width:100%;padding:7px 34px 7px 12px;
|
|
3140
3169
|
border-radius:999px;font:700 11.5px/1 inherit;color:#eef1f8;background:rgba(12,18,32,.86);
|
|
@@ -3182,7 +3211,7 @@ var CSS = (
|
|
|
3182
3211
|
.sl-confirm-confidence strong,.sl-confirm-confidence small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
3183
3212
|
.sl-confirm-confidence strong{font-size:11px}.sl-confirm-confidence small{margin-top:2px;color:var(--sl-muted);font-size:9.5px}
|
|
3184
3213
|
.sl-confirm-confidence em{display:none;font-style:normal}
|
|
3185
|
-
.sl-confirm-confidence>b{flex:none;font-size:11px;color:var(--sl-accent)}
|
|
3214
|
+
.sl-confirm-confidence>b{flex:none;font-size:11px;color:var(--sl-accent-text)}
|
|
3186
3215
|
.sl-confirm-confidence:hover,.sl-confirm-confidence:focus-visible{border-color:var(--sl-accent)}
|
|
3187
3216
|
/* The production 3D decision dock is deliberately denser than the 2D popup:
|
|
3188
3217
|
the venue remains the main content and the two inspection actions share one
|
|
@@ -3264,7 +3293,7 @@ var CSS = (
|
|
|
3264
3293
|
.sl-view3d-compare-actions{display:grid;grid-template-columns:repeat(3,1fr);gap:7px;margin-top:12px}
|
|
3265
3294
|
.sl-view3d-compare-actions button{min-height:44px;border-radius:9px;border:1px solid var(--sl-line);font-size:12px;font-weight:800}
|
|
3266
3295
|
.sl-view3d-compare-actions .select{background:var(--sl-accent);color:var(--sl-accent-ink);border-color:transparent}
|
|
3267
|
-
.sl-view3d-compare-actions button:disabled{opacity:.
|
|
3296
|
+
.sl-view3d-compare-actions button:disabled{opacity:.62;cursor:not-allowed}
|
|
3268
3297
|
.sl-view3d-passport-shell{position:absolute;inset:0;z-index:40;display:grid;place-items:center;padding:16px}
|
|
3269
3298
|
.sl-view3d-passport-scrim{position:absolute;inset:0;background:rgba(3,6,12,.8);backdrop-filter:blur(6px)}
|
|
3270
3299
|
.sl-view3d-passport{position:relative;width:min(540px,100%);max-height:min(700px,calc(100% - 20px));overflow:auto;
|
|
@@ -3305,14 +3334,17 @@ var CSS = (
|
|
|
3305
3334
|
.sl-confirm-3d:hover,.sl-confirm-3d:focus-visible{border-color:var(--sl-accent);background:color-mix(in srgb,var(--sl-accent) 20%,transparent)}
|
|
3306
3335
|
.sl-confirm-3d svg{width:15px;height:15px;stroke:currentColor;stroke-width:1.9;fill:none;stroke-linecap:round;stroke-linejoin:round}
|
|
3307
3336
|
|
|
3308
|
-
/* multi-floor
|
|
3309
|
-
.sl-floors{display:none;
|
|
3337
|
+
/* One compact multi-floor selector (flows within the left-rail region). */
|
|
3338
|
+
.sl-floors{display:none;align-items:center;gap:6px;max-width:min(260px,100%);padding:5px;border-radius:999px;
|
|
3339
|
+
background:color-mix(in srgb,var(--sl-surface) 92%,transparent);border:1px solid var(--sl-line);
|
|
3340
|
+
box-shadow:var(--sl-shadow);backdrop-filter:blur(8px)}
|
|
3310
3341
|
.sl-floors.on{display:flex}
|
|
3311
|
-
.sl-floors
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
.sl-
|
|
3315
|
-
|
|
3342
|
+
.sl-floors select{min-width:0;max-width:210px;min-height:34px;padding:6px 30px 6px 10px;border:0;border-radius:999px;
|
|
3343
|
+
font:700 12px/1 inherit;color:var(--sl-text);background:var(--sl-surface);cursor:pointer}
|
|
3344
|
+
.sl-floors select:focus-visible,.sl-floor-info:focus-visible{outline:2px solid var(--sl-accent);outline-offset:2px}
|
|
3345
|
+
.sl-floor-info{display:inline-grid;place-items:center;flex:0 0 30px;width:30px;height:30px;border-radius:999px;
|
|
3346
|
+
border:1px solid var(--sl-line);color:var(--sl-muted);background:transparent;font:800 13px/1 inherit;cursor:help}
|
|
3347
|
+
.sl-floor-info:hover{color:var(--sl-text);border-color:var(--sl-accent)}
|
|
3316
3348
|
|
|
3317
3349
|
/* tapped-section summary card \u2014 docks INSIDE the top-center anchor region on
|
|
3318
3350
|
wide (flows below the rung pills, never over them, never floating over the
|
|
@@ -3347,8 +3379,14 @@ var CSS = (
|
|
|
3347
3379
|
.sl-seccard-mix-dot{width:8px;height:8px;border-radius:50%;flex:none}
|
|
3348
3380
|
.sl-seccard-mix-price{font-weight:700;color:var(--sl-text)}
|
|
3349
3381
|
.sl-seccard-foot{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:10px}
|
|
3350
|
-
.sl-seccard-overview{font-size:12px;font-weight:800;color:var(--sl-accent)}
|
|
3382
|
+
.sl-seccard-overview{font-size:12px;font-weight:800;color:var(--sl-accent-text)}
|
|
3351
3383
|
.sl-seccard-hint{font-size:10.5px;color:var(--sl-muted)}
|
|
3384
|
+
.sl-seccard-nav{display:inline-flex;align-items:center;gap:4px;margin-left:auto}
|
|
3385
|
+
.sl-seccard-nav button{width:28px;height:28px;border:1px solid var(--sl-line);border-radius:999px;color:var(--sl-text);
|
|
3386
|
+
background:color-mix(in srgb,var(--sl-surface) 88%,var(--sl-bg));font-size:14px;font-weight:850;line-height:1}
|
|
3387
|
+
.sl-seccard-nav button:hover,.sl-seccard-nav button:focus-visible{border-color:var(--sl-accent);color:var(--sl-accent-text)}
|
|
3388
|
+
.sl-seccard.mini .sl-seccard-nav,.sl-seccard.strip .sl-seccard-nav{margin-left:2px}
|
|
3389
|
+
.sl-seccard.mini .sl-seccard-nav button,.sl-seccard.strip .sl-seccard-nav button{width:24px;height:24px;font-size:12px}
|
|
3352
3390
|
|
|
3353
3391
|
/* view-from-seat button on the confirm popover */
|
|
3354
3392
|
/* Eager sightline preview inside the confirm card */
|
|
@@ -3401,10 +3439,29 @@ var CSS = (
|
|
|
3401
3439
|
white-space:nowrap;pointer-events:none;max-width:90%;overflow:hidden;text-overflow:ellipsis}
|
|
3402
3440
|
|
|
3403
3441
|
/* F3 minimap \u2014 venue overview + live viewport rect (flows in the bottom-left region) */
|
|
3404
|
-
.sl-minimap{border:1px solid var(--sl-line);border-radius:9px;
|
|
3405
|
-
|
|
3442
|
+
.sl-minimap{border:1px solid var(--sl-line);border-radius:9px;overflow:hidden;background:var(--sl-surface);
|
|
3443
|
+
box-shadow:0 12px 34px -14px rgba(0,0,0,.55);line-height:0;touch-action:none}
|
|
3444
|
+
.sl-minimap-bar{min-height:24px;padding:3px 5px 3px 8px;display:flex;align-items:center;gap:6px;border-bottom:1px solid var(--sl-line);
|
|
3445
|
+
color:var(--sl-muted);font-size:9px;font-weight:800;line-height:1.2;letter-spacing:.06em;text-transform:uppercase}
|
|
3446
|
+
.sl-minimap-label{min-width:0;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
3447
|
+
.sl-minimap-key{display:inline-flex;align-items:center;gap:3px;flex:none}
|
|
3448
|
+
.sl-minimap-key i{width:6px;height:6px;border-radius:999px;background:var(--sl-success)}
|
|
3449
|
+
.sl-minimap-key i.selected{background:var(--sl-accent)}
|
|
3450
|
+
.sl-minimap-key i.inactive{background:var(--sl-muted);opacity:.45}
|
|
3451
|
+
.sl-minimap-key i.focused{background:transparent;border:1.5px solid var(--sl-accent);width:7px;height:7px}
|
|
3452
|
+
.sl-minimap-close{display:none;width:24px;height:24px;border-radius:6px;color:var(--sl-muted);font-size:15px;line-height:1}
|
|
3406
3453
|
.sl-minimap canvas{display:block}
|
|
3454
|
+
.sl-minimap canvas:focus-visible{outline:2px solid var(--sl-accent);outline-offset:-3px}
|
|
3455
|
+
.sl-minimap canvas.sl-dragging{cursor:grabbing}
|
|
3456
|
+
.sl-minimap-toggle{display:none;width:42px;height:42px;border:1px solid var(--sl-line);border-radius:10px;background:var(--sl-surface);
|
|
3457
|
+
color:var(--sl-text);box-shadow:0 10px 28px -14px rgba(0,0,0,.65);align-items:center;justify-content:center}
|
|
3458
|
+
.sl-minimap-toggle svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linejoin:round}
|
|
3407
3459
|
.sl-picker[data-layout="narrow"] .sl-minimap{display:none}
|
|
3460
|
+
.sl-picker[data-layout="narrow"] .sl-minimap-toggle{display:flex}
|
|
3461
|
+
.sl-picker[data-layout="narrow"][data-minimap-open="true"] .sl-minimap{display:block}
|
|
3462
|
+
.sl-picker[data-layout="narrow"][data-minimap-open="true"] .sl-minimap-toggle{display:none}
|
|
3463
|
+
.sl-picker[data-layout="narrow"] .sl-minimap canvas{width:min(168px,42vw)!important;height:auto!important}
|
|
3464
|
+
.sl-picker[data-layout="narrow"] .sl-minimap-close{display:grid;place-items:center}
|
|
3408
3465
|
|
|
3409
3466
|
/* F4 legend reflection: rows + counts for out-of-band categories read muted */
|
|
3410
3467
|
/* Out-of-band rows are exactly the rows a buyer reads while deciding whether
|
|
@@ -3482,17 +3539,88 @@ function formatWhen(startsAt, timezone, locale) {
|
|
|
3482
3539
|
}
|
|
3483
3540
|
return at.toLocaleString(locale, options);
|
|
3484
3541
|
}
|
|
3485
|
-
function
|
|
3542
|
+
function parseThemeColor(value) {
|
|
3543
|
+
if (!value) return null;
|
|
3544
|
+
const color = value.trim().toLowerCase();
|
|
3545
|
+
if (color === "white") return { r: 255, g: 255, b: 255 };
|
|
3546
|
+
if (color === "black") return { r: 0, g: 0, b: 0 };
|
|
3547
|
+
const hex = /^#([0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.exec(color)?.[1];
|
|
3548
|
+
if (hex) {
|
|
3549
|
+
const expanded = hex.length <= 4 ? [...hex].map((digit) => `${digit}${digit}`).join("") : hex;
|
|
3550
|
+
return {
|
|
3551
|
+
r: Number.parseInt(expanded.slice(0, 2), 16),
|
|
3552
|
+
g: Number.parseInt(expanded.slice(2, 4), 16),
|
|
3553
|
+
b: Number.parseInt(expanded.slice(4, 6), 16)
|
|
3554
|
+
};
|
|
3555
|
+
}
|
|
3556
|
+
const rgb = /^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:\s*[,/]\s*[\d.]+%?)?\s*\)$/i.exec(color);
|
|
3557
|
+
if (!rgb) return null;
|
|
3558
|
+
return {
|
|
3559
|
+
r: Math.max(0, Math.min(255, Number(rgb[1]))),
|
|
3560
|
+
g: Math.max(0, Math.min(255, Number(rgb[2]))),
|
|
3561
|
+
b: Math.max(0, Math.min(255, Number(rgb[3])))
|
|
3562
|
+
};
|
|
3563
|
+
}
|
|
3564
|
+
function relativeLuminance(color) {
|
|
3565
|
+
const channel = (value) => {
|
|
3566
|
+
const normalized = value / 255;
|
|
3567
|
+
return normalized <= 0.04045 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4;
|
|
3568
|
+
};
|
|
3569
|
+
return 0.2126 * channel(color.r) + 0.7152 * channel(color.g) + 0.0722 * channel(color.b);
|
|
3570
|
+
}
|
|
3571
|
+
function themeContrast(foreground, background) {
|
|
3572
|
+
const fg = parseThemeColor(foreground);
|
|
3573
|
+
const bg = parseThemeColor(background);
|
|
3574
|
+
if (!fg || !bg) return null;
|
|
3575
|
+
const lighter = Math.max(relativeLuminance(fg), relativeLuminance(bg));
|
|
3576
|
+
const darker = Math.min(relativeLuminance(fg), relativeLuminance(bg));
|
|
3577
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
3578
|
+
}
|
|
3579
|
+
function highestContrastInk(background, dark = "#172033", light = "#eef1f8") {
|
|
3580
|
+
return (themeContrast(dark, background) ?? 0) >= (themeContrast(light, background) ?? 0) ? dark : light;
|
|
3581
|
+
}
|
|
3582
|
+
function rgbHex(color) {
|
|
3583
|
+
const channel = (value) => Math.round(value).toString(16).padStart(2, "0");
|
|
3584
|
+
return `#${channel(color.r)}${channel(color.g)}${channel(color.b)}`;
|
|
3585
|
+
}
|
|
3586
|
+
function mixThemeColors(first, second, firstWeight) {
|
|
3587
|
+
const a = parseThemeColor(first);
|
|
3588
|
+
const b = parseThemeColor(second);
|
|
3589
|
+
if (!a || !b) return null;
|
|
3590
|
+
const weight = Math.max(0, Math.min(1, firstWeight));
|
|
3591
|
+
return rgbHex({
|
|
3592
|
+
r: a.r * weight + b.r * (1 - weight),
|
|
3593
|
+
g: a.g * weight + b.g * (1 - weight),
|
|
3594
|
+
b: a.b * weight + b.b * (1 - weight)
|
|
3595
|
+
});
|
|
3596
|
+
}
|
|
3597
|
+
function readableAccentText(accent, text, background, surface) {
|
|
3598
|
+
if ((themeContrast(accent, background) ?? 4.5) >= 4.5 && (themeContrast(accent, surface) ?? 4.5) >= 4.5) return accent;
|
|
3599
|
+
if (!parseThemeColor(accent) || !parseThemeColor(text)) return text;
|
|
3600
|
+
for (let textWeight = 0.15; textWeight <= 1; textWeight += 0.05) {
|
|
3601
|
+
const candidate = mixThemeColors(accent, text, 1 - textWeight);
|
|
3602
|
+
if (candidate && (themeContrast(candidate, background) ?? 0) >= 4.5 && (themeContrast(candidate, surface) ?? 0) >= 4.5) return candidate;
|
|
3603
|
+
}
|
|
3604
|
+
return text;
|
|
3605
|
+
}
|
|
3606
|
+
function resolvePickerTokens(chart, host) {
|
|
3607
|
+
const background = host?.background ?? chart?.background ?? "#0f1522";
|
|
3608
|
+
const isLight = parseThemeColor(background) ? highestContrastInk(background) === "#172033" : false;
|
|
3609
|
+
const surface = host?.surface ?? (isLight ? "#ffffff" : "#1a2234");
|
|
3486
3610
|
const accent = host?.accent ?? chart?.accent ?? "#f4b740";
|
|
3487
|
-
const
|
|
3611
|
+
const chartAccentInk = chart?.accentInk;
|
|
3612
|
+
const accentInk = host?.accentInk ?? (chartAccentInk && (themeContrast(chartAccentInk, accent) ?? 4.5) >= 4.5 ? chartAccentInk : highestContrastInk(accent, "#1a1200", "#ffffff"));
|
|
3613
|
+
const chartText = chart?.textColor;
|
|
3614
|
+
const text = host?.text ?? (chartText && (themeContrast(chartText, background) ?? 4.5) >= 4.5 && (themeContrast(chartText, surface) ?? 4.5) >= 4.5 ? chartText : highestContrastInk(background));
|
|
3488
3615
|
return {
|
|
3489
3616
|
"--sl-accent": accent,
|
|
3490
3617
|
"--sl-accent-ink": accentInk,
|
|
3491
|
-
"--sl-
|
|
3492
|
-
"--sl-
|
|
3493
|
-
"--sl-
|
|
3494
|
-
"--sl-
|
|
3495
|
-
"--sl-
|
|
3618
|
+
"--sl-accent-text": readableAccentText(accent, text, background, surface),
|
|
3619
|
+
"--sl-bg": background,
|
|
3620
|
+
"--sl-surface": surface,
|
|
3621
|
+
"--sl-text": text,
|
|
3622
|
+
"--sl-muted": host?.muted ?? (isLight ? "#667085" : "#a5aec2"),
|
|
3623
|
+
"--sl-line": host?.line ?? (isLight ? "rgba(23,32,51,.16)" : "rgba(165,174,194,.24)"),
|
|
3496
3624
|
"--sl-font": host?.fontFamily ?? chart?.fontFamily ?? "-apple-system,BlinkMacSystemFont,'Segoe UI',Inter,sans-serif",
|
|
3497
3625
|
"--sl-radius": `${host?.radius ?? 14}px`
|
|
3498
3626
|
};
|
|
@@ -3624,7 +3752,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
3624
3752
|
// F3 minimap
|
|
3625
3753
|
this.miniCanvas = null;
|
|
3626
3754
|
this.miniBase = null;
|
|
3755
|
+
this.miniWrap = null;
|
|
3756
|
+
this.miniToggle = null;
|
|
3757
|
+
this.miniLabel = null;
|
|
3627
3758
|
this.miniTf = null;
|
|
3759
|
+
this.miniDrag = null;
|
|
3760
|
+
this.miniSuppressClick = false;
|
|
3761
|
+
this.pickerLayoutSize = { width: 0, height: 0 };
|
|
3628
3762
|
// F4 price-band filter — active band's category keys (null = all prices)
|
|
3629
3763
|
this.priceBandKeys = null;
|
|
3630
3764
|
this.focusedCatKey = null;
|
|
@@ -3725,6 +3859,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
3725
3859
|
mapTheme: options.theme?.map ?? null,
|
|
3726
3860
|
onSelectionChange: () => {
|
|
3727
3861
|
this.syncTray();
|
|
3862
|
+
this.refreshMinimap();
|
|
3728
3863
|
if (this.committedSelection().length) this.collapseSectionCard();
|
|
3729
3864
|
this.syncSelectionTo3d();
|
|
3730
3865
|
},
|
|
@@ -3784,6 +3919,12 @@ var SeatPicker = class _SeatPicker {
|
|
|
3784
3919
|
},
|
|
3785
3920
|
// Tapped-section glide-in → surface (or clear) the section-summary card.
|
|
3786
3921
|
onSectionFocus: (summary) => this.showSectionCard(summary),
|
|
3922
|
+
onDeckTap: () => {
|
|
3923
|
+
this.showSectionCard(null);
|
|
3924
|
+
this.syncFloors();
|
|
3925
|
+
this.syncRung();
|
|
3926
|
+
this.refreshMinimap();
|
|
3927
|
+
},
|
|
3787
3928
|
onFocusSeat: (seat) => this.announceSeat(seat),
|
|
3788
3929
|
onSeatHover: (d) => this.updateTooltip(d),
|
|
3789
3930
|
onHint: (m) => {
|
|
@@ -4001,7 +4142,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4001
4142
|
window.removeEventListener("keydown", this.fsEscHandler);
|
|
4002
4143
|
this.fsEscHandler = null;
|
|
4003
4144
|
}
|
|
4004
|
-
requestAnimationFrame(() => this.controller.
|
|
4145
|
+
requestAnimationFrame(() => this.controller.refitCurrentView());
|
|
4005
4146
|
}
|
|
4006
4147
|
setFsFallback(on) {
|
|
4007
4148
|
if (this.fsFallback === on) return;
|
|
@@ -4017,7 +4158,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4017
4158
|
window.removeEventListener("keydown", this.fsEscHandler);
|
|
4018
4159
|
this.fsEscHandler = null;
|
|
4019
4160
|
}
|
|
4020
|
-
requestAnimationFrame(() => this.controller.
|
|
4161
|
+
requestAnimationFrame(() => this.controller.refitCurrentView());
|
|
4021
4162
|
}
|
|
4022
4163
|
/**
|
|
4023
4164
|
* Close the picker. In modal mode (SeatPicker.open()) this dismisses the
|
|
@@ -4157,11 +4298,17 @@ var SeatPicker = class _SeatPicker {
|
|
|
4157
4298
|
}
|
|
4158
4299
|
async render() {
|
|
4159
4300
|
if (this.rendered) return this;
|
|
4301
|
+
const renderStartedAt = performance.now();
|
|
4302
|
+
const renderProfile = {};
|
|
4303
|
+
const duration = (startedAt) => Math.round((performance.now() - startedAt) * 100) / 100;
|
|
4160
4304
|
this.rendered = true;
|
|
4161
4305
|
ensureStyle();
|
|
4306
|
+
const localeStartedAt = performance.now();
|
|
4162
4307
|
await loadLocale2(this.opts.locale);
|
|
4308
|
+
renderProfile.loadLocale = duration(localeStartedAt);
|
|
4163
4309
|
if (this.opts.messages) setStringOverrides2(this.opts.messages);
|
|
4164
4310
|
if (this.checkoutMode === "hosted") this.paymentOptions = this.pubApi.paymentOptions(this.opts.event);
|
|
4311
|
+
const shellStartedAt = performance.now();
|
|
4165
4312
|
const mount = resolveContainer3(this.opts.container);
|
|
4166
4313
|
const root = document.createElement("div");
|
|
4167
4314
|
root.className = "sl-picker";
|
|
@@ -4185,7 +4332,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4185
4332
|
this.syncTray();
|
|
4186
4333
|
}
|
|
4187
4334
|
});
|
|
4188
|
-
Object.entries(
|
|
4335
|
+
Object.entries(resolvePickerTokens(void 0, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
|
|
4189
4336
|
root.innerHTML = `
|
|
4190
4337
|
<div class="sl-head">
|
|
4191
4338
|
<div class="sl-logo" data-ref="logo"></div>
|
|
@@ -4264,14 +4411,20 @@ var SeatPicker = class _SeatPicker {
|
|
|
4264
4411
|
const applyLayout = () => {
|
|
4265
4412
|
const w = root.clientWidth;
|
|
4266
4413
|
if (w <= 0) return;
|
|
4414
|
+
const h = root.clientHeight;
|
|
4415
|
+
const sizeChanged = w !== this.pickerLayoutSize.width || h !== this.pickerLayoutSize.height;
|
|
4416
|
+
this.pickerLayoutSize = { width: w, height: h };
|
|
4267
4417
|
this.reportFramedHeight();
|
|
4268
4418
|
const next = w < 640 ? "narrow" : "wide";
|
|
4269
|
-
const density =
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4419
|
+
const density = h < 560 ? "compact" : "comfortable";
|
|
4420
|
+
const layoutChanged = root.dataset.layout !== next || root.dataset.density !== density;
|
|
4421
|
+
if (layoutChanged) {
|
|
4422
|
+
root.dataset.layout = next;
|
|
4423
|
+
root.dataset.density = density;
|
|
4424
|
+
if (next === "narrow" && !root.dataset.sheet) root.dataset.sheet = "peek";
|
|
4425
|
+
this.dockLayoutChrome();
|
|
4426
|
+
}
|
|
4427
|
+
if (sizeChanged && this.miniBase) this.refreshMinimap();
|
|
4275
4428
|
};
|
|
4276
4429
|
this.ro = new ResizeObserver(applyLayout);
|
|
4277
4430
|
this.ro.observe(root);
|
|
@@ -4284,7 +4437,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4284
4437
|
this.fsChangeHandler = () => {
|
|
4285
4438
|
if (!document.fullscreenElement) this.setFsFallback(false);
|
|
4286
4439
|
this.syncFullscreenButtons();
|
|
4287
|
-
requestAnimationFrame(() => this.controller.
|
|
4440
|
+
requestAnimationFrame(() => this.controller.refitCurrentView());
|
|
4288
4441
|
};
|
|
4289
4442
|
document.addEventListener("fullscreenchange", this.fsChangeHandler);
|
|
4290
4443
|
const head = this.els.sheetHead;
|
|
@@ -4386,7 +4539,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
4386
4539
|
const canvasHost = document.createElement("div");
|
|
4387
4540
|
canvasHost.style.cssText = "position:absolute;inset:0";
|
|
4388
4541
|
this.mapHost.appendChild(canvasHost);
|
|
4542
|
+
renderProfile.mountBuyerShell = duration(shellStartedAt);
|
|
4543
|
+
const chartStartedAt = performance.now();
|
|
4389
4544
|
const info = await this.controller.render(canvasHost);
|
|
4545
|
+
renderProfile.loadChartAndStatuses = duration(chartStartedAt);
|
|
4390
4546
|
if (this.destroyed) return this;
|
|
4391
4547
|
if (!info) {
|
|
4392
4548
|
this.els.boot.innerHTML = `<div class="sl-boot-title">${this.tf("picker.mapDidNotLoad", "The seat map didn\u2019t load")}</div><div>${this.tf("picker.checkConnection", "Check your connection and try again.")}</div><button type="button" class="sl-boot-retry">${this.tf("picker.accessRetry", "Try again")}</button>`;
|
|
@@ -4398,7 +4554,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
4398
4554
|
});
|
|
4399
4555
|
return this;
|
|
4400
4556
|
}
|
|
4557
|
+
Object.assign(renderProfile, info.performanceProfile);
|
|
4401
4558
|
this.els.boot.remove();
|
|
4559
|
+
const controlsStartedAt = performance.now();
|
|
4402
4560
|
this.startRealtime();
|
|
4403
4561
|
this.salesClosed = !!info.salesClosed || !!this.opts.readOnly;
|
|
4404
4562
|
root.dataset.eventMode = info.mode === "test" ? "test" : "live";
|
|
@@ -4414,7 +4572,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4414
4572
|
this.regions["top-left"].appendChild(badge);
|
|
4415
4573
|
}
|
|
4416
4574
|
const chartTheme = this.controller.doc?.theme;
|
|
4417
|
-
Object.entries(
|
|
4575
|
+
Object.entries(resolvePickerTokens(chartTheme, this.opts.theme)).forEach(([k, v]) => root.style.setProperty(k, v));
|
|
4418
4576
|
this.currency = info.currency ?? this.opts.currency ?? "USD";
|
|
4419
4577
|
this.eventTimezone = info.timezone ?? null;
|
|
4420
4578
|
const logoUrl = this.opts.theme?.logoUrl ?? chartTheme?.logoUrl ?? info.posterUrl ?? null;
|
|
@@ -4522,12 +4680,34 @@ var SeatPicker = class _SeatPicker {
|
|
|
4522
4680
|
this.buildPanelToggle();
|
|
4523
4681
|
if (this.opts.panelCollapsed) this.setPanelCollapsed(true);
|
|
4524
4682
|
this.dockLayoutChrome();
|
|
4683
|
+
renderProfile.buildBuyerControls = duration(controlsStartedAt);
|
|
4684
|
+
const holdStartedAt = performance.now();
|
|
4525
4685
|
await this.restoreRememberedHold();
|
|
4686
|
+
renderProfile.restoreHold = duration(holdStartedAt);
|
|
4526
4687
|
if (this.destroyed) return this;
|
|
4527
4688
|
if (this.salesClosed) this.applySalesClosed();
|
|
4528
4689
|
this.syncPrices();
|
|
4529
4690
|
this.syncTray();
|
|
4530
4691
|
this.resumeHostedOrder();
|
|
4692
|
+
const doc = this.controller.doc;
|
|
4693
|
+
this.emitAnalytics("chart_rendered", {
|
|
4694
|
+
renderingTimeMillis: duration(renderStartedAt),
|
|
4695
|
+
performanceProfile: renderProfile,
|
|
4696
|
+
seatCount: this.allSeats().length,
|
|
4697
|
+
objectCount: doc?.objects.length ?? 0,
|
|
4698
|
+
sectionCount: doc?.objects.filter((object) => object.type === "section").length ?? 0,
|
|
4699
|
+
view: this.buyerView,
|
|
4700
|
+
// Dimensions a host needs to read the timings above as anything other
|
|
4701
|
+
// than one undifferentiated number: WHICH chart, HOW big, WHICH data
|
|
4702
|
+
// path, and whether the browser had already warmed for this chart.
|
|
4703
|
+
// The event key is the chart's identity here — an event owns exactly one
|
|
4704
|
+
// chart — and the name is the same string already drawn in the header.
|
|
4705
|
+
eventKey: this.opts.event,
|
|
4706
|
+
chartName: doc?.name,
|
|
4707
|
+
floorCount: this.controller.getFloors().length,
|
|
4708
|
+
transport: this.opts.transport ? "custom" : "pubapi",
|
|
4709
|
+
load: markChartLoaded(this.opts.event) ? "repeat" : "cold"
|
|
4710
|
+
});
|
|
4531
4711
|
return this;
|
|
4532
4712
|
}
|
|
4533
4713
|
/**
|
|
@@ -4589,7 +4769,8 @@ var SeatPicker = class _SeatPicker {
|
|
|
4589
4769
|
const collapsed = this.sideCollapsed;
|
|
4590
4770
|
btn.setAttribute("aria-expanded", String(!collapsed));
|
|
4591
4771
|
btn.setAttribute("aria-label", collapsed ? this.tf("picker.showTicketPanel", "Show the ticket panel") : this.tf("picker.hideTicketPanel", "Hide the ticket panel"));
|
|
4592
|
-
btn.
|
|
4772
|
+
btn.title = collapsed ? this.tf("picker.showTicketPanel", "Show the ticket panel") : this.tf("picker.hideTicketPanel", "Hide the ticket panel");
|
|
4773
|
+
btn.innerHTML = collapsed ? `<svg viewBox="0 0 24 24"><path d="M15 6l-6 6 6 6"/></svg><span>${this.tf("picker.tickets", "Tickets")}</span>` : `<svg viewBox="0 0 24 24"><path d="M9 6l6 6-6 6"/></svg>`;
|
|
4593
4774
|
}
|
|
4594
4775
|
/**
|
|
4595
4776
|
* Collapse or reopen the wide-layout ticket panel in place (host-drivable —
|
|
@@ -4602,7 +4783,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
4602
4783
|
if (this.destroyed) return;
|
|
4603
4784
|
this.sideCollapsed = collapsed;
|
|
4604
4785
|
this.applyPanelCollapsed();
|
|
4605
|
-
this.scheduleMotion(() => this.controller.
|
|
4786
|
+
this.scheduleMotion(() => this.controller.refitCurrentView(), 340);
|
|
4606
4787
|
}
|
|
4607
4788
|
/** Idempotent DOM apply, also run on every layout flip (inert must lift on narrow). */
|
|
4608
4789
|
applyPanelCollapsed() {
|
|
@@ -4997,10 +5178,77 @@ var SeatPicker = class _SeatPicker {
|
|
|
4997
5178
|
buildMinimap() {
|
|
4998
5179
|
const vp = this.controller.getViewport();
|
|
4999
5180
|
if (!vp || !this.els.map) return;
|
|
5000
|
-
const
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5181
|
+
const host = this.regions["bottom-left"] ?? this.els.map;
|
|
5182
|
+
const toggle = document.createElement("button");
|
|
5183
|
+
toggle.type = "button";
|
|
5184
|
+
toggle.className = "sl-minimap-toggle";
|
|
5185
|
+
toggle.setAttribute("aria-label", this.tf("picker.openVenueMap", "Open venue overview map"));
|
|
5186
|
+
toggle.setAttribute("aria-expanded", "false");
|
|
5187
|
+
toggle.innerHTML = '<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 6l5-2 8 3 5-2v13l-5 2-8-3-5 2zM8 4v13M16 7v13"/></svg>';
|
|
5188
|
+
host.appendChild(toggle);
|
|
5189
|
+
this.miniToggle = toggle;
|
|
5190
|
+
const wrap = document.createElement("div");
|
|
5191
|
+
wrap.className = "sl-minimap";
|
|
5192
|
+
wrap.setAttribute("role", "group");
|
|
5193
|
+
const bar = document.createElement("div");
|
|
5194
|
+
bar.className = "sl-minimap-bar";
|
|
5195
|
+
const label = document.createElement("span");
|
|
5196
|
+
label.className = "sl-minimap-label";
|
|
5197
|
+
const key = document.createElement("span");
|
|
5198
|
+
key.className = "sl-minimap-key";
|
|
5199
|
+
key.setAttribute("aria-label", this.tf("picker.minimapStateKey", "Focused, selected, available, and inactive sections"));
|
|
5200
|
+
key.innerHTML = '<i class="focused" title="Focused"></i><i class="selected" title="Selected"></i><i title="Available"></i><i class="inactive" title="Inactive"></i>';
|
|
5201
|
+
const close = document.createElement("button");
|
|
5202
|
+
close.type = "button";
|
|
5203
|
+
close.className = "sl-minimap-close";
|
|
5204
|
+
close.setAttribute("aria-label", this.tf("picker.closeVenueMap", "Close venue overview map"));
|
|
5205
|
+
close.textContent = "\xD7";
|
|
5206
|
+
bar.append(label, key, close);
|
|
5207
|
+
const canvas = document.createElement("canvas");
|
|
5208
|
+
canvas.tabIndex = 0;
|
|
5209
|
+
canvas.setAttribute("role", "application");
|
|
5210
|
+
canvas.setAttribute("aria-label", this.tf("picker.venueMapInstructions", "Venue overview map. Drag the viewport, click a section, or use arrow keys to pan."));
|
|
5211
|
+
wrap.append(bar, canvas);
|
|
5212
|
+
host.appendChild(wrap);
|
|
5213
|
+
this.miniWrap = wrap;
|
|
5214
|
+
this.miniLabel = label;
|
|
5215
|
+
this.miniCanvas = canvas;
|
|
5216
|
+
const base = document.createElement("canvas");
|
|
5217
|
+
this.miniBase = base;
|
|
5218
|
+
toggle.addEventListener("click", () => this.setMinimapOpen(true));
|
|
5219
|
+
close.addEventListener("click", () => this.setMinimapOpen(false));
|
|
5220
|
+
canvas.addEventListener("click", (event) => {
|
|
5221
|
+
if (this.miniSuppressClick) {
|
|
5222
|
+
this.miniSuppressClick = false;
|
|
5223
|
+
return;
|
|
5224
|
+
}
|
|
5225
|
+
this.minimapJump(event);
|
|
5226
|
+
});
|
|
5227
|
+
canvas.addEventListener("pointerdown", (event) => this.minimapPointerDown(event));
|
|
5228
|
+
canvas.addEventListener("pointermove", (event) => this.minimapPointerMove(event));
|
|
5229
|
+
canvas.addEventListener("pointerup", (event) => this.minimapPointerUp(event));
|
|
5230
|
+
canvas.addEventListener("pointercancel", (event) => this.minimapPointerUp(event));
|
|
5231
|
+
canvas.addEventListener("keydown", (event) => this.minimapKeydown(event));
|
|
5232
|
+
this.layoutMinimap();
|
|
5233
|
+
this.syncMinimapLabel();
|
|
5234
|
+
this.drawMinimapStatic();
|
|
5235
|
+
this.drawMinimapRect();
|
|
5236
|
+
}
|
|
5237
|
+
setMinimapOpen(open) {
|
|
5238
|
+
this.root?.setAttribute("data-minimap-open", String(open));
|
|
5239
|
+
this.miniToggle?.setAttribute("aria-expanded", String(open));
|
|
5240
|
+
if (open) this.miniCanvas?.focus();
|
|
5241
|
+
else this.miniToggle?.focus();
|
|
5242
|
+
}
|
|
5243
|
+
/** Re-fit the thumbnail whenever the active floor or complete overview
|
|
5244
|
+
* changes bounds. This is presentation-only; the source chart is untouched. */
|
|
5245
|
+
layoutMinimap() {
|
|
5246
|
+
const canvas = this.miniCanvas;
|
|
5247
|
+
const base = this.miniBase;
|
|
5248
|
+
const b = this.controller.getViewport()?.bounds;
|
|
5249
|
+
if (!canvas || !base || !b || !(b.width > 0 && b.height > 0)) return;
|
|
5250
|
+
const MAXW = 196;
|
|
5251
|
+
const MAXH = 146;
|
|
5004
5252
|
const PAD = 6;
|
|
5005
5253
|
const aspect = b.width / Math.max(1, b.height);
|
|
5006
5254
|
let w = MAXW;
|
|
@@ -5012,32 +5260,40 @@ var SeatPicker = class _SeatPicker {
|
|
|
5012
5260
|
w = Math.max(64, w);
|
|
5013
5261
|
h = Math.max(48, h);
|
|
5014
5262
|
const dpr = Math.min(2, window.devicePixelRatio || 1);
|
|
5015
|
-
const wrap = document.createElement("div");
|
|
5016
|
-
wrap.className = "sl-minimap";
|
|
5017
|
-
wrap.setAttribute("aria-hidden", "true");
|
|
5018
|
-
const canvas = document.createElement("canvas");
|
|
5019
5263
|
canvas.width = Math.round(w * dpr);
|
|
5020
5264
|
canvas.height = Math.round(h * dpr);
|
|
5021
5265
|
canvas.style.width = `${w}px`;
|
|
5022
5266
|
canvas.style.height = `${h}px`;
|
|
5023
|
-
wrap.appendChild(canvas);
|
|
5024
|
-
(this.regions["bottom-left"] ?? this.els.map).appendChild(wrap);
|
|
5025
|
-
this.miniCanvas = canvas;
|
|
5026
|
-
const scale = Math.min((w - PAD * 2) / Math.max(1, b.width), (h - PAD * 2) / Math.max(1, b.height)) * dpr;
|
|
5027
|
-
const offX = (w * dpr - b.width * scale) / 2 - b.x * scale;
|
|
5028
|
-
const offY = (h * dpr - b.height * scale) / 2 - b.y * scale;
|
|
5029
|
-
this.miniTf = { scale, offX, offY, dpr };
|
|
5030
|
-
const base = document.createElement("canvas");
|
|
5031
5267
|
base.width = canvas.width;
|
|
5032
5268
|
base.height = canvas.height;
|
|
5033
|
-
this.
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
this.
|
|
5269
|
+
this.miniTf = createMinimapTransform(b, canvas.width, canvas.height, dpr, PAD);
|
|
5270
|
+
}
|
|
5271
|
+
syncMinimapLabel() {
|
|
5272
|
+
if (!this.miniLabel || !this.miniWrap) return;
|
|
5273
|
+
const floors = this.controller.getFloors();
|
|
5274
|
+
const floor = this.controller.isFloorOverview() ? this.tf("picker.allFloors", "All floors") : floors.find((candidate) => candidate.id === this.controller.getActiveFloorId())?.name ?? this.tf("picker.venueOverview", "Venue overview");
|
|
5275
|
+
const sections = this.controller.getMinimapSnapshot().sections;
|
|
5276
|
+
const focused = sections.find((section) => section.active);
|
|
5277
|
+
const focusedLabel = focused?.label ?? this.lastSection?.label;
|
|
5278
|
+
const label = focusedLabel ? `${floor} \xB7 ${focusedLabel}` : floor;
|
|
5279
|
+
this.miniLabel.textContent = label;
|
|
5280
|
+
const selected = sections.filter((section) => section.state === "selected").length;
|
|
5281
|
+
const available = sections.filter((section) => section.state === "available").length;
|
|
5282
|
+
const inactive = sections.filter((section) => section.state === "inactive").length;
|
|
5283
|
+
this.miniWrap.setAttribute(
|
|
5284
|
+
"aria-label",
|
|
5285
|
+
sections.length ? `${label}. ${selected} selected, ${available} available, ${inactive} inactive sections.` : label
|
|
5286
|
+
);
|
|
5287
|
+
this.miniCanvas?.setAttribute(
|
|
5288
|
+
"aria-label",
|
|
5289
|
+
sections.length ? `${label}. ${selected} selected, ${available} available, ${inactive} inactive sections. ${this.tf("picker.venueMapInstructions", "The outlined region is the visible map area. Drag it, click a section, or use arrow keys to pan.")}` : `${label}. ${this.tf("picker.venueMapInstructions", "The outlined region is the visible map area. Drag it, click elsewhere, or use arrow keys to pan.")}`
|
|
5290
|
+
);
|
|
5037
5291
|
}
|
|
5038
5292
|
/** Repaint the static overview + rect (floor switch, live open/close). */
|
|
5039
5293
|
refreshMinimap() {
|
|
5040
5294
|
if (!this.miniBase) return;
|
|
5295
|
+
this.layoutMinimap();
|
|
5296
|
+
this.syncMinimapLabel();
|
|
5041
5297
|
this.drawMinimapStatic();
|
|
5042
5298
|
this.drawMinimapRect();
|
|
5043
5299
|
}
|
|
@@ -5045,8 +5301,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
5045
5301
|
drawMinimapStatic() {
|
|
5046
5302
|
const base = this.miniBase;
|
|
5047
5303
|
const tf = this.miniTf;
|
|
5048
|
-
|
|
5049
|
-
if (!base || !tf || !doc) return;
|
|
5304
|
+
if (!base || !tf) return;
|
|
5050
5305
|
const ctx = base.getContext("2d");
|
|
5051
5306
|
if (!ctx) return;
|
|
5052
5307
|
ctx.clearRect(0, 0, base.width, base.height);
|
|
@@ -5055,34 +5310,42 @@ var SeatPicker = class _SeatPicker {
|
|
|
5055
5310
|
const line = this.cssVar("--sl-line") || "rgba(139,147,167,.5)";
|
|
5056
5311
|
const muted = this.cssVar("--sl-muted") || "#8b93a7";
|
|
5057
5312
|
const accent = this.cssVar("--sl-accent") || "#6e7bff";
|
|
5058
|
-
const
|
|
5059
|
-
|
|
5060
|
-
for (const
|
|
5061
|
-
if (
|
|
5062
|
-
drewSection = true;
|
|
5063
|
-
const closed = this.controller.isSectionClosed(o.id);
|
|
5064
|
-
const fill = closed ? muted : o.color ?? (o.zone && zoneColor.get(o.zone)) ?? accent;
|
|
5313
|
+
const available = this.cssVar("--sl-success") || "#22a06b";
|
|
5314
|
+
const snapshot = this.controller.getMinimapSnapshot();
|
|
5315
|
+
for (const section of snapshot.sections) {
|
|
5316
|
+
if (section.outline.length < 3) continue;
|
|
5065
5317
|
ctx.beginPath();
|
|
5066
|
-
|
|
5318
|
+
section.outline.forEach((point, index) => index === 0 ? ctx.moveTo(fx(point.x), fy(point.y)) : ctx.lineTo(fx(point.x), fy(point.y)));
|
|
5067
5319
|
ctx.closePath();
|
|
5068
|
-
ctx.globalAlpha =
|
|
5069
|
-
ctx.fillStyle =
|
|
5320
|
+
ctx.globalAlpha = section.state === "inactive" ? 0.18 : section.state === "selected" ? 0.72 : 0.34;
|
|
5321
|
+
ctx.fillStyle = section.state === "inactive" ? muted : section.state === "selected" ? accent : available;
|
|
5070
5322
|
ctx.fill();
|
|
5071
|
-
ctx.globalAlpha = 0.
|
|
5323
|
+
ctx.globalAlpha = 0.82;
|
|
5072
5324
|
ctx.lineWidth = Math.max(1, tf.dpr);
|
|
5073
5325
|
ctx.strokeStyle = line;
|
|
5074
5326
|
ctx.stroke();
|
|
5327
|
+
if (section.active) {
|
|
5328
|
+
ctx.globalAlpha = 1;
|
|
5329
|
+
ctx.lineJoin = "round";
|
|
5330
|
+
ctx.lineWidth = Math.max(4, tf.dpr * 3.5);
|
|
5331
|
+
ctx.strokeStyle = this.cssVar("--sl-surface") || "#fff";
|
|
5332
|
+
ctx.stroke();
|
|
5333
|
+
ctx.lineWidth = Math.max(2, tf.dpr * 1.75);
|
|
5334
|
+
ctx.strokeStyle = accent;
|
|
5335
|
+
ctx.stroke();
|
|
5336
|
+
}
|
|
5075
5337
|
}
|
|
5076
5338
|
ctx.globalAlpha = 1;
|
|
5077
|
-
if (!
|
|
5339
|
+
if (!snapshot.sections.length) {
|
|
5078
5340
|
const r = Math.max(1, tf.dpr);
|
|
5079
|
-
for (const seat of
|
|
5080
|
-
|
|
5081
|
-
ctx.fillStyle =
|
|
5341
|
+
for (const seat of snapshot.seats) {
|
|
5342
|
+
ctx.globalAlpha = seat.state === "inactive" ? 0.22 : 0.78;
|
|
5343
|
+
ctx.fillStyle = seat.state === "inactive" ? muted : seat.state === "selected" ? accent : available;
|
|
5082
5344
|
ctx.beginPath();
|
|
5083
5345
|
ctx.arc(fx(seat.x), fy(seat.y), r, 0, Math.PI * 2);
|
|
5084
5346
|
ctx.fill();
|
|
5085
5347
|
}
|
|
5348
|
+
ctx.globalAlpha = 1;
|
|
5086
5349
|
}
|
|
5087
5350
|
}
|
|
5088
5351
|
/** Blit the base overview, then stroke the current viewport rectangle on top. */
|
|
@@ -5097,41 +5360,106 @@ var SeatPicker = class _SeatPicker {
|
|
|
5097
5360
|
ctx.drawImage(base, 0, 0);
|
|
5098
5361
|
const vp = this.controller.getViewport();
|
|
5099
5362
|
if (!vp) return;
|
|
5100
|
-
const
|
|
5101
|
-
const
|
|
5102
|
-
const
|
|
5103
|
-
const w = v.width * tf.scale;
|
|
5104
|
-
const h = v.height * tf.scale;
|
|
5105
|
-
const accent = this.cssVar("--sl-accent") || "#f4b740";
|
|
5363
|
+
const plan = minimapViewportPlan(vp.visible, tf, canvas.width, canvas.height, Math.max(2, tf.dpr * 2));
|
|
5364
|
+
const ink = this.cssVar("--sl-text") || "#12151c";
|
|
5365
|
+
const surface = this.cssVar("--sl-surface") || "#fff";
|
|
5106
5366
|
ctx.save();
|
|
5107
|
-
ctx.globalAlpha = 0.
|
|
5108
|
-
ctx.fillStyle =
|
|
5109
|
-
ctx.fillRect(x, y,
|
|
5367
|
+
ctx.globalAlpha = 0.1;
|
|
5368
|
+
ctx.fillStyle = ink;
|
|
5369
|
+
ctx.fillRect(plan.raw.x, plan.raw.y, plan.raw.width, plan.raw.height);
|
|
5370
|
+
ctx.globalAlpha = 0.9;
|
|
5371
|
+
ctx.lineJoin = "round";
|
|
5372
|
+
ctx.lineWidth = Math.max(4, tf.dpr * 3.25);
|
|
5373
|
+
ctx.strokeStyle = surface;
|
|
5374
|
+
ctx.strokeRect(plan.raw.x, plan.raw.y, plan.raw.width, plan.raw.height);
|
|
5110
5375
|
ctx.globalAlpha = 1;
|
|
5111
|
-
ctx.lineWidth = Math.max(
|
|
5112
|
-
ctx.strokeStyle =
|
|
5113
|
-
ctx.strokeRect(x, y,
|
|
5376
|
+
ctx.lineWidth = Math.max(2, tf.dpr * 1.75);
|
|
5377
|
+
ctx.strokeStyle = ink;
|
|
5378
|
+
ctx.strokeRect(plan.raw.x, plan.raw.y, plan.raw.width, plan.raw.height);
|
|
5379
|
+
if (plan.clipped && Object.values(plan.clippedEdges).some(Boolean)) {
|
|
5380
|
+
ctx.setLineDash([Math.max(3, tf.dpr * 3), Math.max(2, tf.dpr * 2)]);
|
|
5381
|
+
ctx.strokeRect(plan.clipped.x, plan.clipped.y, plan.clipped.width, plan.clipped.height);
|
|
5382
|
+
}
|
|
5114
5383
|
ctx.restore();
|
|
5115
5384
|
}
|
|
5116
|
-
|
|
5117
|
-
minimapJump(e) {
|
|
5385
|
+
minimapWorldPoint(event) {
|
|
5118
5386
|
const canvas = this.miniCanvas;
|
|
5119
5387
|
const tf = this.miniTf;
|
|
5120
|
-
if (!canvas || !tf) return;
|
|
5121
|
-
const
|
|
5122
|
-
const px = (
|
|
5123
|
-
const py = (
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5388
|
+
if (!canvas || !tf) return null;
|
|
5389
|
+
const rect = canvas.getBoundingClientRect();
|
|
5390
|
+
const px = (event.clientX - rect.left) * (canvas.width / Math.max(1, rect.width));
|
|
5391
|
+
const py = (event.clientY - rect.top) * (canvas.height / Math.max(1, rect.height));
|
|
5392
|
+
return { ...minimapPointToWorld({ x: px, y: py }, tf), px, py };
|
|
5393
|
+
}
|
|
5394
|
+
minimapPointerDown(event) {
|
|
5395
|
+
const point = this.minimapWorldPoint(event);
|
|
5396
|
+
const visible = this.controller.getViewport()?.visible;
|
|
5397
|
+
if (!point || !visible) return;
|
|
5398
|
+
const inside = point.x >= visible.x && point.x <= visible.x + visible.width && point.y >= visible.y && point.y <= visible.y + visible.height;
|
|
5399
|
+
if (!inside) return;
|
|
5400
|
+
this.miniDrag = {
|
|
5401
|
+
pointerId: event.pointerId,
|
|
5402
|
+
offsetX: point.x - (visible.x + visible.width / 2),
|
|
5403
|
+
offsetY: point.y - (visible.y + visible.height / 2),
|
|
5404
|
+
startX: event.clientX,
|
|
5405
|
+
startY: event.clientY,
|
|
5406
|
+
moved: false
|
|
5407
|
+
};
|
|
5408
|
+
this.miniCanvas?.setPointerCapture(event.pointerId);
|
|
5409
|
+
this.miniCanvas?.classList.add("sl-dragging");
|
|
5410
|
+
event.preventDefault();
|
|
5411
|
+
}
|
|
5412
|
+
minimapPointerMove(event) {
|
|
5413
|
+
const drag = this.miniDrag;
|
|
5414
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
5415
|
+
const point = this.minimapWorldPoint(event);
|
|
5416
|
+
if (!point) return;
|
|
5417
|
+
if (!drag.moved && Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) > 2) {
|
|
5418
|
+
drag.moved = true;
|
|
5419
|
+
this.controller.clearSectionFocus();
|
|
5420
|
+
}
|
|
5421
|
+
this.controller.panToWorld({ x: point.x - drag.offsetX, y: point.y - drag.offsetY });
|
|
5422
|
+
event.preventDefault();
|
|
5423
|
+
}
|
|
5424
|
+
minimapPointerUp(event) {
|
|
5425
|
+
const drag = this.miniDrag;
|
|
5426
|
+
if (!drag || drag.pointerId !== event.pointerId) return;
|
|
5427
|
+
this.miniSuppressClick = drag.moved;
|
|
5428
|
+
this.miniDrag = null;
|
|
5429
|
+
this.miniCanvas?.classList.remove("sl-dragging");
|
|
5430
|
+
if (this.miniCanvas?.hasPointerCapture(event.pointerId)) this.miniCanvas.releasePointerCapture(event.pointerId);
|
|
5431
|
+
}
|
|
5432
|
+
minimapKeydown(event) {
|
|
5433
|
+
const visible = this.controller.getViewport()?.visible;
|
|
5434
|
+
if (!visible) return;
|
|
5435
|
+
if (event.key === "Home") {
|
|
5436
|
+
this.controller.overview();
|
|
5437
|
+
event.preventDefault();
|
|
5438
|
+
return;
|
|
5439
|
+
}
|
|
5440
|
+
const dx = event.key === "ArrowLeft" ? -visible.width * 0.2 : event.key === "ArrowRight" ? visible.width * 0.2 : 0;
|
|
5441
|
+
const dy = event.key === "ArrowUp" ? -visible.height * 0.2 : event.key === "ArrowDown" ? visible.height * 0.2 : 0;
|
|
5442
|
+
if (!dx && !dy) return;
|
|
5443
|
+
this.controller.clearSectionFocus();
|
|
5444
|
+
this.controller.panToWorld({
|
|
5445
|
+
x: visible.x + visible.width / 2 + dx,
|
|
5446
|
+
y: visible.y + visible.height / 2 + dy
|
|
5447
|
+
});
|
|
5448
|
+
event.preventDefault();
|
|
5449
|
+
}
|
|
5450
|
+
/** Minimap click → focus an available section, otherwise pan there. */
|
|
5451
|
+
minimapJump(e) {
|
|
5452
|
+
const point = this.minimapWorldPoint(e);
|
|
5453
|
+
if (!point) return;
|
|
5454
|
+
for (const section of this.controller.getMinimapSnapshot().sections) {
|
|
5455
|
+
if (section.state === "inactive" || section.outline.length < 3) continue;
|
|
5456
|
+
if (pointInPolygon(point.x, point.y, section.outline)) {
|
|
5457
|
+
this.controller.focusSection(section.id);
|
|
5131
5458
|
return;
|
|
5132
5459
|
}
|
|
5133
5460
|
}
|
|
5134
|
-
this.controller.
|
|
5461
|
+
this.controller.clearSectionFocus();
|
|
5462
|
+
this.controller.panToWorld(point);
|
|
5135
5463
|
}
|
|
5136
5464
|
// ---- F4 price-band filter -------------------------------------------------
|
|
5137
5465
|
/** Effective display price of a category: host pricing override → first tier → base. */
|
|
@@ -5140,32 +5468,39 @@ var SeatPicker = class _SeatPicker {
|
|
|
5140
5468
|
if (chart === void 0 || !c.key) return chart;
|
|
5141
5469
|
return this.paidPrice(c.key, c.tiers?.[0]?.id ?? null, chart);
|
|
5142
5470
|
}
|
|
5143
|
-
/**
|
|
5471
|
+
/** Complete buyer-visible price range, including every ticket tier and
|
|
5472
|
+
* tier-specific host override. */
|
|
5473
|
+
catPriceRange(c) {
|
|
5474
|
+
const prices = c.tiers?.length ? c.tiers.map((tier) => this.paidPrice(c.key, tier.id ?? null, tier.price)) : c.price == null ? [] : [this.paidPrice(c.key, null, c.price)];
|
|
5475
|
+
return prices.length ? { min: Math.min(...prices), max: Math.max(...prices) } : void 0;
|
|
5476
|
+
}
|
|
5477
|
+
/** Derive category price bands without hiding the upper end of tier ranges. */
|
|
5144
5478
|
priceBands() {
|
|
5145
5479
|
const doc = this.controller.doc;
|
|
5146
5480
|
if (!doc) return [];
|
|
5147
|
-
const priced = doc.categories.map((c) => ({ key: c.key,
|
|
5481
|
+
const priced = doc.categories.map((c) => ({ key: c.key, range: this.catPriceRange(c) })).filter((x) => x.range != null);
|
|
5148
5482
|
if (!priced.length) return [];
|
|
5149
|
-
const distinct = [...new
|
|
5483
|
+
const distinct = [...new Map(priced.map((item) => [`${item.range.min}:${item.range.max}`, item.range])).values()].sort((a, b) => a.min - b.min || a.max - b.max);
|
|
5150
5484
|
if (distinct.length <= 5) {
|
|
5151
|
-
return distinct.map((
|
|
5152
|
-
id: `p${
|
|
5153
|
-
label: this.money(
|
|
5154
|
-
keys: priced.filter((
|
|
5155
|
-
min:
|
|
5156
|
-
max:
|
|
5485
|
+
return distinct.map((range) => ({
|
|
5486
|
+
id: `p${range.min}-${range.max}`,
|
|
5487
|
+
label: range.min === range.max ? this.money(range.min) : `${this.money(range.min)}\u2013${this.money(range.max)}`,
|
|
5488
|
+
keys: priced.filter((item) => item.range.min === range.min && item.range.max === range.max).map((item) => item.key),
|
|
5489
|
+
min: range.min,
|
|
5490
|
+
max: range.max
|
|
5157
5491
|
}));
|
|
5158
5492
|
}
|
|
5159
|
-
const
|
|
5493
|
+
const ordered = [...priced].sort((a, b) => a.range.min - b.range.min || a.range.max - b.range.max);
|
|
5494
|
+
const chunk = Math.ceil(ordered.length / 4);
|
|
5160
5495
|
const bands = [];
|
|
5161
|
-
for (let i = 0; i <
|
|
5162
|
-
const slice =
|
|
5163
|
-
const lo = slice
|
|
5164
|
-
const hi = slice
|
|
5496
|
+
for (let i = 0; i < ordered.length; i += chunk) {
|
|
5497
|
+
const slice = ordered.slice(i, i + chunk);
|
|
5498
|
+
const lo = Math.min(...slice.map((item) => item.range.min));
|
|
5499
|
+
const hi = Math.max(...slice.map((item) => item.range.max));
|
|
5165
5500
|
bands.push({
|
|
5166
5501
|
id: `b${i}`,
|
|
5167
5502
|
label: lo === hi ? this.money(lo) : `${this.money(lo)}\u2013${this.money(hi)}`,
|
|
5168
|
-
keys:
|
|
5503
|
+
keys: slice.map((item) => item.key),
|
|
5169
5504
|
min: lo,
|
|
5170
5505
|
max: hi
|
|
5171
5506
|
});
|
|
@@ -5220,7 +5555,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
5220
5555
|
this.syncProjection();
|
|
5221
5556
|
}
|
|
5222
5557
|
if (hasSections) {
|
|
5223
|
-
const
|
|
5558
|
+
const allObjects = [
|
|
5559
|
+
...doc.objects,
|
|
5560
|
+
...(doc.floors ?? []).flatMap((floor) => floor.objects)
|
|
5561
|
+
];
|
|
5562
|
+
const authoredZoneIds = new Set((doc.zones ?? []).map((zone) => zone.id));
|
|
5563
|
+
const hasZones = allObjects.some((object) => object.type === "section" && typeof object.zone === "string" && authoredZoneIds.has(object.zone));
|
|
5564
|
+
const RUNGS = hasZones ? ["zones", "sections", "seats"] : ["sections", "seats"];
|
|
5224
5565
|
const pills = document.createElement("div");
|
|
5225
5566
|
pills.className = "sl-rungs on";
|
|
5226
5567
|
pills.setAttribute("role", "group");
|
|
@@ -5253,18 +5594,38 @@ var SeatPicker = class _SeatPicker {
|
|
|
5253
5594
|
const floors = this.controller.getFloors();
|
|
5254
5595
|
const rail = document.createElement("div");
|
|
5255
5596
|
rail.className = "sl-floors on";
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
5259
|
-
|
|
5260
|
-
|
|
5261
|
-
|
|
5262
|
-
|
|
5263
|
-
|
|
5264
|
-
|
|
5265
|
-
|
|
5266
|
-
|
|
5597
|
+
const select = document.createElement("select");
|
|
5598
|
+
select.setAttribute("aria-label", this.tf("picker.chooseLevel", "Choose level"));
|
|
5599
|
+
const overview = document.createElement("option");
|
|
5600
|
+
overview.value = "";
|
|
5601
|
+
overview.textContent = this.tf("picker.allFloors", "All floors");
|
|
5602
|
+
select.appendChild(overview);
|
|
5603
|
+
for (const floor of floors) {
|
|
5604
|
+
const option = document.createElement("option");
|
|
5605
|
+
option.value = floor.id;
|
|
5606
|
+
option.textContent = floor.name;
|
|
5607
|
+
select.appendChild(option);
|
|
5608
|
+
}
|
|
5609
|
+
select.addEventListener("change", () => {
|
|
5610
|
+
if (select.value === "") this.controller.setFloorOverview(true);
|
|
5611
|
+
else this.controller.setFloor(select.value);
|
|
5612
|
+
this.showSectionCard(null);
|
|
5613
|
+
this.syncFloors();
|
|
5614
|
+
this.syncRung();
|
|
5615
|
+
this.refreshMinimap();
|
|
5267
5616
|
});
|
|
5617
|
+
rail.appendChild(select);
|
|
5618
|
+
const info = document.createElement("button");
|
|
5619
|
+
info.type = "button";
|
|
5620
|
+
info.className = "sl-floor-info";
|
|
5621
|
+
info.textContent = "i";
|
|
5622
|
+
const explanation = this.tf(
|
|
5623
|
+
"picker.floorHelp",
|
|
5624
|
+
"All floors shows the complete venue. Choose one level to inspect and select its seats."
|
|
5625
|
+
);
|
|
5626
|
+
info.setAttribute("aria-label", explanation);
|
|
5627
|
+
info.title = explanation;
|
|
5628
|
+
rail.appendChild(info);
|
|
5268
5629
|
this.regions["left-rail"].appendChild(rail);
|
|
5269
5630
|
this.floorsEl = rail;
|
|
5270
5631
|
this.syncFloors();
|
|
@@ -5310,19 +5671,59 @@ var SeatPicker = class _SeatPicker {
|
|
|
5310
5671
|
syncFloors() {
|
|
5311
5672
|
if (!this.floorsEl) return;
|
|
5312
5673
|
const active = this.controller.getActiveFloorId();
|
|
5313
|
-
this.
|
|
5314
|
-
|
|
5315
|
-
|
|
5674
|
+
const overview = this.controller.isFloorOverview();
|
|
5675
|
+
const select = this.floorsEl.querySelector("select");
|
|
5676
|
+
if (select) select.value = overview ? "" : active;
|
|
5316
5677
|
}
|
|
5317
5678
|
/** Show (or clear, on null) the tapped-section summary card. */
|
|
5318
5679
|
showSectionCard(summary) {
|
|
5319
5680
|
this.lastSection = summary;
|
|
5320
5681
|
this.secCardEl?.remove();
|
|
5321
5682
|
this.secCardEl = null;
|
|
5322
|
-
if (!summary)
|
|
5683
|
+
if (!summary) {
|
|
5684
|
+
this.refreshMinimap();
|
|
5685
|
+
return;
|
|
5686
|
+
}
|
|
5323
5687
|
this.secCardCollapsed = this.controller.getRung() === "seats";
|
|
5324
5688
|
this.secCardShownAt = Date.now();
|
|
5325
5689
|
this.renderSectionCard(summary);
|
|
5690
|
+
this.refreshMinimap();
|
|
5691
|
+
}
|
|
5692
|
+
/**
|
|
5693
|
+
* Open sections next to the focused one, in authored order.
|
|
5694
|
+
*
|
|
5695
|
+
* This is intentionally a circular ring: arena sections are usually authored
|
|
5696
|
+
* clockwise, so moving past the last section should reach the first without
|
|
5697
|
+
* forcing a buyer back through the overview level. Closed or empty sections
|
|
5698
|
+
* are skipped because focusing one cannot produce a useful buyer card.
|
|
5699
|
+
*/
|
|
5700
|
+
sectionNeighbours(sectionId) {
|
|
5701
|
+
const sections = this.activeFloorObjects().filter((object) => object.type === "section" && !this.controller.isSectionClosed(object.id) && this.controller.getSectionSeatCount(object.id) > 0);
|
|
5702
|
+
if (sections.length < 2) return null;
|
|
5703
|
+
const index = sections.findIndex((section) => section.id === sectionId);
|
|
5704
|
+
if (index < 0) return null;
|
|
5705
|
+
return {
|
|
5706
|
+
previous: sections[(index - 1 + sections.length) % sections.length],
|
|
5707
|
+
next: sections[(index + 1) % sections.length]
|
|
5708
|
+
};
|
|
5709
|
+
}
|
|
5710
|
+
/** Previous/next controls shared by expanded, mini, and phone section cards. */
|
|
5711
|
+
sectionNavigationHtml(summary) {
|
|
5712
|
+
const neighbours = this.sectionNeighbours(summary.id);
|
|
5713
|
+
if (!neighbours) return "";
|
|
5714
|
+
const previousLabel = neighbours.previous.label || neighbours.previous.id;
|
|
5715
|
+
const nextLabel = neighbours.next.label || neighbours.next.id;
|
|
5716
|
+
return `<span class="sl-seccard-nav" aria-label="${this.tf("picker.browseSections", "Browse sections")}"><button type="button" data-section-nav="${escapeOption(neighbours.previous.id)}" aria-label="${this.tf("picker.previousSection", "Previous section")}: ${escapeOption(previousLabel)}" title="${escapeOption(previousLabel)}">\u2190</button><button type="button" data-section-nav="${escapeOption(neighbours.next.id)}" aria-label="${this.tf("picker.nextSection", "Next section")}: ${escapeOption(nextLabel)}" title="${escapeOption(nextLabel)}">\u2192</button></span>`;
|
|
5717
|
+
}
|
|
5718
|
+
wireSectionNavigation(card) {
|
|
5719
|
+
card.querySelectorAll("[data-section-nav]").forEach((button) => {
|
|
5720
|
+
button.addEventListener("click", (event) => {
|
|
5721
|
+
event.preventDefault();
|
|
5722
|
+
event.stopPropagation();
|
|
5723
|
+
const sectionId = button.dataset.sectionNav;
|
|
5724
|
+
if (sectionId) this.controller.focusSection(sectionId);
|
|
5725
|
+
});
|
|
5726
|
+
});
|
|
5326
5727
|
}
|
|
5327
5728
|
/**
|
|
5328
5729
|
* Render the section card in the form the layout + state want: expanded card
|
|
@@ -5336,26 +5737,32 @@ var SeatPicker = class _SeatPicker {
|
|
|
5336
5737
|
this.lastSection = null;
|
|
5337
5738
|
return;
|
|
5338
5739
|
}
|
|
5339
|
-
const
|
|
5340
|
-
|
|
5341
|
-
|
|
5740
|
+
const paidRanges = summary.categories.length ? summary.categories.map((item) => {
|
|
5741
|
+
const category = this.controller.doc?.categories.find((candidate) => candidate.key === item.key);
|
|
5742
|
+
return category ? this.catPriceRange(category) : { min: item.priceMin, max: item.priceMax };
|
|
5743
|
+
}).filter((range) => range != null) : [{ min: summary.priceMin, max: summary.priceMax }];
|
|
5744
|
+
const paidMin = Math.min(...paidRanges.map((range) => range.min));
|
|
5745
|
+
const paidMax = Math.max(...paidRanges.map((range) => range.max));
|
|
5342
5746
|
const priceLabel = paidMin === paidMax ? this.money(paidMin) : `${this.money(paidMin)}\u2013${this.money(paidMax)}`;
|
|
5343
5747
|
const leftLabel = tCount("picker.seatsLeftInSection", summary.seatsLeft);
|
|
5344
5748
|
const xBtn = `<button type="button" class="sl-seccard-x" aria-label="${t2("picker.closeSectionSummary")}">\u2715</button>`;
|
|
5749
|
+
const sectionNav = this.sectionNavigationHtml(summary);
|
|
5345
5750
|
const card = document.createElement("div");
|
|
5346
5751
|
const narrow = this.root?.dataset.layout === "narrow";
|
|
5347
5752
|
if (narrow) {
|
|
5348
5753
|
card.className = "sl-seccard strip on";
|
|
5349
5754
|
card.setAttribute("role", "status");
|
|
5350
5755
|
card.setAttribute("aria-label", t2("picker.sectionSummaryAria", { label: summary.label }));
|
|
5351
|
-
card.innerHTML = `<span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span><span class="sl-seccard-left">${leftLabel}</span>` + (summary.categories.length ? `<span class="sl-seccard-price">${priceLabel}</span>` : "") + xBtn;
|
|
5756
|
+
card.innerHTML = `<span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span><span class="sl-seccard-left">${leftLabel}</span>` + (summary.categories.length ? `<span class="sl-seccard-price">${priceLabel}</span>` : "") + sectionNav + xBtn;
|
|
5757
|
+
this.wireSectionNavigation(card);
|
|
5352
5758
|
card.querySelector(".sl-seccard-x").addEventListener("click", () => this.controller.overview());
|
|
5353
5759
|
(this.els.sheetHead ?? this.els.side ?? this.els.map).appendChild(card);
|
|
5354
5760
|
} else if (this.secCardCollapsed) {
|
|
5355
5761
|
card.className = "sl-seccard mini on";
|
|
5356
5762
|
card.setAttribute("role", "button");
|
|
5357
5763
|
card.setAttribute("aria-label", t2("picker.sectionSummaryAria", { label: summary.label }));
|
|
5358
|
-
card.innerHTML = `<span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span><span class="sl-seccard-left">${leftLabel}</span>` + xBtn;
|
|
5764
|
+
card.innerHTML = `<span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span><span class="sl-seccard-left">${leftLabel}</span>` + sectionNav + xBtn;
|
|
5765
|
+
this.wireSectionNavigation(card);
|
|
5359
5766
|
card.addEventListener("click", (e) => {
|
|
5360
5767
|
if (e.target.closest(".sl-seccard-x")) return;
|
|
5361
5768
|
this.secCardCollapsed = false;
|
|
@@ -5370,9 +5777,13 @@ var SeatPicker = class _SeatPicker {
|
|
|
5370
5777
|
card.setAttribute("aria-label", t2("picker.sectionSummaryAria", { label: summary.label }));
|
|
5371
5778
|
const mix = summary.categories.map((c) => {
|
|
5372
5779
|
const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
|
|
5373
|
-
|
|
5780
|
+
const category = this.controller.doc?.categories.find((candidate) => candidate.key === c.key);
|
|
5781
|
+
const range = category ? this.catPriceRange(category) : { min: c.priceMin, max: c.priceMax };
|
|
5782
|
+
const rangeLabel = range && range.min !== range.max ? `${this.money(range.min)}\u2013${this.money(range.max)}` : this.money(range?.min ?? c.price);
|
|
5783
|
+
return `<span class="sl-seccard-mix-item${dim ? " sl-dim" : ""}"><span class="sl-seccard-mix-dot" style="background:${c.color}"></span>${c.label} <span class="sl-seccard-mix-price">${rangeLabel}</span></span>`;
|
|
5374
5784
|
}).join("");
|
|
5375
|
-
card.innerHTML = `<div class="sl-seccard-head"><span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span>` + (summary.categories.length ? `<span class="sl-seccard-price">${priceLabel}</span>` : "") + xBtn + `</div><div class="sl-seccard-zone">${summary.zoneLabel ? `${summary.zoneLabel} \xB7 ` : ""}<span class="sl-seccard-left">${leftLabel}</span></div>` + (summary.entrance ? `<div class="sl-seccard-entrance">${t2("picker.entrance")} ${String(summary.entrance).replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[ch])}</div>` : "") + (mix ? `<div class="sl-seccard-mix">${mix}</div>` : "") + `<div class="sl-seccard-foot"><button type="button" class="sl-seccard-overview">\u2190 ${t2("picker.overview")}</button
|
|
5785
|
+
card.innerHTML = `<div class="sl-seccard-head"><span class="sl-seccard-dot" style="background:${summary.color}"></span><span class="sl-seccard-name">${summary.label}</span>` + (summary.categories.length ? `<span class="sl-seccard-price">${priceLabel}</span>` : "") + xBtn + `</div><div class="sl-seccard-zone">${summary.zoneLabel ? `${summary.zoneLabel} \xB7 ` : ""}<span class="sl-seccard-left">${leftLabel}</span></div>` + (summary.entrance ? `<div class="sl-seccard-entrance">${t2("picker.entrance")} ${String(summary.entrance).replace(/[&<>"]/g, (ch) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[ch])}</div>` : "") + (mix ? `<div class="sl-seccard-mix">${mix}</div>` : "") + `<div class="sl-seccard-foot"><button type="button" class="sl-seccard-overview">\u2190 ${t2("picker.overview")}</button>` + sectionNav + `<span class="sl-seccard-hint">${t2("picker.tapSeatHint")}</span></div>`;
|
|
5786
|
+
this.wireSectionNavigation(card);
|
|
5376
5787
|
card.querySelector(".sl-seccard-x").addEventListener("click", () => this.controller.overview());
|
|
5377
5788
|
card.querySelector(".sl-seccard-overview").addEventListener("click", () => this.controller.overview());
|
|
5378
5789
|
(this.regions["top-center"] ?? this.els.map).appendChild(card);
|
|
@@ -5437,13 +5848,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
5437
5848
|
const cat = this.controller.doc?.categories.find((c) => c.key === seat.categoryKey);
|
|
5438
5849
|
const status = this.controller.getStatus(seat.id) ?? "free";
|
|
5439
5850
|
const statusText = status === "free" ? this.tf("picker.statusAvailable", "available") : status === "held" ? this.tf("picker.statusOnHold", "on hold") : this.tf("picker.statusTaken2", "taken");
|
|
5440
|
-
const
|
|
5851
|
+
const priceRange = cat ? this.catPriceRange(cat) : void 0;
|
|
5852
|
+
const priceText = priceRange ? priceRange.min === priceRange.max ? this.money(priceRange.min) : `${this.money(priceRange.min)}\u2013${this.money(priceRange.max)}` : void 0;
|
|
5441
5853
|
const table = this.controller.tableSelection(seat.id);
|
|
5442
5854
|
const details = table ?? this.controller.seatDetails(seat.id);
|
|
5443
5855
|
const typeWord = this.rowTypeWord(details);
|
|
5444
5856
|
const buyerName = details?.rowLabel ?? details?.displayLabel ?? seat.displayLabel ?? seat.label;
|
|
5445
5857
|
const identity = table ? `${typeWord} ${buyerName}, ${table.bookingMode === "variable" ? t2("picker.minToMaxGuests", { min: table.minOccupancy, max: table.maxOccupancy }) : t2("picker.capacityGuests", { count: table.capacity })}` : details?.objectType === "booth" ? `${typeWord} ${buyerName}` : details?.objectType === "table" ? `${typeWord} ${buyerName}, ${t2("picker.seatNumberLower", { label: details.seatNumber ?? seat.label })}` : t2("picker.seatLabel", { label: details?.displayLabel ?? seat.displayLabel ?? seat.label });
|
|
5446
|
-
this.srEl.textContent = `${identity}, ${cat?.label ?? seat.categoryKey}${
|
|
5858
|
+
this.srEl.textContent = `${identity}, ${cat?.label ?? seat.categoryKey}${priceText ? `, ${priceText}` : ""}, ${statusText}`;
|
|
5447
5859
|
}
|
|
5448
5860
|
// ---- atomic table confirmation / guest quantity ---------------------------
|
|
5449
5861
|
showTableDialog(table, held, returnFocus) {
|
|
@@ -5610,12 +6022,22 @@ var SeatPicker = class _SeatPicker {
|
|
|
5610
6022
|
details?.rowLabel || details?.objectType === "booth" ? `<div class="sl-confirm-field"><span class="sl-confirm-key">${safe(this.rowTypeWord(details))}</span><span class="sl-confirm-value">${safe(this.rowShort(details) ?? details?.displayLabel ?? seat.displayLabel ?? seat.label)}</span></div>` : "",
|
|
5611
6023
|
details?.objectType !== "booth" ? `<div class="sl-confirm-field"><span class="sl-confirm-key">${this.tf("picker.seat", "Seat")}</span><span class="sl-confirm-value">${safe(details?.seatNumber ?? details?.displayLabel ?? seat.displayLabel ?? seat.label)}</span></div>` : ""
|
|
5612
6024
|
].filter(Boolean).join("");
|
|
6025
|
+
const tiers = details?.tiers ?? cat?.tiers ?? [];
|
|
6026
|
+
const selectedTierId = details?.tierId ?? tiers[0]?.id;
|
|
6027
|
+
const tierGuidance = (tier) => tier.buyerMessage?.trim() || (tier.restriction === "companion" ? this.tf("picker.companionRequiresWheelchair", "Requires the adjacent wheelchair place") : void 0);
|
|
6028
|
+
const tierChoices = tiers.length > 1 ? `<fieldset class="sl-confirm-tiers"><legend>${this.tf("picker.ticketType", "Ticket type")}</legend>` + tiers.map((tier) => {
|
|
6029
|
+
const guidance = tierGuidance(tier);
|
|
6030
|
+
return `<button type="button" class="sl-confirm-tier" data-confirm-tier="${safe(tier.id)}" aria-pressed="${tier.id === selectedTierId}"><span class="sl-confirm-tier-copy"><span class="sl-confirm-tier-name">${safe(tier.name)}</span>` + (guidance ? `<small class="sl-confirm-tier-note">${safe(guidance)}</small>` : "") + `</span><span class="sl-confirm-tier-price">${this.money(this.paidPrice(seat.categoryKey, tier.id, tier.price))}</span></button>`;
|
|
6031
|
+
}).join("") + `</fieldset>` : tiers[0] && tierGuidance(tiers[0]) ? `<p class="sl-confirm-tier-note">${safe(tierGuidance(tiers[0]))}</p>` : "";
|
|
5613
6032
|
const el = document.createElement("div");
|
|
5614
6033
|
el.className = "sl-confirm";
|
|
5615
6034
|
el.setAttribute("role", "dialog");
|
|
5616
6035
|
el.setAttribute("aria-label", t2("picker.confirmSeatLabel", { label: seat.label }));
|
|
5617
|
-
|
|
5618
|
-
|
|
6036
|
+
const categoryColor = cat?.color ?? "#6e7bff";
|
|
6037
|
+
const categoryBand = mixThemeColors(categoryColor, this.cssVar("--sl-surface") || "#1a2234", 0.76) ?? categoryColor;
|
|
6038
|
+
el.style.setProperty("--sl-cat", categoryColor);
|
|
6039
|
+
el.style.setProperty("--sl-cat-ink", highestContrastInk(categoryBand, "#172033", "#ffffff"));
|
|
6040
|
+
el.innerHTML = `<div class="sl-confirm-grid">` + identityFields + `</div><div class="sl-confirm-cat"><span class="sl-dot" style="background:${cat?.color ?? "#6e7bff"}"></span><span class="sl-confirm-cat-name">${safe(details?.categoryLabel ?? cat?.label ?? seat.categoryKey)}</span>` + (price != null ? `<span class="sl-confirm-price">${this.money(price)}</span>` : "") + `</div><div class="sl-confirm-body">` + tierChoices + this.wheelchairConfirmHtml(details?.wheelchairSpaceType) + this.commercialConfirmHtml(seat.commercial) + (this.seatViewEnabled() && this.buyerView !== "venue3d" ? this.confirmThumbHtml(seat) : "") + (this.buyerView === "venue3d" ? `${this.seatConfidenceConfirmHtml(seat, `${details?.displayLabel ?? seat.displayLabel ?? seat.label} \xB7 ${price == null ? this.tf("picker.priceNotSupplied", "Price not supplied") : this.money(price)}`)}<div class="sl-confirm-inspect-row">${this.view3dCompareConfirmHtml(seat)}${this.see3dConfirmHtml()}</div>` : this.see3dConfirmHtml()) + `<div class="sl-confirm-row"><button type="button" class="sl-confirm-cancel">${t2("common.cancel")}</button><button type="button" class="sl-confirm-add"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4 4L19 7"/></svg>${this.tf("picker.select", "Select")}</button></div></div>`;
|
|
5619
6041
|
this.els.map.appendChild(el);
|
|
5620
6042
|
this.confirmEl = el;
|
|
5621
6043
|
const thumb = el.querySelector(".sl-confirm-thumb");
|
|
@@ -5631,6 +6053,22 @@ var SeatPicker = class _SeatPicker {
|
|
|
5631
6053
|
this.openSeatConfidencePassport(seat, event.currentTarget instanceof HTMLElement ? event.currentTarget : null);
|
|
5632
6054
|
});
|
|
5633
6055
|
el.querySelector(".sl-confirm-compare")?.addEventListener("click", () => this.saveView3dComparisonSeat(seat));
|
|
6056
|
+
el.querySelectorAll("[data-confirm-tier]").forEach((button) => {
|
|
6057
|
+
button.addEventListener("click", () => {
|
|
6058
|
+
const tierId = button.dataset.confirmTier;
|
|
6059
|
+
const tier = tiers.find((candidate) => candidate.id === tierId);
|
|
6060
|
+
if (!tier) return;
|
|
6061
|
+
this.controller.setSeatTier(seat.id, tier.id);
|
|
6062
|
+
el.querySelectorAll("[data-confirm-tier]").forEach((candidate) => {
|
|
6063
|
+
candidate.setAttribute("aria-pressed", String(candidate === button));
|
|
6064
|
+
});
|
|
6065
|
+
const priceElement = el.querySelector(".sl-confirm-price");
|
|
6066
|
+
if (priceElement) {
|
|
6067
|
+
priceElement.textContent = this.money(this.paidPrice(seat.categoryKey, tier.id, tier.price));
|
|
6068
|
+
}
|
|
6069
|
+
this.reanchorConfirm();
|
|
6070
|
+
});
|
|
6071
|
+
});
|
|
5634
6072
|
el.querySelector(".sl-confirm-3d")?.addEventListener("click", () => {
|
|
5635
6073
|
if (this.buyerView === "venue3d") {
|
|
5636
6074
|
this.dismissConfirm();
|
|
@@ -6007,21 +6445,23 @@ var SeatPicker = class _SeatPicker {
|
|
|
6007
6445
|
const doc = this.controller.doc;
|
|
6008
6446
|
if (!doc || !this.els.prices) return;
|
|
6009
6447
|
const left = this.controller.categoryAvailability();
|
|
6010
|
-
|
|
6011
|
-
this.
|
|
6448
|
+
const sellable = doc.categories.filter((c) => !c.notForSale);
|
|
6449
|
+
this.narrateAvailability(sellable, left);
|
|
6450
|
+
this.syncSoldout(sellable, left);
|
|
6012
6451
|
const PRICE_LIMIT = 5;
|
|
6013
|
-
const overflow =
|
|
6452
|
+
const overflow = sellable.length - PRICE_LIMIT;
|
|
6014
6453
|
const collapsed = overflow > 1 && !this.pricesExpanded;
|
|
6015
|
-
const shown = collapsed ?
|
|
6454
|
+
const shown = collapsed ? sellable.slice(0, PRICE_LIMIT) : sellable;
|
|
6016
6455
|
this.els.prices.classList.toggle("sl-expanded", overflow > 1 && this.pricesExpanded);
|
|
6017
6456
|
this.els.prices.innerHTML = shown.map((c) => {
|
|
6018
6457
|
const price = this.catPrice(c);
|
|
6458
|
+
const range = this.catPriceRange(c);
|
|
6019
6459
|
const offer = this.offerPrice(c.key);
|
|
6020
6460
|
const previous = offer?.previousPrice != null && offer.previousPrice > offer.price ? offer.previousPrice : null;
|
|
6021
6461
|
const active = this.focusedCatKey === c.key;
|
|
6022
6462
|
const dim = this.priceBandKeys != null && !this.priceBandKeys.has(c.key);
|
|
6023
|
-
return `<div class="sl-price-row${dim ? " sl-dim" : ""}${active ? " sl-active" : ""}" data-cat="${escapeOption(c.key)}" role="button" tabindex="0" aria-pressed="${active}" title="${escapeOption(active ? this.tf("picker.showAllSeats", "Show all seats") : t2("picker.showLabelSeatsOnMap", { label: c.label }))}"><span class="sl-dot" style="background:${escapeOption(c.color)}"></span><span class="sl-price-label">${escapeOption(c.label)}` + (offer?.offerName ? `<small class="sl-price-offer">${escapeOption(offer.offerName)}</small>` : "") + `</span><span class="sl-price-left">${tCount("picker.leftCount", left[c.key] ?? 0)}</span>` + (previous != null ? `<span class="sl-price-was">${escapeOption(this.money(previous))}</span>` : "") + (price != null ? `<span class="sl-price-amt">${this.money(price)}</span>` : "") + `</div>`;
|
|
6024
|
-
}).join("") + (overflow > 1 ? `<button type="button" class="sl-price-more" aria-expanded="${!collapsed}">` + (collapsed ? t2("picker.showAllTicketTypes", { count:
|
|
6463
|
+
return `<div class="sl-price-row${dim ? " sl-dim" : ""}${active ? " sl-active" : ""}" data-cat="${escapeOption(c.key)}" role="button" tabindex="0" aria-pressed="${active}" title="${escapeOption(active ? this.tf("picker.showAllSeats", "Show all seats") : t2("picker.showLabelSeatsOnMap", { label: c.label }))}"><span class="sl-dot" style="background:${escapeOption(c.color)}"></span><span class="sl-price-label">${escapeOption(c.label)}` + (offer?.offerName ? `<small class="sl-price-offer">${escapeOption(offer.offerName)}</small>` : "") + `</span><span class="sl-price-left">${tCount("picker.leftCount", left[c.key] ?? 0)}</span>` + (previous != null ? `<span class="sl-price-was">${escapeOption(this.money(previous))}</span>` : "") + (range != null ? `<span class="sl-price-amt">${range.min === range.max ? this.money(range.min) : `${this.money(range.min)}\u2013${this.money(range.max)}`}</span>` : price != null ? `<span class="sl-price-amt">${this.money(price)}</span>` : "") + `</div>`;
|
|
6464
|
+
}).join("") + (overflow > 1 ? `<button type="button" class="sl-price-more" aria-expanded="${!collapsed}">` + (collapsed ? t2("picker.showAllTicketTypes", { count: sellable.length }) : this.tf("picker.showFewer", "Show fewer")) + `</button>` : "") + `<div class="sl-status-key" aria-label="${this.tf("picker.seatStatusLegend", "Seat status legend")}"><span class="sl-status-item"><i class="sl-status-icon" aria-hidden="true"><svg viewBox="0 0 24 24"><rect x="5" y="10" width="14" height="10" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></i>${this.tf("picker.temporarilyHeld", "Temporarily held")}</span><span class="sl-status-item"><i class="sl-status-icon sold" aria-hidden="true"><svg viewBox="0 0 24 24"><path d="M7 17L17 7"/></svg></i>${this.tf("picker.sold", "Sold")}</span></div>`;
|
|
6025
6465
|
this.els.prices.querySelectorAll(".sl-price-row").forEach((row) => {
|
|
6026
6466
|
row.addEventListener("mouseenter", () => this.controller.getRenderer()?.setCategoryHighlight?.(row.dataset.cat ?? null));
|
|
6027
6467
|
row.addEventListener("mouseleave", () => this.controller.getRenderer()?.setCategoryHighlight?.(null));
|
|
@@ -6125,7 +6565,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
6125
6565
|
const noPicks = !seats.length && !heldItems.length && !this.pendingGACount();
|
|
6126
6566
|
if (noPicks) this.baReopen = false;
|
|
6127
6567
|
if (!this.salesClosed && !this.hold && (noPicks || this.baReopen || this.bestAvailableBusy || this.bestAvailableConfirm)) {
|
|
6128
|
-
const cats = this.controller.doc?.categories ?? [];
|
|
6568
|
+
const cats = (this.controller.doc?.categories ?? []).filter((c) => !c.notForSale);
|
|
6129
6569
|
const zones = this.controller.getBestAvailableZones();
|
|
6130
6570
|
if (this.baZone && !zones.some((zone) => zone.id === this.baZone)) this.baZone = "";
|
|
6131
6571
|
parts.push(this.bestAvailableConfirm ? `<div class="sl-ba" role="alert"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>${this.tf("picker.replaceCurrentChoices", "Replace your current choices?")}</div><div class="sl-ba-replace"><b>${t2("picker.willFindSeatsTogether", { count: this.baQty })}</b><span>${this.tf("picker.manualTicketsRemovedNote", "Your manually selected tickets will be removed only after a new group is secured.")}</span></div><div class="sl-ba-actions"><button type="button" data-ba-cancel>${this.tf("picker.keepMine", "Keep mine")}</button><button type="button" class="replace" data-ba-replace>${this.tf("picker.findNewSeats", "Find new seats")}</button></div></div>` : `<div class="sl-ba"><div class="sl-ba-title"><span class="spark" aria-hidden="true">\u2726</span>${this.tf("picker.findBestSeatsTogether", "Find the best seats together")}</div><div class="sl-ba-copy"><span class="wide">${this.tf("picker.willChooseClosestGroup", "We\u2019ll choose the closest available group for you.")}</span><span class="narrow">${this.tf("picker.closestGroupChosenInstantly", "Closest available group, chosen instantly.")}</span></div>` + // Premium quick-pick — present only when the chart actually has premium
|
|
@@ -6181,8 +6621,10 @@ var SeatPicker = class _SeatPicker {
|
|
|
6181
6621
|
nextTrayKeys.add(itemKey);
|
|
6182
6622
|
const cat = this.controller.doc?.categories.find((c) => c.key === s.categoryKey);
|
|
6183
6623
|
const tierSelect = s.tiers && s.tiers.length ? `<select class="tier" data-tier="${s.id}" aria-label="${t2("picker.ticketTierFor", { label: s.label })}">` + s.tiers.map((ti) => `<option value="${ti.id}"${ti.id === s.tierId ? " selected" : ""}>${ti.name} \xB7 ${this.money(this.paidPrice(s.categoryKey, ti.id, ti.price))}</option>`).join("") + `</select>` : "";
|
|
6624
|
+
const chosenTier = s.tiers?.find((tier) => tier.id === s.tierId) ?? s.tiers?.[0];
|
|
6625
|
+
const tierNote = chosenTier?.buyerMessage?.trim() || (chosenTier?.restriction === "companion" ? this.tf("picker.companionRequiresWheelchair", "Requires the adjacent wheelchair place") : "");
|
|
6184
6626
|
parts.push(
|
|
6185
|
-
`<div class="sl-chip${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-seat="${s.id}" data-locate="${s.id}"><div class="sl-chip-main">` + idGrid(s.id, s.label, s.objectType, s.quantity ?? 1, s.objectId, s) + `<div class="sl-chip-sub"><span class="sl-ticket-state" aria-label="${this.tf("picker.selected", "Selected")}" title="${this.tf("picker.selected", "Selected")}"><svg viewBox="0 0 24 24"><path d="M5 12l4 4L19 6"/></svg></span><span class="cat">${cat?.label ?? s.categoryKey}</span>` + (s.objectType === "table" && s.bookingMode === "variable" ? `<button type="button" class="sl-table-edit" data-table-edit="${encodeURIComponent(s.label)}">${t2("picker.guestsCountEdit", { count: s.quantity ?? 1 })}</button>` : "") + `${this.wheelchairChipMarker(s.wheelchairSpaceType)}${this.commercialChipMarker(s.commercial)}${tierSelect}<span class="amt">${this.money(this.paidPrice(s.categoryKey, s.tierId ?? null, s.price) * (s.quantity ?? 1))}</span></div
|
|
6627
|
+
`<div class="sl-chip${this.lastTrayKeys.has(itemKey) ? "" : " sl-enter"}" data-key="${itemKey}" data-seat="${s.id}" data-locate="${s.id}"><div class="sl-chip-main">` + idGrid(s.id, s.label, s.objectType, s.quantity ?? 1, s.objectId, s) + `<div class="sl-chip-sub"><span class="sl-ticket-state" aria-label="${this.tf("picker.selected", "Selected")}" title="${this.tf("picker.selected", "Selected")}"><svg viewBox="0 0 24 24"><path d="M5 12l4 4L19 6"/></svg></span><span class="cat">${cat?.label ?? s.categoryKey}</span>` + (s.objectType === "table" && s.bookingMode === "variable" ? `<button type="button" class="sl-table-edit" data-table-edit="${encodeURIComponent(s.label)}">${t2("picker.guestsCountEdit", { count: s.quantity ?? 1 })}</button>` : "") + `${this.wheelchairChipMarker(s.wheelchairSpaceType)}${this.commercialChipMarker(s.commercial)}${tierSelect}<span class="amt">${this.money(this.paidPrice(s.categoryKey, s.tierId ?? null, s.price) * (s.quantity ?? 1))}</span></div>` + (tierNote ? `<small class="sl-tier-note">${escapeOption(tierNote)}</small>` : "") + `</div>` + iconRail(t2("picker.removeSeatLabel", { label: s.label }), canView && s.objectType !== "table" ? s.label : null) + `</div>`
|
|
6186
6628
|
);
|
|
6187
6629
|
}
|
|
6188
6630
|
for (const area of gaAreas) {
|
|
@@ -6495,7 +6937,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
6495
6937
|
const problem = err;
|
|
6496
6938
|
const labels = (problem.conflicts ?? []).map((conflict) => conflict.label).filter(Boolean).slice(0, 3);
|
|
6497
6939
|
if (problem.reason === "event_closed") this.setSalesClosed(true);
|
|
6498
|
-
const message = problem.reason === "event_closed" ? this.tf("picker.seatSalesClosedForEvent", "Seat sales have closed for this event.") : labels.length ? labels.length === 1 ? t2("picker.labelsNoLongerAvailable.one", { labels: labels.join(", ") }) : t2("picker.labelsNoLongerAvailable.other", { labels: labels.join(", ") }) : this.tf("picker.seatsJustTaken", "One or more seats were just taken. Please pick again.");
|
|
6940
|
+
const message = problem.reason === "event_closed" ? this.tf("picker.seatSalesClosedForEvent", "Seat sales have closed for this event.") : problem.reason === "companion_pair_required" ? this.tf("picker.companionPairRequired", "Select the adjacent wheelchair place with each companion ticket.") : problem.reason === "ticket_option_not_applicable" ? this.tf("picker.ticketOptionNotApplicable", "That ticket choice is not available for the selected seat.") : labels.length ? labels.length === 1 ? t2("picker.labelsNoLongerAvailable.one", { labels: labels.join(", ") }) : t2("picker.labelsNoLongerAvailable.other", { labels: labels.join(", ") }) : this.tf("picker.seatsJustTaken", "One or more seats were just taken. Please pick again.");
|
|
6499
6941
|
this.toast(message, "error");
|
|
6500
6942
|
this.setCtaPhase("idle");
|
|
6501
6943
|
} finally {
|
|
@@ -6824,13 +7266,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
6824
7266
|
const isBooth = details.objectType === "booth";
|
|
6825
7267
|
const hasLoc = details.sectionLabel || details.rowLabel || details.seatNumber;
|
|
6826
7268
|
const grid = isGroupedTable ? `<div class="sl-tip-grid"><div class="sl-tip-field"><span class="sl-tip-key">${esc(this.rowTypeWord(details))}</span><span class="sl-tip-val">${esc(details.rowLabel ?? details.displayLabel ?? details.label)}</span></div><div class="sl-tip-field"><span class="sl-tip-key">${this.tf("picker.guests", "Guests")}</span><span class="sl-tip-val">${details.bookingMode === "variable" ? `${details.minOccupancy}\u2013${details.maxOccupancy}` : details.capacity}</span></div></div>` : isBooth ? `<div class="sl-tip-grid">` + (details.sectionLabel ? `<div class="sl-tip-field"><span class="sl-tip-key">${this.tf("picker.section", "Section")}</span><span class="sl-tip-val">${esc(details.sectionLabel)}</span></div>` : "") + `<div class="sl-tip-field"><span class="sl-tip-key">${esc(this.rowTypeWord(details))}</span><span class="sl-tip-val">${esc(details.rowLabel ?? details.displayLabel ?? details.label)}</span></div></div>` : hasLoc ? `<div class="sl-tip-grid">` + (details.sectionLabel ? `<div class="sl-tip-field"><span class="sl-tip-key">${this.tf("picker.section", "Section")}</span><span class="sl-tip-val">${esc(details.sectionLabel)}</span></div>` : "") + (details.rowLabel ? `<div class="sl-tip-field"><span class="sl-tip-key">${esc(this.rowTypeWord(details))}</span><span class="sl-tip-val">${esc(this.rowShort(details))}</span></div>` : "") + (details.seatNumber ? `<div class="sl-tip-field"><span class="sl-tip-key">${this.tf("picker.seat", "Seat")}</span><span class="sl-tip-val">${esc(details.seatNumber)}</span></div>` : "") + `</div>` : `<div class="sl-tip-grid one"><div class="sl-tip-field"><span class="sl-tip-key">${this.tf("picker.seat", "Seat")}</span><span class="sl-tip-val">${esc(details.displayLabel ?? details.label)}</span></div></div>`;
|
|
6827
|
-
const
|
|
7269
|
+
const notForSale = details.status === "not_for_sale";
|
|
7270
|
+
const statusLine = details.status === "free" ? "" : `<div class="sl-tip-status">${notForSale ? this.tf("picker.notAvailable", "Not available") : details.status === "held" ? t2("map.statusHeld") : t2("map.statusTaken")}</div>`;
|
|
6828
7271
|
const limited = this.limitedViewLabel(details.commercial);
|
|
6829
7272
|
const cxLine = limited ? `<div class="sl-tip-cx"><span class="g" aria-hidden="true">\u25D0</span>${esc(limited)}</div>` : "";
|
|
6830
7273
|
const wheelchair = this.wheelchairProvisionLabel(details.wheelchairSpaceType);
|
|
6831
7274
|
const wheelchairLine = wheelchair ? `<div class="sl-tip-cx"><span class="g" aria-hidden="true">\u267F</span>${esc(wheelchair)}</div>` : "";
|
|
6832
7275
|
this.tipEl.style.setProperty("--sl-cat", details.categoryColor);
|
|
6833
|
-
this.tipEl.innerHTML = grid + `<div class="sl-tip-cat"><span class="sl-tip-dot" style="background:${details.categoryColor}"></span><span class="sl-tip-name">${esc(details.categoryLabel)}</span
|
|
7276
|
+
this.tipEl.innerHTML = grid + `<div class="sl-tip-cat"><span class="sl-tip-dot" style="background:${details.categoryColor}"></span><span class="sl-tip-name">${esc(details.categoryLabel)}</span>` + (notForSale ? "" : `<span class="sl-tip-amt">${price}</span>`) + `</div>` + wheelchairLine + cxLine + statusLine;
|
|
6834
7277
|
this.tipEl.style.display = "block";
|
|
6835
7278
|
this.placeTooltip();
|
|
6836
7279
|
}
|
|
@@ -7053,13 +7496,16 @@ var SeatPicker = class _SeatPicker {
|
|
|
7053
7496
|
sourceLabel: this.tf("picker.chartDerivedModel", "Chart-derived model")
|
|
7054
7497
|
};
|
|
7055
7498
|
}
|
|
7056
|
-
/** Route
|
|
7057
|
-
|
|
7499
|
+
/** Route decoupled analytics into the host callback, tagged buyer. */
|
|
7500
|
+
emitAnalytics(event, props) {
|
|
7058
7501
|
try {
|
|
7059
7502
|
this.opts.onAnalytics?.(event, { ...props, surface: "buyer" });
|
|
7060
7503
|
} catch {
|
|
7061
7504
|
}
|
|
7062
7505
|
}
|
|
7506
|
+
emit3dAnalytics(event, props) {
|
|
7507
|
+
this.emitAnalytics(event, props);
|
|
7508
|
+
}
|
|
7063
7509
|
/** A 3D seat tap runs the SAME selection path as a 2D tap: toggle through the
|
|
7064
7510
|
* controller, then raise the shared confirm card (bottom-sheeted in 3D). */
|
|
7065
7511
|
onView3dSeatPick(seatId) {
|
|
@@ -7078,9 +7524,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
7078
7524
|
this.syncSelectionTo3d();
|
|
7079
7525
|
return;
|
|
7080
7526
|
}
|
|
7081
|
-
const
|
|
7082
|
-
if (
|
|
7083
|
-
this.showUnavailable3dSeat(seat,
|
|
7527
|
+
const status = this.controller.getStatus(seat.id);
|
|
7528
|
+
if (status === "held" || status === "booked" || status === "not_for_sale") {
|
|
7529
|
+
this.showUnavailable3dSeat(seat, this.seatState3dFor(seat), status);
|
|
7084
7530
|
this.syncSelectionTo3d();
|
|
7085
7531
|
return;
|
|
7086
7532
|
}
|
|
@@ -7102,14 +7548,14 @@ var SeatPicker = class _SeatPicker {
|
|
|
7102
7548
|
else this.syncTray();
|
|
7103
7549
|
this.syncSelectionTo3d();
|
|
7104
7550
|
}
|
|
7105
|
-
/** Explain a
|
|
7106
|
-
*
|
|
7107
|
-
*
|
|
7108
|
-
|
|
7551
|
+
/** Explain a 3D seat that INVENTORY refuses — never one the buyer's own
|
|
7552
|
+
* filters merely dimmed, which stays buyable exactly as it is in 2D. Category
|
|
7553
|
+
* colour remains visible in the venue; this card names the availability state
|
|
7554
|
+
* explicitly so yellow never has to carry both meanings. */
|
|
7555
|
+
showUnavailable3dSeat(seat, visualState, status) {
|
|
7109
7556
|
const overlay = this.view3dEl;
|
|
7110
7557
|
if (!overlay) return;
|
|
7111
7558
|
this.dismissUnavailable3dSeat();
|
|
7112
|
-
const status = this.controller.getStatus(seat.id);
|
|
7113
7559
|
const details = this.controller.seatDetails(seat.id);
|
|
7114
7560
|
const identity = details?.displayLabel ?? seat.displayLabel ?? seat.label;
|
|
7115
7561
|
const section = details?.sectionLabel;
|
|
@@ -7121,12 +7567,9 @@ var SeatPicker = class _SeatPicker {
|
|
|
7121
7567
|
} : status === "booked" ? {
|
|
7122
7568
|
title: this.tf("picker.sold", "Sold"),
|
|
7123
7569
|
message: this.tf("picker.soldSeatExplanation", "This seat has already been booked.")
|
|
7124
|
-
} :
|
|
7570
|
+
} : {
|
|
7125
7571
|
title: this.tf("picker.notForSale", "Not for sale"),
|
|
7126
7572
|
message: this.tf("picker.notForSaleExplanation", "This seat is not included in the current sale.")
|
|
7127
|
-
} : {
|
|
7128
|
-
title: this.tf("picker.filteredSeat", "Unavailable with current filters"),
|
|
7129
|
-
message: this.tf("picker.filteredSeatExplanation", "Change the active price or view filters to make this seat selectable.")
|
|
7130
7573
|
};
|
|
7131
7574
|
const card = document.createElement("div");
|
|
7132
7575
|
card.className = "sl-view3d-unavailable";
|
|
@@ -7504,7 +7947,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
7504
7947
|
const floors = handle.floors();
|
|
7505
7948
|
const floorLabels = new Set(floors.map((floor) => floor.label.trim().toLocaleLowerCase()));
|
|
7506
7949
|
const zones = handle.zones().filter((zone) => zone.seatCount > 0 && !floorLabels.has(zone.label.trim().toLocaleLowerCase()));
|
|
7507
|
-
const allSections = handle.sections().filter((
|
|
7950
|
+
const allSections = handle.sections().filter((section) => section.seatCount > 0 && !floorLabels.has(section.label.trim().toLocaleLowerCase()));
|
|
7508
7951
|
const sections = zones.length > 1 ? [] : allSections;
|
|
7509
7952
|
const wantFloors = floors.length > 1;
|
|
7510
7953
|
const wantZones = zones.length > 1;
|
|
@@ -7531,16 +7974,24 @@ var SeatPicker = class _SeatPicker {
|
|
|
7531
7974
|
});
|
|
7532
7975
|
setFinderOpen(false);
|
|
7533
7976
|
nav.appendChild(finderToggle);
|
|
7977
|
+
let syncAreasForFloor = (_index) => void 0;
|
|
7978
|
+
let syncPressedFloor = (_index) => void 0;
|
|
7534
7979
|
if (wantFloors) {
|
|
7535
7980
|
const row = document.createElement("div");
|
|
7536
7981
|
row.setAttribute("role", "group");
|
|
7537
7982
|
row.setAttribute("aria-label", this.tf("picker.levels", "Levels"));
|
|
7538
7983
|
const pills = [];
|
|
7984
|
+
syncPressedFloor = (index) => {
|
|
7985
|
+
pills.forEach((pill) => {
|
|
7986
|
+
pill.setAttribute("aria-pressed", String(
|
|
7987
|
+
(pill.dataset.floor === "" ? null : Number(pill.dataset.floor)) === index
|
|
7988
|
+
));
|
|
7989
|
+
});
|
|
7990
|
+
};
|
|
7539
7991
|
const select = (index) => {
|
|
7540
7992
|
if (!handle.focusFloor(index)) return;
|
|
7541
|
-
|
|
7542
|
-
|
|
7543
|
-
});
|
|
7993
|
+
syncPressedFloor(index);
|
|
7994
|
+
syncAreasForFloor(index);
|
|
7544
7995
|
};
|
|
7545
7996
|
const add = (label, index) => {
|
|
7546
7997
|
const b = document.createElement("button");
|
|
@@ -7565,39 +8016,55 @@ var SeatPicker = class _SeatPicker {
|
|
|
7565
8016
|
row.setAttribute("aria-label", this.tf("picker.areas", "Areas"));
|
|
7566
8017
|
const entries = wantZones ? zones.map((z) => ({ id: z.id, label: z.label || z.id, go: () => {
|
|
7567
8018
|
handle.focusZone(z.id);
|
|
7568
|
-
} })) : sections.map((s) => ({
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7572
|
-
|
|
7573
|
-
|
|
7574
|
-
|
|
7575
|
-
placeholder.value = "";
|
|
7576
|
-
placeholder.textContent = this.tf("picker.jumpToSection", "Jump to section");
|
|
7577
|
-
select.appendChild(placeholder);
|
|
7578
|
-
for (const entry of entries) {
|
|
7579
|
-
const option = document.createElement("option");
|
|
7580
|
-
option.value = entry.id;
|
|
7581
|
-
option.textContent = entry.label;
|
|
7582
|
-
select.appendChild(option);
|
|
8019
|
+
} })) : sections.map((s) => ({
|
|
8020
|
+
id: s.id,
|
|
8021
|
+
label: s.label || s.id,
|
|
8022
|
+
floorIndex: s.floorIndex,
|
|
8023
|
+
go: () => {
|
|
8024
|
+
handle.focusSection(s.id);
|
|
8025
|
+
if (Number.isFinite(s.floorIndex)) syncPressedFloor(s.floorIndex);
|
|
7583
8026
|
}
|
|
7584
|
-
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
row.
|
|
7588
|
-
|
|
7589
|
-
|
|
7590
|
-
|
|
7591
|
-
|
|
7592
|
-
|
|
7593
|
-
|
|
7594
|
-
|
|
8027
|
+
}));
|
|
8028
|
+
const renderEntries = (floorIndex) => {
|
|
8029
|
+
const visible = wantZones || floorIndex === null ? entries : entries.filter((entry) => entry.floorIndex === void 0 || entry.floorIndex === floorIndex);
|
|
8030
|
+
row.replaceChildren();
|
|
8031
|
+
if (!visible.length) {
|
|
8032
|
+
row.remove();
|
|
8033
|
+
return;
|
|
8034
|
+
}
|
|
8035
|
+
if (!row.isConnected) nav.appendChild(row);
|
|
8036
|
+
if (!wantZones && visible.length > MAX_3D_SECTION_PILLS) {
|
|
8037
|
+
const select = document.createElement("select");
|
|
8038
|
+
select.setAttribute("aria-label", this.tf("picker.jumpToSection", "Jump to section"));
|
|
8039
|
+
const placeholder = document.createElement("option");
|
|
8040
|
+
placeholder.value = "";
|
|
8041
|
+
placeholder.textContent = this.tf("picker.jumpToSection", "Jump to section");
|
|
8042
|
+
select.appendChild(placeholder);
|
|
8043
|
+
for (const entry of visible) {
|
|
8044
|
+
const option = document.createElement("option");
|
|
8045
|
+
option.value = entry.id;
|
|
8046
|
+
option.textContent = entry.label;
|
|
8047
|
+
select.appendChild(option);
|
|
8048
|
+
}
|
|
8049
|
+
select.addEventListener("change", () => {
|
|
8050
|
+
visible.find((entry) => entry.id === select.value)?.go();
|
|
8051
|
+
});
|
|
8052
|
+
row.appendChild(select);
|
|
8053
|
+
return;
|
|
8054
|
+
}
|
|
8055
|
+
for (const entry of visible) {
|
|
8056
|
+
const button = document.createElement("button");
|
|
8057
|
+
button.type = "button";
|
|
8058
|
+
button.textContent = entry.label;
|
|
8059
|
+
button.addEventListener("click", () => {
|
|
8060
|
+
entry.go();
|
|
7595
8061
|
setFinderOpen(false);
|
|
7596
8062
|
});
|
|
7597
|
-
row.appendChild(
|
|
8063
|
+
row.appendChild(button);
|
|
7598
8064
|
}
|
|
7599
|
-
}
|
|
7600
|
-
|
|
8065
|
+
};
|
|
8066
|
+
syncAreasForFloor = renderEntries;
|
|
8067
|
+
renderEntries(null);
|
|
7601
8068
|
}
|
|
7602
8069
|
overlay.appendChild(nav);
|
|
7603
8070
|
}
|
|
@@ -7796,7 +8263,7 @@ var SeatPicker = class _SeatPicker {
|
|
|
7796
8263
|
} catch (err) {
|
|
7797
8264
|
this.opts.onError?.(err);
|
|
7798
8265
|
const reason = err?.reason;
|
|
7799
|
-
const message = reason === "not_enough_together" ? t2("picker.couldNotFindSeatsTogether", { count: qty }) : reason === "sold_out" ? this.tf("picker.ticketTypeSoldOut", "That ticket type is sold out. Try another ticket type.") : reason === "event_closed" ? this.tf("picker.seatSalesClosedForEvent", "Seat sales have closed for this event.") : this.tf("picker.seatsNoLongerAvailableTryAnother", "Those seats are no longer available. Try another quantity or ticket type.");
|
|
8266
|
+
const message = reason === "not_enough_together" ? t2("picker.couldNotFindSeatsTogether", { count: qty }) : reason === "companion_pair_required" ? this.tf("picker.companionPairRequired", "Select the adjacent wheelchair place with each companion ticket.") : reason === "ticket_option_not_applicable" ? this.tf("picker.ticketOptionNotApplicable", "That ticket choice is not available for the selected seat.") : reason === "sold_out" ? this.tf("picker.ticketTypeSoldOut", "That ticket type is sold out. Try another ticket type.") : reason === "event_closed" ? this.tf("picker.seatSalesClosedForEvent", "Seat sales have closed for this event.") : this.tf("picker.seatsNoLongerAvailableTryAnother", "Those seats are no longer available. Try another quantity or ticket type.");
|
|
7800
8267
|
this.toast(message, "error");
|
|
7801
8268
|
return null;
|
|
7802
8269
|
} finally {
|