@seatlayer/core 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.
Files changed (37) hide show
  1. package/dist/{chunk-GD267BFB.js → chunk-UID7KN44.js} +75 -2
  2. package/dist/chunk-UID7KN44.js.map +1 -0
  3. package/dist/core/seatConfidence.d.cts +1 -1
  4. package/dist/core/seatConfidence.d.ts +1 -1
  5. package/dist/{de-V53JPFHN.js → de-YGODYJ3E.js} +3 -3
  6. package/dist/de-YGODYJ3E.js.map +1 -0
  7. package/dist/{es-37ISLCZA.js → es-VMBTGVFQ.js} +3 -3
  8. package/dist/es-VMBTGVFQ.js.map +1 -0
  9. package/dist/{fr-QKK2T64Q.js → fr-SLBE2ULB.js} +3 -3
  10. package/dist/fr-SLBE2ULB.js.map +1 -0
  11. package/dist/index.cjs +647 -60
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +131 -17
  14. package/dist/index.d.ts +131 -17
  15. package/dist/index.js +575 -58
  16. package/dist/index.js.map +1 -1
  17. package/dist/picker/minimapGeometry.cjs +80 -0
  18. package/dist/picker/minimapGeometry.cjs.map +1 -0
  19. package/dist/picker/minimapGeometry.d.cts +37 -0
  20. package/dist/picker/minimapGeometry.d.ts +37 -0
  21. package/dist/picker/minimapGeometry.js +52 -0
  22. package/dist/picker/minimapGeometry.js.map +1 -0
  23. package/dist/{types-DR54nqKL.d.cts → types-tLOunI-B.d.cts} +131 -1
  24. package/dist/{types-DR54nqKL.d.ts → types-tLOunI-B.d.ts} +131 -1
  25. package/dist/view3d/crossfade/panorama.d.cts +1 -1
  26. package/dist/view3d/crossfade/panorama.d.ts +1 -1
  27. package/dist/view3d/index.cjs +1158 -123
  28. package/dist/view3d/index.cjs.map +1 -1
  29. package/dist/view3d/index.d.cts +20 -12
  30. package/dist/view3d/index.d.ts +20 -12
  31. package/dist/view3d/index.js +1158 -122
  32. package/dist/view3d/index.js.map +1 -1
  33. package/package.json +14 -1
  34. package/dist/chunk-GD267BFB.js.map +0 -1
  35. package/dist/de-V53JPFHN.js.map +0 -1
  36. package/dist/es-37ISLCZA.js.map +0 -1
  37. package/dist/fr-QKK2T64Q.js.map +0 -1
package/dist/index.js CHANGED
@@ -22,8 +22,10 @@ import {
22
22
  expandTable,
23
23
  expandTableSlots,
24
24
  floorObjects,
25
+ floorOverviewTranslations,
25
26
  floorsOf,
26
27
  layerOf,
28
+ layoutFloorOverview,
27
29
  objectCenter,
28
30
  owningSectionForObject,
29
31
  pointInPolygon,
@@ -37,8 +39,9 @@ import {
37
39
  sectionGeometry,
38
40
  stackFloors,
39
41
  tableInventoryCount,
40
- tableSeatCountsBySide
41
- } from "./chunk-GD267BFB.js";
42
+ tableSeatCountsBySide,
43
+ translateObject
44
+ } from "./chunk-UID7KN44.js";
42
45
 
43
46
  // src/core/ga.ts
44
47
  var PREFIX = "__sl_ga__";
