@seatlayer/core 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -15,8 +15,21 @@ interface Category {
15
15
  key: string;
16
16
  label: string;
17
17
  color: string;
18
- /** Demo-only convenience; real pricing comes from ticket tiers server-side. */
18
+ /** Base price used when the category has no explicit tiers. */
19
19
  price?: number;
20
+ /**
21
+ * Ticket tiers (Adult / Child / Senior…). When present, a buyer picks a tier
22
+ * per seat in this category and the tier's price applies; the first tier is the
23
+ * default. Per-category (not per-seat) pricing — see Batch 3.5. Empty/absent =
24
+ * a single price (the `price` above).
25
+ */
26
+ tiers?: CategoryTier[];
27
+ }
28
+ /** One ticket tier within a category: a named price (Adult, Child, Senior…). */
29
+ interface CategoryTier {
30
+ id: string;
31
+ name: string;
32
+ price: number;
20
33
  }
21
34
  /**
22
35
  * Accessibility accommodations a seat can carry. Mirrors the taxonomy real
@@ -103,7 +116,9 @@ interface RowObject {
103
116
  seatLabelStart?: number;
104
117
  /** Seat numbering within the row (default ltr, step 1). */
105
118
  seatNumbering?: {
106
- direction: 'ltr' | 'rtl';
119
+ /** ltr / rtl number from an end; `center` numbers outward from the middle
120
+ * (centre seat lowest — the premium-centre theatre convention). */
121
+ direction: 'ltr' | 'rtl' | 'center';
107
122
  /** 2 = odd/even numbering (1,3,5… — start at 2 for evens). */
108
123
  step?: 1 | 2;
109
124
  };
