@seatlayer/core 0.48.2 → 0.49.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-I6DPX3EM.js → chunk-BE3F7CT3.js} +6 -5
- package/dist/chunk-BE3F7CT3.js.map +1 -0
- package/dist/{chunk-TCTAS4Z2.js → chunk-GD267BFB.js} +36 -8
- package/dist/chunk-GD267BFB.js.map +1 -0
- package/dist/core/seatConfidence.cjs +76 -0
- package/dist/core/seatConfidence.cjs.map +1 -0
- package/dist/core/seatConfidence.d.cts +18 -0
- package/dist/core/seatConfidence.d.ts +18 -0
- package/dist/core/seatConfidence.js +51 -0
- package/dist/core/seatConfidence.js.map +1 -0
- package/dist/index.cjs +35 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/{types-CE63BK0j.d.cts → types-DR54nqKL.d.cts} +58 -1
- package/dist/{types-CE63BK0j.d.ts → types-DR54nqKL.d.ts} +58 -1
- package/dist/view3d/crossfade/panorama.cjs +5 -4
- package/dist/view3d/crossfade/panorama.cjs.map +1 -1
- package/dist/view3d/crossfade/panorama.d.cts +5 -1
- package/dist/view3d/crossfade/panorama.d.ts +5 -1
- package/dist/view3d/crossfade/panorama.js +1 -1
- package/dist/view3d/index.cjs +781 -255
- package/dist/view3d/index.cjs.map +1 -1
- package/dist/view3d/index.d.cts +22 -1
- package/dist/view3d/index.d.ts +22 -1
- package/dist/view3d/index.js +779 -254
- package/dist/view3d/index.js.map +1 -1
- package/package.json +15 -2
- package/dist/chunk-I6DPX3EM.js.map +0 -1
- package/dist/chunk-TCTAS4Z2.js.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { a as RowObject, A as AccessibilityType, S as SeatOverride, b as SeatViewMetadata, L as LabelStyle, c as RectTableSide, C as ChartDoc, d as ChartObject, B as BoothObject, E as ExpandedSeat, T as TableObject, F as Floor, P as Point, e as SectionObject, f as RectTableSeatCounts, G as GAAreaObject, g as GAInventorySegment, h as RendererQualityEvidence, i as RenderedFreeTextEvidence, I as ISeatmapRenderer, j as RendererOptions, k as SeatStatus, l as RendererViewMode, m as LodRung, n as CategoryTier, o as SeatCommercialAttributes, p as RendererCallbacks } from './types-DR54nqKL.cjs';
|
|
2
|
+
export { q as ACCESSIBILITY_RING_COLOR, r as ACCESSIBILITY_TYPES, s as AccessibilityMeta, t as AuthoringObjectState, u as CHART_STORAGE_KEY, v as Category, w as ChartReferenceImage, x as ChartTheme, y as CubicPath, D as DecorImageObject, z as LABEL_STYLE_MAX_SIZE, H as LABEL_STYLE_MIN_SIZE, J as LabelPresentation, O as ObjectEditorState, K as ReferenceAccessibilitySource, M as ReferenceCalibration, N as ReferenceCategorySource, Q as ReferenceDerivedScale, U as ReferenceInventoryExclusionSource, V as ReferenceInventorySource, W as ReferenceScanProposal, X as ReferenceScanRowProposal, Y as ReferenceScanSectionProposal, Z as ReferenceSeatSeed, _ as ReferenceSectionTraceBatchInput, $ as ReferenceSectionTraceBatchProposal, a0 as ReferenceSectionTraceInput, a1 as ReferenceSectionTraceProposal, a2 as RenderedBookableLabelEvidence, a3 as RenderedGAAreaEvidence, a4 as RenderedHierarchyLabelEvidence, a5 as RenderedLabelHiddenReason, R as ResolvedSeatConfidenceEvidence, a6 as SEAT_COMMERCIAL_MARKS, a7 as SURROUNDINGS_SHAPE_ROLES, a8 as SeatCommercialMark, a9 as SeatCommercialMeta, aa as SeatConfidenceCoverage, ab as SeatConfidenceEvidence, ac as SeatConfidenceModelLevel, ad as SeatConfidenceModeledTarget, ae as SeatConfidenceRealityLevel, af as SeatViewCoverage, ag as SectionOutlinePath, ah as SectionPathSegment, ai as SelectionLayer, aj as ShapeLineCap, ak as ShapeLineEnding, al as ShapeLineJoin, am as ShapeObject, an as TextAlignment, ao as TextBackground, ap as TextObject, aq as ZoneDef, ar as accessibilityMeta, as as accessibilityRingColor, at as layerOf, au as seatCommercialMeta } from './types-DR54nqKL.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The segmented-row model — one logical row made of several physical rows.
|
|
@@ -35,6 +35,7 @@ interface SegmentedRowPlacement {
|
|
|
35
35
|
canonical: RowObject;
|
|
36
36
|
viewFromSeatUrl?: string;
|
|
37
37
|
viewFromSeatMeta?: RowObject['viewFromSeatMeta'];
|
|
38
|
+
confidenceEvidence?: RowObject['confidenceEvidence'];
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { a as RowObject, A as AccessibilityType, S as SeatOverride, b as SeatViewMetadata, L as LabelStyle, c as RectTableSide, C as ChartDoc, d as ChartObject, B as BoothObject, E as ExpandedSeat, T as TableObject, F as Floor, P as Point, e as SectionObject, f as RectTableSeatCounts, G as GAAreaObject, g as GAInventorySegment, h as RendererQualityEvidence, i as RenderedFreeTextEvidence, I as ISeatmapRenderer, j as RendererOptions, k as SeatStatus, l as RendererViewMode, m as LodRung, n as CategoryTier, o as SeatCommercialAttributes, p as RendererCallbacks } from './types-DR54nqKL.js';
|
|
2
|
+
export { q as ACCESSIBILITY_RING_COLOR, r as ACCESSIBILITY_TYPES, s as AccessibilityMeta, t as AuthoringObjectState, u as CHART_STORAGE_KEY, v as Category, w as ChartReferenceImage, x as ChartTheme, y as CubicPath, D as DecorImageObject, z as LABEL_STYLE_MAX_SIZE, H as LABEL_STYLE_MIN_SIZE, J as LabelPresentation, O as ObjectEditorState, K as ReferenceAccessibilitySource, M as ReferenceCalibration, N as ReferenceCategorySource, Q as ReferenceDerivedScale, U as ReferenceInventoryExclusionSource, V as ReferenceInventorySource, W as ReferenceScanProposal, X as ReferenceScanRowProposal, Y as ReferenceScanSectionProposal, Z as ReferenceSeatSeed, _ as ReferenceSectionTraceBatchInput, $ as ReferenceSectionTraceBatchProposal, a0 as ReferenceSectionTraceInput, a1 as ReferenceSectionTraceProposal, a2 as RenderedBookableLabelEvidence, a3 as RenderedGAAreaEvidence, a4 as RenderedHierarchyLabelEvidence, a5 as RenderedLabelHiddenReason, R as ResolvedSeatConfidenceEvidence, a6 as SEAT_COMMERCIAL_MARKS, a7 as SURROUNDINGS_SHAPE_ROLES, a8 as SeatCommercialMark, a9 as SeatCommercialMeta, aa as SeatConfidenceCoverage, ab as SeatConfidenceEvidence, ac as SeatConfidenceModelLevel, ad as SeatConfidenceModeledTarget, ae as SeatConfidenceRealityLevel, af as SeatViewCoverage, ag as SectionOutlinePath, ah as SectionPathSegment, ai as SelectionLayer, aj as ShapeLineCap, ak as ShapeLineEnding, al as ShapeLineJoin, am as ShapeObject, an as TextAlignment, ao as TextBackground, ap as TextObject, aq as ZoneDef, ar as accessibilityMeta, as as accessibilityRingColor, at as layerOf, au as seatCommercialMeta } from './types-DR54nqKL.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* The segmented-row model — one logical row made of several physical rows.
|
|
@@ -35,6 +35,7 @@ interface SegmentedRowPlacement {
|
|
|
35
35
|
canonical: RowObject;
|
|
36
36
|
viewFromSeatUrl?: string;
|
|
37
37
|
viewFromSeatMeta?: RowObject['viewFromSeatMeta'];
|
|
38
|
+
confidenceEvidence?: RowObject['confidenceEvidence'];
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
/**
|
package/dist/index.js
CHANGED
|
@@ -139,6 +139,50 @@ interface SeatViewMetadata {
|
|
|
139
139
|
captureEyeHeightM?: number;
|
|
140
140
|
eventConfigurationId?: string;
|
|
141
141
|
}
|
|
142
|
+
/** How broadly a confidence record applies to the selected inventory unit.
|
|
143
|
+
* This is resolved by the layout engine from the authoring scope; authors do
|
|
144
|
+
* not get to label venue-wide evidence as exact-seat evidence. */
|
|
145
|
+
type SeatConfidenceCoverage = 'exact-seat' | 'row-representative' | 'section-representative' | 'floor-representative' | 'venue-representative';
|
|
146
|
+
type SeatConfidenceModelLevel = 'generated' | 'geometry-verified' | 'configuration-verified';
|
|
147
|
+
type SeatConfidenceRealityLevel = 'none' | 'comparable-build-reference' | 'actual-build-checked';
|
|
148
|
+
/** A bounded, explainable visibility result. It is deliberately a modeled
|
|
149
|
+
* target score—not a promise that this percentage of a show will be visible. */
|
|
150
|
+
interface SeatConfidenceModeledTarget {
|
|
151
|
+
label: string;
|
|
152
|
+
clearRayCount: number;
|
|
153
|
+
totalRayCount: number;
|
|
154
|
+
classification: 'clear' | 'partial' | 'obstructed';
|
|
155
|
+
/** Buyer-safe method name, e.g. "45-ray screen target model". */
|
|
156
|
+
methodLabel: string;
|
|
157
|
+
occluderLabels?: string[];
|
|
158
|
+
}
|
|
159
|
+
/** Versioned source evidence for a seat-confidence passport.
|
|
160
|
+
*
|
|
161
|
+
* Model verification and reality evidence are independent dimensions. A
|
|
162
|
+
* comparable photograph must never outrank a venue-approved current event
|
|
163
|
+
* model, and a strong model must never silently become real-world evidence.
|
|
164
|
+
* `coverage` is added only after scope resolution on ExpandedSeat. */
|
|
165
|
+
interface SeatConfidenceEvidence {
|
|
166
|
+
schema: 'seatlayer.seat-confidence/1';
|
|
167
|
+
evidenceId: string;
|
|
168
|
+
modelVersion: string;
|
|
169
|
+
evidenceKind: 'synthetic-model' | 'organizer-approved' | 'calibrated-provider';
|
|
170
|
+
modelLevel: SeatConfidenceModelLevel;
|
|
171
|
+
realityLevel?: SeatConfidenceRealityLevel;
|
|
172
|
+
/** Required for configuration verification and actual-build checking. */
|
|
173
|
+
eventConfigurationId?: string;
|
|
174
|
+
assessedAt?: string;
|
|
175
|
+
validUntil?: string;
|
|
176
|
+
/** Buyer-safe approving organization or role; never a named individual. */
|
|
177
|
+
approvedByRole?: string;
|
|
178
|
+
/** Buyer-safe evidence provenance, not an asset URL or private filename. */
|
|
179
|
+
sourceLabel?: string;
|
|
180
|
+
limitations?: string[];
|
|
181
|
+
modeledTarget?: SeatConfidenceModeledTarget;
|
|
182
|
+
}
|
|
183
|
+
interface ResolvedSeatConfidenceEvidence extends SeatConfidenceEvidence {
|
|
184
|
+
coverage: SeatConfidenceCoverage;
|
|
185
|
+
}
|
|
142
186
|
interface SeatOverride {
|
|
143
187
|
/** 0-based seat index within the row. */
|
|
144
188
|
index: number;
|
|
@@ -170,6 +214,8 @@ interface SeatOverride {
|
|
|
170
214
|
viewFromSeatUrl?: string;
|
|
171
215
|
/** Calibration/truth for `viewFromSeatUrl`; ignored when the URL is absent. */
|
|
172
216
|
viewFromSeatMeta?: SeatViewMetadata;
|
|
217
|
+
/** Seat-scoped confidence evidence, independent of photo availability. */
|
|
218
|
+
confidenceEvidence?: SeatConfidenceEvidence;
|
|
173
219
|
/** Per-seat label size/color override; falls back to the row/theme default.
|
|
174
220
|
* Size is clamped to LABEL_STYLE_MIN_SIZE..MAX_SIZE; color is passed through
|
|
175
221
|
* the shared auto-contrast rule at paint time (see {@link LabelStyle}). */
|
|
@@ -376,6 +422,7 @@ interface RowObject extends AuthoringObjectState {
|
|
|
376
422
|
labelPresentation?: LabelPresentation;
|
|
377
423
|
viewFromSeatUrl?: string;
|
|
378
424
|
viewFromSeatMeta?: SeatViewMetadata;
|
|
425
|
+
confidenceEvidence?: SeatConfidenceEvidence;
|
|
379
426
|
/** Presentation intent for a continuous node-defined centreline. */
|
|
380
427
|
smoothing?: boolean;
|
|
381
428
|
};
|
|
@@ -452,6 +499,8 @@ interface RowObject extends AuthoringObjectState {
|
|
|
452
499
|
*/
|
|
453
500
|
viewFromSeatUrl?: string;
|
|
454
501
|
viewFromSeatMeta?: SeatViewMetadata;
|
|
502
|
+
/** Row-scoped confidence evidence inherited by seats without a closer record. */
|
|
503
|
+
confidenceEvidence?: SeatConfidenceEvidence;
|
|
455
504
|
/** Default commercial attributes inherited by seats without an override. */
|
|
456
505
|
commercial?: SeatCommercialAttributes;
|
|
457
506
|
}
|
|
@@ -723,6 +772,8 @@ interface SectionObject extends AuthoringObjectState {
|
|
|
723
772
|
*/
|
|
724
773
|
viewFromSeatUrl?: string;
|
|
725
774
|
viewFromSeatMeta?: SeatViewMetadata;
|
|
775
|
+
/** Section-scoped confidence evidence inherited after seat and row evidence. */
|
|
776
|
+
confidenceEvidence?: SeatConfidenceEvidence;
|
|
726
777
|
labelPresentation?: LabelPresentation;
|
|
727
778
|
/**
|
|
728
779
|
* Stable management/inventory identity shared by disconnected visual
|
|
@@ -996,6 +1047,8 @@ interface Floor {
|
|
|
996
1047
|
*/
|
|
997
1048
|
viewFromSeatUrl?: string;
|
|
998
1049
|
viewFromSeatMeta?: SeatViewMetadata;
|
|
1050
|
+
/** Floor-scoped confidence evidence inherited before the venue default. */
|
|
1051
|
+
confidenceEvidence?: SeatConfidenceEvidence;
|
|
999
1052
|
}
|
|
1000
1053
|
interface ReferenceCalibration {
|
|
1001
1054
|
type: 'two-point';
|
|
@@ -1244,6 +1297,8 @@ interface ChartDoc {
|
|
|
1244
1297
|
*/
|
|
1245
1298
|
viewFromSeatUrl?: string;
|
|
1246
1299
|
viewFromSeatMeta?: SeatViewMetadata;
|
|
1300
|
+
/** Venue-scoped confidence evidence. It does not require panorama media. */
|
|
1301
|
+
confidenceEvidence?: SeatConfidenceEvidence;
|
|
1247
1302
|
}
|
|
1248
1303
|
interface ExpandedSeat {
|
|
1249
1304
|
/** Stable id: `${rowId}:${index}` */
|
|
@@ -1281,6 +1336,8 @@ interface ExpandedSeat {
|
|
|
1281
1336
|
viewUrl?: string;
|
|
1282
1337
|
/** Calibration/truth inherited from the same scope that supplied `viewUrl`. */
|
|
1283
1338
|
viewMeta?: SeatViewMetadata;
|
|
1339
|
+
/** Closest configuration-valid evidence plus its engine-resolved coverage. */
|
|
1340
|
+
confidenceEvidence?: ResolvedSeatConfidenceEvidence;
|
|
1284
1341
|
/** Per-seat label size/color override; absent = inherit the row/theme default. */
|
|
1285
1342
|
labelStyle?: LabelStyle;
|
|
1286
1343
|
/**
|
|
@@ -1721,4 +1778,4 @@ interface ISeatmapRenderer {
|
|
|
1721
1778
|
/** localStorage key the Designer writes and the Picker reads. */
|
|
1722
1779
|
declare const CHART_STORAGE_KEY = "seatmap.chart";
|
|
1723
1780
|
|
|
1724
|
-
export { type
|
|
1781
|
+
export { type ReferenceSectionTraceBatchProposal as $, type AccessibilityType as A, type BoothObject as B, type ChartDoc as C, type DecorImageObject as D, type ExpandedSeat as E, type Floor as F, type GAAreaObject as G, LABEL_STYLE_MIN_SIZE as H, type ISeatmapRenderer as I, type LabelPresentation as J, type ReferenceAccessibilitySource as K, type LabelStyle as L, type ReferenceCalibration as M, type ReferenceCategorySource as N, type ObjectEditorState as O, type Point as P, type ReferenceDerivedScale as Q, type ResolvedSeatConfidenceEvidence as R, type SeatOverride as S, type TableObject as T, type ReferenceInventoryExclusionSource as U, type ReferenceInventorySource as V, type ReferenceScanProposal as W, type ReferenceScanRowProposal as X, type ReferenceScanSectionProposal as Y, type ReferenceSeatSeed as Z, type ReferenceSectionTraceBatchInput as _, type RowObject as a, type ReferenceSectionTraceInput as a0, type ReferenceSectionTraceProposal as a1, type RenderedBookableLabelEvidence as a2, type RenderedGAAreaEvidence as a3, type RenderedHierarchyLabelEvidence as a4, type RenderedLabelHiddenReason as a5, SEAT_COMMERCIAL_MARKS as a6, SURROUNDINGS_SHAPE_ROLES as a7, type SeatCommercialMark as a8, type SeatCommercialMeta as a9, type SeatConfidenceCoverage as aa, type SeatConfidenceEvidence as ab, type SeatConfidenceModelLevel as ac, type SeatConfidenceModeledTarget as ad, type SeatConfidenceRealityLevel as ae, type SeatViewCoverage as af, type SectionOutlinePath as ag, type SectionPathSegment as ah, type SelectionLayer as ai, type ShapeLineCap as aj, type ShapeLineEnding as ak, type ShapeLineJoin as al, type ShapeObject as am, type TextAlignment as an, type TextBackground as ao, type TextObject as ap, type ZoneDef as aq, accessibilityMeta as ar, accessibilityRingColor as as, layerOf as at, seatCommercialMeta as au, type SeatViewMetadata as b, type RectTableSide as c, type ChartObject as d, type SectionObject as e, type RectTableSeatCounts as f, type GAInventorySegment as g, type RendererQualityEvidence as h, type RenderedFreeTextEvidence as i, type RendererOptions as j, type SeatStatus as k, type RendererViewMode as l, type LodRung as m, type CategoryTier as n, type SeatCommercialAttributes as o, type RendererCallbacks as p, ACCESSIBILITY_RING_COLOR as q, ACCESSIBILITY_TYPES as r, type AccessibilityMeta as s, type AuthoringObjectState as t, CHART_STORAGE_KEY as u, type Category as v, type ChartReferenceImage as w, type ChartTheme as x, type CubicPath as y, LABEL_STYLE_MAX_SIZE as z };
|
|
@@ -139,6 +139,50 @@ interface SeatViewMetadata {
|
|
|
139
139
|
captureEyeHeightM?: number;
|
|
140
140
|
eventConfigurationId?: string;
|
|
141
141
|
}
|
|
142
|
+
/** How broadly a confidence record applies to the selected inventory unit.
|
|
143
|
+
* This is resolved by the layout engine from the authoring scope; authors do
|
|
144
|
+
* not get to label venue-wide evidence as exact-seat evidence. */
|
|
145
|
+
type SeatConfidenceCoverage = 'exact-seat' | 'row-representative' | 'section-representative' | 'floor-representative' | 'venue-representative';
|
|
146
|
+
type SeatConfidenceModelLevel = 'generated' | 'geometry-verified' | 'configuration-verified';
|
|
147
|
+
type SeatConfidenceRealityLevel = 'none' | 'comparable-build-reference' | 'actual-build-checked';
|
|
148
|
+
/** A bounded, explainable visibility result. It is deliberately a modeled
|
|
149
|
+
* target score—not a promise that this percentage of a show will be visible. */
|
|
150
|
+
interface SeatConfidenceModeledTarget {
|
|
151
|
+
label: string;
|
|
152
|
+
clearRayCount: number;
|
|
153
|
+
totalRayCount: number;
|
|
154
|
+
classification: 'clear' | 'partial' | 'obstructed';
|
|
155
|
+
/** Buyer-safe method name, e.g. "45-ray screen target model". */
|
|
156
|
+
methodLabel: string;
|
|
157
|
+
occluderLabels?: string[];
|
|
158
|
+
}
|
|
159
|
+
/** Versioned source evidence for a seat-confidence passport.
|
|
160
|
+
*
|
|
161
|
+
* Model verification and reality evidence are independent dimensions. A
|
|
162
|
+
* comparable photograph must never outrank a venue-approved current event
|
|
163
|
+
* model, and a strong model must never silently become real-world evidence.
|
|
164
|
+
* `coverage` is added only after scope resolution on ExpandedSeat. */
|
|
165
|
+
interface SeatConfidenceEvidence {
|
|
166
|
+
schema: 'seatlayer.seat-confidence/1';
|
|
167
|
+
evidenceId: string;
|
|
168
|
+
modelVersion: string;
|
|
169
|
+
evidenceKind: 'synthetic-model' | 'organizer-approved' | 'calibrated-provider';
|
|
170
|
+
modelLevel: SeatConfidenceModelLevel;
|
|
171
|
+
realityLevel?: SeatConfidenceRealityLevel;
|
|
172
|
+
/** Required for configuration verification and actual-build checking. */
|
|
173
|
+
eventConfigurationId?: string;
|
|
174
|
+
assessedAt?: string;
|
|
175
|
+
validUntil?: string;
|
|
176
|
+
/** Buyer-safe approving organization or role; never a named individual. */
|
|
177
|
+
approvedByRole?: string;
|
|
178
|
+
/** Buyer-safe evidence provenance, not an asset URL or private filename. */
|
|
179
|
+
sourceLabel?: string;
|
|
180
|
+
limitations?: string[];
|
|
181
|
+
modeledTarget?: SeatConfidenceModeledTarget;
|
|
182
|
+
}
|
|
183
|
+
interface ResolvedSeatConfidenceEvidence extends SeatConfidenceEvidence {
|
|
184
|
+
coverage: SeatConfidenceCoverage;
|
|
185
|
+
}
|
|
142
186
|
interface SeatOverride {
|
|
143
187
|
/** 0-based seat index within the row. */
|
|
144
188
|
index: number;
|
|
@@ -170,6 +214,8 @@ interface SeatOverride {
|
|
|
170
214
|
viewFromSeatUrl?: string;
|
|
171
215
|
/** Calibration/truth for `viewFromSeatUrl`; ignored when the URL is absent. */
|
|
172
216
|
viewFromSeatMeta?: SeatViewMetadata;
|
|
217
|
+
/** Seat-scoped confidence evidence, independent of photo availability. */
|
|
218
|
+
confidenceEvidence?: SeatConfidenceEvidence;
|
|
173
219
|
/** Per-seat label size/color override; falls back to the row/theme default.
|
|
174
220
|
* Size is clamped to LABEL_STYLE_MIN_SIZE..MAX_SIZE; color is passed through
|
|
175
221
|
* the shared auto-contrast rule at paint time (see {@link LabelStyle}). */
|
|
@@ -376,6 +422,7 @@ interface RowObject extends AuthoringObjectState {
|
|
|
376
422
|
labelPresentation?: LabelPresentation;
|
|
377
423
|
viewFromSeatUrl?: string;
|
|
378
424
|
viewFromSeatMeta?: SeatViewMetadata;
|
|
425
|
+
confidenceEvidence?: SeatConfidenceEvidence;
|
|
379
426
|
/** Presentation intent for a continuous node-defined centreline. */
|
|
380
427
|
smoothing?: boolean;
|
|
381
428
|
};
|
|
@@ -452,6 +499,8 @@ interface RowObject extends AuthoringObjectState {
|
|
|
452
499
|
*/
|
|
453
500
|
viewFromSeatUrl?: string;
|
|
454
501
|
viewFromSeatMeta?: SeatViewMetadata;
|
|
502
|
+
/** Row-scoped confidence evidence inherited by seats without a closer record. */
|
|
503
|
+
confidenceEvidence?: SeatConfidenceEvidence;
|
|
455
504
|
/** Default commercial attributes inherited by seats without an override. */
|
|
456
505
|
commercial?: SeatCommercialAttributes;
|
|
457
506
|
}
|
|
@@ -723,6 +772,8 @@ interface SectionObject extends AuthoringObjectState {
|
|
|
723
772
|
*/
|
|
724
773
|
viewFromSeatUrl?: string;
|
|
725
774
|
viewFromSeatMeta?: SeatViewMetadata;
|
|
775
|
+
/** Section-scoped confidence evidence inherited after seat and row evidence. */
|
|
776
|
+
confidenceEvidence?: SeatConfidenceEvidence;
|
|
726
777
|
labelPresentation?: LabelPresentation;
|
|
727
778
|
/**
|
|
728
779
|
* Stable management/inventory identity shared by disconnected visual
|
|
@@ -996,6 +1047,8 @@ interface Floor {
|
|
|
996
1047
|
*/
|
|
997
1048
|
viewFromSeatUrl?: string;
|
|
998
1049
|
viewFromSeatMeta?: SeatViewMetadata;
|
|
1050
|
+
/** Floor-scoped confidence evidence inherited before the venue default. */
|
|
1051
|
+
confidenceEvidence?: SeatConfidenceEvidence;
|
|
999
1052
|
}
|
|
1000
1053
|
interface ReferenceCalibration {
|
|
1001
1054
|
type: 'two-point';
|
|
@@ -1244,6 +1297,8 @@ interface ChartDoc {
|
|
|
1244
1297
|
*/
|
|
1245
1298
|
viewFromSeatUrl?: string;
|
|
1246
1299
|
viewFromSeatMeta?: SeatViewMetadata;
|
|
1300
|
+
/** Venue-scoped confidence evidence. It does not require panorama media. */
|
|
1301
|
+
confidenceEvidence?: SeatConfidenceEvidence;
|
|
1247
1302
|
}
|
|
1248
1303
|
interface ExpandedSeat {
|
|
1249
1304
|
/** Stable id: `${rowId}:${index}` */
|
|
@@ -1281,6 +1336,8 @@ interface ExpandedSeat {
|
|
|
1281
1336
|
viewUrl?: string;
|
|
1282
1337
|
/** Calibration/truth inherited from the same scope that supplied `viewUrl`. */
|
|
1283
1338
|
viewMeta?: SeatViewMetadata;
|
|
1339
|
+
/** Closest configuration-valid evidence plus its engine-resolved coverage. */
|
|
1340
|
+
confidenceEvidence?: ResolvedSeatConfidenceEvidence;
|
|
1284
1341
|
/** Per-seat label size/color override; absent = inherit the row/theme default. */
|
|
1285
1342
|
labelStyle?: LabelStyle;
|
|
1286
1343
|
/**
|
|
@@ -1721,4 +1778,4 @@ interface ISeatmapRenderer {
|
|
|
1721
1778
|
/** localStorage key the Designer writes and the Picker reads. */
|
|
1722
1779
|
declare const CHART_STORAGE_KEY = "seatmap.chart";
|
|
1723
1780
|
|
|
1724
|
-
export { type
|
|
1781
|
+
export { type ReferenceSectionTraceBatchProposal as $, type AccessibilityType as A, type BoothObject as B, type ChartDoc as C, type DecorImageObject as D, type ExpandedSeat as E, type Floor as F, type GAAreaObject as G, LABEL_STYLE_MIN_SIZE as H, type ISeatmapRenderer as I, type LabelPresentation as J, type ReferenceAccessibilitySource as K, type LabelStyle as L, type ReferenceCalibration as M, type ReferenceCategorySource as N, type ObjectEditorState as O, type Point as P, type ReferenceDerivedScale as Q, type ResolvedSeatConfidenceEvidence as R, type SeatOverride as S, type TableObject as T, type ReferenceInventoryExclusionSource as U, type ReferenceInventorySource as V, type ReferenceScanProposal as W, type ReferenceScanRowProposal as X, type ReferenceScanSectionProposal as Y, type ReferenceSeatSeed as Z, type ReferenceSectionTraceBatchInput as _, type RowObject as a, type ReferenceSectionTraceInput as a0, type ReferenceSectionTraceProposal as a1, type RenderedBookableLabelEvidence as a2, type RenderedGAAreaEvidence as a3, type RenderedHierarchyLabelEvidence as a4, type RenderedLabelHiddenReason as a5, SEAT_COMMERCIAL_MARKS as a6, SURROUNDINGS_SHAPE_ROLES as a7, type SeatCommercialMark as a8, type SeatCommercialMeta as a9, type SeatConfidenceCoverage as aa, type SeatConfidenceEvidence as ab, type SeatConfidenceModelLevel as ac, type SeatConfidenceModeledTarget as ad, type SeatConfidenceRealityLevel as ae, type SeatViewCoverage as af, type SectionOutlinePath as ag, type SectionPathSegment as ah, type SelectionLayer as ai, type ShapeLineCap as aj, type ShapeLineEnding as ak, type ShapeLineJoin as al, type ShapeObject as am, type TextAlignment as an, type TextBackground as ao, type TextObject as ap, type ZoneDef as aq, accessibilityMeta as ar, accessibilityRingColor as as, layerOf as at, seatCommercialMeta as au, type SeatViewMetadata as b, type RectTableSide as c, type ChartObject as d, type SectionObject as e, type RectTableSeatCounts as f, type GAInventorySegment as g, type RendererQualityEvidence as h, type RenderedFreeTextEvidence as i, type RendererOptions as j, type SeatStatus as k, type RendererViewMode as l, type LodRung as m, type CategoryTier as n, type SeatCommercialAttributes as o, type RendererCallbacks as p, ACCESSIBILITY_RING_COLOR as q, ACCESSIBILITY_TYPES as r, type AccessibilityMeta as s, type AuthoringObjectState as t, CHART_STORAGE_KEY as u, type Category as v, type ChartReferenceImage as w, type ChartTheme as x, type CubicPath as y, LABEL_STYLE_MAX_SIZE as z };
|
|
@@ -125,12 +125,13 @@ function schedulePanoramaUpgrade(work) {
|
|
|
125
125
|
|
|
126
126
|
// src/view3d/crossfade/panorama.ts
|
|
127
127
|
function seatViewDisclosure(view) {
|
|
128
|
-
const
|
|
128
|
+
const demo = view.mediaKind === "demo-render";
|
|
129
|
+
const coverage = view.generated || view.mediaKind === "model" ? "Live 3D \xB7 chart-derived seat-eye" : demo ? view.coverage === "exact-seat" ? "AI-generated exact-seat demo" : view.coverage === "row-representative" ? "AI-generated representative row demo" : view.coverage === "section-representative" ? "AI-generated representative section demo" : "AI-generated illustrative venue demo" : view.coverage === "exact-seat" ? "Exact seat photo" : view.coverage === "row-representative" ? "Representative row view" : view.coverage === "section-representative" ? "Representative section view" : view.coverage === "venue-representative" ? "Representative venue view" : "Venue photo";
|
|
129
130
|
const year = view.capturedAt && /^\d{4}/.test(view.capturedAt) ? view.capturedAt.slice(0, 4) : "";
|
|
130
|
-
return [coverage, year ?
|
|
131
|
+
return [coverage, year ? `${demo ? "created" : "captured"} ${year}` : "", view.sourceLabel ?? ""].filter(Boolean).join(" \xB7 ");
|
|
131
132
|
}
|
|
132
133
|
function isAuthoredSeatView(view) {
|
|
133
|
-
if (view.generated) return false;
|
|
134
|
+
if (view.generated || view.mediaKind === "model" || view.mediaKind === "demo-render") return false;
|
|
134
135
|
const source = view.sourceLabel?.trim() ?? "";
|
|
135
136
|
return !/^(?:AI[- ]generated|generated\s+(?:demo|preview))/i.test(source);
|
|
136
137
|
}
|
|
@@ -209,7 +210,7 @@ function mountPanorama(container, view, opts = {}) {
|
|
|
209
210
|
root.appendChild(closeBtn);
|
|
210
211
|
if (opts.disclosure) {
|
|
211
212
|
const disclosure = document.createElement("div");
|
|
212
|
-
disclosure.textContent =
|
|
213
|
+
disclosure.textContent = `${opts.disclosurePrefix ?? "360\xB0 panorama"} \xB7 ${opts.disclosure}`;
|
|
213
214
|
Object.assign(disclosure.style, {
|
|
214
215
|
position: "absolute",
|
|
215
216
|
top: "12px",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/view3d/crossfade/panorama.ts","../../../src/view/panoramaDelivery.ts"],"sourcesContent":["/**\n * Slice 3 hand-off — the DOM panorama overlay the fly-to-seat cinematic\n * dissolves into. Decoupled by design: the CALLER supplies the equirectangular\n * image (via mountVenue3D's getSeatView), so view3d never imports the app's\n * panorama generator and the chunk stays lean.\n *\n * Technique (mirrors SeatPicker.openSeatView, reimplemented small): an equirect\n * image panned with `repeat-x`; the initial horizontal offset is set so the\n * panorama's bearing matches the final camera yaw — the dissolve reads as the\n * same view sharpening, not a cut. CSS opacity fade is compositor-only.\n */\n\nimport type { SeatViewCoverage } from '../../core/types';\nimport {\n browserPanoramaConstraints,\n loadPanoramaImage,\n planPanoramaDelivery,\n schedulePanoramaUpgrade,\n} from '../../view/panoramaDelivery';\n\nexport interface SeatView {\n url: string;\n /** Lightweight equirect used for first paint before `url` replaces it. */\n previewUrl?: string;\n /**\n * Optional authenticated byte-to-object-URL resolver. The buyer picker uses\n * this for a deferred sharp-image upgrade so progressive view media never\n * needs a bearer in its URL.\n */\n resolveUrl?: (url: string) => Promise<string | null>;\n sourceWidth?: number;\n sourceHeight?: number;\n previewWidth?: number;\n previewHeight?: number;\n /**\n * True when `url` is a SYNTHESISED panorama rather than a real photograph.\n *\n * It decides which of two very different things the buyer gets, so the caller\n * must say rather than the renderer guess. A generated equirect is a picture\n * of a 3D scene the renderer already has: at 2048×1024 it covers 106° of\n * horizontal view in ~600 source pixels, which a 3072-device-pixel viewport\n * magnifies FIVE times, and no amount of art survives that. So a generated\n * view is not drawn as an image at all — the camera simply looks around the\n * real scene, which has no resolution ceiling.\n *\n * A real photo has no such substitute and goes on the sphere, where a 5760 or\n * 8192-wide capture has three to four times the detail to give.\n */\n generated?: boolean;\n /** Bearing (deg, 0 = facing the focal/stage) the panorama should open centred\n * on, to match the camera's final yaw. Default 0 (both face the stage). */\n initialBearingDeg?: number;\n /** Initial vertical orientation. Positive looks up; clamped to the viewer's\n * safe pitch range. Uploaded captures use this to match the live seat pose. */\n initialPitchDeg?: number;\n /** How specifically this image represents the selected inventory unit. */\n coverage?: SeatViewCoverage;\n /** ISO capture date/time when known. Unknown is preferable to invented age. */\n capturedAt?: string;\n /** Optional buyer-safe provenance, for example \"Provided by the venue\". */\n sourceLabel?: string;\n}\n\n/** Honest, compact buyer disclosure for the panorama currently on screen. */\nexport function seatViewDisclosure(view: SeatView): string {\n const coverage = view.generated\n ? 'Live 3D · exact seat-eye'\n : view.coverage === 'exact-seat'\n ? 'Exact seat photo'\n : view.coverage === 'row-representative'\n ? 'Representative row view'\n : view.coverage === 'section-representative'\n ? 'Representative section view'\n : view.coverage === 'venue-representative'\n ? 'Representative venue view'\n : 'Venue photo';\n const year = view.capturedAt && /^\\d{4}/.test(view.capturedAt) ? view.capturedAt.slice(0, 4) : '';\n return [coverage, year ? `captured ${year}` : '', view.sourceLabel ?? ''].filter(Boolean).join(' · ');\n}\n\n/** True only when the panorama should be presented as organizer-authored media.\n * Representative AI demo assets travel through the same URL field as venue\n * photos, so provenance—not URL presence—must decide the buyer badge. */\nexport function isAuthoredSeatView(view: SeatView): boolean {\n if (view.generated) return false;\n const source = view.sourceLabel?.trim() ?? '';\n return !/^(?:AI[- ]generated|generated\\s+(?:demo|preview))/i.test(source);\n}\n\nexport interface PanoramaHandle {\n /** Fade out and return to the (frozen) 3D view; calls opts.onClose after. */\n close(): void;\n /** Immediate teardown (dispose) — no fade, no onClose. */\n dispose(): void;\n}\n\n/**\n * Vertical field of view (deg) the windowed panorama shows. The source image is\n * a full 180° equirect sphere; showing it raw wastes ~⅔ of the frame on dead sky\n * and black floor, with the horizon content band squished into the middle. We\n * instead scale the image so only this central slice fills the viewport height,\n * horizon-centred, and let the user drag pitch within ±`MAX_PITCH_DEG`.\n */\nexport const VFOV_DEG = 70;\nexport const MIN_VFOV_DEG = 35;\nexport const MAX_VFOV_DEG = 90;\n\n/** Keep wheel, keyboard and pinch zoom inside a useful, non-disorienting range. */\nexport function clampPanoramaFov(fovDeg: number): number {\n return Math.max(MIN_VFOV_DEG, Math.min(MAX_VFOV_DEG, fovDeg));\n}\n/** Users may look this far up/down from the horizon; well inside the image so\n * the clamp never reveals past its top/bottom edge. */\nexport const MAX_PITCH_DEG = 35;\n\n/**\n * Horizontal background-position (px) that centres `bearingDeg` in the viewport,\n * assuming the equirect image's yaw 0 sits at its horizontal centre. `bgW` is the\n * full scaled image width representing 360° — so this is invariant to the vertical\n * FOV windowing (which scales width and height by the same factor). `repeat-x`\n * handles the wrap, so any real value is valid.\n */\nexport function bearingToOffsetPx(bearingDeg: number, viewportW: number, bgW: number): number {\n const col = (0.5 + bearingDeg / 360) * bgW; // image column (px) for the bearing\n return viewportW / 2 - col;\n}\n\n/**\n * Full scaled image height (px) so that a `vfovDeg`-tall slice fills `viewportH`.\n * The image spans 180° vertically, so height = viewportH · 180/vfov.\n */\nexport function windowedBgHeight(viewportH: number, vfovDeg: number = VFOV_DEG): number {\n return viewportH * (180 / vfovDeg);\n}\n\n/**\n * background-position Y (px) that centres the image's horizon (its vertical\n * centre) in the viewport, offset by `pitchPx` (deviation from the horizon,\n * clamped to ±`MAX_PITCH_DEG`). Positive `pitchPx` looks up.\n */\nexport function horizonOffsetPy(viewportH: number, bgH: number, pitchPx: number): number {\n return (viewportH - bgH) / 2 + clampPitchPx(pitchPx, bgH);\n}\n\n/** Clamp a pitch drag (px) to ±MAX_PITCH_DEG of image travel, and never past the\n * image edge. `bgH` px map the full 180°, so a degree is `bgH/180` px. */\nexport function clampPitchPx(pitchPx: number, bgH: number): number {\n const limit = (MAX_PITCH_DEG / 180) * bgH;\n return Math.max(-limit, Math.min(limit, pitchPx));\n}\n\nexport interface PanoramaOptions {\n fadeMs?: number;\n seatLabel?: string;\n disclosure?: string;\n onClose?: () => void;\n /** Cancels stale preview/full-resolution image work when a flight is retargeted. */\n signal?: AbortSignal;\n}\n\nexport function mountPanorama(container: HTMLElement, view: SeatView, opts: PanoramaOptions = {}): PanoramaHandle {\n const fadeMs = opts.fadeMs ?? 400;\n const bearing = view.initialBearingDeg ?? 0;\n\n const delivery = planPanoramaDelivery(view, browserPanoramaConstraints());\n const loadAbort = new AbortController();\n const abortFromCaller = (): void => loadAbort.abort();\n opts.signal?.addEventListener('abort', abortFromCaller, { once: true });\n const root = document.createElement('div');\n const priorFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n root.setAttribute('role', 'dialog');\n root.setAttribute('aria-modal', 'true');\n root.setAttribute('aria-label', opts.seatLabel ? `View from ${opts.seatLabel}` : 'View from seat');\n root.tabIndex = -1;\n Object.assign(root.style, {\n position: 'absolute', inset: '0', zIndex: '10', opacity: '0',\n transition: `opacity ${fadeMs}ms ease`, background: '#05070c',\n overflow: 'hidden', touchAction: 'none',\n } as CSSStyleDeclaration);\n\n const pano = document.createElement('div');\n Object.assign(pano.style, {\n position: 'absolute', inset: '0',\n backgroundImage: `url(\"${delivery.initialUrl}\")`, backgroundRepeat: 'repeat-x',\n cursor: 'grab',\n } as CSSStyleDeclaration);\n root.appendChild(pano);\n\n // Close affordance.\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.setAttribute('aria-label', 'Close');\n closeBtn.textContent = '✕';\n Object.assign(closeBtn.style, {\n position: 'absolute', top: '12px', right: '12px', zIndex: '2',\n width: '44px', height: '44px', borderRadius: '999px', cursor: 'pointer',\n border: '1px solid rgba(255,255,255,0.25)', background: 'rgba(8,12,18,0.6)',\n color: '#e6edf3', fontSize: '15px', lineHeight: '1',\n } as CSSStyleDeclaration);\n root.appendChild(closeBtn);\n\n if (opts.disclosure) {\n const disclosure = document.createElement('div');\n disclosure.textContent = `360° panorama · ${opts.disclosure}`;\n Object.assign(disclosure.style, {\n position: 'absolute', top: '12px', left: '12px', zIndex: '2',\n maxWidth: 'calc(100% - 88px)', padding: '8px 11px', borderRadius: '999px',\n overflow: 'hidden', color: '#dce6f8', background: 'rgba(8,12,18,0.68)',\n font: '600 11px/1.2 ui-sans-serif, system-ui, sans-serif', textOverflow: 'ellipsis',\n whiteSpace: 'nowrap', pointerEvents: 'none', backdropFilter: 'blur(6px)',\n } as CSSStyleDeclaration);\n root.appendChild(disclosure);\n }\n\n const hint = document.createElement('div');\n hint.textContent = 'Drag to look · pinch or scroll to zoom · Esc to close';\n Object.assign(hint.style, {\n position: 'absolute', bottom: '12px', left: '0', right: '0', textAlign: 'center',\n color: 'rgba(230,237,243,0.7)', font: '12px ui-sans-serif, system-ui, sans-serif',\n pointerEvents: 'none',\n } as CSSStyleDeclaration);\n root.appendChild(hint);\n\n container.appendChild(root);\n closeBtn.focus({ preventScroll: true });\n\n // Layout: window a ~70° vertical slice of the sphere (horizon-centred) so the\n // venue fills the frame instead of floating in dead sky + black floor. The\n // image is scaled so that slice is exactly the viewport height; width scales by\n // the same factor, so `bearingToOffsetPx` stays correct. `pitchPx` is the\n // vertical drag deviation from the horizon, clamped to ±35°.\n let bgW = 0;\n let bgH = 0;\n let posX = 0;\n let pitchPx = 0;\n let viewFov = VFOV_DEG;\n const layout = (): void => {\n const vh = root.clientHeight || 1;\n const vw = root.clientWidth || 1;\n const natW = img.naturalWidth || vw * 2;\n const natH = img.naturalHeight || vh;\n const centredImageRatio = bgW > 0 ? (vw / 2 - posX) / bgW : 0;\n const pitchRatio = bgH > 0 ? pitchPx / bgH : 0;\n bgH = windowedBgHeight(vh, viewFov);\n bgW = bgH * (natW / natH);\n pano.style.backgroundSize = `${bgW}px ${bgH}px`;\n if (!posInitialised) {\n posX = bearingToOffsetPx(bearing, vw, bgW);\n pitchPx = clampPitchPx(((view.initialPitchDeg ?? 0) / 180) * bgH, bgH);\n posInitialised = true;\n }\n else posX = vw / 2 - centredImageRatio * bgW;\n pitchPx = clampPitchPx(pitchRatio * bgH, bgH);\n pano.style.backgroundPosition = `${posX}px ${horizonOffsetPy(vh, bgH, pitchPx)}px`;\n };\n let posInitialised = false;\n\n const applyPos = (): void => {\n const vh = root.clientHeight || 1;\n pitchPx = clampPitchPx(pitchPx, bgH);\n pano.style.backgroundPosition = `${posX}px ${horizonOffsetPy(vh, bgH, pitchPx)}px`;\n };\n\n let img = new Image();\n img.onload = layout;\n img.src = delivery.initialUrl;\n let cancelUpgrade = (): void => {};\n if (delivery.upgradeUrl) {\n cancelUpgrade = schedulePanoramaUpgrade(() => {\n const resolved = view.resolveUrl\n ? view.resolveUrl(delivery.upgradeUrl!)\n : Promise.resolve(delivery.upgradeUrl!);\n void resolved.then((url) => {\n if (!url || loadAbort.signal.aborted) return null;\n return loadPanoramaImage(url, loadAbort.signal).then((full) => ({ full, url }));\n }).then((loaded) => {\n if (!loaded) return;\n if (disposed || loadAbort.signal.aborted) return;\n img = loaded.full;\n pano.style.backgroundImage = `url(\"${loaded.url}\")`;\n layout();\n }).catch(() => { /* the preview remains usable */ });\n });\n }\n // If it's already cached, onload may not fire — lay out on next frame too.\n requestAnimationFrame(layout);\n\n // Pan: horizontal (repeat-x wraps seamlessly) + vertical pitch (clamped ±35°).\n let dragging = false;\n let lastX = 0;\n let lastY = 0;\n let pinchDistance = 0;\n const pointers = new Map<number, { x: number; y: number }>();\n const distanceBetweenPointers = (): number => {\n const [a, b] = [...pointers.values()];\n return a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;\n };\n const setViewFov = (nextFov: number): void => {\n const clamped = clampPanoramaFov(nextFov);\n if (clamped === viewFov) return;\n viewFov = clamped;\n layout();\n };\n const onDown = (e: PointerEvent): void => {\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n dragging = pointers.size === 1;\n lastX = e.clientX; lastY = e.clientY;\n if (pointers.size === 2) pinchDistance = distanceBetweenPointers();\n pano.style.cursor = 'grabbing';\n try { pano.setPointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n };\n const onMove = (e: PointerEvent): void => {\n if (!pointers.has(e.pointerId)) return;\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (pointers.size >= 2) {\n const nextDistance = distanceBetweenPointers();\n if (pinchDistance > 0 && nextDistance > 0) setViewFov(viewFov * (pinchDistance / nextDistance));\n pinchDistance = nextDistance;\n dragging = false;\n return;\n }\n if (!dragging) return;\n posX += e.clientX - lastX;\n pitchPx += e.clientY - lastY;\n lastX = e.clientX;\n lastY = e.clientY;\n applyPos();\n };\n const onUp = (e: PointerEvent): void => {\n pointers.delete(e.pointerId);\n pinchDistance = pointers.size === 2 ? distanceBetweenPointers() : 0;\n const remaining = pointers.values().next().value as { x: number; y: number } | undefined;\n dragging = pointers.size === 1;\n if (remaining) { lastX = remaining.x; lastY = remaining.y; }\n else pano.style.cursor = 'grab';\n try { pano.releasePointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n };\n const onWheel = (e: WheelEvent): void => {\n e.preventDefault();\n setViewFov(viewFov + e.deltaY * 0.04);\n };\n pano.addEventListener('pointerdown', onDown);\n pano.addEventListener('pointermove', onMove);\n pano.addEventListener('pointerup', onUp);\n pano.addEventListener('pointercancel', onUp);\n pano.addEventListener('wheel', onWheel, { passive: false });\n\n let closed = false;\n let disposed = false;\n let fadeTimer = 0;\n const removeListeners = (): void => {\n pano.removeEventListener('pointerdown', onDown);\n pano.removeEventListener('pointermove', onMove);\n pano.removeEventListener('pointerup', onUp);\n pano.removeEventListener('pointercancel', onUp);\n pano.removeEventListener('wheel', onWheel);\n window.removeEventListener('keydown', onKey);\n };\n const teardown = (): void => {\n cancelUpgrade();\n loadAbort.abort();\n opts.signal?.removeEventListener('abort', abortFromCaller);\n if (fadeTimer) { window.clearTimeout(fadeTimer); fadeTimer = 0; }\n removeListeners();\n if (root.parentNode) root.parentNode.removeChild(root);\n if (priorFocus?.isConnected) priorFocus.focus({ preventScroll: true });\n };\n const close = (): void => {\n if (closed) return;\n closed = true;\n root.style.opacity = '0';\n // Guard the fade callback: a dispose() (or a retarget that disposes us) inside\n // the fade window clears the timer AND flips `disposed`, so a stray fire can\n // never call onClose into a newer flight/panorama.\n const done = (): void => {\n fadeTimer = 0;\n if (disposed) return;\n teardown();\n opts.onClose?.();\n };\n fadeTimer = window.setTimeout(done, fadeMs);\n };\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === 'Tab') { e.preventDefault(); closeBtn.focus({ preventScroll: true }); return; }\n if (e.key === 'Escape') { e.stopPropagation(); close(); return; }\n if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n posX += e.key === 'ArrowLeft' ? -32 : 32;\n } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n pitchPx += (e.key === 'ArrowUp' ? 4 : -4) * (bgH / 180);\n } else if (e.key === '+' || e.key === '=') {\n setViewFov(viewFov - 5);\n e.preventDefault();\n return;\n } else if (e.key === '-') {\n setViewFov(viewFov + 5);\n e.preventDefault();\n return;\n } else {\n return;\n }\n e.preventDefault();\n applyPos();\n };\n window.addEventListener('keydown', onKey);\n closeBtn.addEventListener('click', close);\n\n // Fade in on the next frame (0 → 1).\n requestAnimationFrame(() => { root.style.opacity = '1'; });\n\n return {\n close,\n dispose(): void { closed = true; disposed = true; teardown(); },\n };\n}\n","/** Progressive delivery policy shared by the WebGL and DOM panorama viewers. */\n\nexport interface PanoramaDeliverySource {\n url: string;\n previewUrl?: string;\n sourceWidth?: number;\n sourceHeight?: number;\n previewWidth?: number;\n previewHeight?: number;\n}\n\nexport interface PanoramaDeliveryConstraints {\n saveData?: boolean;\n maxTextureSize?: number;\n maxDecodedBytes?: number;\n}\n\nexport interface PanoramaDeliveryPlan {\n initialUrl: string;\n upgradeUrl?: string;\n initialWidth?: number;\n initialHeight?: number;\n reason: 'full-only' | 'progressive' | 'save-data' | 'texture-limit' | 'memory-limit';\n}\n\n/** Approximate GPU allocation including a complete mip chain (4/3 overhead). */\nexport function estimatePanoramaTextureBytes(width: number, height: number): number {\n if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return 0;\n return Math.ceil(width * height * 4 * (4 / 3));\n}\n\n/** Conservative per-panorama budget, raised only on devices reporting ample RAM. */\nexport function panoramaTextureBudgetBytes(deviceMemoryGb?: number): number {\n const mib = deviceMemoryGb !== undefined && deviceMemoryGb <= 2\n ? 32\n : deviceMemoryGb !== undefined && deviceMemoryGb >= 8\n ? 192\n : 96;\n return mib * 1024 * 1024;\n}\n\nexport function browserPanoramaConstraints(maxTextureSize?: number): PanoramaDeliveryConstraints {\n const nav = typeof navigator === 'undefined'\n ? undefined\n : navigator as Navigator & { deviceMemory?: number; connection?: { saveData?: boolean } };\n return {\n saveData: nav?.connection?.saveData === true,\n maxTextureSize,\n maxDecodedBytes: panoramaTextureBudgetBytes(nav?.deviceMemory),\n };\n}\n\n/** Select the first image and whether a later sharp-image upgrade is safe. */\nexport function planPanoramaDelivery(\n source: PanoramaDeliverySource,\n constraints: PanoramaDeliveryConstraints = {},\n): PanoramaDeliveryPlan {\n const previewUrl = source.previewUrl?.trim();\n if (!previewUrl || previewUrl === source.url) {\n return {\n initialUrl: source.url,\n initialWidth: source.sourceWidth,\n initialHeight: source.sourceHeight,\n reason: 'full-only',\n };\n }\n\n const initial = {\n initialUrl: previewUrl,\n initialWidth: source.previewWidth,\n initialHeight: source.previewHeight,\n };\n if (constraints.saveData) return { ...initial, reason: 'save-data' };\n if (constraints.maxTextureSize !== undefined && source.sourceWidth !== undefined && source.sourceHeight !== undefined\n && Math.max(source.sourceWidth, source.sourceHeight) > constraints.maxTextureSize) {\n return { ...initial, reason: 'texture-limit' };\n }\n if (constraints.maxDecodedBytes !== undefined && source.sourceWidth !== undefined && source.sourceHeight !== undefined\n && estimatePanoramaTextureBytes(source.sourceWidth, source.sourceHeight) > constraints.maxDecodedBytes) {\n return { ...initial, reason: 'memory-limit' };\n }\n return { ...initial, upgradeUrl: source.url, reason: 'progressive' };\n}\n\nfunction abortError(): DOMException {\n return new DOMException('Panorama image load aborted', 'AbortError');\n}\n\n/** Decode before resolving so callers never upload or reveal a partial image. */\nexport function loadPanoramaImage(url: string, signal?: AbortSignal): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) { reject(abortError()); return; }\n const image = new Image();\n image.crossOrigin = 'anonymous';\n let settled = false;\n const cleanup = (): void => {\n image.onload = null;\n image.onerror = null;\n signal?.removeEventListener('abort', onAbort);\n };\n const finish = (error?: unknown): void => {\n if (settled) return;\n settled = true;\n cleanup();\n if (error) reject(error);\n else resolve(image);\n };\n const onAbort = (): void => {\n image.src = '';\n finish(abortError());\n };\n image.onload = () => {\n const decoded = typeof image.decode === 'function' ? image.decode() : Promise.resolve();\n void decoded.then(() => finish(), () => finish());\n };\n image.onerror = () => finish(new Error('panorama_image_failed'));\n signal?.addEventListener('abort', onAbort, { once: true });\n image.src = url;\n });\n}\n\n/** Let the preview paint before starting a potentially large decode. */\nexport function schedulePanoramaUpgrade(work: () => void): () => void {\n const host = globalThis as typeof globalThis & {\n requestIdleCallback?: (cb: () => void, opts?: { timeout: number }) => number;\n cancelIdleCallback?: (id: number) => void;\n };\n if (host.requestIdleCallback) {\n const id = host.requestIdleCallback(work, { timeout: 1200 });\n return () => host.cancelIdleCallback?.(id);\n }\n const id = globalThis.setTimeout(work, 32);\n return () => globalThis.clearTimeout(id);\n}\n"],"mappings":"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","names":["id"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/view3d/crossfade/panorama.ts","../../../src/view/panoramaDelivery.ts"],"sourcesContent":["/**\n * Slice 3 hand-off — the DOM panorama overlay the fly-to-seat cinematic\n * dissolves into. Decoupled by design: the CALLER supplies the equirectangular\n * image (via mountVenue3D's getSeatView), so view3d never imports the app's\n * panorama generator and the chunk stays lean.\n *\n * Technique (mirrors SeatPicker.openSeatView, reimplemented small): an equirect\n * image panned with `repeat-x`; the initial horizontal offset is set so the\n * panorama's bearing matches the final camera yaw — the dissolve reads as the\n * same view sharpening, not a cut. CSS opacity fade is compositor-only.\n */\n\nimport type { SeatViewCoverage } from '../../core/types';\nimport {\n browserPanoramaConstraints,\n loadPanoramaImage,\n planPanoramaDelivery,\n schedulePanoramaUpgrade,\n} from '../../view/panoramaDelivery';\n\nexport interface SeatView {\n url: string;\n /** Lightweight equirect used for first paint before `url` replaces it. */\n previewUrl?: string;\n /**\n * Optional authenticated byte-to-object-URL resolver. The buyer picker uses\n * this for a deferred sharp-image upgrade so progressive view media never\n * needs a bearer in its URL.\n */\n resolveUrl?: (url: string) => Promise<string | null>;\n sourceWidth?: number;\n sourceHeight?: number;\n previewWidth?: number;\n previewHeight?: number;\n /**\n * True when `url` is a SYNTHESISED panorama rather than a real photograph.\n *\n * It decides which of two very different things the buyer gets, so the caller\n * must say rather than the renderer guess. A generated equirect is a picture\n * of a 3D scene the renderer already has: at 2048×1024 it covers 106° of\n * horizontal view in ~600 source pixels, which a 3072-device-pixel viewport\n * magnifies FIVE times, and no amount of art survives that. So a generated\n * view is not drawn as an image at all — the camera simply looks around the\n * real scene, which has no resolution ceiling.\n *\n * A real photo has no such substitute and goes on the sphere, where a 5760 or\n * 8192-wide capture has three to four times the detail to give.\n */\n generated?: boolean;\n /** Explicit media provenance; avoids inferring buyer truth from source copy. */\n mediaKind?: 'photo' | 'demo-render' | 'model';\n /** Bearing (deg, 0 = facing the focal/stage) the panorama should open centred\n * on, to match the camera's final yaw. Default 0 (both face the stage). */\n initialBearingDeg?: number;\n /** Initial vertical orientation. Positive looks up; clamped to the viewer's\n * safe pitch range. Uploaded captures use this to match the live seat pose. */\n initialPitchDeg?: number;\n /** How specifically this image represents the selected inventory unit. */\n coverage?: SeatViewCoverage;\n /** ISO capture date/time when known. Unknown is preferable to invented age. */\n capturedAt?: string;\n /** Optional buyer-safe provenance, for example \"Provided by the venue\". */\n sourceLabel?: string;\n}\n\n/** Honest, compact buyer disclosure for the panorama currently on screen. */\nexport function seatViewDisclosure(view: SeatView): string {\n const demo = view.mediaKind === 'demo-render';\n const coverage = view.generated || view.mediaKind === 'model'\n ? 'Live 3D · chart-derived seat-eye'\n : demo\n ? view.coverage === 'exact-seat'\n ? 'AI-generated exact-seat demo'\n : view.coverage === 'row-representative'\n ? 'AI-generated representative row demo'\n : view.coverage === 'section-representative'\n ? 'AI-generated representative section demo'\n : 'AI-generated illustrative venue demo'\n : view.coverage === 'exact-seat'\n ? 'Exact seat photo'\n : view.coverage === 'row-representative'\n ? 'Representative row view'\n : view.coverage === 'section-representative'\n ? 'Representative section view'\n : view.coverage === 'venue-representative'\n ? 'Representative venue view'\n : 'Venue photo';\n const year = view.capturedAt && /^\\d{4}/.test(view.capturedAt) ? view.capturedAt.slice(0, 4) : '';\n return [coverage, year ? `${demo ? 'created' : 'captured'} ${year}` : '', view.sourceLabel ?? ''].filter(Boolean).join(' · ');\n}\n\n/** True only when the panorama should be presented as organizer-authored media.\n * Representative AI demo assets travel through the same URL field as venue\n * photos, so provenance—not URL presence—must decide the buyer badge. */\nexport function isAuthoredSeatView(view: SeatView): boolean {\n if (view.generated || view.mediaKind === 'model' || view.mediaKind === 'demo-render') return false;\n const source = view.sourceLabel?.trim() ?? '';\n return !/^(?:AI[- ]generated|generated\\s+(?:demo|preview))/i.test(source);\n}\n\nexport interface PanoramaHandle {\n /** Fade out and return to the (frozen) 3D view; calls opts.onClose after. */\n close(): void;\n /** Immediate teardown (dispose) — no fade, no onClose. */\n dispose(): void;\n}\n\n/**\n * Vertical field of view (deg) the windowed panorama shows. The source image is\n * a full 180° equirect sphere; showing it raw wastes ~⅔ of the frame on dead sky\n * and black floor, with the horizon content band squished into the middle. We\n * instead scale the image so only this central slice fills the viewport height,\n * horizon-centred, and let the user drag pitch within ±`MAX_PITCH_DEG`.\n */\nexport const VFOV_DEG = 70;\nexport const MIN_VFOV_DEG = 35;\nexport const MAX_VFOV_DEG = 90;\n\n/** Keep wheel, keyboard and pinch zoom inside a useful, non-disorienting range. */\nexport function clampPanoramaFov(fovDeg: number): number {\n return Math.max(MIN_VFOV_DEG, Math.min(MAX_VFOV_DEG, fovDeg));\n}\n/** Users may look this far up/down from the horizon; well inside the image so\n * the clamp never reveals past its top/bottom edge. */\nexport const MAX_PITCH_DEG = 35;\n\n/**\n * Horizontal background-position (px) that centres `bearingDeg` in the viewport,\n * assuming the equirect image's yaw 0 sits at its horizontal centre. `bgW` is the\n * full scaled image width representing 360° — so this is invariant to the vertical\n * FOV windowing (which scales width and height by the same factor). `repeat-x`\n * handles the wrap, so any real value is valid.\n */\nexport function bearingToOffsetPx(bearingDeg: number, viewportW: number, bgW: number): number {\n const col = (0.5 + bearingDeg / 360) * bgW; // image column (px) for the bearing\n return viewportW / 2 - col;\n}\n\n/**\n * Full scaled image height (px) so that a `vfovDeg`-tall slice fills `viewportH`.\n * The image spans 180° vertically, so height = viewportH · 180/vfov.\n */\nexport function windowedBgHeight(viewportH: number, vfovDeg: number = VFOV_DEG): number {\n return viewportH * (180 / vfovDeg);\n}\n\n/**\n * background-position Y (px) that centres the image's horizon (its vertical\n * centre) in the viewport, offset by `pitchPx` (deviation from the horizon,\n * clamped to ±`MAX_PITCH_DEG`). Positive `pitchPx` looks up.\n */\nexport function horizonOffsetPy(viewportH: number, bgH: number, pitchPx: number): number {\n return (viewportH - bgH) / 2 + clampPitchPx(pitchPx, bgH);\n}\n\n/** Clamp a pitch drag (px) to ±MAX_PITCH_DEG of image travel, and never past the\n * image edge. `bgH` px map the full 180°, so a degree is `bgH/180` px. */\nexport function clampPitchPx(pitchPx: number, bgH: number): number {\n const limit = (MAX_PITCH_DEG / 180) * bgH;\n return Math.max(-limit, Math.min(limit, pitchPx));\n}\n\nexport interface PanoramaOptions {\n fadeMs?: number;\n seatLabel?: string;\n disclosure?: string;\n /** Prefix for the disclosure chip; generated live geometry is not a panorama. */\n disclosurePrefix?: string;\n onClose?: () => void;\n /** Cancels stale preview/full-resolution image work when a flight is retargeted. */\n signal?: AbortSignal;\n}\n\nexport function mountPanorama(container: HTMLElement, view: SeatView, opts: PanoramaOptions = {}): PanoramaHandle {\n const fadeMs = opts.fadeMs ?? 400;\n const bearing = view.initialBearingDeg ?? 0;\n\n const delivery = planPanoramaDelivery(view, browserPanoramaConstraints());\n const loadAbort = new AbortController();\n const abortFromCaller = (): void => loadAbort.abort();\n opts.signal?.addEventListener('abort', abortFromCaller, { once: true });\n const root = document.createElement('div');\n const priorFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n root.setAttribute('role', 'dialog');\n root.setAttribute('aria-modal', 'true');\n root.setAttribute('aria-label', opts.seatLabel ? `View from ${opts.seatLabel}` : 'View from seat');\n root.tabIndex = -1;\n Object.assign(root.style, {\n position: 'absolute', inset: '0', zIndex: '10', opacity: '0',\n transition: `opacity ${fadeMs}ms ease`, background: '#05070c',\n overflow: 'hidden', touchAction: 'none',\n } as CSSStyleDeclaration);\n\n const pano = document.createElement('div');\n Object.assign(pano.style, {\n position: 'absolute', inset: '0',\n backgroundImage: `url(\"${delivery.initialUrl}\")`, backgroundRepeat: 'repeat-x',\n cursor: 'grab',\n } as CSSStyleDeclaration);\n root.appendChild(pano);\n\n // Close affordance.\n const closeBtn = document.createElement('button');\n closeBtn.type = 'button';\n closeBtn.setAttribute('aria-label', 'Close');\n closeBtn.textContent = '✕';\n Object.assign(closeBtn.style, {\n position: 'absolute', top: '12px', right: '12px', zIndex: '2',\n width: '44px', height: '44px', borderRadius: '999px', cursor: 'pointer',\n border: '1px solid rgba(255,255,255,0.25)', background: 'rgba(8,12,18,0.6)',\n color: '#e6edf3', fontSize: '15px', lineHeight: '1',\n } as CSSStyleDeclaration);\n root.appendChild(closeBtn);\n\n if (opts.disclosure) {\n const disclosure = document.createElement('div');\n disclosure.textContent = `${opts.disclosurePrefix ?? '360° panorama'} · ${opts.disclosure}`;\n Object.assign(disclosure.style, {\n position: 'absolute', top: '12px', left: '12px', zIndex: '2',\n maxWidth: 'calc(100% - 88px)', padding: '8px 11px', borderRadius: '999px',\n overflow: 'hidden', color: '#dce6f8', background: 'rgba(8,12,18,0.68)',\n font: '600 11px/1.2 ui-sans-serif, system-ui, sans-serif', textOverflow: 'ellipsis',\n whiteSpace: 'nowrap', pointerEvents: 'none', backdropFilter: 'blur(6px)',\n } as CSSStyleDeclaration);\n root.appendChild(disclosure);\n }\n\n const hint = document.createElement('div');\n hint.textContent = 'Drag to look · pinch or scroll to zoom · Esc to close';\n Object.assign(hint.style, {\n position: 'absolute', bottom: '12px', left: '0', right: '0', textAlign: 'center',\n color: 'rgba(230,237,243,0.7)', font: '12px ui-sans-serif, system-ui, sans-serif',\n pointerEvents: 'none',\n } as CSSStyleDeclaration);\n root.appendChild(hint);\n\n container.appendChild(root);\n closeBtn.focus({ preventScroll: true });\n\n // Layout: window a ~70° vertical slice of the sphere (horizon-centred) so the\n // venue fills the frame instead of floating in dead sky + black floor. The\n // image is scaled so that slice is exactly the viewport height; width scales by\n // the same factor, so `bearingToOffsetPx` stays correct. `pitchPx` is the\n // vertical drag deviation from the horizon, clamped to ±35°.\n let bgW = 0;\n let bgH = 0;\n let posX = 0;\n let pitchPx = 0;\n let viewFov = VFOV_DEG;\n const layout = (): void => {\n const vh = root.clientHeight || 1;\n const vw = root.clientWidth || 1;\n const natW = img.naturalWidth || vw * 2;\n const natH = img.naturalHeight || vh;\n const centredImageRatio = bgW > 0 ? (vw / 2 - posX) / bgW : 0;\n const pitchRatio = bgH > 0 ? pitchPx / bgH : 0;\n bgH = windowedBgHeight(vh, viewFov);\n bgW = bgH * (natW / natH);\n pano.style.backgroundSize = `${bgW}px ${bgH}px`;\n if (!posInitialised) {\n posX = bearingToOffsetPx(bearing, vw, bgW);\n pitchPx = clampPitchPx(((view.initialPitchDeg ?? 0) / 180) * bgH, bgH);\n posInitialised = true;\n }\n else posX = vw / 2 - centredImageRatio * bgW;\n pitchPx = clampPitchPx(pitchRatio * bgH, bgH);\n pano.style.backgroundPosition = `${posX}px ${horizonOffsetPy(vh, bgH, pitchPx)}px`;\n };\n let posInitialised = false;\n\n const applyPos = (): void => {\n const vh = root.clientHeight || 1;\n pitchPx = clampPitchPx(pitchPx, bgH);\n pano.style.backgroundPosition = `${posX}px ${horizonOffsetPy(vh, bgH, pitchPx)}px`;\n };\n\n let img = new Image();\n img.onload = layout;\n img.src = delivery.initialUrl;\n let cancelUpgrade = (): void => {};\n if (delivery.upgradeUrl) {\n cancelUpgrade = schedulePanoramaUpgrade(() => {\n const resolved = view.resolveUrl\n ? view.resolveUrl(delivery.upgradeUrl!)\n : Promise.resolve(delivery.upgradeUrl!);\n void resolved.then((url) => {\n if (!url || loadAbort.signal.aborted) return null;\n return loadPanoramaImage(url, loadAbort.signal).then((full) => ({ full, url }));\n }).then((loaded) => {\n if (!loaded) return;\n if (disposed || loadAbort.signal.aborted) return;\n img = loaded.full;\n pano.style.backgroundImage = `url(\"${loaded.url}\")`;\n layout();\n }).catch(() => { /* the preview remains usable */ });\n });\n }\n // If it's already cached, onload may not fire — lay out on next frame too.\n requestAnimationFrame(layout);\n\n // Pan: horizontal (repeat-x wraps seamlessly) + vertical pitch (clamped ±35°).\n let dragging = false;\n let lastX = 0;\n let lastY = 0;\n let pinchDistance = 0;\n const pointers = new Map<number, { x: number; y: number }>();\n const distanceBetweenPointers = (): number => {\n const [a, b] = [...pointers.values()];\n return a && b ? Math.hypot(a.x - b.x, a.y - b.y) : 0;\n };\n const setViewFov = (nextFov: number): void => {\n const clamped = clampPanoramaFov(nextFov);\n if (clamped === viewFov) return;\n viewFov = clamped;\n layout();\n };\n const onDown = (e: PointerEvent): void => {\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n dragging = pointers.size === 1;\n lastX = e.clientX; lastY = e.clientY;\n if (pointers.size === 2) pinchDistance = distanceBetweenPointers();\n pano.style.cursor = 'grabbing';\n try { pano.setPointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n };\n const onMove = (e: PointerEvent): void => {\n if (!pointers.has(e.pointerId)) return;\n pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });\n if (pointers.size >= 2) {\n const nextDistance = distanceBetweenPointers();\n if (pinchDistance > 0 && nextDistance > 0) setViewFov(viewFov * (pinchDistance / nextDistance));\n pinchDistance = nextDistance;\n dragging = false;\n return;\n }\n if (!dragging) return;\n posX += e.clientX - lastX;\n pitchPx += e.clientY - lastY;\n lastX = e.clientX;\n lastY = e.clientY;\n applyPos();\n };\n const onUp = (e: PointerEvent): void => {\n pointers.delete(e.pointerId);\n pinchDistance = pointers.size === 2 ? distanceBetweenPointers() : 0;\n const remaining = pointers.values().next().value as { x: number; y: number } | undefined;\n dragging = pointers.size === 1;\n if (remaining) { lastX = remaining.x; lastY = remaining.y; }\n else pano.style.cursor = 'grab';\n try { pano.releasePointerCapture?.(e.pointerId); } catch { /* no active pointer */ }\n };\n const onWheel = (e: WheelEvent): void => {\n e.preventDefault();\n setViewFov(viewFov + e.deltaY * 0.04);\n };\n pano.addEventListener('pointerdown', onDown);\n pano.addEventListener('pointermove', onMove);\n pano.addEventListener('pointerup', onUp);\n pano.addEventListener('pointercancel', onUp);\n pano.addEventListener('wheel', onWheel, { passive: false });\n\n let closed = false;\n let disposed = false;\n let fadeTimer = 0;\n const removeListeners = (): void => {\n pano.removeEventListener('pointerdown', onDown);\n pano.removeEventListener('pointermove', onMove);\n pano.removeEventListener('pointerup', onUp);\n pano.removeEventListener('pointercancel', onUp);\n pano.removeEventListener('wheel', onWheel);\n window.removeEventListener('keydown', onKey);\n };\n const teardown = (): void => {\n cancelUpgrade();\n loadAbort.abort();\n opts.signal?.removeEventListener('abort', abortFromCaller);\n if (fadeTimer) { window.clearTimeout(fadeTimer); fadeTimer = 0; }\n removeListeners();\n if (root.parentNode) root.parentNode.removeChild(root);\n if (priorFocus?.isConnected) priorFocus.focus({ preventScroll: true });\n };\n const close = (): void => {\n if (closed) return;\n closed = true;\n root.style.opacity = '0';\n // Guard the fade callback: a dispose() (or a retarget that disposes us) inside\n // the fade window clears the timer AND flips `disposed`, so a stray fire can\n // never call onClose into a newer flight/panorama.\n const done = (): void => {\n fadeTimer = 0;\n if (disposed) return;\n teardown();\n opts.onClose?.();\n };\n fadeTimer = window.setTimeout(done, fadeMs);\n };\n const onKey = (e: KeyboardEvent): void => {\n if (e.key === 'Tab') { e.preventDefault(); closeBtn.focus({ preventScroll: true }); return; }\n if (e.key === 'Escape') { e.stopPropagation(); close(); return; }\n if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\n posX += e.key === 'ArrowLeft' ? -32 : 32;\n } else if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {\n pitchPx += (e.key === 'ArrowUp' ? 4 : -4) * (bgH / 180);\n } else if (e.key === '+' || e.key === '=') {\n setViewFov(viewFov - 5);\n e.preventDefault();\n return;\n } else if (e.key === '-') {\n setViewFov(viewFov + 5);\n e.preventDefault();\n return;\n } else {\n return;\n }\n e.preventDefault();\n applyPos();\n };\n window.addEventListener('keydown', onKey);\n closeBtn.addEventListener('click', close);\n\n // Fade in on the next frame (0 → 1).\n requestAnimationFrame(() => { root.style.opacity = '1'; });\n\n return {\n close,\n dispose(): void { closed = true; disposed = true; teardown(); },\n };\n}\n","/** Progressive delivery policy shared by the WebGL and DOM panorama viewers. */\n\nexport interface PanoramaDeliverySource {\n url: string;\n previewUrl?: string;\n sourceWidth?: number;\n sourceHeight?: number;\n previewWidth?: number;\n previewHeight?: number;\n}\n\nexport interface PanoramaDeliveryConstraints {\n saveData?: boolean;\n maxTextureSize?: number;\n maxDecodedBytes?: number;\n}\n\nexport interface PanoramaDeliveryPlan {\n initialUrl: string;\n upgradeUrl?: string;\n initialWidth?: number;\n initialHeight?: number;\n reason: 'full-only' | 'progressive' | 'save-data' | 'texture-limit' | 'memory-limit';\n}\n\n/** Approximate GPU allocation including a complete mip chain (4/3 overhead). */\nexport function estimatePanoramaTextureBytes(width: number, height: number): number {\n if (!Number.isFinite(width) || !Number.isFinite(height) || width <= 0 || height <= 0) return 0;\n return Math.ceil(width * height * 4 * (4 / 3));\n}\n\n/** Conservative per-panorama budget, raised only on devices reporting ample RAM. */\nexport function panoramaTextureBudgetBytes(deviceMemoryGb?: number): number {\n const mib = deviceMemoryGb !== undefined && deviceMemoryGb <= 2\n ? 32\n : deviceMemoryGb !== undefined && deviceMemoryGb >= 8\n ? 192\n : 96;\n return mib * 1024 * 1024;\n}\n\nexport function browserPanoramaConstraints(maxTextureSize?: number): PanoramaDeliveryConstraints {\n const nav = typeof navigator === 'undefined'\n ? undefined\n : navigator as Navigator & { deviceMemory?: number; connection?: { saveData?: boolean } };\n return {\n saveData: nav?.connection?.saveData === true,\n maxTextureSize,\n maxDecodedBytes: panoramaTextureBudgetBytes(nav?.deviceMemory),\n };\n}\n\n/** Select the first image and whether a later sharp-image upgrade is safe. */\nexport function planPanoramaDelivery(\n source: PanoramaDeliverySource,\n constraints: PanoramaDeliveryConstraints = {},\n): PanoramaDeliveryPlan {\n const previewUrl = source.previewUrl?.trim();\n if (!previewUrl || previewUrl === source.url) {\n return {\n initialUrl: source.url,\n initialWidth: source.sourceWidth,\n initialHeight: source.sourceHeight,\n reason: 'full-only',\n };\n }\n\n const initial = {\n initialUrl: previewUrl,\n initialWidth: source.previewWidth,\n initialHeight: source.previewHeight,\n };\n if (constraints.saveData) return { ...initial, reason: 'save-data' };\n if (constraints.maxTextureSize !== undefined && source.sourceWidth !== undefined && source.sourceHeight !== undefined\n && Math.max(source.sourceWidth, source.sourceHeight) > constraints.maxTextureSize) {\n return { ...initial, reason: 'texture-limit' };\n }\n if (constraints.maxDecodedBytes !== undefined && source.sourceWidth !== undefined && source.sourceHeight !== undefined\n && estimatePanoramaTextureBytes(source.sourceWidth, source.sourceHeight) > constraints.maxDecodedBytes) {\n return { ...initial, reason: 'memory-limit' };\n }\n return { ...initial, upgradeUrl: source.url, reason: 'progressive' };\n}\n\nfunction abortError(): DOMException {\n return new DOMException('Panorama image load aborted', 'AbortError');\n}\n\n/** Decode before resolving so callers never upload or reveal a partial image. */\nexport function loadPanoramaImage(url: string, signal?: AbortSignal): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n if (signal?.aborted) { reject(abortError()); return; }\n const image = new Image();\n image.crossOrigin = 'anonymous';\n let settled = false;\n const cleanup = (): void => {\n image.onload = null;\n image.onerror = null;\n signal?.removeEventListener('abort', onAbort);\n };\n const finish = (error?: unknown): void => {\n if (settled) return;\n settled = true;\n cleanup();\n if (error) reject(error);\n else resolve(image);\n };\n const onAbort = (): void => {\n image.src = '';\n finish(abortError());\n };\n image.onload = () => {\n const decoded = typeof image.decode === 'function' ? image.decode() : Promise.resolve();\n void decoded.then(() => finish(), () => finish());\n };\n image.onerror = () => finish(new Error('panorama_image_failed'));\n signal?.addEventListener('abort', onAbort, { once: true });\n image.src = url;\n });\n}\n\n/** Let the preview paint before starting a potentially large decode. */\nexport function schedulePanoramaUpgrade(work: () => void): () => void {\n const host = globalThis as typeof globalThis & {\n requestIdleCallback?: (cb: () => void, opts?: { timeout: number }) => number;\n cancelIdleCallback?: (id: number) => void;\n };\n if (host.requestIdleCallback) {\n const id = host.requestIdleCallback(work, { timeout: 1200 });\n return () => host.cancelIdleCallback?.(id);\n }\n const id = globalThis.setTimeout(work, 32);\n return () => globalThis.clearTimeout(id);\n}\n"],"mappings":"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","names":["id"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { af as SeatViewCoverage } from '../../types-DR54nqKL.cjs';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Slice 3 hand-off — the DOM panorama overlay the fly-to-seat cinematic
|
|
@@ -41,6 +41,8 @@ interface SeatView {
|
|
|
41
41
|
* 8192-wide capture has three to four times the detail to give.
|
|
42
42
|
*/
|
|
43
43
|
generated?: boolean;
|
|
44
|
+
/** Explicit media provenance; avoids inferring buyer truth from source copy. */
|
|
45
|
+
mediaKind?: 'photo' | 'demo-render' | 'model';
|
|
44
46
|
/** Bearing (deg, 0 = facing the focal/stage) the panorama should open centred
|
|
45
47
|
* on, to match the camera's final yaw. Default 0 (both face the stage). */
|
|
46
48
|
initialBearingDeg?: number;
|
|
@@ -107,6 +109,8 @@ interface PanoramaOptions {
|
|
|
107
109
|
fadeMs?: number;
|
|
108
110
|
seatLabel?: string;
|
|
109
111
|
disclosure?: string;
|
|
112
|
+
/** Prefix for the disclosure chip; generated live geometry is not a panorama. */
|
|
113
|
+
disclosurePrefix?: string;
|
|
110
114
|
onClose?: () => void;
|
|
111
115
|
/** Cancels stale preview/full-resolution image work when a flight is retargeted. */
|
|
112
116
|
signal?: AbortSignal;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { af as SeatViewCoverage } from '../../types-DR54nqKL.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Slice 3 hand-off — the DOM panorama overlay the fly-to-seat cinematic
|
|
@@ -41,6 +41,8 @@ interface SeatView {
|
|
|
41
41
|
* 8192-wide capture has three to four times the detail to give.
|
|
42
42
|
*/
|
|
43
43
|
generated?: boolean;
|
|
44
|
+
/** Explicit media provenance; avoids inferring buyer truth from source copy. */
|
|
45
|
+
mediaKind?: 'photo' | 'demo-render' | 'model';
|
|
44
46
|
/** Bearing (deg, 0 = facing the focal/stage) the panorama should open centred
|
|
45
47
|
* on, to match the camera's final yaw. Default 0 (both face the stage). */
|
|
46
48
|
initialBearingDeg?: number;
|
|
@@ -107,6 +109,8 @@ interface PanoramaOptions {
|
|
|
107
109
|
fadeMs?: number;
|
|
108
110
|
seatLabel?: string;
|
|
109
111
|
disclosure?: string;
|
|
112
|
+
/** Prefix for the disclosure chip; generated live geometry is not a panorama. */
|
|
113
|
+
disclosurePrefix?: string;
|
|
110
114
|
onClose?: () => void;
|
|
111
115
|
/** Cancels stale preview/full-resolution image work when a flight is retargeted. */
|
|
112
116
|
signal?: AbortSignal;
|