@@ -1286,6 +1289,8 @@ var en = {
1286
1289
  "picker.poweredBy": "Powered by SeatLayer",
1287
1290
  "picker.testMode": "TEST MODE",
1288
1291
  "picker.orphanHint": "This leaves a single seat stranded \u2014 consider shifting one seat over.",
1292
+ "picker.companionRequiresWheelchair": "Requires the adjacent wheelchair place",
1293
+ "picker.companionPairRequired": "Select the adjacent wheelchair place with each companion ticket.",
1289
1294
  // renderer (drawn on the Konva map — shared by the embed SDK)
1290
1295
  "map.aria": "Seating map. Use arrow keys to move between seats, Enter to select.",
1291
1296
  "map.seatsLeft": "{count} LEFT",
@@ -1397,8 +1402,6 @@ var en = {
1397
1402
  "picker.fewerGuests": "Fewer guests",
1398
1403
  "picker.fewerSeats": "Fewer seats",
1399
1404
  "picker.filterAndFocusByPrice": "Filter and focus seats by price",
1400
- "picker.filteredSeat": "Unavailable with current filters",
1401
- "picker.filteredSeatExplanation": "Change the active price or view filters to make this seat selectable.",
1402
1405
  "picker.filters": "Filters",
1403
1406
  "picker.findBestSeatsCount.one": "Find {count} best seat",
1404
1407
  "picker.findBestSeatsCount.other": "Find {count} best seats",
@@ -1634,10 +1637,11 @@ var WHEELCHAIR_GLYPH_MIN_PX = 10;
1634
1637
  var FILTERED_WHEELCHAIR_GLYPH_MIN_PX = 14;
1635
1638
  var SECTION_PROMINENT_SCALE = 0.45 * SEAT_LEGIBLE_SCALE;
1636
1639
  var BLOCK_MELT_TOP = 0.9 * SEAT_LEGIBLE_SCALE;
1637
- var SEAT_FOCUS_SCALE = Math.max(SEAT_LEGIBLE_SCALE * 1.1, BLOCK_MELT_TOP);
1640
+ var SEAT_DETAIL_SCALE = Math.max(SEAT_LEGIBLE_SCALE * 1.1, BLOCK_MELT_TOP);
1638
1641
  var PAN_START_SLOP_PX = 8;
1639
1642
  var GHOST_CLICK_MS = 700;
1640
1643
  var ROW_LABEL_CLEARANCE = 2;
1644
+ var FOCUSED_ROW_LABEL_MIN_PX = MIN_VISIBLE_BOOKABLE_LABEL_PX;
1641
1645
  var ROW_LABEL_COLLISION_CELL = 64;
1642
1646
  var ZONE_PROMINENT_SCALE = 0.55 * SECTION_PROMINENT_SCALE;
1643
1647
  var MAX_LABELS = 700;
@@ -2033,6 +2037,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
2033
2037
  this.activeFloorId = "";
2034
2038
  /** When true on a multi-floor chart, render ALL floors stacked (3D overview). */
2035
2039
  this.stacked = false;
2040
+ /** Buyer-facing 2D overview: floors are rigidly arranged without altering topology. */
2041
+ this.floorOverview = false;
2036
2042
  /** Interactive node per seat/booth — a Circle for seats, a Rect for booths. */
2037
2043
  this.circleById = /* @__PURE__ */ new Map();
2038
2044
  /** Booth block geometry, keyed by booth id (= the unit's rowId). */
@@ -2288,6 +2294,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
2288
2294
  return;
2289
2295
  }
2290
2296
  if (this.pinch && this.pointers.size >= 2) {
2297
+ this.releaseSectionFocusDim();
2291
2298
  const [a, b] = [...this.pointers.values()];
2292
2299
  const dist = Math.hypot(b.x - a.x, b.y - a.y);
2293
2300
  if (this.pinch.startDist < 1) return;
@@ -2306,6 +2313,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
2306
2313
  if (!this.panStarted) {
2307
2314
  if (this.moved <= PAN_START_SLOP_PX) return;
2308
2315
  this.panStarted = true;
2316
+ this.releaseSectionFocusDim();
2309
2317
  }
2310
2318
  this.stage.position({
2311
2319
  x: this.stage.x() + (p.x - this.panLast.x),
@@ -2426,13 +2434,23 @@ var _SeatmapRenderer = class _SeatmapRenderer {
2426
2434
  }
2427
2435
  // ---- ISeatmapRenderer -----------------------------------------------------
2428
2436
  setChart(doc, opts) {
2429
- if (doc !== this.chartDoc) this.stacked = false;
2437
+ if (doc !== this.chartDoc) {
2438
+ this.stacked = false;
2439
+ this.floorOverview = false;
2440
+ }
2430
2441
  this.cancelAssetLoads();
2431
2442
  this.chartDoc = doc;
2432
2443
  this.activeFloorId = opts?.floorId ?? floorsOf(doc)[0].id;
2433
2444
  this.objectFloor.clear();
2434
2445
  if (doc.floors && doc.floors.length > 1) {
2435
- for (const f of doc.floors) for (const o of f.objects) this.objectFloor.set(o.id, f.id);
2446
+ for (const f of doc.floors) {
2447
+ for (const o of f.objects) {
2448
+ this.objectFloor.set(o.id, f.id);
2449
+ if (o.type === "section" && o.logicalSectionId) {
2450
+ this.objectFloor.set(o.logicalSectionId, f.id);
2451
+ }
2452
+ }
2453
+ }
2436
2454
  }
2437
2455
  const view = this.floorView(doc);
2438
2456
  this.focusedId = null;
@@ -2530,7 +2548,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
2530
2548
  this.seats = expandChart(view, {
2531
2549
  // A stacked overview is presentation-only and never opens a seat POV. The
2532
2550
  // per-section renderer below still resolves each object's real floor base.
2533
- floorBaseHeightM: this.stacked ? 0 : this.floorBaseHeightMFor()
2551
+ floorBaseHeightM: this.stacked || this.floorOverview ? 0 : this.floorBaseHeightMFor()
2534
2552
  });
2535
2553
  for (const s of this.seats) {
2536
2554
  this.seatById.set(s.id, s);
@@ -2551,14 +2569,21 @@ var _SeatmapRenderer = class _SeatmapRenderer {
2551
2569
  this.overlayLayer.add(this.focusRing);
2552
2570
  this.zoomToFit();
2553
2571
  }
2554
- /** The chart to render: all floors stacked (3D overview), the active floor, or
2555
- * the whole chart for single-floor charts. */
2572
+ /** The chart to render: authored-coordinate overview, stacked 3D overview,
2573
+ * the active floor, or the whole chart for single-floor charts. */
2556
2574
  floorView(doc) {
2557
2575
  if (!doc.floors || !doc.floors.length) return {
2558
2576
  ...doc,
2559
2577
  referenceImage: void 0,
2560
2578
  backgroundImage: buyerBackgroundImage(doc)
2561
2579
  };
2580
+ if (this.floorOverview && doc.floors.length >= 2) {
2581
+ return {
2582
+ ...layoutFloorOverview(doc),
2583
+ referenceImage: void 0,
2584
+ backgroundImage: buyerBackgroundImage(doc)
2585
+ };
2586
+ }
2562
2587
  if (this.stacked && doc.floors.length >= 2) {
2563
2588
  return {
2564
2589
  ...stackFloors(doc),
@@ -2591,14 +2616,30 @@ var _SeatmapRenderer = class _SeatmapRenderer {
2591
2616
  const multi = !!this.chartDoc.floors && this.chartDoc.floors.length >= 2;
2592
2617
  if (!multi) return;
2593
2618
  this.stacked = on;
2619
+ if (on) this.floorOverview = false;
2594
2620
  this.setChart(this.chartDoc, { floorId: this.activeFloorId });
2595
2621
  }
2596
2622
  isStacked() {
2597
2623
  return this.stacked;
2598
2624
  }
2625
+ /** Toggle the buyer's combined 2D floor overview. Every floor is translated
2626
+ * as one rigid group for clarity; its authored internal topology is exact. */
2627
+ setFloorOverview(on) {
2628
+ if (!this.chartDoc || on === this.floorOverview) return;
2629
+ const multi = !!this.chartDoc.floors && this.chartDoc.floors.length >= 2;
2630
+ if (!multi) return;
2631
+ this.floorOverview = on;
2632
+ if (on) this.stacked = false;
2633
+ this.setChart(this.chartDoc, { floorId: this.activeFloorId });
2634
+ }
2635
+ isFloorOverview() {
2636
+ return this.floorOverview;
2637
+ }
2599
2638
  /** Switch which floor is shown (2D). Re-renders + re-fits; no-op if unchanged. */
2600
2639
  setActiveFloor(floorId) {
2601
- if (!this.chartDoc || floorId === this.activeFloorId) return;
2640
+ if (!this.chartDoc || floorId === this.activeFloorId && !this.floorOverview && !this.stacked) return;
2641
+ this.floorOverview = false;
2642
+ this.stacked = false;
2602
2643
  this.setChart(this.chartDoc, { floorId });
2603
2644
  }
2604
2645
  /** Floors for the host's switcher (single-floor charts return one synthetic floor). */
@@ -3057,6 +3098,13 @@ var _SeatmapRenderer = class _SeatmapRenderer {
3057
3098
  const h = this.stage.height();
3058
3099
  const b = this.viewMode === "perspective" && this.perspectiveBounds ? this.perspectiveBounds : this.bounds;
3059
3100
  this.fitScale = Math.min(w / b.width, h / b.height) || 1;
3101
+ if (this.opts.portraitFitCrop && !this.exportMode) {
3102
+ const widthBound = w / b.width <= h / b.height;
3103
+ const projectedH = b.height * this.fitScale;
3104
+ if (widthBound && projectedH < 0.55 * h) {
3105
+ this.fitScale = Math.min(this.fitScale * 1.3, 0.62 * h / b.height);
3106
+ }
3107
+ }
3060
3108
  const s = this.fitScale;
3061
3109
  this.stage.scale({ x: s, y: s });
3062
3110
  this.stage.position({
@@ -3066,6 +3114,18 @@ var _SeatmapRenderer = class _SeatmapRenderer {
3066
3114
  this.afterViewChange();
3067
3115
  this.stage.batchDraw();
3068
3116
  }
3117
+ /**
3118
+ * Reframe after a host/layout resize without throwing away section context.
3119
+ * The stage already represents the usable map area (the picker side panel is
3120
+ * a sibling), so measuring it here naturally respects open/closed panels.
3121
+ */
3122
+ refitCurrentView() {
3123
+ if (this.focusedSectionId && this.sectionBounds(this.focusedSectionId)) {
3124
+ this.focusRegion(this.focusedSectionId, { animate: false });
3125
+ return;
3126
+ }
3127
+ this.zoomToFit();
3128
+ }
3069
3129
  zoomIn() {
3070
3130
  const center = { x: this.stage.width() / 2, y: this.stage.height() / 2 };
3071
3131
  this.zoomAbout(this.stage.scaleX() * _SeatmapRenderer.ZOOM_STEP, center);
@@ -4156,8 +4216,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
4156
4216
  }
4157
4217
  /**
4158
4218
  * AXS section-focus: dim + desaturate every other section, draw a calm backdrop
4159
- * panel behind this section's seats, and glide the camera in to frame it (the
4160
- * seat-pick gate below only lets buyers pick once seats are LABEL_SCALE big).
4219
+ * panel behind this section's seats, and glide the camera to a padded fit of
4220
+ * the whole section. A large section may therefore use smaller seat markers,
4221
+ * but it never clips its outer rows merely to meet a fixed detail scale.
4161
4222
  */
4162
4223
  focusSection(id) {
4163
4224
  if (!this.sections.some((section) => section.id === id || section.logicalId === id)) return;
@@ -4166,7 +4227,24 @@ var _SeatmapRenderer = class _SeatmapRenderer {
4166
4227
  this.bgLayer.batchDraw();
4167
4228
  this.overlayLayer.batchDraw();
4168
4229
  this.updateLOD();
4169
- this.focusRegion(id, { minScale: SEAT_FOCUS_SCALE });
4230
+ this.focusRegion(id);
4231
+ }
4232
+ /**
4233
+ * Release only the focus dim + backdrop, keeping the focused-section state
4234
+ * (summary card, adjacent-section navigation, LOD) intact. Called on the
4235
+ * buyer's first manual pan/pinch/wheel after a section glide: the dim reads
4236
+ * as "everything else is disabled", but every seat outside the focus was
4237
+ * always hit-testable — free roaming across sections is the product
4238
+ * behaviour, the glide is only an entrance.
4239
+ */
4240
+ releaseSectionFocusDim() {
4241
+ if (!this.focusBackdrop && !this.focusDimOverlay) return;
4242
+ this.focusBackdrop?.destroy();
4243
+ this.focusBackdrop = null;
4244
+ this.focusDimOverlay?.destroy();
4245
+ this.focusDimOverlay = null;
4246
+ this.bgLayer.batchDraw();
4247
+ this.overlayLayer.batchDraw();
4170
4248
  }
4171
4249
  /** Clear an AXS section focus — restore full-bowl brightness + drop the backdrop. */
4172
4250
  clearSectionFocus() {
@@ -4304,6 +4382,44 @@ var _SeatmapRenderer = class _SeatmapRenderer {
4304
4382
  if (!Number.isFinite(minX)) return { x: 0, y: 0, width: 1, height: 1 };
4305
4383
  return { x: minX, y: minY, width: Math.max(1, maxX - minX), height: Math.max(1, maxY - minY) };
4306
4384
  }
4385
+ /** Live minimap geometry comes from the rendered scene, so a content-aware
4386
+ * all-floor layout cannot collapse back onto the source coordinate stack. */
4387
+ getMinimapSnapshot() {
4388
+ const sections = this.sections.map((section) => {
4389
+ const inactive = this.isSectionClosed(section) || section.free <= 0;
4390
+ const selected = section.memberIds.some((id) => this.selection.has(id));
4391
+ const active2 = this.focusedSectionId === section.id || this.focusedSectionId === section.logicalId;
4392
+ return {
4393
+ id: section.logicalId,
4394
+ label: section.label,
4395
+ outline: section.outline.map((point) => ({ ...point })),
4396
+ ...this.objectFloor.get(section.id) ? { floorId: this.objectFloor.get(section.id) } : {},
4397
+ state: selected ? "selected" : inactive ? "inactive" : "available",
4398
+ active: active2
4399
+ };
4400
+ });
4401
+ return {
4402
+ sections,
4403
+ seats: sections.length ? [] : this.seats.map((seat) => ({
4404
+ x: seat.x,
4405
+ y: seat.y,
4406
+ categoryKey: seat.categoryKey,
4407
+ state: this.selection.has(seat.id) ? "selected" : this.statusById.get(seat.id) === "free" ? "available" : "inactive"
4408
+ }))
4409
+ };
4410
+ }
4411
+ /** Minimap drag/click navigation: preserve zoom and move one world point to
4412
+ * the viewport centre. Works for flat and projected views via worldToScreen. */
4413
+ panToWorld(point) {
4414
+ this.cancelGlide();
4415
+ const screen = this.worldToScreen(point);
4416
+ this.stage.position({
4417
+ x: this.stage.x() + this.stage.width() / 2 - screen.x,
4418
+ y: this.stage.y() + this.stage.height() / 2 - screen.y
4419
+ });
4420
+ this.afterViewChange();
4421
+ this.stage.batchDraw();
4422
+ }
4307
4423
  /** Legend hover: highlight one category (dim the rest), or null to clear. */
4308
4424
  setCategoryHighlight(key) {
4309
4425
  if (this.categoryHighlight === key) return;
@@ -5072,7 +5188,15 @@ var _SeatmapRenderer = class _SeatmapRenderer {
5072
5188
  blockT = clamp((BLOCK_MELT_TOP - scale) / (BLOCK_MELT_TOP - SECTION_PROMINENT_SCALE), 0, 1);
5073
5189
  zoneT = clamp((SECTION_PROMINENT_SCALE - scale) / (SECTION_PROMINENT_SCALE - ZONE_PROMINENT_SCALE), 0, 1);
5074
5190
  }
5075
- const sectionOverview = scale < CACHE_THRESHOLD;
5191
+ if (this.floorOverview) {
5192
+ blockT = 0;
5193
+ zoneT = 0;
5194
+ }
5195
+ if (this.focusedSectionId) {
5196
+ blockT = 0;
5197
+ zoneT = 0;
5198
+ }
5199
+ const sectionOverview = !this.floorOverview && !this.focusedSectionId && scale < CACHE_THRESHOLD;
5076
5200
  if (sectionOverview) blockT = 1;
5077
5201
  if (!this.zones.length) zoneT = 0;
5078
5202
  const deferPerspectiveSeatReveal = this.viewMode === "perspective" && this.glideInProgress && this.seats.length > 2500;
@@ -5271,6 +5395,14 @@ var _SeatmapRenderer = class _SeatmapRenderer {
5271
5395
  zone.back.offset({ x: width / 2, y: height / 2 });
5272
5396
  zone.back.cornerRadius(7 * this.uiScale() / sx);
5273
5397
  zone.back.strokeWidth(1 / sx);
5398
+ const abs = zone.back.getAbsolutePosition();
5399
+ const minScreenY = 62 + height * sx / 2;
5400
+ if (abs.y < minScreenY) {
5401
+ const dy = (minScreenY - abs.y) / sx;
5402
+ zone.label.y(zone.label.y() + dy);
5403
+ if (zone.sub) zone.sub.y(zone.sub.y() + dy);
5404
+ zone.back.y(zone.back.y() + dy);
5405
+ }
5274
5406
  }
5275
5407
  /**
5276
5408
  * Map a container-relative screen point back to world coords. Inverts the
@@ -5495,7 +5627,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
5495
5627
  * must fall through and pick.
5496
5628
  */
5497
5629
  sectionBounds(id) {
5498
- const bounds2 = this.sections.filter((section) => section.id === id || section.logicalId === id).map((section) => {
5630
+ const matching = this.sections.filter((section) => section.id === id || section.logicalId === id);
5631
+ const bounds2 = matching.map((section) => {
5499
5632
  if (this.viewMode === "perspective" && section.perspectiveAffine) {
5500
5633
  return polyBounds(section.outline.map((point) => this.projectedSectionPoint(section, point)));
5501
5634
  }
@@ -5507,11 +5640,77 @@ var _SeatmapRenderer = class _SeatmapRenderer {
5507
5640
  return polyBounds(points);
5508
5641
  });
5509
5642
  if (!bounds2.length) return null;
5510
- const left = Math.min(...bounds2.map((box) => box.x));
5511
- const top = Math.min(...bounds2.map((box) => box.y));
5512
- const right = Math.max(...bounds2.map((box) => box.x + box.width));
5513
- const bottom = Math.max(...bounds2.map((box) => box.y + box.height));
5514
- return { x: left, y: top, width: right - left, height: bottom - top };
5643
+ let left = Math.min(...bounds2.map((box) => box.x));
5644
+ let top = Math.min(...bounds2.map((box) => box.y));
5645
+ let right = Math.max(...bounds2.map((box) => box.x + box.width));
5646
+ let bottom = Math.max(...bounds2.map((box) => box.y + box.height));
5647
+ const memberIds = new Set(matching.flatMap((section) => section.memberIds));
5648
+ for (const seatId of memberIds) {
5649
+ const seat = this.seatById.get(seatId);
5650
+ if (!seat) continue;
5651
+ const point = this.renderedSeatPoint(seat);
5652
+ left = Math.min(left, point.x - this.seatR);
5653
+ top = Math.min(top, point.y - this.seatR);
5654
+ right = Math.max(right, point.x + this.seatR);
5655
+ bottom = Math.max(bottom, point.y + this.seatR);
5656
+ }
5657
+ const includeBox = (box) => {
5658
+ if (![box.x, box.y, box.width, box.height].every(Number.isFinite)) return;
5659
+ left = Math.min(left, box.x);
5660
+ top = Math.min(top, box.y);
5661
+ right = Math.max(right, box.x + box.width);
5662
+ bottom = Math.max(bottom, box.y + box.height);
5663
+ };
5664
+ const belongsToSection = (box) => {
5665
+ const centre = { x: box.x + box.width / 2, y: box.y + box.height / 2 };
5666
+ return matching.some((section) => pointInPolygonWithHoles(centre, section.outline, section.holes));
5667
+ };
5668
+ for (const { node, backdrop } of this.freeTextById.values()) {
5669
+ const nodeBox = node.getClientRect({ relativeTo: this.bgLayer, skipShadow: true });
5670
+ if (!belongsToSection(nodeBox)) continue;
5671
+ includeBox(nodeBox);
5672
+ if (backdrop) includeBox(backdrop.getClientRect({ relativeTo: this.bgLayer, skipShadow: true }));
5673
+ }
5674
+ for (const { node } of this.iconNodeById.values()) {
5675
+ const box = node.getClientRect({ relativeTo: this.bgLayer, skipShadow: true });
5676
+ if (belongsToSection(box)) includeBox(box);
5677
+ }
5678
+ const occupiedText = new WorldBoxIndex();
5679
+ for (const { node } of this.freeTextById.values()) {
5680
+ const box = node.getClientRect({ relativeTo: this.bgLayer, skipShadow: true, skipStroke: true });
5681
+ occupiedText.insert({
5682
+ x: box.x - ROW_LABEL_CLEARANCE,
5683
+ y: box.y - ROW_LABEL_CLEARANCE,
5684
+ width: box.width + ROW_LABEL_CLEARANCE * 2,
5685
+ height: box.height + ROW_LABEL_CLEARANCE * 2
5686
+ });
5687
+ }
5688
+ for (const plan of this.rowLabelPlan) {
5689
+ const relevant = plan.focusKeys?.some((key) => key === id || matching.some((section) => key === section.id || key === section.logicalId || key === section.zone)) ?? false;
5690
+ const label = new Text({
5691
+ text: plan.text,
5692
+ fontSize: plan.fontSize,
5693
+ fontStyle: "700",
5694
+ fontFamily: this.labelFont(),
5695
+ rotation: plan.rotation,
5696
+ listening: false
5697
+ });
5698
+ label.offsetX(label.width() / 2);
5699
+ label.offsetY(label.height() / 2);
5700
+ const resolved = this.resolveAutomaticRowLabelPosition(label, plan, occupiedText);
5701
+ const box = this.rowLabelBox(label, resolved);
5702
+ if (relevant) includeBox(box);
5703
+ occupiedText.insert(box);
5704
+ label.destroy();
5705
+ }
5706
+ const span = Math.max(right - left, bottom - top);
5707
+ const padding = Math.max(this.seatR * 2, Math.min(24, span * 0.025));
5708
+ return {
5709
+ x: left - padding,
5710
+ y: top - padding,
5711
+ width: Math.max(1, right - left + padding * 2),
5712
+ height: Math.max(1, bottom - top + padding * 2)
5713
+ };
5515
5714
  }
5516
5715
  sectionFrameScale(id) {
5517
5716
  const b = this.sectionBounds(id);
@@ -5522,7 +5721,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
5522
5721
  const margin = 1.12;
5523
5722
  if (b.width <= 0 || b.height <= 0) return this.stage.scaleX();
5524
5723
  const frameScale = Math.min(w / (b.width * margin), h / (b.height * margin));
5525
- return clamp(Math.max(frameScale, SEAT_FOCUS_SCALE), min, max);
5724
+ return clamp(frameScale, min, max);
5526
5725
  }
5527
5726
  /**
5528
5727
  * Resolve a seat tap: honour the 3D deck-drill and the AXS seat-pick gate,
@@ -5535,7 +5734,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
5535
5734
  * fall through and PICK. This is the invariant: seats are never unpickable.
5536
5735
  */
5537
5736
  handleSeatTap(id) {
5538
- if (this.stacked && this.opts.onDeckTap) {
5737
+ if ((this.stacked || this.floorOverview) && this.opts.onDeckTap) {
5539
5738
  const floorId = this.objectFloor.get(this.seatById.get(id)?.rowId ?? "");
5540
5739
  if (floorId) {
5541
5740
  this.opts.onDeckTap(floorId);
@@ -5610,6 +5809,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
5610
5809
  });
5611
5810
  this.stage.on("wheel", (e) => {
5612
5811
  e.evt.preventDefault();
5812
+ this.releaseSectionFocusDim();
5613
5813
  const r = this.container.getBoundingClientRect();
5614
5814
  const pointer = { x: e.evt.clientX - r.left, y: e.evt.clientY - r.top };
5615
5815
  const factor = Math.exp(-e.evt.deltaY * 2e-3);
@@ -5626,8 +5826,9 @@ var _SeatmapRenderer = class _SeatmapRenderer {
5626
5826
  if (near) this.handleSeatTap(near);
5627
5827
  return;
5628
5828
  }
5629
- if (this.stacked && this.opts.onDeckTap) {
5630
- const floorId = this.deckFloorAt();
5829
+ if ((this.stacked || this.floorOverview) && this.opts.onDeckTap) {
5830
+ const sectionId = this.floorOverview ? this.sectionAt(pointer) : null;
5831
+ const floorId = (sectionId ? this.objectFloor.get(sectionId) : null) ?? this.deckFloorAt();
5631
5832
  if (floorId) {
5632
5833
  this.opts.onDeckTap(floorId);
5633
5834
  return;
@@ -6025,6 +6226,28 @@ var _SeatmapRenderer = class _SeatmapRenderer {
6025
6226
  ...hiddenReason ? { hiddenReason } : {}
6026
6227
  };
6027
6228
  });
6229
+ const rowLabels = this.labelGroup.find((node) => node.getAttr("rowLabel") === true).filter((node) => node instanceof Text).map((node) => {
6230
+ const screenBounds = node.getClientRect({
6231
+ skipShadow: true,
6232
+ skipStroke: true
6233
+ });
6234
+ const outside = screenBounds.x + screenBounds.width < 0 || screenBounds.x > viewport.width || screenBounds.y + screenBounds.height < 0 || screenBounds.y > viewport.height;
6235
+ const ink = node.fill();
6236
+ return {
6237
+ text: node.text(),
6238
+ visible: node.isVisible() && !outside,
6239
+ renderedFontPx: rounded(node.fontSize() * effectiveScale),
6240
+ ink: typeof ink === "string" ? ink : "",
6241
+ ...!outside ? {
6242
+ screenBox: {
6243
+ x: rounded(screenBounds.x),
6244
+ y: rounded(screenBounds.y),
6245
+ width: rounded(screenBounds.width),
6246
+ height: rounded(screenBounds.height)
6247
+ }
6248
+ } : {}
6249
+ };
6250
+ });
6028
6251
  const palette = overviewPalette(this.canvasBackground);
6029
6252
  const neutralSectionFills = new Set(
6030
6253
  ["normal", "nearly-gone", "sold-out", "closed"].map((state) => overviewStateFill(palette, state).toLowerCase())
@@ -6069,7 +6292,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
6069
6292
  labels,
6070
6293
  gaAreas,
6071
6294
  hierarchyLabels,
6072
- freeTextLabels
6295
+ freeTextLabels,
6296
+ rowLabels
6073
6297
  };
6074
6298
  }
6075
6299
  /** Jump the camera to a rung's zoom band (glided). */
@@ -6081,7 +6305,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
6081
6305
  }
6082
6306
  const target = rung === "sections" ? (SECTION_PROMINENT_SCALE + CACHE_THRESHOLD) / 2 : Math.max(
6083
6307
  this.seatLabelTargetScale() * 1.05,
6084
- SEAT_FOCUS_SCALE,
6308
+ SEAT_DETAIL_SCALE,
6085
6309
  CACHE_THRESHOLD * 1.3
6086
6310
  );
6087
6311
  const w = this.stage.width();
@@ -6368,14 +6592,25 @@ var _SeatmapRenderer = class _SeatmapRenderer {
6368
6592
  for (const { objectId, node, backdrop, categoryKey, kind } of this.freeTextById.values()) {
6369
6593
  const gaDimmed = categoryKey != null && (kind === "ga-label" || kind === "ga-capacity") && this.gaCategoryDimmed(categoryKey);
6370
6594
  const gaOverviewHidden = objectId != null && (kind === "ga-label" || kind === "ga-capacity") && this.gaById.get(objectId)?.sectionId != null && effectiveScale < CACHE_THRESHOLD;
6371
- const visible = !gaDimmed && !gaOverviewHidden && isBookableLabelLegibleAtScale(node.fontSize(), effectiveScale);
6595
+ const focusedAnnotation = kind === "free-text" && this.nodeBelongsToFocusedSection(node);
6596
+ const visible = !gaDimmed && !gaOverviewHidden && (focusedAnnotation || isBookableLabelLegibleAtScale(node.fontSize(), effectiveScale));
6372
6597
  node.visible(visible);
6373
6598
  backdrop?.visible(visible);
6374
6599
  }
6375
6600
  for (const { node, fontSize } of this.iconNodeById.values()) {
6376
- node.visible(isBookableLabelLegibleAtScale(fontSize, effectiveScale));
6601
+ node.visible(this.nodeBelongsToFocusedSection(node) || isBookableLabelLegibleAtScale(fontSize, effectiveScale));
6377
6602
  }
6378
6603
  }
6604
+ /** Geometric association only: importer data remains unchanged. */
6605
+ nodeBelongsToFocusedSection(node) {
6606
+ const focus = this.focusedSectionId;
6607
+ if (!focus) return false;
6608
+ const matching = this.sections.filter((section) => section.id === focus || section.logicalId === focus || section.zone === focus);
6609
+ if (!matching.length) return false;
6610
+ const box = node.getClientRect({ relativeTo: this.bgLayer, skipShadow: true });
6611
+ const centre = { x: box.x + box.width / 2, y: box.y + box.height / 2 };
6612
+ return matching.some((section) => pointInPolygonWithHoles(centre, section.outline, section.holes));
6613
+ }
6379
6614
  /** OV-45(a): resolve authored row-letter labels for the buyer/preview map, in
6380
6615
  * world space, matching the Designer's placement contract (start/end/both/none
6381
6616
  * preset, a free-drag `position` that wins over the preset, rotation, and
@@ -6385,18 +6620,25 @@ var _SeatmapRenderer = class _SeatmapRenderer {
6385
6620
  this.rowLabelPlan = [];
6386
6621
  const background = this.theme.background ?? "#0e1117";
6387
6622
  const seatsByRow = /* @__PURE__ */ new Map();
6623
+ const addSeat = (key, seat) => {
6624
+ const list = seatsByRow.get(key) ?? [];
6625
+ list.push(seat);
6626
+ seatsByRow.set(key, list);
6627
+ };
6388
6628
  for (const seat of this.seats) {
6389
6629
  if (seat.kind === "booth") continue;
6390
6630
  const key = seat.logicalRowId ?? seat.rowId;
6391
- (seatsByRow.get(key) ?? seatsByRow.set(key, []).get(key)).push(seat);
6631
+ addSeat(key, seat);
6632
+ if (key !== seat.rowId) addSeat(seat.rowId, seat);
6392
6633
  }
6393
6634
  const seatOrder = (seat) => seat.logicalSeatIndex ?? Number(seat.id.split(":").pop() ?? 0);
6394
6635
  for (const obj of view.objects) {
6395
6636
  if (obj.type !== "row") continue;
6396
- if (obj.segmentedRow && obj.segmentedRow.componentIndex !== 0) continue;
6397
- const presentation = obj.segmentedRow?.labelPresentation ?? obj.labelPresentation;
6637
+ const repeatOnComponents = obj.segmentedRow?.repeatLabelOnComponents === true;
6638
+ if (obj.segmentedRow && !repeatOnComponents && obj.segmentedRow.componentIndex !== 0) continue;
6639
+ const presentation = repeatOnComponents ? obj.labelPresentation : obj.segmentedRow?.labelPresentation ?? obj.labelPresentation;
6398
6640
  if (presentation?.visible === false || presentation?.positionPreset === "none") continue;
6399
- const key = obj.segmentedRow ? obj.segmentedRow.groupId : obj.id;
6641
+ const key = obj.segmentedRow && !repeatOnComponents ? obj.segmentedRow.groupId : obj.id;
6400
6642
  const seats = (seatsByRow.get(key) ?? []).slice().sort((a, b) => seatOrder(a) - seatOrder(b));
6401
6643
  if (!seats.length) continue;
6402
6644
  const first = seats[0];
@@ -6411,6 +6653,10 @@ var _SeatmapRenderer = class _SeatmapRenderer {
6411
6653
  const rotation = presentation?.rotation ?? 0;
6412
6654
  const text = obj.segmentedRow?.displayLabel ?? obj.displayLabel ?? obj.label;
6413
6655
  const opacity = this.objectFilteredOut(obj) ? 0.15 : 1;
6656
+ const containingSection = this.seatSection.get(first.id);
6657
+ const focusKeys = containingSection ? [...new Set([containingSection.id, containingSection.logicalId, containingSection.zone].filter(
6658
+ (value) => Boolean(value)
6659
+ ))] : void 0;
6414
6660
  const push = (x, y, automaticAway) => this.rowLabelPlan.push({
6415
6661
  text,
6416
6662
  x,
@@ -6419,6 +6665,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
6419
6665
  fontSize,
6420
6666
  ink,
6421
6667
  opacity,
6668
+ ...focusKeys?.length ? { focusKeys } : {},
6422
6669
  ...automaticAway ? { automaticAway } : {}
6423
6670
  });
6424
6671
  if (presentation?.position) {
@@ -6438,6 +6685,18 @@ var _SeatmapRenderer = class _SeatmapRenderer {
6438
6685
  push(last.x + away.x * gap, last.y + away.y * gap, away);
6439
6686
  }
6440
6687
  }
6688
+ const mergeRadius = (this.seatR + 12) * 2.2;
6689
+ const merged = [];
6690
+ for (const entry of this.rowLabelPlan) {
6691
+ const partner = merged.find((candidate) => candidate.text === entry.text && Math.hypot(candidate.x - entry.x, candidate.y - entry.y) <= mergeRadius);
6692
+ if (partner) {
6693
+ partner.x = (partner.x + entry.x) / 2;
6694
+ partner.y = (partner.y + entry.y) / 2;
6695
+ continue;
6696
+ }
6697
+ merged.push(entry);
6698
+ }
6699
+ this.rowLabelPlan = merged;
6441
6700
  }
6442
6701
  /** Axis-aligned world bounds of a centred, potentially rotated row label. */
6443
6702
  rowLabelBox(label, position, padding = ROW_LABEL_CLEARANCE) {
@@ -6514,12 +6773,8 @@ var _SeatmapRenderer = class _SeatmapRenderer {
6514
6773
  this.labelGroup.destroyChildren();
6515
6774
  this.labelLiftGroups.clear();
6516
6775
  this.seatLabelById.clear();
6517
- if (!show) {
6518
- this.overlayLayer.batchDraw();
6519
- return;
6520
- }
6521
6776
  let count = 0;
6522
- for (const seat of this.visibleSeatCandidates()) {
6777
+ if (show) for (const seat of this.visibleSeatCandidates()) {
6523
6778
  if (seat.kind === "booth") continue;
6524
6779
  const screen = this.worldToScreen(seat);
6525
6780
  if (screen.x < -20 || screen.x > this.stage.width() + 20 || screen.y < -20 || screen.y > this.stage.height() + 20) continue;
@@ -6610,13 +6865,16 @@ var _SeatmapRenderer = class _SeatmapRenderer {
6610
6865
  });
6611
6866
  }
6612
6867
  for (const rl of this.rowLabelPlan) {
6868
+ const focused = this.focusedSectionId != null && rl.focusKeys?.includes(this.focusedSectionId);
6869
+ const renderedFontSize = focused ? Math.max(rl.fontSize, FOCUSED_ROW_LABEL_MIN_PX / Math.max(effectiveScale, 1e-4)) : rl.fontSize;
6870
+ if (!focused && !isBookableLabelLegibleAtScale(renderedFontSize, effectiveScale)) continue;
6613
6871
  const screen = this.worldToScreen({ x: rl.x, y: rl.y });
6614
6872
  if (screen.x < -40 || screen.x > this.stage.width() + 40 || screen.y < -40 || screen.y > this.stage.height() + 40) continue;
6615
6873
  const t2 = new Text({
6616
6874
  x: rl.x,
6617
6875
  y: rl.y,
6618
6876
  text: rl.text,
6619
- fontSize: rl.fontSize,
6877
+ fontSize: renderedFontSize,
6620
6878
  fontStyle: "700",
6621
6879
  fontFamily: this.labelFont(),
6622
6880
  fill: rl.ink,
@@ -6645,7 +6903,7 @@ var _SeatmapRenderer = class _SeatmapRenderer {
6645
6903
  const h = this.container.clientHeight || 1;
6646
6904
  if (w === this.stage.width() && h === this.stage.height()) return;
6647
6905
  this.stage.size({ width: w, height: h });
6648
- this.zoomToFit();
6906
+ this.refitCurrentView();
6649
6907
  }
6650
6908
  startFpsLoop() {
6651
6909
  const tick = (now) => {
@@ -6803,6 +7061,99 @@ function pickBestAvailable(seats, available, opts) {
6803
7061
  return { labels: ranked.slice(0, qty).map((r) => r.seat.label) };
6804
7062
  }
6805
7063
 
7064
+ // src/core/ticketRestrictions.ts
7065
+ function ticketOptionNotApplicableIssue(labels) {
7066
+ return {
7067
+ code: "ticket_option_not_applicable",
7068
+ labels,
7069
+ message: "That ticket choice is not available for the selected seat."
7070
+ };
7071
+ }
7072
+ function ticketTierAppliesToSeat(tier, seat) {
7073
+ const applicability = tier.applicability;
7074
+ if (!applicability) return true;
7075
+ if (applicability.seatLabels?.length && !applicability.seatLabels.includes(seat.label)) return false;
7076
+ if (applicability.accessibility?.length && !applicability.accessibility.some((key) => seat.accessibility?.includes(key))) return false;
7077
+ return true;
7078
+ }
7079
+ function applicableTicketTiers(doc, seat) {
7080
+ return (doc.categories.find((category) => category.key === seat.categoryKey)?.tiers ?? []).filter((tier) => ticketTierAppliesToSeat(tier, seat));
7081
+ }
7082
+ function resolveTicketTierForSeat(doc, seat, tierId) {
7083
+ const tiers = doc.categories.find((category) => category.key === seat.categoryKey)?.tiers;
7084
+ if (!tiers?.length) return { kind: "none" };
7085
+ const tier = tierId == null ? tiers.find((candidate) => ticketTierAppliesToSeat(candidate, seat)) : tiers.find((candidate) => candidate.id === tierId && ticketTierAppliesToSeat(candidate, seat));
7086
+ return tier ? { kind: "resolved", tier } : { kind: "invalid" };
7087
+ }
7088
+ function sameLogicalRow(a, b) {
7089
+ return (a.logicalRowId ?? a.rowId) === (b.logicalRowId ?? b.rowId);
7090
+ }
7091
+ function authoredIndex(seat) {
7092
+ if (Number.isInteger(seat.logicalSeatIndex)) return seat.logicalSeatIndex;
7093
+ const raw = seat.id.slice(seat.id.lastIndexOf(":") + 1);
7094
+ const index = Number(raw);
7095
+ return Number.isInteger(index) ? index : void 0;
7096
+ }
7097
+ function observablyAdjacent(a, b) {
7098
+ if (!sameLogicalRow(a, b)) return false;
7099
+ const aIndex = authoredIndex(a);
7100
+ const bIndex = authoredIndex(b);
7101
+ return aIndex != null && bIndex != null && Math.abs(aIndex - bIndex) === 1;
7102
+ }
7103
+ function companionSelectionIssue(doc, selections) {
7104
+ if (!selections.length) return null;
7105
+ const byLabel = new Map(expandChart(doc, { resolveEyeHeights: false }).map((seat) => [seat.label, seat]));
7106
+ const selected = selections.flatMap((selection) => {
7107
+ const seat = byLabel.get(selection.label);
7108
+ return seat ? [{ selection, seat }] : [];
7109
+ });
7110
+ const wheelchair = selected.map(({ seat }) => seat).filter((seat) => seat.accessibility?.includes("wheelchair"));
7111
+ const unpaired = [];
7112
+ for (const { selection, seat } of selected) {
7113
+ const resolution = resolveTicketTierForSeat(doc, seat, selection.tierId);
7114
+ const tier = resolution.kind === "resolved" ? resolution.tier : void 0;
7115
+ const isCompanion = seat.accessibility?.includes("companion") || tier?.restriction === "companion";
7116
+ if (isCompanion && !wheelchair.some((candidate) => candidate.id !== seat.id && observablyAdjacent(seat, candidate))) {
7117
+ unpaired.push(seat.label);
7118
+ }
7119
+ }
7120
+ return unpaired.length ? {
7121
+ code: "companion_pair_required",
7122
+ labels: unpaired,
7123
+ message: "Select the adjacent wheelchair place with each companion ticket."
7124
+ } : null;
7125
+ }
7126
+ function ticketSelectionIssue(doc, selections) {
7127
+ if (!selections.length) return null;
7128
+ const byLabel = new Map(expandChart(doc, { resolveEyeHeights: false }).map((seat) => [seat.label, seat]));
7129
+ const notForSaleCategories = new Set(
7130
+ doc.categories.filter((category) => category.notForSale).map((category) => category.key)
7131
+ );
7132
+ const notForSale = [];
7133
+ const inapplicable = [];
7134
+ for (const selection of selections) {
7135
+ const seat = byLabel.get(selection.label);
7136
+ if (!seat) continue;
7137
+ if (notForSaleCategories.has(seat.categoryKey)) {
7138
+ notForSale.push(seat.label);
7139
+ continue;
7140
+ }
7141
+ const resolution = resolveTicketTierForSeat(doc, seat, selection.tierId);
7142
+ if (resolution.kind === "invalid") inapplicable.push(seat.label);
7143
+ }
7144
+ if (notForSale.length) {
7145
+ return {
7146
+ code: "not_for_sale",
7147
+ labels: notForSale,
7148
+ message: "These seats are not for sale."
7149
+ };
7150
+ }
7151
+ if (inapplicable.length) {
7152
+ return ticketOptionNotApplicableIssue(inapplicable);
7153
+ }
7154
+ return companionSelectionIssue(doc, selections);
7155
+ }
7156
+
6806
7157
  // src/core/tableInventory.ts
6807
7158
  var GroupedTableProjectionError = class extends Error {
6808
7159
  constructor(objectId, message) {
@@ -6857,6 +7208,15 @@ function groupedTableInventory(doc, modelVersion) {
6857
7208
  // src/picker/PickerController.ts
6858
7209
  var DEFAULT_MAX_SELECTION = 10;
6859
7210
  var MAX_BACKOFF_MS = 15e3;
7211
+ var PickerSelectionError = class extends Error {
7212
+ constructor(issue) {
7213
+ super(issue.message);
7214
+ this.status = 422;
7215
+ this.name = "PickerSelectionError";
7216
+ this.reason = issue.code;
7217
+ this.labels = issue.labels;
7218
+ }
7219
+ };
6860
7220
  var MAP_THEME_KEYS = ["background", "rowLabelColor", "textColor", "selectionColor"];
6861
7221
  function sameMapTheme(a, b) {
6862
7222
  if (!a || !b) return !a && !b;
@@ -6986,7 +7346,9 @@ var PickerController = class {
6986
7346
  if (!r) return true;
6987
7347
  const selected = r.getSelection().map((seat) => seat.id);
6988
7348
  const floorId = this.getActiveFloorId();
7349
+ const floorOverview = r.isFloorOverview?.() ?? false;
6989
7350
  r.setChart(this.visibleDoc(), floorId ? { floorId } : void 0);
7351
+ if (floorOverview) r.setFloorOverview?.(true);
6990
7352
  if (this.opts.colorblindSafe) r.setColorblindSafe?.(true);
6991
7353
  if (this.closedSections.size) r.setClosedSections?.([...this.closedSections]);
6992
7354
  this.applySeatsMap(Object.fromEntries(this.liveStatuses), this.liveDefault);
@@ -6998,7 +7360,9 @@ var PickerController = class {
6998
7360
  const next = ids.filter((x) => typeof x === "string");
6999
7361
  if (next.length === this.hidden.size && next.every((id) => this.hidden.has(id))) return false;
7000
7362
  this.hidden = new Set(next);
7363
+ const floorOverview = this.renderer?.isFloorOverview?.() ?? false;
7001
7364
  this.renderer?.setChart(this.visibleDoc());
7365
+ if (floorOverview) this.renderer?.setFloorOverview?.(true);
7002
7366
  return true;
7003
7367
  }
7004
7368
  /** Adopt a new closed-section set; restyle (grey + non-pickable) if it differs.
@@ -7073,16 +7437,22 @@ var PickerController = class {
7073
7437
  /** Fetch chart, build label maps, mount the renderer, seed statuses, go live. */
7074
7438
  async render(host) {
7075
7439
  if (this.renderer) return null;
7440
+ const renderStartedAt = performance.now();
7441
+ const profile = {};
7442
+ const duration = (startedAt) => Math.round((performance.now() - startedAt) * 100) / 100;
7076
7443
  this.closed = false;
7077
7444
  let res;
7445
+ const chartStartedAt = performance.now();
7078
7446
  try {
7079
7447
  res = await this.api.chart(this.key);
7080
7448
  } catch (err) {
7081
7449
  this.emitError(err);
7082
7450
  return null;
7083
7451
  }
7452
+ profile.loadChart = duration(chartStartedAt);
7084
7453
  if (this.closed) return null;
7085
7454
  this._doc = res.doc;
7455
+ const normalizeStartedAt = performance.now();
7086
7456
  try {
7087
7457
  const groups = groupedTableInventory(
7088
7458
  res.doc,
@@ -7147,13 +7517,16 @@ var PickerController = class {
7147
7517
  this.labelToId.set(group.label, representative.id);
7148
7518
  this.labelToSeat.set(group.label, representative);
7149
7519
  }
7520
+ profile.normalizeBuyerModel = duration(normalizeStartedAt);
7150
7521
  const currency = res.event.currency ?? this.opts.currency;
7151
7522
  this.currency = currency ?? "USD";
7523
+ const rendererStartedAt = performance.now();
7152
7524
  const renderer = createRenderer(host, {
7153
7525
  // Group chairs are selected together for paint, but count as one logical
7154
7526
  // inventory line. The controller enforces the guest-weighted cap.
7155
7527
  maxSelection: this.rendererSelectionCap(),
7156
7528
  confirmSelection: this.opts.confirmSelection,
7529
+ portraitFitCrop: true,
7157
7530
  currency,
7158
7531
  onSelect: (seat) => {
7159
7532
  this.handleRendererSelect(seat);
@@ -7174,6 +7547,7 @@ var PickerController = class {
7174
7547
  onDeckTap: (floorId) => this.handleDeckTap(floorId),
7175
7548
  onFps: this.opts.onFps
7176
7549
  });
7550
+ profile.createRenderer = duration(rendererStartedAt);
7177
7551
  if (this.closed) {
7178
7552
  renderer.destroy();
7179
7553
  return null;
@@ -7182,6 +7556,7 @@ var PickerController = class {
7182
7556
  let seats = null;
7183
7557
  let seatsDefault = "free";
7184
7558
  let closedIds = [];
7559
+ const statusesStartedAt = performance.now();
7185
7560
  try {
7186
7561
  const objs = await this.api.objects(this.key);
7187
7562
  this.hidden = new Set(objs.hidden ?? []);
@@ -7190,18 +7565,30 @@ var PickerController = class {
7190
7565
  seatsDefault = objs.default ?? "free";
7191
7566
  } catch {
7192
7567
  }
7568
+ profile.loadStatuses = duration(statusesStartedAt);
7193
7569
  if (this.closed) {
7194
7570
  renderer.destroy();
7195
7571
  this.renderer = null;
7196
7572
  return null;
7197
7573
  }
7574
+ const sceneStartedAt = performance.now();
7198
7575
  renderer.setChart(this.visibleDoc());
7576
+ if ((res.doc.floors?.length ?? 0) > 1) renderer.setFloorOverview?.(true);
7199
7577
  if (this.opts.colorblindSafe) renderer.setColorblindSafe?.(true);
7578
+ profile.buildScene = duration(sceneStartedAt);
7200
7579
  this.closedSections = new Set(closedIds);
7580
+ const initialStateStartedAt = performance.now();
7201
7581
  if (closedIds.length) renderer.setClosedSections?.(closedIds);
7202
7582
  if (seats) this.applySeatsMap(seats, seatsDefault);
7583
+ profile.applyInitialState = duration(initialStateStartedAt);
7584
+ const firstPaintStartedAt = performance.now();
7585
+ renderer.forceDraw();
7586
+ profile.firstPaint = duration(firstPaintStartedAt);
7587
+ const realtimeStartedAt = performance.now();
7203
7588
  this.attachVisibilityListener();
7204
7589
  this.connect();
7590
+ profile.startRealtime = duration(realtimeStartedAt);
7591
+ profile.controllerTotal = duration(renderStartedAt);
7205
7592
  return {
7206
7593
  doc: res.doc,
7207
7594
  salesClosed: !!res.event.salesClosed,
@@ -7212,7 +7599,8 @@ var PickerController = class {
7212
7599
  currency,
7213
7600
  // 'test' ⇒ sandbox event: hosts render a TEST MODE ribbon (Phase 11).
7214
7601
  mode: res.event.mode ?? "live",
7215
- posterUrl: res.event.posterUrl ?? null
7602
+ posterUrl: res.event.posterUrl ?? null,
7603
+ performanceProfile: profile
7216
7604
  };
7217
7605
  }
7218
7606
  // ---- selection ------------------------------------------------------------
@@ -7268,11 +7656,23 @@ var PickerController = class {
7268
7656
  select(ids) {
7269
7657
  const before = new Set(this.getSelection().map((seat) => seat.label));
7270
7658
  const expanded = /* @__PURE__ */ new Set();
7659
+ const rejected = [];
7271
7660
  for (const id of ids) {
7272
7661
  const table = this.groupedTableBySeatId.get(id) ?? this.groupedTablesByLabel.get(id);
7273
- if (table) table.chairs.forEach((chair) => expanded.add(chair.id));
7274
- else expanded.add(id);
7662
+ if (table) {
7663
+ const resolution = this.tableTierResolution(table);
7664
+ if (resolution.kind === "invalid") rejected.push(table.label);
7665
+ else table.chairs.forEach((chair) => expanded.add(chair.id));
7666
+ } else {
7667
+ const seat = this.seatById.get(id);
7668
+ if (seat && this.seatTierResolution(seat).kind === "invalid") rejected.push(seat.label);
7669
+ else expanded.add(id);
7670
+ }
7275
7671
  }
7672
+ if (rejected.length) {
7673
+ this.emitError(new PickerSelectionError(ticketOptionNotApplicableIssue(rejected)));
7674
+ }
7675
+ if (!expanded.size) return [];
7276
7676
  this.renderer?.select?.([...expanded]);
7277
7677
  if (this.selectionGuestCount() > this.maxSelection) {
7278
7678
  this.renderer?.deselect([...expanded]);
@@ -7340,6 +7740,13 @@ var PickerController = class {
7340
7740
  }
7341
7741
  handleRendererSelect(seat) {
7342
7742
  const table = this.groupedTableBySeatId.get(seat.id);
7743
+ const resolution = table ? this.tableTierResolution(table) : this.seatTierResolution(seat);
7744
+ if (resolution.kind === "invalid") {
7745
+ this.renderer?.deselect(table ? table.chairs.map((chair) => chair.id) : [seat.id]);
7746
+ this.emitError(new PickerSelectionError(ticketOptionNotApplicableIssue([table?.label ?? seat.label])));
7747
+ this.emitSelectionChange();
7748
+ return;
7749
+ }
7343
7750
  if (table) {
7344
7751
  if (this.selectionGuestCount() > this.maxSelection) {
7345
7752
  this.renderer?.deselect(table.chairs.map((chair) => chair.id));
@@ -7421,6 +7828,8 @@ var PickerController = class {
7421
7828
  const existingGA = (this.hold_?.items ?? []).filter((item) => item.objectType === "ga");
7422
7829
  const combinedLabels = [.../* @__PURE__ */ new Set([...existingGA.map((item) => item.label), ...labels])];
7423
7830
  if (!combinedLabels.length) return null;
7831
+ const restriction = this.ticketIssue(combinedLabels);
7832
+ if (restriction) throw new PickerSelectionError(restriction);
7424
7833
  if (this.hold_ && this.holdCovers(combinedLabels)) return this.hold_;
7425
7834
  try {
7426
7835
  const selections = combinedLabels.map((label) => {
@@ -7592,6 +8001,7 @@ var PickerController = class {
7592
8001
  const available = /* @__PURE__ */ new Set();
7593
8002
  for (const seat of seats) {
7594
8003
  if (held.has(seat.label)) continue;
8004
+ if (this.seatTierResolution(seat).kind === "invalid") continue;
7595
8005
  const id = this.labelToId.get(seat.label);
7596
8006
  const status = id ? this.getStatus(id) : void 0;
7597
8007
  if ((status ?? "free") === "free") available.add(seat.label);
@@ -7673,6 +8083,8 @@ var PickerController = class {
7673
8083
  if (!this.api.book) throw new Error("picker: transport has no book() \u2014 hold-only mode");
7674
8084
  const labels = labelsArg ?? this.getSelection().map((seat) => seat.label);
7675
8085
  if (!labels.length) return null;
8086
+ const restriction = this.ticketIssue(labels);
8087
+ if (restriction) throw new PickerSelectionError(restriction);
7676
8088
  let holdId;
7677
8089
  try {
7678
8090
  if (this.hold_ && this.holdCovers(labels)) {
@@ -7799,6 +8211,20 @@ var PickerController = class {
7799
8211
  getStatus(id) {
7800
8212
  return this.renderer?.getStatus(id);
7801
8213
  }
8214
+ /** Seat ids whose category the author marked not-for-sale. */
8215
+ notForSaleCategorySeatIds() {
8216
+ const doc = this.doc;
8217
+ if (!doc) return [];
8218
+ const flagged = new Set(
8219
+ doc.categories.filter((category) => category.notForSale).map((category) => category.key)
8220
+ );
8221
+ if (!flagged.size) return [];
8222
+ const ids = [];
8223
+ for (const [id, seat] of this.seatById) {
8224
+ if (flagged.has(seat.categoryKey)) ids.push(id);
8225
+ }
8226
+ return ids;
8227
+ }
7802
8228
  flashSeat(id, color) {
7803
8229
  this.renderer?.flashSeat(id, color);
7804
8230
  }
@@ -7811,6 +8237,11 @@ var PickerController = class {
7811
8237
  zoomToFit() {
7812
8238
  this.renderer?.zoomToFit();
7813
8239
  }
8240
+ /** Preserve section focus while the usable map viewport changes. */
8241
+ refitCurrentView() {
8242
+ if (this.renderer?.refitCurrentView) this.renderer.refitCurrentView();
8243
+ else this.renderer?.zoomToFit();
8244
+ }
7814
8245
  worldToScreen(p) {
7815
8246
  return this.renderer?.worldToScreen(p) ?? { x: 0, y: 0 };
7816
8247
  }
@@ -7840,7 +8271,8 @@ var PickerController = class {
7840
8271
  /** Switch the shown floor (2D), then re-apply live seat statuses onto it. */
7841
8272
  setFloor(id) {
7842
8273
  const r = this.renderer;
7843
- if (!r?.setActiveFloor || id === r.getActiveFloorId?.()) return;
8274
+ if (!r?.setActiveFloor || id === r.getActiveFloorId?.() && !r.isFloorOverview?.()) return;
8275
+ r.setFloorOverview?.(false);
7844
8276
  r.setStacked?.(false);
7845
8277
  r.setActiveFloor(id);
7846
8278
  void this.resnapshot();
@@ -7856,12 +8288,24 @@ var PickerController = class {
7856
8288
  isMultiFloor() {
7857
8289
  return (this._doc?.floors?.length ?? 0) > 1;
7858
8290
  }
8291
+ /** Enter/leave the source-coordinate all-floor buyer overview. */
8292
+ setFloorOverview(on) {
8293
+ const r = this.renderer;
8294
+ if (!r?.setFloorOverview || on === r.isFloorOverview?.()) return;
8295
+ r.setFloorOverview(on);
8296
+ void this.resnapshot();
8297
+ this.emitSelectionChange();
8298
+ }
8299
+ isFloorOverview() {
8300
+ return this.renderer?.isFloorOverview?.() ?? false;
8301
+ }
7859
8302
  /** Tap-a-deck-to-enter: leave the 3D stack, drop to flat 2D on `floorId`, and
7860
8303
  * tell the host so it can sync its 2D/3D toggle + floor-switcher state. */
7861
8304
  handleDeckTap(floorId) {
7862
8305
  const r = this.renderer;
7863
8306
  if (!r) return;
7864
8307
  r.setViewMode?.("flat");
8308
+ r.setFloorOverview?.(false);
7865
8309
  r.setStacked?.(false);
7866
8310
  r.setActiveFloor?.(floorId);
7867
8311
  void this.resnapshot();
@@ -7930,10 +8374,30 @@ var PickerController = class {
7930
8374
  if (!r?.getVisibleWorldRect || !r.getWorldBounds) return null;
7931
8375
  return { visible: r.getVisibleWorldRect(), bounds: r.getWorldBounds() };
7932
8376
  }
8377
+ getMinimapSnapshot() {
8378
+ return this.renderer?.getMinimapSnapshot?.() ?? { sections: [], seats: [] };
8379
+ }
8380
+ panToWorld(point) {
8381
+ this.renderer?.panToWorld?.(point);
8382
+ }
8383
+ /** Leave section-focus dimming without changing the current zoom. Used when
8384
+ * the minimap viewport itself is dragged to a different part of the venue. */
8385
+ clearSectionFocus() {
8386
+ this.renderer?.clearSectionFocus?.();
8387
+ this.opts.onSectionFocus?.(null);
8388
+ }
8389
+ /** Read-only browser-render evidence for parity/performance audit tooling. */
8390
+ getRenderedQualityEvidence() {
8391
+ return this.renderer?.getRenderedQualityEvidence() ?? null;
8392
+ }
7933
8393
  /** Glide in on a section and surface its summary (same path as a section tap). */
7934
8394
  focusSection(id) {
7935
8395
  this.handleSectionTap(id);
7936
8396
  }
8397
+ /** Live seat membership count used by buyer section-navigation controls. */
8398
+ getSectionSeatCount(id) {
8399
+ return this.renderer?.sectionMembers?.(id)?.length ?? 0;
8400
+ }
7937
8401
  /** Zoom back out to the whole chart and clear the section-summary card + focus. */
7938
8402
  overview() {
7939
8403
  this.renderer?.clearSectionFocus?.();
@@ -7986,15 +8450,21 @@ var PickerController = class {
7986
8450
  }
7987
8451
  const categories = [...byCat.entries()].map(([key, count]) => {
7988
8452
  const cat = doc.categories.find((c) => c.key === key);
8453
+ const prices = cat?.tiers?.length ? cat.tiers.map((tier) => tier.price) : [cat?.price ?? 0];
8454
+ const priceMin = Math.min(...prices);
8455
+ const priceMax = Math.max(...prices);
7989
8456
  return {
7990
8457
  key,
7991
8458
  count,
7992
8459
  label: cat?.label ?? key,
7993
8460
  color: cat?.color ?? "#6e7bff",
7994
- price: cat?.price ?? 0
8461
+ price: priceMin,
8462
+ priceMin,
8463
+ priceMax
7995
8464
  };
7996
8465
  }).sort((a, b) => a.price - b.price);
7997
- const prices = categories.map((c) => c.price);
8466
+ const minimums = categories.map((c) => c.priceMin);
8467
+ const maximums = categories.map((c) => c.priceMax);
7998
8468
  const zone = sec.zone ? doc.zones?.find((z) => z.id === sec.zone) : void 0;
7999
8469
  const color = sec.color ?? zone?.color ?? categories[0]?.color ?? "#6e7bff";
8000
8470
  return {
@@ -8004,8 +8474,8 @@ var PickerController = class {
8004
8474
  ...sec.entrance && sec.entrance.trim() ? { entrance: sec.entrance.trim() } : {},
8005
8475
  color,
8006
8476
  seatsLeft,
8007
- priceMin: prices.length ? prices[0] : 0,
8008
- priceMax: prices.length ? prices[prices.length - 1] : 0,
8477
+ priceMin: minimums.length ? Math.min(...minimums) : 0,
8478
+ priceMax: maximums.length ? Math.max(...maximums) : 0,
8009
8479
  categories
8010
8480
  };
8011
8481
  }
@@ -8046,11 +8516,12 @@ var PickerController = class {
8046
8516
  const accessibility = s.accessibility?.length ? { accessibility: s.accessibility } : void 0;
8047
8517
  const wheelchair = s.wheelchairSpaceType ? { wheelchairSpaceType: s.wheelchairSpaceType } : void 0;
8048
8518
  const context = this.seatContext.get(s.id);
8049
- const tiers = this.tiersFor(s.categoryKey);
8050
- if (!tiers) {
8051
- return { id: s.id, label: s.label, ...display, ...context, categoryKey: s.categoryKey, price: this.priceFor(s.categoryKey), ...commercial, ...accessibility, ...wheelchair };
8519
+ const tiers = this.tiersForSeat(s);
8520
+ const resolution = this.seatTierResolution(s);
8521
+ if (resolution.kind !== "resolved" || !tiers) {
8522
+ return { id: s.id, label: s.label, ...display, ...context, categoryKey: s.categoryKey, price: this.priceFor(s.categoryKey), currency: this.currency, ...commercial, ...accessibility, ...wheelchair };
8052
8523
  }
8053
- const chosen = tiers.find((t2) => t2.id === this.seatTiers.get(s.id)) ?? tiers[0];
8524
+ const chosen = resolution.tier;
8054
8525
  return {
8055
8526
  id: s.id,
8056
8527
  label: s.label,
@@ -8058,6 +8529,7 @@ var PickerController = class {
8058
8529
  ...context,
8059
8530
  categoryKey: s.categoryKey,
8060
8531
  price: chosen.price,
8532
+ currency: this.currency,
8061
8533
  tiers,
8062
8534
  tierId: chosen.id,
8063
8535
  ...commercial,
@@ -8067,8 +8539,9 @@ var PickerController = class {
8067
8539
  }
8068
8540
  toTableSeat(table) {
8069
8541
  const representative = table.chairs[0];
8070
- const tiers = this.tiersFor(table.categoryKey);
8071
- const chosen = tiers?.find((tier) => tier.id === this.seatTiers.get(representative?.id ?? "")) ?? tiers?.[0];
8542
+ const tiers = this.tiersFor(table.categoryKey)?.filter((tier) => resolveTicketTierForSeat(this._doc, this.tableTierSeat(table), tier.id).kind === "resolved");
8543
+ const resolution = this.tableTierResolution(table);
8544
+ const chosen = resolution.kind === "resolved" ? resolution.tier : void 0;
8072
8545
  return {
8073
8546
  id: representative?.id ?? table.objectId,
8074
8547
  label: table.label,
@@ -8079,6 +8552,7 @@ var PickerController = class {
8079
8552
  rowLabel: table.displayLabel ?? table.label,
8080
8553
  categoryKey: table.categoryKey,
8081
8554
  price: chosen?.price ?? this.priceFor(table.categoryKey),
8555
+ currency: this.currency,
8082
8556
  ...tiers ? { tiers, tierId: chosen?.id } : {},
8083
8557
  objectType: "table",
8084
8558
  bookingMode: table.mode,
@@ -8108,6 +8582,27 @@ var PickerController = class {
8108
8582
  const t2 = this._doc?.categories.find((c) => c.key === categoryKey)?.tiers;
8109
8583
  return t2 && t2.length ? t2 : void 0;
8110
8584
  }
8585
+ tiersForSeat(seat) {
8586
+ if (!this._doc) return void 0;
8587
+ const tiers = applicableTicketTiers(this._doc, seat);
8588
+ return tiers.length ? tiers : void 0;
8589
+ }
8590
+ seatTierResolution(seat) {
8591
+ if (!this._doc) return { kind: "none" };
8592
+ return resolveTicketTierForSeat(this._doc, seat, this.seatTiers.get(seat.id));
8593
+ }
8594
+ tableTierSeat(table) {
8595
+ return {
8596
+ label: table.label,
8597
+ categoryKey: table.categoryKey,
8598
+ accessibility: table.chairs.flatMap((chair) => chair.accessibility ?? [])
8599
+ };
8600
+ }
8601
+ tableTierResolution(table) {
8602
+ if (!this._doc) return { kind: "none" };
8603
+ const representativeId = table.chairs[0]?.id ?? "";
8604
+ return resolveTicketTierForSeat(this._doc, this.tableTierSeat(table), this.seatTiers.get(representativeId));
8605
+ }
8111
8606
  /**
8112
8607
  * Choose a ticket tier for a selected seat (e.g. Adult → Child). Re-emits the
8113
8608
  * selection so the host's cart + the eventual hold carry the new tier + price.
@@ -8117,7 +8612,7 @@ var PickerController = class {
8117
8612
  setSeatTier(seatId, tierId) {
8118
8613
  const seat = this.seatById.get(seatId);
8119
8614
  if (!seat) return;
8120
- const tiers = this.tiersFor(seat.categoryKey);
8615
+ const tiers = this.tiersForSeat(seat);
8121
8616
  if (!tiers) return;
8122
8617
  if (tierId == null) this.seatTiers.delete(seatId);
8123
8618
  else if (tiers.some((t2) => t2.id === tierId)) this.seatTiers.set(seatId, tierId);
@@ -8138,6 +8633,16 @@ var PickerController = class {
8138
8633
  this.opts.onSelectionChange?.(this.getSelection());
8139
8634
  this.emitOrphanHint();
8140
8635
  }
8636
+ /** Check explicit tier applicability and companion relationships for the
8637
+ * current (or supplied) seats. Ticket display names are never semantic input. */
8638
+ ticketIssue(labels) {
8639
+ if (!this._doc) return null;
8640
+ const seats = labels ? this.seatsForLabels(labels) : this.getSelection();
8641
+ return ticketSelectionIssue(this._doc, seats.map((seat) => ({
8642
+ label: seat.label,
8643
+ ...seat.tierId ? { tierId: seat.tierId } : {}
8644
+ })));
8645
+ }
8141
8646
  /**
8142
8647
  * Orphan-seat advice on manual selection: when the buyer's current picks
8143
8648
  * strand one (or more) single free seats between unavailable same-row
@@ -8148,6 +8653,12 @@ var PickerController = class {
8148
8653
  if (!this.opts.onHint) return;
8149
8654
  const r = this.renderer;
8150
8655
  if (!r) return;
8656
+ const restriction = this.ticketIssue();
8657
+ if (restriction) {
8658
+ this.hintShown = true;
8659
+ this.opts.onHint(restriction.code === "companion_pair_required" ? t("picker.companionPairRequired") : "That ticket choice is not available for the selected seat.");
8660
+ return;
8661
+ }
8151
8662
  const selected = new Set(r.getSelection().map((s) => s.id));
8152
8663
  const stranded = selected.size ? strandedSingles(this.seatById.values(), (id) => r.getStatus(id), selected) : [];
8153
8664
  if (stranded.length > 0) {
@@ -8269,6 +8780,8 @@ var PickerController = class {
8269
8780
  ["free", "held", "booked", "not_for_sale"].forEach((st) => {
8270
8781
  if (st !== defaultSeatStatus && byStatus[st].length) r.setStatus(byStatus[st], st);
8271
8782
  });
8783
+ const notForSaleCategoryIds = this.notForSaleCategorySeatIds();
8784
+ if (notForSaleCategoryIds.length) r.setStatus(notForSaleCategoryIds, "not_for_sale");
8272
8785
  this.clearBookedHoldIfSettled();
8273
8786
  this.opts.onStatusChange?.();
8274
8787
  }
@@ -9621,9 +10134,9 @@ function generateSeatThumb(seat, focalPoint, _neighborSeats) {
9621
10134
 
9622
10135
  // src/i18n/bundles.ts
9623
10136
  var LOADERS = {
9624
- es: () => import("./es-37ISLCZA.js").then((m) => ({ default: m.es })),
9625
- de: () => import("./de-V53JPFHN.js").then((m) => ({ default: m.de })),
9626
- fr: () => import("./fr-QKK2T64Q.js").then((m) => ({ default: m.fr }))
10137
+ es: () => import("./es-VMBTGVFQ.js").then((m) => ({ default: m.es })),
10138
+ de: () => import("./de-YGODYJ3E.js").then((m) => ({ default: m.de })),
10139
+ fr: () => import("./fr-SLBE2ULB.js").then((m) => ({ default: m.fr }))
9627
10140
  };
9628
10141
  var loaded = /* @__PURE__ */ new Set(["en"]);
9629
10142
  async function loadLocale(code) {
@@ -9653,6 +10166,7 @@ export {
9653
10166
  MAX_EVENT_INVENTORY,
9654
10167
  MAX_GA_CAPACITY,
9655
10168
  PickerController,
10169
+ PickerSelectionError,
9656
10170
  RENDERED_QUALITY_REPORT_VERSION,
9657
10171
  SEAT_COMMERCIAL_MARKS,
9658
10172
  SUPPORTED_LOCALES,
@@ -9676,6 +10190,7 @@ export {
9676
10190
  expandTable,
9677
10191
  expandTableSlots,
9678
10192
  floorObjects,
10193
+ floorOverviewTranslations,
9679
10194
  floorsOf,
9680
10195
  formatDate,
9681
10196
  formatMinor,
@@ -9694,6 +10209,7 @@ export {
9694
10209
  isGaUnitLabel,
9695
10210
  isSectionHidden,
9696
10211
  layerOf,
10212
+ layoutFloorOverview,
9697
10213
  loadLocale,
9698
10214
  objectCenter,
9699
10215
  owningSectionForObject,
@@ -9713,6 +10229,7 @@ export {
9713
10229
  tCount,
9714
10230
  tableInventoryCount,
9715
10231
  tableSeatCountsBySide,
10232
+ translateObject,
9716
10233
  validGAInventorySegments
9717
10234
  };
9718
10235
  //# sourceMappingURL=index.js.map