@@ -170,6 +185,13 @@ interface TableObject {
170
185
  rotation: number;
171
186
  /** Round tables. */
172
187
  radius?: number;
188
+ /**
189
+ * Round tables: the arc (in degrees) the seats occupy, default 360 (full
190
+ * ring). Below 360 leaves an open side — e.g. a service gap for waiters, a
191
+ * head table facing the room, or clearance against a wall. The opening is
192
+ * centred on the `rotation` direction; seats spread across the rest.
193
+ */
194
+ seatArc?: number;
173
195
  /** Rect tables. */
174
196
  width?: number;
175
197
  height?: number;
@@ -246,15 +268,33 @@ interface TextObject {
246
268
  color?: string;
247
269
  }
248
270
  type ChartObject = RowObject | GAAreaObject | ShapeObject | TableObject | BoothObject | TextObject | SectionObject;
271
+ /**
272
+ * One floor / level of a multi-floor venue (Batch 5). Each floor owns its own
273
+ * geometry, stage focal point, and trace image; categories/zones/tiers stay
274
+ * chart-global (one event, one inventory). A single-floor chart has NO `floors`
275
+ * — its `objects[]` is the whole venue — so all existing charts are untouched.
276
+ */
277
+ interface Floor {
278
+ id: string;
279
+ name: string;
280
+ objects: ChartObject[];
281
+ focalPoint: Point;
282
+ backgroundImage?: ChartDoc['backgroundImage'];
283
+ }
249
284
  interface ChartDoc {
250
285
  version: 1;
251
286
  name: string;
252
287
  venueType: 'SIMPLE' | 'MIXED';
253
- /** The stage / point every seat looks at. Anchors seat-view + sightlines. */
288
+ /** The stage / point every seat looks at. Anchors seat-view + sightlines.
289
+ * Multi-floor: mirrors floor 0; each floor also carries its own focalPoint. */
254
290
  focalPoint: Point;
255
291
  categories: Category[];
256
292
  /** Section groupings for far-zoom navigation + pricing (optional; sections reference by id). */
257
293
  zones?: ZoneDef[];
294
+ /** Multi-floor venues (Batch 5): present ⇒ floors[] is the source of truth;
295
+ * absent ⇒ single-floor and `objects` below is the whole chart. `objects`
296
+ * is kept mirroring floor 0 so single-floor readers never branch. */
297
+ floors?: Floor[];
258
298
  objects: ChartObject[];
259
299
  /** Floor-plan photo the organizer traces over (designer-only aid, also rendered dimly in picker if kept). */
260
300
  backgroundImage?: {
@@ -267,6 +307,13 @@ interface ChartDoc {
267
307
  };
268
308
  /** Brand/venue theming (colors); categories carry their own colors separately. */
269
309
  theme?: ChartTheme;
310
+ /** Parametric-template provenance: present ⇒ the chart came from a capacity-
311
+ * adjustable template family, and the designer offers a capacity control that
312
+ * regenerates it at a new target seat count (Batch 4 "curated singles + resize"). */
313
+ template?: {
314
+ family: string;
315
+ targetSeats: number;
316
+ };
270
317
  }
271
318
  interface ExpandedSeat {
272
319
  /** Stable id: `${rowId}:${index}` */
@@ -298,9 +345,22 @@ interface RendererCallbacks {
298
345
  onFps?: (fps: number) => void;
299
346
  /** Fired when a GA area is clicked (quantity picking is UI-side). */
300
347
  onGAClick?: (areaId: string) => void;
348
+ /**
349
+ * Fired when a tap lands on a section outline while seats are NOT the active
350
+ * rung (i.e. zoomed out, section/zone LOD). The host glides in + shows a
351
+ * section-summary card instead of trying to select a 4px seat (Slice 5).
352
+ */
353
+ onSectionTap?: (sectionId: string) => void;
354
+ /**
355
+ * Fired when a seat/deck is tapped in the 3D all-floors stacked overview — the
356
+ * host drops back to the flat 2D map on that floor ("tap a deck to enter").
357
+ */
358
+ onDeckTap?: (floorId: string) => void;
301
359
  /** Fired after any pan/zoom/resize settles — re-anchor screen-space overlays. */
302
360
  onViewChange?: () => void;
303
361
  }
362
+ /** Far-zoom level-of-detail rung: whole zones → section blocks → individual seats. */
363
+ type LodRung = 'zones' | 'sections' | 'seats';
304
364
  interface RendererOptions extends RendererCallbacks {
305
365
  /** Max seats selectable at once (default 10). */
306
366
  maxSelection?: number;
@@ -314,10 +374,16 @@ interface RendererOptions extends RendererCallbacks {
314
374
  * pushing straight into the cart; `deselect([seat.id])` on Cancel un-highlights.
315
375
  */
316
376
  confirmSelection?: boolean;
377
+ /** ISO 4217 currency for on-map prices ("FROM …"); defaults to money.DEFAULT_CURRENCY.
378
+ * Locale for grouping/symbol placement comes from the active i18n locale. */
379
+ currency?: string;
317
380
  }
318
381
  interface ISeatmapRenderer {
319
- /** Replace the chart. Resets selection and statuses, zooms to fit. */
320
- setChart(doc: ChartDoc): void;
382
+ /** Replace the chart. Resets selection and statuses, zooms to fit.
383
+ * `opts.floorId` picks which floor to render on a multi-floor chart (Batch 5). */
384
+ setChart(doc: ChartDoc, opts?: {
385
+ floorId?: string;
386
+ }): void;
321
387
  /** Bulk status update; re-renders affected seats only. */
322
388
  setStatus(seatIds: string[], status: SeatStatus): void;
323
389
  getStatus(seatId: string): SeatStatus;
@@ -357,12 +423,32 @@ interface ISeatmapRenderer {
357
423
  setAccessibilityFilter(types: AccessibilityType[] | null): void;
358
424
  /** Legend hover-highlight: dim free seats of other categories (null clears). */
359
425
  setCategoryHighlight?(key: string | null): void;
426
+ /** Dim the seats of these section/zone ids (organizer manager: held-back inventory). */
427
+ setDimmedSections?(ids: string[] | null): void;
428
+ /**
429
+ * Colorblind-safe mode: category hues switch to an Okabe-Ito palette and
430
+ * booked seats render hollow (a non-color cue), so seat state never relies
431
+ * on hue alone. Off (the default) renders exactly as before.
432
+ */
433
+ setColorblindSafe?(on: boolean): void;
360
434
  /**
361
435
  * Switch the projection. `'flat'` = normal top-down; `'isometric'` = the "3D"
362
436
  * view (affine skew/rotate + elevation lift), hit-testing preserved in screen
363
437
  * space. Purely visual — the chart is authored flat. Animated unless reduced-motion.
364
438
  */
365
439
  setViewMode?(mode: 'flat' | 'isometric'): void;
440
+ /** Current projection (defaults to 'flat' when unimplemented). */
441
+ getViewMode?(): 'flat' | 'isometric';
442
+ /** Multi-floor (Batch 5): switch the shown floor; list floors; read the active id. */
443
+ setActiveFloor?(floorId: string): void;
444
+ getFloors?(): {
445
+ id: string;
446
+ name: string;
447
+ }[];
448
+ getActiveFloorId?(): string;
449
+ /** Render all floors stacked (3D overview) vs the active floor. No-op single-floor. */
450
+ setStacked?(on: boolean): void;
451
+ isStacked?(): boolean;
366
452
  /**
367
453
  * Section id whose outline contains a container-relative screen point (or null).
368
454
  * Feeds the far-zoom "tap a section to zoom in" flow (Slice 5).
@@ -370,6 +456,23 @@ interface ISeatmapRenderer {
370
456
  sectionAt?(clientPoint: Point): string | null;
371
457
  /** Seat ids belonging to a section — for the section-summary card (Slice 5). */
372
458
  sectionMembers?(id: string): string[];
459
+ /**
460
+ * Smoothly glide (pan+zoom) the camera to frame a section (by id) or a world-
461
+ * space bounds rect over ~450ms easeInOutCubic. `prefers-reduced-motion` snaps.
462
+ * A pointer-down (grab/pan) cancels an in-flight glide. Slice 5 "glide in".
463
+ */
464
+ focusRegion?(target: string | {
465
+ x: number;
466
+ y: number;
467
+ width: number;
468
+ height: number;
469
+ }, opts?: {
470
+ animate?: boolean;
471
+ }): void;
472
+ /** Current LOD rung derived from zoom (for the ZONES/SECTIONS/SEATS pill). */
473
+ getRung?(): LodRung;
474
+ /** Jump the camera to a rung's zoom band, centred on the chart (glided). */
475
+ setRung?(rung: LodRung): void;
373
476
  destroy(): void;
374
477
  }
375
478
  /** localStorage key the Designer writes and the Picker reads. */
@@ -417,7 +520,24 @@ declare function pointInPolygon(p: Point, poly: Point[]): boolean;
417
520
  * shape: bbox centre; text: its position.
418
521
  */
419
522
  declare function objectCenter(o: ChartObject): Point;
420
- /** Expand every seat-bearing object (rows, tables, booths). */
523
+ /**
524
+ * Normalized floor list (Batch 5): a multi-floor chart's `floors`, or a synthetic
525
+ * single floor wrapping a single-floor chart's `objects`. Every consumer that needs
526
+ * to reason about floors goes through this so single-floor charts stay untouched.
527
+ */
528
+ declare function floorsOf(doc: ChartDoc): Floor[];
529
+ /** Objects of one floor by id (defaults to the first floor). */
530
+ declare function floorObjects(doc: ChartDoc, floorId?: string): ChartObject[];
531
+ /** Every object across ALL floors — the whole venue (single-floor = `doc.objects`). */
532
+ declare function allObjects(doc: ChartDoc): ChartObject[];
533
+ /**
534
+ * Multi-floor 3D stack (Batch 5): flatten every floor into ONE doc with floor `i`
535
+ * lifted by `i * spread` in −y, so the isometric view shows the floors as stacked
536
+ * decks (ground at the bottom). Returns a single-floor doc (no `floors`). Meant
537
+ * only for the 3D overview render — 2D still shows one floor via `floorObjects`.
538
+ */
539
+ declare function stackFloors(doc: ChartDoc, spread?: number): ChartDoc;
540
+ /** Expand every seat-bearing object across all floors (rows, tables, booths). */
421
541
  declare function expandChart(doc: ChartDoc): ExpandedSeat[];
422
542
  /** Axis-aligned bounds over every object plus the background image, with padding. */
423
543
  declare function chartBounds(doc: ChartDoc): {
@@ -427,6 +547,78 @@ declare function chartBounds(doc: ChartDoc): {
427
547
  height: number;
428
548
  };
429
549
 
550
+ /**
551
+ * Section membership + hide/show resolution (Batch 3.3).
552
+ *
553
+ * A `SectionObject` is a polygon drawn over existing seat objects; an object
554
+ * "belongs" to a section when its visual centre (`objectCenter`) falls inside
555
+ * the section outline, first section in doc order winning. This is the same
556
+ * spatial notion `objectCenter`'s doc-comment describes, resolved once here so
557
+ * BOTH the event manager (Sections tab counts) and the buyer picker (omit
558
+ * hidden seats) agree on which seats live in which section.
559
+ *
560
+ * Hiding is per-EVENT (the EventDO holds the hidden id set), not a chart edit —
561
+ * so a republish of the chart never disturbs it. A hidden id may be a section
562
+ * id, a zone id (hides every section in the zone), or `UNGROUPED_ID` (the
563
+ * catch-all bucket of seat objects that sit in no section).
564
+ */
565
+
566
+ /** Synthetic section id for seat objects that fall in no drawn section. */
567
+ declare const UNGROUPED_ID = "__ungrouped__";
568
+ /**
569
+ * A per section/zone availability window (Batch 3.4). Wire-compatible with the
570
+ * EventDO's own `AvailabilityRule`. Absence of a rule for an id = on sale.
571
+ * 'hidden' — manual: hidden until the organizer reveals it (3.3).
572
+ * 'timed' — hidden until `revealAt` (epoch ms), then auto-reveals.
573
+ * 'threshold' — auto-reveals once the on-sale inventory is `thresholdPct`% sold.
574
+ * `labels` are the seat labels the id governs, so a threshold's denominator can
575
+ * exclude still-hidden seats.
576
+ */
577
+ interface AvailabilityRule {
578
+ mode: 'hidden' | 'timed' | 'threshold';
579
+ revealAt?: number;
580
+ thresholdPct?: number;
581
+ labels?: string[];
582
+ }
583
+ interface SectionNode {
584
+ /** Section id (a `SectionObject.id`, or `UNGROUPED_ID`). */
585
+ id: string;
586
+ label: string;
587
+ /** Zone id this section points at (`SectionObject.zone`), if any. */
588
+ zone?: string;
589
+ /** Total seats across the member objects. */
590
+ seatCount: number;
591
+ /** Ids of the seat-bearing objects that belong to this section. */
592
+ objectIds: string[];
593
+ /** Seat labels across the member objects (for availability-rule denominators). */
594
+ seatLabels: string[];
595
+ }
596
+ /**
597
+ * Resolve section membership for a chart. Returns one node per drawn section
598
+ * (doc order), a synthetic "Ungrouped" node for loose seat objects (null when
599
+ * every seat sits in a section), and the object→section id map.
600
+ */
601
+ declare function computeSections(doc: ChartDoc): {
602
+ sections: SectionNode[];
603
+ ungrouped: SectionNode | null;
604
+ /** seat-object id → owning section id (or `UNGROUPED_ID`). */
605
+ objectToSection: Map<string, string>;
606
+ };
607
+ /** Is a drawn section hidden under `hidden` — directly, or via its zone? */
608
+ declare function isSectionHidden(s: SectionObject, hidden: ReadonlySet<string>): boolean;
609
+ /**
610
+ * The set of seat-object ids to omit from the buyer view for a given hidden id
611
+ * set. An object is hidden when its owning section is hidden (directly or via
612
+ * zone), or it is ungrouped and the catch-all bucket is hidden.
613
+ */
614
+ declare function hiddenObjectIds(doc: ChartDoc, hidden: ReadonlySet<string>): Set<string>;
615
+ /**
616
+ * A copy of the doc with hidden sections' member objects removed, plus the
617
+ * hidden section overlays themselves (so no empty block renders). Returns the
618
+ * SAME reference when nothing is hidden — callers can skip a re-render on `===`.
619
+ */
620
+ declare function applyHidden(doc: ChartDoc, hidden: ReadonlySet<string>): ChartDoc;
621
+
430
622
  /**
431
623
  * SeatmapRenderer — the shared canvas rendering core (buyer picker shell).
432
624
  *
@@ -447,6 +639,11 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
447
639
  private labelGroup;
448
640
  private seats;
449
641
  private seatById;
642
+ /** Multi-floor (Batch 5): the last-set chart + which floor we're rendering. */
643
+ private chartDoc;
644
+ private activeFloorId;
645
+ /** When true on a multi-floor chart, render ALL floors stacked (3D overview). */
646
+ private stacked;
450
647
  /** Interactive node per seat/booth — a Circle for seats, a Rect for booths. */
451
648
  private circleById;
452
649
  /** Booth block geometry, keyed by booth id (= the unit's rowId). */
@@ -454,6 +651,10 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
454
651
  private statusById;
455
652
  private catColor;
456
653
  private theme;
654
+ /** Colorblind-safe mode (Okabe-Ito hues + hollow booked seats). */
655
+ private colorblind;
656
+ /** Category order from the doc — the stable index into the CB palette. */
657
+ private catOrder;
457
658
  private selection;
458
659
  private selectionRings;
459
660
  private hoverRing;
@@ -471,6 +672,12 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
471
672
  private zones;
472
673
  private seatSection;
473
674
  private catPrice;
675
+ /** ISO 4217 currency for on-map "FROM …" prices (undefined ⇒ money default). */
676
+ private currency;
677
+ /** Section/zone ids to render dimmed (organizer manager: held-back inventory). */
678
+ private dimmedSections;
679
+ /** Object id → floor id (multi-floor only) — resolves a deck tap in the 3D stack. */
680
+ private objectFloor;
474
681
  /** Zone id → colour (drives extruded side faces in iso view). */
475
682
  private zoneColor;
476
683
  private hasSections;
@@ -482,6 +689,8 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
482
689
  private isoRaf;
483
690
  /** Chart centre the iso projection pivots about (bounds centre). */
484
691
  private isoCentre;
692
+ /** rAF for an in-flight camera glide (focusRegion / setRung); 0 = none. */
693
+ private glideRaf;
485
694
  /** Set in destroy() so an in-flight iso tween bails. */
486
695
  private destroyed;
487
696
  /** Cached scale the section/zone labels were last sized for (scale-compensation). */
@@ -507,7 +716,24 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
507
716
  /** Cumulative gesture movement in px — clicks are suppressed after a real pan/pinch. */
508
717
  private moved;
509
718
  constructor(container: HTMLDivElement, options?: RendererOptions);
510
- setChart(doc: ChartDoc): void;
719
+ setChart(doc: ChartDoc, opts?: {
720
+ floorId?: string;
721
+ }): void;
722
+ /** The chart to render: all floors stacked (3D overview), the active floor, or
723
+ * the whole chart for single-floor charts. */
724
+ private floorView;
725
+ /** Toggle the 3D all-floors stacked overview (Batch 5). Re-renders; no-op on
726
+ * single-floor charts. The caller re-applies statuses + animates the iso view. */
727
+ setStacked(on: boolean): void;
728
+ isStacked(): boolean;
729
+ /** Switch which floor is shown (2D). Re-renders + re-fits; no-op if unchanged. */
730
+ setActiveFloor(floorId: string): void;
731
+ /** Floors for the host's switcher (single-floor charts return one synthetic floor). */
732
+ getFloors(): {
733
+ id: string;
734
+ name: string;
735
+ }[];
736
+ getActiveFloorId(): string;
511
737
  setStatus(seatIds: string[], status: SeatStatus): void;
512
738
  getStatus(seatId: string): SeatStatus;
513
739
  getSelection(): ExpandedSeat[];
@@ -541,6 +767,8 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
541
767
  * inverse) keeps landing on the projected seats/sections.
542
768
  */
543
769
  setViewMode(mode: 'flat' | 'isometric'): void;
770
+ /** Current projection — reflects the tween target, not the mid-tween isoT. */
771
+ getViewMode(): 'flat' | 'isometric';
544
772
  /** Iso angle (rad) + y-squash for the current isoT. */
545
773
  private isoParams;
546
774
  /** Effective vertical scale = stage scale × iso squash — legibility math uses this. */
@@ -579,8 +807,21 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
579
807
  private renderSeats;
580
808
  /** A booth renders as a click-selectable rounded block (dims from the doc). */
581
809
  private renderBoothUnit;
810
+ /** The category's display color — Okabe-Ito hue when colorblind-safe is on. */
811
+ private seatBaseColor;
582
812
  /** Apply fill/stroke/opacity for a seat's current status + selection. */
583
813
  private paintSeat;
814
+ /**
815
+ * Colorblind-safe mode: swap category hues for the Okabe-Ito palette and
816
+ * render booked seats hollow. Off restores the exact default rendering.
817
+ */
818
+ setColorblindSafe(on: boolean): void;
819
+ /**
820
+ * Dim the seats of these section/zone ids (organizer manager use) so held-back
821
+ * inventory is visually distinct on the canvas without hiding it. `null`/empty
822
+ * clears. Unlike the buyer's applyHidden, the sections stay rendered.
823
+ */
824
+ setDimmedSections(ids: string[] | null): void;
584
825
  /** Legend hover: highlight one category (dim the rest), or null to clear. */
585
826
  setCategoryHighlight(key: string | null): void;
586
827
  private renderBackground;
@@ -639,6 +880,14 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
639
880
  private toggleSeat;
640
881
  private setSelected;
641
882
  private wireInteraction;
883
+ /**
884
+ * Which deck (floor) a screen tap landed on in the 3D stacked overview. The
885
+ * seat layer is melt-cached at that zoom so individual seat nodes aren't
886
+ * hit-testable; instead we invert the iso+stage transform via the layer's
887
+ * relative pointer and take the nearest seat's floor. Only the floor matters,
888
+ * so within-floor elevation offsets don't affect the result. Null if none.
889
+ */
890
+ private deckFloorAt;
642
891
  private toLocal;
643
892
  private onPointerDown;
644
893
  private onPointerMove;
@@ -652,6 +901,27 @@ declare class SeatmapRenderer implements ISeatmapRenderer {
652
901
  private zoomAbout;
653
902
  /** Zoom + pan so world-rect `b` fills the viewport (with a small margin). */
654
903
  private zoomToBounds;
904
+ /**
905
+ * Smoothly glide the camera to frame a section (by id) or a world-space bounds
906
+ * rect — the Slice 5 "glide in". Pan+zoom tween over ~450ms easeInOutCubic; the
907
+ * melt/LOD rides the camera every frame. `prefers-reduced-motion` (or
908
+ * `opts.animate === false`) snaps via zoomToBounds. A grab (pointer-down) or a
909
+ * newer glide cancels an in-flight one.
910
+ */
911
+ focusRegion(target: string | {
912
+ x: number;
913
+ y: number;
914
+ width: number;
915
+ height: number;
916
+ }, opts?: {
917
+ animate?: boolean;
918
+ }): void;
919
+ /** Cancel an in-flight camera glide (a grab or a newer glide interrupts it). */
920
+ private cancelGlide;
921
+ /** Current LOD rung derived from effective zoom — drives the ZONES/SECTIONS/SEATS pill. */
922
+ getRung(): LodRung;
923
+ /** Jump the camera to a rung's zoom band, centred on the chart (glided). */
924
+ setRung(rung: LodRung): void;
655
925
  /** Recompute LOD (cache/labels) after any pan/zoom settles. */
656
926
  private afterViewChange;
657
927
  /** rAF-coalesced `onViewChange` — at most one host callback per animation frame. */
@@ -668,7 +938,12 @@ interface PickerSeat {
668
938
  id: string;
669
939
  label: string;
670
940
  categoryKey: string;
941
+ /** Price for the chosen tier when the category has tiers, else the base price. */
671
942
  price: number;
943
+ /** Ticket tiers the seat's category offers (Adult/Child/…); absent when none. */
944
+ tiers?: CategoryTier[];
945
+ /** The chosen tier's id — defaults to the first tier; absent when no tiers. */
946
+ tierId?: string;
672
947
  }
673
948
  interface HoldConflict {
674
949
  label: string;
@@ -679,6 +954,35 @@ interface HoldInfo {
679
954
  holdId: string;
680
955
  labels: string[];
681
956
  expiresAt: number;
957
+ /** The held seats with their chosen ticket tier — what the host books against. */
958
+ seats: PickerSeat[];
959
+ }
960
+ /** One category's slice of a section (dot + price + count) for the summary card. */
961
+ interface SectionCategory {
962
+ key: string;
963
+ label: string;
964
+ color: string;
965
+ price: number;
966
+ count: number;
967
+ }
968
+ /**
969
+ * Big-venue section-summary — everything the tapped-section card renders: name,
970
+ * its zone, live seats-left, price range, and the per-category breakdown.
971
+ * Computed from the renderer's spatial section membership (Slice 5).
972
+ */
973
+ interface SectionSummary {
974
+ id: string;
975
+ label: string;
976
+ /** Zone label (from ChartDoc.zones); '' when the section has no zone. */
977
+ zoneLabel: string;
978
+ /** Mix/section colour for the card's dot (section.color → zone colour → dominant category). */
979
+ color: string;
980
+ /** Free member seats right now. */
981
+ seatsLeft: number;
982
+ priceMin: number;
983
+ priceMax: number;
984
+ /** Per-category breakdown, cheapest first. */
985
+ categories: SectionCategory[];
682
986
  }
683
987
  interface HoldResponse {
684
988
  holdId: string;
@@ -697,10 +1001,12 @@ interface PickerTransport {
697
1001
  salesClosed?: boolean;
698
1002
  venue?: string | null;
699
1003
  startsAt?: number | null;
1004
+ currency?: string;
700
1005
  };
701
1006
  }>;
702
1007
  objects(key: string): Promise<{
703
1008
  seats: Record<string, string>;
1009
+ hidden?: string[];
704
1010
  }>;
705
1011
  hold(key: string, labels: string[]): Promise<HoldResponse>;
706
1012
  bestAvailable(key: string, qty: number, categoryKey?: string): Promise<BestAvailableResponse>;
@@ -722,6 +1028,20 @@ interface PickerCallbacks extends RendererCallbacks {
722
1028
  onStatusChange?: () => void;
723
1029
  /** The server declared the event closed (a 409 event_closed). */
724
1030
  onSalesClosed?: () => void;
1031
+ /**
1032
+ * A section block was tapped at the far/zone rung — the controller has glided
1033
+ * the camera in and passes the computed summary (or null when cleared, e.g.
1034
+ * overview() / zoom-to-zones) so the host can show/hide the summary card.
1035
+ */
1036
+ onSectionFocus?: (summary: SectionSummary | null) => void;
1037
+ /** A deck was tapped in the 3D all-floors overview — host enters that floor in 2D. */
1038
+ onDeckTap?: (floorId: string) => void;
1039
+ /**
1040
+ * Non-blocking selection advice (localized): currently the orphan-seat hint —
1041
+ * the selection would strand a single free seat between unavailable
1042
+ * neighbors. `null` clears a previously shown hint. Never prevents anything.
1043
+ */
1044
+ onHint?: (message: string | null) => void;
725
1045
  onError?: (err: unknown) => void;
726
1046
  }
727
1047
  interface PickerOptions extends PickerCallbacks {
@@ -730,8 +1050,12 @@ interface PickerOptions extends PickerCallbacks {
730
1050
  maxSelection?: number;
731
1051
  /** Renderer confirm-card mode (host shows a confirm popover instead of instant cart add). */
732
1052
  confirmSelection?: boolean;
1053
+ /** ISO 4217 currency for on-map prices (default from money.DEFAULT_CURRENCY). */
1054
+ currency?: string;
733
1055
  /** Flash a pulse when a seat we didn't touch goes free→taken (live-activity cue). */
734
1056
  flashOnLiveChange?: boolean;
1057
+ /** Start in colorblind-safe rendering (Okabe-Ito hues + hollow booked seats). */
1058
+ colorblindSafe?: boolean;
735
1059
  }
736
1060
  declare class PickerController {
737
1061
  private readonly opts;
@@ -740,9 +1064,15 @@ declare class PickerController {
740
1064
  private readonly maxSelection;
741
1065
  private renderer;
742
1066
  private _doc;
1067
+ /** Section/zone ids hidden from buyers this event (3.3) — seats vanish, not grey. */
1068
+ private hidden;
743
1069
  /** label ⇄ id maps — backend speaks labels, the engine speaks ids. */
744
1070
  private labelToId;
745
1071
  private labelToSeat;
1072
+ /** seatId → chosen ticket-tier id (absent ⇒ the category's first/default tier). */
1073
+ private seatTiers;
1074
+ /** id → seat, for the section-summary breakdown (renderer members are ids). */
1075
+ private seatById;
746
1076
  private allIds;
747
1077
  private ws;
748
1078
  private reconnectTimer;
@@ -752,6 +1082,10 @@ declare class PickerController {
752
1082
  private expiryTimer;
753
1083
  constructor(options: PickerOptions);
754
1084
  get doc(): ChartDoc | null;
1085
+ /** The chart with hidden sections' seats removed — what buyers actually see. */
1086
+ private visibleDoc;
1087
+ /** Adopt a new hidden set; rebuild the visible chart only if it differs. */
1088
+ private syncHidden;
755
1089
  currentHold(): HoldInfo | null;
756
1090
  getRenderer(): ISeatmapRenderer | null;
757
1091
  seatByLabel(label: string): ExpandedSeat | undefined;
@@ -763,6 +1097,7 @@ declare class PickerController {
763
1097
  eventName: string;
764
1098
  venue?: string | null;
765
1099
  startsAt?: number | null;
1100
+ currency?: string;
766
1101
  } | null>;
767
1102
  getSelection(): PickerSeat[];
768
1103
  clearSelection(): void;
@@ -806,10 +1141,63 @@ declare class PickerController {
806
1141
  y: number;
807
1142
  };
808
1143
  setAccessibilityFilter(types: string[] | null): void;
1144
+ /** Floors for the buyer's switcher (>1 ⇒ show it). Single-floor ⇒ one entry. */
1145
+ getFloors(): {
1146
+ id: string;
1147
+ name: string;
1148
+ }[];
1149
+ getActiveFloorId(): string;
1150
+ /** Switch the shown floor (2D), then re-apply live seat statuses onto it. */
1151
+ setFloor(id: string): void;
1152
+ /** 3D all-floors stacked overview ⇄ active floor. Re-applies statuses after. */
1153
+ setStacked(on: boolean): void;
1154
+ isMultiFloor(): boolean;
1155
+ /** Tap-a-deck-to-enter: leave the 3D stack, drop to flat 2D on `floorId`, and
1156
+ * tell the host so it can sync its 2D/3D toggle + floor-switcher state. */
1157
+ private handleDeckTap;
1158
+ /** Switch the map projection (2D flat ⇄ 3D isometric). No-op on a flat renderer. */
1159
+ setViewMode(mode: 'flat' | 'isometric'): void;
1160
+ getViewMode(): 'flat' | 'isometric';
1161
+ /** Current LOD rung (for the ZONES/SECTIONS/SEATS pill). */
1162
+ getRung(): LodRung;
1163
+ /** Jump to a rung; ZONES clears any focused summary (back to overview). */
1164
+ setRung(rung: LodRung): void;
1165
+ /** Glide in on a section and surface its summary (same path as a section tap). */
1166
+ focusSection(id: string): void;
1167
+ /** Zoom back out to the whole chart and clear the section-summary card. */
1168
+ overview(): void;
1169
+ /** Glide the camera into a tapped section and emit its computed summary. */
1170
+ private handleSectionTap;
1171
+ /**
1172
+ * Build a section summary from the renderer's spatial membership: section +
1173
+ * zone labels, live seats-left, price range, and the per-category breakdown.
1174
+ */
1175
+ private sectionSummary;
809
1176
  destroy(): void;
810
1177
  private toSeat;
811
1178
  private priceFor;
1179
+ private tiersFor;
1180
+ /**
1181
+ * Choose a ticket tier for a selected seat (e.g. Adult → Child). Re-emits the
1182
+ * selection so the host's cart + the eventual hold carry the new tier + price.
1183
+ * `tierId = null` reverts to the category's first (default) tier. No-op if the
1184
+ * seat's category has no tiers or the id isn't one of them.
1185
+ */
1186
+ setSeatTier(seatId: string, tierId: string | null): void;
1187
+ /** PickerSeats (with chosen tier) for a set of held labels. */
1188
+ private seatsForLabels;
812
1189
  private emitSelectionChange;
1190
+ /** Whether the last emitted hint was non-null (avoids clearing repeatedly). */
1191
+ private hintShown;
1192
+ /**
1193
+ * Orphan-seat advice on manual selection: when the buyer's current picks
1194
+ * strand one (or more) single free seats between unavailable same-row
1195
+ * neighbors, surface a localized, non-blocking hint. Cleared (null) as soon
1196
+ * as the selection stops stranding anyone.
1197
+ */
1198
+ private emitOrphanHint;
1199
+ /** Toggle colorblind-safe rendering at runtime (see PickerOptions.colorblindSafe). */
1200
+ setColorblindSafe(on: boolean): void;
813
1201
  private emitError;
814
1202
  private holdCovers;
815
1203
  private handle409Conflicts;
@@ -823,4 +1211,76 @@ declare class PickerController {
823
1211
  private scheduleReconnect;
824
1212
  }
825
1213
 
826
- export { ACCESSIBILITY_TYPES, type AccessibilityMeta, type AccessibilityType, type BoothObject, CHART_STORAGE_KEY, type Category, type ChartDoc, type ChartObject, type ChartTheme, type ExpandedSeat, type GAAreaObject, type HoldConflict, type HoldInfo, type ISeatmapRenderer, type PickerCallbacks, PickerController, type PickerOptions, type PickerSeat, type PickerTransport, type Point, type RendererCallbacks, type RendererOptions, type RowObject, type RowSeatSlot, type SeatOverride, type SeatStatus, SeatmapRenderer, type SectionObject, type SelectionLayer, type ShapeObject, type TableObject, type TextObject, type ZoneDef, accessibilityMeta, chartBounds, createRenderer, expandBooth, expandChart, expandRow, expandRowSlots, expandTable, layerOf, objectCenter, pointInPolygon };
1214
+ /**
1215
+ * i18n core — deliberately tiny and framework-free so the embed SDK can share
1216
+ * it without dragging in a runtime library (the 60KB-gzipped SDK budget is a
1217
+ * product contract).
1218
+ *
1219
+ * Scope (docs/design-port-plan.md): the buyer surface (picker, public event
1220
+ * page, SDK) ships fully translated in en/es/de/fr; dashboard pages route
1221
+ * their strings through t() as they are rebuilt but ship English this round.
1222
+ *
1223
+ * Keys are flat dot-namespaced strings ("picker.holdSeats"). Interpolation
1224
+ * uses {name} placeholders. Missing keys fall back to English, then to the
1225
+ * key itself — a page never crashes over a translation.
1226
+ */
1227
+ type Locale = 'en' | 'es' | 'de' | 'fr';
1228
+ declare const SUPPORTED_LOCALES: Locale[];
1229
+ type Dict = Record<string, string>;
1230
+ /** explicit setting → stored preference → browser language → en */
1231
+ declare function resolveLocale(explicit?: string | null, stored?: string | null): Locale;
1232
+ declare function getLocale(): Locale;
1233
+ /**
1234
+ * Set the active locale. Locale bundles other than English are registered by
1235
+ * the surface that needs them (the picker imports its own es/de/fr bundles;
1236
+ * the dashboard stays English until its translations ship).
1237
+ */
1238
+ declare function setLocale(locale: Locale, bundle?: Dict): void;
1239
+ declare function setStringOverrides(next: Dict): void;
1240
+ declare function t(key: string, vars?: Record<string, string | number>): string;
1241
+ /** "1 seat" / "3 seats" without hand-rolled concatenation. */
1242
+ declare function tCount(key: string, count: number, vars?: Record<string, string | number>): string;
1243
+ declare function formatDate(value: number | Date, opts?: Intl.DateTimeFormatOptions): string;
1244
+
1245
+ /**
1246
+ * Locale bundle loader — keeps non-English translations OUT of the initial
1247
+ * bundle (the 60 KB SDK budget is a product contract) and code-splits each
1248
+ * locale so a page/SDK only downloads the language it actually uses.
1249
+ *
1250
+ * `loadLocale('de')` dynamic-imports the German dictionary, registers it via
1251
+ * setLocale(), and resolves once it's active. English is built in, so
1252
+ * `loadLocale('en')` is synchronous and never fetches. Unknown/unsupported
1253
+ * codes fall back to English without throwing.
1254
+ */
1255
+
1256
+ /**
1257
+ * Resolve `code` to a supported locale, load its bundle if needed, and make it
1258
+ * active. Returns the locale that ended up active (English on any failure).
1259
+ */
1260
+ declare function loadLocale(code?: string | null): Promise<Locale>;
1261
+
1262
+ /**
1263
+ * Money formatting — the ONE place currency rendering happens.
1264
+ *
1265
+ * Ticket money is multi-currency: the org sets a default and each event can
1266
+ * override it (ISO 4217 code delivered with the event/chart payload). Until
1267
+ * those backend fields land, callers fall back to DEFAULT_CURRENCY, which is
1268
+ * kept at EUR so live buyer pages render exactly what they rendered when the
1269
+ * symbol was hardcoded. Flipping an org to USD/INR/… later is data, not code.
1270
+ *
1271
+ * Amounts are in MAJOR units (45 === €45) matching Category.price in
1272
+ * src/core/types.ts. If/when backend money fields arrive in minor units,
1273
+ * convert at the API boundary, not here.
1274
+ */
1275
+ declare const DEFAULT_CURRENCY = "USD";
1276
+ declare function setMoneyLocale(locale: string | undefined): void;
1277
+ /**
1278
+ * "€45" / "$1,500" / "45 €" (locale-dependent placement).
1279
+ * Whole amounts render without ".00" (design shows "$45", "$120"); pass
1280
+ * `fractionDigits` for fixed precision (e.g. 3 for "$0.045 / credit").
1281
+ */
1282
+ declare function formatMoney(amount: number, currency?: string, fractionDigits?: number): string;
1283
+ /** Bare symbol for input adornments ("€", "$", "₹"). */
1284
+ declare function currencySymbol(currency?: string): string;
1285
+
1286
+ export { ACCESSIBILITY_TYPES, type AccessibilityMeta, type AccessibilityType, type AvailabilityRule, type BoothObject, CHART_STORAGE_KEY, type Category, type CategoryTier, type ChartDoc, type ChartObject, type ChartTheme, DEFAULT_CURRENCY, type Dict, type ExpandedSeat, type Floor, type GAAreaObject, type HoldConflict, type HoldInfo, type ISeatmapRenderer, type Locale, type LodRung, type PickerCallbacks, PickerController, type PickerOptions, type PickerSeat, type PickerTransport, type Point, type RendererCallbacks, type RendererOptions, type RowObject, type RowSeatSlot, SUPPORTED_LOCALES, type SeatOverride, type SeatStatus, SeatmapRenderer, type SectionCategory, type SectionNode, type SectionObject, type SectionSummary, type SelectionLayer, type ShapeObject, type TableObject, type TextObject, UNGROUPED_ID, type ZoneDef, accessibilityMeta, allObjects, applyHidden, chartBounds, computeSections, createRenderer, currencySymbol, expandBooth, expandChart, expandRow, expandRowSlots, expandTable, floorObjects, floorsOf, formatDate, formatMoney, getLocale, hiddenObjectIds, isSectionHidden, layerOf, loadLocale, objectCenter, pointInPolygon, resolveLocale, setLocale, setMoneyLocale, setStringOverrides, stackFloors, t, tCount };