@seatlayer/core 0.34.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-MGC5BVAD.js → chunk-5MRJCIZP.js} +116 -97
- package/dist/chunk-5MRJCIZP.js.map +1 -0
- package/dist/index.cjs +452 -135
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -8
- package/dist/index.d.ts +87 -8
- package/dist/index.js +338 -41
- package/dist/index.js.map +1 -1
- package/dist/{types-DO_J5DtX.d.cts → types-DBnRO2hX.d.cts} +13 -1
- package/dist/{types-DO_J5DtX.d.ts → types-DBnRO2hX.d.ts} +13 -1
- package/dist/view3d/index.cjs.map +1 -1
- package/dist/view3d/index.d.cts +1 -1
- package/dist/view3d/index.d.ts +1 -1
- package/dist/view3d/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-MGC5BVAD.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/types.ts","../src/core/units.ts","../src/core/complexGeometry.ts","../src/core/polygonAnchor.ts","../src/core/sectionPath.ts","../src/core/segmentedRowModel.ts","../src/core/labeling.ts","../src/core/seatNumbering.ts","../src/core/venueStructure.ts","../src/core/layout.ts"],"sourcesContent":["/**\n * SeatMap chart document — the single source of truth shared by the\n * Designer (authoring) and the Renderer (buyer picker).\n *\n * Coordinates are abstract units (roughly \"pixels at scale 1\"); the renderer\n * fits the chart to its container.\n * Rows are PARAMETRIC (origin + count + spacing + curve + rotation) — never\n * store per-seat coordinates in the document; expansion happens in layout.ts.\n */\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface CubicPath {\n start: Point;\n control1: Point;\n control2: Point;\n end: Point;\n}\n\n/** A real closed section boundary. `outline` remains its sampled collision and\n * persistence fallback; this path is the smooth authoring/buyer paint source. */\nexport type SectionPathSegment =\n | { kind: 'line'; end: Point }\n | { kind: 'arc'; center: Point; radius: number; clockwise: boolean; end: Point }\n | { kind: 'bezier'; control1: Point; control2: Point; end: Point };\n\nexport interface SectionOutlinePath {\n version: 1;\n closed: true;\n start: Point;\n segments: SectionPathSegment[];\n}\n\nexport interface Category {\n key: string;\n label: string;\n color: string;\n /** Base price — used when the category has no explicit tiers. */\n price?: number;\n /** Durable evidence for semantic/display facts proposed while converting a\n * private reference into sellable inventory. */\n referenceCategorySource?: ReferenceCategorySource;\n /**\n * Ticket tiers (Adult / Child / Senior…). When present, a buyer picks a tier\n * per seat in this category and the tier's price applies; the first tier is the\n * default. Per-category (not per-seat) pricing — see Batch 3.5. Empty/absent =\n * a single price (the `price` above).\n */\n tiers?: CategoryTier[];\n}\n\nexport interface ReferenceCategorySource {\n assetId: string;\n /** Original sampled section color. `Category.color` is the approved output\n * color and may differ only with separately recorded evidence. */\n sourceColor: string;\n /** Every exact sampled color normalized into the same original 4-bit/channel\n * segmentation class. Older documents may contain only `sourceColor`. */\n sourceColors?: string[];\n /** Logical sections whose generated inventory uses this category. */\n logicalSectionIds?: string[];\n /** Source-color grouping is deterministic; a semantic regrouping needs its\n * own confirmed assignment evidence. */\n assignmentDerivation?: 'source-color-class' | 'confirmed-logical-sections';\n assignmentEvidence?: 'user-confirmed' | 'authoritative-source';\n assignmentSourceDescription?: string;\n /** Optional legend/commercial swatch stated by the source. This remains\n * immutable provenance when the approved accessible output color differs. */\n sourcePaletteColor?: string;\n sourcePaletteColorEvidence?: 'user-confirmed' | 'authoritative-source';\n sourcePaletteColorSourceDescription?: string;\n labelEvidence: 'user-confirmed' | 'authoritative-source';\n labelSourceDescription: string;\n priceEvidence: 'user-confirmed' | 'authoritative-source';\n priceSourceDescription: string;\n outputColorEvidence?: 'user-confirmed' | 'authoritative-source';\n outputColorSourceDescription?: string;\n}\n\n/** One ticket tier within a category: a named price (Adult, Child, Senior…). */\nexport interface CategoryTier {\n id: string;\n name: string;\n price: number;\n}\n\n/**\n * Accessibility accommodations a seat can carry. Mirrors the taxonomy real\n * venues (and seats.io) expose so buyers can filter for exactly what they need\n * and organizers can mark seats precisely. `wheelchair` is the legacy default.\n */\nexport type AccessibilityType =\n | 'wheelchair'\n | 'companion'\n | 'semi-ambulatory'\n | 'hearing'\n | 'cart'\n | 'sign-language'\n | 'plus-size'\n | 'lift-armrest';\n\nexport interface AccessibilityMeta {\n key: AccessibilityType;\n /** Full descriptive label (designer checkbox, picker legend). */\n label: string;\n /** Compact label for chips/badges. */\n short: string;\n /** Single-glyph badge shown on seat chips + filter chips. */\n icon: string;\n}\n\n/** Ordered taxonomy — drives the designer seat panel and the picker filters. */\nexport const ACCESSIBILITY_TYPES: AccessibilityMeta[] = [\n { key: 'wheelchair', label: 'Wheelchair space', short: 'Wheelchair', icon: '♿' },\n { key: 'companion', label: 'Companion seat', short: 'Companion', icon: '🧑🤝🧑' },\n { key: 'semi-ambulatory', label: 'Semi-ambulatory (limited mobility)', short: 'Limited mobility', icon: '🦯' },\n { key: 'hearing', label: 'Assistive listening', short: 'Hearing', icon: '🦻' },\n { key: 'cart', label: 'CART live-caption view', short: 'CART captions', icon: 'CC' },\n { key: 'sign-language', label: 'Sign-language view', short: 'Sign language', icon: '🤟' },\n { key: 'plus-size', label: 'Plus-size seat', short: 'Plus-size', icon: '💺' },\n { key: 'lift-armrest', label: 'Lift-up armrest', short: 'Lift armrest', icon: '↕️' },\n];\n\nconst ACCESSIBILITY_LABEL = new Map(ACCESSIBILITY_TYPES.map((a) => [a.key, a]));\n\n/** Metadata for one accessibility key (undefined for unknown keys). */\nexport function accessibilityMeta(key: AccessibilityType): AccessibilityMeta | undefined {\n return ACCESSIBILITY_LABEL.get(key);\n}\n\n/**\n * Outer-ring colour per accommodation. Shared by the buyer picker and the\n * designer canvas so an accessible seat reads with the same hue in both — the\n * seat's first-listed type wins. `wheelchair` blue is the default fallback.\n */\nexport const ACCESSIBILITY_RING_COLOR: Record<AccessibilityType, string> = {\n wheelchair: '#3b82f6',\n companion: '#8b5cf6',\n 'semi-ambulatory': '#0ea5e9',\n hearing: '#14b8a6',\n cart: '#7c3aed',\n 'sign-language': '#f59e0b',\n 'plus-size': '#ec4899',\n 'lift-armrest': '#22c55e',\n};\n\n/** Ring colour for a seat's accessibility set (first-listed type wins). */\nexport function accessibilityRingColor(types: AccessibilityType[] | undefined): string {\n const primary = types?.[0];\n return (primary && ACCESSIBILITY_RING_COLOR[primary]) || '#3b82f6';\n}\n\nexport interface SeatOverride {\n /** 0-based seat index within the row. */\n index: number;\n /** Physical seat absent (pillar, sound desk) — numbering gap preserved. */\n skip?: boolean;\n /** Position nudge in chart units. */\n dx?: number;\n dy?: number;\n /** Replace the computed label entirely. */\n label?: string;\n /** Buyer-facing copy only. Booking/API identity remains row id + slot index\n * internally and the legacy `label` externally for backwards compatibility. */\n displayLabel?: string;\n categoryKey?: string;\n /** @deprecated legacy flag — read as `['wheelchair']`; write `accessibility`. */\n accessible?: boolean;\n /** Accessibility accommodations of this seat (empty/absent = none). */\n accessibility?: AccessibilityType[];\n /**\n * Physical wheelchair provision. Absent keeps legacy seat rendering;\n * `seat-present` is an explicit removable/fixed accessible chair, while\n * `no-seat` is an empty wheelchair bay that remains one sellable inventory\n * unit. This is deliberately distinct from `skip`, which removes inventory.\n */\n wheelchairSpaceType?: 'seat-present' | 'no-seat';\n /** Commercial selling/view attributes are deliberately not accessibility. */\n commercial?: SeatCommercialAttributes;\n /** Seat-specific view photo; falls back to the row photo. */\n viewFromSeatUrl?: string;\n /** Per-seat label size/color override; falls back to the row/theme default.\n * Size is clamped to LABEL_STYLE_MIN_SIZE..MAX_SIZE; color is passed through\n * the shared auto-contrast rule at paint time (see {@link LabelStyle}). */\n labelStyle?: LabelStyle;\n}\n\nexport interface SeatCommercialAttributes {\n restrictedView?: boolean;\n obstructedView?: boolean;\n premium?: boolean;\n note?: string;\n}\n\n/** The selling attributes that can be marked on a seat one at a time, the same\n * way an accommodation can. `note` is free text and belongs to one seat, so it\n * is deliberately not here. */\nexport type SeatCommercialMark = 'obstructedView' | 'restrictedView' | 'premium';\n\nexport interface SeatCommercialMeta {\n key: SeatCommercialMark;\n /** Full descriptive label (designer panel, tooltips). */\n label: string;\n /** Compact label for chips. */\n short: string;\n /** Single-glyph badge, matching the accessibility chips. */\n icon: string;\n}\n\n/**\n * Ordered taxonomy of sellable view attributes.\n *\n * Kept beside {@link ACCESSIBILITY_TYPES} and shaped identically because the\n * designer paints them with the same tool: an author marking the back two rows\n * of a stand as obstructed is doing the same gesture as marking a wheelchair\n * space, and at a thousand seats it has to cost the same. They stay separate\n * TYPES because they mean different things — an accommodation is a buyer's\n * access need, a selling attribute is what the seat is worth.\n */\nexport const SEAT_COMMERCIAL_MARKS: SeatCommercialMeta[] = [\n { key: 'obstructedView', label: 'Obstructed view', short: 'Obstructed', icon: '⛔' },\n { key: 'restrictedView', label: 'Restricted view', short: 'Restricted', icon: '👁' },\n { key: 'premium', label: 'Premium seat', short: 'Premium', icon: '★' },\n];\n\nconst SEAT_COMMERCIAL_LABEL = new Map(SEAT_COMMERCIAL_MARKS.map((mark) => [mark.key, mark]));\n\n/** Metadata for one selling attribute (undefined for unknown keys). */\nexport function seatCommercialMeta(key: SeatCommercialMark): SeatCommercialMeta | undefined {\n return SEAT_COMMERCIAL_LABEL.get(key);\n}\n\n/**\n * Per-object label ink + size overrides layered on top of the chart-wide Theme\n * defaults (rowLabelColor / textColor for rows, the section-name ink for\n * sections). Both fields are optional: an absent field means \"inherit the theme\n * default\". `color` is a preferred hex — renderers still pass it through the\n * shared auto-contrast rule (`stateAwareBookableLabelInk`), so a choice that\n * would be illegible over the seat/section background is switched to black or\n * white at paint time. `size` is a font size in chart units, clamped to\n * LABEL_STYLE_MIN_SIZE..LABEL_STYLE_MAX_SIZE by the shared ops.\n */\nexport interface LabelStyle {\n size?: number;\n color?: string;\n}\n\n/** Clamp bounds for a per-object label `size`, shared by ops, MCP, and UI. */\nexport const LABEL_STYLE_MIN_SIZE = 8;\nexport const LABEL_STYLE_MAX_SIZE = 24;\n\nexport interface LabelPresentation {\n visible?: boolean;\n /** Exact Designer-owned label anchor; public semantic MCP schemas omit it. */\n position?: Point;\n rotation?: number;\n style?: 'plain' | 'pill';\n /** Per-object size/color override for this row's or section's label. */\n labelStyle?: LabelStyle;\n /**\n * End-position preset for a ROW label — which end(s) of the row show it:\n * - `start` (default/undefined) — the row's numbering-start end (legacy behaviour).\n * - `end` — the far end of the row.\n * - `both` — a label at BOTH ends.\n * - `none` — hidden (kept coherent with `visible: false`).\n * A free-drag `position` overrides the preset (the designer shows a 'custom'\n * state). Ignored for sections (they use `position`/`visible` only).\n */\n positionPreset?: 'start' | 'end' | 'both' | 'none';\n}\n\n/** Brand/venue theming — applied by the renderer in both designer and picker. */\nexport interface ChartTheme {\n /** Canvas background color (default dark: #0e1117-ish radial). */\n background?: string;\n /** Preferred text color for the numbers inside seat markers. */\n seatLabelColor?: string;\n /** Preferred color for row identifiers such as A, B, C. Falls back to textColor. */\n rowLabelColor?: string;\n /** Selection ring / accent color (default white ring + brand accent). */\n selectionColor?: string;\n /** Décor (stage/shape) default fill. */\n decorFill?: string;\n /** Free-text color default. */\n textColor?: string;\n /** Font family (CSS stack) for all rendered text — row labels, seat numbers, sections, décor text. */\n fontFamily?: string;\n /** Seat size multiplier on the base radius (0.7–1.6, default 1) — bigger seats fit longer labels. */\n seatScale?: number;\n // ---- White-label branding (applied by the buyer picker chrome) ----\n /** Brand accent color — recolors buttons, links, the hold pill, selection UI. */\n accent?: string;\n /** Ink color for text on the accent (e.g. button labels). Default light. */\n accentInk?: string;\n /** Organizer logo shown in the picker header (data/R2 URL). Falls back to the name. */\n logoUrl?: string;\n /** Brand/venue name shown in the picker header when no event name is set. */\n brandName?: string;\n /** Paid-tier flag: hide the \"Powered by SeatMap\" badge. */\n hideBadge?: boolean;\n}\n\nexport interface RowObject {\n type: 'row';\n id: string;\n /** Present when this row was materialized by the in-canvas reference scan.\n * A re-scan of the same asset replaces rows carrying this marker and NEVER\n * touches hand-authored rows — the same replace-generated-only invariant as\n * applyReferenceInventory. */\n referenceScan?: { assetId: string };\n /** Row label, e.g. \"A\". Seat labels are `${label}-${n}`. */\n label: string;\n /** Buyer-facing row name. `label` remains the legacy inventory prefix. */\n displayLabel?: string;\n /**\n * Buyer-facing type word override (seats.io \"Displayed type\"). Replaces the\n * hardcoded \"Row\" in the picker tooltip/confirm/cart, e.g. \"Table\", \"Bench\",\n * \"Aisle\". ≤24 chars; absent = the default \"Row\". Pure presentation.\n */\n displayType?: string;\n labelPresentation?: LabelPresentation;\n /** Position of the FIRST seat. */\n origin: Point;\n /** Degrees, clockwise. 0 = seats laid out along +x. */\n rotation: number;\n /**\n * Total arc sweep in degrees across the whole row. 0 = straight.\n * Positive bends away from +y (concave toward the focal point when the\n * row faces it). Typical theatre rows: 10–40.\n */\n curve: number;\n seatCount: number;\n /** Distance between adjacent seat centers, in chart units. */\n seatSpacing: number;\n /** Optional exact cubic centreline. Normal code owns these coordinates and\n * distributes seats by arc length; MCP/model inputs never submit them. */\n path?: CubicPath;\n categoryKey: string;\n /** Deterministic provenance for rows fitted from confirmed reference\n * inventory. It enables revision-safe replacement without touching manually\n * authored rows or accepting client coordinates. */\n referenceInventorySource?: ReferenceInventorySource;\n /** Semantic parameters for a row produced by the shared Arc/Fan operation.\n * Designer can reopen these parameters while every segment in the group\n * still carries the same generation signature. Public MCP tools never accept\n * the stored center/angles as arbitrary model-authored coordinates. */\n arcFanGeneration?: {\n kind: 'arc-fan-v1';\n groupId: string;\n center: Point;\n innerRadius: number;\n rowCount: number;\n rowGap: number;\n startAngle: number;\n endAngle: number;\n seatPitch: number;\n fit: 'seat-pitch' | 'fixed-count';\n seatsPerRow?: number;\n facing: 'inward' | 'outward';\n taperDegrees: number;\n skewDegrees: number;\n aisleGaps: { left: number; center: number; right: number };\n rowLabelStart: number;\n seatLabelStart: number;\n rowIndex: number;\n segmentIndex: number;\n };\n /**\n * Membership in one buyer-facing segmented row. Physical component rows and\n * their `${rowId}:${slotIndex}` inventory ids remain authoritative; this\n * metadata only supplies logical ordering/presentation and explicit aisle\n * continuity. The descriptor is repeated on every component so selecting any\n * one can resolve the complete logical row without a chart-level side table.\n */\n segmentedRow?: {\n kind: 'segmented-row-v1';\n groupId: string;\n componentIndex: number;\n componentCount: number;\n /** The first component must use `start`; later boundaries are explicit. */\n boundaryBefore: 'start' | 'continuous' | 'break';\n /** Buyer-facing row name; technical component `label` values never change. */\n displayLabel: string;\n displayType?: string;\n labelPresentation?: LabelPresentation;\n viewFromSeatUrl?: string;\n /** Presentation intent for a continuous node-defined centreline. */\n smoothing?: boolean;\n };\n /**\n * Versioned provenance for rows created by the multiple/intertwined block\n * generator. Manual geometry edits remove this marker rather than allowing a\n * later regeneration to overwrite hand-authored work.\n */\n rowBlockGeneration?: {\n kind: 'row-block-v1';\n groupId: string;\n style: 'multiple' | 'intertwined';\n rowIndex: number;\n rowCount: number;\n seatsPerRow: number;\n origin: Point;\n rotation: number;\n rowGap: number;\n seatSpacing: number;\n curve: number;\n /** Stable canonical generator signature shared by every intact member. */\n signature: string;\n };\n /** First seat number (default 1). Roman/letters read it as a 1-based ordinal\n * (start 1 → I / A). */\n seatLabelStart?: number;\n /** Seat numbering within the row (default decimal, ltr, step 1). */\n seatNumbering?: {\n /** ltr / rtl number from an end; `center` numbers outward from the middle\n * (centre seat lowest — the premium-centre theatre convention). */\n direction: 'ltr' | 'rtl' | 'center';\n /** 2 = odd/even numbering (1,3,5… — start at 2 for evens). */\n step?: 1 | 2;\n /**\n * Label scheme for the seat NUMBER part (the row prefix is separate).\n * Default `decimal`. Composition with `direction`/`step`/`seatLabelStart`:\n * - `decimal` 1,2,3 — honours direction + step + start.\n * - `odd` 1,3,5 — odd numbers from the first odd ≥ start.\n * - `even` 2,4,6 — even numbers from the first even ≥ start.\n * - `updown` 1,3,5,…,6,4,2 — odd-up-even-back; REPLACES direction (uses\n * physical left→right order); start shifts.\n * - `updown-descending` …5,3,1,2,4,6 — odd-back-even-up; the distinct\n * reverse up/down sequence. Also replaces\n * direction and uses physical order.\n * - `roman` I,II,III — honours direction + step + start (uppercase).\n * - `letters-upper` A,B,C…Z,AA — honours direction + step + start.\n * - `letters-lower` a,b,c…z,aa — honours direction + step + start.\n * Like `step`/`direction` today, the scheme changes the seat's inventory\n * label (its booking identity), by design.\n */\n scheme?: 'decimal' | 'odd' | 'even' | 'updown' | 'updown-descending' | 'roman' | 'letters-upper' | 'letters-lower';\n /** Optional prefix prepended to every seat number, e.g. 'R' → 'R1', 'R2'. */\n prefix?: string;\n /**\n * End-at preset (\"useEndAt\"): the row's numbering ENDS at this value instead\n * of starting at `seatLabelStart`. The start is derived so the last-numbered\n * seat (highest position rank) lands on `endAt`, respecting the scheme's step\n * (odd/even = 2). When set it WINS over the stored `seatLabelStart` (which is\n * left untouched). For letters it is a 1-based number index (26 → last seat\n * 'Z'); `updown` owns its own sequence and ignores `endAt`.\n */\n endAt?: number;\n };\n /**\n * Per-seat exceptions, keyed by seat index (0-based position in the row).\n * `skip` removes the physical seat but keeps the numbering gap (theatre\n * convention: a pillar eats A-3; A-4 stays A-4).\n */\n overrides?: SeatOverride[];\n /**\n * Organizer-supplied equirectangular 360 (or wide photo) shown as the\n * view-from-seat for every seat in this row. When absent, the picker\n * generates a synthetic panorama from chart geometry.\n */\n viewFromSeatUrl?: string;\n /** Default commercial attributes inherited by seats without an override. */\n commercial?: SeatCommercialAttributes;\n}\n\nexport interface GAAreaObject {\n type: 'gaArea';\n id: string;\n /** Stable technical/inventory label. */\n label: string;\n /** Buyer-facing area name; technical `label` and GA unit ids stay stable. */\n displayLabel?: string;\n /** Buyer-facing type word override (seats.io \"Displayed type\"), ≤24 chars.\n * Absent = the default type word. Pure presentation. */\n displayType?: string;\n /** Closed polygon, in chart units. */\n points: Point[];\n /** Explicit aisles/pillars/cutouts excluded from the sellable GA surface. */\n holes?: Point[][];\n capacity: number;\n categoryKey: string;\n /** Corner-rounding radius in chart units (default 0 = sharp corners). Pure\n * presentation — softens the polygon's corners in every renderer without\n * touching capacity, unit identities, or the stored points. Clamped per\n * corner to half the shorter adjacent edge at draw time. */\n cornerRadius?: number;\n /**\n * Durable inventory provenance for a surface produced by Join Areas.\n *\n * A GA unit is identified by the id and zero-based range of the area that\n * originally authored it, not by the current polygon which happens to own\n * it. Keeping those source ranges means a geometric join never renumbers an\n * already published/booked unit. Ordinary (never-joined) areas omit this\n * field and implicitly own `[0, capacity)` under their own id.\n *\n * The ranges must be non-overlapping, contain positive whole counts, and sum\n * exactly to `capacity`; validation rejects malformed metadata. Capacity\n * growth appends a new range under the surviving area id, while shrinking a\n * joined area is deliberately refused because it would silently destroy\n * stable inventory identities.\n */\n inventorySegments?: GAInventorySegment[];\n referenceInventorySource?: ReferenceInventorySource;\n}\n\nexport interface GAInventorySegment {\n sourceAreaId: string;\n startIndex: number;\n count: number;\n}\n\n/**\n * Durable evidence link for sellable objects generated from a private reference.\n * The client supplies facts and stable logical-section ids, never coordinates.\n */\nexport interface ReferenceAccessibilitySource {\n placementDerivation: 'server-synthesized-row-edges';\n groupLogicalSectionIds: string[];\n assignmentEvidence: 'user-confirmed' | 'authoritative-source';\n assignmentSourceDescription: string;\n counts: Array<{\n type: AccessibilityType;\n count: number;\n evidence: 'user-confirmed' | 'authoritative-source';\n sourceDescription: string;\n }>;\n}\n\nexport interface ReferenceInventorySource {\n assetId: string;\n logicalSectionId: string;\n evidence: 'user-confirmed' | 'authoritative-source';\n sourceDescription: string;\n /** Distinguishes directly supplied inventory from a user-approved server\n * distribution based only on an aggregate capacity. */\n derivation?: 'explicit-inventory' | 'server-synthesized-from-aggregate';\n /** Evidence for the aggregate figure; synthesized rows remain\n * `user-confirmed` and are never mislabeled as source-extracted. */\n aggregateEvidence?: 'user-confirmed' | 'authoritative-source';\n /** Separate evidence for assigning standing inventory to this logical\n * section. Aggregate evidence alone cannot prove section placement. */\n sectionAssignmentEvidence?: 'user-confirmed' | 'authoritative-source';\n sectionAssignmentSourceDescription?: string;\n /** Aggregate row synthesis may propose numbering, but persistence requires\n * the applying user/agent to confirm that policy explicitly. */\n numberingEvidence?: 'user-confirmed';\n numberingSourceDescription?: string;\n /** Evidence and deterministic placement contract for synthesized accessible\n * units in this logical section. */\n accessibility?: ReferenceAccessibilitySource;\n}\n\n/** Durable evidence that a visible source-backed section shell intentionally\n * carries no generated sellable inventory in this reference configuration. */\nexport interface ReferenceInventoryExclusionSource {\n assetId: string;\n logicalSectionId: string;\n reason: string;\n evidence: 'user-confirmed' | 'authoritative-source';\n sourceDescription: string;\n}\n\n/** Open-path stroke semantics. Optional ShapeObject fields retain the legacy\n * round/round/no-ending rendering when absent. */\nexport type ShapeLineCap = 'butt' | 'round' | 'square';\nexport type ShapeLineJoin = 'miter' | 'round' | 'bevel';\nexport type ShapeLineEnding = 'none' | 'arrow';\n\n/** Non-bookable décor: stage, walls, exits. */\nexport interface ShapeObject {\n type: 'shape';\n id: string;\n /**\n * Closed area shapes (`rect`/`ellipse`/`polygon`) take a `fill`; open path\n * primitives (`line` = two points, `polyline` = n points) are stroke-only and\n * never filled. All kinds honour the optional `stroke`.\n */\n kind: 'rect' | 'ellipse' | 'polygon' | 'line' | 'polyline';\n label?: string;\n /** For rect/ellipse: bounding box. For a stage polygon: the base (pre-shape) box, so its kind can be regenerated. */\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n /** For polygon/line/polyline. */\n points?: Point[];\n fill?: string;\n /** Optional outline. `width` is in chart units; both fields are required together. */\n stroke?: { color: string; width: number };\n /** Open line/polyline only. Absent fields preserve round/round/no-ending legacy rendering. */\n lineCap?: ShapeLineCap;\n /** Controls corners between open-path segments. */\n lineJoin?: ShapeLineJoin;\n /** Independent open-path start/end decorations. Closed outlines never use these fields. */\n startEnding?: ShapeLineEnding;\n endEnding?: ShapeLineEnding;\n /** Rect only — corner rounding radius in chart units, clamped to half the short side at edit time. */\n cornerRadius?: number;\n /** Whole-shape opacity 0.1–1 (default 1). */\n opacity?: number;\n /** Degrees clockwise about the shape's center (default 0). Applied at render time. */\n rotation?: number;\n /**\n * Semantic tag driving special rendering. `'stage'` gets the gradient +\n * prominent uppercase label treatment; a décor landmark role (bar, exit…)\n * gets a quieter label. Loose string to avoid a circular import with\n * stage.ts / decor.ts (see StageKind / DecorRole there).\n */\n role?: string;\n /** For a stage: which `StageKind` its polygon was generated from. */\n stageKind?: string;\n}\n\nexport type RectTableSide = 'top' | 'bottom' | 'left' | 'right';\n\n/** Exact rectangular-table chair distribution. The four keys are deliberately\n * required: zero means that edge has no chair, while the sum is the authored\n * `seatCount`. Numeric chair identity remains `${table.id}:${index}` in the\n * canonical top, bottom, left, right expansion order. */\nexport interface RectTableSeatCounts {\n top: number;\n bottom: number;\n left: number;\n right: number;\n}\n\n/** Seats arranged around a table. Grouped selling is activated only by an\n * event's explicit inventory-model-2 snapshot; model-1 events continue to\n * treat every authored chair as an independent unit. */\nexport interface TableObject {\n type: 'table';\n id: string;\n /** e.g. \"T1\" — seat labels are `${label}-${n}`. */\n label: string;\n /** Buyer-facing table name; technical chair/group labels stay stable. */\n displayLabel?: string;\n /** Buyer-facing type word override (seats.io \"Displayed type\"), ≤24 chars.\n * Absent = the default \"Row\"/\"Table\" word. Pure presentation. */\n displayType?: string;\n center: Point;\n shape: 'round' | 'rect';\n /** Seats around the perimeter (round) or along the enabled edges (rect). */\n seatCount: number;\n /** Rect tables: which edges get seats (default ['top','bottom']). */\n sides?: RectTableSide[];\n /**\n * Rect tables only: exact chairs on every edge. Absent preserves the legacy\n * `seatCount` + `sides` round-robin distribution byte-for-byte. When present,\n * all four values are whole numbers >= 0 and their sum equals `seatCount`.\n */\n seatCountsBySide?: RectTableSeatCounts;\n /** Individual-chair semantic overrides. Grouped whole/variable tables cannot\n * author these because their only sellable identity is the table itself. */\n overrides?: SeatOverride[];\n rotation: number;\n /** Round tables. */\n radius?: number;\n /**\n * Round tables: the arc (in degrees) the seats occupy, default 360 (full\n * ring). Below 360 leaves an open side — e.g. a service gap for waiters, a\n * head table facing the room, or clearance against a wall. The opening is\n * centred on the `rotation` direction; seats spread across the rest.\n */\n seatArc?: number;\n /** Rect tables. */\n width?: number;\n height?: number;\n categoryKey: string;\n /** One buyer owns the complete table at exactly `seatCount` guests. */\n bookAsWhole?: boolean;\n /** One buyer owns the complete table and chooses a bounded guest quantity. */\n variableOccupancy?: boolean;\n /** Required inclusive guest bounds when `variableOccupancy` is true. */\n minOccupancy?: number;\n maxOccupancy?: number;\n referenceInventorySource?: ReferenceInventorySource;\n}\n\n/** A booth: one bookable unit rendered as a block (trade shows, VIP boxes). */\nexport interface BoothObject {\n type: 'booth';\n id: string;\n /** Stable technical/inventory label. */\n label: string;\n /** Buyer-facing booth name; technical `label` stays stable. */\n displayLabel?: string;\n /** Buyer-facing type word override (seats.io \"Displayed type\"), ≤24 chars.\n * Absent = the default type word. Pure presentation. */\n displayType?: string;\n center: Point;\n width: number;\n height: number;\n rotation: number;\n /**\n * Optional custom outline (closed polygon, absolute chart coordinates) for\n * non-rectangular booths — L-shaped, corner, or island units on expo floors.\n * Absent = the default axis-aligned rectangle described by `width`/`height`/\n * `rotation`. A booth stays exactly ONE atomic sellable unit whatever its\n * outline; `points` is purely geometric. `width`/`height` are retained as the\n * last rectangular size so \"Back to rectangle\" can restore it. When `points`\n * is present, renderers draw the polygon and ignore `rotation`.\n */\n points?: Point[];\n categoryKey: string;\n referenceInventorySource?: ReferenceInventorySource;\n}\n\n/**\n * A named region of the venue (Balcony Left, Floor B…). Sections are outlines\n * only — objects belong to a section spatially (center inside the outline),\n * keeping the document flat (no nesting; simpler for tools and for AI edits).\n * Renderer: far zoom shows section shapes/labels instead of seats; clicking\n * a section zooms into it.\n */\nexport interface SectionObject {\n type: 'section';\n id: string;\n label: string;\n /** Buyer-facing section name; logical/id fields remain stable. */\n displayLabel?: string;\n /**\n * Buyer-facing entrance/door hint shown in the picker section card\n * (\"Entrance X\"). ≤40 chars; absent = no entrance line. Pure presentation.\n */\n entrance?: string;\n /**\n * Organizer-supplied view image inherited by buyer inventory whose owning\n * row/table/booth sits in this logical section. Seat and row photos take\n * precedence; multipart components are kept in sync by the shared section\n * metadata operation.\n */\n viewFromSeatUrl?: string;\n labelPresentation?: LabelPresentation;\n /**\n * Stable management/inventory identity shared by disconnected visual\n * components of one logical section. When absent, `id` is the logical id.\n * Each component keeps its own `id` and reference provenance so rendering,\n * editing, measured diffs, and source restoration remain exact.\n */\n logicalSectionId?: string;\n /** Shared semantic Arc/Fan group wrapped by this section. The section id is\n * preserved when the fan parameters are reopened and regenerated. */\n arcFanGroupId?: string;\n /** Closed polygon, chart units. */\n outline: Point[];\n /** Optional true line/arc/cubic boundary. `outline` is a deterministic sample\n * of this path and remains authoritative for membership, validation, and\n * clients that predate curved section rendering. */\n outlinePath?: SectionOutlinePath;\n /** Explicit aisle/cutout polygons excluded from rendering, hit-testing, and membership. */\n holes?: Point[][];\n /** Durable opaque link to the private reference component. Unlike generator\n * provenance, this survives manual geometry edits so a measured diff can\n * report drift and server-owned code can restore the source contour. */\n referenceSource?: {\n assetId: string;\n regionId: string;\n };\n /** Evidence-backed reason this source section remains a visible shell without\n * synthesized sellable inventory (press, closed technical zone, etc.). */\n referenceInventoryExclusion?: ReferenceInventoryExclusionSource;\n /** Deterministic generator provenance for editable reference/parametric shells. */\n geometry?: {\n kind: 'rectangle' | 'tapered' | 'bezier' | 'contour';\n sourceRegionId?: string;\n contourMethod?: 'pixel-edge-loops-rdp' | 'shared-edge-vector-fit-v1';\n simplificationTolerancePx?: number;\n vectorFitErrorPx?: number;\n sharedEdgeCount?: number;\n };\n /** Optional tint override (defaults to a neutral fill / dominant category mix). */\n color?: string;\n /** Zone this section belongs to (id into `ChartDoc.zones`). Far-zoom nav + pricing group. */\n zone?: string;\n /**\n * Tier height. 0 = floor (default). Higher values lift the section in the\n * picker's isometric (\"3D\") view, drawn on extruded side faces. Same field a\n * future multi-floor mode reuses — authored in 2D, never drawn by the user.\n *\n * This is the coarse, back-compat source for {@link height}/{@link rake}: when\n * those are absent, {@link sectionGeometry} derives real geometry from this\n * tier so legacy charts render pixel-identical.\n */\n elevation?: number;\n /**\n * 3D foundations (Phase A, additive — no migration; charts are JSON blobs).\n * Metres the section's **front edge** sits above floor 0 (a balcony/tier floor\n * height). Absent ⇒ derived from the coarse {@link elevation} tier via\n * {@link sectionGeometry}. Deliberately two scalars, not a foundation polygon:\n * front-height + {@link rake} fully determine a rectangular tier's back-height.\n *\n * NOTE: no consumer reads this raw field directly — all callers go through\n * {@link sectionGeometry}. Phase B consumers (iso view lift in\n * `SeatmapRenderer`, per-seat eye-height in the `generatePanorama` 360°\n * generator) are intentionally NOT wired in Phase A. Range 0–120 m.\n */\n height?: number;\n /**\n * Degrees of seating incline within the section (0 = flat; typical stalls\n * 5–15°, steep tiers 25–35°). Absent ⇒ 0. Consumed alongside {@link height}\n * by the future Phase B iso-lift shear and 360° sightline math — never in\n * Phase A. Range 0–45°.\n */\n rake?: number;\n /**\n * Override how 3D builds this section's seating surface. Absent = inferred.\n *\n * The renderer infers structure from the authored geometry — which rows form a\n * stand, where its front is, whether the rows are concentric arcs or straight\n * blocks — and that inference is correct across every shipped template. But\n * inference cannot be right on every chart a customer will ever draw, and when\n * it is wrong the author needs a way to say so without us inventing a second\n * source of truth for the geometry itself.\n *\n * - `flat`: a level deck, whatever the rake says. Terraces, boxes, standing.\n * - `rakedRows`: level ribbons stepping back, the default for a raked stand.\n *\n * Deliberately NOT a \"venue type\" picker: the chart is authored once in 2D,\n * and every 3D-only property that is not derived from that authoring is a\n * source of drift. This is a per-section correction, in the same place and of\n * the same kind as {@link height} and {@link rake}.\n */\n surfaceKind?: 'flat' | 'rakedRows';\n /** Uniform scale about the outline centroid (1 = as drawn). Scales members too. */\n scale?: number;\n /** 0–100: reviewed strength last used to bend member rows toward a common fitted arc. */\n smoothing?: number;\n /**\n * Corner smoothing: the raw clicked polygon this section was drawn from, kept\n * verbatim so {@link cornerSmoothing} stays re-derivable and fully reversible.\n * When present, {@link outlinePath} is a curve computed from THIS polygon (not\n * a reference/blueprint contour); {@link outline} is its deterministic sample.\n * Additive and optional — legacy charts and reference-derived curves omit it.\n */\n sourceOutline?: Point[];\n /**\n * 0–100 corner-smoothing strength applied to {@link sourceOutline} to produce\n * the curved {@link outlinePath}. 0/absent = exact clicked corners. Higher\n * rounds the wide (gently-angled) corners more; sharp corners stay crisp.\n * Coordinate-free, so it round-trips over MCP (`update_sections`).\n */\n cornerSmoothing?: number;\n /**\n * Per-edge curvature, one entry per edge of {@link sourceOutline}, -100..100,\n * 0 = straight. Edge `i` runs from clicked vertex `i` to vertex `i+1`.\n *\n * Positive bows to the LEFT of that edge's own direction. The sign is relative\n * to the edge, never to world axes, which is what lets curvature survive\n * rotate / flip / mirror / radial repeat with no per-operation handling.\n *\n * Coordinate-free like {@link cornerSmoothing}, so it round-trips over MCP and\n * the server never accepts client geometry. Absent/all-zero = straight edges;\n * zeroing it restores the exact clicked polygon.\n */\n edgeCurvature?: number[];\n /** Degrees clockwise about the outline centroid (default 0). Rotates members too. */\n rotation?: number;\n}\n\n/**\n * A group of sections (Lower Bowl, Upper Bowl, Floor…). One concept, three jobs:\n * the farthest-zoom navigation unit, a pricing group, and (Batch 3) a timed-\n * release unit. Kept as a flat list on the doc; sections point back by `zone` id.\n */\nexport interface ZoneDef {\n id: string;\n label: string;\n color?: string;\n /**\n * Authored point this zone faces. Optional only for legacy documents: runtime\n * consumers fall back to the active floor/chart focal, while publication of\n * a zone-mode draft requires every used zone to carry an explicit point.\n */\n focalPoint?: Point;\n}\n\n/**\n * Selection layer — a hit-test/dim filter in the designer, NOT z-order management.\n * Fixed set of four; derived from object type via `layerOf()` (no per-object field yet).\n */\nexport type SelectionLayer = 'interactive' | 'background' | 'foreground' | 'surroundings';\n\n/** Shape roles emitted by the curated venue-landmark palette. Keep this list in\n * lockstep with `DECOR_PRESETS`; the selection-layer unit test fails if either\n * vocabulary changes without an explicit routing decision. `reference-focal`\n * is source-backed venue context rather than an authoring-palette preset. */\nexport const SURROUNDINGS_SHAPE_ROLES = [\n 'reference-focal',\n 'bar',\n 'entrance',\n 'exit',\n 'restroom',\n 'screen',\n 'sound',\n 'concession',\n 'coat',\n 'wall',\n] as const;\n\nconst SURROUNDINGS_SHAPE_ROLE_SET: ReadonlySet<string> = new Set(SURROUNDINGS_SHAPE_ROLES);\n\n/** Derive an object's selection layer from its type. */\nexport function layerOf(obj: ChartObject): SelectionLayer {\n switch (obj.type) {\n case 'row':\n case 'table':\n case 'gaArea':\n case 'booth':\n case 'section':\n return 'interactive';\n // Raster décor follows its explicit authored z-layer. Absence retains the\n // legacy Background default; bitmap content is never guessed semantically.\n case 'decorImage':\n return obj.layer === 'foreground' ? 'foreground' : 'background';\n // Source-backed focal geometry and curated venue landmarks help an author\n // orient around the sellable plan. A stage and an ordinary authored shape\n // remain Background even when they carry an arbitrary/custom role.\n case 'shape':\n return obj.role && SURROUNDINGS_SHAPE_ROLE_SET.has(obj.role)\n ? 'surroundings'\n : 'background';\n // Free text — including semantic icon text — is background furniture.\n case 'text':\n return 'background';\n default:\n return 'interactive';\n }\n}\n\n/** Free-standing text on the chart (aisle names, door labels…). */\nexport interface TextObject {\n type: 'text';\n id: string;\n /** Persisted provenance for objects created from the venue-icon palette. */\n semanticKind?: 'icon';\n /**\n * Registry key for a vector wayfinding icon (see src/core/icons.ts). Present\n * on modern icon placements; the object then renders as a single-color vector\n * Path instead of `text`. Absent on legacy emoji icons, which keep rendering\n * `text` through the shared glyph path — old charts are never rewritten.\n */\n iconKey?: string;\n text: string;\n position: Point;\n fontSize: number;\n /** Optional CSS family stack for this annotation; absent inherits ChartTheme.fontFamily. */\n fontFamily?: string;\n rotation: number;\n color?: string;\n /** Render weight (default false). Maps to Konva fontStyle bold. */\n bold?: boolean;\n /** Render slant (default false). Maps to Konva fontStyle italic. */\n italic?: boolean;\n}\n\n/**\n * A raster/vector decor graphic drawn IN the chart, beneath the seats and\n * sections (ice rink, basketball court, stage art, pitch markings). Purely\n * visual venue context — never bookable, never hit-tested, so it never steals a\n * seat click. `href` is a self-contained data URL (image or SVG) produced by the\n * same client-side downscale used for row photos, so it travels with the doc and\n * caches as a single bitmap blit (zero per-frame cost). Placed by top-left\n * (x,y) + size, rotated about its centre — the same handles a shape rect uses.\n */\nexport interface DecorImageObject {\n type: 'decorImage';\n id: string;\n /** Image or SVG data URL. */\n href: string;\n x: number;\n y: number;\n width: number;\n height: number;\n /** Degrees clockwise about the image centre (default 0). */\n rotation?: number;\n /** 0–1 (default 1). Multiplied by any chart-theme dimming at render time. */\n opacity?: number;\n /**\n * Z-layer relative to the interactive seat layer. `background` (default)\n * draws beneath the seats/sections; `foreground` draws above them (a roof\n * canopy, an overlay graphic). Absent = background — no migration needed.\n */\n layer?: 'background' | 'foreground';\n /** Optional caption for the designer inspector / accessibility (not drawn). */\n label?: string;\n}\n\nexport type ChartObject =\n | RowObject\n | GAAreaObject\n | ShapeObject\n | TableObject\n | BoothObject\n | TextObject\n | SectionObject\n | DecorImageObject;\n\n/**\n * One floor / level of a multi-floor venue (Batch 5). Each floor owns its own\n * geometry, stage focal point, and trace image; categories/zones/tiers stay\n * chart-global (one event, one inventory). A single-floor chart has NO `floors`\n * — its `objects[]` is the whole venue — so all existing charts are untouched.\n */\nexport interface Floor {\n id: string;\n name: string;\n /**\n * Absolute physical deck height in metres above the venue/stage datum.\n * Optional for backwards compatibility; an absent value resolves to ground\n * level (0 m). Section tiers and rakes are separate, section-local metadata.\n * Range 0–120 m.\n */\n baseHeightM?: number;\n objects: ChartObject[];\n focalPoint: Point;\n /**\n * Private organizer trace/calibration layer. This is authoring evidence and\n * must never be served to, or rendered by, a buyer surface.\n */\n referenceImage?: ChartReferenceImage;\n /**\n * Buyer-visible aesthetic background. Canonical documents store URL-only\n * images here. Historical `assetId` values are interpreted as a trace layer\n * by the background compatibility helpers.\n */\n backgroundImage?: ChartDoc['backgroundImage'];\n /**\n * Per-floor override of the venue-wide view-from-seat fallback: a single\n * organizer 360° (or wide photo) inherited by every seat on THIS floor that\n * has no closer row/section photo. Falls below section photos and above the\n * chart-level `viewFromSeatUrl`. Absent ⇒ the venue default (then a synthetic\n * panorama) applies.\n */\n viewFromSeatUrl?: string;\n}\n\nexport interface ReferenceCalibration {\n type: 'two-point';\n /** Points in immutable source-image pixels, selected by a human or trusted detector. */\n sourceA: Point;\n sourceB: Point;\n /** Verified real-world distance between the two source points. */\n distance: number;\n unit: 'm' | 'ft' | 'chart-unit';\n /** Derived and stored for deterministic geometry compilers. */\n pixelsPerUnit: number;\n}\n\n/**\n * A single human-placed seat probe: the author clicks one seat on the reference\n * image and the server reads the surrounding seat lattice from it.\n *\n * COORDINATE-POLICY CARVE-OUT (owner decision 2026-07-21). The reference\n * blueprint pipeline runs `coordinatePolicy: 'opaque-region-ids-only'` — the\n * server is the sole source of chart coordinates and MCP clients select opaque\n * `reg_*` ids, never points. This type is a deliberate, narrow exception on the\n * same grounds as `ReferenceCalibration`: the point is placed by a human in the\n * designer canvas, not proposed by a model.\n *\n * Therefore this is DESIGNER-ONLY and is intentionally NOT exposed over MCP.\n * That is an accepted, documented exception to the MCP-parity rule — the\n * server-is-sole-source guarantee for model-driven edits is worth more than\n * parity here. Do not \"fix\" it by adding a seed point to an MCP tool schema.\n */\nexport interface ReferenceSeatSeed {\n /** Seed centre in immutable source-image pixels (never chart coordinates). */\n source: Point;\n /** Half-width of the author's sizing ring, in source pixels — the \"this is how\n * big one seat is\" hint that replaces seats.io's zoom-until-it-matches step. */\n radius: number;\n /** Whether `radius` was fitted from image pixels or set by hand. Detection\n * weights an author-set radius more heavily than one we guessed. */\n origin: 'auto-fit' | 'manual';\n}\n\n/** One detected row in a scan proposal — a straight seat run in CHART\n * coordinates (the server maps source pixels through referencePixelToChart;\n * clients never see source-pixel geometry back). */\nexport interface ReferenceScanRowProposal {\n start: Point;\n end: Point;\n seatCount: number;\n}\n\n/** Detected rows attributed to one compiled section (or unattributed when the\n * lattice extends outside every compiled polygon). */\nexport interface ReferenceScanSectionProposal {\n /** Id of the compiled SectionObject the rows landed in; null = unattributed. */\n sectionId: string | null;\n name: string;\n rows: ReferenceScanRowProposal[];\n seatCount: number;\n /** 0..1 — how well this section's lattice agreed with the probe's pitch. */\n confidence: number;\n /** Index of the seed (multi-probe) whose pitch produced these rows. */\n seedIndex: number;\n}\n\n/** Server response for an in-canvas reference scan. A PROPOSAL — nothing is\n * committed until the author applies it in the designer (chartOps + undo). */\nexport interface ReferenceScanProposal {\n assetId: string;\n /** Measured seat diameter / centre-to-centre pitch, in chart units. */\n seatDiameter: number;\n seatPitch: number;\n totalSeats: number;\n totalRows: number;\n sections: ReferenceScanSectionProposal[];\n}\n\n/**\n * Human-only Magic Trace request. `source` is one click in immutable\n * source-image pixels; it is accepted only by the browser Designer HTTP\n * surfaces and must never be added to an MCP schema.\n */\nexport interface ReferenceSectionTraceInput {\n assetId: string;\n floorId?: string;\n expectedUpdatedAt: number;\n source: Point;\n}\n\n/**\n * Whole-reference Magic Trace request. Unlike the one-region request this\n * carries no source coordinates: the server returns every persisted closed\n * region as an ID-free proposal for explicit browser review.\n */\nexport interface ReferenceSectionTraceBatchInput {\n assetId: string;\n floorId?: string;\n expectedUpdatedAt: number;\n}\n\n/**\n * Read-only Magic Trace result. This is deliberately not a SectionObject:\n * there is no object id or label until the author accepts the proposal through\n * the normal Designer command/undo boundary.\n *\n * All geometry is in chart coordinates. Source pixels, private contour\n * vertices and reference bounds are never returned.\n */\nexport interface ReferenceSectionTraceProposal {\n assetId: string;\n floorId: string;\n outline: Point[];\n /** Required exact fitted line/arc/cubic boundary. */\n outlinePath: SectionOutlinePath;\n /** Only structural source voids; printed labels/icons are filtered out. */\n holes: Point[][];\n /**\n * Optional, and the tracer deliberately omits it. The colour a section is\n * drawn in belongs to the CHART, not to the screenshot it was traced from —\n * see the note in `referenceSectionTrace.ts`. The sampled value lives on the\n * region row as `sampled_color`. Consumers fall back to zone/theme colour.\n */\n color?: string;\n referenceSource: {\n assetId: string;\n regionId: string;\n };\n geometry: {\n kind: 'contour';\n sourceRegionId: string;\n contourMethod: 'shared-edge-vector-fit-v1';\n simplificationTolerancePx: number;\n vectorFitErrorPx: number;\n sharedEdgeCount: number;\n };\n provenance: {\n regionId: string;\n analysisVersion: number;\n vectorTopologyVersion: number;\n selection: 'human-source-pixel-seed' | 'human-bulk-reference-review';\n registration: 'persisted-reference-registration-v1';\n };\n /** Coordinate-free evidence suitable for an author-facing confirmation. */\n diagnostics: {\n regionMarker: string;\n structuralHoleCount: number;\n fittedComponentCount: number;\n sharedBoundaryCount: number;\n maximumAllowedVectorErrorPx: number;\n measuredVectorErrorPx: number;\n /** Source component footprint, as a percentage of the analyzed image. */\n sourceAreaPercent?: number;\n /**\n * The name already printed on the plan for this region, when one has been\n * read. The review proposes it instead of a generated number — a venue plan\n * names its own sections, and retyping forty of them is work the author\n * should never have to do. Absent means fall back to numbering.\n */\n visibleLabel?: string;\n };\n}\n\n/** Read-only whole-reference proposal. The author may approve each proposal or\n * all proposals; the Designer then materializes the accepted set atomically. */\nexport interface ReferenceSectionTraceBatchProposal {\n assetId: string;\n floorId: string;\n proposals: ReferenceSectionTraceProposal[];\n diagnostics: {\n analyzedRegionCount: number;\n proposalCount: number;\n alreadyTracedCount: number;\n withinToleranceCount: number;\n /** Regions that could not be traced into a usable boundary and were left\n * out. One unusable region must not cost the author the rest of the plan. */\n unusableRegionCount?: number;\n };\n}\n\n/** Coordinate-free physical scale derived by server code from a confirmed\n * semantic feature. Unlike manual two-point calibration, no source points pass\n * through an MCP client or language model. */\nexport interface ReferenceDerivedScale {\n method: 'confirmed-focal-axis-v1';\n feature: 'focal-long-axis' | 'focal-short-axis';\n distance: number;\n unit: 'm' | 'ft';\n evidence: 'user-confirmed' | 'authoritative-source';\n sourceDescription: string;\n chartUnitsPerUnit: number;\n}\n\nexport interface ChartReferenceImage {\n /** Stable private reference asset. New cloud-authored charts use this. */\n assetId?: string;\n /** Legacy/self-contained source. Optional when assetId is present. */\n url?: string;\n center: Point;\n /** Rendered width in chart units (height follows the cropped image aspect). */\n width: number;\n opacity: number;\n rotation?: number;\n visible?: boolean;\n layer?: 'below' | 'above';\n locked?: boolean;\n /** Normalized source crop; defaults to the full image. */\n crop?: { x: number; y: number; width: number; height: number };\n calibration?: ReferenceCalibration;\n /** Server-derived semantic calibration without source-image coordinates. */\n derivedScale?: ReferenceDerivedScale;\n}\n\nexport interface ChartDoc {\n version: 1;\n name: string;\n venueType: 'SIMPLE' | 'MIXED';\n /** The stage / point every seat looks at. Anchors seat-view + sightlines.\n * Multi-floor: mirrors floor 0; each floor also carries its own focalPoint. */\n focalPoint: Point;\n categories: Category[];\n /** Section groupings for far-zoom navigation + pricing (optional; sections reference by id). */\n zones?: ZoneDef[];\n /** Multi-floor venues (Batch 5): present ⇒ floors[] is the source of truth;\n * absent ⇒ single-floor and `objects` below is the whole chart. `objects`\n * is kept mirroring floor 0 so single-floor readers never branch. */\n floors?: Floor[];\n objects: ChartObject[];\n /**\n * Private floor-plan source used for tracing, calibration, scanning and\n * reference-backed generation. Buyer projections always remove this field.\n */\n referenceImage?: ChartReferenceImage;\n /**\n * Buyer-visible aesthetic background. Canonical values are URL-only.\n * Compatibility: a historical value containing `assetId` is trace-only and\n * is never rendered or exposed to buyers.\n */\n backgroundImage?: ChartReferenceImage;\n /** Brand/venue theming (colors); categories carry their own colors separately. */\n theme?: ChartTheme;\n /** Parametric-template provenance: present ⇒ the chart came from a capacity-\n * adjustable template family, and the designer offers a capacity control that\n * regenerates it at a new target seat count (Batch 4 \"curated singles + resize\"). */\n template?: { family: string; targetSeats: number };\n /**\n * Venue-wide view-from-seat fallback: a single organizer 360° (or wide photo)\n * inherited by every seat that has no closer row/section/floor photo. Absent ⇒\n * the picker generates a synthetic panorama.\n */\n viewFromSeatUrl?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Expanded (render-time) model — produced by layout.ts, consumed by the engine\n// ---------------------------------------------------------------------------\n\nexport interface ExpandedSeat {\n /** Stable id: `${rowId}:${index}` */\n id: string;\n /** Public label: `${rowLabel}-${seatNumber}` */\n label: string;\n /** Buyer-facing copy; absent on legacy charts, where `label` is displayed. */\n displayLabel?: string;\n x: number;\n y: number;\n rowId: string;\n /** Owning logical section and navigation zone, resolved once at expand time. */\n sectionId?: string;\n zoneId?: string;\n /** Zone focal when authored, otherwise the active floor/chart legacy fallback. */\n focalPoint?: Point;\n /** Buyer-facing segmented-row identity. `rowId` stays the physical owner id. */\n logicalRowId?: string;\n /**\n * Seat order inside the logical row. A deliberate missing integer is inserted\n * at every aisle boundary, so numerical adjacency cannot bridge a gap.\n */\n logicalSeatIndex?: number;\n categoryKey: string;\n /** 'booth' units render as blocks (dimensions looked up via rowId = booth id). */\n kind?: 'seat' | 'booth';\n /** True when the seat has any accessibility accommodation — renderer rings/dims these. */\n accessible?: boolean;\n /** Specific accessibility accommodations (absent = none) — picker badges/filters these. */\n accessibility?: AccessibilityType[];\n /** Physical wheelchair provision resolved from the seat override. */\n wheelchairSpaceType?: 'seat-present' | 'no-seat';\n commercial?: SeatCommercialAttributes;\n /** Organizer-supplied view-from-seat image (inherited from the row). */\n viewUrl?: string;\n /** Per-seat label size/color override; absent = inherit the row/theme default. */\n labelStyle?: LabelStyle;\n /**\n * Real-world eye height in metres above the focal/stage datum, resolved at\n * expand time from the owning section's `{height, rake}` + drawn depth (Phase B2).\n * Feeds the auto-360° generator's stage-pitch math. Absent ⇒ flat seated eye\n * height (legacy / seats in no section) — so old charts stay pixel-identical.\n */\n eyeHeightM?: number;\n}\n\nexport type SeatStatus = 'free' | 'held' | 'booked' | 'not_for_sale';\n\n/** Buyer canvas projection. Perspective is a view-only projected-2.5D lane. */\nexport type RendererViewMode = 'flat' | 'isometric' | 'perspective';\n\n// ---------------------------------------------------------------------------\n// Renderer engine public API — implemented in src/engine/SeatmapRenderer.ts\n// ---------------------------------------------------------------------------\n\nexport interface RendererCallbacks {\n onSelect?: (seat: ExpandedSeat) => void;\n onDeselect?: (seat: ExpandedSeat) => void;\n /** Buyer tried to add a seat after the active selection cap was reached. */\n onSelectionLimit?: (maxSelection: number) => void;\n /** seat is null when the pointer leaves any seat. */\n onHover?: (seat: ExpandedSeat | null) => void;\n /** Keyboard focus moved to a seat (arrow-key navigation) — for screen-reader announcements. */\n onFocusSeat?: (seat: ExpandedSeat | null) => void;\n /** Called ~1×/sec with the measured frames-per-second. */\n onFps?: (fps: number) => void;\n /** Fired when a GA area is clicked (quantity picking is UI-side). */\n onGAClick?: (areaId: string) => void;\n /**\n * Fired when a tap lands on a section outline while seats are NOT the active\n * rung (i.e. zoomed out, section/zone LOD). The host glides in + shows a\n * section-summary card instead of trying to select a 4px seat (Slice 5).\n */\n onSectionTap?: (sectionId: string) => void;\n /**\n * Fired when a seat/deck is tapped in the 3D all-floors stacked overview — the\n * host drops back to the flat 2D map on that floor (\"tap a deck to enter\").\n */\n onDeckTap?: (floorId: string) => void;\n /** Fired after any pan/zoom/resize settles — re-anchor screen-space overlays. */\n onViewChange?: () => void;\n /**\n * Organizer manage-mode only (`manageMode` + `marqueeSelect`): fired on\n * pointer-UP after a rubber-band marquee drag, or a ⌘A/Escape bulk shortcut,\n * with the FULL current selection (selectable seats only). The host toolbar\n * reads this to drive bulk block/unblock. Never fires when manageMode is off.\n */\n onMarquee?: (seats: ExpandedSeat[]) => void;\n}\n\n/** Far-zoom level-of-detail rung: whole zones → section blocks → individual seats. */\nexport type LodRung = 'zones' | 'sections' | 'seats';\n\nexport interface RendererOptions extends RendererCallbacks {\n /**\n * Internal fixed-output mode used by the free maker's PNG/PDF pipeline.\n * It disables interaction, ResizeObserver, animation/FPS work and overview\n * bitmap caching while keeping the ordinary buyer renderer unchanged.\n */\n exportMode?: boolean;\n /** Max seats selectable at once (default 10). */\n maxSelection?: number;\n /** Only these statuses are clickable (default ['free']). */\n selectableStatuses?: SeatStatus[];\n /**\n * Opt-in host behaviour flag — the renderer's own click/selection logic is\n * unchanged (it still selects + fires `onSelect` immediately, so the seat\n * highlights right away). When true, the host (e.g. PublicEventPage) treats\n * that `onSelect` as a pending candidate and shows a confirm card instead of\n * pushing straight into the cart; `deselect([seat.id])` on Cancel un-highlights.\n */\n confirmSelection?: boolean;\n /** ISO 4217 currency for on-map prices (\"FROM …\"); defaults to money.DEFAULT_CURRENCY.\n * Locale for grouping/symbol placement comes from the active i18n locale. */\n currency?: string;\n /**\n * Organizer manage surface (SDK SeatManager). Opt-in — enables the manage-mode\n * gestures (marquee, ⌘A/Escape) and the bulk-selection helpers. Buyer pan /\n * pinch / tap and every existing code path are byte-identical when this is\n * false (every manage branch is gated on it). Default false.\n */\n manageMode?: boolean;\n /**\n * When `manageMode` is on, a mouse/pen primary-button drag at the seats rung\n * draws a rubber-band marquee that bulk-selects the seats it covers (emitting\n * `onMarquee` on pointer-up) instead of panning. Touch keeps single-finger\n * pan (pinch to zoom); a middle-button drag pans with a mouse. Disabled below\n * the seats rung (zoom in first). No effect unless `manageMode` is also set.\n */\n marqueeSelect?: boolean;\n}\n\nexport type RenderedLabelHiddenReason =\n | 'below-minimum-size'\n | 'outside-viewport'\n | 'dimmed-or-unavailable'\n | 'clutter-or-fit'\n | 'renderer-hidden';\n\n/** Browser-renderer evidence used by visual QA and catalog release gates. */\nexport interface RenderedBookableLabelEvidence {\n seatId: string;\n label: string;\n kind: 'seat' | 'booth';\n /** Painted inventory silhouette. Empty wheelchair bays are deliberately\n * square, while physical seats retain the ordinary circular marker. */\n markerShape: 'circle' | 'square' | 'booth';\n /** Physical wheelchair provision represented by this inventory unit. */\n wheelchairSpaceType?: 'seat-present' | 'no-seat';\n categoryKey: string;\n sectionId?: string;\n zoneId?: string;\n status: SeatStatus;\n selected: boolean;\n visible: boolean;\n renderedFontPx: number;\n fill: string;\n ink: string;\n opacity: number;\n /** Buyer-visible accessibility glyph evidence. Filter emphasis uses a\n * screen-space minimum so wheelchair provision remains recognizable at fit. */\n accessibilityMarker?: {\n glyphVisible: boolean;\n glyphWidthPx: number;\n emphasizedByFilter: boolean;\n };\n /** Direct Konva shape bounds and the production near-miss rescue combined. */\n pointerTarget: {\n active: boolean;\n directWidthPx: number;\n directHeightPx: number;\n effectiveMinimumPx: number;\n };\n /** Centre of the painted unit, even when its text is intentionally hidden. */\n screenCenter: { x: number; y: number };\n screenBox?: { x: number; y: number; width: number; height: number };\n hiddenReason?: RenderedLabelHiddenReason;\n}\n\nexport interface RenderedHierarchyLabelEvidence {\n id: string;\n kind: 'section' | 'zone';\n role: 'name' | 'availability' | 'price';\n label: string;\n visible: boolean;\n renderedFontPx: number;\n opacity: number;\n fill: string;\n ink: string;\n /** Independent geometric containment check for section-owned text. */\n fitsContainer?: boolean;\n screenBox?: { x: number; y: number; width: number; height: number };\n}\n\nexport interface RenderedFreeTextEvidence {\n objectId: string;\n kind: 'free-text' | 'stage' | 'table' | 'decor' | 'ga-label' | 'ga-capacity';\n text: string;\n visible: boolean;\n renderedFontPx: number;\n ink: string;\n background: string;\n opacity: number;\n screenBox?: { x: number; y: number; width: number; height: number };\n hiddenReason?: 'below-minimum-size' | 'outside-viewport' | 'renderer-hidden';\n}\n\nexport interface RenderedGAAreaEvidence {\n areaId: string;\n label: string;\n capacity: number;\n categoryKey: string;\n /** Owning logical section when the rendered GA surface is section-contained. */\n sectionId?: string;\n visible: boolean;\n interactive: boolean;\n opacity: number;\n fill: string;\n effectiveBackground: string;\n screenBox?: { x: number; y: number; width: number; height: number };\n}\n\nexport interface RendererQualityEvidence {\n viewport: { width: number; height: number };\n /** Runtime projection actually used for the pixels and hit graph below. */\n projection: RendererViewMode;\n /** Phase-C proof metadata. Present only in the projected-2.5D lane. */\n perspective?: {\n model: 'pinhole-exact-seat-anchors';\n sectionSurfaceModel: 'tangent-plane';\n exactSeatAnchorCount: number;\n depthSorted: true;\n };\n canvasBackground: string;\n effectiveScale: number;\n rung: LodRung;\n minimumVisibleLabelPx: number;\n totalLabelledBookableUnits: number;\n visibleLabels: number;\n hiddenLabels: number;\n /** Seats/table-seats/booths plus the full GA capacity. */\n totalBookableUnits: number;\n selectionRingSeatIds: string[];\n selectionRingColor: string;\n focusedSectionId: string | null;\n focusBackdropVisible: boolean;\n categoryFilterKeys: string[] | null;\n /** Exact scene-graph proof for the clean section-first overview contract. */\n overviewStyle: {\n visibleSectionShells: number;\n categoryPaintedSectionShells: number;\n visibleCategoryDetailOutlines: number;\n visibleSectionRowHints: number;\n visibleSectionAvailabilityLabels: number;\n visibleSectionGADetails: number;\n };\n labels: RenderedBookableLabelEvidence[];\n gaAreas: RenderedGAAreaEvidence[];\n hierarchyLabels: RenderedHierarchyLabelEvidence[];\n freeTextLabels: RenderedFreeTextEvidence[];\n}\n\nexport interface ISeatmapRenderer {\n /** Replace the chart. Resets selection and statuses, zooms to fit.\n * `opts.floorId` picks which floor to render on a multi-floor chart (Batch 5). */\n setChart(doc: ChartDoc, opts?: { floorId?: string }): void;\n /** Bulk status update; re-renders affected seats only. */\n setStatus(seatIds: string[], status: SeatStatus): void;\n /**\n * Mark the active buyer's own held seats. They remain server-status `held`,\n * but render with the buyer selection treatment instead of the anonymous\n * unavailable treatment used for another buyer's hold.\n */\n setOwnedHold?(seatIds: string[] | null): void;\n /**\n * Mark one selected seat as the buyer's pending confirmation candidate.\n * The candidate receives a strong focus halo while unrelated seats recede;\n * pass null after Select/Cancel. This is visual only and never mutates the\n * renderer selection.\n */\n setSelectionFocus?(seatId: string | null): void;\n /**\n * SYNCHRONOUS repaint that bypasses requestAnimationFrame. Konva's batchDraw()\n * (used by setStatus and friends) schedules the actual paint on the next rAF\n * tick, which Chrome throttles/pauses on hidden, backgrounded, or occluded\n * tabs — so a seat-status delta updates the scene graph but the pixels never\n * change until the tab is foregrounded again. forceDraw() paints the affected\n * layers immediately (Layer.draw() is synchronous) and flushes any pending\n * cache-debounce, so a caller (visibilitychange catch-up, or an opted-in\n * always-live board) can guarantee the canvas reflects current state\n * regardless of tab visibility. No-op difference in the foreground.\n */\n forceDraw(): void;\n getStatus(seatId: string): SeatStatus;\n getSelection(): ExpandedSeat[];\n clearSelection(): void;\n /** Update the buyer selection cap without rebuilding the chart or camera. */\n setMaxSelection?(maxSelection: number): void;\n /**\n * Programmatically restore free seats (for example an Undo action). Added\n * seats respect the active cap and do not reopen a confirmation popover.\n */\n select?(seatIds: string[]): ExpandedSeat[];\n /**\n * Dynamically update organizer-only interaction without rebuilding the\n * renderer. No buyer surface calls this; every behavior remains gated by\n * `manageMode` exactly as it is at construction time.\n */\n setManageInteraction?(options: {\n manageMode: boolean;\n marqueeSelect: boolean;\n selectableStatuses: SeatStatus[];\n maxSelection?: number;\n }): void;\n /** Organizer-only section heat overlay. Values are normalized 0..1. */\n setSectionHeat?(scores: Record<string, number> | null): void;\n /**\n * Manage-mode bulk selection helpers (no-op / empty unless `manageMode`).\n * They select the matching SELECTABLE seats (respecting `selectableStatuses`\n * + closed sections), union with the current selection, and return the seats\n * they added — the SDK SeatManager expands category/row/section picks to\n * labels and drives one batched block/unblock from them.\n */\n selectAllSelectable?(): ExpandedSeat[];\n selectByLabels?(labels: string[]): ExpandedSeat[];\n /** Exact-render QA only: select one server-chosen unit without label ambiguity. */\n setEvidenceSelection?(seatId: string): boolean;\n /** Selectable seats belonging to a section OR zone id (no selection side-effect). */\n getSelectableInSection?(sectionId: string): ExpandedSeat[];\n /** Programmatic deselect of specific seats (e.g. chip × in the cart). */\n deselect(seatIds: string[]): void;\n /**\n * Brief attention pulse on a seat (a ring that expands + fades once) — used to\n * signal live activity, e.g. a seat \"just taken\" by another buyer via a WS\n * delta. Purely visual; no state change. `color` overrides the default.\n */\n flashSeat(seatId: string, color?: string): void;\n /**\n * Brief organizer attention pulse around a whole section. This is a visual\n * overlay only: it never changes section geometry, hit targets, selection, or\n * the active camera. Useful for grouped realtime operations at venue overview.\n */\n flashSection?(sectionId: string, color?: string): void;\n zoomToFit(): void;\n /** Zoom in one step about the viewport center, clamped to the usual zoom bounds. */\n zoomIn(): void;\n /** Zoom out one step about the viewport center, clamped to the usual zoom bounds. */\n zoomOut(): void;\n /** Individually status-managed seats/table-seats/booths; excludes GA capacity. */\n seatCount(): number;\n /** Seats/table-seats/booths plus the full capacity of rendered GA areas. */\n bookableCount(): number;\n /**\n * Maps a chart-space point (or a seat, by its x/y) to container-relative\n * screen pixels, using the current stage scale/position. Lets host UI anchor\n * DOM overlays (confirm card, tooltip) over a live seat and re-anchor them\n * on `onViewChange`.\n */\n worldToScreen(point: Point): { x: number; y: number };\n /** When on, dim non-accessible free seats so accessible seats stand out. */\n setAccessibleFilter(on: boolean): void;\n /**\n * Dim free seats that lack ANY of these accessibility types. `null` clears the\n * filter; `[]` means \"any accessible seat\" (same as setAccessibleFilter(true)).\n */\n setAccessibilityFilter(types: AccessibilityType[] | null): void;\n /** Legend hover-highlight: dim free seats of other categories (null clears). */\n setCategoryHighlight?(key: string | null): void;\n /** Price-band filter (F4): dim free seats whose category is NOT in `keys`\n * (null clears). The widget resolves which categories fall in the band. */\n setCategoryFilter?(keys: string[] | null): void;\n /** Smoothly frame the currently available seats in these categories. `null`\n * returns to the full chart. Used after an explicit buyer price-filter action. */\n focusCategories?(keys: string[] | null): void;\n /** Dim the seats of these section/zone ids (organizer manager: held-back inventory). */\n setDimmedSections?(ids: string[] | null): void;\n /**\n * Phase 2 event-level section states: mark these section/zone ids `closed` —\n * flat grey block, seats greyed + not pickable, section stays rendered.\n * `null`/empty clears. (Distinct from the buyer's applyHidden seat-strip.)\n */\n setClosedSections?(ids: string[] | null): void;\n /**\n * AXS section-focus: dim + desaturate every other section, draw a calm backdrop\n * behind this section, and glide the camera to frame it. Seat-picking is gated\n * until seats are large enough on screen (≥ LABEL_SCALE). Slice 5 / Phase 2 §4.\n */\n focusSection?(id: string): void;\n /** Clear an AXS section focus (restore full-bowl brightness + drop backdrop). */\n clearSectionFocus?(): void;\n /** The currently AXS-focused section id, or null. */\n getFocusedSection?(): string | null;\n /** World-space rect currently visible in the viewport (minimap viewport frame). */\n getVisibleWorldRect?(): { x: number; y: number; width: number; height: number };\n /** Axis-aligned world bounds of all seats + section outlines (minimap frame). */\n getWorldBounds?(): { x: number; y: number; width: number; height: number };\n /**\n * Colorblind-safe mode: category hues switch to an Okabe-Ito palette and\n * booked seats render hollow (a non-color cue), so seat state never relies\n * on hue alone. Off (the default) renders exactly as before.\n */\n setColorblindSafe?(on: boolean): void;\n /**\n * Switch the projection. `'flat'` = normal top-down; `'isometric'` = the\n * legacy affine preview; `'perspective'` = projected 2.5D with exact pinhole\n * seat anchors/native hit shapes and bounded per-section tangent surfaces.\n * Purely visual — the chart is authored flat.\n */\n setViewMode?(mode: RendererViewMode): void;\n /** Current projection (defaults to 'flat' when unimplemented). */\n getViewMode?(): RendererViewMode;\n /** Multi-floor (Batch 5): switch the shown floor; list floors; read the active id. */\n setActiveFloor?(floorId: string): void;\n getFloors?(): { id: string; name: string }[];\n getActiveFloorId?(): string;\n /** Render all floors stacked (3D overview) vs the active floor. No-op single-floor. */\n setStacked?(on: boolean): void;\n isStacked?(): boolean;\n /**\n * Section id whose outline contains a container-relative screen point (or null).\n * Feeds the far-zoom \"tap a section to zoom in\" flow (Slice 5).\n */\n sectionAt?(clientPoint: Point): string | null;\n /** Seat ids belonging to a section — for the section-summary card (Slice 5). */\n sectionMembers?(id: string): string[];\n /**\n * Smoothly glide (pan+zoom) the camera to frame a section (by id) or a world-\n * space bounds rect over a calm easeInOutCubic glide. `prefers-reduced-motion` snaps.\n * A pointer-down (grab/pan) cancels an in-flight glide. Slice 5 \"glide in\".\n */\n focusRegion?(\n target: string | { x: number; y: number; width: number; height: number },\n opts?: { animate?: boolean; minScale?: number; durationMs?: number },\n ): void;\n /** Current LOD rung derived from zoom (for the ZONES/SECTIONS/SEATS pill). */\n getRung?(): LodRung;\n /** Jump the camera to a rung's zoom band, centred on the chart (glided). */\n setRung?(rung: LodRung): void;\n /** Read actual browser-rendered label visibility, size, fill, ink and state.\n * Pure diagnostic: it never changes chart or renderer state. */\n getRenderedQualityEvidence(): RendererQualityEvidence;\n destroy(): void;\n}\n\n/** localStorage key the Designer writes and the Picker reads. */\nexport const CHART_STORAGE_KEY = 'seatmap.chart';\n","/**\n * Real-world scale primitives — the ONE place the app fixes chart-unit ↔ metre ↔\n * renderer-world scale, and the section 3D-geometry resolver that rides on it.\n *\n * Everything that converts between chart units, metres, and renderer \"world\"\n * units derives from {@link METRES_PER_CHART_UNIT}. Renderer world units and\n * chart units are 1:1, so metres → world is exactly {@link CHART_UNITS_PER_METRE}\n * (the single m→world conversion constant Phase B consumers use).\n *\n * This is a leaf module (types only) so both `layout.ts` and `sections.ts` can\n * import it without the two forming an import cycle.\n */\nimport type { SectionObject } from './types';\n\n/**\n * Chart units → metres. seatSpacing 24 ≈ 0.55 m (a real seat pitch). This single\n * constant anchors every real-world scale in the app; `generatePanorama` and the\n * iso lift both derive from it instead of re-declaring their own 0.55/24.\n */\nexport const METRES_PER_CHART_UNIT = 0.55 / 24;\n\n/**\n * Metres → chart units, i.e. metres → renderer world units (world == chart units\n * in the engine). THE single m→world conversion constant: the iso view lifts an\n * elevated section by `sectionGeometry(section).height × CHART_UNITS_PER_METRE`.\n */\nexport const CHART_UNITS_PER_METRE = 1 / METRES_PER_CHART_UNIT;\n\n/**\n * Renderer world units a section lifts per {@link SectionObject.elevation} tier in\n * the legacy iso view (mirror of `SeatmapRenderer`'s old `LIFT_PER_STEP`). Kept so\n * the tier→metres fallback below reproduces today's iso look byte-for-byte.\n */\nexport const LIFT_PER_STEP_WORLD = 58;\n\n/**\n * Metres of front-edge height one elevation tier represents. Chosen (not guessed)\n * so `elevation × TIER_HEIGHT_M` metres, scaled back through\n * {@link CHART_UNITS_PER_METRE}, equals the legacy `elevation × LIFT_PER_STEP`\n * world lift exactly — an un-authored chart stays pixel-identical. ≈ 1.329 m/tier.\n */\nexport const TIER_HEIGHT_M = LIFT_PER_STEP_WORLD * METRES_PER_CHART_UNIT;\n\n/** Canonical authored section-geometry bounds. Keep every UI/API/renderer on\n * these constants so a producer cannot silently invent a second unit system. */\nexport const SECTION_ELEVATION_TIER_MIN = 0;\nexport const SECTION_ELEVATION_TIER_MAX = 3;\nexport const SECTION_HEIGHT_MIN_M = 0;\nexport const SECTION_HEIGHT_MAX_M = 120;\nexport const SECTION_RAKE_MIN_DEG = 0;\nexport const SECTION_RAKE_MAX_DEG = 45;\n\n/** Curated charts released before the height field used coarse levels 4–7.\n * Preserve their established lift while drafts/templates migrate to an explicit\n * height. Values above 7 came from broken compiler multipliers and must never be\n * interpreted as physical tiers. */\nexport const LEGACY_SECTION_ELEVATION_TIER_MAX = 7;\n\n/**\n * Seated spectator eye height above the tier floor (arena ≈ 1.20 m; theatre refs\n * use 1.15 m). Also the flat-ground baseline eye height, so a flat, ground-level\n * seat produces zero elevation offset in the 360° stage-pitch math (back-compat).\n */\nexport const SEATED_EYE_HEIGHT_M = 1.2;\n\n/**\n * Resolve a section's real 3D geometry, applying the legacy-elevation fallback so\n * old charts and new charts share ONE code path. Every consumer (iso lift, 360°\n * eye-height, author lint, any 2D depth cue) must call this and never read the raw\n * {@link SectionObject.height}/{@link SectionObject.rake} fields — that is what\n * keeps un-authored charts rendering identically to today.\n *\n * - `height`: authored absolute metres if present, else owning-floor base height\n * plus `elevation × TIER_HEIGHT_M`.\n * - `rake`: authored degrees if present, else 0 (flat).\n *\n * Malformed values are bounded here as a last defensive barrier. Structural\n * validation still reports them so drafts can be repaired instead of silently\n * persisting a renderer-only interpretation.\n *\n * Pure — no document mutation, no side effects.\n */\nexport interface SectionGeometryContext {\n /** Absolute physical height of the owning floor above the venue datum. */\n floorBaseHeightM?: number;\n}\n\nfunction finiteClamped(value: number | undefined, min: number, max: number, fallback: number): number {\n return Number.isFinite(value) ? Math.max(min, Math.min(max, value as number)) : fallback;\n}\n\n/** Canonical 0–3 tier exposed by Designer/shared operations. */\nexport function sectionElevationTier(value: number | undefined): number {\n if (!Number.isFinite(value)) return SECTION_ELEVATION_TIER_MIN;\n return Math.max(\n SECTION_ELEVATION_TIER_MIN,\n Math.min(SECTION_ELEVATION_TIER_MAX, Math.round(value as number)),\n );\n}\n\n/** Runtime-only compatibility tier. Released curated charts used integers 4–7;\n * preserve those while bounding compiler mistakes such as 12/55/220 to the\n * canonical maximum. New documents must store only {@link sectionElevationTier}. */\nfunction compatibleAutomaticTier(value: number | undefined): number {\n if (!Number.isFinite(value) || !Number.isInteger(value) || (value as number) < 0) return 0;\n if ((value as number) <= LEGACY_SECTION_ELEVATION_TIER_MAX) return value as number;\n return SECTION_ELEVATION_TIER_MAX;\n}\n\nexport function sectionGeometry(\n section: Pick<SectionObject, 'elevation' | 'height' | 'rake'>,\n context: SectionGeometryContext = {},\n): {\n height: number;\n rake: number;\n} {\n const floorBaseHeightM = finiteClamped(\n context.floorBaseHeightM,\n SECTION_HEIGHT_MIN_M,\n SECTION_HEIGHT_MAX_M,\n 0,\n );\n const automaticHeight = Math.min(\n SECTION_HEIGHT_MAX_M,\n floorBaseHeightM + compatibleAutomaticTier(section.elevation) * TIER_HEIGHT_M,\n );\n const height = section.height === undefined\n ? automaticHeight\n : finiteClamped(section.height, SECTION_HEIGHT_MIN_M, SECTION_HEIGHT_MAX_M, automaticHeight);\n const rake = finiteClamped(section.rake, SECTION_RAKE_MIN_DEG, SECTION_RAKE_MAX_DEG, 0);\n return { height, rake };\n}\n","import type { CubicPath, Point } from './types';\n\nexport type SectionShellMode = 'auto' | 'rectangle' | 'tapered' | 'bezier';\n\nexport function cubicPoint(path: CubicPath, t: number): Point {\n const u = 1 - t;\n const a = u * u * u;\n const b = 3 * u * u * t;\n const c = 3 * u * t * t;\n const d = t * t * t;\n return {\n x: a * path.start.x + b * path.control1.x + c * path.control2.x + d * path.end.x,\n y: a * path.start.y + b * path.control1.y + c * path.control2.y + d * path.end.y,\n };\n}\n\n/**\n * Build a predictable single-bend cubic whose midpoint passes exactly through\n * `middle`. At t=.5 a cubic weighs both controls by 3/8, so moving both\n * controls together by 4/3 of the requested midpoint offset makes the visible\n * curve pass through the point the author dragged to.\n *\n * Lives here rather than in the Designer because two features need the same\n * answer: the row bend handle (`renderRowPathHandles`) and the row curvature\n * operations in `controller/rowOpsFacet`.\n */\nexport function smoothCubicThroughMiddle(start: Point, end: Point, middle: Point): CubicPath {\n const chordMiddle = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 };\n const offset = {\n x: (middle.x - chordMiddle.x) * 4 / 3,\n y: (middle.y - chordMiddle.y) * 4 / 3,\n };\n return {\n start: { ...start },\n control1: {\n x: start.x + (end.x - start.x) / 3 + offset.x,\n y: start.y + (end.y - start.y) / 3 + offset.y,\n },\n control2: {\n x: start.x + ((end.x - start.x) * 2) / 3 + offset.x,\n y: start.y + ((end.y - start.y) * 2) / 3 + offset.y,\n },\n end: { ...end },\n };\n}\n\n/** Equal-arc-length points on a cubic path, including both endpoints. */\nexport function distributeAlongCubic(path: CubicPath, count: number, resolution = 192): Point[] {\n if (!Number.isInteger(count) || count < 1) throw new Error('Path point count must be a positive integer');\n if (count === 1) return [cubicPoint(path, 0.5)];\n const samples = Array.from({ length: resolution + 1 }, (_, index) => cubicPoint(path, index / resolution));\n const lengths = new Float64Array(samples.length);\n for (let index = 1; index < samples.length; index += 1) {\n const dx = samples[index].x - samples[index - 1].x;\n const dy = samples[index].y - samples[index - 1].y;\n lengths[index] = lengths[index - 1] + Math.hypot(dx, dy);\n }\n const total = lengths[lengths.length - 1];\n if (total <= 1e-9) return Array.from({ length: count }, () => ({ ...path.start }));\n const output: Point[] = [];\n let segment = 1;\n for (let index = 0; index < count; index += 1) {\n const target = (total * index) / (count - 1);\n while (segment < lengths.length - 1 && lengths[segment] < target) segment += 1;\n const before = lengths[segment - 1];\n const after = lengths[segment];\n const ratio = after === before ? 0 : (target - before) / (after - before);\n output.push({\n x: samples[segment - 1].x + (samples[segment].x - samples[segment - 1].x) * ratio,\n y: samples[segment - 1].y + (samples[segment].y - samples[segment - 1].y) * ratio,\n });\n }\n return output;\n}\n\nfunction add(a: Point, b: Point): Point { return { x: a.x + b.x, y: a.y + b.y }; }\nfunction sub(a: Point, b: Point): Point { return { x: a.x - b.x, y: a.y - b.y }; }\nfunction mul(a: Point, scale: number): Point { return { x: a.x * scale, y: a.y * scale }; }\nfunction distance(a: Point, b: Point): number { return Math.hypot(a.x - b.x, a.y - b.y); }\nfunction lerp(a: Point, b: Point, t: number): Point { return add(a, mul(sub(b, a), t)); }\n\nfunction sample(path: CubicPath, count = 13): Point[] {\n return Array.from({ length: count }, (_, index) => cubicPoint(path, index / (count - 1)));\n}\n\nfunction pointOnSampledPath(points: Point[], t: number): Point {\n const scaled = Math.max(0, Math.min(1, t)) * (points.length - 1);\n const index = Math.min(points.length - 2, Math.floor(scaled));\n return lerp(points[index], points[index + 1], scaled - index);\n}\n\nfunction sampledPathSegment(points: Point[], from: number, to: number): Point[] {\n const start = Math.max(0, Math.min(1, from));\n const end = Math.max(start, Math.min(1, to));\n const output = [pointOnSampledPath(points, start)];\n for (let index = 1; index < points.length - 1; index += 1) {\n const t = index / (points.length - 1);\n if (t > start + 1e-9 && t < end - 1e-9) output.push(points[index]);\n }\n output.push(pointOnSampledPath(points, end));\n return output;\n}\n\ninterface SectionFrame {\n p0: Point;\n p1: Point;\n p2: Point;\n p3: Point;\n normal: Point;\n depth: number;\n}\n\nfunction orientSectionFrame(corners: Point[], focal: Point): SectionFrame {\n const edges: Array<[number, number]> = [[0, 1], [1, 2], [2, 3], [3, 0]];\n let nearEdge = 0;\n let nearDistance = Number.POSITIVE_INFINITY;\n edges.forEach(([a, b], index) => {\n const d = distance(lerp(corners[a], corners[b], 0.5), focal);\n if (d < nearDistance) {\n nearDistance = d;\n nearEdge = index;\n }\n });\n const p0 = corners[nearEdge];\n const p1 = corners[(nearEdge + 1) % 4];\n const p2 = corners[(nearEdge + 2) % 4];\n const p3 = corners[(nearEdge + 3) % 4];\n const nearMid = lerp(p0, p1, 0.5);\n const inward = sub(focal, nearMid);\n const inwardLength = Math.max(1e-9, Math.hypot(inward.x, inward.y));\n return {\n p0,\n p1,\n p2,\n p3,\n normal: mul(inward, 1 / inwardLength),\n depth: (distance(p0, p3) + distance(p1, p2)) / 2,\n };\n}\n\nfunction bezierSectionPaths(frame: SectionFrame): { nearPath: CubicPath; farPath: CubicPath } {\n const { p0, p1, p2, p3, normal, depth } = frame;\n const nearSpan = sub(p1, p0);\n const farSpan = sub(p2, p3);\n const nearCurve = depth * 0.22;\n const farCurve = depth * 0.12;\n return {\n nearPath: {\n start: p0,\n control1: add(add(p0, mul(nearSpan, 1 / 3)), mul(normal, nearCurve)),\n control2: add(add(p0, mul(nearSpan, 2 / 3)), mul(normal, nearCurve)),\n end: p1,\n },\n farPath: {\n start: p2,\n control1: add(add(p3, mul(farSpan, 2 / 3)), mul(normal, farCurve)),\n control2: add(add(p3, mul(farSpan, 1 / 3)), mul(normal, farCurve)),\n end: p3,\n },\n };\n}\n\n/**\n * Convert a server-mapped rectangle into an editable complex shell. The four\n * corners must be clockwise. No caller-supplied control points are accepted.\n */\nexport function buildSectionShell(corners: Point[], focal: Point, requested: SectionShellMode): {\n mode: Exclude<SectionShellMode, 'auto'>;\n outline: Point[];\n} {\n if (corners.length !== 4) throw new Error('A section shell requires four mapped bounds corners');\n const center = corners.reduce((sum, point) => add(sum, point), { x: 0, y: 0 });\n center.x /= 4;\n center.y /= 4;\n const width = distance(corners[0], corners[1]);\n const height = distance(corners[0], corners[3]);\n const mode = requested === 'auto'\n ? (width > height * 1.25 && distance(center, focal) > height ? 'bezier' : 'tapered')\n : requested;\n if (mode === 'rectangle') return { mode, outline: corners.map((point) => ({ ...point })) };\n\n // Rotate corners so local p0→p1 is the edge nearest the focal point and\n // p3→p2 is the far edge, while preserving clockwise winding.\n const frame = orientSectionFrame(corners, focal);\n const { p0, p1, p2, p3 } = frame;\n\n if (mode === 'tapered') {\n const inset = 0.14;\n return {\n mode,\n outline: [\n add(p0, mul(sub(p1, p0), inset)),\n add(p0, mul(sub(p1, p0), 1 - inset)),\n p2,\n p3,\n ],\n };\n }\n\n const { nearPath, farPath } = bezierSectionPaths(frame);\n return { mode, outline: [...sample(nearPath), ...sample(farPath).slice(1)] };\n}\n\n/** Server-owned straight aisle cutouts spanning the near/far edges. */\nexport function buildAisleHoles(\n corners: Point[],\n focal: Point,\n count: number,\n mode: Exclude<SectionShellMode, 'auto'>,\n): Point[][] {\n if (corners.length !== 4) throw new Error('Aisle generation requires four mapped bounds corners');\n if (!Number.isInteger(count) || count < 0 || count > 3) throw new Error('Aisle count must be an integer from 0 to 3');\n if (count === 0) return [];\n const frame = orientSectionFrame(corners, focal);\n const { p0, p1, p2, p3 } = frame;\n const paths = mode === 'bezier' ? bezierSectionPaths(frame) : null;\n // For curved shells, interpolate the same sampled polylines stored in the\n // document. Exact cubic points between samples can sit fractionally outside\n // their chord approximation and fail topology validation/hit clipping.\n const nearSamples = paths ? sample(paths.nearPath) : null;\n const farLateralSamples = paths ? sample(paths.farPath).reverse() : null;\n const widthFraction = Math.min(0.055, 0.18 / (count + 1));\n return Array.from({ length: count }, (_, index) => {\n const t = (index + 1) / (count + 1);\n const left = t - widthFraction / 2;\n const right = t + widthFraction / 2;\n if (nearSamples && farLateralSamples) {\n return [\n ...sampledPathSegment(nearSamples, left, right),\n ...sampledPathSegment(farLateralSamples, left, right).reverse(),\n ];\n }\n return [lerp(p0, p1, left), lerp(p0, p1, right), lerp(p3, p2, right), lerp(p3, p2, left)];\n });\n}\n","import type { Point } from './types';\n\n/**\n * Where a label belongs inside a polygon.\n *\n * A polygon's average-of-vertices centroid is not a label anchor. Two things\n * break it on real venue geometry:\n *\n * - Concavity. An L-shaped concourse block, a C-shaped bowl tier, or a wedge\n * with an aisle cut out can have its centroid land in the notch — outside\n * the shape entirely.\n * - Vertex density. A traced contour samples a curved outer arc with dozens of\n * points and the two straight radial edges with two. The average is dragged\n * onto the arc, so the label sits hard against the boundary and its pill\n * spills into the neighbouring section.\n *\n * The right anchor is the *pole of inaccessibility*: the interior point\n * furthest from every edge — the centre of the largest circle that fits inside\n * the polygon. That is the fattest part of the shape, which is exactly where a\n * label has room. This is the Mapbox `polylabel` algorithm: subdivide the\n * bounding box into cells, keep the cell whose optimistic upper bound is best,\n * and stop once no cell can beat the incumbent by more than `precision`.\n *\n * The returned `radius` is as useful as the point: it is the half-width of the\n * room available, so a caller can shrink a label to fit — or decide it never\n * will — without measuring glyphs.\n */\nexport interface InscribedAnchor {\n /** Interior point furthest from every ring (outer boundary and holes). */\n point: Point;\n /** Distance from `point` to the nearest edge — the inscribed circle radius. */\n radius: number;\n}\n\n/** Cells are explored best-first; a hand-rolled binary heap keeps that cheap. */\ninterface Cell {\n x: number;\n y: number;\n /** Half the cell's side. */\n h: number;\n /** Signed distance from the cell centre to the polygon (positive = inside). */\n d: number;\n /** Upper bound on the distance anywhere in this cell. */\n max: number;\n}\n\n/**\n * Hard cap on explored cells. Precision alone bounds the work for a normal\n * shape, but a pathological ring (thousands of traced vertices, near-zero\n * area) could otherwise churn. A big venue runs this once per section per\n * rebuild, so the cap is what keeps a 53k-seat chart's rebuild honest.\n */\nconst MAX_CELLS = 4000;\n\nfunction distanceToSegment(p: Point, a: Point, b: Point): number {\n const dx = b.x - a.x;\n const dy = b.y - a.y;\n const denominator = dx * dx + dy * dy;\n const t = denominator\n ? Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) / denominator))\n : 0;\n return Math.hypot(p.x - (a.x + t * dx), p.y - (a.y + t * dy));\n}\n\n/** Distance from `p` to the closest edge of any ring (unsigned). */\nexport function distanceToRings(p: Point, rings: readonly (readonly Point[])[]): number {\n let best = Infinity;\n for (const ring of rings) {\n if (ring.length < 2) continue;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n const d = distanceToSegment(p, ring[j], ring[i]);\n if (d < best) best = d;\n }\n }\n return best;\n}\n\nfunction inRing(p: Point, ring: readonly Point[]): boolean {\n let inside = false;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n const xi = ring[i].x;\n const yi = ring[i].y;\n const xj = ring[j].x;\n const yj = ring[j].y;\n const hit = yi > p.y !== yj > p.y && p.x < ((xj - xi) * (p.y - yi)) / (yj - yi) + xi;\n if (hit) inside = !inside;\n }\n return inside;\n}\n\n/** Positive inside the outer ring and outside every hole; negative otherwise. */\nfunction signedDistance(\n p: Point,\n outer: readonly Point[],\n holes: readonly (readonly Point[])[],\n): number {\n const inside = inRing(p, outer) && !holes.some((hole) => inRing(p, hole));\n const distance = distanceToRings(p, [outer, ...holes]);\n return inside ? distance : -distance;\n}\n\nfunction cellAt(x: number, y: number, h: number, outer: readonly Point[], holes: readonly (readonly Point[])[]): Cell {\n const d = signedDistance({ x, y }, outer, holes);\n return { x, y, h, d, max: d + h * Math.SQRT2 };\n}\n\n/** Max-heap on `max`. Small, allocation-light, and never sorts the whole set. */\nclass CellQueue {\n private items: Cell[] = [];\n\n get size(): number {\n return this.items.length;\n }\n\n push(cell: Cell): void {\n const items = this.items;\n items.push(cell);\n let index = items.length - 1;\n while (index > 0) {\n const parent = (index - 1) >> 1;\n if (items[parent].max >= items[index].max) break;\n [items[parent], items[index]] = [items[index], items[parent]];\n index = parent;\n }\n }\n\n pop(): Cell | undefined {\n const items = this.items;\n const top = items[0];\n const last = items.pop();\n if (items.length && last) {\n items[0] = last;\n let index = 0;\n for (;;) {\n const left = index * 2 + 1;\n const right = left + 1;\n let best = index;\n if (left < items.length && items[left].max > items[best].max) best = left;\n if (right < items.length && items[right].max > items[best].max) best = right;\n if (best === index) break;\n [items[best], items[index]] = [items[index], items[best]];\n index = best;\n }\n }\n return top;\n }\n}\n\n/** Area of a ring (absolute, shoelace). */\nexport function polygonArea(ring: readonly Point[]): number {\n if (ring.length < 3) return 0;\n let sum = 0;\n for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {\n sum += (ring[j].x + ring[i].x) * (ring[j].y - ring[i].y);\n }\n return Math.abs(sum) / 2;\n}\n\n/** Net area of a polygon with holes — the space a section actually occupies. */\nexport function polygonNetArea(outer: readonly Point[], holes?: readonly (readonly Point[])[]): number {\n return Math.max(0, polygonArea(outer) - (holes ?? []).reduce((total, hole) => total + polygonArea(hole), 0));\n}\n\n/**\n * Largest-inscribed-circle anchor for a polygon (with optional holes).\n *\n * Deterministic: no randomness, no dependence on vertex order or winding, so\n * the same geometry always produces the same anchor. Callers can therefore\n * memoize on geometry alone.\n */\nexport function polygonInscribedAnchor(\n outer: readonly Point[],\n holes?: readonly (readonly Point[])[],\n): InscribedAnchor {\n const rings = holes ?? [];\n if (!outer.length) return { point: { x: 0, y: 0 }, radius: 0 };\n if (outer.length < 3) {\n const x = outer.reduce((total, p) => total + p.x, 0) / outer.length;\n const y = outer.reduce((total, p) => total + p.y, 0) / outer.length;\n return { point: { x, y }, radius: 0 };\n }\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (const p of outer) {\n if (p.x < minX) minX = p.x;\n if (p.y < minY) minY = p.y;\n if (p.x > maxX) maxX = p.x;\n if (p.y > maxY) maxY = p.y;\n }\n const width = maxX - minX;\n const height = maxY - minY;\n const cellSize = Math.min(width, height);\n if (!(cellSize > 0)) {\n return { point: { x: (minX + maxX) / 2, y: (minY + maxY) / 2 }, radius: 0 };\n }\n // Relative precision: a metre-scale room and a stadium both get ~the same\n // number of refinement steps, so cost does not depend on the unit system.\n // 1/150 of the long edge is well inside the sub-pixel band a label anchor can\n // express, and each extra digit of precision costs a full 4-way subdivision\n // level — measured over 300 dense traced shells, 1/500 cost 470 ms per\n // rebuild against 1/150's 163 ms, for an anchor no human could tell apart\n // (both stay within 5% of an exhaustive grid search's inscribed radius).\n const precision = Math.max(width, height) / 150;\n\n const queue = new CellQueue();\n let h = cellSize / 2;\n for (let x = minX; x < maxX; x += cellSize) {\n for (let y = minY; y < maxY; y += cellSize) {\n queue.push(cellAt(x + h, y + h, h, outer, rings));\n }\n }\n\n // Seed with the bbox centre; a convex-ish shape often stops right here.\n let best = cellAt(minX + width / 2, minY + height / 2, 0, outer, rings);\n let explored = 0;\n while (queue.size) {\n const cell = queue.pop();\n if (!cell) break;\n if (cell.d > best.d) best = cell;\n if (cell.max - best.d <= precision) continue;\n if (explored >= MAX_CELLS) continue;\n explored += 1;\n h = cell.h / 2;\n queue.push(cellAt(cell.x - h, cell.y - h, h, outer, rings));\n queue.push(cellAt(cell.x + h, cell.y - h, h, outer, rings));\n queue.push(cellAt(cell.x - h, cell.y + h, h, outer, rings));\n queue.push(cellAt(cell.x + h, cell.y + h, h, outer, rings));\n }\n return { point: { x: best.x, y: best.y }, radius: Math.max(0, best.d) };\n}\n","import type { Point, SectionOutlinePath, SectionPathSegment } from './types';\n\nconst TAU = Math.PI * 2;\n\nfunction finitePoint(point: Point): boolean {\n return Number.isFinite(point.x) && Number.isFinite(point.y);\n}\n\nfunction distance(left: Point, right: Point): number {\n return Math.hypot(left.x - right.x, left.y - right.y);\n}\n\nfunction arcSweep(start: number, end: number, clockwise: boolean): number {\n let delta = end - start;\n if (clockwise) {\n while (delta < 0) delta += TAU;\n while (delta >= TAU) delta -= TAU;\n } else {\n while (delta > 0) delta -= TAU;\n while (delta <= -TAU) delta += TAU;\n }\n return delta;\n}\n\nexport function sectionPathSegmentEnd(segment: SectionPathSegment): Point {\n return segment.end;\n}\n\n/** Validate finite, continuous closed-path geometry without trusting renderer\n * behaviour. Arc endpoints must lie on their declared circle. */\nexport function validSectionOutlinePath(path: SectionOutlinePath): boolean {\n if (path.version !== 1 || path.closed !== true || !finitePoint(path.start)\n || !Array.isArray(path.segments) || path.segments.length < 2 || path.segments.length > 512) return false;\n let current = path.start;\n for (const segment of path.segments) {\n if (!segment || !finitePoint(segment.end)) return false;\n if (segment.kind === 'line') {\n if (distance(current, segment.end) <= 1e-9) return false;\n } else if (segment.kind === 'bezier') {\n if (!finitePoint(segment.control1) || !finitePoint(segment.control2)\n || distance(current, segment.end) <= 1e-9) return false;\n } else if (segment.kind === 'arc') {\n if (!finitePoint(segment.center) || !Number.isFinite(segment.radius) || segment.radius <= 1e-6\n || Math.abs(distance(current, segment.center) - segment.radius) > Math.max(1e-4, segment.radius * 1e-4)\n || Math.abs(distance(segment.end, segment.center) - segment.radius) > Math.max(1e-4, segment.radius * 1e-4)\n || distance(current, segment.end) <= 1e-9) return false;\n } else return false;\n current = segment.end;\n }\n return distance(current, path.start) <= 1e-4;\n}\n\nfunction cubicPoint(start: Point, segment: Extract<SectionPathSegment, { kind: 'bezier' }>, t: number): Point {\n const mt = 1 - t;\n return {\n x: mt ** 3 * start.x + 3 * mt ** 2 * t * segment.control1.x\n + 3 * mt * t ** 2 * segment.control2.x + t ** 3 * segment.end.x,\n y: mt ** 3 * start.y + 3 * mt ** 2 * t * segment.control1.y\n + 3 * mt * t ** 2 * segment.control2.y + t ** 3 * segment.end.y,\n };\n}\n\n/** Deterministic polygon sample used by validation, hit testing, diff, and old\n * clients. The returned loop omits the duplicate closing point. */\nexport function sampleSectionOutlinePath(path: SectionOutlinePath, maxStep = 2): Point[] {\n if (!validSectionOutlinePath(path)) throw new Error('Section outline path is invalid');\n const step = Math.max(0.25, maxStep);\n const points: Point[] = [{ ...path.start }];\n let current = path.start;\n for (const segment of path.segments) {\n if (segment.kind === 'line') {\n points.push({ ...segment.end });\n } else if (segment.kind === 'arc') {\n const startAngle = Math.atan2(current.y - segment.center.y, current.x - segment.center.x);\n const endAngle = Math.atan2(segment.end.y - segment.center.y, segment.end.x - segment.center.x);\n const sweep = arcSweep(startAngle, endAngle, segment.clockwise);\n const count = Math.max(2, Math.ceil(Math.abs(sweep) * segment.radius / step));\n for (let index = 1; index <= count; index += 1) {\n const angle = startAngle + sweep * index / count;\n points.push({\n x: segment.center.x + Math.cos(angle) * segment.radius,\n y: segment.center.y + Math.sin(angle) * segment.radius,\n });\n }\n } else {\n const controlLength = distance(current, segment.control1)\n + distance(segment.control1, segment.control2)\n + distance(segment.control2, segment.end);\n const count = Math.max(2, Math.ceil(controlLength / step));\n for (let index = 1; index <= count; index += 1) points.push(cubicPoint(current, segment, index / count));\n }\n current = segment.end;\n }\n if (distance(points[points.length - 1], points[0]) <= 1e-4) points.pop();\n return points;\n}\n\nexport function translateSectionOutlinePath(path: SectionOutlinePath, dx: number, dy: number): SectionOutlinePath {\n const translate = (point: Point): Point => ({ x: point.x + dx, y: point.y + dy });\n return transformSectionOutlinePath(path, translate);\n}\n\n/** Apply a similarity transform. Callers provide the point transform and the\n * absolute radius scale; reflections toggle arc direction. */\nexport function transformSectionOutlinePath(\n path: SectionOutlinePath,\n transform: (point: Point) => Point,\n radiusScale = 1,\n reflected = false,\n): SectionOutlinePath {\n return {\n ...path,\n start: transform(path.start),\n segments: path.segments.map((segment) => segment.kind === 'line'\n ? { ...segment, end: transform(segment.end) }\n : segment.kind === 'arc'\n ? {\n ...segment,\n center: transform(segment.center),\n radius: segment.radius * Math.abs(radiusScale),\n clockwise: reflected ? !segment.clockwise : segment.clockwise,\n end: transform(segment.end),\n }\n : {\n ...segment,\n control1: transform(segment.control1),\n control2: transform(segment.control2),\n end: transform(segment.end),\n }),\n };\n}\n\n/** Default corner-smoothing strength applied automatically when a hand-clicked\n * polygon section is closed. Wide, gently-angled corners round; sharp structural\n * corners stay crisp, so a rectangle drawn as a polygon is left untouched. */\nexport const DEFAULT_SECTION_CORNER_SMOOTHING = 45;\n\n/** Only corners at least this wide (interior angle, degrees) are rounded — the\n * arc-approximating points of a hand-traced wedge, not its true corners. */\nconst WIDE_ANGLE_THRESHOLD_DEG = 150;\n\n/** Circle-approximation constant: control-point pull from a cut point toward the\n * original vertex for a smooth cubic corner. */\nconst CORNER_KAPPA = 0.5523;\n\nfunction polygonPointsEqual(a: Point, b: Point): boolean {\n return Math.abs(a.x - b.x) <= 1e-6 && Math.abs(a.y - b.y) <= 1e-6;\n}\n\n/**\n * Dedupe, but also report which ORIGINAL index each surviving vertex came from.\n *\n * Per-edge curvature is indexed against the polygon the author clicked. Deduping\n * silently renumbers edges, so anything carrying per-edge data must remap through\n * `sourceIndex` rather than assuming the arrays still line up — otherwise a single\n * duplicate click shifts every curve onto the wrong edge.\n */\nfunction dedupeClosedPolygonIndexed(points: Point[]): { points: Point[]; sourceIndex: number[] } {\n const out: Point[] = [];\n const sourceIndex: number[] = [];\n points.forEach((point, index) => {\n if (!finitePoint(point)) return;\n if (out.length && polygonPointsEqual(out[out.length - 1], point)) return;\n out.push({ x: point.x, y: point.y });\n sourceIndex.push(index);\n });\n while (out.length > 1 && polygonPointsEqual(out[0], out[out.length - 1])) {\n out.pop();\n sourceIndex.pop();\n }\n return { points: out, sourceIndex };\n}\n\nfunction lerp(a: Point, b: Point, t: number): Point {\n return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };\n}\n\nfunction smoothstep(t: number): number {\n const c = Math.min(1, Math.max(0, t));\n return c * c * (3 - 2 * c);\n}\n\n/**\n * Build a smooth closed {@link SectionOutlinePath} by rounding the wide corners\n * of a hand-clicked polygon. The polygon stays authoritative — this is a purely\n * additive rendered curve. Returns `null` when nothing qualifies (strength 0, a\n * degenerate polygon, or no corner wide enough), letting the caller keep the raw\n * straight polygon. Deterministic: same input always yields the same path.\n */\nexport function sectionCornerSmoothingPath(\n polygon: Point[],\n strength: number,\n options?: { wideAngleThresholdDeg?: number },\n): SectionOutlinePath | null {\n return sectionOutlinePathFrom(polygon, { cornerSmoothing: strength, ...options });\n}\n\n/**\n * Largest bow, as a fraction of the edge's own length, at curvature ±100. Half\n * the chord reads as a strong arc while staying well short of a shape that folds\n * back on itself.\n */\nexport const MAX_EDGE_SAGITTA_RATIO = 0.5;\n\n/**\n * Cubic control offset that makes the drawn curve pass exactly through the\n * requested midpoint bulge. A cubic whose two controls are both offset by `d`\n * perpendicular (at ⅓ and ⅔ along the chord) passes through 0.75·d at t=0.5, so\n * the offset must be 4/3 of the wanted sagitta. Same constant the designer's\n * bend handle already uses, so a dragged curve lands where the cursor was.\n */\nconst SAGITTA_TO_CONTROL = 4 / 3;\n\nexport interface SectionOutlineDerivation {\n /** 0–100. Rounds corners wider than the threshold. */\n cornerSmoothing?: number;\n /**\n * One entry per edge of the clicked polygon, -100..100, 0 = straight. Edge `i`\n * runs from vertex `i` to vertex `i+1`. Positive bows to the LEFT of that\n * direction; the sign is relative to edge direction, never to world axes, so\n * curvature survives rotate / flip / mirror untouched.\n */\n edgeCurvature?: readonly number[];\n wideAngleThresholdDeg?: number;\n}\n\n/** Bow a straight run into a cubic that bulges `sagitta` at its midpoint. */\nfunction bowedSegment(from: Point, to: Point, sagitta: number): SectionPathSegment {\n const dx = to.x - from.x;\n const dy = to.y - from.y;\n const length = Math.hypot(dx, dy);\n if (length <= 1e-9) return { kind: 'line', end: { ...to } };\n // Unit normal, left of travel.\n const nx = -dy / length;\n const ny = dx / length;\n const offset = sagitta * SAGITTA_TO_CONTROL;\n return {\n kind: 'bezier',\n control1: { x: from.x + dx / 3 + nx * offset, y: from.y + dy / 3 + ny * offset },\n control2: { x: from.x + (dx * 2) / 3 + nx * offset, y: from.y + (dy * 2) / 3 + ny * offset },\n end: { ...to },\n };\n}\n\n/**\n * Derive the rendered outline from the clicked polygon plus its coordinate-free\n * shaping (corner smoothing and per-edge curvature).\n *\n * The polygon stays authoritative — this is purely a rendered curve, so zeroing\n * the shaping restores the exact clicked shape. Deterministic: same input always\n * yields the same path. Returns `null` when there is nothing to draw beyond the\n * raw polygon, letting the caller keep the straight outline.\n */\nexport function sectionOutlinePathFrom(\n polygon: Point[],\n derivation: SectionOutlineDerivation,\n): SectionOutlinePath | null {\n const { points: pts, sourceIndex } = dedupeClosedPolygonIndexed(polygon);\n const n = pts.length;\n if (n < 3) return null;\n const s = Math.min(100, Math.max(0, derivation.cornerSmoothing ?? 0)) / 100;\n\n // Remap curvature onto the deduped ring: surviving vertex k began life at\n // sourceIndex[k], so the edge leaving it is the source edge of that index.\n const rawCurvature = derivation.edgeCurvature;\n const curvature: number[] = new Array(n).fill(0);\n if (rawCurvature?.length) {\n for (let i = 0; i < n; i += 1) {\n const value = rawCurvature[sourceIndex[i]];\n curvature[i] = Number.isFinite(value) ? Math.min(100, Math.max(-100, value)) / 100 : 0;\n }\n }\n const anyCurved = curvature.some((c) => Math.abs(c) > 1e-6);\n if (s <= 0 && !anyCurved) return null;\n const threshold = derivation.wideAngleThresholdDeg ?? WIDE_ANGLE_THRESHOLD_DEG;\n\n const edgeLen: number[] = [];\n for (let i = 0; i < n; i += 1) edgeLen[i] = distance(pts[i], pts[(i + 1) % n]);\n\n // Per-vertex corner cut distance. Wide (near-straight) corners round most;\n // corners sharper than the threshold are left untouched.\n const cut: number[] = new Array(n).fill(0);\n for (let i = 0; i < n; i += 1) {\n const prev = pts[(i - 1 + n) % n];\n const curr = pts[i];\n const next = pts[(i + 1) % n];\n const toPrev = { x: prev.x - curr.x, y: prev.y - curr.y };\n const toNext = { x: next.x - curr.x, y: next.y - curr.y };\n const lenPrev = Math.hypot(toPrev.x, toPrev.y);\n const lenNext = Math.hypot(toNext.x, toNext.y);\n if (lenPrev <= 1e-9 || lenNext <= 1e-9) continue;\n const cos = Math.min(1, Math.max(-1, (toPrev.x * toNext.x + toPrev.y * toNext.y) / (lenPrev * lenNext)));\n const interiorDeg = (Math.acos(cos) * 180) / Math.PI;\n if (interiorDeg < threshold) continue; // sharp structural corner — keep crisp\n const weight = smoothstep((interiorDeg - threshold) / (180 - threshold));\n cut[i] = s * weight * 0.5 * Math.min(lenPrev, lenNext);\n }\n\n // Never let two neighbouring cuts overrun a shared edge.\n for (let i = 0; i < n; i += 1) {\n const j = (i + 1) % n;\n const total = cut[i] + cut[j];\n if (total > edgeLen[i] && total > 1e-9) {\n const scale = edgeLen[i] / total;\n cut[i] *= scale;\n cut[j] *= scale;\n }\n }\n\n const entry: Point[] = new Array(n); // point on the incoming edge, before the vertex\n const exit: Point[] = new Array(n); // point on the outgoing edge, after the vertex\n for (let i = 0; i < n; i += 1) {\n const prev = pts[(i - 1 + n) % n];\n const curr = pts[i];\n const next = pts[(i + 1) % n];\n if (cut[i] <= 1e-6) {\n entry[i] = { ...curr };\n exit[i] = { ...curr };\n continue;\n }\n const lenPrev = distance(curr, prev) || 1;\n const lenNext = distance(curr, next) || 1;\n entry[i] = { x: curr.x + ((prev.x - curr.x) / lenPrev) * cut[i], y: curr.y + ((prev.y - curr.y) / lenPrev) * cut[i] };\n exit[i] = { x: curr.x + ((next.x - curr.x) / lenNext) * cut[i], y: curr.y + ((next.y - curr.y) / lenNext) * cut[i] };\n }\n\n // Nothing rounded AND nothing bowed → let the caller keep the straight polygon.\n if (cut.every((c) => c <= 1e-6) && !anyCurved) return null;\n\n const segments: SectionPathSegment[] = [];\n const start = { ...exit[0] };\n let current = start;\n // `edgeIndex` is the edge being traversed: the run into vertex `i` came along\n // the edge that leaves vertex `i-1`.\n const pushEdge = (end: Point, edgeIndex: number) => {\n if (distance(current, end) <= 1e-9) return;\n const bend = curvature[edgeIndex];\n if (Math.abs(bend) > 1e-6) {\n // Sagitta is measured against the FULL edge, not the shortened run left\n // between two rounded corners, so rounding a corner cannot flatten the\n // curve the author asked for.\n segments.push(bowedSegment(current, end, bend * MAX_EDGE_SAGITTA_RATIO * edgeLen[edgeIndex]));\n } else {\n segments.push({ kind: 'line', end: { ...end } });\n }\n current = end;\n };\n for (let step = 1; step <= n; step += 1) {\n const i = step % n;\n const vertex = pts[i];\n // Run along the edge from the previous exit to this vertex's entry.\n pushEdge(entry[i], (step - 1) % n);\n if (cut[i] > 1e-6 && distance(entry[i], exit[i]) > 1e-9) {\n // Rounded corner: a cubic pulling toward the original vertex.\n segments.push({\n kind: 'bezier',\n control1: lerp(entry[i], vertex, CORNER_KAPPA),\n control2: lerp(exit[i], vertex, CORNER_KAPPA),\n end: { ...exit[i] },\n });\n current = exit[i];\n }\n }\n if (!segments.length) return null;\n // Guarantee exact closure onto the start point.\n const last = segments[segments.length - 1];\n if (distance(last.end, start) > 1e-9) {\n if (last.kind === 'line') last.end = { ...start };\n else segments.push({ kind: 'line', end: { ...start } });\n }\n const path: SectionOutlinePath = { version: 1, closed: true, start, segments };\n return validSectionOutlinePath(path) ? path : null;\n}\n\n/** Reverse a closed path without changing its painted boundary. */\nexport function reverseSectionOutlinePath(path: SectionOutlinePath): SectionOutlinePath {\n const starts: Point[] = [];\n let current = path.start;\n for (const segment of path.segments) {\n starts.push(current);\n current = segment.end;\n }\n const segments: SectionPathSegment[] = [];\n for (let index = path.segments.length - 1; index >= 0; index -= 1) {\n const segment = path.segments[index];\n const end = starts[index];\n if (segment.kind === 'line') segments.push({ kind: 'line', end: { ...end } });\n else if (segment.kind === 'arc') segments.push({\n kind: 'arc', center: { ...segment.center }, radius: segment.radius,\n clockwise: !segment.clockwise, end: { ...end },\n });\n else segments.push({\n kind: 'bezier', control1: { ...segment.control2 }, control2: { ...segment.control1 }, end: { ...end },\n });\n }\n return { version: 1, closed: true, start: { ...path.start }, segments };\n}\n","/**\n * The segmented-row model — one logical row made of several physical rows.\n *\n * A segmented row is drawn as a chain of nodes and stored as N physical\n * `RowObject`s tied together by `segmentedRow` metadata. Two questions have to\n * be answered consistently or the feature lies to somebody:\n *\n * 1. **At draft time** — given the author's nodes, exactly which components,\n * seat counts and pitches will be committed? (`segmentedRowComponents`)\n * 2. **At read time** — given stored physical rows, what is the logical row they\n * form, and where does each component sit inside its numbering?\n * (`resolveSegmentedRowGroups`)\n *\n * These lived apart — (1) in `rowDraft.ts` next to the ghost preview, (2) in\n * `layout.ts` next to buyer expansion — which is precisely how the canvas came\n * to number a segmented row `1-7` / `1-6` while buyers correctly saw\n * `A-1…A-13`. They are two halves of one rule, so they live in one module.\n *\n * The standing rule this module exists to enforce: **the ghost preview and the\n * commit share one resolver.** A preview that can lie is a bug.\n */\nimport type { ChartObject, Point, RowObject } from './types';\n\n// ---------------------------------------------------------------------------\n// Draft time — nodes to components\n// ---------------------------------------------------------------------------\n\n/** One physical component of a segmented (node-drawn) row. */\nexport interface SegmentedRowComponent {\n origin: Point;\n /** Bearing in degrees, matching RowObject.rotation. */\n rotation: number;\n seatCount: number;\n /** Pitch is per-component: each span divides evenly into whole seats. */\n seatSpacing: number;\n}\n\n/**\n * Resolve node points into the exact components `finishSegmentedRowDrawing`\n * commits, so the ghost preview and the committed rows always agree.\n *\n * `boundaries[i]` describes the junction BEFORE component i+1. The first\n * component owns the first node's seat; every later component starts one pitch\n * after its junction so a continuous bend never double-books a seat, and a\n * declared break skips two pitches to leave a visible aisle.\n */\nexport function segmentedRowComponents(\n points: readonly Point[],\n boundaries: readonly ('continuous' | 'break')[],\n seatSpacing: number,\n): SegmentedRowComponent[] {\n const components: SegmentedRowComponent[] = [];\n for (let index = 0; index < points.length - 1; index += 1) {\n const start = points[index];\n const end = points[index + 1];\n const distance = Math.hypot(end.x - start.x, end.y - start.y);\n if (distance <= 0) continue;\n const pitchCount = Math.max(1, Math.round(distance / seatSpacing));\n const pitch = distance / pitchCount;\n const angle = Math.atan2(end.y - start.y, end.x - start.x);\n const firstComponent = index === 0;\n const boundaryBefore = index > 0 ? boundaries[index - 1] : undefined;\n const skippedPitches = boundaryBefore === 'break' ? 2 : 1;\n const origin = firstComponent\n ? { ...start }\n : {\n x: start.x + Math.cos(angle) * pitch * skippedPitches,\n y: start.y + Math.sin(angle) * pitch * skippedPitches,\n };\n const seatCount = firstComponent ? pitchCount + 1 : Math.max(1, pitchCount - (skippedPitches - 1));\n components.push({ origin, rotation: (angle * 180) / Math.PI, seatCount, seatSpacing: pitch });\n }\n return components;\n}\n\n/** Seat centres for a resolved component, in seat order. */\nexport function segmentedRowComponentSeats(component: SegmentedRowComponent): Point[] {\n const rad = (component.rotation * Math.PI) / 180;\n const axis = { x: Math.cos(rad), y: Math.sin(rad) };\n return Array.from({ length: component.seatCount }, (_, index) => ({\n x: component.origin.x + axis.x * index * component.seatSpacing,\n y: component.origin.y + axis.y * index * component.seatSpacing,\n }));\n}\n\n/** Total seats a segmented draft would commit — the number the badge shows. */\nexport function segmentedRowSeatTotal(components: readonly SegmentedRowComponent[]): number {\n return components.reduce((total, component) => total + component.seatCount, 0);\n}\n\n// ---------------------------------------------------------------------------\n// Read time — physical rows to the logical row\n// ---------------------------------------------------------------------------\n\n/** Resolved logical placement of one physical component inside a segmented row. */\nexport interface SegmentedRowPlacement {\n groupId: string;\n /** Buyer adjacency index base (a declared aisle break costs one index). */\n adjacencyOffset: number;\n /** Continuous seat-numbering base across the whole logical row. */\n displayOffset: number;\n displayLabel: string;\n totalSeats: number;\n canonical: RowObject;\n viewFromSeatUrl?: string;\n}\n\n/**\n * Group physical rows into their logical segmented rows, keyed by physical row id.\n *\n * Resolves only complete, internally coherent groups. Malformed metadata is\n * surfaced by validation and deliberately falls back to physical-row semantics,\n * so a corrupt document can never make buyer adjacency more permissive than the\n * legacy model.\n *\n * Shared by buyer expansion AND the Designer canvas so a segmented row cannot be\n * numbered one way for buyers and another way for the author.\n */\nexport function resolveSegmentedRowGroups(\n objects: readonly ChartObject[],\n): Map<string, SegmentedRowPlacement> {\n const segmented = new Map<string, SegmentedRowPlacement>();\n const grouped = new Map<string, RowObject[]>();\n for (const object of objects) {\n if (object.type !== 'row' || !object.segmentedRow) continue;\n const list = grouped.get(object.segmentedRow.groupId) ?? [];\n list.push(object);\n grouped.set(object.segmentedRow.groupId, list);\n }\n for (const [groupId, members] of grouped) {\n const ordered = members.slice().sort((left, right) => (\n left.segmentedRow!.componentIndex - right.segmentedRow!.componentIndex\n ));\n const expectedCount = ordered[0]?.segmentedRow?.componentCount ?? 0;\n const first = ordered[0]?.segmentedRow;\n if (!first) continue;\n const valid = expectedCount >= 2\n && ordered.length === expectedCount\n && first?.boundaryBefore === 'start'\n && ordered.every((row, index) => (\n row.segmentedRow?.kind === 'segmented-row-v1'\n && row.segmentedRow.groupId === groupId\n && row.segmentedRow.componentCount === expectedCount\n && row.segmentedRow.componentIndex === index\n && (index === 0\n ? row.segmentedRow.boundaryBefore === 'start'\n : row.segmentedRow.boundaryBefore !== 'start')\n && row.segmentedRow.displayLabel === first.displayLabel\n ));\n if (!valid) continue;\n const totalSeats = ordered.reduce((sum, row) => sum + row.seatCount, 0);\n let adjacencyOffset = 0;\n let displayOffset = 0;\n for (const row of ordered) {\n if (row.segmentedRow!.boundaryBefore === 'break') adjacencyOffset += 1;\n segmented.set(row.id, {\n groupId,\n adjacencyOffset,\n displayOffset,\n displayLabel: first.displayLabel,\n totalSeats,\n canonical: ordered[0],\n viewFromSeatUrl: first.viewFromSeatUrl,\n });\n adjacencyOffset += row.seatCount;\n displayOffset += row.seatCount;\n }\n }\n return segmented;\n}\n","/**\n * Pure row/seat labeling helpers used by the designer's \"relabel\" tools\n * (e.g. bulk-renumber rows A, B, C… or 1, 2, 3…). No dependencies.\n */\n\nexport interface RelabelOptions {\n scheme: 'letters' | 'numbers';\n /** 1-based start: letters 1=A; numbers 1=1. */\n startAt: number;\n /** Letters only: skip I and O (common theatre convention). */\n skipAmbiguous?: boolean;\n /** Apply labels in reverse order over the given array. */\n reverse?: boolean;\n}\n\nconst FULL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';\nconst SAFE_ALPHABET = 'ABCDEFGHJKLMNPQRSTUVWXYZ'; // no I, no O\n\nconst LOWER_ALPHABET = 'abcdefghijklmnopqrstuvwxyz';\n\n/**\n * Bijective base-N conversion: 0 -> first letter, N -> second letter's\n * first repeat cycle (e.g. base26: 25 -> 'Z', 26 -> 'AA').\n */\nexport function toBijectiveBase(index: number, alphabet: string): string {\n const base = alphabet.length;\n let n = index + 1; // bijective numeration is 1-based\n let out = '';\n while (n > 0) {\n n -= 1;\n const rem = n % base;\n out = alphabet[rem] + out;\n n = Math.floor(n / base);\n }\n return out;\n}\n\nexport function indexLabel(i: number, opts: RelabelOptions): string {\n const start = opts.startAt - 1; // convert 1-based startAt to 0-based offset\n const idx = i + start;\n if (opts.scheme === 'numbers') {\n return String(idx + 1);\n }\n const alphabet = opts.skipAmbiguous ? SAFE_ALPHABET : FULL_ALPHABET;\n return toBijectiveBase(idx, alphabet);\n}\n\n/**\n * Alphabetic label for a 1-based value (1 -> 'A', 26 -> 'Z', 27 -> 'AA').\n * Uppercase by default; `lower` gives the a,b,c… variant. Values ≤ 0 clamp to 'A'.\n */\nexport function toLetters(value: number, lower = false): string {\n const alphabet = lower ? LOWER_ALPHABET : FULL_ALPHABET;\n return toBijectiveBase(Math.max(0, Math.floor(value) - 1), alphabet);\n}\n\nconst ROMAN_TABLE: Array<[number, string]> = [\n [1000, 'M'], [900, 'CM'], [500, 'D'], [400, 'CD'], [100, 'C'], [90, 'XC'],\n [50, 'L'], [40, 'XL'], [10, 'X'], [9, 'IX'], [5, 'V'], [4, 'IV'], [1, 'I'],\n];\n\n/** Uppercase Roman numeral for a positive integer (1 -> 'I', 4 -> 'IV', 9 -> 'IX').\n * Non-positive / non-finite inputs fall back to their decimal string. */\nexport function toRoman(value: number): string {\n if (!Number.isFinite(value) || value <= 0) return String(value);\n let remaining = Math.floor(value);\n let out = '';\n for (const [n, sym] of ROMAN_TABLE) {\n while (remaining >= n) {\n out += sym;\n remaining -= n;\n }\n }\n return out;\n}\n\nexport function relabelRows<T extends { label: string }>(rows: T[], opts: RelabelOptions): T[] {\n const n = rows.length;\n return rows.map((row, i) => {\n const assignIndex = opts.reverse ? n - 1 - i : i;\n return { ...row, label: indexLabel(assignIndex, opts) };\n });\n}\n","/**\n * Seat numbering — and the one distinction the whole feature turns on.\n *\n * Every seat carries TWO names and they are not interchangeable:\n *\n * - **Inventory label** (`label`) is the seat's booking identity. It is derived\n * from the PHYSICAL row that stores the seat and must never move because rows\n * were grouped, renumbered for display, or re-segmented. A ticket, a hold and\n * a report all key off this.\n * - **Display label** (`displayLabel`) is what a human reads. For a segmented\n * row it follows the LOGICAL row: components number continuously\n * (`A-1…A-13`), not per physical component (`A-1…A-7`, then `A-1…A-6`).\n *\n * Getting this split wrong is not cosmetic and it already happened: the data was\n * correct — buyers saw `A-1…A-13` — but the Designer canvas built its labels\n * from the physical row and showed `1-7` / `1-6`. Two surfaces disagreed about\n * the same seats, which is the fastest way to lose trust in correct data.\n *\n * The split survived only because each surface reconstructed the logical\n * numbering row by hand. `logicalNumberingRow` is that construction, named once,\n * so canvas, buyer expansion and MCP cannot drift apart again.\n */\nimport { toLetters, toRoman } from './labeling';\nimport type { RowObject, SeatOverride } from './types';\nimport type { SegmentedRowPlacement } from './segmentedRowModel';\n\n/**\n * Number outward from the middle: rank seats by distance from centre (inner-left\n * wins ties), so the centre seat gets rank 0 (the lowest number). Shared by the\n * `center` direction across every scheme.\n */\nfunction centerRank(n: number): number[] {\n const rank = new Array<number>(n);\n Array.from({ length: n }, (_, i) => i)\n .sort((a, b) => Math.abs(2 * a - (n - 1)) - Math.abs(2 * b - (n - 1)) || a - b)\n .forEach((idx, k) => (rank[idx] = k));\n return rank;\n}\n\n/**\n * The seat NUMBER part of a row seat's label (the row prefix is prepended by the\n * caller). Applies the row's numbering scheme, direction, step, start and label\n * prefix. Labels only — never geometry. See `RowObject.seatNumbering.scheme`.\n */\nexport function seatLabelPart(row: RowObject, i: number): string {\n const rawStart = row.seatLabelStart ?? 1;\n const dir = row.seatNumbering?.direction ?? 'ltr';\n const step = row.seatNumbering?.step ?? 1;\n const scheme = row.seatNumbering?.scheme ?? 'decimal';\n const prefix = row.seatNumbering?.prefix ?? '';\n const endAt = row.seatNumbering?.endAt;\n const n = row.seatCount;\n\n // Both up/down variants replace direction and number by physical left→right\n // order. `updown` is odd-up-even-back (1,3,5,…,6,4,2); the distinct reverse\n // variant is odd-back-even-up (…5,3,1,2,4,6). `start` shifts either sequence.\n // They own their sequence, so both ignore `endAt`.\n if (scheme === 'updown' || scheme === 'updown-descending') {\n const half = Math.ceil(n / 2);\n const core = scheme === 'updown'\n ? (i < half ? rawStart + 2 * i : rawStart - 1 + 2 * (n - i))\n : (i < half ? rawStart + 2 * (half - 1 - i) : rawStart + 1 + 2 * (i - half));\n return `${prefix}${core}`;\n }\n\n // End-at preset (\"useEndAt\"): derive `start` so the LAST-numbered seat\n // (position rank n-1) lands on `endAt`, honouring the scheme's effective step\n // (odd/even = 2). `endAt` wins over the stored `seatLabelStart`.\n const effStep = scheme === 'odd' || scheme === 'even' ? 2 : step;\n const start = endAt != null && Number.isFinite(endAt) ? endAt - (n - 1) * effStep : rawStart;\n\n // Position rank p ∈ [0, n-1]: the 0-based ordinal along the numbering\n // direction. Every remaining scheme is a formatting of `start + p*step`.\n const p = dir === 'center' ? centerRank(n)[i] : dir === 'rtl' ? n - 1 - i : i;\n\n let core: string;\n switch (scheme) {\n case 'odd': {\n const firstOdd = start % 2 === 1 ? start : start + 1;\n core = String(firstOdd + p * 2);\n break;\n }\n case 'even': {\n const firstEven = start % 2 === 0 ? start : start + 1;\n core = String(firstEven + p * 2);\n break;\n }\n case 'roman':\n core = toRoman(start + p * step);\n break;\n case 'letters-upper':\n core = toLetters(start + p * step, false);\n break;\n case 'letters-lower':\n core = toLetters(start + p * step, true);\n break;\n case 'decimal':\n default:\n core = String(start + p * step);\n break;\n }\n return `${prefix}${core}`;\n}\n\n/**\n * The synthetic row that DISPLAY numbering is computed against.\n *\n * A segmented component must number as though it were the whole logical row:\n * the logical row's total seat count and label, so that scheme, direction,\n * `endAt` and `center` ranking all resolve across the full span rather than\n * restarting inside each component. Combined with the component's\n * `displayOffset`, this is what makes `A-1…A-13` continuous.\n *\n * Every surface that renders a display label must go through this rather than\n * assembling the same object inline — three copies of it are exactly how canvas\n * and buyer drifted apart.\n */\nexport function logicalNumberingRow(placement: SegmentedRowPlacement): RowObject {\n return {\n ...placement.canonical,\n seatCount: placement.totalSeats,\n label: placement.displayLabel,\n displayLabel: placement.displayLabel,\n };\n}\n\n/**\n * The seat's BOOKING IDENTITY. Always physical: the storing row's label and the\n * physical index within it. A seat-level override wins because it was authored\n * deliberately.\n */\nexport function seatInventoryLabel(\n row: RowObject,\n physicalIndex: number,\n override?: SeatOverride,\n): string {\n return override?.label ?? `${row.label}-${seatLabelPart(row, physicalIndex)}`;\n}\n\n/**\n * The seat's HUMAN-FACING label.\n *\n * With no `placement` this is the physical label under the row's own display\n * prefix. With a `placement` the seat numbers against the logical row at its\n * component's offset — the identical construction buyer expansion uses, so the\n * author and the buyer can never see different seat numbers.\n *\n * A seat-level `displayLabel` override remains the highest-precedence copy.\n */\nexport function seatDisplayLabel(\n row: RowObject,\n physicalIndex: number,\n placement?: SegmentedRowPlacement,\n override?: SeatOverride,\n): string {\n if (override?.displayLabel) return override.displayLabel;\n if (!placement) {\n const prefix = row.displayLabel ?? row.label;\n return `${prefix}-${seatLabelPart(row, physicalIndex)}`;\n }\n const numberingRow = logicalNumberingRow(placement);\n const ordinal = placement.displayOffset + physicalIndex;\n return `${placement.displayLabel}-${seatLabelPart(numberingRow, ordinal)}`;\n}\n","/**\n * THE structure resolver — what a chart's seats actually form, geometrically.\n *\n * A chart is authored in 2D as sections, rows and seats. Rendering it in 3D needs\n * a level above that which the author never states: which rows belong to the same\n * physical STAND, which of them is that stand's front row, and how deep into the\n * stand each row sits. Every height in the venue follows from those three facts.\n *\n * This module resolves them, once, from the authored geometry alone. It is pure —\n * no renderer types, no GL — so the same structure feeds the 3D surface, the\n * eye-height model in `layout.ts`, and anything added later (labels, LOD\n * streaming, per-block camera framing) without any of them re-deriving it. That\n * matters because re-derivation is this codebase's most expensive bug class:\n * three surfaces held apart by offsets, and a cap and its seats measured from\n * different datums, both came from two files computing the same fact differently.\n *\n * ## Why blocks, and why depth is measured inside one\n *\n * A section is not a stand. The amphitheatre's `sec-gall` is a single section\n * holding six wedge blocks that wrap the bowl; the arena's tiers are four blocks\n * each. Height used to be a function of distance from the venue focal, which is\n * wrong for exactly this reason — the side blocks of a tier sit further from the\n * focal than the centre blocks, so they were raised as though they were further\n * back. Measured on `sec-gall`, authored at 6.20 m: its six blocks started at\n * 6.35, 6.37, 8.18, 8.23, 10.60 and 10.65 m. The side wedges were **4.4 m too\n * high**, and that is the tilt-and-lift the owner could see.\n *\n * Depth is therefore measured WITHIN a block, from that block's own front row:\n *\n * level(row) = section.height + blockDepth(row) · tan(rake)\n *\n * so every block of a tier starts at the tier's authored height, and rows rise by\n * the real spacing between them. A venue whose tier is genuinely flat authors\n * `rake = 0` and every row lands on the same level — the same model, no special\n * case, which is what lets one code path serve a stepped bowl and a flat terrace.\n */\n\nimport type { ExpandedSeat, Point } from './types';\n\n/** A row resolved into its stand. */\nexport interface ResolvedRow {\n /** The row's authored id, or a synthetic one for a seat that has none. */\n id: string;\n /** The row's seats as an ordered polyline (the shape the deck is built on). */\n pts: Point[];\n /** Indices into the input seat array, parallel to nothing — for attribution. */\n seatIndices: number[];\n /** Which block (stand) this row belongs to, within its section. */\n blockId: number;\n /** 0 for the block's front row, then 1, 2, … going back. */\n ordinal: number;\n /**\n * Distance back from the block's FRONT row, chart units, accumulated over the\n * real gaps between consecutive rows.\n *\n * Not the ordinal times a nominal pitch: a stand with an aisle break or a\n * widening row gap should rise over that gap, and accumulating the measured\n * spacing keeps the drawn deck on the seats. Not distance from the venue focal\n * either — that is the defect described above.\n */\n blockDepth: number;\n /** Mean distance of the row's seats from the venue focal (front-row ordering). */\n focalDistance: number;\n}\n\nexport interface ResolvedSection {\n sectionId: string;\n /** Rows ordered by block, then front-to-back within the block. */\n rows: ResolvedRow[];\n /** Number of distinct blocks found. */\n blockCount: number;\n}\n\n/**\n * Two rows join the same block when a probe on one lands within this multiple of\n * the section's typical row spacing of the other.\n *\n * Above 1 so consecutive rows of one stand always connect (they are one spacing\n * apart by definition, and real charts vary). Well below the width of an aisle or\n * a vomitory, which is what has to stay a break — otherwise the six wedges of a\n * bowl merge into one block and the defect this module exists to fix comes back.\n */\nconst BLOCK_LINK_RATIO = 1.8;\n\n/** Sample points along a row, used for every distance test here. */\nfunction probesOf(pts: readonly Point[]): Point[] {\n const n = pts.length;\n if (n <= 3) return [...pts];\n return [pts[Math.floor(n * 0.25)], pts[Math.floor(n * 0.5)], pts[Math.floor(n * 0.75)]];\n}\n\n/** Perpendicular distance from a point to a polyline, chart units. */\nexport function distanceToPolyline(pts: readonly Point[], x: number, y: number): number {\n if (!pts.length) return Infinity;\n if (pts.length === 1) return Math.hypot(x - pts[0].x, y - pts[0].y);\n let best = Infinity;\n for (let i = 0; i + 1 < pts.length; i++) {\n const a = pts[i], b = pts[i + 1];\n const vx = b.x - a.x, vy = b.y - a.y;\n const len2 = vx * vx + vy * vy;\n let t = len2 > 1e-12 ? ((x - a.x) * vx + (y - a.y) * vy) / len2 : 0;\n if (t < 0) t = 0; else if (t > 1) t = 1;\n const d = Math.hypot(x - (a.x + t * vx), y - (a.y + t * vy));\n if (d < best) best = d;\n }\n return best;\n}\n\n/** A row's bounding circle — the prune that keeps block detection off O(n²) work. */\ninterface RowBound { cx: number; cy: number; r: number }\n\nfunction boundOf(pts: readonly Point[]): RowBound {\n let cx = 0, cy = 0;\n for (const p of pts) { cx += p.x; cy += p.y; }\n const n = pts.length || 1;\n cx /= n; cy /= n;\n let r = 0;\n for (const p of pts) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > r) r = d;\n }\n return { cx, cy, r };\n}\n\n/**\n * Lower bound on the gap between two rows, from their bounding circles.\n *\n * Admissible: the true gap can never be smaller, so pruning on this cannot\n * discard a pair that would have linked. Pair testing is quadratic in a section's\n * row count, and a 50k-seat venue has sections with thousands of rows — without\n * this prune, resolving one of them is hundreds of millions of segment tests.\n */\nfunction boundGap(a: RowBound, b: RowBound): number {\n return Math.hypot(a.cx - b.cx, a.cy - b.cy) - a.r - b.r;\n}\n\n/** Shortest distance between two rows, measured from probes on each. */\nfunction rowGap(a: readonly Point[], b: readonly Point[]): number {\n let best = Infinity;\n for (const p of probesOf(a)) {\n const d = distanceToPolyline(b, p.x, p.y);\n if (d < best) best = d;\n }\n for (const p of probesOf(b)) {\n const d = distanceToPolyline(a, p.x, p.y);\n if (d < best) best = d;\n }\n return best;\n}\n\n/**\n * Order a row's seats along the row.\n *\n * The chord between the two extremes only decides the ORDER; distances are never\n * measured against it. A curved row orders correctly under this even though its\n * chord is a poor fit to its shape.\n */\nfunction orderAlongRow(pts: Point[], indices: number[]): { pts: Point[]; seatIndices: number[] } {\n if (pts.length < 3) return { pts, seatIndices: indices };\n let cx = 0, cy = 0;\n for (const p of pts) { cx += p.x; cy += p.y; }\n cx /= pts.length; cy /= pts.length;\n let far = -1, ax = pts[0];\n for (const p of pts) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n if (d > far) { far = d; ax = p; }\n }\n let dx = ax.x - cx, dy = ax.y - cy;\n const len = Math.hypot(dx, dy);\n if (len > 1e-9) { dx /= len; dy /= len; } else { dx = 1; dy = 0; }\n const order = pts.map((p, i) => ({ p, i, t: (p.x - cx) * dx + (p.y - cy) * dy }))\n .sort((u, v) => u.t - v.t);\n return { pts: order.map((o) => o.p), seatIndices: order.map((o) => indices[o.i]) };\n}\n\n/**\n * Least-squares circle through a set of points (Kåsa fit), or null if degenerate.\n *\n * Used to find the centre a section's rows were struck about, which is NOT the\n * chart's focal point. The amphitheatre makes this concrete: its rows are arcs\n * about a centre `C`, while `focalPoint` is the stage at {0, 75}. Measuring depth\n * from the focal therefore varied ALONG each arc — the original row tilt — and\n * made concentric rows look radial to every heuristic downstream.\n *\n * The focal is where the audience LOOKS. The row centre is what the seating was\n * drawn around. Conflating them is the mistake underneath several of the defects\n * in this file's history.\n */\nfunction fitCircle(pts: readonly Point[]): { cx: number; cy: number } | null {\n const n = pts.length;\n if (n < 3) return null;\n let mx = 0, my = 0;\n for (const p of pts) { mx += p.x; my += p.y; }\n mx /= n; my /= n;\n let suu = 0, suv = 0, svv = 0, suuu = 0, svvv = 0, suvv = 0, svuu = 0;\n for (const p of pts) {\n const u = p.x - mx, v = p.y - my;\n suu += u * u; svv += v * v; suv += u * v;\n suuu += u * u * u; svvv += v * v * v;\n suvv += u * v * v; svuu += v * u * u;\n }\n const det = suu * svv - suv * suv;\n if (Math.abs(det) < 1e-9) return null;\n const b1 = (suuu + suvv) / 2;\n const b2 = (svvv + svuu) / 2;\n const uc = (b1 * svv - b2 * suv) / det;\n const vc = (b2 * suu - b1 * suv) / det;\n const cx = uc + mx, cy = vc + my;\n if (!Number.isFinite(cx) || !Number.isFinite(cy)) return null;\n return { cx, cy };\n}\n\n/**\n * Largest share of a section's radial depth that one row may span about a fitted\n * centre before the concentric model is rejected.\n *\n * A true arc row spans ~0 radially. A straight row spans a lot. 0.15 separates\n * them decisively without needing to know which kind of venue this is.\n */\nconst CIRCLE_ROW_SPREAD_LIMIT = 0.15;\n\n/**\n * A block's rake axis: the direction its rows recede in, or null when its rows do\n * not share one.\n *\n * Fitted PER BLOCK, never per section. A section routinely holds several stands\n * at different angles (a bowl's six wedges), and one axis over all of them fits\n * none — that was tried and did nothing. Within a block the rows ARE parallel, so\n * the fit is well posed.\n *\n * The fit is then VERIFIED rather than trusted: a row must be narrow along the\n * axis compared with the block's whole depth, which is what \"the rows lie across\n * the axis\" means. Rows that run radially fail this by construction, and the\n * caller falls back rather than producing nonsense from a fit that does not hold.\n */\nfunction fitBlockAxis(group: readonly ResolvedRow[]): { x: number; y: number } | null {\n if (group.length < 2) return null;\n const cents = group.map((r) => {\n let x = 0, y = 0;\n for (const p of r.pts) { x += p.x; y += p.y; }\n return { x: x / r.pts.length, y: y / r.pts.length };\n });\n let ox = 0, oy = 0;\n for (const c of cents) { ox += c.x; oy += c.y; }\n ox /= cents.length; oy /= cents.length;\n\n let sxx = 0, sxy = 0, syy = 0;\n for (const c of cents) {\n const dx = c.x - ox, dy = c.y - oy;\n sxx += dx * dx; sxy += dx * dy; syy += dy * dy;\n }\n const tr = sxx + syy;\n const det = sxx * syy - sxy * sxy;\n const lambda = tr / 2 + Math.sqrt(Math.max(0, (tr * tr) / 4 - det));\n let ax: number, ay: number;\n if (Math.abs(sxy) > 1e-12) { ax = lambda - syy; ay = sxy; }\n else if (sxx >= syy) { ax = 1; ay = 0; }\n else { ax = 0; ay = 1; }\n const len = Math.hypot(ax, ay);\n if (!(len > 1e-12)) return null;\n ax /= len; ay /= len;\n\n // Verify: how wide is a row along the axis, against the block's total range?\n let lo = Infinity, hi = -Infinity;\n const widths: number[] = [];\n for (const r of group) {\n let rlo = Infinity, rhi = -Infinity;\n for (const p of r.pts) {\n const t = p.x * ax + p.y * ay;\n if (t < rlo) rlo = t;\n if (t > rhi) rhi = t;\n if (t < lo) lo = t;\n if (t > hi) hi = t;\n }\n widths.push(rhi - rlo);\n }\n const range = hi - lo;\n if (!(range > 1e-9)) return null;\n widths.sort((a, b) => a - b);\n // The 90th percentile, not the median. \"The rows lie across this axis\" has to\n // hold for nearly ALL of them; a median passes a block that is half wrong. The\n // amphitheatre orchestra is exactly that case: its four wedges merge into one\n // block (their radial rows converge near the focal and genuinely touch), so\n // rows in two of the wedges lie across any fitted axis and rows in the other\n // two run along it. The median was narrow, the fit was accepted, and depth\n // accumulated to 1,716 units — a 9.81 m rise on a tier authored at 0 m, with\n // the terrace above it at 2.20 m. One outlying tail is tolerated; half a block\n // is not.\n const p90 = widths[Math.min(widths.length - 1, Math.floor(widths.length * 0.9))];\n return p90 / range <= AXIS_ROW_WIDTH_LIMIT ? { x: ax, y: ay } : null;\n}\n\n/**\n * Largest share of a block's total depth that a single row may span along the\n * fitted axis before the fit is rejected.\n *\n * A row lying across the axis spans almost nothing along it, so real stands sit\n * far below this. A radially-running row spans nearly the whole range and is\n * rejected. 0.35 sits clear of both.\n */\nconst AXIS_ROW_WIDTH_LIMIT = 0.35;\n\n/**\n * Resolve one section's seats into blocks and depth-ordered rows.\n *\n * `seatIndices` are indices into the caller's own seat array, so a caller can\n * attribute the result back without this module knowing what it is attributing to.\n */\nexport function resolveSection(\n sectionId: string,\n seats: readonly ExpandedSeat[],\n seatIndices: readonly number[],\n focal: Point,\n): ResolvedSection {\n // --- Group by authored row -------------------------------------------------\n const groups = new Map<string, { pts: Point[]; idx: number[] }>();\n for (const i of seatIndices) {\n const s = seats[i];\n // A seat with no row is its own group: it can never drag a real row's shape,\n // and it still gets a deck under it downstream.\n const key = s.rowId || `__seat-${i}`;\n let g = groups.get(key);\n if (!g) { g = { pts: [], idx: [] }; groups.set(key, g); }\n g.pts.push({ x: s.x, y: s.y });\n g.idx.push(i);\n }\n if (!groups.size) return { sectionId, rows: [], blockCount: 0 };\n\n const rows: ResolvedRow[] = [];\n for (const [id, g] of groups) {\n const ordered = orderAlongRow(g.pts, g.idx);\n let sum = 0;\n for (const p of ordered.pts) sum += Math.hypot(p.x - focal.x, p.y - focal.y);\n rows.push({\n id,\n pts: ordered.pts,\n seatIndices: ordered.seatIndices,\n blockId: -1,\n ordinal: 0,\n blockDepth: 0,\n focalDistance: sum / ordered.pts.length,\n });\n }\n\n // --- Typical row spacing, to scale the block-link threshold -----------------\n // The median nearest-row gap: robust to a section holding one isolated row, and\n // to blocks whose internal spacing differs from each other's.\n const bounds = rows.map((r) => boundOf(r.pts));\n const nearest: number[] = [];\n for (let i = 0; i < rows.length; i++) {\n let best = Infinity;\n for (let j = 0; j < rows.length; j++) {\n if (i === j) continue;\n // Prune before the exact test; the bound can only understate the gap.\n if (boundGap(bounds[i], bounds[j]) >= best) continue;\n const d = rowGap(rows[i].pts, rows[j].pts);\n if (d < best) best = d;\n }\n if (Number.isFinite(best)) nearest.push(best);\n }\n nearest.sort((a, b) => a - b);\n const typicalGap = nearest.length ? nearest[Math.floor(nearest.length / 2)] : 1;\n const linkDistance = Math.max(typicalGap * BLOCK_LINK_RATIO, 1e-6);\n\n // --- Blocks: connected components of \"close enough to be the same stand\" ----\n const adjacency: number[][] = rows.map(() => []);\n for (let i = 0; i < rows.length; i++) {\n for (let j = i + 1; j < rows.length; j++) {\n if (boundGap(bounds[i], bounds[j]) > linkDistance) continue;\n if (rowGap(rows[i].pts, rows[j].pts) <= linkDistance) {\n adjacency[i].push(j);\n adjacency[j].push(i);\n }\n }\n }\n let blockCount = 0;\n for (let i = 0; i < rows.length; i++) {\n if (rows[i].blockId !== -1) continue;\n const id = blockCount++;\n const stack = [i];\n rows[i].blockId = id;\n while (stack.length) {\n const k = stack.pop()!;\n for (const n of adjacency[k]) {\n if (rows[n].blockId !== -1) continue;\n rows[n].blockId = id;\n stack.push(n);\n }\n }\n }\n\n // --- Depth within each block, along that block's own rake axis --------------\n const byBlock = new Map<number, ResolvedRow[]>();\n for (const r of rows) {\n const a = byBlock.get(r.blockId) ?? [];\n a.push(r);\n byBlock.set(r.blockId, a);\n }\n // --- Concentric model first: is this section a set of arcs about one centre? --\n //\n // Tried before the per-block axis because it is the stronger statement. When it\n // holds, depth is a single scalar (radius) shared by EVERY block, so the wedges\n // of a tier are level with each other by construction rather than by each\n // block happening to resolve the same way. That is what a real tier is.\n // Fit PER ROW and take the median centre, rather than one circle through every\n // seat. A single algebraic fit over points lying on many concentric rings is\n // biased toward the middle ring — measured, it put the amphitheatre orchestra's\n // centre at y = -265 when the chart struck those arcs about y = -520, which\n // made true arcs look 27 % radially spread and got the concentric model\n // rejected. One row is a single arc, so its own fit is well conditioned, and\n // the median across rows is immune to the few rows that are short or straight.\n const centres: Point[] = [];\n for (const r of rows) {\n if (r.pts.length < 4) continue;\n const c = fitCircle(r.pts);\n if (!c) continue;\n if (!Number.isFinite(c.cx) || !Number.isFinite(c.cy)) continue;\n centres.push({ x: c.cx, y: c.cy });\n }\n const centre = centres.length >= 2\n ? ((): { cx: number; cy: number } => {\n const xs = centres.map((c) => c.x).sort((a, b) => a - b);\n const ys = centres.map((c) => c.y).sort((a, b) => a - b);\n const mid = Math.floor(centres.length / 2);\n return { cx: xs[mid], cy: ys[mid] };\n })()\n : null;\n if (centre) {\n const radiusOf = (p: Point): number => Math.hypot(p.x - centre.cx, p.y - centre.cy);\n let lo = Infinity, hi = -Infinity;\n const spreads: number[] = [];\n for (const r of rows) {\n let rlo = Infinity, rhi = -Infinity;\n for (const p of r.pts) {\n const d = radiusOf(p);\n if (d < rlo) rlo = d;\n if (d > rhi) rhi = d;\n }\n spreads.push(rhi - rlo);\n if (rlo < lo) lo = rlo;\n if (rhi > hi) hi = rhi;\n }\n const range = hi - lo;\n spreads.sort((a, b) => a - b);\n const p90 = spreads[Math.min(spreads.length - 1, Math.floor(spreads.length * 0.9))];\n if (range > 1e-9 && p90 / range <= CIRCLE_ROW_SPREAD_LIMIT) {\n const withRadius = rows.map((r) => {\n let sum = 0;\n for (const p of r.pts) sum += radiusOf(p);\n return { row: r, radius: sum / r.pts.length };\n });\n let minR = Infinity;\n for (const w of withRadius) if (w.radius < minR) minR = w.radius;\n withRadius.sort((a, b) => a.radius - b.radius);\n // Rows at the same radius are the SAME row split by an aisle, so they share\n // an ordinal and a level regardless of which block they landed in.\n let ordinal = -1, lastRadius = -Infinity;\n const tolerance = range / Math.max(1, withRadius.length) * 0.5;\n const ordered: ResolvedRow[] = [];\n for (const w of withRadius) {\n if (w.radius - lastRadius > tolerance) { ordinal++; lastRadius = w.radius; }\n w.row.ordinal = ordinal;\n w.row.blockDepth = w.radius - minR;\n ordered.push(w.row);\n }\n return { sectionId, rows: ordered, blockCount };\n }\n }\n\n const out: ResolvedRow[] = [];\n for (const [, group] of byBlock) {\n const axis = fitBlockAxis(group);\n if (axis) {\n // Rows are parallel and across the axis: order and measure along it. Depth\n // accumulates the REAL gap between consecutive rows, so an aisle break\n // inside a stand rises across it rather than being flattened to one step.\n const key = (r: ResolvedRow): number => {\n let sum = 0;\n for (const p of r.pts) sum += p.x * axis.x + p.y * axis.y;\n return sum / r.pts.length;\n };\n group.sort((a, b) => key(a) - key(b));\n let depth = 0;\n for (let i = 0; i < group.length; i++) {\n if (i > 0) depth += rowGap(group[i - 1].pts, group[i].pts);\n group[i].ordinal = i;\n group[i].blockDepth = depth;\n out.push(group[i]);\n }\n } else {\n // No usable axis — this block's rows are not across a common direction.\n // The amphitheatre orchestra is the real case: its rows run RADIALLY, each\n // spanning radius 6 to 310, so no ordering of them is a front-to-back\n // ordering. Sorting by focal distance and accumulating gaps ran the depth\n // to 8,585 units (197 m) and pinned the tier against its runaway clamp.\n //\n // Fall back to depth measured straight from the focal, relative to this\n // block's own front. It is the pre-block model, which handled this shape\n // correctly, and confining it to the block keeps the property that every\n // block of a tier starts at the tier's authored height.\n let front = Infinity;\n for (const r of group) if (r.focalDistance < front) front = r.focalDistance;\n group.sort((a, b) => a.focalDistance - b.focalDistance);\n for (let i = 0; i < group.length; i++) {\n group[i].ordinal = i;\n group[i].blockDepth = Math.max(0, group[i].focalDistance - front);\n out.push(group[i]);\n }\n }\n }\n return { sectionId, rows: out, blockCount };\n}\n","/**\n * Pure geometry helpers — no Konva, no DOM. Turns the parametric chart\n * document into concrete seat coordinates and computes bounding boxes.\n */\n\nimport type {\n AccessibilityType,\n BoothObject,\n ChartDoc,\n ChartObject,\n ExpandedSeat,\n Floor,\n LabelStyle,\n Point,\n RowObject,\n RectTableSeatCounts,\n RectTableSide,\n SeatOverride,\n SectionObject,\n TableObject,\n} from './types';\nimport { distributeAlongCubic } from './complexGeometry';\nimport { polygonInscribedAnchor } from './polygonAnchor';\nimport { translateSectionOutlinePath } from './sectionPath';\nimport { resolveSegmentedRowGroups, type SegmentedRowPlacement } from './segmentedRowModel';\nimport { seatDisplayLabel, seatInventoryLabel } from './seatNumbering';\nimport { METRES_PER_CHART_UNIT, SEATED_EYE_HEIGHT_M, sectionGeometry } from './units';\nimport { resolveSection } from './venueStructure';\n\n/** Resolve a seat override's accessibility, honouring the legacy boolean flag. */\nfunction overrideAccessibility(o: SeatOverride | undefined): AccessibilityType[] {\n if (!o) return [];\n if (o.accessibility && o.accessibility.length) {\n return o.wheelchairSpaceType && !o.accessibility.includes('wheelchair')\n ? ['wheelchair', ...o.accessibility]\n : o.accessibility;\n }\n if (o.wheelchairSpaceType) return ['wheelchair'];\n return o.accessible ? ['wheelchair'] : [];\n}\n\nconst DEG = Math.PI / 180;\n/** Seat visual radius (mirrors the renderer) — used only for bounds padding. */\nconst SEAT_R = 9;\n/** How far outside a table's body its seats sit. */\nconst TABLE_SEAT_OFFSET = 16;\n\n/** Rotate a local point clockwise by `deg` (screen coords, +y down) then translate. */\nfunction place(lx: number, ly: number, deg: number, origin: Point): { x: number; y: number } {\n const a = deg * DEG;\n const cos = Math.cos(a);\n const sin = Math.sin(a);\n return {\n x: origin.x + lx * cos - ly * sin,\n y: origin.y + lx * sin + ly * cos,\n };\n}\n\n/**\n * Expand a parametric row into seat positions.\n *\n * Straight (curve === 0): seat i sits at local (i·spacing, 0).\n *\n * Curved: seats lie on a circular arc. Per-seat angular step is\n * `curve/(seatCount-1)`; radius is derived so the chord between neighbours\n * equals seatSpacing → radius = spacing / (2·sin(step/2)). Seat 0 sits at the\n * bottom of the arc (local origin) with the circle centre directly above it at\n * (0,-radius); increasing index sweeps toward +x while the ends rise toward -y,\n * i.e. positive curve is concave toward -y (a row wrapping a stage above it).\n * Local points are then rotated by `rotation` and translated to `origin`.\n *\n * Numbering (labels only, never geometry): `seatNumbering.direction === 'rtl'`\n * numbers from the far physical end; `step === 2` produces odd/even numbering\n * (start at 1 → 1,3,5…; start at 2 → 2,4,6…).\n */\n/** Base (pre-override) seat centre per index — shared by expandRow + designer edit mode. */\nexport function rowSeatPositions(row: RowObject): Point[] {\n const { seatCount, seatSpacing, curve, rotation, origin } = row;\n const out: Point[] = [];\n if (row.path) return distributeAlongCubic(row.path, seatCount);\n if (seatCount <= 1) {\n if (seatCount === 1) out.push({ x: origin.x, y: origin.y });\n return out;\n }\n if (curve === 0) {\n for (let i = 0; i < seatCount; i++) out.push(place(i * seatSpacing, 0, rotation, origin));\n return out;\n }\n const arcStep = (curve / (seatCount - 1)) * DEG; // radians per seat\n const radius = seatSpacing / (2 * Math.sin(Math.abs(arcStep) / 2));\n // Centre above seat 0. φ measured from the downward vertical, growing with index.\n for (let i = 0; i < seatCount; i++) {\n const phi = i * arcStep;\n const lx = radius * Math.sin(phi);\n const ly = -radius + radius * Math.cos(phi); // ≤ 0 → ends rise toward -y\n out.push(place(lx, ly, rotation, origin));\n }\n return out;\n}\n\nfunction overrideMap(row: RowObject): Map<number, SeatOverride> {\n const m = new Map<number, SeatOverride>();\n if (row.overrides) for (const o of row.overrides) m.set(o.index, o);\n return m;\n}\n\n/** Sellable row slots: skipped slots are absent; empty wheelchair bays remain. */\nexport function rowInventoryCount(row: RowObject): number {\n const skipped = new Set((row.overrides ?? [])\n .filter((override) => override.skip && Number.isInteger(override.index)\n && override.index >= 0 && override.index < row.seatCount)\n .map((override) => override.index));\n return Math.max(0, row.seatCount - skipped.size);\n}\n\n/**\n * Every seat slot of a row INCLUDING skipped ones (designer seat-edit mode uses\n * this to draw un-skip handles). Overrides (dx/dy/label/categoryKey) are applied\n * but a skipped slot is flagged, not omitted.\n */\nexport interface RowSeatSlot {\n index: number;\n x: number;\n y: number;\n label: string;\n displayLabel: string;\n categoryKey: string;\n skipped: boolean;\n accessible: boolean;\n accessibility: AccessibilityType[];\n wheelchairSpaceType?: SeatOverride['wheelchairSpaceType'];\n commercial?: RowObject['commercial'];\n viewUrl?: string;\n labelStyle?: LabelStyle;\n}\n\n/** Every authored table-chair slot, including skipped inventory. This mirrors\n * row seat slots so Designer, MCP and buyer/event expansion share one semantic\n * source while retaining the table's stable numeric slot identity. */\nexport interface TableSeatSlot extends RowSeatSlot {\n side?: RectTableSide;\n}\n\n/**\n * Seat slots for one physical row.\n *\n * `placement` makes the slots read as part of a logical segmented row: labels\n * continue across components (`A-8`, not a second `A-1`) using the logical row's\n * numbering scheme and total seat count.\n */\nexport function expandRowSlots(row: RowObject, placement?: SegmentedRowPlacement): RowSeatSlot[] {\n const ov = overrideMap(row);\n return rowSeatPositions(row).map((p, i) => {\n const o = ov.get(i);\n const accessibility = overrideAccessibility(o);\n // Inventory label stays PHYSICAL (stable booking identity); only the\n // human-facing label follows a segmented row's logical numbering. Both\n // resolved by `seatNumbering` so no surface can make this split differently.\n const inventoryLabel = seatInventoryLabel(row, i, o);\n const commercial = { ...row.commercial, ...o?.commercial };\n return {\n index: i,\n x: p.x + (o?.dx ?? 0),\n y: p.y + (o?.dy ?? 0),\n label: inventoryLabel,\n displayLabel: seatDisplayLabel(row, i, placement, o),\n categoryKey: o?.categoryKey ?? row.categoryKey,\n skipped: !!o?.skip,\n accessible: accessibility.length > 0,\n accessibility,\n wheelchairSpaceType: o?.wheelchairSpaceType,\n commercial: Object.values(commercial).some((value) => value !== undefined && value !== false && value !== '') ? commercial : undefined,\n viewUrl: o?.viewFromSeatUrl ?? row.viewFromSeatUrl,\n labelStyle: o?.labelStyle,\n };\n });\n}\n\nexport function expandRow(row: RowObject): ExpandedSeat[] {\n const seats: ExpandedSeat[] = [];\n for (const slot of expandRowSlots(row)) {\n if (slot.skipped) continue; // physical seat absent; numbering gap preserved\n seats.push({\n id: `${row.id}:${slot.index}`,\n label: slot.label,\n displayLabel: slot.displayLabel === slot.label ? undefined : slot.displayLabel,\n x: slot.x,\n y: slot.y,\n rowId: row.id,\n categoryKey: slot.categoryKey,\n accessible: slot.accessible || undefined,\n accessibility: slot.accessibility.length ? slot.accessibility : undefined,\n wheelchairSpaceType: slot.wheelchairSpaceType,\n commercial: slot.commercial,\n viewUrl: slot.viewUrl,\n labelStyle: slot.labelStyle,\n });\n }\n return seats;\n}\n\n/**\n * Expand a table into its perimeter seats.\n *\n * Round: seats spread evenly on a circle of radius `radius + 16`, the first at\n * angle `rotation` (degrees, clockwise from +x). Rect: seats line the top and\n * bottom edges (split evenly, any remainder to the top), 16u outside the edge,\n * the whole set rotated about the table centre.\n */\n/** Legacy rect tables distribute aggregate capacity round-robin across enabled\n * sides, then emit chairs in canonical top/bottom/left/right order. */\nexport function tableSeatCountsBySide(t: TableObject): RectTableSeatCounts {\n if (t.seatCountsBySide) return { ...t.seatCountsBySide };\n const enabled = t.sides && t.sides.length ? t.sides : ['top', 'bottom'];\n const order = (['top', 'bottom', 'left', 'right'] as const).filter((side) => enabled.includes(side));\n const counts: RectTableSeatCounts = { top: 0, bottom: 0, left: 0, right: 0 };\n if (!order.length) return counts;\n const n = Math.max(0, Math.round(t.seatCount));\n for (let index = 0; index < n; index++) counts[order[index % order.length]] += 1;\n return counts;\n}\n\n/** Expand every authored table chair, including skipped slots needed by the\n * Designer to restore inventory. */\nexport function expandTableSlots(t: TableObject): TableSeatSlot[] {\n const seats: TableSeatSlot[] = [];\n const n = Math.max(0, Math.round(t.seatCount));\n if (n === 0) return seats;\n const overrides = new Map((t.overrides ?? []).map((override) => [override.index, override]));\n const mk = (index: number, x: number, y: number, side?: RectTableSide): TableSeatSlot => {\n const override = overrides.get(index);\n const accessibility = overrideAccessibility(override);\n const label = override?.label ?? `${t.label}-${index + 1}`;\n const displayPrefix = t.displayLabel ?? t.label;\n return {\n index,\n label,\n displayLabel: override?.displayLabel ?? `${displayPrefix}-${index + 1}`,\n x: x + (override?.dx ?? 0),\n y: y + (override?.dy ?? 0),\n categoryKey: override?.categoryKey ?? t.categoryKey,\n skipped: !!override?.skip,\n accessible: accessibility.length > 0,\n accessibility,\n wheelchairSpaceType: override?.wheelchairSpaceType,\n commercial: override?.commercial,\n viewUrl: override?.viewFromSeatUrl,\n labelStyle: override?.labelStyle,\n ...(side ? { side } : {}),\n };\n };\n\n if (t.shape === 'round') {\n const R = (t.radius ?? 40) + TABLE_SEAT_OFFSET;\n const base = t.rotation * DEG;\n const arc = Math.max(0, Math.min(360, t.seatArc ?? 360));\n if (arc >= 360 || n === 1) {\n for (let i = 0; i < n; i++) {\n const a = base + (i / n) * 2 * Math.PI;\n seats.push(mk(i, t.center.x + R * Math.cos(a), t.center.y + R * Math.sin(a)));\n }\n return seats;\n }\n const arcRad = arc * DEG;\n const halfGap = (2 * Math.PI - arcRad) / 2;\n const start = base + halfGap;\n for (let i = 0; i < n; i++) {\n const a = start + (i / (n - 1)) * arcRad;\n seats.push(mk(i, t.center.x + R * Math.cos(a), t.center.y + R * Math.sin(a)));\n }\n return seats;\n }\n\n const w = t.width ?? 80;\n const h = t.height ?? 50;\n const counts = tableSeatCountsBySide(t);\n const order = ['top', 'bottom', 'left', 'right'] as const;\n let idx = 0;\n for (const side of order) {\n const count = counts[side];\n for (let j = 0; j < count; j++) {\n let localX: number;\n let localY: number;\n if (side === 'top') {\n localX = -w / 2 + ((j + 0.5) * w) / count;\n localY = -h / 2 - TABLE_SEAT_OFFSET;\n } else if (side === 'bottom') {\n localX = -w / 2 + ((j + 0.5) * w) / count;\n localY = h / 2 + TABLE_SEAT_OFFSET;\n } else if (side === 'left') {\n localX = -w / 2 - TABLE_SEAT_OFFSET;\n localY = -h / 2 + ((j + 0.5) * h) / count;\n } else {\n localX = w / 2 + TABLE_SEAT_OFFSET;\n localY = -h / 2 + ((j + 0.5) * h) / count;\n }\n const point = place(localX, localY, t.rotation, t.center);\n seats.push(mk(idx++, point.x, point.y, side));\n }\n }\n return seats;\n}\n\n/** Sellable individual table chairs. Grouped tables own one atomic inventory\n * unit elsewhere and therefore retain their full authored chair capacity. */\nexport function tableInventoryCount(t: TableObject): number {\n if (t.bookAsWhole || t.variableOccupancy) return Math.max(0, Math.round(t.seatCount));\n return expandTableSlots(t).filter((slot) => !slot.skipped).length;\n}\n\nexport function expandTable(t: TableObject): ExpandedSeat[] {\n return expandTableSlots(t).filter((slot) => !slot.skipped).map((slot) => ({\n id: `${t.id}:${slot.index}`,\n label: slot.label,\n ...(slot.displayLabel !== slot.label ? { displayLabel: slot.displayLabel } : {}),\n x: slot.x,\n y: slot.y,\n rowId: t.id,\n categoryKey: slot.categoryKey,\n ...(slot.accessible ? { accessible: true } : {}),\n ...(slot.accessibility.length ? { accessibility: slot.accessibility } : {}),\n ...(slot.wheelchairSpaceType ? { wheelchairSpaceType: slot.wheelchairSpaceType } : {}),\n ...(slot.commercial ? { commercial: slot.commercial } : {}),\n ...(slot.viewUrl ? { viewUrl: slot.viewUrl } : {}),\n ...(slot.labelStyle ? { labelStyle: slot.labelStyle } : {}),\n }));\n}\n\n/** Expand a booth into its single bookable block unit. */\nexport function expandBooth(b: BoothObject): ExpandedSeat[] {\n return [\n {\n id: `${b.id}:0`,\n label: b.label,\n ...(b.displayLabel && b.displayLabel !== b.label ? { displayLabel: b.displayLabel } : {}),\n x: b.center.x,\n y: b.center.y,\n rowId: b.id,\n categoryKey: b.categoryKey,\n kind: 'booth',\n },\n ];\n}\n\n/** Ray-cast point-in-polygon test — odd crossings ⇒ inside. */\nexport function pointInPolygon(p: Point, poly: Point[]): boolean {\n let inside = false;\n for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {\n const xi = poly[i].x;\n const yi = poly[i].y;\n const xj = poly[j].x;\n const yj = poly[j].y;\n const hit = yi > p.y !== yj > p.y && p.x < ((xj - xi) * (p.y - yi)) / (yj - yi) + xi;\n if (hit) inside = !inside;\n }\n return inside;\n}\n\nfunction pointOnPolygonBoundary(p: Point, poly: Point[]): boolean {\n return poly.some((start, index) => {\n const end = poly[(index + 1) % poly.length];\n const cross = (p.y - start.y) * (end.x - start.x) - (p.x - start.x) * (end.y - start.y);\n if (Math.abs(cross) > 1e-7) return false;\n const dot = (p.x - start.x) * (end.x - start.x) + (p.y - start.y) * (end.y - start.y);\n const lengthSquared = (end.x - start.x) ** 2 + (end.y - start.y) ** 2;\n return dot >= -1e-7 && dot <= lengthSquared + 1e-7;\n });\n}\n\nexport function pointInPolygonWithHoles(p: Point, outer: Point[], holes: Point[][] | undefined): boolean {\n return pointInPolygon(p, outer)\n && !(holes ?? []).some((hole) => pointInPolygon(p, hole) || pointOnPolygonBoundary(p, hole));\n}\n\n/**\n * Stable interior label anchor: the centre of the largest circle that fits in\n * the polygon (holes excluded).\n *\n * This used to return the average-of-vertices centroid whenever that happened\n * to be inside, and only fell back to a coarse 24×24 grid search when it was\n * not. \"Inside\" is a much weaker property than \"has room\": on a traced arena\n * the outer arc of a wedge carries dozens of sampled vertices and the radial\n * edges carry two, so the average is dragged onto the arc and the label pill —\n * far wider than a point — straddled the boundary into the next section.\n * Measured on the 36-section traced arena corpus, 15 of 36 anchors had under\n * 75% of the clearance actually available, some as little as 43%.\n *\n * `polygonInscribedAnchor` is deterministic and coordinate-free, so callers may\n * memoize it on geometry alone.\n */\nexport function polygonLabelPoint(outer: Point[], holes: Point[][] | undefined): Point {\n return polygonInscribedAnchor(outer, holes).point;\n}\n\n/** Average of polygon vertices (v1 centroid — good enough for labels/membership). */\nfunction polygonCentroid(pts: Point[]): Point {\n if (!pts.length) return { x: 0, y: 0 };\n let x = 0;\n let y = 0;\n for (const p of pts) {\n x += p.x;\n y += p.y;\n }\n return { x: x / pts.length, y: y / pts.length };\n}\n\n/**\n * Visual centre of any object — used for spatial section membership and for\n * rotating an object about its centre. Rows: centroid of expanded seats (or\n * origin when empty); tables/booths: centre; GA/section: polygon centroid;\n * shape: bbox centre; text: its position.\n */\nexport function objectCenter(o: ChartObject): Point {\n switch (o.type) {\n case 'row': {\n const seats = expandRow(o);\n if (!seats.length) return { x: o.origin.x, y: o.origin.y };\n let x = 0;\n let y = 0;\n for (const s of seats) {\n x += s.x;\n y += s.y;\n }\n return { x: x / seats.length, y: y / seats.length };\n }\n case 'table':\n case 'booth':\n return { x: o.center.x, y: o.center.y };\n case 'gaArea':\n return polygonCentroid(o.points);\n case 'section':\n return polygonCentroid(o.outline);\n case 'text':\n return { x: o.position.x, y: o.position.y };\n case 'shape':\n if (o.points && o.points.length) return polygonCentroid(o.points);\n if (o.x != null && o.y != null && o.width != null && o.height != null) {\n return { x: o.x + o.width / 2, y: o.y + o.height / 2 };\n }\n return { x: o.x ?? 0, y: o.y ?? 0 };\n case 'decorImage':\n return { x: o.x + o.width / 2, y: o.y + o.height / 2 };\n }\n}\n\nfunction samePoints(left: Point[], right: Point[]): boolean {\n return left.length === right.length\n && left.every((point, index) => point.x === right[index].x && point.y === right[index].y);\n}\n\nfunction sameGASurfaceAsSection(object: ChartObject, section: SectionObject): boolean {\n if (object.type !== 'gaArea' || !samePoints(object.points, section.outline)) return false;\n const objectHoles = object.holes ?? [];\n const sectionHoles = section.holes ?? [];\n return objectHoles.length === sectionHoles.length\n && objectHoles.every((hole, index) => samePoints(hole, sectionHoles[index]));\n}\n\n/**\n * Resolve one bookable object's owning physical section using the canonical\n * first-match rule. Generated reference inventory may name a logical section,\n * but that provenance is trusted only when the stored geometry confirms it.\n * Keeping this primitive in layout lets section inventory, category painting,\n * and buyer view inheritance share one ownership decision.\n */\nexport function owningSectionForObject(\n objects: ChartObject[],\n object: ChartObject,\n): SectionObject | undefined {\n const sections = objects.filter((candidate): candidate is SectionObject => candidate.type === 'section');\n const referencedLogicalId = 'referenceInventorySource' in object\n ? object.referenceInventorySource?.logicalSectionId\n : undefined;\n const center = objectCenter(object);\n const referencedOwner = referencedLogicalId\n ? sections.find((section) => (\n (section.logicalSectionId ?? section.id) === referencedLogicalId\n && (sameGASurfaceAsSection(object, section)\n || pointInPolygonWithHoles(center, section.outline, section.holes))\n ))\n : undefined;\n return referencedOwner\n ?? sections.find((section) => pointInPolygonWithHoles(center, section.outline, section.holes));\n}\n\n/**\n * Normalized floor list (Batch 5): a multi-floor chart's `floors`, or a synthetic\n * single floor wrapping a single-floor chart's `objects`. Every consumer that needs\n * to reason about floors goes through this so single-floor charts stay untouched.\n */\nexport function floorsOf(doc: ChartDoc): Floor[] {\n if (doc.floors && doc.floors.length) return doc.floors;\n return [{\n id: 'floor-0',\n name: 'Main',\n objects: doc.objects,\n focalPoint: doc.focalPoint,\n referenceImage: doc.referenceImage,\n backgroundImage: doc.backgroundImage,\n }];\n}\n\n/** Objects of one floor by id (defaults to the first floor). */\nexport function floorObjects(doc: ChartDoc, floorId?: string): ChartObject[] {\n const floors = floorsOf(doc);\n return (floorId ? floors.find((f) => f.id === floorId) : floors[0])?.objects ?? [];\n}\n\n/** Every object across ALL floors — the whole venue (single-floor = `doc.objects`). */\nexport function allObjects(doc: ChartDoc): ChartObject[] {\n return doc.floors && doc.floors.length ? doc.floors.flatMap((f) => f.objects) : doc.objects;\n}\n\n/** A copy of an object translated by (dx, dy) — every coordinate field shifted. */\nfunction translateObject(o: ChartObject, dx: number, dy: number): ChartObject {\n const p = (pt: Point): Point => ({ x: pt.x + dx, y: pt.y + dy });\n const pts = (a: Point[]): Point[] => a.map(p);\n switch (o.type) {\n case 'row':\n return { ...o, origin: p(o.origin) };\n case 'table':\n case 'booth':\n return { ...o, center: p(o.center) };\n case 'gaArea':\n return { ...o, points: pts(o.points), ...(o.holes ? { holes: o.holes.map(pts) } : {}) };\n case 'section':\n return {\n ...o,\n outline: pts(o.outline),\n ...(o.outlinePath ? { outlinePath: translateSectionOutlinePath(o.outlinePath, dx, dy) } : {}),\n ...(o.holes ? { holes: o.holes.map(pts) } : {}),\n };\n case 'text':\n return { ...o, position: p(o.position) };\n case 'shape':\n return {\n ...o,\n ...(o.points ? { points: pts(o.points) } : {}),\n ...(o.x != null ? { x: o.x + dx } : {}),\n ...(o.y != null ? { y: o.y + dy } : {}),\n };\n case 'decorImage':\n return { ...o, x: o.x + dx, y: o.y + dy };\n }\n}\n\n/**\n * Multi-floor 3D stack (Batch 5): flatten every floor into ONE doc with floor `i`\n * lifted by `i * spread` in −y, so the isometric view shows the floors as stacked\n * decks (ground at the bottom). Returns a single-floor doc (no `floors`). Meant\n * only for the 3D overview render — 2D still shows one floor via `floorObjects`.\n */\nexport function stackFloors(doc: ChartDoc, spread = 900): ChartDoc {\n if (!doc.floors || doc.floors.length < 2) return doc;\n const objects: ChartObject[] = [];\n doc.floors.forEach((f, i) => {\n const dy = -i * spread;\n for (const o of f.objects) objects.push(dy === 0 ? o : translateObject(o, 0, dy));\n });\n return { ...doc, objects, floors: undefined };\n}\n\nexport interface ExpandChartOptions {\n /** Physical height for a single-floor projection extracted from a multi-floor\n * document. Full multi-floor documents resolve each floor directly. */\n floorBaseHeightM?: number;\n}\n\nfunction expandFloorObjects(\n objects: ChartObject[],\n zones: ChartDoc['zones'],\n fallbackFocal: Point | undefined,\n /**\n * Lowest-priority authored view-from-seat fallback for this floor: the\n * floor-level url if set, else the venue/chart-level default. Applied as the\n * final `??=` after seat/row/section, so any closer photo always wins. Absent\n * ⇒ seats with no closer photo keep `viewUrl` undefined (generated panorama).\n */\n viewFallback?: string,\n): ExpandedSeat[] {\n const out: ExpandedSeat[] = [];\n const segmented = resolveSegmentedRowGroups(objects);\n\n for (const obj of objects) {\n let seats: ExpandedSeat[] = [];\n if (obj.type === 'row') seats = expandRow(obj);\n else if (obj.type === 'table') seats = expandTable(obj);\n else if (obj.type === 'booth') seats = expandBooth(obj);\n if (!seats.length) continue;\n if (obj.type === 'row') {\n const logical = segmented.get(obj.id);\n if (logical) {\n const overrides = new Map((obj.overrides ?? []).map((override) => [override.index, override]));\n for (const seat of seats) {\n const physicalIndex = Number(seat.id.slice(seat.id.lastIndexOf(':') + 1));\n if (!Number.isInteger(physicalIndex)) continue;\n seat.logicalRowId = logical.groupId;\n seat.logicalSeatIndex = logical.adjacencyOffset + physicalIndex;\n // A seat-level display override remains the highest-precedence copy.\n // Left to `expandRow`, which drops it when it equals the inventory\n // label; only the DERIVED logical label is recomputed here.\n if (!overrides.get(physicalIndex)?.displayLabel) {\n seat.displayLabel = seatDisplayLabel(obj, physicalIndex, logical);\n }\n seat.viewUrl ??= logical.viewFromSeatUrl;\n }\n }\n }\n const owner = owningSectionForObject(objects, obj);\n const inheritedView = owner?.viewFromSeatUrl;\n const zone = owner?.zone ? zones?.find((candidate) => candidate.id === owner.zone) : undefined;\n const resolvedFocal = zone?.focalPoint ?? fallbackFocal;\n for (const seat of seats) {\n if (inheritedView) seat.viewUrl ??= inheritedView;\n // Floor > venue default: the last authored tier before a generated panorama.\n if (viewFallback) seat.viewUrl ??= viewFallback;\n if (owner) seat.sectionId = owner.logicalSectionId ?? owner.id;\n if (owner?.zone) seat.zoneId = owner.zone;\n if (resolvedFocal) seat.focalPoint = { ...resolvedFocal };\n }\n out.push(...seats);\n }\n return out;\n}\n\n/** Expand every seat-bearing object across all floors (rows, tables, booths).\n * Multi-floor ownership is resolved one floor at a time: local coordinates may\n * overlap between floors and must never assign a seat to another floor's section. */\nexport function expandChart(doc: ChartDoc, options: ExpandChartOptions = {}): ExpandedSeat[] {\n if (doc.floors?.length) {\n const out: ExpandedSeat[] = [];\n for (const floor of doc.floors) {\n const floorFocal = floor.focalPoint ?? doc.focalPoint;\n // Per-floor photo overrides the venue default for seats on this floor.\n const floorView = floor.viewFromSeatUrl ?? doc.viewFromSeatUrl;\n const seats = expandFloorObjects(floor.objects, doc.zones, floorFocal, floorView);\n assignEyeHeights(floor.objects, floor.focalPoint ?? doc.focalPoint, floor.baseHeightM ?? 0, seats);\n out.push(...seats);\n }\n return out;\n }\n const out = expandFloorObjects(doc.objects, doc.zones, doc.focalPoint, doc.viewFromSeatUrl);\n assignEyeHeights(doc.objects, doc.focalPoint, options.floorBaseHeightM ?? 0, out);\n return out;\n}\n\n/**\n * Phase B2: annotate each expanded seat with a real-world eye height (metres above\n * the focal/stage datum) for the auto-360° generator. Resolved once at expand time\n * — never per render frame — so the 13k-seat render path pays no cost.\n *\n * `eyeHeightM = section front-edge height + row rise + seated eye height`, where\n * the ROW RISE is derived from the seat's DRAWN radial depth into its section\n * (chart units → metres × tan(rake)), NOT a hard-coded row pitch. Deriving rise\n * from drawn geometry (the same distances the panorama already uses horizontally)\n * is the binding fix for the 30–40% under-rise the sightline de-risk study flagged\n * (docs/3d-sightline-derisk-2026-07-21.md §7).\n *\n * A chart with no elevated/raked/height-authored section skips the spatial pass\n * entirely and every seat resolves to the flat seated-eye baseline — so legacy\n * charts stay pixel-identical and `expandChart`'s other callers pay nothing.\n */\nfunction assignEyeHeights(\n objects: ChartObject[],\n focal: Point | undefined,\n floorBaseHeightM: number,\n seats: ExpandedSeat[],\n): void {\n const sections = objects.filter((o): o is SectionObject => o.type === 'section');\n const hasGeometry = floorBaseHeightM > 0\n || sections.some((s) => s.height !== undefined || s.rake !== undefined || (s.elevation ?? 0) > 0);\n if (!sections.length || !hasGeometry || !focal) {\n for (const seat of seats) seat.eyeHeightM = floorBaseHeightM + SEATED_EYE_HEIGHT_M;\n return;\n }\n // Pass 1: owning section (first drawn section containing the seat), grouped by\n // row so each section can fit its own rake axis.\n const owner = new Array<SectionObject | null>(seats.length);\n const geo = new Map<string, { height: number; rake: number }>();\n const seatsBySection = new Map<string, number[]>();\n for (let i = 0; i < seats.length; i++) {\n const seat = seats[i];\n const sec = sections.find((s) => pointInPolygonWithHoles({ x: seat.x, y: seat.y }, s.outline, s.holes)) ?? null;\n owner[i] = sec;\n if (!sec) continue;\n if (!geo.has(sec.id)) geo.set(sec.id, sectionGeometry(sec, { floorBaseHeightM }));\n const list = seatsBySection.get(sec.id);\n if (list) list.push(i); else seatsBySection.set(sec.id, [i]);\n }\n\n // Pass 2: resolve each section's blocks and per-row depth, then bake a level.\n //\n // The SAME resolver the 3D deck is built from (`venueStructure.ts`). Depth is\n // measured back from each BLOCK's own front row, not from the venue focal:\n // measuring from the focal raised a bowl's side wedges as though they sat\n // further back, by up to 4.4 m on the amphitheatre gallery. And it must be the\n // same call in both files — a seat's eye height and the deck it stands on\n // disagreeing is the trap that made every offset constant chart-specific.\n const seatLevel = new Array<number | undefined>(seats.length).fill(undefined);\n for (const [secId, indices] of seatsBySection) {\n const g = geo.get(secId);\n if (!g) continue;\n const rakeTan = g.rake > 0 ? Math.tan((g.rake * Math.PI) / 180) : 0;\n const structure = resolveSection(secId, seats, indices, focal);\n for (const row of structure.rows) {\n const riseM = row.blockDepth * METRES_PER_CHART_UNIT * rakeTan;\n const level = g.height + riseM;\n for (const si of row.seatIndices) seatLevel[si] = level;\n }\n }\n\n for (let i = 0; i < seats.length; i++) {\n const seat = seats[i];\n const sec = owner[i];\n if (!sec) { seat.eyeHeightM = floorBaseHeightM + SEATED_EYE_HEIGHT_M; continue; }\n const g = geo.get(sec.id)!;\n seat.eyeHeightM = (seatLevel[i] ?? g.height) + SEATED_EYE_HEIGHT_M;\n }\n}\n\nconst PAD = 40;\n\n/** Axis-aligned bounds over every object plus the background image, with padding. */\nexport function chartBounds(doc: ChartDoc): { x: number; y: number; width: number; height: number } {\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n const acc = (x: number, y: number) => {\n if (x < minX) minX = x;\n if (y < minY) minY = y;\n if (x > maxX) maxX = x;\n if (y > maxY) maxY = y;\n };\n\n for (const s of expandChart(doc)) acc(s.x, s.y);\n\n for (const obj of allObjects(doc)) {\n if (obj.type === 'gaArea') {\n for (const p of obj.points) acc(p.x, p.y);\n } else if (obj.type === 'section') {\n for (const p of obj.outline) acc(p.x, p.y);\n } else if (obj.type === 'decorImage') {\n acc(obj.x, obj.y);\n acc(obj.x + obj.width, obj.y + obj.height);\n } else if (obj.type === 'shape') {\n if (obj.points && obj.points.length) {\n for (const p of obj.points) acc(p.x, p.y);\n } else if (obj.x != null && obj.y != null && obj.width != null && obj.height != null) {\n acc(obj.x, obj.y);\n acc(obj.x + obj.width, obj.y + obj.height);\n }\n } else if (obj.type === 'table') {\n const off = TABLE_SEAT_OFFSET + SEAT_R;\n const ext =\n obj.shape === 'round'\n ? (obj.radius ?? 40) + off\n : Math.max((obj.width ?? 80) / 2, (obj.height ?? 50) / 2) + off; // rotation-agnostic over-approximation\n acc(obj.center.x - ext, obj.center.y - ext);\n acc(obj.center.x + ext, obj.center.y + ext);\n } else if (obj.type === 'booth') {\n const ext = Math.max(obj.width, obj.height) / 2;\n acc(obj.center.x - ext, obj.center.y - ext);\n acc(obj.center.x + ext, obj.center.y + ext);\n } else if (obj.type === 'text') {\n const w = obj.fontSize * obj.text.length * 0.6; // approximate glyph advance\n acc(obj.position.x, obj.position.y);\n acc(obj.position.x + w, obj.position.y + obj.fontSize);\n }\n }\n\n for (const image of [doc.referenceImage, doc.backgroundImage]) {\n if (!image) continue;\n const { center, width } = image;\n const bh = (width * 3) / 4; // assume 4:3 when the true aspect is unknown at bounds-time\n acc(center.x - width / 2, center.y - bh / 2);\n acc(center.x + width / 2, center.y + bh / 2);\n }\n\n // Empty document → a sane default box around the focal point.\n if (!isFinite(minX)) {\n const f = doc.focalPoint ?? { x: 0, y: 0 };\n return { x: f.x - 200, y: f.y - 200, width: 400, height: 400 };\n }\n\n return {\n x: minX - PAD,\n y: minY - PAD,\n width: maxX - minX + PAD * 2,\n height: maxY - minY + PAD * 2,\n };\n}\n"],"mappings":";AAmHO,IAAM,sBAA2C;AAAA,EACtD,EAAE,KAAK,cAAc,OAAO,oBAAoB,OAAO,cAAc,MAAM,SAAI;AAAA,EAC/E,EAAE,KAAK,aAAa,OAAO,kBAAkB,OAAO,aAAa,MAAM,0CAAW;AAAA,EAClF,EAAE,KAAK,mBAAmB,OAAO,sCAAsC,OAAO,oBAAoB,MAAM,YAAK;AAAA,EAC7G,EAAE,KAAK,WAAW,OAAO,uBAAuB,OAAO,WAAW,MAAM,YAAK;AAAA,EAC7E,EAAE,KAAK,QAAQ,OAAO,0BAA0B,OAAO,iBAAiB,MAAM,KAAK;AAAA,EACnF,EAAE,KAAK,iBAAiB,OAAO,sBAAsB,OAAO,iBAAiB,MAAM,YAAK;AAAA,EACxF,EAAE,KAAK,aAAa,OAAO,kBAAkB,OAAO,aAAa,MAAM,YAAK;AAAA,EAC5E,EAAE,KAAK,gBAAgB,OAAO,mBAAmB,OAAO,gBAAgB,MAAM,eAAK;AACrF;AAEA,IAAM,sBAAsB,IAAI,IAAI,oBAAoB,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;AAGvE,SAAS,kBAAkB,KAAuD;AACvF,SAAO,oBAAoB,IAAI,GAAG;AACpC;AAOO,IAAM,2BAA8D;AAAA,EACzE,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,mBAAmB;AAAA,EACnB,SAAS;AAAA,EACT,MAAM;AAAA,EACN,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,gBAAgB;AAClB;AAGO,SAAS,uBAAuB,OAAgD;AACrF,QAAM,UAAU,QAAQ,CAAC;AACzB,SAAQ,WAAW,yBAAyB,OAAO,KAAM;AAC3D;AAqEO,IAAM,wBAA8C;AAAA,EACzD,EAAE,KAAK,kBAAkB,OAAO,mBAAmB,OAAO,cAAc,MAAM,SAAI;AAAA,EAClF,EAAE,KAAK,kBAAkB,OAAO,mBAAmB,OAAO,cAAc,MAAM,YAAK;AAAA,EACnF,EAAE,KAAK,WAAW,OAAO,gBAAgB,OAAO,WAAW,MAAM,SAAI;AACvE;AAEA,IAAM,wBAAwB,IAAI,IAAI,sBAAsB,IAAI,CAAC,SAAS,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;AAGpF,SAAS,mBAAmB,KAAyD;AAC1F,SAAO,sBAAsB,IAAI,GAAG;AACtC;AAkBO,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;AA6nB7B,IAAM,2BAA2B;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,IAAM,8BAAmD,IAAI,IAAI,wBAAwB;AAGlF,SAAS,QAAQ,KAAkC;AACxD,UAAQ,IAAI,MAAM;AAAA,IAChB,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA;AAAA;AAAA,IAGT,KAAK;AACH,aAAO,IAAI,UAAU,eAAe,eAAe;AAAA;AAAA;AAAA;AAAA,IAIrD,KAAK;AACH,aAAO,IAAI,QAAQ,4BAA4B,IAAI,IAAI,IAAI,IACvD,iBACA;AAAA;AAAA,IAEN,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAsyBO,IAAM,oBAAoB;;;ACrrD1B,IAAM,wBAAwB,OAAO;AAOrC,IAAM,wBAAwB,IAAI;AAOlC,IAAM,sBAAsB;AAQ5B,IAAM,gBAAgB,sBAAsB;AAI5C,IAAM,6BAA6B;AACnC,IAAM,6BAA6B;AACnC,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;AAC7B,IAAM,uBAAuB;AAM7B,IAAM,oCAAoC;AAO1C,IAAM,sBAAsB;AAwBnC,SAAS,cAAc,OAA2B,KAAa,KAAa,UAA0B;AACpG,SAAO,OAAO,SAAS,KAAK,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAe,CAAC,IAAI;AAClF;AAGO,SAAS,qBAAqB,OAAmC;AACtE,MAAI,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AACpC,SAAO,KAAK;AAAA,IACV;AAAA,IACA,KAAK,IAAI,4BAA4B,KAAK,MAAM,KAAe,CAAC;AAAA,EAClE;AACF;AAKA,SAAS,wBAAwB,OAAmC;AAClE,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,CAAC,OAAO,UAAU,KAAK,KAAM,QAAmB,EAAG,QAAO;AACzF,MAAK,SAAoB,kCAAmC,QAAO;AACnE,SAAO;AACT;AAEO,SAAS,gBACd,SACA,UAAkC,CAAC,GAInC;AACA,QAAM,mBAAmB;AAAA,IACvB,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,kBAAkB,KAAK;AAAA,IAC3B;AAAA,IACA,mBAAmB,wBAAwB,QAAQ,SAAS,IAAI;AAAA,EAClE;AACA,QAAM,SAAS,QAAQ,WAAW,SAC9B,kBACA,cAAc,QAAQ,QAAQ,sBAAsB,sBAAsB,eAAe;AAC7F,QAAM,OAAO,cAAc,QAAQ,MAAM,sBAAsB,sBAAsB,CAAC;AACtF,SAAO,EAAE,QAAQ,KAAK;AACxB;;;AC/HO,SAAS,WAAW,MAAiB,GAAkB;AAC5D,QAAM,IAAI,IAAI;AACd,QAAM,IAAI,IAAI,IAAI;AAClB,QAAM,IAAI,IAAI,IAAI,IAAI;AACtB,QAAM,IAAI,IAAI,IAAI,IAAI;AACtB,QAAM,IAAI,IAAI,IAAI;AAClB,SAAO;AAAA,IACL,GAAG,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI;AAAA,IAC/E,GAAG,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI;AAAA,EACjF;AACF;AAiCO,SAAS,qBAAqB,MAAiB,OAAe,aAAa,KAAc;AAC9F,MAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,EAAG,OAAM,IAAI,MAAM,6CAA6C;AACxG,MAAI,UAAU,EAAG,QAAO,CAAC,WAAW,MAAM,GAAG,CAAC;AAC9C,QAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,aAAa,EAAE,GAAG,CAAC,GAAG,UAAU,WAAW,MAAM,QAAQ,UAAU,CAAC;AACzG,QAAM,UAAU,IAAI,aAAa,QAAQ,MAAM;AAC/C,WAAS,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,SAAS,GAAG;AACtD,UAAM,KAAK,QAAQ,KAAK,EAAE,IAAI,QAAQ,QAAQ,CAAC,EAAE;AACjD,UAAM,KAAK,QAAQ,KAAK,EAAE,IAAI,QAAQ,QAAQ,CAAC,EAAE;AACjD,YAAQ,KAAK,IAAI,QAAQ,QAAQ,CAAC,IAAI,KAAK,MAAM,IAAI,EAAE;AAAA,EACzD;AACA,QAAM,QAAQ,QAAQ,QAAQ,SAAS,CAAC;AACxC,MAAI,SAAS,KAAM,QAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,OAAO,EAAE,GAAG,KAAK,MAAM,EAAE;AACjF,QAAM,SAAkB,CAAC;AACzB,MAAI,UAAU;AACd,WAAS,QAAQ,GAAG,QAAQ,OAAO,SAAS,GAAG;AAC7C,UAAM,SAAU,QAAQ,SAAU,QAAQ;AAC1C,WAAO,UAAU,QAAQ,SAAS,KAAK,QAAQ,OAAO,IAAI,OAAQ,YAAW;AAC7E,UAAM,SAAS,QAAQ,UAAU,CAAC;AAClC,UAAM,QAAQ,QAAQ,OAAO;AAC7B,UAAM,QAAQ,UAAU,SAAS,KAAK,SAAS,WAAW,QAAQ;AAClE,WAAO,KAAK;AAAA,MACV,GAAG,QAAQ,UAAU,CAAC,EAAE,KAAK,QAAQ,OAAO,EAAE,IAAI,QAAQ,UAAU,CAAC,EAAE,KAAK;AAAA,MAC5E,GAAG,QAAQ,UAAU,CAAC,EAAE,KAAK,QAAQ,OAAO,EAAE,IAAI,QAAQ,UAAU,CAAC,EAAE,KAAK;AAAA,IAC9E,CAAC;AAAA,EACH;AACA,SAAO;AACT;;;ACrBA,IAAM,YAAY;AAElB,SAAS,kBAAkB,GAAU,GAAU,GAAkB;AAC/D,QAAM,KAAK,EAAE,IAAI,EAAE;AACnB,QAAM,KAAK,EAAE,IAAI,EAAE;AACnB,QAAM,cAAc,KAAK,KAAK,KAAK;AACnC,QAAM,IAAI,cACN,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,EAAE,IAAI,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,MAAM,WAAW,CAAC,IAC5E;AACJ,SAAO,KAAK,MAAM,EAAE,KAAK,EAAE,IAAI,IAAI,KAAK,EAAE,KAAK,EAAE,IAAI,IAAI,GAAG;AAC9D;AAGO,SAAS,gBAAgB,GAAU,OAA8C;AACtF,MAAI,OAAO;AACX,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,SAAS,EAAG;AACrB,aAAS,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,IAAI,KAAK,QAAQ,IAAI,KAAK;AAC7D,YAAM,IAAI,kBAAkB,GAAG,KAAK,CAAC,GAAG,KAAK,CAAC,CAAC;AAC/C,UAAI,IAAI,KAAM,QAAO;AAAA,IACvB;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,OAAO,GAAU,MAAiC;AACzD,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,IAAI,KAAK,QAAQ,IAAI,KAAK;AAC7D,UAAM,KAAK,KAAK,CAAC,EAAE;AACnB,UAAM,KAAK,KAAK,CAAC,EAAE;AACnB,UAAM,KAAK,KAAK,CAAC,EAAE;AACnB,UAAM,KAAK,KAAK,CAAC,EAAE;AACnB,UAAM,MAAM,KAAK,EAAE,MAAM,KAAK,EAAE,KAAK,EAAE,KAAM,KAAK,OAAO,EAAE,IAAI,OAAQ,KAAK,MAAM;AAClF,QAAI,IAAK,UAAS,CAAC;AAAA,EACrB;AACA,SAAO;AACT;AAGA,SAAS,eACP,GACA,OACA,OACQ;AACR,QAAM,SAAS,OAAO,GAAG,KAAK,KAAK,CAAC,MAAM,KAAK,CAAC,SAAS,OAAO,GAAG,IAAI,CAAC;AACxE,QAAM,WAAW,gBAAgB,GAAG,CAAC,OAAO,GAAG,KAAK,CAAC;AACrD,SAAO,SAAS,WAAW,CAAC;AAC9B;AAEA,SAAS,OAAO,GAAW,GAAW,GAAW,OAAyB,OAA4C;AACpH,QAAM,IAAI,eAAe,EAAE,GAAG,EAAE,GAAG,OAAO,KAAK;AAC/C,SAAO,EAAE,GAAG,GAAG,GAAG,GAAG,KAAK,IAAI,IAAI,KAAK,MAAM;AAC/C;AAGA,IAAM,YAAN,MAAgB;AAAA,EAAhB;AACE,SAAQ,QAAgB,CAAC;AAAA;AAAA,EAEzB,IAAI,OAAe;AACjB,WAAO,KAAK,MAAM;AAAA,EACpB;AAAA,EAEA,KAAK,MAAkB;AACrB,UAAM,QAAQ,KAAK;AACnB,UAAM,KAAK,IAAI;AACf,QAAI,QAAQ,MAAM,SAAS;AAC3B,WAAO,QAAQ,GAAG;AAChB,YAAM,SAAU,QAAQ,KAAM;AAC9B,UAAI,MAAM,MAAM,EAAE,OAAO,MAAM,KAAK,EAAE,IAAK;AAC3C,OAAC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC;AAC5D,cAAQ;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAwB;AACtB,UAAM,QAAQ,KAAK;AACnB,UAAM,MAAM,MAAM,CAAC;AACnB,UAAM,OAAO,MAAM,IAAI;AACvB,QAAI,MAAM,UAAU,MAAM;AACxB,YAAM,CAAC,IAAI;AACX,UAAI,QAAQ;AACZ,iBAAS;AACP,cAAM,OAAO,QAAQ,IAAI;AACzB,cAAM,QAAQ,OAAO;AACrB,YAAI,OAAO;AACX,YAAI,OAAO,MAAM,UAAU,MAAM,IAAI,EAAE,MAAM,MAAM,IAAI,EAAE,IAAK,QAAO;AACrE,YAAI,QAAQ,MAAM,UAAU,MAAM,KAAK,EAAE,MAAM,MAAM,IAAI,EAAE,IAAK,QAAO;AACvE,YAAI,SAAS,MAAO;AACpB,SAAC,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC;AACxD,gBAAQ;AAAA,MACV;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;AAGO,SAAS,YAAY,MAAgC;AAC1D,MAAI,KAAK,SAAS,EAAG,QAAO;AAC5B,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,IAAI,KAAK,QAAQ,IAAI,KAAK;AAC7D,YAAQ,KAAK,CAAC,EAAE,IAAI,KAAK,CAAC,EAAE,MAAM,KAAK,CAAC,EAAE,IAAI,KAAK,CAAC,EAAE;AAAA,EACxD;AACA,SAAO,KAAK,IAAI,GAAG,IAAI;AACzB;AAGO,SAAS,eAAe,OAAyB,OAA+C;AACrG,SAAO,KAAK,IAAI,GAAG,YAAY,KAAK,KAAK,SAAS,CAAC,GAAG,OAAO,CAAC,OAAO,SAAS,QAAQ,YAAY,IAAI,GAAG,CAAC,CAAC;AAC7G;AASO,SAAS,uBACd,OACA,OACiB;AACjB,QAAM,QAAQ,SAAS,CAAC;AACxB,MAAI,CAAC,MAAM,OAAQ,QAAO,EAAE,OAAO,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,QAAQ,EAAE;AAC7D,MAAI,MAAM,SAAS,GAAG;AACpB,UAAM,IAAI,MAAM,OAAO,CAAC,OAAO,MAAM,QAAQ,EAAE,GAAG,CAAC,IAAI,MAAM;AAC7D,UAAM,IAAI,MAAM,OAAO,CAAC,OAAO,MAAM,QAAQ,EAAE,GAAG,CAAC,IAAI,MAAM;AAC7D,WAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,QAAQ,EAAE;AAAA,EACtC;AACA,MAAI,OAAO;AACX,MAAI,OAAO;AACX,MAAI,OAAO;AACX,MAAI,OAAO;AACX,aAAW,KAAK,OAAO;AACrB,QAAI,EAAE,IAAI,KAAM,QAAO,EAAE;AACzB,QAAI,EAAE,IAAI,KAAM,QAAO,EAAE;AACzB,QAAI,EAAE,IAAI,KAAM,QAAO,EAAE;AACzB,QAAI,EAAE,IAAI,KAAM,QAAO,EAAE;AAAA,EAC3B;AACA,QAAM,QAAQ,OAAO;AACrB,QAAM,SAAS,OAAO;AACtB,QAAM,WAAW,KAAK,IAAI,OAAO,MAAM;AACvC,MAAI,EAAE,WAAW,IAAI;AACnB,WAAO,EAAE,OAAO,EAAE,IAAI,OAAO,QAAQ,GAAG,IAAI,OAAO,QAAQ,EAAE,GAAG,QAAQ,EAAE;AAAA,EAC5E;AAQA,QAAM,YAAY,KAAK,IAAI,OAAO,MAAM,IAAI;AAE5C,QAAM,QAAQ,IAAI,UAAU;AAC5B,MAAI,IAAI,WAAW;AACnB,WAAS,IAAI,MAAM,IAAI,MAAM,KAAK,UAAU;AAC1C,aAAS,IAAI,MAAM,IAAI,MAAM,KAAK,UAAU;AAC1C,YAAM,KAAK,OAAO,IAAI,GAAG,IAAI,GAAG,GAAG,OAAO,KAAK,CAAC;AAAA,IAClD;AAAA,EACF;AAGA,MAAI,OAAO,OAAO,OAAO,QAAQ,GAAG,OAAO,SAAS,GAAG,GAAG,OAAO,KAAK;AACtE,MAAI,WAAW;AACf,SAAO,MAAM,MAAM;AACjB,UAAM,OAAO,MAAM,IAAI;AACvB,QAAI,CAAC,KAAM;AACX,QAAI,KAAK,IAAI,KAAK,EAAG,QAAO;AAC5B,QAAI,KAAK,MAAM,KAAK,KAAK,UAAW;AACpC,QAAI,YAAY,UAAW;AAC3B,gBAAY;AACZ,QAAI,KAAK,IAAI;AACb,UAAM,KAAK,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,GAAG,OAAO,KAAK,CAAC;AAC1D,UAAM,KAAK,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,GAAG,OAAO,KAAK,CAAC;AAC1D,UAAM,KAAK,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,GAAG,OAAO,KAAK,CAAC;AAC1D,UAAM,KAAK,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,GAAG,OAAO,KAAK,CAAC;AAAA,EAC5D;AACA,SAAO,EAAE,OAAO,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,EAAE,GAAG,QAAQ,KAAK,IAAI,GAAG,KAAK,CAAC,EAAE;AACxE;;;ACrOA,IAAM,MAAM,KAAK,KAAK;AA+Ff,SAAS,4BAA4B,MAA0B,IAAY,IAAgC;AAChH,QAAM,YAAY,CAAC,WAAyB,EAAE,GAAG,MAAM,IAAI,IAAI,GAAG,MAAM,IAAI,GAAG;AAC/E,SAAO,4BAA4B,MAAM,SAAS;AACpD;AAIO,SAAS,4BACd,MACA,WACA,cAAc,GACd,YAAY,OACQ;AACpB,SAAO;AAAA,IACL,GAAG;AAAA,IACH,OAAO,UAAU,KAAK,KAAK;AAAA,IAC3B,UAAU,KAAK,SAAS,IAAI,CAAC,YAAY,QAAQ,SAAS,SACtD,EAAE,GAAG,SAAS,KAAK,UAAU,QAAQ,GAAG,EAAE,IAC1C,QAAQ,SAAS,QACf;AAAA,MACE,GAAG;AAAA,MACH,QAAQ,UAAU,QAAQ,MAAM;AAAA,MAChC,QAAQ,QAAQ,SAAS,KAAK,IAAI,WAAW;AAAA,MAC7C,WAAW,YAAY,CAAC,QAAQ,YAAY,QAAQ;AAAA,MACpD,KAAK,UAAU,QAAQ,GAAG;AAAA,IAC5B,IACA;AAAA,MACE,GAAG;AAAA,MACH,UAAU,UAAU,QAAQ,QAAQ;AAAA,MACpC,UAAU,UAAU,QAAQ,QAAQ;AAAA,MACpC,KAAK,UAAU,QAAQ,GAAG;AAAA,IAC5B,CAAC;AAAA,EACT;AACF;AAiFA,IAAM,qBAAqB,IAAI;;;AC7FxB,SAAS,0BACd,SACoC;AACpC,QAAM,YAAY,oBAAI,IAAmC;AACzD,QAAM,UAAU,oBAAI,IAAyB;AAC7C,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,SAAS,SAAS,CAAC,OAAO,aAAc;AACnD,UAAM,OAAO,QAAQ,IAAI,OAAO,aAAa,OAAO,KAAK,CAAC;AAC1D,SAAK,KAAK,MAAM;AAChB,YAAQ,IAAI,OAAO,aAAa,SAAS,IAAI;AAAA,EAC/C;AACA,aAAW,CAAC,SAAS,OAAO,KAAK,SAAS;AACxC,UAAM,UAAU,QAAQ,MAAM,EAAE,KAAK,CAAC,MAAM,UAC1C,KAAK,aAAc,iBAAiB,MAAM,aAAc,cACzD;AACD,UAAM,gBAAgB,QAAQ,CAAC,GAAG,cAAc,kBAAkB;AAClE,UAAM,QAAQ,QAAQ,CAAC,GAAG;AAC1B,QAAI,CAAC,MAAO;AACZ,UAAM,QAAQ,iBAAiB,KAC1B,QAAQ,WAAW,iBACnB,OAAO,mBAAmB,WAC1B,QAAQ,MAAM,CAAC,KAAK,UACrB,IAAI,cAAc,SAAS,sBACxB,IAAI,aAAa,YAAY,WAC7B,IAAI,aAAa,mBAAmB,iBACpC,IAAI,aAAa,mBAAmB,UACnC,UAAU,IACV,IAAI,aAAa,mBAAmB,UACpC,IAAI,aAAa,mBAAmB,YACrC,IAAI,aAAa,iBAAiB,MAAM,YAC5C;AACH,QAAI,CAAC,MAAO;AACZ,UAAM,aAAa,QAAQ,OAAO,CAAC,KAAK,QAAQ,MAAM,IAAI,WAAW,CAAC;AACtE,QAAI,kBAAkB;AACtB,QAAI,gBAAgB;AACpB,eAAW,OAAO,SAAS;AACzB,UAAI,IAAI,aAAc,mBAAmB,QAAS,oBAAmB;AACrE,gBAAU,IAAI,IAAI,IAAI;AAAA,QACpB;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAc,MAAM;AAAA,QACpB;AAAA,QACA,WAAW,QAAQ,CAAC;AAAA,QACpB,iBAAiB,MAAM;AAAA,MACzB,CAAC;AACD,yBAAmB,IAAI;AACvB,uBAAiB,IAAI;AAAA,IACvB;AAAA,EACF;AACA,SAAO;AACT;;;AC1JA,IAAM,gBAAgB;AAGtB,IAAM,iBAAiB;AAMhB,SAAS,gBAAgB,OAAe,UAA0B;AACvE,QAAM,OAAO,SAAS;AACtB,MAAI,IAAI,QAAQ;AAChB,MAAI,MAAM;AACV,SAAO,IAAI,GAAG;AACZ,SAAK;AACL,UAAM,MAAM,IAAI;AAChB,UAAM,SAAS,GAAG,IAAI;AACtB,QAAI,KAAK,MAAM,IAAI,IAAI;AAAA,EACzB;AACA,SAAO;AACT;AAgBO,SAAS,UAAU,OAAe,QAAQ,OAAe;AAC9D,QAAM,WAAW,QAAQ,iBAAiB;AAC1C,SAAO,gBAAgB,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,IAAI,CAAC,GAAG,QAAQ;AACrE;AAEA,IAAM,cAAuC;AAAA,EAC3C,CAAC,KAAM,GAAG;AAAA,EAAG,CAAC,KAAK,IAAI;AAAA,EAAG,CAAC,KAAK,GAAG;AAAA,EAAG,CAAC,KAAK,IAAI;AAAA,EAAG,CAAC,KAAK,GAAG;AAAA,EAAG,CAAC,IAAI,IAAI;AAAA,EACxE,CAAC,IAAI,GAAG;AAAA,EAAG,CAAC,IAAI,IAAI;AAAA,EAAG,CAAC,IAAI,GAAG;AAAA,EAAG,CAAC,GAAG,IAAI;AAAA,EAAG,CAAC,GAAG,GAAG;AAAA,EAAG,CAAC,GAAG,IAAI;AAAA,EAAG,CAAC,GAAG,GAAG;AAC3E;AAIO,SAAS,QAAQ,OAAuB;AAC7C,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,SAAS,EAAG,QAAO,OAAO,KAAK;AAC9D,MAAI,YAAY,KAAK,MAAM,KAAK;AAChC,MAAI,MAAM;AACV,aAAW,CAAC,GAAG,GAAG,KAAK,aAAa;AAClC,WAAO,aAAa,GAAG;AACrB,aAAO;AACP,mBAAa;AAAA,IACf;AAAA,EACF;AACA,SAAO;AACT;;;AC3CA,SAAS,WAAW,GAAqB;AACvC,QAAM,OAAO,IAAI,MAAc,CAAC;AAChC,QAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,MAAM,CAAC,EAClC,KAAK,CAAC,GAAG,MAAM,KAAK,IAAI,IAAI,KAAK,IAAI,EAAE,IAAI,KAAK,IAAI,IAAI,KAAK,IAAI,EAAE,KAAK,IAAI,CAAC,EAC7E,QAAQ,CAAC,KAAK,MAAO,KAAK,GAAG,IAAI,CAAE;AACtC,SAAO;AACT;AAOO,SAAS,cAAc,KAAgB,GAAmB;AAC/D,QAAM,WAAW,IAAI,kBAAkB;AACvC,QAAM,MAAM,IAAI,eAAe,aAAa;AAC5C,QAAM,OAAO,IAAI,eAAe,QAAQ;AACxC,QAAM,SAAS,IAAI,eAAe,UAAU;AAC5C,QAAM,SAAS,IAAI,eAAe,UAAU;AAC5C,QAAM,QAAQ,IAAI,eAAe;AACjC,QAAM,IAAI,IAAI;AAMd,MAAI,WAAW,YAAY,WAAW,qBAAqB;AACzD,UAAM,OAAO,KAAK,KAAK,IAAI,CAAC;AAC5B,UAAMA,QAAO,WAAW,WACnB,IAAI,OAAO,WAAW,IAAI,IAAI,WAAW,IAAI,KAAK,IAAI,KACtD,IAAI,OAAO,WAAW,KAAK,OAAO,IAAI,KAAK,WAAW,IAAI,KAAK,IAAI;AACxE,WAAO,GAAG,MAAM,GAAGA,KAAI;AAAA,EACzB;AAKA,QAAM,UAAU,WAAW,SAAS,WAAW,SAAS,IAAI;AAC5D,QAAM,QAAQ,SAAS,QAAQ,OAAO,SAAS,KAAK,IAAI,SAAS,IAAI,KAAK,UAAU;AAIpF,QAAM,IAAI,QAAQ,WAAW,WAAW,CAAC,EAAE,CAAC,IAAI,QAAQ,QAAQ,IAAI,IAAI,IAAI;AAE5E,MAAI;AACJ,UAAQ,QAAQ;AAAA,IACd,KAAK,OAAO;AACV,YAAM,WAAW,QAAQ,MAAM,IAAI,QAAQ,QAAQ;AACnD,aAAO,OAAO,WAAW,IAAI,CAAC;AAC9B;AAAA,IACF;AAAA,IACA,KAAK,QAAQ;AACX,YAAM,YAAY,QAAQ,MAAM,IAAI,QAAQ,QAAQ;AACpD,aAAO,OAAO,YAAY,IAAI,CAAC;AAC/B;AAAA,IACF;AAAA,IACA,KAAK;AACH,aAAO,QAAQ,QAAQ,IAAI,IAAI;AAC/B;AAAA,IACF,KAAK;AACH,aAAO,UAAU,QAAQ,IAAI,MAAM,KAAK;AACxC;AAAA,IACF,KAAK;AACH,aAAO,UAAU,QAAQ,IAAI,MAAM,IAAI;AACvC;AAAA,IACF,KAAK;AAAA,IACL;AACE,aAAO,OAAO,QAAQ,IAAI,IAAI;AAC9B;AAAA,EACJ;AACA,SAAO,GAAG,MAAM,GAAG,IAAI;AACzB;AAeO,SAAS,oBAAoB,WAA6C;AAC/E,SAAO;AAAA,IACL,GAAG,UAAU;AAAA,IACb,WAAW,UAAU;AAAA,IACrB,OAAO,UAAU;AAAA,IACjB,cAAc,UAAU;AAAA,EAC1B;AACF;AAOO,SAAS,mBACd,KACA,eACA,UACQ;AACR,SAAO,UAAU,SAAS,GAAG,IAAI,KAAK,IAAI,cAAc,KAAK,aAAa,CAAC;AAC7E;AAYO,SAAS,iBACd,KACA,eACA,WACA,UACQ;AACR,MAAI,UAAU,aAAc,QAAO,SAAS;AAC5C,MAAI,CAAC,WAAW;AACd,UAAM,SAAS,IAAI,gBAAgB,IAAI;AACvC,WAAO,GAAG,MAAM,IAAI,cAAc,KAAK,aAAa,CAAC;AAAA,EACvD;AACA,QAAM,eAAe,oBAAoB,SAAS;AAClD,QAAM,UAAU,UAAU,gBAAgB;AAC1C,SAAO,GAAG,UAAU,YAAY,IAAI,cAAc,cAAc,OAAO,CAAC;AAC1E;;;ACjFA,IAAM,mBAAmB;AAGzB,SAAS,SAAS,KAAgC;AAChD,QAAM,IAAI,IAAI;AACd,MAAI,KAAK,EAAG,QAAO,CAAC,GAAG,GAAG;AAC1B,SAAO,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC;AACxF;AAGO,SAAS,mBAAmB,KAAuB,GAAW,GAAmB;AACtF,MAAI,CAAC,IAAI,OAAQ,QAAO;AACxB,MAAI,IAAI,WAAW,EAAG,QAAO,KAAK,MAAM,IAAI,IAAI,CAAC,EAAE,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC;AAClE,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,IAAI,IAAI,QAAQ,KAAK;AACvC,UAAM,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC;AAC/B,UAAM,KAAK,EAAE,IAAI,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE;AACnC,UAAM,OAAO,KAAK,KAAK,KAAK;AAC5B,QAAI,IAAI,OAAO,UAAU,IAAI,EAAE,KAAK,MAAM,IAAI,EAAE,KAAK,MAAM,OAAO;AAClE,QAAI,IAAI,EAAG,KAAI;AAAA,aAAY,IAAI,EAAG,KAAI;AACtC,UAAM,IAAI,KAAK,MAAM,KAAK,EAAE,IAAI,IAAI,KAAK,KAAK,EAAE,IAAI,IAAI,GAAG;AAC3D,QAAI,IAAI,KAAM,QAAO;AAAA,EACvB;AACA,SAAO;AACT;AAKA,SAAS,QAAQ,KAAiC;AAChD,MAAI,KAAK,GAAG,KAAK;AACjB,aAAW,KAAK,KAAK;AAAE,UAAM,EAAE;AAAG,UAAM,EAAE;AAAA,EAAG;AAC7C,QAAM,IAAI,IAAI,UAAU;AACxB,QAAM;AAAG,QAAM;AACf,MAAI,IAAI;AACR,aAAW,KAAK,KAAK;AACnB,UAAM,IAAI,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,IAAI,EAAE;AACvC,QAAI,IAAI,EAAG,KAAI;AAAA,EACjB;AACA,SAAO,EAAE,IAAI,IAAI,EAAE;AACrB;AAUA,SAAS,SAAS,GAAa,GAAqB;AAClD,SAAO,KAAK,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;AACxD;AAGA,SAAS,OAAO,GAAqB,GAA6B;AAChE,MAAI,OAAO;AACX,aAAW,KAAK,SAAS,CAAC,GAAG;AAC3B,UAAM,IAAI,mBAAmB,GAAG,EAAE,GAAG,EAAE,CAAC;AACxC,QAAI,IAAI,KAAM,QAAO;AAAA,EACvB;AACA,aAAW,KAAK,SAAS,CAAC,GAAG;AAC3B,UAAM,IAAI,mBAAmB,GAAG,EAAE,GAAG,EAAE,CAAC;AACxC,QAAI,IAAI,KAAM,QAAO;AAAA,EACvB;AACA,SAAO;AACT;AASA,SAAS,cAAc,KAAc,SAA4D;AAC/F,MAAI,IAAI,SAAS,EAAG,QAAO,EAAE,KAAK,aAAa,QAAQ;AACvD,MAAI,KAAK,GAAG,KAAK;AACjB,aAAW,KAAK,KAAK;AAAE,UAAM,EAAE;AAAG,UAAM,EAAE;AAAA,EAAG;AAC7C,QAAM,IAAI;AAAQ,QAAM,IAAI;AAC5B,MAAI,MAAM,IAAI,KAAK,IAAI,CAAC;AACxB,aAAW,KAAK,KAAK;AACnB,UAAM,IAAI,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,IAAI,EAAE;AACvC,QAAI,IAAI,KAAK;AAAE,YAAM;AAAG,WAAK;AAAA,IAAG;AAAA,EAClC;AACA,MAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,IAAI;AAChC,QAAM,MAAM,KAAK,MAAM,IAAI,EAAE;AAC7B,MAAI,MAAM,MAAM;AAAE,UAAM;AAAK,UAAM;AAAA,EAAK,OAAO;AAAE,SAAK;AAAG,SAAK;AAAA,EAAG;AACjE,QAAM,QAAQ,IAAI,IAAI,CAAC,GAAG,OAAO,EAAE,GAAG,GAAG,IAAI,EAAE,IAAI,MAAM,MAAM,EAAE,IAAI,MAAM,GAAG,EAAE,EAC7E,KAAK,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,CAAC;AAC3B,SAAO,EAAE,KAAK,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,aAAa,MAAM,IAAI,CAAC,MAAM,QAAQ,EAAE,CAAC,CAAC,EAAE;AACnF;AAeA,SAAS,UAAU,KAA0D;AAC3E,QAAM,IAAI,IAAI;AACd,MAAI,IAAI,EAAG,QAAO;AAClB,MAAI,KAAK,GAAG,KAAK;AACjB,aAAW,KAAK,KAAK;AAAE,UAAM,EAAE;AAAG,UAAM,EAAE;AAAA,EAAG;AAC7C,QAAM;AAAG,QAAM;AACf,MAAI,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO;AACpE,aAAW,KAAK,KAAK;AACnB,UAAM,IAAI,EAAE,IAAI,IAAI,IAAI,EAAE,IAAI;AAC9B,WAAO,IAAI;AAAG,WAAO,IAAI;AAAG,WAAO,IAAI;AACvC,YAAQ,IAAI,IAAI;AAAG,YAAQ,IAAI,IAAI;AACnC,YAAQ,IAAI,IAAI;AAAG,YAAQ,IAAI,IAAI;AAAA,EACrC;AACA,QAAM,MAAM,MAAM,MAAM,MAAM;AAC9B,MAAI,KAAK,IAAI,GAAG,IAAI,KAAM,QAAO;AACjC,QAAM,MAAM,OAAO,QAAQ;AAC3B,QAAM,MAAM,OAAO,QAAQ;AAC3B,QAAM,MAAM,KAAK,MAAM,KAAK,OAAO;AACnC,QAAM,MAAM,KAAK,MAAM,KAAK,OAAO;AACnC,QAAM,KAAK,KAAK,IAAI,KAAK,KAAK;AAC9B,MAAI,CAAC,OAAO,SAAS,EAAE,KAAK,CAAC,OAAO,SAAS,EAAE,EAAG,QAAO;AACzD,SAAO,EAAE,IAAI,GAAG;AAClB;AASA,IAAM,0BAA0B;AAgBhC,SAAS,aAAa,OAAgE;AACpF,MAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,QAAM,QAAQ,MAAM,IAAI,CAAC,MAAM;AAC7B,QAAI,IAAI,GAAG,IAAI;AACf,eAAW,KAAK,EAAE,KAAK;AAAE,WAAK,EAAE;AAAG,WAAK,EAAE;AAAA,IAAG;AAC7C,WAAO,EAAE,GAAG,IAAI,EAAE,IAAI,QAAQ,GAAG,IAAI,EAAE,IAAI,OAAO;AAAA,EACpD,CAAC;AACD,MAAI,KAAK,GAAG,KAAK;AACjB,aAAW,KAAK,OAAO;AAAE,UAAM,EAAE;AAAG,UAAM,EAAE;AAAA,EAAG;AAC/C,QAAM,MAAM;AAAQ,QAAM,MAAM;AAEhC,MAAI,MAAM,GAAG,MAAM,GAAG,MAAM;AAC5B,aAAW,KAAK,OAAO;AACrB,UAAM,KAAK,EAAE,IAAI,IAAI,KAAK,EAAE,IAAI;AAChC,WAAO,KAAK;AAAI,WAAO,KAAK;AAAI,WAAO,KAAK;AAAA,EAC9C;AACA,QAAM,KAAK,MAAM;AACjB,QAAM,MAAM,MAAM,MAAM,MAAM;AAC9B,QAAM,SAAS,KAAK,IAAI,KAAK,KAAK,KAAK,IAAI,GAAI,KAAK,KAAM,IAAI,GAAG,CAAC;AAClE,MAAI,IAAY;AAChB,MAAI,KAAK,IAAI,GAAG,IAAI,OAAO;AAAE,SAAK,SAAS;AAAK,SAAK;AAAA,EAAK,WACjD,OAAO,KAAK;AAAE,SAAK;AAAG,SAAK;AAAA,EAAG,OAClC;AAAE,SAAK;AAAG,SAAK;AAAA,EAAG;AACvB,QAAM,MAAM,KAAK,MAAM,IAAI,EAAE;AAC7B,MAAI,EAAE,MAAM,OAAQ,QAAO;AAC3B,QAAM;AAAK,QAAM;AAGjB,MAAI,KAAK,UAAU,KAAK;AACxB,QAAM,SAAmB,CAAC;AAC1B,aAAW,KAAK,OAAO;AACrB,QAAI,MAAM,UAAU,MAAM;AAC1B,eAAW,KAAK,EAAE,KAAK;AACrB,YAAM,IAAI,EAAE,IAAI,KAAK,EAAE,IAAI;AAC3B,UAAI,IAAI,IAAK,OAAM;AACnB,UAAI,IAAI,IAAK,OAAM;AACnB,UAAI,IAAI,GAAI,MAAK;AACjB,UAAI,IAAI,GAAI,MAAK;AAAA,IACnB;AACA,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB;AACA,QAAM,QAAQ,KAAK;AACnB,MAAI,EAAE,QAAQ,MAAO,QAAO;AAC5B,SAAO,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAU3B,QAAM,MAAM,OAAO,KAAK,IAAI,OAAO,SAAS,GAAG,KAAK,MAAM,OAAO,SAAS,GAAG,CAAC,CAAC;AAC/E,SAAO,MAAM,SAAS,uBAAuB,EAAE,GAAG,IAAI,GAAG,GAAG,IAAI;AAClE;AAUA,IAAM,uBAAuB;AAQtB,SAAS,eACd,WACA,OACA,aACA,OACiB;AAEjB,QAAM,SAAS,oBAAI,IAA6C;AAChE,aAAW,KAAK,aAAa;AAC3B,UAAM,IAAI,MAAM,CAAC;AAGjB,UAAM,MAAM,EAAE,SAAS,UAAU,CAAC;AAClC,QAAI,IAAI,OAAO,IAAI,GAAG;AACtB,QAAI,CAAC,GAAG;AAAE,UAAI,EAAE,KAAK,CAAC,GAAG,KAAK,CAAC,EAAE;AAAG,aAAO,IAAI,KAAK,CAAC;AAAA,IAAG;AACxD,MAAE,IAAI,KAAK,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,EAAE,CAAC;AAC7B,MAAE,IAAI,KAAK,CAAC;AAAA,EACd;AACA,MAAI,CAAC,OAAO,KAAM,QAAO,EAAE,WAAW,MAAM,CAAC,GAAG,YAAY,EAAE;AAE9D,QAAM,OAAsB,CAAC;AAC7B,aAAW,CAAC,IAAI,CAAC,KAAK,QAAQ;AAC5B,UAAM,UAAU,cAAc,EAAE,KAAK,EAAE,GAAG;AAC1C,QAAI,MAAM;AACV,eAAW,KAAK,QAAQ,IAAK,QAAO,KAAK,MAAM,EAAE,IAAI,MAAM,GAAG,EAAE,IAAI,MAAM,CAAC;AAC3E,SAAK,KAAK;AAAA,MACR;AAAA,MACA,KAAK,QAAQ;AAAA,MACb,aAAa,QAAQ;AAAA,MACrB,SAAS;AAAA,MACT,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,eAAe,MAAM,QAAQ,IAAI;AAAA,IACnC,CAAC;AAAA,EACH;AAKA,QAAM,SAAS,KAAK,IAAI,CAAC,MAAM,QAAQ,EAAE,GAAG,CAAC;AAC7C,QAAM,UAAoB,CAAC;AAC3B,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAI,OAAO;AACX,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAI,MAAM,EAAG;AAEb,UAAI,SAAS,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,KAAK,KAAM;AAC5C,YAAM,IAAI,OAAO,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE,GAAG;AACzC,UAAI,IAAI,KAAM,QAAO;AAAA,IACvB;AACA,QAAI,OAAO,SAAS,IAAI,EAAG,SAAQ,KAAK,IAAI;AAAA,EAC9C;AACA,UAAQ,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC5B,QAAM,aAAa,QAAQ,SAAS,QAAQ,KAAK,MAAM,QAAQ,SAAS,CAAC,CAAC,IAAI;AAC9E,QAAM,eAAe,KAAK,IAAI,aAAa,kBAAkB,IAAI;AAGjE,QAAM,YAAwB,KAAK,IAAI,MAAM,CAAC,CAAC;AAC/C,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,aAAS,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACxC,UAAI,SAAS,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,aAAc;AACnD,UAAI,OAAO,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE,GAAG,KAAK,cAAc;AACpD,kBAAU,CAAC,EAAE,KAAK,CAAC;AACnB,kBAAU,CAAC,EAAE,KAAK,CAAC;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AACA,MAAI,aAAa;AACjB,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAI,KAAK,CAAC,EAAE,YAAY,GAAI;AAC5B,UAAM,KAAK;AACX,UAAM,QAAQ,CAAC,CAAC;AAChB,SAAK,CAAC,EAAE,UAAU;AAClB,WAAO,MAAM,QAAQ;AACnB,YAAM,IAAI,MAAM,IAAI;AACpB,iBAAW,KAAK,UAAU,CAAC,GAAG;AAC5B,YAAI,KAAK,CAAC,EAAE,YAAY,GAAI;AAC5B,aAAK,CAAC,EAAE,UAAU;AAClB,cAAM,KAAK,CAAC;AAAA,MACd;AAAA,IACF;AAAA,EACF;AAGA,QAAM,UAAU,oBAAI,IAA2B;AAC/C,aAAW,KAAK,MAAM;AACpB,UAAM,IAAI,QAAQ,IAAI,EAAE,OAAO,KAAK,CAAC;AACrC,MAAE,KAAK,CAAC;AACR,YAAQ,IAAI,EAAE,SAAS,CAAC;AAAA,EAC1B;AAcA,QAAM,UAAmB,CAAC;AAC1B,aAAW,KAAK,MAAM;AACpB,QAAI,EAAE,IAAI,SAAS,EAAG;AACtB,UAAM,IAAI,UAAU,EAAE,GAAG;AACzB,QAAI,CAAC,EAAG;AACR,QAAI,CAAC,OAAO,SAAS,EAAE,EAAE,KAAK,CAAC,OAAO,SAAS,EAAE,EAAE,EAAG;AACtD,YAAQ,KAAK,EAAE,GAAG,EAAE,IAAI,GAAG,EAAE,GAAG,CAAC;AAAA,EACnC;AACA,QAAM,SAAS,QAAQ,UAAU,KAC5B,MAAkC;AACnC,UAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACvD,UAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACvD,UAAM,MAAM,KAAK,MAAM,QAAQ,SAAS,CAAC;AACzC,WAAO,EAAE,IAAI,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,EAAE;AAAA,EACpC,GAAG,IACD;AACJ,MAAI,QAAQ;AACV,UAAM,WAAW,CAAC,MAAqB,KAAK,MAAM,EAAE,IAAI,OAAO,IAAI,EAAE,IAAI,OAAO,EAAE;AAClF,QAAI,KAAK,UAAU,KAAK;AACxB,UAAM,UAAoB,CAAC;AAC3B,eAAW,KAAK,MAAM;AACpB,UAAI,MAAM,UAAU,MAAM;AAC1B,iBAAW,KAAK,EAAE,KAAK;AACrB,cAAM,IAAI,SAAS,CAAC;AACpB,YAAI,IAAI,IAAK,OAAM;AACnB,YAAI,IAAI,IAAK,OAAM;AAAA,MACrB;AACA,cAAQ,KAAK,MAAM,GAAG;AACtB,UAAI,MAAM,GAAI,MAAK;AACnB,UAAI,MAAM,GAAI,MAAK;AAAA,IACrB;AACA,UAAM,QAAQ,KAAK;AACnB,YAAQ,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAC5B,UAAM,MAAM,QAAQ,KAAK,IAAI,QAAQ,SAAS,GAAG,KAAK,MAAM,QAAQ,SAAS,GAAG,CAAC,CAAC;AAClF,QAAI,QAAQ,QAAQ,MAAM,SAAS,yBAAyB;AAC1D,YAAM,aAAa,KAAK,IAAI,CAAC,MAAM;AACjC,YAAI,MAAM;AACV,mBAAW,KAAK,EAAE,IAAK,QAAO,SAAS,CAAC;AACxC,eAAO,EAAE,KAAK,GAAG,QAAQ,MAAM,EAAE,IAAI,OAAO;AAAA,MAC9C,CAAC;AACD,UAAI,OAAO;AACX,iBAAW,KAAK,WAAY,KAAI,EAAE,SAAS,KAAM,QAAO,EAAE;AAC1D,iBAAW,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AAG7C,UAAI,UAAU,IAAI,aAAa;AAC/B,YAAM,YAAY,QAAQ,KAAK,IAAI,GAAG,WAAW,MAAM,IAAI;AAC3D,YAAM,UAAyB,CAAC;AAChC,iBAAW,KAAK,YAAY;AAC1B,YAAI,EAAE,SAAS,aAAa,WAAW;AAAE;AAAW,uBAAa,EAAE;AAAA,QAAQ;AAC3E,UAAE,IAAI,UAAU;AAChB,UAAE,IAAI,aAAa,EAAE,SAAS;AAC9B,gBAAQ,KAAK,EAAE,GAAG;AAAA,MACpB;AACA,aAAO,EAAE,WAAW,MAAM,SAAS,WAAW;AAAA,IAChD;AAAA,EACF;AAEA,QAAM,MAAqB,CAAC;AAC5B,aAAW,CAAC,EAAE,KAAK,KAAK,SAAS;AAC/B,UAAM,OAAO,aAAa,KAAK;AAC/B,QAAI,MAAM;AAIR,YAAM,MAAM,CAAC,MAA2B;AACtC,YAAI,MAAM;AACV,mBAAW,KAAK,EAAE,IAAK,QAAO,EAAE,IAAI,KAAK,IAAI,EAAE,IAAI,KAAK;AACxD,eAAO,MAAM,EAAE,IAAI;AAAA,MACrB;AACA,YAAM,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC;AACpC,UAAI,QAAQ;AACZ,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAI,IAAI,EAAG,UAAS,OAAO,MAAM,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,EAAE,GAAG;AACzD,cAAM,CAAC,EAAE,UAAU;AACnB,cAAM,CAAC,EAAE,aAAa;AACtB,YAAI,KAAK,MAAM,CAAC,CAAC;AAAA,MACnB;AAAA,IACF,OAAO;AAWL,UAAI,QAAQ;AACZ,iBAAW,KAAK,MAAO,KAAI,EAAE,gBAAgB,MAAO,SAAQ,EAAE;AAC9D,YAAM,KAAK,CAAC,GAAG,MAAM,EAAE,gBAAgB,EAAE,aAAa;AACtD,eAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,cAAM,CAAC,EAAE,UAAU;AACnB,cAAM,CAAC,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,CAAC,EAAE,gBAAgB,KAAK;AAChE,YAAI,KAAK,MAAM,CAAC,CAAC;AAAA,MACnB;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,WAAW,MAAM,KAAK,WAAW;AAC5C;;;ACjeA,SAAS,sBAAsB,GAAkD;AAC/E,MAAI,CAAC,EAAG,QAAO,CAAC;AAChB,MAAI,EAAE,iBAAiB,EAAE,cAAc,QAAQ;AAC7C,WAAO,EAAE,uBAAuB,CAAC,EAAE,cAAc,SAAS,YAAY,IAClE,CAAC,cAAc,GAAG,EAAE,aAAa,IACjC,EAAE;AAAA,EACR;AACA,MAAI,EAAE,oBAAqB,QAAO,CAAC,YAAY;AAC/C,SAAO,EAAE,aAAa,CAAC,YAAY,IAAI,CAAC;AAC1C;AAEA,IAAM,MAAM,KAAK,KAAK;AAEtB,IAAM,SAAS;AAEf,IAAM,oBAAoB;AAG1B,SAAS,MAAM,IAAY,IAAY,KAAa,QAAyC;AAC3F,QAAM,IAAI,MAAM;AAChB,QAAM,MAAM,KAAK,IAAI,CAAC;AACtB,QAAM,MAAM,KAAK,IAAI,CAAC;AACtB,SAAO;AAAA,IACL,GAAG,OAAO,IAAI,KAAK,MAAM,KAAK;AAAA,IAC9B,GAAG,OAAO,IAAI,KAAK,MAAM,KAAK;AAAA,EAChC;AACF;AAoBO,SAAS,iBAAiB,KAAyB;AACxD,QAAM,EAAE,WAAW,aAAa,OAAO,UAAU,OAAO,IAAI;AAC5D,QAAM,MAAe,CAAC;AACtB,MAAI,IAAI,KAAM,QAAO,qBAAqB,IAAI,MAAM,SAAS;AAC7D,MAAI,aAAa,GAAG;AAClB,QAAI,cAAc,EAAG,KAAI,KAAK,EAAE,GAAG,OAAO,GAAG,GAAG,OAAO,EAAE,CAAC;AAC1D,WAAO;AAAA,EACT;AACA,MAAI,UAAU,GAAG;AACf,aAAS,IAAI,GAAG,IAAI,WAAW,IAAK,KAAI,KAAK,MAAM,IAAI,aAAa,GAAG,UAAU,MAAM,CAAC;AACxF,WAAO;AAAA,EACT;AACA,QAAM,UAAW,SAAS,YAAY,KAAM;AAC5C,QAAM,SAAS,eAAe,IAAI,KAAK,IAAI,KAAK,IAAI,OAAO,IAAI,CAAC;AAEhE,WAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,UAAM,MAAM,IAAI;AAChB,UAAM,KAAK,SAAS,KAAK,IAAI,GAAG;AAChC,UAAM,KAAK,CAAC,SAAS,SAAS,KAAK,IAAI,GAAG;AAC1C,QAAI,KAAK,MAAM,IAAI,IAAI,UAAU,MAAM,CAAC;AAAA,EAC1C;AACA,SAAO;AACT;AAEA,SAAS,YAAY,KAA2C;AAC9D,QAAM,IAAI,oBAAI,IAA0B;AACxC,MAAI,IAAI,UAAW,YAAW,KAAK,IAAI,UAAW,GAAE,IAAI,EAAE,OAAO,CAAC;AAClE,SAAO;AACT;AAGO,SAAS,kBAAkB,KAAwB;AACxD,QAAM,UAAU,IAAI,KAAK,IAAI,aAAa,CAAC,GACxC,OAAO,CAAC,aAAa,SAAS,QAAQ,OAAO,UAAU,SAAS,KAAK,KACjE,SAAS,SAAS,KAAK,SAAS,QAAQ,IAAI,SAAS,EACzD,IAAI,CAAC,aAAa,SAAS,KAAK,CAAC;AACpC,SAAO,KAAK,IAAI,GAAG,IAAI,YAAY,QAAQ,IAAI;AACjD;AAqCO,SAAS,eAAe,KAAgB,WAAkD;AAC/F,QAAM,KAAK,YAAY,GAAG;AAC1B,SAAO,iBAAiB,GAAG,EAAE,IAAI,CAAC,GAAG,MAAM;AACzC,UAAM,IAAI,GAAG,IAAI,CAAC;AAClB,UAAM,gBAAgB,sBAAsB,CAAC;AAI7C,UAAM,iBAAiB,mBAAmB,KAAK,GAAG,CAAC;AACnD,UAAM,aAAa,EAAE,GAAG,IAAI,YAAY,GAAG,GAAG,WAAW;AACzD,WAAO;AAAA,MACL,OAAO;AAAA,MACP,GAAG,EAAE,KAAK,GAAG,MAAM;AAAA,MACnB,GAAG,EAAE,KAAK,GAAG,MAAM;AAAA,MACnB,OAAO;AAAA,MACP,cAAc,iBAAiB,KAAK,GAAG,WAAW,CAAC;AAAA,MACnD,aAAa,GAAG,eAAe,IAAI;AAAA,MACnC,SAAS,CAAC,CAAC,GAAG;AAAA,MACd,YAAY,cAAc,SAAS;AAAA,MACnC;AAAA,MACA,qBAAqB,GAAG;AAAA,MACxB,YAAY,OAAO,OAAO,UAAU,EAAE,KAAK,CAAC,UAAU,UAAU,UAAa,UAAU,SAAS,UAAU,EAAE,IAAI,aAAa;AAAA,MAC7H,SAAS,GAAG,mBAAmB,IAAI;AAAA,MACnC,YAAY,GAAG;AAAA,IACjB;AAAA,EACF,CAAC;AACH;AAEO,SAAS,UAAU,KAAgC;AACxD,QAAM,QAAwB,CAAC;AAC/B,aAAW,QAAQ,eAAe,GAAG,GAAG;AACtC,QAAI,KAAK,QAAS;AAClB,UAAM,KAAK;AAAA,MACT,IAAI,GAAG,IAAI,EAAE,IAAI,KAAK,KAAK;AAAA,MAC3B,OAAO,KAAK;AAAA,MACZ,cAAc,KAAK,iBAAiB,KAAK,QAAQ,SAAY,KAAK;AAAA,MAClE,GAAG,KAAK;AAAA,MACR,GAAG,KAAK;AAAA,MACR,OAAO,IAAI;AAAA,MACX,aAAa,KAAK;AAAA,MAClB,YAAY,KAAK,cAAc;AAAA,MAC/B,eAAe,KAAK,cAAc,SAAS,KAAK,gBAAgB;AAAA,MAChE,qBAAqB,KAAK;AAAA,MAC1B,YAAY,KAAK;AAAA,MACjB,SAAS,KAAK;AAAA,MACd,YAAY,KAAK;AAAA,IACnB,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAYO,SAAS,sBAAsB,GAAqC;AACzE,MAAI,EAAE,iBAAkB,QAAO,EAAE,GAAG,EAAE,iBAAiB;AACvD,QAAM,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,EAAE,QAAQ,CAAC,OAAO,QAAQ;AACtE,QAAM,QAAS,CAAC,OAAO,UAAU,QAAQ,OAAO,EAAY,OAAO,CAAC,SAAS,QAAQ,SAAS,IAAI,CAAC;AACnG,QAAM,SAA8B,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,EAAE;AAC3E,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,EAAE,SAAS,CAAC;AAC7C,WAAS,QAAQ,GAAG,QAAQ,GAAG,QAAS,QAAO,MAAM,QAAQ,MAAM,MAAM,CAAC,KAAK;AAC/E,SAAO;AACT;AAIO,SAAS,iBAAiB,GAAiC;AAChE,QAAM,QAAyB,CAAC;AAChC,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,EAAE,SAAS,CAAC;AAC7C,MAAI,MAAM,EAAG,QAAO;AACpB,QAAM,YAAY,IAAI,KAAK,EAAE,aAAa,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,SAAS,OAAO,QAAQ,CAAC,CAAC;AAC3F,QAAM,KAAK,CAAC,OAAe,GAAW,GAAW,SAAwC;AACvF,UAAM,WAAW,UAAU,IAAI,KAAK;AACpC,UAAM,gBAAgB,sBAAsB,QAAQ;AACpD,UAAM,QAAQ,UAAU,SAAS,GAAG,EAAE,KAAK,IAAI,QAAQ,CAAC;AACxD,UAAM,gBAAgB,EAAE,gBAAgB,EAAE;AAC1C,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,cAAc,UAAU,gBAAgB,GAAG,aAAa,IAAI,QAAQ,CAAC;AAAA,MACrE,GAAG,KAAK,UAAU,MAAM;AAAA,MACxB,GAAG,KAAK,UAAU,MAAM;AAAA,MACxB,aAAa,UAAU,eAAe,EAAE;AAAA,MACxC,SAAS,CAAC,CAAC,UAAU;AAAA,MACrB,YAAY,cAAc,SAAS;AAAA,MACnC;AAAA,MACA,qBAAqB,UAAU;AAAA,MAC/B,YAAY,UAAU;AAAA,MACtB,SAAS,UAAU;AAAA,MACnB,YAAY,UAAU;AAAA,MACtB,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,IACzB;AAAA,EACF;AAEA,MAAI,EAAE,UAAU,SAAS;AACvB,UAAM,KAAK,EAAE,UAAU,MAAM;AAC7B,UAAM,OAAO,EAAE,WAAW;AAC1B,UAAM,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,EAAE,WAAW,GAAG,CAAC;AACvD,QAAI,OAAO,OAAO,MAAM,GAAG;AACzB,eAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,cAAM,IAAI,OAAQ,IAAI,IAAK,IAAI,KAAK;AACpC,cAAM,KAAK,GAAG,GAAG,EAAE,OAAO,IAAI,IAAI,KAAK,IAAI,CAAC,GAAG,EAAE,OAAO,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC;AAAA,MAC9E;AACA,aAAO;AAAA,IACT;AACA,UAAM,SAAS,MAAM;AACrB,UAAM,WAAW,IAAI,KAAK,KAAK,UAAU;AACzC,UAAM,QAAQ,OAAO;AACrB,aAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,YAAM,IAAI,QAAS,KAAK,IAAI,KAAM;AAClC,YAAM,KAAK,GAAG,GAAG,EAAE,OAAO,IAAI,IAAI,KAAK,IAAI,CAAC,GAAG,EAAE,OAAO,IAAI,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC;AAAA,IAC9E;AACA,WAAO;AAAA,EACT;AAEA,QAAM,IAAI,EAAE,SAAS;AACrB,QAAM,IAAI,EAAE,UAAU;AACtB,QAAM,SAAS,sBAAsB,CAAC;AACtC,QAAM,QAAQ,CAAC,OAAO,UAAU,QAAQ,OAAO;AAC/C,MAAI,MAAM;AACV,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,OAAO,IAAI;AACzB,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAI;AACJ,UAAI;AACJ,UAAI,SAAS,OAAO;AAClB,iBAAS,CAAC,IAAI,KAAM,IAAI,OAAO,IAAK;AACpC,iBAAS,CAAC,IAAI,IAAI;AAAA,MACpB,WAAW,SAAS,UAAU;AAC5B,iBAAS,CAAC,IAAI,KAAM,IAAI,OAAO,IAAK;AACpC,iBAAS,IAAI,IAAI;AAAA,MACnB,WAAW,SAAS,QAAQ;AAC1B,iBAAS,CAAC,IAAI,IAAI;AAClB,iBAAS,CAAC,IAAI,KAAM,IAAI,OAAO,IAAK;AAAA,MACtC,OAAO;AACL,iBAAS,IAAI,IAAI;AACjB,iBAAS,CAAC,IAAI,KAAM,IAAI,OAAO,IAAK;AAAA,MACtC;AACA,YAAM,QAAQ,MAAM,QAAQ,QAAQ,EAAE,UAAU,EAAE,MAAM;AACxD,YAAM,KAAK,GAAG,OAAO,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAAA,IAC9C;AAAA,EACF;AACA,SAAO;AACT;AAIO,SAAS,oBAAoB,GAAwB;AAC1D,MAAI,EAAE,eAAe,EAAE,kBAAmB,QAAO,KAAK,IAAI,GAAG,KAAK,MAAM,EAAE,SAAS,CAAC;AACpF,SAAO,iBAAiB,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,OAAO,EAAE;AAC7D;AAEO,SAAS,YAAY,GAAgC;AAC1D,SAAO,iBAAiB,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,KAAK,OAAO,EAAE,IAAI,CAAC,UAAU;AAAA,IACxE,IAAI,GAAG,EAAE,EAAE,IAAI,KAAK,KAAK;AAAA,IACzB,OAAO,KAAK;AAAA,IACZ,GAAI,KAAK,iBAAiB,KAAK,QAAQ,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,IAC9E,GAAG,KAAK;AAAA,IACR,GAAG,KAAK;AAAA,IACR,OAAO,EAAE;AAAA,IACT,aAAa,KAAK;AAAA,IAClB,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,IAAI,CAAC;AAAA,IAC9C,GAAI,KAAK,cAAc,SAAS,EAAE,eAAe,KAAK,cAAc,IAAI,CAAC;AAAA,IACzE,GAAI,KAAK,sBAAsB,EAAE,qBAAqB,KAAK,oBAAoB,IAAI,CAAC;AAAA,IACpF,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,IACzD,GAAI,KAAK,UAAU,EAAE,SAAS,KAAK,QAAQ,IAAI,CAAC;AAAA,IAChD,GAAI,KAAK,aAAa,EAAE,YAAY,KAAK,WAAW,IAAI,CAAC;AAAA,EAC3D,EAAE;AACJ;AAGO,SAAS,YAAY,GAAgC;AAC1D,SAAO;AAAA,IACL;AAAA,MACE,IAAI,GAAG,EAAE,EAAE;AAAA,MACX,OAAO,EAAE;AAAA,MACT,GAAI,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,QAAQ,EAAE,cAAc,EAAE,aAAa,IAAI,CAAC;AAAA,MACvF,GAAG,EAAE,OAAO;AAAA,MACZ,GAAG,EAAE,OAAO;AAAA,MACZ,OAAO,EAAE;AAAA,MACT,aAAa,EAAE;AAAA,MACf,MAAM;AAAA,IACR;AAAA,EACF;AACF;AAGO,SAAS,eAAe,GAAU,MAAwB;AAC/D,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,IAAI,KAAK,QAAQ,IAAI,KAAK;AAC7D,UAAM,KAAK,KAAK,CAAC,EAAE;AACnB,UAAM,KAAK,KAAK,CAAC,EAAE;AACnB,UAAM,KAAK,KAAK,CAAC,EAAE;AACnB,UAAM,KAAK,KAAK,CAAC,EAAE;AACnB,UAAM,MAAM,KAAK,EAAE,MAAM,KAAK,EAAE,KAAK,EAAE,KAAM,KAAK,OAAO,EAAE,IAAI,OAAQ,KAAK,MAAM;AAClF,QAAI,IAAK,UAAS,CAAC;AAAA,EACrB;AACA,SAAO;AACT;AAEA,SAAS,uBAAuB,GAAU,MAAwB;AAChE,SAAO,KAAK,KAAK,CAAC,OAAO,UAAU;AACjC,UAAM,MAAM,MAAM,QAAQ,KAAK,KAAK,MAAM;AAC1C,UAAM,SAAS,EAAE,IAAI,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,EAAE,IAAI,MAAM,MAAM,IAAI,IAAI,MAAM;AACrF,QAAI,KAAK,IAAI,KAAK,IAAI,KAAM,QAAO;AACnC,UAAM,OAAO,EAAE,IAAI,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,EAAE,IAAI,MAAM,MAAM,IAAI,IAAI,MAAM;AACnF,UAAM,iBAAiB,IAAI,IAAI,MAAM,MAAM,KAAK,IAAI,IAAI,MAAM,MAAM;AACpE,WAAO,OAAO,SAAS,OAAO,gBAAgB;AAAA,EAChD,CAAC;AACH;AAEO,SAAS,wBAAwB,GAAU,OAAgB,OAAuC;AACvG,SAAO,eAAe,GAAG,KAAK,KACzB,EAAE,SAAS,CAAC,GAAG,KAAK,CAAC,SAAS,eAAe,GAAG,IAAI,KAAK,uBAAuB,GAAG,IAAI,CAAC;AAC/F;AAkBO,SAAS,kBAAkB,OAAgB,OAAqC;AACrF,SAAO,uBAAuB,OAAO,KAAK,EAAE;AAC9C;AAGA,SAAS,gBAAgB,KAAqB;AAC5C,MAAI,CAAC,IAAI,OAAQ,QAAO,EAAE,GAAG,GAAG,GAAG,EAAE;AACrC,MAAI,IAAI;AACR,MAAI,IAAI;AACR,aAAW,KAAK,KAAK;AACnB,SAAK,EAAE;AACP,SAAK,EAAE;AAAA,EACT;AACA,SAAO,EAAE,GAAG,IAAI,IAAI,QAAQ,GAAG,IAAI,IAAI,OAAO;AAChD;AAQO,SAAS,aAAa,GAAuB;AAClD,UAAQ,EAAE,MAAM;AAAA,IACd,KAAK,OAAO;AACV,YAAM,QAAQ,UAAU,CAAC;AACzB,UAAI,CAAC,MAAM,OAAQ,QAAO,EAAE,GAAG,EAAE,OAAO,GAAG,GAAG,EAAE,OAAO,EAAE;AACzD,UAAI,IAAI;AACR,UAAI,IAAI;AACR,iBAAW,KAAK,OAAO;AACrB,aAAK,EAAE;AACP,aAAK,EAAE;AAAA,MACT;AACA,aAAO,EAAE,GAAG,IAAI,MAAM,QAAQ,GAAG,IAAI,MAAM,OAAO;AAAA,IACpD;AAAA,IACA,KAAK;AAAA,IACL,KAAK;AACH,aAAO,EAAE,GAAG,EAAE,OAAO,GAAG,GAAG,EAAE,OAAO,EAAE;AAAA,IACxC,KAAK;AACH,aAAO,gBAAgB,EAAE,MAAM;AAAA,IACjC,KAAK;AACH,aAAO,gBAAgB,EAAE,OAAO;AAAA,IAClC,KAAK;AACH,aAAO,EAAE,GAAG,EAAE,SAAS,GAAG,GAAG,EAAE,SAAS,EAAE;AAAA,IAC5C,KAAK;AACH,UAAI,EAAE,UAAU,EAAE,OAAO,OAAQ,QAAO,gBAAgB,EAAE,MAAM;AAChE,UAAI,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,SAAS,QAAQ,EAAE,UAAU,MAAM;AACrE,eAAO,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,GAAG,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE;AAAA,MACvD;AACA,aAAO,EAAE,GAAG,EAAE,KAAK,GAAG,GAAG,EAAE,KAAK,EAAE;AAAA,IACpC,KAAK;AACH,aAAO,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,GAAG,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE;AAAA,EACzD;AACF;AAEA,SAAS,WAAW,MAAe,OAAyB;AAC1D,SAAO,KAAK,WAAW,MAAM,UACxB,KAAK,MAAM,CAAC,OAAO,UAAU,MAAM,MAAM,MAAM,KAAK,EAAE,KAAK,MAAM,MAAM,MAAM,KAAK,EAAE,CAAC;AAC5F;AAEA,SAAS,uBAAuB,QAAqB,SAAiC;AACpF,MAAI,OAAO,SAAS,YAAY,CAAC,WAAW,OAAO,QAAQ,QAAQ,OAAO,EAAG,QAAO;AACpF,QAAM,cAAc,OAAO,SAAS,CAAC;AACrC,QAAM,eAAe,QAAQ,SAAS,CAAC;AACvC,SAAO,YAAY,WAAW,aAAa,UACtC,YAAY,MAAM,CAAC,MAAM,UAAU,WAAW,MAAM,aAAa,KAAK,CAAC,CAAC;AAC/E;AASO,SAAS,uBACd,SACA,QAC2B;AAC3B,QAAM,WAAW,QAAQ,OAAO,CAAC,cAA0C,UAAU,SAAS,SAAS;AACvG,QAAM,sBAAsB,8BAA8B,SACtD,OAAO,0BAA0B,mBACjC;AACJ,QAAM,SAAS,aAAa,MAAM;AAClC,QAAM,kBAAkB,sBACpB,SAAS,KAAK,CAAC,aACZ,QAAQ,oBAAoB,QAAQ,QAAQ,wBACzC,uBAAuB,QAAQ,OAAO,KACrC,wBAAwB,QAAQ,QAAQ,SAAS,QAAQ,KAAK,EACpE,IACD;AACJ,SAAO,mBACF,SAAS,KAAK,CAAC,YAAY,wBAAwB,QAAQ,QAAQ,SAAS,QAAQ,KAAK,CAAC;AACjG;AAOO,SAAS,SAAS,KAAwB;AAC/C,MAAI,IAAI,UAAU,IAAI,OAAO,OAAQ,QAAO,IAAI;AAChD,SAAO,CAAC;AAAA,IACN,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,SAAS,IAAI;AAAA,IACb,YAAY,IAAI;AAAA,IAChB,gBAAgB,IAAI;AAAA,IACpB,iBAAiB,IAAI;AAAA,EACvB,CAAC;AACH;AAGO,SAAS,aAAa,KAAe,SAAiC;AAC3E,QAAM,SAAS,SAAS,GAAG;AAC3B,UAAQ,UAAU,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,OAAO,IAAI,OAAO,CAAC,IAAI,WAAW,CAAC;AACnF;AAGO,SAAS,WAAW,KAA8B;AACvD,SAAO,IAAI,UAAU,IAAI,OAAO,SAAS,IAAI,OAAO,QAAQ,CAAC,MAAM,EAAE,OAAO,IAAI,IAAI;AACtF;AAGA,SAAS,gBAAgB,GAAgB,IAAY,IAAyB;AAC5E,QAAM,IAAI,CAAC,QAAsB,EAAE,GAAG,GAAG,IAAI,IAAI,GAAG,GAAG,IAAI,GAAG;AAC9D,QAAM,MAAM,CAAC,MAAwB,EAAE,IAAI,CAAC;AAC5C,UAAQ,EAAE,MAAM;AAAA,IACd,KAAK;AACH,aAAO,EAAE,GAAG,GAAG,QAAQ,EAAE,EAAE,MAAM,EAAE;AAAA,IACrC,KAAK;AAAA,IACL,KAAK;AACH,aAAO,EAAE,GAAG,GAAG,QAAQ,EAAE,EAAE,MAAM,EAAE;AAAA,IACrC,KAAK;AACH,aAAO,EAAE,GAAG,GAAG,QAAQ,IAAI,EAAE,MAAM,GAAG,GAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,EAAE,IAAI,CAAC,EAAG;AAAA,IACxF,KAAK;AACH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,SAAS,IAAI,EAAE,OAAO;AAAA,QACtB,GAAI,EAAE,cAAc,EAAE,aAAa,4BAA4B,EAAE,aAAa,IAAI,EAAE,EAAE,IAAI,CAAC;AAAA,QAC3F,GAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,EAAE,IAAI,CAAC;AAAA,MAC/C;AAAA,IACF,KAAK;AACH,aAAO,EAAE,GAAG,GAAG,UAAU,EAAE,EAAE,QAAQ,EAAE;AAAA,IACzC,KAAK;AACH,aAAO;AAAA,QACL,GAAG;AAAA,QACH,GAAI,EAAE,SAAS,EAAE,QAAQ,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC;AAAA,QAC5C,GAAI,EAAE,KAAK,OAAO,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI,CAAC;AAAA,QACrC,GAAI,EAAE,KAAK,OAAO,EAAE,GAAG,EAAE,IAAI,GAAG,IAAI,CAAC;AAAA,MACvC;AAAA,IACF,KAAK;AACH,aAAO,EAAE,GAAG,GAAG,GAAG,EAAE,IAAI,IAAI,GAAG,EAAE,IAAI,GAAG;AAAA,EAC5C;AACF;AAQO,SAAS,YAAY,KAAe,SAAS,KAAe;AACjE,MAAI,CAAC,IAAI,UAAU,IAAI,OAAO,SAAS,EAAG,QAAO;AACjD,QAAM,UAAyB,CAAC;AAChC,MAAI,OAAO,QAAQ,CAAC,GAAG,MAAM;AAC3B,UAAM,KAAK,CAAC,IAAI;AAChB,eAAW,KAAK,EAAE,QAAS,SAAQ,KAAK,OAAO,IAAI,IAAI,gBAAgB,GAAG,GAAG,EAAE,CAAC;AAAA,EAClF,CAAC;AACD,SAAO,EAAE,GAAG,KAAK,SAAS,QAAQ,OAAU;AAC9C;AAQA,SAAS,mBACP,SACA,OACA,eAOA,cACgB;AAChB,QAAM,MAAsB,CAAC;AAC7B,QAAM,YAAY,0BAA0B,OAAO;AAEnD,aAAW,OAAO,SAAS;AACzB,QAAI,QAAwB,CAAC;AAC7B,QAAI,IAAI,SAAS,MAAO,SAAQ,UAAU,GAAG;AAAA,aACpC,IAAI,SAAS,QAAS,SAAQ,YAAY,GAAG;AAAA,aAC7C,IAAI,SAAS,QAAS,SAAQ,YAAY,GAAG;AACtD,QAAI,CAAC,MAAM,OAAQ;AACnB,QAAI,IAAI,SAAS,OAAO;AACtB,YAAM,UAAU,UAAU,IAAI,IAAI,EAAE;AACpC,UAAI,SAAS;AACX,cAAM,YAAY,IAAI,KAAK,IAAI,aAAa,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,SAAS,OAAO,QAAQ,CAAC,CAAC;AAC7F,mBAAW,QAAQ,OAAO;AACxB,gBAAM,gBAAgB,OAAO,KAAK,GAAG,MAAM,KAAK,GAAG,YAAY,GAAG,IAAI,CAAC,CAAC;AACxE,cAAI,CAAC,OAAO,UAAU,aAAa,EAAG;AACtC,eAAK,eAAe,QAAQ;AAC5B,eAAK,mBAAmB,QAAQ,kBAAkB;AAIlD,cAAI,CAAC,UAAU,IAAI,aAAa,GAAG,cAAc;AAC/C,iBAAK,eAAe,iBAAiB,KAAK,eAAe,OAAO;AAAA,UAClE;AACA,eAAK,YAAY,QAAQ;AAAA,QAC3B;AAAA,MACF;AAAA,IACF;AACA,UAAM,QAAQ,uBAAuB,SAAS,GAAG;AACjD,UAAM,gBAAgB,OAAO;AAC7B,UAAM,OAAO,OAAO,OAAO,OAAO,KAAK,CAAC,cAAc,UAAU,OAAO,MAAM,IAAI,IAAI;AACrF,UAAM,gBAAgB,MAAM,cAAc;AAC1C,eAAW,QAAQ,OAAO;AACxB,UAAI,cAAe,MAAK,YAAY;AAEpC,UAAI,aAAc,MAAK,YAAY;AACnC,UAAI,MAAO,MAAK,YAAY,MAAM,oBAAoB,MAAM;AAC5D,UAAI,OAAO,KAAM,MAAK,SAAS,MAAM;AACrC,UAAI,cAAe,MAAK,aAAa,EAAE,GAAG,cAAc;AAAA,IAC1D;AACA,QAAI,KAAK,GAAG,KAAK;AAAA,EACnB;AACA,SAAO;AACT;AAKO,SAAS,YAAY,KAAe,UAA8B,CAAC,GAAmB;AAC3F,MAAI,IAAI,QAAQ,QAAQ;AACtB,UAAMC,OAAsB,CAAC;AAC7B,eAAW,SAAS,IAAI,QAAQ;AAC9B,YAAM,aAAa,MAAM,cAAc,IAAI;AAE3C,YAAM,YAAY,MAAM,mBAAmB,IAAI;AAC/C,YAAM,QAAQ,mBAAmB,MAAM,SAAS,IAAI,OAAO,YAAY,SAAS;AAChF,uBAAiB,MAAM,SAAS,MAAM,cAAc,IAAI,YAAY,MAAM,eAAe,GAAG,KAAK;AACjG,MAAAA,KAAI,KAAK,GAAG,KAAK;AAAA,IACnB;AACA,WAAOA;AAAA,EACT;AACA,QAAM,MAAM,mBAAmB,IAAI,SAAS,IAAI,OAAO,IAAI,YAAY,IAAI,eAAe;AAC1F,mBAAiB,IAAI,SAAS,IAAI,YAAY,QAAQ,oBAAoB,GAAG,GAAG;AAChF,SAAO;AACT;AAkBA,SAAS,iBACP,SACA,OACA,kBACA,OACM;AACN,QAAM,WAAW,QAAQ,OAAO,CAAC,MAA0B,EAAE,SAAS,SAAS;AAC/E,QAAM,cAAc,mBAAmB,KAClC,SAAS,KAAK,CAAC,MAAM,EAAE,WAAW,UAAa,EAAE,SAAS,WAAc,EAAE,aAAa,KAAK,CAAC;AAClG,MAAI,CAAC,SAAS,UAAU,CAAC,eAAe,CAAC,OAAO;AAC9C,eAAW,QAAQ,MAAO,MAAK,aAAa,mBAAmB;AAC/D;AAAA,EACF;AAGA,QAAM,QAAQ,IAAI,MAA4B,MAAM,MAAM;AAC1D,QAAM,MAAM,oBAAI,IAA8C;AAC9D,QAAM,iBAAiB,oBAAI,IAAsB;AACjD,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,MAAM,SAAS,KAAK,CAAC,MAAM,wBAAwB,EAAE,GAAG,KAAK,GAAG,GAAG,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,KAAK;AAC3G,UAAM,CAAC,IAAI;AACX,QAAI,CAAC,IAAK;AACV,QAAI,CAAC,IAAI,IAAI,IAAI,EAAE,EAAG,KAAI,IAAI,IAAI,IAAI,gBAAgB,KAAK,EAAE,iBAAiB,CAAC,CAAC;AAChF,UAAM,OAAO,eAAe,IAAI,IAAI,EAAE;AACtC,QAAI,KAAM,MAAK,KAAK,CAAC;AAAA,QAAQ,gBAAe,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC;AAAA,EAC7D;AAUA,QAAM,YAAY,IAAI,MAA0B,MAAM,MAAM,EAAE,KAAK,MAAS;AAC5E,aAAW,CAAC,OAAO,OAAO,KAAK,gBAAgB;AAC7C,UAAM,IAAI,IAAI,IAAI,KAAK;AACvB,QAAI,CAAC,EAAG;AACR,UAAM,UAAU,EAAE,OAAO,IAAI,KAAK,IAAK,EAAE,OAAO,KAAK,KAAM,GAAG,IAAI;AAClE,UAAM,YAAY,eAAe,OAAO,OAAO,SAAS,KAAK;AAC7D,eAAW,OAAO,UAAU,MAAM;AAChC,YAAM,QAAQ,IAAI,aAAa,wBAAwB;AACvD,YAAM,QAAQ,EAAE,SAAS;AACzB,iBAAW,MAAM,IAAI,YAAa,WAAU,EAAE,IAAI;AAAA,IACpD;AAAA,EACF;AAEA,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,MAAM,MAAM,CAAC;AACnB,QAAI,CAAC,KAAK;AAAE,WAAK,aAAa,mBAAmB;AAAqB;AAAA,IAAU;AAChF,UAAM,IAAI,IAAI,IAAI,IAAI,EAAE;AACxB,SAAK,cAAc,UAAU,CAAC,KAAK,EAAE,UAAU;AAAA,EACjD;AACF;AAEA,IAAM,MAAM;AAGL,SAAS,YAAY,KAAwE;AAClG,MAAI,OAAO;AACX,MAAI,OAAO;AACX,MAAI,OAAO;AACX,MAAI,OAAO;AAEX,QAAM,MAAM,CAAC,GAAW,MAAc;AACpC,QAAI,IAAI,KAAM,QAAO;AACrB,QAAI,IAAI,KAAM,QAAO;AACrB,QAAI,IAAI,KAAM,QAAO;AACrB,QAAI,IAAI,KAAM,QAAO;AAAA,EACvB;AAEA,aAAW,KAAK,YAAY,GAAG,EAAG,KAAI,EAAE,GAAG,EAAE,CAAC;AAE9C,aAAW,OAAO,WAAW,GAAG,GAAG;AACjC,QAAI,IAAI,SAAS,UAAU;AACzB,iBAAW,KAAK,IAAI,OAAQ,KAAI,EAAE,GAAG,EAAE,CAAC;AAAA,IAC1C,WAAW,IAAI,SAAS,WAAW;AACjC,iBAAW,KAAK,IAAI,QAAS,KAAI,EAAE,GAAG,EAAE,CAAC;AAAA,IAC3C,WAAW,IAAI,SAAS,cAAc;AACpC,UAAI,IAAI,GAAG,IAAI,CAAC;AAChB,UAAI,IAAI,IAAI,IAAI,OAAO,IAAI,IAAI,IAAI,MAAM;AAAA,IAC3C,WAAW,IAAI,SAAS,SAAS;AAC/B,UAAI,IAAI,UAAU,IAAI,OAAO,QAAQ;AACnC,mBAAW,KAAK,IAAI,OAAQ,KAAI,EAAE,GAAG,EAAE,CAAC;AAAA,MAC1C,WAAW,IAAI,KAAK,QAAQ,IAAI,KAAK,QAAQ,IAAI,SAAS,QAAQ,IAAI,UAAU,MAAM;AACpF,YAAI,IAAI,GAAG,IAAI,CAAC;AAChB,YAAI,IAAI,IAAI,IAAI,OAAO,IAAI,IAAI,IAAI,MAAM;AAAA,MAC3C;AAAA,IACF,WAAW,IAAI,SAAS,SAAS;AAC/B,YAAM,MAAM,oBAAoB;AAChC,YAAM,MACJ,IAAI,UAAU,WACT,IAAI,UAAU,MAAM,MACrB,KAAK,KAAK,IAAI,SAAS,MAAM,IAAI,IAAI,UAAU,MAAM,CAAC,IAAI;AAChE,UAAI,IAAI,OAAO,IAAI,KAAK,IAAI,OAAO,IAAI,GAAG;AAC1C,UAAI,IAAI,OAAO,IAAI,KAAK,IAAI,OAAO,IAAI,GAAG;AAAA,IAC5C,WAAW,IAAI,SAAS,SAAS;AAC/B,YAAM,MAAM,KAAK,IAAI,IAAI,OAAO,IAAI,MAAM,IAAI;AAC9C,UAAI,IAAI,OAAO,IAAI,KAAK,IAAI,OAAO,IAAI,GAAG;AAC1C,UAAI,IAAI,OAAO,IAAI,KAAK,IAAI,OAAO,IAAI,GAAG;AAAA,IAC5C,WAAW,IAAI,SAAS,QAAQ;AAC9B,YAAM,IAAI,IAAI,WAAW,IAAI,KAAK,SAAS;AAC3C,UAAI,IAAI,SAAS,GAAG,IAAI,SAAS,CAAC;AAClC,UAAI,IAAI,SAAS,IAAI,GAAG,IAAI,SAAS,IAAI,IAAI,QAAQ;AAAA,IACvD;AAAA,EACF;AAEA,aAAW,SAAS,CAAC,IAAI,gBAAgB,IAAI,eAAe,GAAG;AAC7D,QAAI,CAAC,MAAO;AACZ,UAAM,EAAE,QAAQ,MAAM,IAAI;AAC1B,UAAM,KAAM,QAAQ,IAAK;AACzB,QAAI,OAAO,IAAI,QAAQ,GAAG,OAAO,IAAI,KAAK,CAAC;AAC3C,QAAI,OAAO,IAAI,QAAQ,GAAG,OAAO,IAAI,KAAK,CAAC;AAAA,EAC7C;AAGA,MAAI,CAAC,SAAS,IAAI,GAAG;AACnB,UAAM,IAAI,IAAI,cAAc,EAAE,GAAG,GAAG,GAAG,EAAE;AACzC,WAAO,EAAE,GAAG,EAAE,IAAI,KAAK,GAAG,EAAE,IAAI,KAAK,OAAO,KAAK,QAAQ,IAAI;AAAA,EAC/D;AAEA,SAAO;AAAA,IACL,GAAG,OAAO;AAAA,IACV,GAAG,OAAO;AAAA,IACV,OAAO,OAAO,OAAO,MAAM;AAAA,IAC3B,QAAQ,OAAO,OAAO,MAAM;AAAA,EAC9B;AACF;","names":["core","out"]}
|