@virtualdisplay.io/client 4.0.0-alpha.3 → 4.0.0-alpha.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +218 -1
  2. package/README.md +116 -155
  3. package/dist/browser-DfB_An57.cjs +8 -0
  4. package/dist/browser-ey6Iih3j.js +1540 -0
  5. package/dist/index.d.ts +660 -0
  6. package/dist/virtualdisplay.client.cjs.js +606 -1
  7. package/dist/virtualdisplay.client.es.js +4113 -794
  8. package/dist/virtualdisplay.client.umd.js +613 -1
  9. package/package.json +3 -3
  10. package/dist/src/attributes/attribute.d.ts +0 -38
  11. package/dist/src/attributes/attribute.d.ts.map +0 -1
  12. package/dist/src/attributes/attributes.d.ts +0 -43
  13. package/dist/src/attributes/attributes.d.ts.map +0 -1
  14. package/dist/src/attributes/selections.d.ts +0 -16
  15. package/dist/src/attributes/selections.d.ts.map +0 -1
  16. package/dist/src/attributes/value.d.ts +0 -13
  17. package/dist/src/attributes/value.d.ts.map +0 -1
  18. package/dist/src/camera/camera.d.ts +0 -40
  19. package/dist/src/camera/camera.d.ts.map +0 -1
  20. package/dist/src/camera/index.d.ts +0 -4
  21. package/dist/src/camera/index.d.ts.map +0 -1
  22. package/dist/src/camera/position.d.ts +0 -43
  23. package/dist/src/camera/position.d.ts.map +0 -1
  24. package/dist/src/client/client.d.ts +0 -83
  25. package/dist/src/client/client.d.ts.map +0 -1
  26. package/dist/src/client/error.d.ts +0 -26
  27. package/dist/src/client/error.d.ts.map +0 -1
  28. package/dist/src/client/iframe-builder.d.ts +0 -25
  29. package/dist/src/client/iframe-builder.d.ts.map +0 -1
  30. package/dist/src/client/options.d.ts +0 -17
  31. package/dist/src/client/options.d.ts.map +0 -1
  32. package/dist/src/client/ready-state-tracker.d.ts +0 -26
  33. package/dist/src/client/ready-state-tracker.d.ts.map +0 -1
  34. package/dist/src/client/types.d.ts +0 -5
  35. package/dist/src/client/types.d.ts.map +0 -1
  36. package/dist/src/index.d.ts +0 -11
  37. package/dist/src/index.d.ts.map +0 -1
  38. package/dist/src/snapshot/index.d.ts +0 -3
  39. package/dist/src/snapshot/index.d.ts.map +0 -1
  40. package/dist/src/snapshot/photo.d.ts +0 -47
  41. package/dist/src/snapshot/photo.d.ts.map +0 -1
  42. package/dist/src/snapshot/snapshot.d.ts +0 -30
  43. package/dist/src/snapshot/snapshot.d.ts.map +0 -1
  44. package/dist/src/ui/viewer-config.d.ts +0 -63
  45. package/dist/src/ui/viewer-config.d.ts.map +0 -1
  46. package/dist/src/utils/logger.d.ts +0 -11
  47. package/dist/src/utils/logger.d.ts.map +0 -1
@@ -1,798 +1,4117 @@
1
- //#region src/client/error.ts
2
- var e = {
3
- NO_MAPPING: "NO_MAPPING",
4
- ATTRIBUTE_NOT_FOUND: "ATTRIBUTE_NOT_FOUND",
5
- VALUE_NOT_FOUND: "VALUE_NOT_FOUND",
6
- PARENT_NOT_FOUND: "PARENT_NOT_FOUND",
7
- INVALID_PARAMETER: "INVALID_PARAMETER",
8
- SNAPSHOT_FAILED: "SNAPSHOT_FAILED"
9
- }, t = class t extends Error {
10
- code;
11
- constructor(e, n) {
12
- super(e), this.name = "VirtualdisplayError", this.code = n, Error.captureStackTrace?.(this, t);
13
- }
14
- static noMapping() {
15
- return new t("No attributes available. Waiting for server metadata.", e.NO_MAPPING);
16
- }
17
- static attributeNotFound(n) {
18
- return new t(`Attribute "${n}" not found in mapping`, e.ATTRIBUTE_NOT_FOUND);
19
- }
20
- static valueNotFound(n, r) {
21
- return new t(`Value "${r}" not found for attribute "${n}"`, e.VALUE_NOT_FOUND);
22
- }
23
- static parentNotFound(n) {
24
- return new t(`Parent element not found: ${n}`, e.PARENT_NOT_FOUND);
25
- }
26
- static invalidParameter(n) {
27
- return new t(n, e.INVALID_PARAMETER);
28
- }
29
- static snapshotFailed(n, r) {
30
- return new t(`Snapshot failed for "${n}": ${r}`, e.SNAPSHOT_FAILED);
31
- }
32
- }, n = class {
33
- defaultServer = "https://server.virtualdisplay.io";
34
- options;
35
- constructor(e) {
36
- this.options = e;
37
- }
38
- create() {
39
- let e = this.createIframe();
40
- return this.attachToParent(e), e;
41
- }
42
- createIframe() {
43
- let e = document.createElement("iframe");
44
- return e.src = this.buildUrl(), Object.assign(e.style, {
45
- width: "100%",
46
- height: "100%",
47
- border: "none"
48
- }), e.setAttribute("allow", "xr-spatial-tracking; fullscreen"), e;
49
- }
50
- attachToParent(e) {
51
- this.getParentElement().appendChild(e);
52
- }
53
- buildUrl() {
54
- let e = this.getServerUrl(), t = new URL(e);
55
- return t.searchParams.set("license", this.options.license), t.searchParams.set("model", this.options.model), this.options.debug === !0 && t.searchParams.set("debug", "true"), this.options.language !== void 0 && t.searchParams.set("language", this.options.language), t.toString();
56
- }
57
- getServerUrl() {
58
- return this.defaultServer;
59
- }
60
- getParentElement() {
61
- if (typeof this.options.parent != "string") return this.options.parent;
62
- let e = document.querySelector(this.options.parent);
63
- if (e === null) throw t.parentNotFound(this.options.parent);
64
- return e;
65
- }
1
+ const c = {
2
+ HANDSHAKE: "handshake",
3
+ SESSION: "session",
4
+ MUTATION: "mutation",
5
+ STATE: "state",
6
+ LOADING_INDICATOR: "loading_indicator",
7
+ CAMERA: "camera",
8
+ SNAPSHOT: "snapshot",
9
+ MAPPING: "mapping",
10
+ AR_ACTIVATE: "ar_activate",
11
+ FULLSCREEN: "fullscreen",
12
+ CONFIG: "config",
13
+ ANNOTATION_TOGGLE: "annotation_toggle",
14
+ FOCUS: "focus",
15
+ PRICING: "pricing",
16
+ QR_SHOWN: "qr_shown",
17
+ SYNC: "sync",
18
+ SELECTABLE: "selectable",
19
+ APP_RESOLVED: "app_resolved"
20
+ }, L = "main", et = {
21
+ FOCUS: "focus",
22
+ UNFOCUS: "unfocus"
23
+ }, It = {
24
+ FOCUSED: "focused"
25
+ }, Ot = {
26
+ /** Price and submit button both shown (default). */
27
+ AVAILABLE: "available",
28
+ /** Price shown, submit hidden (sold out / coming soon). */
29
+ VIEW_ONLY: "view_only",
30
+ /** Behaves exactly like an unpriced combination: no price, no button. */
31
+ HIDDEN: "hidden"
32
+ }, Lt = new Set(Object.values(c));
33
+ function Mt(n) {
34
+ if (typeof n != "object" || n === null)
35
+ return !1;
36
+ const t = n;
37
+ return typeof t.target == "string" ? !1 : typeof t.type == "string" && Lt.has(t.type);
38
+ }
39
+ function Nt(n) {
40
+ const t = S(n);
41
+ return t !== null && t.type === c.HANDSHAKE;
42
+ }
43
+ function zt(n) {
44
+ const t = S(n);
45
+ return t !== null && t.type === c.SESSION && typeof t.sessionId == "string";
46
+ }
47
+ function Pt(n) {
48
+ const t = S(n);
49
+ return t !== null && t.type === c.APP_RESOLVED && typeof t.appType == "string" && t.appType !== "" && typeof t.name == "string";
50
+ }
51
+ function Ft(n) {
52
+ const t = S(n);
53
+ return t !== null && t.type === c.MUTATION && typeof t.instance == "string" && t.instance !== "" && Array.isArray(t.results);
54
+ }
55
+ function $t(n) {
56
+ const t = S(n);
57
+ return t !== null && t.type === c.SNAPSHOT && ("data" in t || "error" in t);
58
+ }
59
+ function Dt(n) {
60
+ const t = S(n);
61
+ return t !== null && t.type === c.CAMERA && Array.isArray(t.transforms);
62
+ }
63
+ function Bt(n) {
64
+ const t = S(n);
65
+ return t !== null && t.type === c.MAPPING && typeof t.instance == "string" && t.instance !== "" && Array.isArray(t.attributes);
66
+ }
67
+ function Ut(n) {
68
+ const t = S(n);
69
+ return t !== null && t.type === c.LOADING_INDICATOR && typeof t.progress == "number";
70
+ }
71
+ function Vt(n) {
72
+ const t = S(n);
73
+ return t !== null && t.type === c.SYNC && typeof t.connected == "boolean";
74
+ }
75
+ function Ht(n) {
76
+ const t = S(n);
77
+ return t !== null && t.type === c.SELECTABLE && typeof t.instance == "string" && t.instance !== "" && Array.isArray(t.attributes);
78
+ }
79
+ function jt(n) {
80
+ const t = S(n);
81
+ return t !== null && t.type === c.CONFIG && typeof t.viewerSettings == "object";
82
+ }
83
+ function qt(n) {
84
+ const t = S(n);
85
+ return t !== null && t.type === c.FOCUS && typeof t.instance == "string" && t.instance !== "" && typeof t.state == "string";
86
+ }
87
+ function Gt(n) {
88
+ const t = S(n);
89
+ return t !== null && t.type === c.PRICING && typeof t.instance == "string" && t.instance !== "" && typeof t.pricing == "object" && t.pricing !== null;
90
+ }
91
+ function S(n) {
92
+ return typeof n != "object" || n === null ? null : n;
93
+ }
94
+ class nt {
95
+ constructor(t) {
96
+ this.processor = t;
97
+ }
98
+ processor;
99
+ pending = [];
100
+ isReady = !1;
101
+ /**
102
+ * Add a message — processed immediately if ready, queued otherwise
103
+ */
104
+ enqueue(t) {
105
+ if (this.isReady) {
106
+ this.processor(t);
107
+ return;
108
+ }
109
+ this.pending.push(t);
110
+ }
111
+ /**
112
+ * Mark as ready and flush all pending messages
113
+ */
114
+ markReady() {
115
+ if (this.isReady || (this.isReady = !0, this.pending.length === 0))
116
+ return;
117
+ const t = [...this.pending];
118
+ this.pending.length = 0;
119
+ for (const e of t)
120
+ this.processor(e);
121
+ }
122
+ get ready() {
123
+ return this.isReady;
124
+ }
125
+ get pendingCount() {
126
+ return this.pending.length;
127
+ }
128
+ /**
129
+ * Check if a message of a specific type is pending
130
+ */
131
+ hasPending(t) {
132
+ return this.pending.some((e) => e.type === t);
133
+ }
134
+ /**
135
+ * Clear all pending messages
136
+ */
137
+ clear() {
138
+ this.pending.length = 0;
139
+ }
140
+ }
141
+ class Zt {
142
+ constructor(t, e) {
143
+ this.getTarget = t, this.sourceId = `vd-${Math.random().toString(36).slice(2, 8)}`, this.targetOrigin = "*", this.isValid = e?.isValid ?? Mt, this.onHandlerError = e?.onHandlerError ?? Wt, this.publisher = new nt((i) => this.post(i)), this.consumer = new nt((i) => this.routeToHandler(i)), e?.bufferIncoming !== !0 && this.consumer.markReady();
144
+ }
145
+ getTarget;
146
+ handlers = /* @__PURE__ */ new Map();
147
+ publisher;
148
+ consumer;
149
+ listener = null;
150
+ sourceId;
151
+ targetOrigin;
152
+ isValid;
153
+ onHandlerError;
154
+ /**
155
+ * Register a handler for a specific message type
156
+ */
157
+ on(t, e) {
158
+ this.handlers.set(t, e);
159
+ }
160
+ /**
161
+ * Update the target origin for outgoing postMessage calls.
162
+ * Use when the target origin is discovered at runtime (e.g. from the first incoming message).
163
+ */
164
+ setTargetOrigin(t) {
165
+ this.targetOrigin = t;
166
+ }
167
+ /**
168
+ * Send a message to the target window
169
+ * Queued until markReady() is called
170
+ */
171
+ send(t) {
172
+ this.publisher.enqueue(t);
173
+ }
174
+ // For a transport that delivers messages some way other than the iframe
175
+ // channel this messenger listens on, such as the viewer's live sync channel,
176
+ // and needs them to land exactly where a postMessage would.
177
+ /**
178
+ * Route a message as if it had arrived by postMessage, without the round trip.
179
+ * Still validated against `isValid` and still subject to the consumer queue.
180
+ */
181
+ receive(t) {
182
+ this.isValid(t) && this.consumer.enqueue(t);
183
+ }
184
+ /**
185
+ * Enable sending and flush queued outgoing messages
186
+ */
187
+ markReady() {
188
+ this.publisher.markReady();
189
+ }
190
+ /**
191
+ * Enable processing of buffered incoming messages
192
+ * Only relevant when constructed with bufferIncoming: true
193
+ */
194
+ markConsumerReady() {
195
+ this.consumer.markReady();
196
+ }
197
+ /**
198
+ * Check if an incoming message of a specific type is pending in the consumer buffer
199
+ */
200
+ consumerHasPending(t) {
201
+ return this.consumer.hasPending(t);
202
+ }
203
+ /**
204
+ * Start listening for incoming messages
205
+ */
206
+ start() {
207
+ this.listener === null && (this.listener = (t) => {
208
+ const { data: e } = t;
209
+ if (!this.isValid(e) || e._source === this.sourceId)
210
+ return;
211
+ const i = this.getTarget();
212
+ if (i !== null && t.source !== null && t.source !== i)
213
+ return;
214
+ const o = e;
215
+ this.consumer.enqueue(o);
216
+ }, window.addEventListener("message", this.listener));
217
+ }
218
+ /**
219
+ * Stop listening and clean up all state
220
+ */
221
+ destroy() {
222
+ this.listener !== null && (window.removeEventListener("message", this.listener), this.listener = null), this.publisher.clear(), this.consumer.clear(), this.handlers.clear();
223
+ }
224
+ post(t) {
225
+ const e = this.getTarget();
226
+ e !== null && e.postMessage({ ...t, _source: this.sourceId }, this.targetOrigin);
227
+ }
228
+ routeToHandler(t) {
229
+ const e = this.handlers.get(t.type);
230
+ if (e === void 0)
231
+ return;
232
+ const i = e(t);
233
+ i instanceof Promise && i.catch((o) => {
234
+ this.onHandlerError(t, o);
235
+ });
236
+ }
237
+ }
238
+ function Wt(n, t) {
239
+ const e = t instanceof Error ? t.message : String(t);
240
+ console.error(`Virtualdisplay: error handling ${n.type}: ${e}`);
241
+ }
242
+ class Qt {
243
+ /** @internal */
244
+ constructor(t, e) {
245
+ this.send = t, this.instance = e;
246
+ }
247
+ send;
248
+ instance;
249
+ setEnabled(t) {
250
+ this.send({
251
+ type: c.ANNOTATION_TOGGLE,
252
+ instance: this.instance,
253
+ enabled: t
254
+ });
255
+ }
256
+ }
257
+ class Yt {
258
+ /** @internal */
259
+ constructor(t, e) {
260
+ this.value = t, this.attribute = e;
261
+ }
262
+ value;
263
+ attribute;
264
+ isSelectedValue = !1;
265
+ get isSelected() {
266
+ return this.isSelectedValue;
267
+ }
268
+ /** @internal */
269
+ setSelected(t) {
270
+ this.isSelectedValue = t;
271
+ }
272
+ }
273
+ const A = {
274
+ NO_MAPPING: "NO_MAPPING",
275
+ ATTRIBUTE_NOT_FOUND: "ATTRIBUTE_NOT_FOUND",
276
+ VALUE_NOT_FOUND: "VALUE_NOT_FOUND",
277
+ PARENT_NOT_FOUND: "PARENT_NOT_FOUND",
278
+ INVALID_PARAMETER: "INVALID_PARAMETER",
279
+ SNAPSHOT_FAILED: "SNAPSHOT_FAILED"
280
+ };
281
+ class y extends Error {
282
+ code;
283
+ constructor(t, e) {
284
+ super(t), this.name = "VirtualdisplayError", this.code = e;
285
+ }
286
+ static valueNotFound(t, e) {
287
+ return new y(
288
+ `Value "${e}" not found for attribute "${t}"`,
289
+ A.VALUE_NOT_FOUND
290
+ );
291
+ }
292
+ static parentNotFound(t) {
293
+ return new y(
294
+ `Parent element not found: ${t}`,
295
+ A.PARENT_NOT_FOUND
296
+ );
297
+ }
298
+ static invalidParameter(t) {
299
+ return new y(t, A.INVALID_PARAMETER);
300
+ }
301
+ }
302
+ class Kt {
303
+ name;
304
+ defaultValue;
305
+ values;
306
+ onSelect;
307
+ onChangeCallback;
308
+ /** @internal */
309
+ constructor(t) {
310
+ this.name = t.name, this.defaultValue = t.defaultValue, this.onSelect = t.onSelect, this.values = this.createValues(t.values);
311
+ }
312
+ /**
313
+ * Select a value for this attribute
314
+ * @throws {VirtualdisplayError} When value not found
315
+ */
316
+ select(t) {
317
+ if (!this.values.has(t))
318
+ throw y.valueNotFound(this.name, t);
319
+ this.onSelect(this.name, t);
320
+ }
321
+ getValues() {
322
+ return Array.from(this.values.values());
323
+ }
324
+ getValue(t) {
325
+ return this.values.get(t);
326
+ }
327
+ /** The currently selected value, or undefined before any selection lands. */
328
+ get selected() {
329
+ for (const t of this.values.values())
330
+ if (t.isSelected)
331
+ return t.value;
332
+ }
333
+ /**
334
+ * Register callback that fires when selection changes
335
+ * Receives the newly selected AttributeValue
336
+ */
337
+ onChange(t) {
338
+ this.onChangeCallback = t;
339
+ }
340
+ /**
341
+ * Update selection state from server mutation result
342
+ * Direct value comparison - no source/target translation
343
+ * @internal
344
+ */
345
+ handleResult(t) {
346
+ let e;
347
+ for (const i of this.values.values()) {
348
+ const o = i.value === t;
349
+ i.setSelected(o), o && (e = i);
350
+ }
351
+ e !== void 0 && this.onChangeCallback !== void 0 && this.onChangeCallback(e);
352
+ }
353
+ createValues(t) {
354
+ const e = /* @__PURE__ */ new Map();
355
+ for (const i of t) {
356
+ const o = new Yt(i, this.name);
357
+ i === this.defaultValue && o.setSelected(!0), e.set(i, o);
358
+ }
359
+ return e;
360
+ }
361
+ }
362
+ class Xt {
363
+ attributes = /* @__PURE__ */ new Map();
364
+ constructor(t, e, i) {
365
+ for (const o of t) {
366
+ const s = this.buildOptions(o, e);
367
+ this.attributes.set(o.name, new Kt(s));
368
+ }
369
+ this.onMutationFailed = i;
370
+ }
371
+ onMutationFailed;
372
+ get(t) {
373
+ return this.attributes.get(t);
374
+ }
375
+ /** The current selection as attribute name→value pairs, skipping unset attributes. */
376
+ getSelection() {
377
+ const t = {};
378
+ for (const [e, i] of this.attributes) {
379
+ const o = i.selected;
380
+ o !== void 0 && (t[e] = o);
381
+ }
382
+ return t;
383
+ }
384
+ /** @internal */
385
+ handleResults(t) {
386
+ for (const e of t) {
387
+ if (!e.success) {
388
+ this.onMutationFailed?.(e.name, e.value);
389
+ continue;
390
+ }
391
+ this.get(e.name)?.handleResult(e.value);
392
+ }
393
+ }
394
+ buildOptions(t, e) {
395
+ const i = {
396
+ name: t.name,
397
+ values: t.values,
398
+ onSelect: (o, s) => {
399
+ e([{ name: o, value: s }], !1);
400
+ }
401
+ };
402
+ return t.defaultValue !== void 0 ? { ...i, defaultValue: t.defaultValue } : i;
403
+ }
404
+ }
405
+ const N = 4096, $ = 16384, Jt = [1024, 256, 64], te = 20, Q = ["timestamp", "level", "msg", "service", "trace_id", "span_id"];
406
+ function ee(n) {
407
+ const t = new Date(n.timestamp).toLocaleTimeString("en-GB", { hour12: !1 }), e = n.level.toUpperCase().padEnd(5), i = n.trace_id?.slice(0, 8) ?? "--------", o = n.msg, s = Object.entries(n).filter(([r]) => !Q.includes(r)).map(([r, a]) => `${r}=${O(a, N).line}`).join(" ");
408
+ return `${t} ${e} [${i}] ${o}${s ? " " + s : ""}`;
409
+ }
410
+ function ne(n) {
411
+ for (const o of [N, ...Jt]) {
412
+ const { line: s, truncated: r } = O(n, o);
413
+ if (!r) {
414
+ if (s.length <= $)
415
+ return s;
416
+ continue;
417
+ }
418
+ const { line: a } = O({ ...n, log_truncated: !0 }, o);
419
+ if (a.length <= $)
420
+ return a;
421
+ }
422
+ const t = Object.keys(n).filter((o) => !Q.includes(o)), e = { ...oe(n, Q), log_truncated: !0, log_dropped_count: t.length }, i = O({ ...e, log_dropped_attrs: t.slice(0, te) }, N);
423
+ return i.line.length <= $ ? i.line : O(e, N).line;
424
+ }
425
+ function O(n, t) {
426
+ const e = [];
427
+ let i = !1;
428
+ return { line: JSON.stringify(n, function(r, a) {
429
+ if (typeof a == "string")
430
+ return a.length > t && (i = !0), ie(a, t);
431
+ if (typeof a == "bigint")
432
+ return a.toString();
433
+ if (typeof a != "object" || a === null)
434
+ return a;
435
+ for (; e.length > 0 && e.at(-1) !== this; )
436
+ e.pop();
437
+ return e.includes(a) ? "[Circular]" : (e.push(a), a);
438
+ }), truncated: i };
439
+ }
440
+ function ie(n, t) {
441
+ return n.length <= t ? n : `${n.slice(0, t)}...[truncated ${n.length - t} chars]`;
442
+ }
443
+ function oe(n, t) {
444
+ const e = {};
445
+ for (const i of t)
446
+ n[i] !== void 0 && (e[i] = n[i]);
447
+ return e;
448
+ }
449
+ function se(n, t) {
450
+ const e = t ? ee(n) : ne(n);
451
+ n.level === "error" ? console.error(e) : n.level === "warn" ? console.warn(e) : console.log(e);
452
+ }
453
+ function ft(n, t = 0) {
454
+ const e = {
455
+ name: n.name,
456
+ message: n.message
457
+ };
458
+ n.stack !== void 0 && (e.stack = n.stack);
459
+ for (const i of Object.getOwnPropertyNames(n)) {
460
+ if (i === "name" || i === "message" || i === "stack" || i === "cause")
461
+ continue;
462
+ const o = n[i];
463
+ o !== void 0 && (e[i] = o);
464
+ }
465
+ return n.cause !== void 0 && n.cause !== null && (e.cause = n.cause instanceof Error && t < 5 ? ft(n.cause, t + 1) : String(n.cause)), e;
466
+ }
467
+ function re(n) {
468
+ const t = {};
469
+ for (const [e, i] of Object.entries(n))
470
+ t[e] = i instanceof Error ? ft(i) : i;
471
+ return t;
472
+ }
473
+ function ae() {
474
+ return /* @__PURE__ */ new Date();
475
+ }
476
+ function le(n) {
477
+ return n.toISOString();
478
+ }
479
+ function gt() {
480
+ return typeof navigator > "u" ? "" : navigator.userAgent;
481
+ }
482
+ function ce() {
483
+ return typeof navigator > "u" ? !1 : navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1;
484
+ }
485
+ function de() {
486
+ return /iPhone|iPad|iPod/i.test(gt()) || ce();
487
+ }
488
+ function ue() {
489
+ return /Android/i.test(gt());
490
+ }
491
+ function he() {
492
+ return de() || ue();
493
+ }
494
+ class pe {
495
+ constructor(t) {
496
+ this.config = t;
497
+ }
498
+ config;
499
+ /**
500
+ * Log at specified level with optional attributes.
501
+ */
502
+ log(t, e, i = {}) {
503
+ const o = this.config.traceProvider?.() ?? {}, s = {
504
+ timestamp: le(ae()),
505
+ level: t,
506
+ msg: e,
507
+ service: this.config.service,
508
+ ...o,
509
+ ...re(i)
510
+ };
511
+ se(s, this.config.isDev);
512
+ }
513
+ /**
514
+ * Log at DEBUG level (development only).
515
+ */
516
+ debug(t, e) {
517
+ this.config.isDev && this.log("debug", t, e);
518
+ }
519
+ /**
520
+ * Log at INFO level.
521
+ */
522
+ info(t, e) {
523
+ this.log("info", t, e);
524
+ }
525
+ /**
526
+ * Log at WARN level.
527
+ */
528
+ warn(t, e) {
529
+ this.log("warn", t, e);
530
+ }
531
+ /**
532
+ * Log at ERROR level.
533
+ */
534
+ error(t, e) {
535
+ this.log("error", t, e);
536
+ }
537
+ }
538
+ function bt(n, t) {
539
+ const e = t?.isDev !== void 0 ? t.isDev : typeof process < "u" ? process.env.NODE_ENV !== "production" : !0;
540
+ return new pe({ service: n, isDev: e, traceProvider: t?.traceProvider });
541
+ }
542
+ function M(n) {
543
+ if (n !== void 0)
544
+ return typeof n == "object" && n !== null && !Array.isArray(n) && !(n instanceof Error) ? n : n instanceof Error ? { error: n.message } : { value: n };
545
+ }
546
+ function vt(n) {
547
+ return {
548
+ debug: (t, e) => n.debug(t, M(e)),
549
+ info: (t, e) => n.info(t, M(e)),
550
+ warn: (t, e) => n.warn(t, M(e)),
551
+ error: (t, e) => n.error(t, M(e))
552
+ };
553
+ }
554
+ let yt = !1, w = vt(bt("client", { isDev: !1 }));
555
+ function me() {
556
+ yt = !0, w = vt(bt("client", { isDev: !0 }));
557
+ }
558
+ function Ao() {
559
+ return yt;
560
+ }
561
+ class fe {
562
+ /** @internal */
563
+ constructor(t, e, i) {
564
+ this.send = t, this.instance = e, this.onMutationFailed = i;
565
+ }
566
+ send;
567
+ instance;
568
+ onMutationFailed;
569
+ registry;
570
+ pendingCallbacks = [];
571
+ /**
572
+ * Handle incoming MAPPING message from server
573
+ * @internal
574
+ */
575
+ handleMappingReceived(t) {
576
+ w.debug("Initializing from server mapping", {
577
+ attributeCount: t.attributes.length
578
+ }), this.registry = new Xt(
579
+ t.attributes,
580
+ (e, i) => this.sendMutation(e, i),
581
+ this.onMutationFailed
582
+ ), this.replayPendingCallbacks();
583
+ }
584
+ /** @internal */
585
+ handleMutationResult(t) {
586
+ this.registry?.handleResults(t.results);
587
+ }
588
+ /**
589
+ * Select a value for an attribute - sends mutation to server.
590
+ * Can be called before the mapping arrives — the mutation is sent immediately
591
+ * and the messenger's publisher queue holds it until the iframe is ready.
592
+ */
593
+ select(t, e) {
594
+ this.sendMutation([{ name: t, value: e }], !1);
595
+ }
596
+ /**
597
+ * A drag's intermediate step (parametric dimensions): the viewer applies it
598
+ * so the model follows the hand, but it is never recorded as a choice; the
599
+ * settling `select()` is the one tracked value. Used by the measurement
600
+ * slider per step crossed. Deliberately not delegated on the public client:
601
+ * `select()` staying the whole choice API is the spec's own evidence the
602
+ * shape is right, and no integrator has asked for live-follow yet.
603
+ */
604
+ selectTransient(t, e) {
605
+ this.sendMutation([{ name: t, value: e }], !1, !0);
606
+ }
607
+ getValues(t) {
608
+ return this.registry?.get(t)?.getValues() ?? [];
609
+ }
610
+ getValue(t, e) {
611
+ return this.registry?.get(t)?.getValue(e);
612
+ }
613
+ /**
614
+ * Register onChange callback for an attribute.
615
+ * Buffers the callback if the mapping hasn't arrived yet.
616
+ */
617
+ onChange(t, e) {
618
+ if (this.registry === void 0) {
619
+ this.pendingCallbacks.push({ name: t, callback: e });
620
+ return;
621
+ }
622
+ const i = this.registry.get(t);
623
+ if (i === void 0) {
624
+ w.warn(`onChange: attribute "${t}" not found — callback will not fire`);
625
+ return;
626
+ }
627
+ i.onChange(e);
628
+ }
629
+ getAttribute(t) {
630
+ return this.registry?.get(t);
631
+ }
632
+ /** The current selection as attribute name→value pairs, empty before the mapping arrives. */
633
+ getSelection() {
634
+ return this.registry?.getSelection() ?? {};
635
+ }
636
+ sendMutation(t, e, i = !1) {
637
+ const o = {
638
+ type: c.MUTATION,
639
+ instance: this.instance,
640
+ mutations: t,
641
+ isInitial: e,
642
+ ...i && { transient: !0 }
643
+ };
644
+ this.send(o);
645
+ }
646
+ replayPendingCallbacks() {
647
+ for (const { name: t, callback: e } of this.pendingCallbacks) {
648
+ const i = this.registry?.get(t);
649
+ i === void 0 && w.warn(`onChange: attribute "${t}" not found — buffered callback will not fire`), i?.onChange(e);
650
+ }
651
+ this.pendingCallbacks.length = 0;
652
+ }
653
+ }
654
+ class ge {
655
+ snapshot;
656
+ callbacks = [];
657
+ /** @internal */
658
+ handleSnapshot(t) {
659
+ this.snapshot = t.attributes;
660
+ for (const e of this.callbacks)
661
+ e(t.attributes);
662
+ }
663
+ onChange(t) {
664
+ this.callbacks.push(t), this.snapshot !== void 0 && t(this.snapshot);
665
+ }
666
+ /** The selectable values for one attribute, or null when unrestricted. */
667
+ selectableFor(t) {
668
+ return this.snapshot?.find((i) => i.name === t)?.selectable ?? null;
669
+ }
670
+ /** The latest snapshot; empty before the viewer has reported anything. */
671
+ get current() {
672
+ return this.snapshot ?? [];
673
+ }
674
+ }
675
+ let be = class {
676
+ /** @internal */
677
+ constructor(t) {
678
+ this.send = t;
679
+ }
680
+ send;
681
+ commands = [];
682
+ /**
683
+ * Rotate camera horizontally
684
+ * @param degrees - Absolute rotation in degrees (0 = front view)
685
+ * @throws VirtualdisplayError if value is not a finite number
686
+ */
687
+ rotate(t) {
688
+ return this.validateFiniteNumber(t, "rotate"), this.commands.push({ action: "rotate", value: t }), this;
689
+ }
690
+ /**
691
+ * Tilt camera vertically
692
+ * @param degrees - Absolute tilt in degrees (0 = top view, 90 = horizontal)
693
+ * @throws VirtualdisplayError if value is not a finite number
694
+ */
695
+ tilt(t) {
696
+ return this.validateFiniteNumber(t, "tilt"), this.commands.push({ action: "tilt", value: t }), this;
697
+ }
698
+ /**
699
+ * Zoom camera
700
+ * @param percentage - Zoom percentage (100 = normal, >100 = zoom in, <100 = zoom out)
701
+ * @throws VirtualdisplayError if value is not a finite number
702
+ */
703
+ zoom(t) {
704
+ return this.validateFiniteNumber(t, "zoom"), this.commands.push({ action: "zoom", value: t }), this;
705
+ }
706
+ /**
707
+ * Execute accumulated camera commands
708
+ * @throws VirtualdisplayError if no commands have been accumulated
709
+ */
710
+ set() {
711
+ if (this.commands.length === 0)
712
+ throw y.invalidParameter(
713
+ "No camera commands to execute. Call rotate(), tilt(), or zoom() before set()."
714
+ );
715
+ const t = {
716
+ type: c.CAMERA,
717
+ commands: this.commands
718
+ };
719
+ this.send(t), this.commands = [];
720
+ }
721
+ validateFiniteNumber(t, e) {
722
+ if (!Number.isFinite(t))
723
+ throw y.invalidParameter(
724
+ `${e}() requires a finite number, got ${String(t)}`
725
+ );
726
+ }
727
+ /**
728
+ * Reset camera to initial position
729
+ * This is not chainable and executes immediately
730
+ */
731
+ reset() {
732
+ const t = {
733
+ type: c.CAMERA,
734
+ commands: [{ action: "reset" }]
735
+ };
736
+ this.send(t);
737
+ }
738
+ };
739
+ class ve {
740
+ state;
741
+ limitsState;
742
+ changeCallbacks = [];
743
+ /** @internal */
744
+ constructor() {
745
+ this.state = {
746
+ rotation: 0,
747
+ tilt: 0,
748
+ zoom: 100
749
+ };
750
+ }
751
+ /**
752
+ * Get current rotation in degrees
753
+ */
754
+ get rotation() {
755
+ return this.state.rotation;
756
+ }
757
+ /**
758
+ * Get current tilt in degrees
759
+ */
760
+ get tilt() {
761
+ return this.state.tilt;
762
+ }
763
+ /**
764
+ * Get current zoom percentage
765
+ */
766
+ get zoom() {
767
+ return this.state.zoom;
768
+ }
769
+ /**
770
+ * Subscribe to camera position changes. Also fires when the viewer reports new
771
+ * movement limits, so controls can re-evaluate their disabled state without a
772
+ * second callback.
773
+ */
774
+ onChange(t) {
775
+ this.changeCallbacks.push(t);
776
+ }
777
+ /**
778
+ * Current camera movement limits (set from CONFIG message).
779
+ * Undefined until the server sends limits in its CONFIG payload.
780
+ */
781
+ get limits() {
782
+ return this.limitsState;
783
+ }
784
+ /**
785
+ * Apply camera limits received from the server CONFIG message.
786
+ * Fires onChange callbacks so button disabled states update immediately.
787
+ * @internal
788
+ */
789
+ applyLimits(t) {
790
+ this.limitsState = t;
791
+ const e = { rotation: this.rotation, tilt: this.tilt, zoom: this.zoom };
792
+ for (const i of this.changeCallbacks)
793
+ i(e);
794
+ }
795
+ /**
796
+ * Handle incoming camera position message from server
797
+ * @internal
798
+ */
799
+ handlePositionUpdate(t) {
800
+ this.update(t.transforms);
801
+ }
802
+ /**
803
+ * Update camera position from server transforms
804
+ * @internal
805
+ */
806
+ update(t) {
807
+ let e = !1;
808
+ for (const i of t)
809
+ switch (i.type) {
810
+ case "rotation":
811
+ this.state.rotation = i.value, e = !0;
812
+ break;
813
+ case "tilt":
814
+ this.state.tilt = i.value, e = !0;
815
+ break;
816
+ case "zoom":
817
+ this.state.zoom = i.value, e = !0;
818
+ break;
819
+ }
820
+ if (e) {
821
+ const i = {
822
+ rotation: this.rotation,
823
+ tilt: this.tilt,
824
+ zoom: this.zoom
825
+ };
826
+ for (const o of this.changeCallbacks)
827
+ o(i);
828
+ }
829
+ }
830
+ }
831
+ function ye(n, t) {
832
+ const e = n.filter((s) => we(s, t));
833
+ if (e.length === 0)
834
+ return;
835
+ const i = Math.max(...e.map(it)), o = e.filter((s) => it(s) === i);
836
+ return o.length === 1 ? o[0] : void 0;
837
+ }
838
+ function we(n, t) {
839
+ return Object.entries(n.attributes).every(([e, i]) => t[e] === i);
840
+ }
841
+ function it(n) {
842
+ return Object.keys(n.attributes).length;
843
+ }
844
+ class Se {
845
+ /** @internal */
846
+ constructor(t, e) {
847
+ this.dispatchChange = t, this.dispatchSubmit = e;
848
+ }
849
+ dispatchChange;
850
+ dispatchSubmit;
851
+ pricing;
852
+ selection = {};
853
+ resolved;
854
+ changeCallbacks = [];
855
+ renderCallbacks = [];
856
+ submitCallback;
857
+ submitDefaultLabel;
858
+ /** The resolved configuration, or undefined when nothing matches. */
859
+ get current() {
860
+ return this.resolved;
861
+ }
862
+ /** The VAT label for bare embeds (e.g. "incl. btw"), or undefined without pricing. */
863
+ get vatDisplay() {
864
+ return this.pricing?.vatDisplay;
865
+ }
866
+ /**
867
+ * The CTA label: the customer's `button_label`, or the adapter's `defaultLabel`
868
+ * when that is empty. Undefined when neither is set — a button must know what
869
+ * it says, so the CTA does not render.
870
+ */
871
+ get buttonLabel() {
872
+ const t = this.pricing?.buttonLabel;
873
+ if (t != null && t !== "")
874
+ return t;
875
+ if (this.submitDefaultLabel !== void 0 && this.submitDefaultLabel !== "")
876
+ return this.submitDefaultLabel;
877
+ }
878
+ /**
879
+ * Whether the current configuration may be submitted: a handler is registered
880
+ * and the configuration is either price-less or an `available` priced one. The
881
+ * CTA's visibility ({@link submitEnabled}) layers a label requirement on top of
882
+ * this; a programmatic `submit()` does not need a label.
883
+ */
884
+ get submittable() {
885
+ return this.submitCallback === void 0 ? !1 : this.pricing === void 0 ? !0 : this.resolved?.availability === "available";
886
+ }
887
+ /**
888
+ * Whether the CTA should render: the configuration is submittable and a label
889
+ * exists. A button must say something, so an empty label hides the CTA rather
890
+ * than rendering blank.
891
+ */
892
+ get submitEnabled() {
893
+ return this.submittable && this.buttonLabel !== void 0;
894
+ }
895
+ /**
896
+ * Subscribe to configuration changes. Fires after every selection change with
897
+ * the resolved configuration, including undefined when nothing matches.
898
+ */
899
+ onChange(t) {
900
+ this.changeCallbacks.push(t);
901
+ }
902
+ /**
903
+ * Register the submit handler the CTA hands the configuration to. Registering
904
+ * one is what switches the button on (capability driven). `defaultLabel` fills
905
+ * in when the customer authored no button label.
906
+ */
907
+ onSubmit(t, e) {
908
+ this.submitCallback = t, this.submitDefaultLabel = e?.defaultLabel, this.notifyRender();
909
+ }
910
+ /**
911
+ * Subscribe the built-in UI to re-render the price + CTA. Fires on every
912
+ * resolve and when a submit handler is registered (which can switch the button
913
+ * on without a configuration change). Separate from `onChange`, which stays the
914
+ * host-facing, mutation-driven contract. Returns a disposer.
915
+ * @internal
916
+ */
917
+ onRender(t) {
918
+ return this.renderCallbacks.push(t), () => {
919
+ const e = this.renderCallbacks.indexOf(t);
920
+ e !== -1 && this.renderCallbacks.splice(e, 1);
921
+ };
922
+ }
923
+ /**
924
+ * Hand the current configuration to the registered handler and fire the
925
+ * bubbling `vd:configuration-submit` event. No-op without a handler or a
926
+ * resolved configuration. Returns the handler's promise so the caller can
927
+ * reflect the result (bezig / gelukt / mislukt).
928
+ */
929
+ async submit() {
930
+ if (!this.submittable)
931
+ return;
932
+ const t = this.resolved !== void 0 ? { attributes: this.resolved.attributes, reference: this.resolved.reference } : { attributes: { ...this.selection }, reference: "" };
933
+ this.dispatchSubmit(t), await this.submitCallback?.(t);
934
+ }
935
+ /**
936
+ * Called by the client when a PRICING message arrives from the viewer.
937
+ * @internal
938
+ */
939
+ handlePricingReceived(t) {
940
+ this.pricing = t, this.recompute();
941
+ }
942
+ /**
943
+ * Called by the client after every mutation result, with the full selection.
944
+ * @internal
945
+ */
946
+ handleSelectionChanged(t) {
947
+ this.selection = t, this.recompute();
948
+ }
949
+ recompute() {
950
+ this.resolved = this.resolve();
951
+ for (const t of this.changeCallbacks)
952
+ t(this.resolved);
953
+ this.dispatchChange(this.resolved), this.notifyRender();
954
+ }
955
+ notifyRender() {
956
+ for (const t of this.renderCallbacks)
957
+ t();
958
+ }
959
+ resolve() {
960
+ if (this.pricing === void 0)
961
+ return;
962
+ const t = ye(this.pricing.rules, this.selection);
963
+ if (!(t === void 0 || t.availability === Ot.HIDDEN))
964
+ return {
965
+ attributes: { ...this.selection },
966
+ reference: t.reference,
967
+ price: t.priceCents,
968
+ currency: this.pricing.currency,
969
+ availability: t.availability
970
+ };
971
+ }
972
+ }
973
+ class _e {
974
+ /** @internal */
975
+ constructor(t, e, i) {
976
+ this.send = t, this.dispatch = e, this.instance = i;
977
+ }
978
+ send;
979
+ dispatch;
980
+ instance;
981
+ focused = null;
982
+ attributes = [];
983
+ changeCallbacks = [];
984
+ /** The focused component name, or null in overview. */
985
+ get current() {
986
+ return this.focused;
987
+ }
988
+ /**
989
+ * Focus a component by name. Sends a focus message to the viewer.
990
+ * @throws VirtualdisplayError if component is not a non-empty string
991
+ */
992
+ set(t) {
993
+ if (typeof t != "string" || t.length === 0)
994
+ throw y.invalidParameter(
995
+ `set() requires a non-empty component name, got ${String(t)}`
996
+ );
997
+ this.send({
998
+ type: c.FOCUS,
999
+ instance: this.instance,
1000
+ action: et.FOCUS,
1001
+ component: t
1002
+ });
1003
+ }
1004
+ /** Return to the overview — unfocus the current component. */
1005
+ clear() {
1006
+ this.send({ type: c.FOCUS, instance: this.instance, action: et.UNFOCUS });
1007
+ }
1008
+ /**
1009
+ * Subscribe to focus changes. The callback fires with the focus state when a
1010
+ * component is focused, and with null when returning to overview.
1011
+ */
1012
+ onChange(t) {
1013
+ this.changeCallbacks.push(t);
1014
+ }
1015
+ /**
1016
+ * Called by the client when a FocusStateMessage is received from the viewer.
1017
+ * @internal
1018
+ */
1019
+ handleStateReceived(t) {
1020
+ t.state === It.FOCUSED && t.component !== void 0 ? (this.focused = t.component, this.attributes = t.attributes ?? []) : (this.focused = null, this.attributes = []);
1021
+ const e = this.focused !== null ? { component: this.focused, attributes: this.attributes } : null;
1022
+ for (const i of this.changeCallbacks)
1023
+ i(e);
1024
+ this.dispatch(e);
1025
+ }
1026
+ }
1027
+ class ke {
1028
+ /** @internal */
1029
+ constructor(t) {
1030
+ this.filename = t;
1031
+ }
1032
+ filename;
1033
+ developedCallbacks = /* @__PURE__ */ new Set();
1034
+ errorCallbacks = /* @__PURE__ */ new Set();
1035
+ developed = !1;
1036
+ failed = !1;
1037
+ photoData = null;
1038
+ /**
1039
+ * Register a callback for when the photo is developed
1040
+ * If the photo is already developed, the callback is called immediately
1041
+ *
1042
+ * @param callback Function to call with photo data
1043
+ * @returns This Photo instance for chaining
1044
+ */
1045
+ onDeveloped(t) {
1046
+ return this.developed && this.photoData !== null ? (t(this.photoData), this) : (this.developedCallbacks.add(t), this);
1047
+ }
1048
+ /**
1049
+ * Register a callback for when the photo capture fails
1050
+ *
1051
+ * @param callback Function to call with error
1052
+ * @returns This Photo instance for chaining
1053
+ */
1054
+ onError(t) {
1055
+ return this.errorCallbacks.add(t), this;
1056
+ }
1057
+ /**
1058
+ * Get the filename for this photo
1059
+ */
1060
+ getFilename() {
1061
+ return this.filename;
1062
+ }
1063
+ /**
1064
+ * Mark the photo as developed and notify all callbacks
1065
+ * This method is called by Snapshot when the photo data arrives from the server
1066
+ * @internal
1067
+ */
1068
+ develop(t) {
1069
+ this.developed || this.failed || (this.photoData = t, this.developed = !0, this.developedCallbacks.forEach((e) => {
1070
+ e(this.photoData);
1071
+ }), this.developedCallbacks.clear(), this.errorCallbacks.clear());
1072
+ }
1073
+ /**
1074
+ * Mark the photo as failed and notify error callbacks
1075
+ * @internal
1076
+ */
1077
+ fail(t) {
1078
+ this.developed || this.failed || (this.failed = !0, this.errorCallbacks.forEach((e) => {
1079
+ e(t);
1080
+ }), this.developedCallbacks.clear(), this.errorCallbacks.clear());
1081
+ }
1082
+ }
1083
+ const ot = 3e4;
1084
+ class Ce {
1085
+ /** @internal */
1086
+ constructor(t) {
1087
+ this.send = t;
1088
+ }
1089
+ send;
1090
+ validExtensions = [".png"];
1091
+ pendingPhotos = /* @__PURE__ */ new Map();
1092
+ /**
1093
+ * Take a snapshot of the current 3D model view
1094
+ *
1095
+ * @param filename The filename for the snapshot (must include valid extension)
1096
+ * @returns Photo object for registering callbacks
1097
+ * @throws VirtualdisplayError if filename is invalid
1098
+ */
1099
+ take(t) {
1100
+ this.validateFilename(t), w.debug("Taking snapshot", { filename: t });
1101
+ const e = new ke(t), i = setTimeout(() => {
1102
+ this.pendingPhotos.delete(t), w.warn("Snapshot timed out", { filename: t }), e.fail(new y(
1103
+ `Snapshot timed out: the viewer did not respond within ${ot}ms`,
1104
+ A.SNAPSHOT_FAILED
1105
+ ));
1106
+ }, ot);
1107
+ return this.pendingPhotos.set(t, { photo: e, timeout: i }), this.send({
1108
+ type: c.SNAPSHOT,
1109
+ filename: t
1110
+ }), e;
1111
+ }
1112
+ /**
1113
+ * Handle incoming snapshot result from server
1114
+ * Matches the message to a pending photo by filename, then develops or fails it
1115
+ * @internal
1116
+ */
1117
+ handleSnapshotResult(t) {
1118
+ const e = this.pendingPhotos.get(t.filename);
1119
+ if (e === void 0) {
1120
+ w.warn("No pending photo found for filename", {
1121
+ filename: t.filename,
1122
+ pendingFilenames: Array.from(this.pendingPhotos.keys())
1123
+ });
1124
+ return;
1125
+ }
1126
+ clearTimeout(e.timeout), this.pendingPhotos.delete(t.filename);
1127
+ const { photo: i } = e;
1128
+ if (t.error !== void 0) {
1129
+ i.fail(new y(t.error, A.SNAPSHOT_FAILED));
1130
+ return;
1131
+ }
1132
+ t.data !== void 0 && i.develop({ filename: t.filename, data: t.data });
1133
+ }
1134
+ /**
1135
+ * Fail any pending photos and forget them. Called from client destroy
1136
+ * so callers awaiting onError() see a deterministic outcome instead of
1137
+ * leaking captures whose results will never arrive.
1138
+ */
1139
+ clear() {
1140
+ if (this.pendingPhotos.size === 0)
1141
+ return;
1142
+ const t = new y(
1143
+ "Client destroyed before snapshot completed",
1144
+ A.SNAPSHOT_FAILED
1145
+ );
1146
+ for (const { photo: e, timeout: i } of this.pendingPhotos.values())
1147
+ clearTimeout(i), e.fail(t);
1148
+ this.pendingPhotos.clear();
1149
+ }
1150
+ /**
1151
+ * Validate filename has a valid image extension
1152
+ */
1153
+ validateFilename(t) {
1154
+ if (t === "" || typeof t != "string")
1155
+ throw new y(
1156
+ "Filename must be a non-empty string",
1157
+ A.INVALID_PARAMETER
1158
+ );
1159
+ if (!this.validExtensions.some(
1160
+ (i) => t.toLowerCase().endsWith(i)
1161
+ ))
1162
+ throw new y(
1163
+ `Invalid filename: "${t}". Filename must end with one of: ${this.validExtensions.join(", ")}`,
1164
+ A.INVALID_PARAMETER
1165
+ );
1166
+ if (t.includes("..") || t.includes("/") || t.includes("\\"))
1167
+ throw new y(
1168
+ 'Filename cannot contain path separators or ".."',
1169
+ A.INVALID_PARAMETER
1170
+ );
1171
+ }
1172
+ }
1173
+ class Ae {
1174
+ container = null;
1175
+ attach(t) {
1176
+ this.container = t;
1177
+ }
1178
+ dispatch(t, e, i = !1) {
1179
+ const o = new CustomEvent(t, { detail: e, cancelable: i });
1180
+ return this.container?.dispatchEvent(o), o;
1181
+ }
1182
+ on(t, e) {
1183
+ const i = (o) => {
1184
+ const s = o, r = { ...s.detail, preventDefault: () => {
1185
+ s.preventDefault();
1186
+ } };
1187
+ e(r);
1188
+ };
1189
+ return this.container?.addEventListener(t, i), () => {
1190
+ this.container?.removeEventListener(t, i);
1191
+ };
1192
+ }
1193
+ }
1194
+ const Ee = {}, xe = "en", wt = (
1195
+ /** @type {const} */
1196
+ ["en", "nl", "de"]
1197
+ ), X = [
1198
+ "baseLocale"
1199
+ ], st = [], Y = typeof window > "u";
1200
+ globalThis.__paraglide = /** @type {any} */
1201
+ globalThis.__paraglide ?? {};
1202
+ globalThis.__paraglide.ssr = /** @type {any} */
1203
+ globalThis.__paraglide.ssr ?? {};
1204
+ let rt = !1, p = () => {
1205
+ let n = X;
1206
+ !Y && typeof window < "u" && window.location?.href && (n = _t(window.location.href));
1207
+ const t = Te(n);
1208
+ if (t)
1209
+ return rt || (rt = !0, St(t, { reload: !1 })), t;
1210
+ throw new Error("No locale found. Read the docs https://paraglidejs.com/errors#no-locale-found");
1211
+ };
1212
+ function Te(n, t) {
1213
+ let e;
1214
+ for (const i of n) {
1215
+ if (i === "baseLocale")
1216
+ e = xe;
1217
+ else if (kt(i) && z.has(i)) {
1218
+ const s = z.get(i);
1219
+ if (s) {
1220
+ const r = s.getLocale();
1221
+ if (r instanceof Promise)
1222
+ continue;
1223
+ if (r !== void 0)
1224
+ return Oe(r);
1225
+ }
1226
+ }
1227
+ const o = J(e);
1228
+ if (o)
1229
+ return o;
1230
+ }
1231
+ }
1232
+ const Re = (n) => {
1233
+ window.location.reload();
1234
+ };
1235
+ let St = (n, t) => {
1236
+ const e = {
1237
+ reload: !0,
1238
+ ...t
1239
+ };
1240
+ let i;
1241
+ try {
1242
+ i = p();
1243
+ } catch {
1244
+ }
1245
+ const o = [];
1246
+ let s = X;
1247
+ !Y && typeof window < "u" && window.location?.href && (s = _t(window.location.href));
1248
+ for (const a of s)
1249
+ if (a !== "baseLocale" && kt(a) && z.has(a)) {
1250
+ const d = z.get(a);
1251
+ if (d) {
1252
+ let u = d.setLocale(n);
1253
+ u instanceof Promise && (u = u.catch((f) => {
1254
+ throw new Error(`Custom strategy "${a}" setLocale failed.`, {
1255
+ cause: f
1256
+ });
1257
+ }), o.push(u));
1258
+ }
1259
+ }
1260
+ const r = () => {
1261
+ !Y && e.reload && window.location && n !== i && Re();
1262
+ };
1263
+ if (o.length)
1264
+ return Promise.all(o).then(() => {
1265
+ r();
1266
+ });
1267
+ r();
1268
+ }, Ie = () => typeof window < "u" ? window.location.origin : "http://fallback.com";
1269
+ function J(n) {
1270
+ if (typeof n != "string")
1271
+ return;
1272
+ const t = n.toLowerCase();
1273
+ for (const e of wt)
1274
+ if (e.toLowerCase() === t)
1275
+ return e;
1276
+ }
1277
+ function Oe(n) {
1278
+ const t = J(n);
1279
+ if (t)
1280
+ return t;
1281
+ throw new Error(`Invalid locale: ${n}. Expected one of: ${wt.join(", ")}`);
1282
+ }
1283
+ function Le(n, t) {
1284
+ return n.exec(t.href);
1285
+ }
1286
+ function Me(n) {
1287
+ return Ne(n);
1288
+ }
1289
+ function Ne(n) {
1290
+ const t = typeof n == "string" ? new URL(n, Ie()) : new URL(n), e = t.pathname.split("/").filter(Boolean);
1291
+ return e.length > 0 && J(e[0]) && (t.pathname = "/" + e.slice(1).join("/")), t;
1292
+ }
1293
+ let at, lt;
1294
+ function ze(n) {
1295
+ if (st.length === 0)
1296
+ return;
1297
+ const t = typeof n == "string" ? n : n.href;
1298
+ if (at === t)
1299
+ return lt;
1300
+ const e = new URL(t, "http://example.com"), i = Me(e), o = i.href === e.href ? [e] : [e, i];
1301
+ let s;
1302
+ for (const r of o) {
1303
+ for (const a of st) {
1304
+ const d = new Ee(a.match, r.href);
1305
+ if (Le(d, r)) {
1306
+ s = a;
1307
+ break;
1308
+ }
1309
+ }
1310
+ if (s)
1311
+ break;
1312
+ }
1313
+ return at = t, lt = s, s;
1314
+ }
1315
+ function _t(n) {
1316
+ const t = ze(n);
1317
+ return t && t.exclude !== !0 && Array.isArray(t.strategy) ? t.strategy : X;
1318
+ }
1319
+ const z = /* @__PURE__ */ new Map();
1320
+ function kt(n) {
1321
+ return typeof n == "string" && /^custom-[A-Za-z0-9_-]+$/.test(n);
1322
+ }
1323
+ const Pe = (
1324
+ /** @type {(inputs: Commerce_Submit_BusyInputs) => LocalizedString} */
1325
+ () => (
1326
+ /** @type {LocalizedString} */
1327
+ "Working…"
1328
+ )
1329
+ ), Fe = (
1330
+ /** @type {(inputs: Commerce_Submit_BusyInputs) => LocalizedString} */
1331
+ () => (
1332
+ /** @type {LocalizedString} */
1333
+ "Bezig…"
1334
+ )
1335
+ ), $e = (
1336
+ /** @type {(inputs: Commerce_Submit_BusyInputs) => LocalizedString} */
1337
+ () => (
1338
+ /** @type {LocalizedString} */
1339
+ "Läuft…"
1340
+ )
1341
+ ), De = (
1342
+ /** @type {((inputs?: Commerce_Submit_BusyInputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Commerce_Submit_BusyInputs, { locale?: "en" | "nl" | "de" }, {}>} */
1343
+ ((n = {}, t = {}) => {
1344
+ const e = t.locale ?? p();
1345
+ return e === "nl" ? Fe() : e === "de" ? $e() : Pe();
1346
+ })
1347
+ ), Be = (
1348
+ /** @type {(inputs: Commerce_Submit_FailedInputs) => LocalizedString} */
1349
+ () => (
1350
+ /** @type {LocalizedString} */
1351
+ "Failed"
1352
+ )
1353
+ ), Ue = (
1354
+ /** @type {(inputs: Commerce_Submit_FailedInputs) => LocalizedString} */
1355
+ () => (
1356
+ /** @type {LocalizedString} */
1357
+ "Mislukt"
1358
+ )
1359
+ ), Ve = (
1360
+ /** @type {(inputs: Commerce_Submit_FailedInputs) => LocalizedString} */
1361
+ () => (
1362
+ /** @type {LocalizedString} */
1363
+ "Fehlgeschlagen"
1364
+ )
1365
+ ), He = (
1366
+ /** @type {((inputs?: Commerce_Submit_FailedInputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Commerce_Submit_FailedInputs, { locale?: "en" | "nl" | "de" }, {}>} */
1367
+ ((n = {}, t = {}) => {
1368
+ const e = t.locale ?? p();
1369
+ return e === "nl" ? Ue() : e === "de" ? Ve() : Be();
1370
+ })
1371
+ ), je = (
1372
+ /** @type {(inputs: Commerce_Submit_SuccessInputs) => LocalizedString} */
1373
+ () => (
1374
+ /** @type {LocalizedString} */
1375
+ "Done"
1376
+ )
1377
+ ), qe = (
1378
+ /** @type {(inputs: Commerce_Submit_SuccessInputs) => LocalizedString} */
1379
+ () => (
1380
+ /** @type {LocalizedString} */
1381
+ "Gelukt"
1382
+ )
1383
+ ), Ge = (
1384
+ /** @type {(inputs: Commerce_Submit_SuccessInputs) => LocalizedString} */
1385
+ () => (
1386
+ /** @type {LocalizedString} */
1387
+ "Fertig"
1388
+ )
1389
+ ), Ze = (
1390
+ /** @type {((inputs?: Commerce_Submit_SuccessInputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Commerce_Submit_SuccessInputs, { locale?: "en" | "nl" | "de" }, {}>} */
1391
+ ((n = {}, t = {}) => {
1392
+ const e = t.locale ?? p();
1393
+ return e === "nl" ? qe() : e === "de" ? Ge() : je();
1394
+ })
1395
+ ), We = (
1396
+ /** @type {(inputs: Controls_Annotations_Buttonlabel1Inputs) => LocalizedString} */
1397
+ () => (
1398
+ /** @type {LocalizedString} */
1399
+ "Annotations"
1400
+ )
1401
+ ), Qe = (
1402
+ /** @type {(inputs: Controls_Annotations_Buttonlabel1Inputs) => LocalizedString} */
1403
+ () => (
1404
+ /** @type {LocalizedString} */
1405
+ "Annotaties"
1406
+ )
1407
+ ), Ye = (
1408
+ /** @type {(inputs: Controls_Annotations_Buttonlabel1Inputs) => LocalizedString} */
1409
+ () => (
1410
+ /** @type {LocalizedString} */
1411
+ "Anmerkungen"
1412
+ )
1413
+ ), Ke = (
1414
+ /** @type {((inputs?: Controls_Annotations_Buttonlabel1Inputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Annotations_Buttonlabel1Inputs, { locale?: "en" | "nl" | "de" }, {}>} */
1415
+ ((n = {}, t = {}) => {
1416
+ const e = t.locale ?? p();
1417
+ return e === "nl" ? Qe() : e === "de" ? Ye() : We();
1418
+ })
1419
+ ), Xe = (
1420
+ /** @type {(inputs: Controls_Ar_Buttonlabel1Inputs) => LocalizedString} */
1421
+ () => (
1422
+ /** @type {LocalizedString} */
1423
+ "Activate AR"
1424
+ )
1425
+ ), Je = (
1426
+ /** @type {(inputs: Controls_Ar_Buttonlabel1Inputs) => LocalizedString} */
1427
+ () => (
1428
+ /** @type {LocalizedString} */
1429
+ "AR activeren"
1430
+ )
1431
+ ), tn = (
1432
+ /** @type {(inputs: Controls_Ar_Buttonlabel1Inputs) => LocalizedString} */
1433
+ () => (
1434
+ /** @type {LocalizedString} */
1435
+ "AR aktivieren"
1436
+ )
1437
+ ), en = (
1438
+ /** @type {((inputs?: Controls_Ar_Buttonlabel1Inputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Ar_Buttonlabel1Inputs, { locale?: "en" | "nl" | "de" }, {}>} */
1439
+ ((n = {}, t = {}) => {
1440
+ const e = t.locale ?? p();
1441
+ return e === "nl" ? Je() : e === "de" ? tn() : Xe();
1442
+ })
1443
+ ), nn = (
1444
+ /** @type {(inputs: Controls_Camera_ResetInputs) => LocalizedString} */
1445
+ () => (
1446
+ /** @type {LocalizedString} */
1447
+ "Reset camera"
1448
+ )
1449
+ ), on = (
1450
+ /** @type {(inputs: Controls_Camera_ResetInputs) => LocalizedString} */
1451
+ () => (
1452
+ /** @type {LocalizedString} */
1453
+ "Camera resetten"
1454
+ )
1455
+ ), sn = (
1456
+ /** @type {(inputs: Controls_Camera_ResetInputs) => LocalizedString} */
1457
+ () => (
1458
+ /** @type {LocalizedString} */
1459
+ "Kamera zurücksetzen"
1460
+ )
1461
+ ), rn = (
1462
+ /** @type {((inputs?: Controls_Camera_ResetInputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Camera_ResetInputs, { locale?: "en" | "nl" | "de" }, {}>} */
1463
+ ((n = {}, t = {}) => {
1464
+ const e = t.locale ?? p();
1465
+ return e === "nl" ? on() : e === "de" ? sn() : nn();
1466
+ })
1467
+ ), an = (
1468
+ /** @type {(inputs: Controls_Camera_Rotateleft1Inputs) => LocalizedString} */
1469
+ () => (
1470
+ /** @type {LocalizedString} */
1471
+ "Rotate left"
1472
+ )
1473
+ ), ln = (
1474
+ /** @type {(inputs: Controls_Camera_Rotateleft1Inputs) => LocalizedString} */
1475
+ () => (
1476
+ /** @type {LocalizedString} */
1477
+ "Naar links draaien"
1478
+ )
1479
+ ), cn = (
1480
+ /** @type {(inputs: Controls_Camera_Rotateleft1Inputs) => LocalizedString} */
1481
+ () => (
1482
+ /** @type {LocalizedString} */
1483
+ "Nach links drehen"
1484
+ )
1485
+ ), dn = (
1486
+ /** @type {((inputs?: Controls_Camera_Rotateleft1Inputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Camera_Rotateleft1Inputs, { locale?: "en" | "nl" | "de" }, {}>} */
1487
+ ((n = {}, t = {}) => {
1488
+ const e = t.locale ?? p();
1489
+ return e === "nl" ? ln() : e === "de" ? cn() : an();
1490
+ })
1491
+ ), un = (
1492
+ /** @type {(inputs: Controls_Camera_Rotateright1Inputs) => LocalizedString} */
1493
+ () => (
1494
+ /** @type {LocalizedString} */
1495
+ "Rotate right"
1496
+ )
1497
+ ), hn = (
1498
+ /** @type {(inputs: Controls_Camera_Rotateright1Inputs) => LocalizedString} */
1499
+ () => (
1500
+ /** @type {LocalizedString} */
1501
+ "Naar rechts draaien"
1502
+ )
1503
+ ), pn = (
1504
+ /** @type {(inputs: Controls_Camera_Rotateright1Inputs) => LocalizedString} */
1505
+ () => (
1506
+ /** @type {LocalizedString} */
1507
+ "Nach rechts drehen"
1508
+ )
1509
+ ), mn = (
1510
+ /** @type {((inputs?: Controls_Camera_Rotateright1Inputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Camera_Rotateright1Inputs, { locale?: "en" | "nl" | "de" }, {}>} */
1511
+ ((n = {}, t = {}) => {
1512
+ const e = t.locale ?? p();
1513
+ return e === "nl" ? hn() : e === "de" ? pn() : un();
1514
+ })
1515
+ ), fn = (
1516
+ /** @type {(inputs: Controls_Camera_Tiltdown1Inputs) => LocalizedString} */
1517
+ () => (
1518
+ /** @type {LocalizedString} */
1519
+ "Tilt down"
1520
+ )
1521
+ ), gn = (
1522
+ /** @type {(inputs: Controls_Camera_Tiltdown1Inputs) => LocalizedString} */
1523
+ () => (
1524
+ /** @type {LocalizedString} */
1525
+ "Omlaag kantelen"
1526
+ )
1527
+ ), bn = (
1528
+ /** @type {(inputs: Controls_Camera_Tiltdown1Inputs) => LocalizedString} */
1529
+ () => (
1530
+ /** @type {LocalizedString} */
1531
+ "Nach unten kippen"
1532
+ )
1533
+ ), vn = (
1534
+ /** @type {((inputs?: Controls_Camera_Tiltdown1Inputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Camera_Tiltdown1Inputs, { locale?: "en" | "nl" | "de" }, {}>} */
1535
+ ((n = {}, t = {}) => {
1536
+ const e = t.locale ?? p();
1537
+ return e === "nl" ? gn() : e === "de" ? bn() : fn();
1538
+ })
1539
+ ), yn = (
1540
+ /** @type {(inputs: Controls_Camera_Tiltup1Inputs) => LocalizedString} */
1541
+ () => (
1542
+ /** @type {LocalizedString} */
1543
+ "Tilt up"
1544
+ )
1545
+ ), wn = (
1546
+ /** @type {(inputs: Controls_Camera_Tiltup1Inputs) => LocalizedString} */
1547
+ () => (
1548
+ /** @type {LocalizedString} */
1549
+ "Omhoog kantelen"
1550
+ )
1551
+ ), Sn = (
1552
+ /** @type {(inputs: Controls_Camera_Tiltup1Inputs) => LocalizedString} */
1553
+ () => (
1554
+ /** @type {LocalizedString} */
1555
+ "Nach oben kippen"
1556
+ )
1557
+ ), _n = (
1558
+ /** @type {((inputs?: Controls_Camera_Tiltup1Inputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Camera_Tiltup1Inputs, { locale?: "en" | "nl" | "de" }, {}>} */
1559
+ ((n = {}, t = {}) => {
1560
+ const e = t.locale ?? p();
1561
+ return e === "nl" ? wn() : e === "de" ? Sn() : yn();
1562
+ })
1563
+ ), kn = (
1564
+ /** @type {(inputs: Controls_Camera_Zoomin1Inputs) => LocalizedString} */
1565
+ () => (
1566
+ /** @type {LocalizedString} */
1567
+ "Zoom in"
1568
+ )
1569
+ ), Cn = (
1570
+ /** @type {(inputs: Controls_Camera_Zoomin1Inputs) => LocalizedString} */
1571
+ () => (
1572
+ /** @type {LocalizedString} */
1573
+ "Inzoomen"
1574
+ )
1575
+ ), An = (
1576
+ /** @type {(inputs: Controls_Camera_Zoomin1Inputs) => LocalizedString} */
1577
+ () => (
1578
+ /** @type {LocalizedString} */
1579
+ "Hineinzoomen"
1580
+ )
1581
+ ), En = (
1582
+ /** @type {((inputs?: Controls_Camera_Zoomin1Inputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Camera_Zoomin1Inputs, { locale?: "en" | "nl" | "de" }, {}>} */
1583
+ ((n = {}, t = {}) => {
1584
+ const e = t.locale ?? p();
1585
+ return e === "nl" ? Cn() : e === "de" ? An() : kn();
1586
+ })
1587
+ ), xn = (
1588
+ /** @type {(inputs: Controls_Camera_Zoomout1Inputs) => LocalizedString} */
1589
+ () => (
1590
+ /** @type {LocalizedString} */
1591
+ "Zoom out"
1592
+ )
1593
+ ), Tn = (
1594
+ /** @type {(inputs: Controls_Camera_Zoomout1Inputs) => LocalizedString} */
1595
+ () => (
1596
+ /** @type {LocalizedString} */
1597
+ "Uitzoomen"
1598
+ )
1599
+ ), Rn = (
1600
+ /** @type {(inputs: Controls_Camera_Zoomout1Inputs) => LocalizedString} */
1601
+ () => (
1602
+ /** @type {LocalizedString} */
1603
+ "Herauszoomen"
1604
+ )
1605
+ ), In = (
1606
+ /** @type {((inputs?: Controls_Camera_Zoomout1Inputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Camera_Zoomout1Inputs, { locale?: "en" | "nl" | "de" }, {}>} */
1607
+ ((n = {}, t = {}) => {
1608
+ const e = t.locale ?? p();
1609
+ return e === "nl" ? Tn() : e === "de" ? Rn() : xn();
1610
+ })
1611
+ ), On = (
1612
+ /** @type {(inputs: Controls_Configurator_CloseInputs) => LocalizedString} */
1613
+ () => (
1614
+ /** @type {LocalizedString} */
1615
+ "Close"
1616
+ )
1617
+ ), Ln = (
1618
+ /** @type {(inputs: Controls_Configurator_CloseInputs) => LocalizedString} */
1619
+ () => (
1620
+ /** @type {LocalizedString} */
1621
+ "Sluiten"
1622
+ )
1623
+ ), Mn = (
1624
+ /** @type {(inputs: Controls_Configurator_CloseInputs) => LocalizedString} */
1625
+ () => (
1626
+ /** @type {LocalizedString} */
1627
+ "Schließen"
1628
+ )
1629
+ ), Nn = (
1630
+ /** @type {((inputs?: Controls_Configurator_CloseInputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Configurator_CloseInputs, { locale?: "en" | "nl" | "de" }, {}>} */
1631
+ ((n = {}, t = {}) => {
1632
+ const e = t.locale ?? p();
1633
+ return e === "nl" ? Ln() : e === "de" ? Mn() : On();
1634
+ })
1635
+ ), zn = (
1636
+ /** @type {(inputs: Controls_Configurator_OpenInputs) => LocalizedString} */
1637
+ () => (
1638
+ /** @type {LocalizedString} */
1639
+ "Configure"
1640
+ )
1641
+ ), Pn = (
1642
+ /** @type {(inputs: Controls_Configurator_OpenInputs) => LocalizedString} */
1643
+ () => (
1644
+ /** @type {LocalizedString} */
1645
+ "Configureren"
1646
+ )
1647
+ ), Fn = (
1648
+ /** @type {(inputs: Controls_Configurator_OpenInputs) => LocalizedString} */
1649
+ () => (
1650
+ /** @type {LocalizedString} */
1651
+ "Konfigurieren"
1652
+ )
1653
+ ), $n = (
1654
+ /** @type {((inputs?: Controls_Configurator_OpenInputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Configurator_OpenInputs, { locale?: "en" | "nl" | "de" }, {}>} */
1655
+ ((n = {}, t = {}) => {
1656
+ const e = t.locale ?? p();
1657
+ return e === "nl" ? Pn() : e === "de" ? Fn() : zn();
1658
+ })
1659
+ ), Dn = (
1660
+ /** @type {(inputs: Controls_Fullscreen_Buttonlabel1Inputs) => LocalizedString} */
1661
+ () => (
1662
+ /** @type {LocalizedString} */
1663
+ "Fullscreen"
1664
+ )
1665
+ ), Bn = (
1666
+ /** @type {(inputs: Controls_Fullscreen_Buttonlabel1Inputs) => LocalizedString} */
1667
+ () => (
1668
+ /** @type {LocalizedString} */
1669
+ "Volledig scherm"
1670
+ )
1671
+ ), Un = (
1672
+ /** @type {(inputs: Controls_Fullscreen_Buttonlabel1Inputs) => LocalizedString} */
1673
+ () => (
1674
+ /** @type {LocalizedString} */
1675
+ "Vollbild"
1676
+ )
1677
+ ), Vn = (
1678
+ /** @type {((inputs?: Controls_Fullscreen_Buttonlabel1Inputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Fullscreen_Buttonlabel1Inputs, { locale?: "en" | "nl" | "de" }, {}>} */
1679
+ ((n = {}, t = {}) => {
1680
+ const e = t.locale ?? p();
1681
+ return e === "nl" ? Bn() : e === "de" ? Un() : Dn();
1682
+ })
1683
+ ), Hn = (
1684
+ /** @type {(inputs: Controls_Qr_Closebutton1Inputs) => LocalizedString} */
1685
+ () => (
1686
+ /** @type {LocalizedString} */
1687
+ "Close"
1688
+ )
1689
+ ), jn = (
1690
+ /** @type {(inputs: Controls_Qr_Closebutton1Inputs) => LocalizedString} */
1691
+ () => (
1692
+ /** @type {LocalizedString} */
1693
+ "Sluiten"
1694
+ )
1695
+ ), qn = (
1696
+ /** @type {(inputs: Controls_Qr_Closebutton1Inputs) => LocalizedString} */
1697
+ () => (
1698
+ /** @type {LocalizedString} */
1699
+ "Schließen"
1700
+ )
1701
+ ), Gn = (
1702
+ /** @type {((inputs?: Controls_Qr_Closebutton1Inputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Qr_Closebutton1Inputs, { locale?: "en" | "nl" | "de" }, {}>} */
1703
+ ((n = {}, t = {}) => {
1704
+ const e = t.locale ?? p();
1705
+ return e === "nl" ? jn() : e === "de" ? qn() : Hn();
1706
+ })
1707
+ ), Zn = (
1708
+ /** @type {(inputs: Controls_Qr_Modaldescription1Inputs) => LocalizedString} */
1709
+ () => (
1710
+ /** @type {LocalizedString} */
1711
+ "Use your smartphone camera to scan this QR code"
1712
+ )
1713
+ ), Wn = (
1714
+ /** @type {(inputs: Controls_Qr_Modaldescription1Inputs) => LocalizedString} */
1715
+ () => (
1716
+ /** @type {LocalizedString} */
1717
+ "Gebruik de camera van je smartphone om deze QR-code te scannen"
1718
+ )
1719
+ ), Qn = (
1720
+ /** @type {(inputs: Controls_Qr_Modaldescription1Inputs) => LocalizedString} */
1721
+ () => (
1722
+ /** @type {LocalizedString} */
1723
+ "Verwende die Kamera deines Smartphones, um diesen QR-Code zu scannen"
1724
+ )
1725
+ ), Yn = (
1726
+ /** @type {((inputs?: Controls_Qr_Modaldescription1Inputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Qr_Modaldescription1Inputs, { locale?: "en" | "nl" | "de" }, {}>} */
1727
+ ((n = {}, t = {}) => {
1728
+ const e = t.locale ?? p();
1729
+ return e === "nl" ? Wn() : e === "de" ? Qn() : Zn();
1730
+ })
1731
+ ), Kn = (
1732
+ /** @type {(inputs: Controls_Qr_Modalinstructions1Inputs) => LocalizedString} */
1733
+ () => (
1734
+ /** @type {LocalizedString} */
1735
+ "Your phone will open the 3D model in AR mode."
1736
+ )
1737
+ ), Xn = (
1738
+ /** @type {(inputs: Controls_Qr_Modalinstructions1Inputs) => LocalizedString} */
1739
+ () => (
1740
+ /** @type {LocalizedString} */
1741
+ "Je telefoon opent het 3D-model in AR-modus."
1742
+ )
1743
+ ), Jn = (
1744
+ /** @type {(inputs: Controls_Qr_Modalinstructions1Inputs) => LocalizedString} */
1745
+ () => (
1746
+ /** @type {LocalizedString} */
1747
+ "Dein Telefon öffnet das 3D-Modell im AR-Modus."
1748
+ )
1749
+ ), ti = (
1750
+ /** @type {((inputs?: Controls_Qr_Modalinstructions1Inputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Qr_Modalinstructions1Inputs, { locale?: "en" | "nl" | "de" }, {}>} */
1751
+ ((n = {}, t = {}) => {
1752
+ const e = t.locale ?? p();
1753
+ return e === "nl" ? Xn() : e === "de" ? Jn() : Kn();
1754
+ })
1755
+ ), ei = (
1756
+ /** @type {(inputs: Controls_Qr_Modaltitle1Inputs) => LocalizedString} */
1757
+ () => (
1758
+ /** @type {LocalizedString} */
1759
+ "Scan to view in AR"
1760
+ )
1761
+ ), ni = (
1762
+ /** @type {(inputs: Controls_Qr_Modaltitle1Inputs) => LocalizedString} */
1763
+ () => (
1764
+ /** @type {LocalizedString} */
1765
+ "Scan om in AR te bekijken"
1766
+ )
1767
+ ), ii = (
1768
+ /** @type {(inputs: Controls_Qr_Modaltitle1Inputs) => LocalizedString} */
1769
+ () => (
1770
+ /** @type {LocalizedString} */
1771
+ "Scannen für AR-Ansicht"
1772
+ )
1773
+ ), oi = (
1774
+ /** @type {((inputs?: Controls_Qr_Modaltitle1Inputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Qr_Modaltitle1Inputs, { locale?: "en" | "nl" | "de" }, {}>} */
1775
+ ((n = {}, t = {}) => {
1776
+ const e = t.locale ?? p();
1777
+ return e === "nl" ? ni() : e === "de" ? ii() : ei();
1778
+ })
1779
+ ), si = (
1780
+ /** @type {(inputs: Controls_Screenshot_Buttonlabel1Inputs) => LocalizedString} */
1781
+ () => (
1782
+ /** @type {LocalizedString} */
1783
+ "Screenshot"
1784
+ )
1785
+ ), ri = (
1786
+ /** @type {(inputs: Controls_Screenshot_Buttonlabel1Inputs) => LocalizedString} */
1787
+ () => (
1788
+ /** @type {LocalizedString} */
1789
+ "Screenshot"
1790
+ )
1791
+ ), ai = (
1792
+ /** @type {(inputs: Controls_Screenshot_Buttonlabel1Inputs) => LocalizedString} */
1793
+ () => (
1794
+ /** @type {LocalizedString} */
1795
+ "Screenshot"
1796
+ )
1797
+ ), li = (
1798
+ /** @type {((inputs?: Controls_Screenshot_Buttonlabel1Inputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Controls_Screenshot_Buttonlabel1Inputs, { locale?: "en" | "nl" | "de" }, {}>} */
1799
+ ((n = {}, t = {}) => {
1800
+ const e = t.locale ?? p();
1801
+ return e === "nl" ? ri() : e === "de" ? ai() : si();
1802
+ })
1803
+ ), ci = (
1804
+ /** @type {(inputs: Loading_Downloadingshort1Inputs) => LocalizedString} */
1805
+ () => (
1806
+ /** @type {LocalizedString} */
1807
+ "Downloading"
1808
+ )
1809
+ ), di = (
1810
+ /** @type {(inputs: Loading_Downloadingshort1Inputs) => LocalizedString} */
1811
+ () => (
1812
+ /** @type {LocalizedString} */
1813
+ "Downloaden"
1814
+ )
1815
+ ), ui = (
1816
+ /** @type {(inputs: Loading_Downloadingshort1Inputs) => LocalizedString} */
1817
+ () => (
1818
+ /** @type {LocalizedString} */
1819
+ "Herunterladen"
1820
+ )
1821
+ ), hi = (
1822
+ /** @type {((inputs?: Loading_Downloadingshort1Inputs, options?: { locale?: "en" | "nl" | "de" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Loading_Downloadingshort1Inputs, { locale?: "en" | "nl" | "de" }, {}>} */
1823
+ ((n = {}, t = {}) => {
1824
+ const e = t.locale ?? p();
1825
+ return e === "nl" ? di() : e === "de" ? ui() : ci();
1826
+ })
1827
+ );
1828
+ const pi = {
1829
+ xmlns: "http://www.w3.org/2000/svg",
1830
+ width: 24,
1831
+ height: 24,
1832
+ viewBox: "0 0 24 24",
1833
+ fill: "none",
1834
+ stroke: "currentColor",
1835
+ "stroke-width": 2,
1836
+ "stroke-linecap": "round",
1837
+ "stroke-linejoin": "round"
66
1838
  };
67
- //#endregion
68
- //#region ../logger/src/format.ts
69
- function r(e) {
70
- let t = new Date(e.timestamp).toLocaleTimeString("en-GB", { hour12: !1 }), n = e.level.toUpperCase().padEnd(5), r = e.trace_id?.slice(0, 8) ?? "--------", i = e.msg, a = [
71
- "timestamp",
72
- "level",
73
- "msg",
74
- "service",
75
- "trace_id",
76
- "span_id"
77
- ], o = Object.entries(e).filter(([e]) => !a.includes(e)).map(([e, t]) => `${e}=${JSON.stringify(t)}`).join(" ");
78
- return `${t} ${n} [${r}] ${i}${o ? " " + o : ""}`;
79
- }
80
- function i(e) {
81
- return JSON.stringify(e);
82
- }
83
- //#endregion
84
- //#region ../logger/src/write.ts
85
- function a(e, t) {
86
- let n = t ? r(e) : i(e);
87
- e.level === "error" ? console.error(n) : e.level === "warn" ? console.warn(n) : console.log(n);
88
- }
89
- //#endregion
90
- //#region ../utils/src/date.ts
91
- function o() {
92
- return /* @__PURE__ */ new Date();
93
- }
94
- function s(e) {
95
- return e.toISOString();
96
- }
97
- //#endregion
98
- //#region ../logger/src/logger.ts
99
- var c = class e {
100
- constructor(e) {
101
- this.config = e;
102
- }
103
- log(e, t, n = {}) {
104
- let r = this.config.traceProvider?.() ?? {};
105
- a({
106
- timestamp: s(o()),
107
- level: e,
108
- msg: t,
109
- service: this.config.service,
110
- ...r,
111
- ...n
112
- }, this.config.isDev);
113
- }
114
- debug(e, t) {
115
- this.config.isDev && this.log("debug", e, t);
116
- }
117
- info(e, t) {
118
- this.log("info", e, t);
119
- }
120
- warn(e, t) {
121
- this.log("warn", e, t);
122
- }
123
- error(e, t) {
124
- this.log("error", e, t);
125
- }
126
- with(t) {
127
- let n = new e(this.config);
128
- return n.log = ((e, n, r = {}) => {
129
- this.log(e, n, {
130
- ...t,
131
- ...r
132
- });
133
- }).bind(this), n;
134
- }
1839
+ const Ct = ([n, t, e]) => {
1840
+ const i = document.createElementNS("http://www.w3.org/2000/svg", n);
1841
+ return Object.keys(t).forEach((o) => {
1842
+ i.setAttribute(o, String(t[o]));
1843
+ }), e?.length && e.forEach((o) => {
1844
+ const s = Ct(o);
1845
+ i.appendChild(s);
1846
+ }), i;
1847
+ }, At = (n, t = {}) => {
1848
+ const i = {
1849
+ ...pi,
1850
+ ...t
1851
+ };
1852
+ return Ct(["svg", i, n]);
135
1853
  };
136
- function l(e, t) {
137
- return new c({
138
- service: e,
139
- isDev: t?.isDev === void 0 ? typeof process < "u" ? process.env.NODE_ENV !== "production" : !0 : t.isDev,
140
- traceProvider: t?.traceProvider
141
- });
142
- }
143
- //#endregion
144
- //#region src/utils/logger.ts
145
- function u(e) {
146
- if (e !== void 0) return typeof e == "object" && e && !Array.isArray(e) && !(e instanceof Error) ? e : e instanceof Error ? { error: e.message } : { value: e };
147
- }
148
- function d(e) {
149
- return {
150
- debug: (t, n) => e.debug(t, u(n)),
151
- info: (t, n) => e.info(t, u(n)),
152
- warn: (t, n) => e.warn(t, u(n)),
153
- error: (t, n) => e.error(t, u(n))
154
- };
155
- }
156
- var f = !1, p = d(l("client", { isDev: !1 }));
157
- function m() {
158
- f = !0, p = d(l("client", { isDev: !0 }));
159
- }
160
- function h() {
161
- return f;
162
- }
163
- //#endregion
164
- //#region src/client/ready-state-tracker.ts
165
- var g = class {
166
- ready = !1;
167
- onReady;
168
- constructor(e) {
169
- this.onReady = e;
170
- }
171
- handleMappingReceived() {
172
- this.markReady("Mapping received from server");
173
- }
174
- handleMutationResult(e) {
175
- e.isInitial && this.markReady("Initial mutations processed");
176
- }
177
- get isReady() {
178
- return this.ready;
179
- }
180
- clear() {
181
- this.ready = !1;
182
- }
183
- markReady(e) {
184
- this.ready || (this.ready = !0, p.info(`${e} - client is now ready`), this.onReady?.());
185
- }
186
- }, _ = class {
187
- isSelectedValue = !1;
188
- constructor(e, t) {
189
- this.value = e, this.attribute = t;
190
- }
191
- get isSelected() {
192
- return this.isSelectedValue;
193
- }
194
- setSelected(e) {
195
- this.isSelectedValue = e;
196
- }
197
- }, v = class {
198
- name;
199
- defaultValue;
200
- values;
201
- onSelect;
202
- onChangeCallback;
203
- constructor(e) {
204
- this.name = e.name, this.defaultValue = e.defaultValue, this.onSelect = e.onSelect, this.values = this.createValues(e.values);
205
- }
206
- select(e) {
207
- if (!this.values.has(e)) throw t.valueNotFound(this.name, e);
208
- this.onSelect(this.name, e);
209
- }
210
- getValues() {
211
- return Array.from(this.values.values());
212
- }
213
- getValue(e) {
214
- return this.values.get(e);
215
- }
216
- onChange(e) {
217
- this.onChangeCallback = e;
218
- }
219
- handleResult(e) {
220
- let t;
221
- for (let n of this.values.values()) {
222
- let r = n.value === e;
223
- n.setSelected(r), r && (t = n);
224
- }
225
- t !== void 0 && this.onChangeCallback !== void 0 && this.onChangeCallback(t);
226
- }
227
- createValues(e) {
228
- let t = /* @__PURE__ */ new Map();
229
- for (let n of e) {
230
- let e = new _(n, this.name);
231
- n === this.defaultValue && e.setSelected(!0), t.set(n, e);
232
- }
233
- return t;
234
- }
235
- }, y = class {
236
- attributes = /* @__PURE__ */ new Map();
237
- constructor(e, t, n) {
238
- for (let n of e) {
239
- let e = this.buildOptions(n, t);
240
- this.attributes.set(n.name, new v(e));
241
- }
242
- this.onMutationFailed = n;
243
- }
244
- onMutationFailed;
245
- get(e) {
246
- return this.attributes.get(e);
247
- }
248
- defaults() {
249
- let e = [];
250
- for (let t of this.attributes.values()) t.defaultValue !== void 0 && e.push({
251
- name: t.name,
252
- value: t.defaultValue
253
- });
254
- return e;
255
- }
256
- handleResults(e) {
257
- for (let t of e) {
258
- if (!t.success) {
259
- this.onMutationFailed?.(t.name, t.value);
260
- continue;
261
- }
262
- this.get(t.name)?.handleResult(t.value);
263
- }
264
- }
265
- buildOptions(e, t) {
266
- let n = {
267
- name: e.name,
268
- values: e.values,
269
- onSelect: (e, n) => {
270
- t([{
271
- name: e,
272
- value: n
273
- }], !1);
274
- }
275
- };
276
- return e.defaultValue === void 0 ? n : {
277
- ...n,
278
- defaultValue: e.defaultValue
279
- };
280
- }
281
- }, b = {
282
- READY: "ready",
283
- MUTATION: "mutation",
284
- STATE: "state",
285
- CONFIG: "config",
286
- CAMERA: "camera",
287
- SNAPSHOT: "snapshot",
288
- MAPPING: "mapping"
289
- }, x = new Set(Object.values(b));
290
- function S(e) {
291
- if (typeof e != "object" || !e) return !1;
292
- let t = e;
293
- return typeof t.target == "string" ? !1 : typeof t.type == "string" && x.has(t.type);
294
- }
295
- function C(e) {
296
- let t = O(e);
297
- return t !== null && t.type === b.READY;
298
- }
299
- function w(e) {
300
- let t = O(e);
301
- return t !== null && t.type === b.MUTATION && Array.isArray(t.results);
302
- }
303
- function T(e) {
304
- let t = O(e);
305
- return t !== null && t.type === b.SNAPSHOT && ("data" in t || "error" in t);
306
- }
307
- function E(e) {
308
- let t = O(e);
309
- return t !== null && t.type === b.CAMERA && Array.isArray(t.transforms);
310
- }
311
- function D(e) {
312
- let t = O(e);
313
- return t !== null && t.type === b.MAPPING && Array.isArray(t.attributes);
314
- }
315
- function O(e) {
316
- return typeof e != "object" || !e ? null : e;
317
- }
318
- //#endregion
319
- //#region ../messaging/src/queue.ts
320
- var k = class {
321
- pending = [];
322
- isReady = !1;
323
- constructor(e) {
324
- this.processor = e;
325
- }
326
- enqueue(e) {
327
- if (this.isReady) {
328
- this.processor(e);
329
- return;
330
- }
331
- this.pending.push(e);
332
- }
333
- markReady() {
334
- if (this.isReady || (this.isReady = !0, this.pending.length === 0)) return;
335
- let e = [...this.pending];
336
- this.pending.length = 0;
337
- for (let t of e) this.processor(t);
338
- }
339
- get ready() {
340
- return this.isReady;
341
- }
342
- get pendingCount() {
343
- return this.pending.length;
344
- }
345
- hasPending(e) {
346
- return this.pending.some((t) => t.type === e);
347
- }
348
- clear() {
349
- this.pending.length = 0;
350
- }
351
- }, A = class {
352
- handlers = /* @__PURE__ */ new Map();
353
- publisher;
354
- consumer;
355
- shouldBuffer;
356
- listener = null;
357
- sourceId;
358
- constructor(e, t) {
359
- this.getTarget = e, this.sourceId = `vd-${Math.random().toString(36).slice(2, 8)}`, this.publisher = new k((e) => this.post(e)), this.consumer = new k((e) => this.routeToHandler(e)), this.shouldBuffer = t?.shouldBuffer ?? null, t?.bufferIncoming !== !0 && this.consumer.markReady();
360
- }
361
- on(e, t) {
362
- this.handlers.set(e, t);
363
- }
364
- off(e) {
365
- this.handlers.delete(e);
366
- }
367
- send(e) {
368
- this.publisher.enqueue(e);
369
- }
370
- markReady() {
371
- this.publisher.markReady();
372
- }
373
- markConsumerReady() {
374
- this.consumer.markReady();
375
- }
376
- consumerHasPending(e) {
377
- return this.consumer.hasPending(e);
378
- }
379
- start() {
380
- this.listener === null && (this.listener = (e) => {
381
- let { data: t } = e;
382
- if (!S(t) || t._source === this.sourceId) return;
383
- let n = t;
384
- if (this.shouldBuffer !== null && !this.shouldBuffer(n)) {
385
- this.routeToHandler(n);
386
- return;
387
- }
388
- this.consumer.enqueue(n);
389
- }, window.addEventListener("message", this.listener));
390
- }
391
- destroy() {
392
- this.listener !== null && (window.removeEventListener("message", this.listener), this.listener = null), this.publisher.clear(), this.consumer.clear(), this.handlers.clear();
393
- }
394
- get ready() {
395
- return this.publisher.ready;
396
- }
397
- get pendingCount() {
398
- return this.publisher.pendingCount;
399
- }
400
- post(e) {
401
- let t = this.getTarget();
402
- t !== null && t.postMessage({
403
- ...e,
404
- _source: this.sourceId
405
- }, "*");
406
- }
407
- routeToHandler(e) {
408
- let t = this.handlers.get(e.type);
409
- if (t === void 0) return;
410
- let n = t(e);
411
- n instanceof Promise && n.catch((t) => {
412
- let n = t instanceof Error ? t.message : String(t);
413
- console.error(`Virtualdisplay: error handling ${e.type}: ${n}`);
414
- });
415
- }
1854
+ const mi = [
1855
+ [
1856
+ "path",
1857
+ {
1858
+ d: "M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"
1859
+ }
1860
+ ],
1861
+ ["path", { d: "m3.3 7 8.7 5 8.7-5" }],
1862
+ ["path", { d: "M12 22V12" }]
1863
+ ];
1864
+ const fi = [
1865
+ [
1866
+ "path",
1867
+ {
1868
+ d: "M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z"
1869
+ }
1870
+ ],
1871
+ ["circle", { cx: "12", cy: "13", r: "3" }]
1872
+ ];
1873
+ const Et = [["path", { d: "m6 9 6 6 6-6" }]];
1874
+ const gi = [["path", { d: "m15 18-6-6 6-6" }]];
1875
+ const bi = [["path", { d: "m9 18 6-6-6-6" }]];
1876
+ const vi = [["path", { d: "m18 15-6-6-6 6" }]];
1877
+ const yi = [
1878
+ ["path", { d: "M12.75 7.09a3 3 0 0 1 2.16 2.16" }],
1879
+ [
1880
+ "path",
1881
+ {
1882
+ d: "M17.072 17.072c-1.634 2.17-3.527 3.912-4.471 4.727a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 1.432-4.568"
1883
+ }
1884
+ ],
1885
+ ["path", { d: "m2 2 20 20" }],
1886
+ ["path", { d: "M8.475 2.818A8 8 0 0 1 20 10c0 1.183-.31 2.377-.81 3.533" }],
1887
+ ["path", { d: "M9.13 9.13a3 3 0 0 0 3.74 3.74" }]
1888
+ ];
1889
+ const wi = [
1890
+ [
1891
+ "path",
1892
+ {
1893
+ d: "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0"
1894
+ }
1895
+ ],
1896
+ ["circle", { cx: "12", cy: "10", r: "3" }]
1897
+ ];
1898
+ const Si = [
1899
+ ["path", { d: "M15 3h6v6" }],
1900
+ ["path", { d: "m21 3-7 7" }],
1901
+ ["path", { d: "m3 21 7-7" }],
1902
+ ["path", { d: "M9 21H3v-6" }]
1903
+ ];
1904
+ const _i = [
1905
+ ["path", { d: "m14 10 7-7" }],
1906
+ ["path", { d: "M20 10h-6V4" }],
1907
+ ["path", { d: "m3 21 7-7" }],
1908
+ ["path", { d: "M4 14h6v6" }]
1909
+ ];
1910
+ const ki = [
1911
+ ["path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }],
1912
+ ["path", { d: "M3 3v5h5" }]
1913
+ ];
1914
+ const Ci = [
1915
+ ["path", { d: "M10 5H3" }],
1916
+ ["path", { d: "M12 19H3" }],
1917
+ ["path", { d: "M14 3v4" }],
1918
+ ["path", { d: "M16 17v4" }],
1919
+ ["path", { d: "M21 12h-9" }],
1920
+ ["path", { d: "M21 19h-5" }],
1921
+ ["path", { d: "M21 5h-7" }],
1922
+ ["path", { d: "M8 10v4" }],
1923
+ ["path", { d: "M8 12H3" }]
1924
+ ];
1925
+ const Ai = [
1926
+ ["path", { d: "M18 6 6 18" }],
1927
+ ["path", { d: "m6 6 12 12" }]
1928
+ ];
1929
+ const Ei = [
1930
+ ["circle", { cx: "11", cy: "11", r: "8" }],
1931
+ ["line", { x1: "21", x2: "16.65", y1: "21", y2: "16.65" }],
1932
+ ["line", { x1: "11", x2: "11", y1: "8", y2: "14" }],
1933
+ ["line", { x1: "8", x2: "14", y1: "11", y2: "11" }]
1934
+ ];
1935
+ const xi = [
1936
+ ["circle", { cx: "11", cy: "11", r: "8" }],
1937
+ ["line", { x1: "21", x2: "16.65", y1: "21", y2: "16.65" }],
1938
+ ["line", { x1: "8", x2: "14", y1: "11", y2: "11" }]
1939
+ ];
1940
+ function b(n, { size: t = 20, strokeWidth: e = 1.75 } = {}) {
1941
+ return At(n, { width: t, height: t, "stroke-width": e });
1942
+ }
1943
+ const g = {
1944
+ ar: (n) => b(mi, n),
1945
+ annotations: (n) => b(wi, n),
1946
+ annotationsOff: (n) => b(yi, n),
1947
+ fullscreen: (n) => b(Si, n),
1948
+ exitFullscreen: (n) => b(_i, n),
1949
+ screenshot: (n) => b(fi, n),
1950
+ tiltUp: (n) => b(vi, n),
1951
+ tiltDown: (n) => b(Et, n),
1952
+ rotateLeft: (n) => b(gi, n),
1953
+ rotateRight: (n) => b(bi, n),
1954
+ zoomIn: (n) => b(Ei, n),
1955
+ zoomOut: (n) => b(xi, n),
1956
+ reset: (n) => b(ki, n),
1957
+ configurator: (n) => b(Ci, n),
1958
+ close: (n) => b(Ai, n)
1959
+ };
1960
+ class v {
1961
+ element;
1962
+ constructor({ variant: t = "default", size: e = "default", label: i, icon: o, onClick: s, disabled: r = !1 } = {}) {
1963
+ this.element = document.createElement("button"), this.element.className = `ui-btn ui-btn--${t} ui-btn--size-${e}`, i !== void 0 && (this.element.setAttribute("aria-label", i), this.element.title = i), o !== void 0 ? this.element.appendChild(o) : i !== void 0 && (this.element.textContent = i), s && this.element.addEventListener("click", s), this.element.disabled = r;
1964
+ }
1965
+ setIcon(t) {
1966
+ const e = this.element.querySelector("svg");
1967
+ e !== null ? e.replaceWith(t) : this.element.appendChild(t);
1968
+ }
1969
+ }
1970
+ const m = {
1971
+ semantic: {
1972
+ // eslint-disable-next-line vd-custom/enforce-error-codes -- This is a hex color, not an error code
1973
+ error: "#dc2626"
1974
+ },
1975
+ neutral: {
1976
+ 500: "#737373",
1977
+ 600: "#525252",
1978
+ 900: "#171717"
1979
+ },
1980
+ white: "#ffffff",
1981
+ black: "#000000"
1982
+ }, l = {
1983
+ 0: "0",
1984
+ 0.5: "0.125rem",
1985
+ // 2px
1986
+ 1: "0.25rem",
1987
+ // 4px
1988
+ 1.5: "0.375rem",
1989
+ // 6px
1990
+ 2: "0.5rem",
1991
+ // 10px
1992
+ 3: "0.75rem",
1993
+ // 14px
1994
+ 4: "1rem",
1995
+ // 16px
1996
+ 5: "1.25rem",
1997
+ // 20px
1998
+ 6: "1.5rem",
1999
+ // 28px
2000
+ 8: "2rem",
2001
+ // 32px
2002
+ 9: "2.25rem"
2003
+ };
2004
+ class Ti {
2005
+ element;
2006
+ constructor() {
2007
+ this.element = document.createElement("div"), Object.assign(this.element.style, {
2008
+ background: m.white,
2009
+ borderRadius: l[3],
2010
+ padding: l[6],
2011
+ boxShadow: `0 ${l[2]} ${l[8]} oklch(0% 0 0 / 20%)`,
2012
+ maxWidth: "20rem",
2013
+ width: "100%"
2014
+ });
2015
+ }
2016
+ }
2017
+ class Ri {
2018
+ constructor(t = {}) {
2019
+ this.options = t, this.card = new Ti(), this.backdrop = this.createBackdrop(), this.backdrop.appendChild(this.card.element);
2020
+ }
2021
+ options;
2022
+ card;
2023
+ backdrop;
2024
+ keyHandler = null;
2025
+ open() {
2026
+ document.body.appendChild(this.backdrop), this.keyHandler = (t) => {
2027
+ t.key === "Escape" && this.close();
2028
+ }, document.addEventListener("keydown", this.keyHandler);
2029
+ }
2030
+ close() {
2031
+ this.backdrop.remove(), this.keyHandler !== null && (document.removeEventListener("keydown", this.keyHandler), this.keyHandler = null), this.options.onClose?.();
2032
+ }
2033
+ createBackdrop() {
2034
+ const t = document.createElement("div");
2035
+ return Object.assign(t.style, {
2036
+ position: "fixed",
2037
+ inset: l[0],
2038
+ background: "oklch(0% 0 0 / 50%)",
2039
+ zIndex: "9999",
2040
+ display: "flex",
2041
+ alignItems: "center",
2042
+ justifyContent: "center"
2043
+ }), t.addEventListener("click", (e) => {
2044
+ e.target === t && this.close();
2045
+ }), t;
2046
+ }
2047
+ }
2048
+ class Ii {
2049
+ element;
2050
+ selectCallback = null;
2051
+ constructor(t, e, i) {
2052
+ this.element = document.createElement("div"), this.element.className = "ui-toggle-group";
2053
+ for (let o = 0; o < t.length; o++) {
2054
+ const s = t[o], r = document.createElement("button");
2055
+ r.className = "ui-toggle-group-item", r.dataset.value = s, r.textContent = i?.[o] ?? s, r.dataset.state = s === e ? "on" : "off", r.setAttribute("aria-pressed", s === e ? "true" : "false"), r.addEventListener("click", () => {
2056
+ this.selectCallback?.(s);
2057
+ }), this.element.appendChild(r);
2058
+ }
2059
+ }
2060
+ setSelected(t) {
2061
+ for (const e of this.element.querySelectorAll(".ui-toggle-group-item")) {
2062
+ const i = e.dataset.value === t;
2063
+ e.dataset.state = i ? "on" : "off", e.setAttribute("aria-pressed", i ? "true" : "false");
2064
+ }
2065
+ }
2066
+ onSelect(t) {
2067
+ this.selectCallback = t;
2068
+ }
2069
+ /**
2070
+ * Enable only the given values; null lifts every restriction. A disabled
2071
+ * value stays visible (the buyer sees the option exists) but cannot be
2072
+ * picked: the selectable snapshot's "not in the list = grey".
2073
+ */
2074
+ setEnabled(t) {
2075
+ for (const e of this.element.querySelectorAll(".ui-toggle-group-item")) {
2076
+ const i = t === null || t.includes(e.dataset.value ?? "");
2077
+ e.disabled = !i, e.setAttribute("aria-disabled", i ? "false" : "true");
2078
+ }
2079
+ }
2080
+ }
2081
+ class Oi {
2082
+ element;
2083
+ select;
2084
+ constructor(t, e, i, o, s) {
2085
+ this.select = document.createElement("select"), this.select.className = "ui-native-select", this.select.dataset.name = t, this.select.setAttribute("aria-label", i);
2086
+ const r = document.createElement("option");
2087
+ r.value = "", r.disabled = !0, r.textContent = i, this.select.appendChild(r);
2088
+ let a = !1;
2089
+ for (let u = 0; u < e.length; u++) {
2090
+ const f = e[u], h = document.createElement("option");
2091
+ h.value = f, h.textContent = s?.[u] ?? f, f === o && (h.selected = !0, a = !0), this.select.appendChild(h);
2092
+ }
2093
+ a || (r.selected = !0);
2094
+ const d = At(Et, { width: 16, height: 16, "stroke-width": 2 });
2095
+ d.classList.add("ui-native-select-icon"), this.element = document.createElement("div"), this.element.className = "ui-native-select-wrap", this.element.appendChild(this.select), this.element.appendChild(d);
2096
+ }
2097
+ setSelected(t) {
2098
+ this.select.value = t;
2099
+ }
2100
+ onSelect(t) {
2101
+ this.select.addEventListener("change", () => {
2102
+ t(this.select.value);
2103
+ });
2104
+ }
2105
+ /** Enable only the given values; null lifts every restriction (see ToggleGroup.setEnabled). */
2106
+ setEnabled(t) {
2107
+ for (const e of this.select.options)
2108
+ e.value !== "" && (e.disabled = t !== null && !t.includes(e.value));
2109
+ }
2110
+ }
2111
+ class Li {
2112
+ element;
2113
+ input;
2114
+ valueText;
2115
+ values;
2116
+ options;
2117
+ selectCallback = null;
2118
+ settleCallback = null;
2119
+ constructor(t, e) {
2120
+ if (t.length === 0)
2121
+ throw new Error("Virtualdisplay: Slider needs at least one value");
2122
+ this.values = t, this.options = e, this.input = document.createElement("input"), this.input.type = "range", this.input.className = "ui-slider-input", this.input.min = "0", this.input.max = String(t.length - 1), this.input.step = "1", this.input.setAttribute("aria-label", e.label), this.valueText = document.createElement("span"), this.valueText.className = "ui-slider-value", this.valueText.setAttribute("aria-hidden", "true");
2123
+ const i = document.createElement("div");
2124
+ i.className = "ui-slider-track", i.appendChild(this.input), i.appendChild(this.buildEnds()), this.element = document.createElement("div"), this.element.className = "ui-slider", this.element.appendChild(i), this.element.appendChild(this.valueText);
2125
+ const o = e.initialSelected === void 0 ? 0 : t.indexOf(e.initialSelected);
2126
+ this.applyIndex(o === -1 ? 0 : o), this.listen();
2127
+ }
2128
+ setSelected(t) {
2129
+ const e = this.values.indexOf(t);
2130
+ e !== -1 && this.applyIndex(e);
2131
+ }
2132
+ /** Fires on every step a drag or keypress crosses, with the value it lands on. */
2133
+ onSelect(t) {
2134
+ this.selectCallback = t;
2135
+ }
2136
+ /** Fires once per interaction, when the value commits (drag released, key released). */
2137
+ onSettle(t) {
2138
+ this.settleCallback = t;
2139
+ }
2140
+ listen() {
2141
+ this.input.addEventListener("input", () => {
2142
+ const t = this.values[this.currentIndex];
2143
+ t !== void 0 && (this.applyIndex(this.currentIndex), this.selectCallback?.(t));
2144
+ }), this.input.addEventListener("change", () => {
2145
+ const t = this.values[this.currentIndex];
2146
+ t !== void 0 && this.settleCallback?.(t);
2147
+ });
2148
+ }
2149
+ get currentIndex() {
2150
+ return Number(this.input.value);
2151
+ }
2152
+ applyIndex(t) {
2153
+ this.input.value = String(t);
2154
+ const e = this.displayAt(t);
2155
+ this.valueText.textContent = e, this.input.setAttribute("aria-valuetext", e);
2156
+ const i = this.values.length === 1 ? 100 : t / (this.values.length - 1) * 100;
2157
+ this.element.style.setProperty("--ui-slider-pct", `${i}%`);
2158
+ }
2159
+ buildEnds() {
2160
+ const t = document.createElement("div");
2161
+ t.className = "ui-slider-ends", t.setAttribute("aria-hidden", "true");
2162
+ for (const e of [0, this.values.length - 1]) {
2163
+ const i = document.createElement("span");
2164
+ i.textContent = this.labelAt(e), t.appendChild(i);
2165
+ }
2166
+ return t;
2167
+ }
2168
+ labelAt(t) {
2169
+ return this.options.labels?.[t] ?? this.values[t] ?? "";
2170
+ }
2171
+ displayAt(t) {
2172
+ const e = this.labelAt(t);
2173
+ return this.options.unit === void 0 ? e : `${e} ${this.options.unit}`;
2174
+ }
2175
+ }
2176
+ const _ = {
2177
+ fontFamily: {
2178
+ sans: ["Mona Sans", "ui-sans-serif", "system-ui", "sans-serif", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"]
2179
+ },
2180
+ fontSize: {
2181
+ xs: "0.75rem",
2182
+ // 12px
2183
+ sm: "0.875rem",
2184
+ // 14px
2185
+ base: "1rem",
2186
+ // 16px
2187
+ lg: "1.125rem"
2188
+ },
2189
+ fontWeight: {
2190
+ medium: "500",
2191
+ semibold: "600"
2192
+ }
2193
+ }, x = _.fontFamily.sans.join(", "), Mi = `
2194
+ --primary: oklch(5% 0 0);
2195
+ --primary-foreground: oklch(100% 0 0);
2196
+ --secondary: oklch(92% 0 0);
2197
+ --secondary-foreground: oklch(5% 0 0);
2198
+ --background: oklch(100% 0 0);
2199
+ --foreground: oklch(5% 0 0);
2200
+ --border: oklch(92% 0 0);
2201
+ --ring: oklch(44% 0 0);
2202
+ --radius: 0.625rem;
2203
+ --accent: oklch(92% 0 0);
2204
+ --accent-foreground: oklch(5% 0 0);
2205
+ --muted-foreground: oklch(44% 0 0);
2206
+ `, Ni = `
2207
+ :root { ${Mi} }
2208
+
2209
+ /* ── Button ──────────────────────────────────────────────── */
2210
+ .ui-btn {
2211
+ display: inline-flex;
2212
+ align-items: center;
2213
+ justify-content: center;
2214
+ gap: 0.5rem;
2215
+ border: 1px solid transparent;
2216
+ border-radius: var(--radius);
2217
+ font-family: ${x};
2218
+ font-size: 0.875rem;
2219
+ font-weight: 600;
2220
+ white-space: nowrap;
2221
+ cursor: pointer;
2222
+ transition: all 200ms;
2223
+ flex-shrink: 0;
2224
+ }
2225
+ .ui-btn:active { transform: scale(0.98); }
2226
+ .ui-btn:disabled { opacity: 0.5; pointer-events: none; }
2227
+ .ui-btn svg { inline-size: 1rem; block-size: 1rem; pointer-events: none; flex-shrink: 0; }
2228
+
2229
+ .ui-btn--default {
2230
+ background: var(--primary);
2231
+ color: var(--primary-foreground);
2232
+ box-shadow: 0 1px 3px oklch(5% 0 0 / 20%);
2233
+ }
2234
+ .ui-btn--default:hover { background: oklch(10% 0 0); }
2235
+
2236
+ .ui-btn--outline {
2237
+ background: var(--background);
2238
+ color: var(--foreground);
2239
+ border-color: var(--border);
2240
+ box-shadow: 0 1px 2px oklch(5% 0 0 / 8%);
2241
+ }
2242
+ .ui-btn--outline:hover { background: var(--accent); border-color: oklch(85% 0 0); }
2243
+
2244
+ .ui-btn--secondary {
2245
+ background: var(--secondary);
2246
+ color: var(--secondary-foreground);
2247
+ box-shadow: 0 1px 2px oklch(5% 0 0 / 8%);
2248
+ }
2249
+ .ui-btn--secondary:hover { background: oklch(88% 0 0); }
2250
+
2251
+ .ui-btn--ghost { background: transparent; color: var(--foreground); }
2252
+ .ui-btn--ghost:hover { background: var(--accent); }
2253
+
2254
+ .ui-btn--size-default { block-size: 2.25rem; padding: 0 1rem; }
2255
+ .ui-btn--size-sm { block-size: 2rem; padding: 0 0.75rem; border-radius: calc(var(--radius) - 0.125rem); }
2256
+ .ui-btn--size-lg { block-size: 2.5rem; padding: 0 1.5rem; }
2257
+ .ui-btn--size-icon { inline-size: 2.25rem; block-size: 2.25rem; padding: 0; }
2258
+ .ui-btn--size-icon-sm { inline-size: 2rem; block-size: 2rem; padding: 0; }
2259
+ .ui-btn--size-icon-lg { inline-size: 2.5rem; block-size: 2.5rem; padding: 0; }
2260
+
2261
+ /* ── Toggle group ────────────────────────────────────────── */
2262
+ .ui-toggle-group { display: inline-flex; flex-wrap: wrap; gap: 0.25rem; }
2263
+
2264
+ .ui-toggle-group-item {
2265
+ display: inline-flex;
2266
+ align-items: center;
2267
+ justify-content: center;
2268
+ block-size: 2rem;
2269
+ padding: 0 0.75rem;
2270
+ border: 1px solid var(--border);
2271
+ border-radius: var(--radius);
2272
+ background: var(--background);
2273
+ color: var(--foreground);
2274
+ font-family: ${x};
2275
+ font-size: 0.875rem;
2276
+ font-weight: 600;
2277
+ cursor: pointer;
2278
+ white-space: nowrap;
2279
+ transition: all 200ms;
2280
+ box-shadow: 0 1px 2px oklch(5% 0 0 / 8%);
2281
+ }
2282
+ .ui-toggle-group-item:hover { background: var(--accent); border-color: oklch(85% 0 0); }
2283
+ .ui-toggle-group-item:disabled { opacity: 0.4; cursor: not-allowed; }
2284
+ .ui-toggle-group-item:disabled:hover { background: var(--background); border-color: var(--border); }
2285
+ .ui-toggle-group-item:active { transform: scale(0.98); }
2286
+ .ui-toggle-group-item:focus-visible {
2287
+ outline: none;
2288
+ border-color: var(--ring);
2289
+ box-shadow: 0 0 0 3px oklch(5% 0 0 / 12%);
2290
+ }
2291
+ .ui-toggle-group-item[data-state="on"] {
2292
+ background: var(--primary);
2293
+ color: var(--primary-foreground);
2294
+ border-color: var(--primary);
2295
+ }
2296
+ .ui-toggle-group-item[data-state="on"]:hover { background: oklch(10% 0 0); }
2297
+
2298
+ /* ── Native select ───────────────────────────────────────── */
2299
+ .ui-native-select-wrap {
2300
+ position: relative;
2301
+ display: inline-flex;
2302
+ min-inline-size: 8rem;
2303
+ }
2304
+
2305
+ .ui-native-select {
2306
+ display: block;
2307
+ inline-size: 100%;
2308
+ block-size: 2.25rem;
2309
+ padding: 0 2.25rem 0 0.75rem;
2310
+ border: 1px solid var(--border);
2311
+ border-radius: var(--radius);
2312
+ background: var(--background);
2313
+ appearance: none;
2314
+ color: var(--foreground);
2315
+ font-family: ${x};
2316
+ font-size: 0.875rem;
2317
+ font-weight: 400;
2318
+ cursor: pointer;
2319
+ transition: color 200ms, box-shadow 200ms, border-color 200ms;
2320
+ box-shadow: 0 1px 2px oklch(5% 0 0 / 8%);
2321
+ }
2322
+ .ui-native-select:hover { border-color: oklch(85% 0 0); }
2323
+ .ui-native-select:focus-visible {
2324
+ outline: none;
2325
+ border-color: var(--ring);
2326
+ box-shadow: 0 0 0 3px oklch(5% 0 0 / 12%);
2327
+ }
2328
+
2329
+ .ui-native-select-icon {
2330
+ position: absolute;
2331
+ inset-block-start: 50%;
2332
+ inset-inline-end: 0.625rem;
2333
+ transform: translateY(-50%);
2334
+ color: var(--muted-foreground);
2335
+ pointer-events: none;
2336
+ }
2337
+
2338
+ /* ── Slider ──────────────────────────────────────────────── */
2339
+ .ui-slider {
2340
+ display: flex;
2341
+ align-items: center;
2342
+ gap: 0.75rem;
2343
+ inline-size: 100%;
2344
+ }
2345
+
2346
+ .ui-slider-track {
2347
+ flex: 1;
2348
+ min-inline-size: 0;
2349
+ }
2350
+
2351
+ /* Tabular figures keep the readout and layout steady mid-drag. */
2352
+ .ui-slider-value {
2353
+ font-family: ${x};
2354
+ font-size: 0.875rem;
2355
+ font-weight: 600;
2356
+ color: var(--foreground);
2357
+ font-variant-numeric: tabular-nums;
2358
+ white-space: nowrap;
2359
+ }
2360
+
2361
+ .ui-slider-ends {
2362
+ display: flex;
2363
+ justify-content: space-between;
2364
+ font-family: ${x};
2365
+ font-size: 0.75rem;
2366
+ color: var(--muted-foreground);
2367
+ font-variant-numeric: tabular-nums;
2368
+ }
2369
+
2370
+ /* 2.75rem tall: the whole input is the touch target (44px). */
2371
+ .ui-slider-input {
2372
+ appearance: none;
2373
+ display: block;
2374
+ inline-size: 100%;
2375
+ block-size: 2.75rem;
2376
+ margin: 0;
2377
+ padding: 0;
2378
+ background: transparent;
2379
+ cursor: pointer;
2380
+ }
2381
+ .ui-slider-input:focus-visible { outline: none; }
2382
+
2383
+ /* --ui-slider-pct is set by the component; it fills the track up to the thumb. */
2384
+ .ui-slider-input::-webkit-slider-runnable-track {
2385
+ block-size: 0.375rem;
2386
+ border-radius: 9999px;
2387
+ background: linear-gradient(to right, var(--primary) var(--ui-slider-pct, 0%), var(--secondary) var(--ui-slider-pct, 0%));
2388
+ }
2389
+ .ui-slider-input::-webkit-slider-thumb {
2390
+ appearance: none;
2391
+ inline-size: 1.25rem;
2392
+ block-size: 1.25rem;
2393
+ margin-block-start: -0.4375rem; /* centre the 1.25rem thumb on the 0.375rem track */
2394
+ border-radius: 50%;
2395
+ border: 2px solid var(--primary);
2396
+ background: var(--background);
2397
+ box-shadow: 0 1px 3px oklch(5% 0 0 / 20%);
2398
+ transition: box-shadow 200ms, border-color 200ms;
2399
+ }
2400
+ .ui-slider-input:focus-visible::-webkit-slider-thumb {
2401
+ border-color: var(--ring);
2402
+ box-shadow: 0 0 0 3px oklch(5% 0 0 / 12%);
2403
+ }
2404
+
2405
+ .ui-slider-input::-moz-range-track {
2406
+ block-size: 0.375rem;
2407
+ border-radius: 9999px;
2408
+ background: var(--secondary);
2409
+ }
2410
+ .ui-slider-input::-moz-range-progress {
2411
+ block-size: 0.375rem;
2412
+ border-radius: 9999px;
2413
+ background: var(--primary);
2414
+ }
2415
+ .ui-slider-input::-moz-range-thumb {
2416
+ inline-size: 1.25rem;
2417
+ block-size: 1.25rem;
2418
+ border-radius: 50%;
2419
+ border: 2px solid var(--primary);
2420
+ background: var(--background);
2421
+ box-shadow: 0 1px 3px oklch(5% 0 0 / 20%);
2422
+ transition: box-shadow 200ms, border-color 200ms;
2423
+ }
2424
+ .ui-slider-input:focus-visible::-moz-range-thumb {
2425
+ border-color: var(--ring);
2426
+ box-shadow: 0 0 0 3px oklch(5% 0 0 / 12%);
2427
+ }
2428
+
2429
+ /* ── Annotation layer ───────────────────────────────────── */
2430
+ .vd-annotations-layer {
2431
+ position: absolute;
2432
+ inset-block-start: 0;
2433
+ inset-inline-start: 0;
2434
+ inline-size: 100%;
2435
+ block-size: 100%;
2436
+ pointer-events: none;
2437
+ overflow: visible !important; /* override CSS2DRenderer inline style */
2438
+ }
2439
+
2440
+ .vd-annotation-anchor {
2441
+ position: relative;
2442
+ }
2443
+
2444
+ .vd-annotation-marker {
2445
+ display: flex;
2446
+ align-items: center;
2447
+ justify-content: center;
2448
+ inline-size: 1.75rem;
2449
+ block-size: 1.75rem;
2450
+ border-radius: 50%;
2451
+ border: 2px solid oklch(100% 0 0);
2452
+ background: var(--primary);
2453
+ color: var(--primary-foreground);
2454
+ font-family: ${x};
2455
+ font-size: 0.8125rem;
2456
+ font-weight: 600;
2457
+ cursor: pointer;
2458
+ pointer-events: auto;
2459
+ box-shadow: 0 2px 8px oklch(5% 0 0 / 20%);
2460
+ transition: transform 100ms ease;
2461
+ position: relative;
2462
+ }
2463
+ .vd-annotation-marker:hover { transform: scale(1.1); }
2464
+ .vd-annotation-marker:focus-visible {
2465
+ outline: 2px solid var(--ring);
2466
+ outline-offset: 2px;
2467
+ }
2468
+
2469
+ .vd-annotation-popup {
2470
+ position: absolute;
2471
+ inset-block-end: calc(100% + 0.5rem);
2472
+ inset-inline-start: 50%;
2473
+ transform: translateX(-50%);
2474
+ background: var(--background);
2475
+ color: var(--foreground);
2476
+ border: 1px solid var(--border);
2477
+ border-radius: var(--radius);
2478
+ padding: 0.75rem 1rem;
2479
+ /* The popup is absolutely positioned inside the ~28px marker anchor, so
2480
+ without an explicit width it shrink-to-fits against that anchor and
2481
+ collapses to one word per line. max-content sizes it by its own text,
2482
+ capped below. */
2483
+ inline-size: max-content;
2484
+ max-inline-size: 15rem;
2485
+ box-shadow: 0 4px 6px oklch(5% 0 0 / 8%), 0 2px 4px oklch(5% 0 0 / 6%);
2486
+ pointer-events: auto;
2487
+ white-space: normal;
2488
+ }
2489
+ .vd-annotation-popup:focus { outline: none; }
2490
+
2491
+ /* Flipped below the marker when the popup would clip the container's top
2492
+ edge; the layer positions it at open time and on the render cadence. */
2493
+ .vd-annotation-popup--below {
2494
+ inset-block-end: auto;
2495
+ inset-block-start: calc(100% + 0.5rem);
2496
+ }
2497
+
2498
+ .vd-annotation-popup-close {
2499
+ position: absolute;
2500
+ inset-block-start: 0.375rem;
2501
+ inset-inline-end: 0.375rem;
2502
+ display: flex;
2503
+ align-items: center;
2504
+ justify-content: center;
2505
+ inline-size: 1.25rem;
2506
+ block-size: 1.25rem;
2507
+ padding: 0;
2508
+ border: none;
2509
+ border-radius: calc(var(--radius) - 4px);
2510
+ background: transparent;
2511
+ color: var(--muted-foreground);
2512
+ cursor: pointer;
2513
+ pointer-events: auto;
2514
+ }
2515
+ .vd-annotation-popup-close:hover {
2516
+ color: var(--foreground);
2517
+ background: var(--secondary);
2518
+ }
2519
+ .vd-annotation-popup-close:focus-visible {
2520
+ outline: 2px solid var(--ring);
2521
+ outline-offset: 1px;
2522
+ }
2523
+ .vd-annotation-popup-close svg {
2524
+ inline-size: 0.875rem;
2525
+ block-size: 0.875rem;
2526
+ }
2527
+
2528
+ @media (prefers-reduced-motion: no-preference) {
2529
+ .vd-annotation-popup {
2530
+ animation: vd-popup-in 150ms ease;
2531
+ }
2532
+ }
2533
+
2534
+ /* Opacity only: the layer sets the popup's transform inline to clamp it
2535
+ inside the container, and a keyframe animating transform would override
2536
+ that for its duration and make the popup jump at the end. */
2537
+ @keyframes vd-popup-in {
2538
+ from { opacity: 0; }
2539
+ to { opacity: 1; }
2540
+ }
2541
+
2542
+ .vd-annotation-popup-title {
2543
+ font-family: ${x};
2544
+ font-size: 0.875rem;
2545
+ font-weight: 600;
2546
+ line-height: 1.25rem;
2547
+ /* Keep the title clear of the absolutely positioned close button. */
2548
+ padding-inline-end: 1.5rem;
2549
+ }
2550
+
2551
+ .vd-annotation-popup-description {
2552
+ font-family: ${x};
2553
+ font-size: 0.8125rem;
2554
+ font-weight: 400;
2555
+ line-height: 1.125rem;
2556
+ color: var(--muted-foreground);
2557
+ margin-block-start: 0.25rem;
2558
+ }
2559
+
2560
+ /* ── QR modal ────────────────────────────────────────────── */
2561
+ /* The slot the QR renders into. While loading it is a calm pulsing
2562
+ placeholder of the same size, so the modal opens instantly and the QR
2563
+ swaps in without any layout shift. */
2564
+ .ui-qr-slot {
2565
+ inline-size: 6rem;
2566
+ block-size: 6rem;
2567
+ border-radius: 0.25rem;
2568
+ overflow: hidden;
2569
+ }
2570
+
2571
+ .ui-qr-slot img {
2572
+ inline-size: 100%;
2573
+ block-size: 100%;
2574
+ display: block;
2575
+ }
2576
+
2577
+ .ui-qr-slot--loading {
2578
+ background: var(--secondary);
2579
+ }
2580
+
2581
+ @media (prefers-reduced-motion: no-preference) {
2582
+ .ui-qr-slot--loading {
2583
+ animation: vd-qr-pulse 1.6s ease-in-out infinite;
2584
+ }
2585
+ }
2586
+
2587
+ @keyframes vd-qr-pulse {
2588
+ 0%, 100% { opacity: 1; }
2589
+ 50% { opacity: 0.55; }
2590
+ }
2591
+ `;
2592
+ let ct = !1;
2593
+ function xt() {
2594
+ if (ct || typeof document > "u")
2595
+ return;
2596
+ ct = !0;
2597
+ const n = document.createElement("style");
2598
+ n.id = "vd-ui-vanilla", n.textContent = Ni, document.head.appendChild(n);
2599
+ }
2600
+ const zi = _.fontFamily.sans.join(", ");
2601
+ class Pi {
2602
+ qrLib = null;
2603
+ dialog = null;
2604
+ qrSlot = null;
2605
+ /** Open the modal immediately, with a loading placeholder in the QR slot. */
2606
+ show() {
2607
+ this.hide(), xt(), this.dialog = new Ri({ onClose: () => {
2608
+ this.dialog = null, this.qrSlot = null;
2609
+ } }), Object.assign(this.dialog.card.element.style, {
2610
+ display: "flex",
2611
+ flexDirection: "column",
2612
+ alignItems: "center",
2613
+ gap: l[3],
2614
+ fontFamily: zi
2615
+ }), this.dialog.card.element.appendChild(this.createText(oi(), {
2616
+ fontSize: _.fontSize.base,
2617
+ fontWeight: _.fontWeight.semibold,
2618
+ color: m.neutral[900]
2619
+ })), this.dialog.card.element.appendChild(this.createText(Yn(), {
2620
+ fontSize: _.fontSize.sm,
2621
+ color: m.neutral[600]
2622
+ })), this.qrSlot = this.createQrSlot(), this.dialog.card.element.appendChild(this.qrSlot), this.dialog.card.element.appendChild(this.createText(ti(), {
2623
+ fontSize: _.fontSize.xs,
2624
+ color: m.neutral[500]
2625
+ }));
2626
+ const t = new v({
2627
+ variant: "outline",
2628
+ label: Gn(),
2629
+ onClick: () => {
2630
+ this.hide();
2631
+ }
2632
+ });
2633
+ this.dialog.card.element.appendChild(t.element), this.dialog.open();
2634
+ }
2635
+ /**
2636
+ * Render the QR for `url` into the open modal's slot, replacing the loading
2637
+ * placeholder. Resolves `true` once the QR is actually on screen; `false`
2638
+ * when the modal was closed before it could render (nothing reopens).
2639
+ */
2640
+ async update(t) {
2641
+ this.qrLib ??= await import("./browser-ey6Iih3j.js").then((i) => i.b);
2642
+ const e = await this.qrLib.toDataURL(t, {
2643
+ width: 256,
2644
+ margin: 4,
2645
+ errorCorrectionLevel: "M",
2646
+ color: { dark: m.black, light: m.white }
2647
+ });
2648
+ return this.qrSlot === null ? !1 : (this.qrSlot.classList.remove("ui-qr-slot--loading"), this.qrSlot.replaceChildren(this.createQrImage(e)), !0);
2649
+ }
2650
+ hide() {
2651
+ this.dialog?.close(), this.dialog = null, this.qrSlot = null;
2652
+ }
2653
+ createText(t, e) {
2654
+ const i = document.createElement("p");
2655
+ return i.textContent = t, Object.assign(i.style, { margin: "0", textAlign: "center", ...e }), i;
2656
+ }
2657
+ createQrSlot() {
2658
+ const t = document.createElement("div");
2659
+ return t.className = "ui-qr-slot ui-qr-slot--loading", t;
2660
+ }
2661
+ createQrImage(t) {
2662
+ const e = document.createElement("img");
2663
+ return e.src = t, e.alt = "QR code", e;
2664
+ }
2665
+ }
2666
+ function dt(n) {
2667
+ const t = Math.max(0, Math.min(1, n));
2668
+ return t * t * (3 - 2 * t);
2669
+ }
2670
+ function Fi(n, t, e, i, o, s) {
2671
+ const [r, a, d] = n, u = Math.cos(t), f = Math.sin(t), h = u * r + f * d, E = -f * r + u * d, C = Math.cos(e), k = Math.sin(e), I = C * a - k * E, F = k * a + C * E, tt = 0.55;
2672
+ return [i + h * s * tt, o - I * s * tt, F];
2673
+ }
2674
+ function $i(n, t, e) {
2675
+ n.beginPath(), n.moveTo(t[0], t[1]), n.lineTo(e[0], e[1]), n.strokeStyle = "#000", n.lineWidth = 2, n.stroke();
2676
+ }
2677
+ function Di(n, t, e, i, o) {
2678
+ const s = t(e), r = t(i), a = [
2679
+ s[0] + (r[0] - s[0]) * o,
2680
+ s[1] + (r[1] - s[1]) * o,
2681
+ s[2]
2682
+ ];
2683
+ $i(n, s, a);
2684
+ }
2685
+ const D = [-1, -1, 1], B = [1, -1, 1], U = [1, -1, -1], V = [-1, -1, -1], H = [-1, 1, 1], j = [1, 1, 1], q = [1, 1, -1], G = [-1, 1, -1], ut = [
2686
+ [D, B],
2687
+ [B, U],
2688
+ [U, V],
2689
+ [V, D],
2690
+ [D, H],
2691
+ [B, j],
2692
+ [U, q],
2693
+ [V, G],
2694
+ [H, j],
2695
+ [j, q],
2696
+ [q, G],
2697
+ [G, H]
2698
+ ], Bi = 400, ht = '-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif';
2699
+ function Ui() {
2700
+ const n = document.createElement("div");
2701
+ Object.assign(n.style, {
2702
+ position: "absolute",
2703
+ inset: "0",
2704
+ display: "flex",
2705
+ flexDirection: "column",
2706
+ alignItems: "center",
2707
+ justifyContent: "center",
2708
+ background: "#fff",
2709
+ zIndex: "20",
2710
+ opacity: "0",
2711
+ transition: "opacity 0.3s ease-in-out"
2712
+ });
2713
+ const t = document.createElement("canvas");
2714
+ Object.assign(t.style, {
2715
+ width: "100%",
2716
+ height: "100%",
2717
+ position: "absolute",
2718
+ inset: "0"
2719
+ });
2720
+ const e = document.createElement("div");
2721
+ Object.assign(e.style, {
2722
+ position: "absolute",
2723
+ top: "56%",
2724
+ left: "0",
2725
+ right: "0",
2726
+ textAlign: "center",
2727
+ pointerEvents: "none"
2728
+ });
2729
+ const i = document.createElement("span");
2730
+ Object.assign(i.style, {
2731
+ fontSize: "13px",
2732
+ fontWeight: "400",
2733
+ color: "#000",
2734
+ fontVariantNumeric: "tabular-nums",
2735
+ fontFamily: ht
2736
+ });
2737
+ const o = document.createElement("span");
2738
+ return Object.assign(o.style, {
2739
+ fontSize: "13px",
2740
+ color: "#000",
2741
+ fontFamily: ht
2742
+ }), o.textContent = hi(), e.appendChild(i), e.appendChild(o), n.appendChild(t), n.appendChild(e), { overlay: n, canvas: t, pctEl: i, phaseEl: o };
2743
+ }
2744
+ class Vi {
2745
+ overlay;
2746
+ canvas;
2747
+ ctx;
2748
+ pctEl;
2749
+ phaseEl;
2750
+ animationId = null;
2751
+ showTimerId = null;
2752
+ startTime;
2753
+ hidden = !1;
2754
+ visible = !1;
2755
+ constructor(t, e = {}) {
2756
+ this.startTime = performance.now();
2757
+ const i = Ui();
2758
+ this.overlay = i.overlay, this.canvas = i.canvas, this.pctEl = i.pctEl, this.phaseEl = i.phaseEl, t.appendChild(this.overlay), this.ctx = this.canvas.getContext("2d"), this.ctx !== null && (this.sizeCanvas(), this.tick());
2759
+ const o = e.showDelay ?? Bi;
2760
+ this.showTimerId = setTimeout(() => {
2761
+ this.reveal();
2762
+ }, o);
2763
+ }
2764
+ updateProgress(t, e) {
2765
+ this.hidden || (this.pctEl.textContent = `${Math.round(t)}%`, this.phaseEl.textContent = e !== "" ? ` · ${e}` : "");
2766
+ }
2767
+ /**
2768
+ * Show an error state — stops animation and displays the error message.
2769
+ * The overlay stays visible so the user knows something went wrong.
2770
+ */
2771
+ showError(t) {
2772
+ if (this.hidden = !1, this.animationId !== null && (cancelAnimationFrame(this.animationId), this.animationId = null), this.ctx !== null) {
2773
+ const e = Math.min(devicePixelRatio, 2), i = this.canvas.width / e, o = this.canvas.height / e;
2774
+ this.ctx.fillStyle = "#fff", this.ctx.fillRect(0, 0, i * e, o * e);
2775
+ }
2776
+ this.pctEl.textContent = "", this.phaseEl.textContent = t, this.phaseEl.style.color = "#b91c1c", this.reveal();
2777
+ }
2778
+ hide() {
2779
+ if (!this.hidden) {
2780
+ if (this.hidden = !0, this.cancelShowTimer(), !this.visible) {
2781
+ this.destroy();
2782
+ return;
2783
+ }
2784
+ this.overlay.style.opacity = "0", this.overlay.addEventListener("transitionend", () => {
2785
+ this.destroy();
2786
+ }, { once: !0 }), setTimeout(() => {
2787
+ this.destroy();
2788
+ }, 500);
2789
+ }
2790
+ }
2791
+ destroy() {
2792
+ this.cancelShowTimer(), this.animationId !== null && (cancelAnimationFrame(this.animationId), this.animationId = null), this.overlay.remove();
2793
+ }
2794
+ reveal() {
2795
+ this.hidden || this.visible || (this.visible = !0, this.overlay.style.opacity = "1");
2796
+ }
2797
+ cancelShowTimer() {
2798
+ this.showTimerId !== null && (clearTimeout(this.showTimerId), this.showTimerId = null);
2799
+ }
2800
+ sizeCanvas() {
2801
+ if (this.ctx === null)
2802
+ return;
2803
+ const t = Math.min(devicePixelRatio, 2), e = this.overlay.clientWidth || 440, i = this.overlay.clientHeight || 360;
2804
+ this.canvas.width = e * t, this.canvas.height = i * t, this.ctx.scale(t, t), this.ctx.lineCap = "round", this.ctx.lineJoin = "round";
2805
+ }
2806
+ tick = () => {
2807
+ this.hidden || (this.drawFrame(), this.animationId = requestAnimationFrame(this.tick));
2808
+ };
2809
+ drawFrame() {
2810
+ if (this.ctx === null)
2811
+ return;
2812
+ const t = this.canvas.width / Math.min(devicePixelRatio, 2), e = this.canvas.height / Math.min(devicePixelRatio, 2), i = (performance.now() - this.startTime) / 1e3;
2813
+ this.ctx.fillStyle = "#fff", this.ctx.fillRect(0, 0, t, e);
2814
+ const o = t / 2, s = e * 0.44, r = Math.min(t, e) * 0.08, a = 0.785, d = 0.615, u = (k) => Fi(k, a, d, o, s, r), f = 6, h = i % f / f, E = ut.length;
2815
+ let C;
2816
+ h < 0.35 ? C = dt(h / 0.35) * E : h < 0.55 ? C = E : h < 0.9 ? C = (1 - dt((h - 0.55) / 0.35)) * E : C = 0, this.ctx.shadowBlur = 5, this.ctx.shadowColor = "rgba(30,30,30,0.08)";
2817
+ for (let k = 0; k < E && !(k >= C); k++) {
2818
+ const I = ut[k];
2819
+ if (I === void 0)
2820
+ continue;
2821
+ const F = Math.min(1, C - k);
2822
+ Di(this.ctx, u, I[0], I[1], F);
2823
+ }
2824
+ this.ctx.shadowBlur = 0;
2825
+ }
2826
+ }
2827
+ class Hi {
2828
+ constructor(t) {
2829
+ this.options = t, this.element = document.createElement("div"), Object.assign(this.element.style, { display: "flex", gap: "0.375rem" }), this.onFullscreenChange = () => {
2830
+ this.setFullscreenIcon(document.fullscreenElement !== null);
2831
+ }, document.addEventListener("fullscreenchange", this.onFullscreenChange);
2832
+ }
2833
+ options;
2834
+ element;
2835
+ arBtn = null;
2836
+ fullscreenBtn = null;
2837
+ annotationsBtn = null;
2838
+ /**
2839
+ * Whether markers are currently shown, as this control believes it. Seeded
2840
+ * from the mode when the button first renders, then owned by the clicks:
2841
+ * the toggle channel has no return leg, so the button is the state holder.
2842
+ */
2843
+ annotationsShown = !1;
2844
+ onFullscreenChange;
2845
+ setFullscreenIcon(t) {
2846
+ this.fullscreenBtn !== null && this.fullscreenBtn.setIcon(t ? g.exitFullscreen() : g.fullscreen());
2847
+ }
2848
+ syncButton(t, e, i) {
2849
+ if (e && t === null) {
2850
+ const o = i();
2851
+ return this.element.appendChild(o.element), o;
2852
+ }
2853
+ return !e && t !== null ? (t.element.remove(), null) : t;
2854
+ }
2855
+ applySettings(t) {
2856
+ this.arBtn = this.syncButton(this.arBtn, t.ar !== !1 && this.options.onAr !== void 0, () => new v({
2857
+ variant: "outline",
2858
+ size: "icon",
2859
+ label: en(),
2860
+ icon: g.ar(),
2861
+ onClick: () => {
2862
+ this.options.onAr?.();
2863
+ }
2864
+ })), this.fullscreenBtn = this.syncButton(this.fullscreenBtn, t.fullscreen !== !1 && this.options.onFullscreen !== void 0, () => new v({
2865
+ variant: "outline",
2866
+ size: "icon",
2867
+ label: Vn(),
2868
+ icon: g.fullscreen(),
2869
+ onClick: () => {
2870
+ this.options.onFullscreen?.();
2871
+ }
2872
+ }));
2873
+ const e = t.annotations === "toggle-shown" || t.annotations === "toggle-hidden";
2874
+ this.annotationsBtn = this.syncButton(this.annotationsBtn, e && this.options.onAnnotationsToggle !== void 0, () => {
2875
+ this.annotationsShown = t.annotations === "toggle-shown";
2876
+ const i = new v({
2877
+ variant: "outline",
2878
+ size: "icon",
2879
+ label: Ke(),
2880
+ icon: this.annotationsShown ? g.annotations() : g.annotationsOff(),
2881
+ onClick: () => {
2882
+ this.flipAnnotations();
2883
+ }
2884
+ });
2885
+ return i.element.setAttribute("aria-pressed", String(this.annotationsShown)), i;
2886
+ });
2887
+ }
2888
+ flipAnnotations() {
2889
+ this.annotationsShown = !this.annotationsShown, this.annotationsBtn?.element.setAttribute("aria-pressed", String(this.annotationsShown)), this.annotationsBtn?.setIcon(this.annotationsShown ? g.annotations() : g.annotationsOff()), this.options.onAnnotationsToggle?.(this.annotationsShown);
2890
+ }
2891
+ destroy() {
2892
+ document.removeEventListener("fullscreenchange", this.onFullscreenChange);
2893
+ }
2894
+ }
2895
+ const R = 0.5;
2896
+ class P {
2897
+ element;
2898
+ constructor(t, e) {
2899
+ this.element = Object.assign(document.createElement("div"), { className: "vd-camera-controls" });
2900
+ const i = {
2901
+ targetRotation: e.rotation,
2902
+ targetTilt: e.tilt,
2903
+ targetZoom: e.zoom,
2904
+ thetaOffset: 0
2905
+ }, o = ji(t, e, i);
2906
+ e.onChange((s) => {
2907
+ i.targetRotation = s.rotation - i.thetaOffset, i.targetTilt = s.tilt, i.targetZoom = s.zoom, qi(o, e, s);
2908
+ }), this.element.appendChild(P.buildZoom(o.zoomOut, o.zoomIn)), this.element.appendChild(P.buildDpad(o.tiltUp, o.rotateLeft, o.reset, o.rotateRight, o.tiltDown));
2909
+ }
2910
+ static buildZoom(t, e) {
2911
+ const i = document.createElement("div");
2912
+ return i.className = "vd-zoom", i.appendChild(t.element), i.appendChild(e.element), i;
2913
+ }
2914
+ static buildDpad(t, e, i, o, s) {
2915
+ const r = document.createElement("div");
2916
+ r.className = "vd-dpad";
2917
+ for (const [a, d] of [
2918
+ [t, "up"],
2919
+ [e, "left"],
2920
+ [i, "center"],
2921
+ [o, "right"],
2922
+ [s, "down"]
2923
+ ])
2924
+ a.element.classList.add(`vd-dpad-${d}`), r.appendChild(a.element);
2925
+ return r;
2926
+ }
2927
+ }
2928
+ function ji(n, t, e) {
2929
+ const i = (r) => Math.max(t.limits?.minZoom ?? 1, Math.min(t.limits?.maxZoom ?? 1 / 0, r)), o = (r) => {
2930
+ const a = t.limits?.minRotate, d = t.limits?.maxRotate;
2931
+ return a !== void 0 || d !== void 0 ? Math.max(a ?? -1 / 0, Math.min(d ?? 1 / 0, r)) : r;
2932
+ }, s = (r) => {
2933
+ const a = t.limits?.minTilt, d = t.limits?.maxTilt;
2934
+ if (a !== void 0 || d !== void 0) {
2935
+ e.targetTilt = Math.max(a ?? 0, Math.min(d ?? 180, e.targetTilt + r)), n.tilt(e.targetTilt).set();
2936
+ return;
2937
+ }
2938
+ const u = e.targetTilt + r;
2939
+ u > 180 || u < 0 ? (e.targetTilt = u > 180 ? 360 - u : -u, e.thetaOffset = (e.thetaOffset + 180) % 360, n.tilt(e.targetTilt).rotate(e.targetRotation + e.thetaOffset).set()) : (e.targetTilt = u, n.tilt(e.targetTilt).set());
2940
+ };
2941
+ return {
2942
+ zoomOut: new v({ variant: "outline", size: "icon", label: In(), icon: g.zoomOut(), onClick: () => {
2943
+ e.targetZoom = i(e.targetZoom - 10), n.zoom(e.targetZoom).set();
2944
+ } }),
2945
+ zoomIn: new v({ variant: "outline", size: "icon", label: En(), icon: g.zoomIn(), onClick: () => {
2946
+ e.targetZoom = i(e.targetZoom + 10), n.zoom(e.targetZoom).set();
2947
+ } }),
2948
+ tiltUp: new v({ variant: "outline", size: "icon", label: _n(), icon: g.tiltUp(), onClick: () => {
2949
+ s(-15);
2950
+ } }),
2951
+ rotateLeft: new v({ variant: "outline", size: "icon", label: dn(), icon: g.rotateLeft(), onClick: () => {
2952
+ e.targetRotation = o(e.targetRotation - 15), n.rotate(e.targetRotation + e.thetaOffset).set();
2953
+ } }),
2954
+ reset: new v({ variant: "outline", size: "icon", label: rn(), icon: g.reset(), onClick: () => {
2955
+ e.thetaOffset = 0, n.reset();
2956
+ } }),
2957
+ rotateRight: new v({ variant: "outline", size: "icon", label: mn(), icon: g.rotateRight(), onClick: () => {
2958
+ e.targetRotation = o(e.targetRotation + 15), n.rotate(e.targetRotation + e.thetaOffset).set();
2959
+ } }),
2960
+ tiltDown: new v({ variant: "outline", size: "icon", label: vn(), icon: g.tiltDown(), onClick: () => {
2961
+ s(15);
2962
+ } })
2963
+ };
2964
+ }
2965
+ function qi(n, t, e) {
2966
+ const i = t.limits;
2967
+ i !== void 0 && (n.zoomOut.element.disabled = i.minZoom !== void 0 && e.zoom <= i.minZoom + R, n.zoomIn.element.disabled = i.maxZoom !== void 0 && e.zoom >= i.maxZoom - R, n.tiltUp.element.disabled = i.minTilt !== void 0 && e.tilt <= i.minTilt + R, n.tiltDown.element.disabled = i.maxTilt !== void 0 && e.tilt >= i.maxTilt - R, n.rotateLeft.element.disabled = i.minRotate !== void 0 && e.rotation <= i.minRotate + R, n.rotateRight.element.disabled = i.maxRotate !== void 0 && e.rotation >= i.maxRotate - R);
2968
+ }
2969
+ const T = {
2970
+ /** Attribute selection requested (cancelable, does not bubble). `detail`: `{ attribute, value }`. */
2971
+ SELECT: "vd:select",
2972
+ /** Attribute selection applied (does not bubble). `detail`: `{ attribute, value }`. */
2973
+ SELECTED: "vd:selected",
2974
+ /** Focus changed (bubbles). `detail`: `{ component, attributes }`, or `null` for the overview. */
2975
+ FOCUS_CHANGE: "vd:focus-change",
2976
+ /** Resolved configuration changed (bubbles). `detail`: the resolved configuration, or `null` when nothing matches. */
2977
+ CONFIGURATION_CHANGE: "vd:configuration-change",
2978
+ /** Configuration submitted (bubbles). `detail`: the resolved configuration handed to the host. */
2979
+ CONFIGURATION_SUBMIT: "vd:configuration-submit"
2980
+ }, Tt = {
2981
+ mm: 1,
2982
+ cm: 10,
2983
+ m: 1e3,
2984
+ in: 25.4
2985
+ }, pt = 3;
2986
+ function Gi(n) {
2987
+ const t = n.stepMm / Tt[n.displayUnit];
2988
+ for (let e = 0; e <= pt; e++) {
2989
+ const i = t * 10 ** e;
2990
+ if (Math.abs(i - Math.round(i)) < 1e-9)
2991
+ return e;
2992
+ }
2993
+ return pt;
2994
+ }
2995
+ function Zi(n, t, e) {
2996
+ const i = Gi(t), o = n / Tt[t.displayUnit];
2997
+ return new Intl.NumberFormat(e, {
2998
+ minimumFractionDigits: i,
2999
+ maximumFractionDigits: i
3000
+ }).format(o);
3001
+ }
3002
+ function Wi(n, t, e, i, o, s, r) {
3003
+ const a = p(), d = n.values.map((h) => Zi(Number(h), t, a)), u = n.values.find((h) => e.getValue(n.name, h)?.isSelected === !0) ?? n.defaultValue, f = new Li(n.values, {
3004
+ label: n.name,
3005
+ labels: d,
3006
+ unit: t.displayUnit,
3007
+ ...u !== void 0 && { initialSelected: u }
3008
+ });
3009
+ return f.onSelect((h) => {
3010
+ o(n.name, h);
3011
+ }), f.onSettle((h) => {
3012
+ i.dispatch(T.SELECT, { attribute: n.name, value: h }, !0).defaultPrevented || s(n.name, h);
3013
+ }), r(n.name, (h) => {
3014
+ f.setSelected(h.value), i.dispatch(T.SELECTED, { attribute: n.name, value: h.value });
3015
+ }), {
3016
+ element: f.element,
3017
+ // No per-notch grey-out on the slider in v1: a range control cannot
3018
+ // honestly disable one notch. The exclusion still works, the way it
3019
+ // always does, on the other side: the viewer's selectable snapshot greys
3020
+ // the leg that cannot do this size. A slider-side treatment is a later
3021
+ // refinement, noted in task 040.
3022
+ setSelectable: () => {
3023
+ }
3024
+ };
3025
+ }
3026
+ const Qi = 5;
3027
+ let Yi = 0;
3028
+ function K(n) {
3029
+ const t = n.replace(/([A-Z])/g, " $1").trim();
3030
+ return t.charAt(0).toUpperCase() + t.slice(1).toLowerCase();
3031
+ }
3032
+ function Ki(n, t, e, i, o, s) {
3033
+ const r = document.createElement("div");
3034
+ r.className = "vd-attribute", r.dataset.name = n.name;
3035
+ const a = document.createElement("span");
3036
+ a.className = "vd-attribute-label", a.textContent = K(n.name), a.id = `vd-attribute-label-${Yi++}`, r.appendChild(a), r.setAttribute("role", "group"), r.setAttribute("aria-labelledby", a.id);
3037
+ const d = (f) => {
3038
+ r.style.display = f ? "none" : "";
3039
+ }, u = n.dimension !== void 0 ? Wi(
3040
+ n,
3041
+ n.dimension,
3042
+ t,
3043
+ e,
3044
+ o,
3045
+ i,
3046
+ s
3047
+ ) : Xi(n, t, e, i, s);
3048
+ return r.appendChild(u.element), { element: r, setSelectable: u.setSelectable, setHidden: d };
3049
+ }
3050
+ function Xi(n, t, e, i, o) {
3051
+ const s = n.values.find(
3052
+ (d) => t.getValue(n.name, d)?.isSelected === !0
3053
+ ), r = n.values.map(K), a = n.values.length <= Qi ? new Ii(n.values, s, r) : new Oi(n.name, n.values, K(n.name), s, r);
3054
+ return a.onSelect((d) => {
3055
+ e.dispatch(T.SELECT, { attribute: n.name, value: d }, !0).defaultPrevented || i(n.name, d);
3056
+ }), o(n.name, (d) => {
3057
+ a.setSelected(d.value), e.dispatch(T.SELECTED, { attribute: n.name, value: d.value });
3058
+ }), {
3059
+ element: a.element,
3060
+ setSelectable: (d) => {
3061
+ a.setEnabled(d);
3062
+ }
3063
+ };
3064
+ }
3065
+ const Ji = 80, to = 2500;
3066
+ class eo {
3067
+ constructor(t, e = to) {
3068
+ this.configuration = t, this.resetMs = e, this.element = document.createElement("div"), this.element.className = "vd-commerce vd-hidden";
3069
+ const i = document.createElement("div");
3070
+ i.className = "vd-commerce-price", this.priceEl = document.createElement("span"), this.priceEl.className = "vd-price", this.vatEl = document.createElement("span"), this.vatEl.className = "vd-vat", i.append(this.priceEl, this.vatEl), this.button = new v({ variant: "default", onClick: () => {
3071
+ this.handleSubmit();
3072
+ } }), this.button.element.classList.add("vd-cta", "vd-hidden"), this.element.append(i, this.button.element), this.unsubscribe = this.configuration.onRender(() => {
3073
+ this.update();
3074
+ }), this.update();
3075
+ }
3076
+ configuration;
3077
+ resetMs;
3078
+ element;
3079
+ priceEl;
3080
+ vatEl;
3081
+ button;
3082
+ unsubscribe;
3083
+ state = "idle";
3084
+ resetTimer;
3085
+ /** Re-render from the facade. Called on every resolve and on submit-handler registration. */
3086
+ update() {
3087
+ this.renderPrice(this.configuration.current), this.renderButton();
3088
+ const t = this.configuration.current !== void 0 || this.configuration.submitEnabled;
3089
+ this.element.classList.toggle("vd-hidden", !t);
3090
+ }
3091
+ destroy() {
3092
+ this.unsubscribe(), this.resetTimer !== void 0 && clearTimeout(this.resetTimer), this.element.remove();
3093
+ }
3094
+ renderPrice(t) {
3095
+ if (t === void 0) {
3096
+ this.priceEl.textContent = "", this.vatEl.textContent = "";
3097
+ return;
3098
+ }
3099
+ this.priceEl.textContent = no(t.price, t.currency), this.vatEl.textContent = mt(this.configuration.vatDisplay);
3100
+ }
3101
+ renderButton() {
3102
+ const t = this.configuration.submitEnabled;
3103
+ this.button.element.classList.toggle("vd-hidden", !t), t && this.state === "idle" && this.setButtonText(mt(this.configuration.buttonLabel));
3104
+ }
3105
+ async handleSubmit() {
3106
+ if (this.state !== "busy") {
3107
+ this.setState("busy", De());
3108
+ try {
3109
+ await this.configuration.submit(), this.setState("success", Ze());
3110
+ } catch {
3111
+ this.setState("failed", He());
3112
+ }
3113
+ this.scheduleReset();
3114
+ }
3115
+ }
3116
+ scheduleReset() {
3117
+ this.resetTimer !== void 0 && clearTimeout(this.resetTimer), this.resetTimer = setTimeout(() => {
3118
+ this.state = "idle", this.update();
3119
+ }, this.resetMs);
3120
+ }
3121
+ setState(t, e) {
3122
+ this.state = t, this.element.dataset.submit = t, this.button.element.disabled = t === "busy", this.setButtonText(e);
3123
+ }
3124
+ setButtonText(t) {
3125
+ this.button.element.textContent = t, this.button.element.setAttribute("aria-label", t);
3126
+ }
3127
+ }
3128
+ function mt(n) {
3129
+ return (n ?? "").slice(0, Ji);
3130
+ }
3131
+ function no(n, t) {
3132
+ try {
3133
+ const e = new Intl.NumberFormat(p(), { style: "currency", currency: t }), i = e.resolvedOptions().maximumFractionDigits ?? 2;
3134
+ return e.format(n / 10 ** i);
3135
+ } catch {
3136
+ return `${t} ${(n / 100).toFixed(2)}`;
3137
+ }
3138
+ }
3139
+ class Rt {
3140
+ slot = null;
3141
+ panel = null;
3142
+ attach(t, e) {
3143
+ this.panel = e, e.style.display = "none", this.slot = document.createElement("div"), this.slot.className = "vd-slot-bottom-left", this.slot.appendChild(e), t.appendChild(this.slot);
3144
+ }
3145
+ setVisible(t) {
3146
+ this.panel !== null && (this.panel.style.display = t ? "flex" : "none");
3147
+ }
3148
+ destroy() {
3149
+ this.slot?.remove(), this.slot = null, this.panel = null;
3150
+ }
3151
+ }
3152
+ const io = 572;
3153
+ class oo {
3154
+ /** @param actionBarSlot The shared corner slot the AR/fullscreen buttons live in — the
3155
+ * "open configurator" toggle joins them there instead of floating as its own overlay. */
3156
+ constructor(t) {
3157
+ this.actionBarSlot = t;
3158
+ }
3159
+ actionBarSlot;
3160
+ element = null;
3161
+ body = null;
3162
+ openButton = null;
3163
+ closeButton = null;
3164
+ container = null;
3165
+ observer = null;
3166
+ collapsible = !1;
3167
+ expanded = !0;
3168
+ visible = !1;
3169
+ attach(t, e) {
3170
+ this.container = t, e.classList.add("vd-attributes-sidebar"), this.closeButton = new v({
3171
+ variant: "ghost",
3172
+ size: "icon-sm",
3173
+ label: Nn(),
3174
+ icon: g.close({ size: 18 }),
3175
+ onClick: () => {
3176
+ this.expanded = !1, this.render();
3177
+ }
3178
+ });
3179
+ const i = document.createElement("div");
3180
+ i.className = "vd-sidebar-header", i.appendChild(this.closeButton.element), this.body = document.createElement("div"), this.body.className = "vd-sidebar-body", this.body.appendChild(e), this.element = document.createElement("div"), this.element.className = "vd-sidebar vd-hidden", this.element.append(i, this.body), t.appendChild(this.element), this.openButton = new v({
3181
+ variant: "outline",
3182
+ size: "icon",
3183
+ label: $n(),
3184
+ icon: g.configurator({ size: 18 }),
3185
+ onClick: () => {
3186
+ this.expanded = !0, this.render();
3187
+ }
3188
+ }), this.openButton.element.classList.add("vd-configurator-toggle", "vd-hidden"), this.actionBarSlot.appendChild(this.openButton.element), this.observe();
3189
+ }
3190
+ setVisible(t) {
3191
+ this.visible = t, this.render();
3192
+ }
3193
+ destroy() {
3194
+ this.observer?.disconnect(), this.observer = null, this.openButton?.element.remove(), this.element?.remove(), this.container?.classList.remove("vd-layout-sidebar-right", "vd-layout-sidebar-permanent"), this.container = null;
3195
+ }
3196
+ observe() {
3197
+ typeof ResizeObserver > "u" || this.container === null || (this.observer = new ResizeObserver((t) => {
3198
+ const e = t[0]?.contentRect.width ?? 0;
3199
+ this.setCollapsible(e > 0 && e < io);
3200
+ }), this.observer.observe(this.container));
3201
+ }
3202
+ setCollapsible(t) {
3203
+ t !== this.collapsible && (this.collapsible = t, this.expanded = !t, this.render());
3204
+ }
3205
+ render() {
3206
+ if (this.element === null || this.openButton === null || this.closeButton === null || this.container === null)
3207
+ return;
3208
+ const t = this.visible && this.expanded, e = this.visible && !this.expanded;
3209
+ this.element.classList.toggle("vd-hidden", !this.visible), this.element.classList.toggle("vd-sidebar-collapsed", e), this.openButton.element.classList.toggle("vd-hidden", !e), this.closeButton.element.style.display = this.collapsible ? "" : "none", this.container.classList.toggle("vd-layout-sidebar-right", t), this.container.classList.toggle("vd-layout-sidebar-permanent", t && !this.collapsible);
3210
+ }
3211
+ }
3212
+ class so {
3213
+ attach(t, e) {
3214
+ }
3215
+ setVisible(t) {
3216
+ }
3217
+ destroy() {
3218
+ }
3219
+ }
3220
+ const ro = {
3221
+ "inframe-bottom-left": () => new Rt(),
3222
+ "sidebar-right": (n) => new oo(n),
3223
+ hidden: () => new so()
416
3224
  };
417
- new Set([b.CAMERA]);
418
- //#endregion
419
- //#region src/attributes/attributes.ts
420
- var j = class {
421
- registry;
422
- pendingCallbacks = [];
423
- constructor(e, t) {
424
- this.send = e, this.onMutationFailed = t;
425
- }
426
- handleMappingReceived(e) {
427
- p.debug("Initializing from server mapping", { attributeCount: e.attributes.length }), this.registry = new y(e.attributes, (e, t) => this.sendMutation(e, t), this.onMutationFailed);
428
- let t = this.registry.defaults();
429
- t.length > 0 && this.sendMutation(t, !0), this.replayPendingCallbacks();
430
- }
431
- handleMutationResult(e) {
432
- this.registry?.handleResults(e.results);
433
- }
434
- select(e, t) {
435
- this.sendMutation([{
436
- name: e,
437
- value: t
438
- }], !1);
439
- }
440
- getValues(e) {
441
- return this.registry?.get(e)?.getValues() ?? [];
442
- }
443
- getValue(e, t) {
444
- return this.registry?.get(e)?.getValue(t);
445
- }
446
- onChange(e, t) {
447
- if (this.registry === void 0) {
448
- this.pendingCallbacks.push({
449
- name: e,
450
- callback: t
451
- });
452
- return;
453
- }
454
- let n = this.registry.get(e);
455
- if (n === void 0) {
456
- p.warn(`onChange: attribute "${e}" not found — callback will not fire`);
457
- return;
458
- }
459
- n.onChange(t);
460
- }
461
- getAttribute(e) {
462
- return this.registry?.get(e);
463
- }
464
- get hasMapping() {
465
- return this.registry !== void 0;
466
- }
467
- sendMutation(e, t) {
468
- let n = {
469
- type: b.MUTATION,
470
- mutations: e,
471
- isInitial: t
472
- };
473
- this.send(n);
474
- }
475
- replayPendingCallbacks() {
476
- for (let { name: e, callback: t } of this.pendingCallbacks) {
477
- let n = this.registry?.get(e);
478
- n === void 0 && p.warn(`onChange: attribute "${e}" not found — buffered callback will not fire`), n?.onChange(t);
479
- }
480
- this.pendingCallbacks.length = 0;
481
- }
482
- }, M = class {
483
- commands = [];
484
- constructor(e) {
485
- this.send = e;
486
- }
487
- rotate(e) {
488
- return this.validateFiniteNumber(e, "rotate"), this.commands.push({
489
- action: "rotate",
490
- value: e
491
- }), this;
492
- }
493
- tilt(e) {
494
- return this.validateFiniteNumber(e, "tilt"), this.commands.push({
495
- action: "tilt",
496
- value: e
497
- }), this;
498
- }
499
- zoom(e) {
500
- return this.validateFiniteNumber(e, "zoom"), this.commands.push({
501
- action: "zoom",
502
- value: e
503
- }), this;
504
- }
505
- set() {
506
- if (this.commands.length === 0) throw t.invalidParameter("No camera commands to execute. Call rotate(), tilt(), or zoom() before set().");
507
- let e = {
508
- type: b.CAMERA,
509
- commands: this.commands
510
- };
511
- this.send(e), this.commands = [];
512
- }
513
- validateFiniteNumber(e, n) {
514
- if (!Number.isFinite(e)) throw t.invalidParameter(`${n}() requires a finite number, got ${String(e)}`);
515
- }
516
- reset() {
517
- let e = {
518
- type: b.CAMERA,
519
- commands: [{ action: "reset" }]
520
- };
521
- this.send(e);
522
- }
523
- }, N = class {
524
- state;
525
- changeCallbacks = [];
526
- constructor() {
527
- this.state = {
528
- rotation: 0,
529
- tilt: 0,
530
- zoom: 0
531
- };
532
- }
533
- get rotation() {
534
- return this.state.rotation;
535
- }
536
- get tilt() {
537
- return this.state.tilt;
538
- }
539
- get zoom() {
540
- return this.state.zoom;
541
- }
542
- onChange(e) {
543
- this.changeCallbacks.push(e);
544
- }
545
- handlePositionUpdate(e) {
546
- this.update(e.transforms);
547
- }
548
- update(e) {
549
- let t = !1;
550
- for (let n of e) switch (n.type) {
551
- case "rotation":
552
- this.state.rotation = n.value, t = !0;
553
- break;
554
- case "tilt":
555
- this.state.tilt = n.value, t = !0;
556
- break;
557
- case "zoom":
558
- this.state.zoom = n.value, t = !0;
559
- break;
560
- default: break;
561
- }
562
- if (t) {
563
- let e = {
564
- rotation: this.rotation,
565
- tilt: this.tilt,
566
- zoom: this.zoom
567
- };
568
- for (let t of this.changeCallbacks) t(e);
569
- }
570
- }
571
- }, P = class {
572
- developedCallbacks = /* @__PURE__ */ new Set();
573
- errorCallbacks = /* @__PURE__ */ new Set();
574
- developed = !1;
575
- failed = !1;
576
- photoData = null;
577
- constructor(e) {
578
- this.filename = e;
579
- }
580
- onDeveloped(e) {
581
- return this.developed && this.photoData !== null ? (e(this.photoData), this) : (this.developedCallbacks.add(e), this);
582
- }
583
- onError(e) {
584
- return this.errorCallbacks.add(e), this;
585
- }
586
- getFilename() {
587
- return this.filename;
588
- }
589
- develop(e) {
590
- this.developed || this.failed || (this.photoData = e, this.developed = !0, this.developedCallbacks.forEach((e) => {
591
- e(this.photoData);
592
- }), this.developedCallbacks.clear(), this.errorCallbacks.clear());
593
- }
594
- fail(e) {
595
- this.developed || this.failed || (this.failed = !0, this.errorCallbacks.forEach((t) => {
596
- t(e);
597
- }), this.developedCallbacks.clear(), this.errorCallbacks.clear());
598
- }
599
- }, F = class {
600
- validExtensions = [".png"];
601
- pendingPhotos = /* @__PURE__ */ new Map();
602
- constructor(e) {
603
- this.send = e;
604
- }
605
- take(e) {
606
- this.validateFilename(e), p.debug("Taking snapshot", { filename: e });
607
- let t = new P(e);
608
- return this.pendingPhotos.set(t.getFilename(), t), this.send({
609
- type: b.SNAPSHOT,
610
- filename: e
611
- }), t;
612
- }
613
- handleSnapshotResult(n) {
614
- let r = this.pendingPhotos.get(n.filename);
615
- if (r === void 0) {
616
- p.warn("No pending photo found for filename", {
617
- filename: n.filename,
618
- pendingFilenames: Array.from(this.pendingPhotos.keys())
619
- });
620
- return;
621
- }
622
- if (this.pendingPhotos.delete(n.filename), n.error !== void 0) {
623
- r.fail(new t(n.error, e.SNAPSHOT_FAILED));
624
- return;
625
- }
626
- n.data !== void 0 && r.develop({
627
- filename: n.filename,
628
- data: n.data
629
- });
630
- }
631
- validateFilename(n) {
632
- if (n === "" || typeof n != "string") throw new t("Filename must be a non-empty string", e.INVALID_PARAMETER);
633
- if (!this.validExtensions.some((e) => n.toLowerCase().endsWith(e))) throw new t(`Invalid filename: "${n}". Filename must end with one of: ${this.validExtensions.join(", ")}`, e.INVALID_PARAMETER);
634
- if (n.includes("..") || n.includes("/") || n.includes("\\")) throw new t("Filename cannot contain path separators or \"..\"", e.INVALID_PARAMETER);
635
- }
636
- }, I = class {
637
- send;
638
- constructor(e) {
639
- this.send = e;
640
- }
641
- sendInitialConfig(e) {
642
- let t = this.buildUIConfig(e), n = this.buildViewerConfig(t, e.camera);
643
- n !== void 0 && this.sendConfig(n);
644
- }
645
- buildUIConfig(e) {
646
- let t = this.extractUIValues(e);
647
- if (Object.values(t).some((e) => e !== void 0)) return {
648
- ...t.arEnabled !== void 0 && { arEnabled: t.arEnabled },
649
- ...t.fullscreenEnabled !== void 0 && { fullscreenEnabled: t.fullscreenEnabled },
650
- ...t.loadingIndicatorEnabled !== void 0 && { loadingIndicatorEnabled: t.loadingIndicatorEnabled }
651
- };
652
- }
653
- extractUIValues(e) {
654
- return {
655
- arEnabled: e.arEnabled ?? e.ui?.arEnabled,
656
- fullscreenEnabled: e.fullscreenEnabled ?? e.ui?.fullscreenEnabled,
657
- loadingIndicatorEnabled: e.loadingIndicatorEnabled ?? e.ui?.loadingIndicatorEnabled
658
- };
659
- }
660
- buildViewerConfig(e, t) {
661
- if (!(e === void 0 && t === void 0)) return {
662
- ...e !== void 0 && { ui: e },
663
- ...t !== void 0 && { camera: t }
664
- };
665
- }
666
- setArEnabled(e) {
667
- p.debug(`Setting AR button enabled: ${e}`), this.updateUIConfig({ arEnabled: e });
668
- }
669
- setFullscreenEnabled(e) {
670
- p.debug(`Setting fullscreen button enabled: ${e}`), this.updateUIConfig({ fullscreenEnabled: e });
671
- }
672
- setLoadingIndicatorEnabled(e) {
673
- p.debug(`Setting loading indicator enabled: ${e}`), this.updateUIConfig({ loadingIndicatorEnabled: e });
674
- }
675
- updateUIConfig(e) {
676
- p.debug("Updating UI configuration", e), Object.keys(e).length > 0 && this.sendConfig({ ui: e });
677
- }
678
- hideAllUI() {
679
- p.debug("Hiding all UI elements"), this.sendUIConfig({
680
- arEnabled: !1,
681
- fullscreenEnabled: !1,
682
- loadingIndicatorEnabled: !1
683
- });
684
- }
685
- showAllUI() {
686
- p.debug("Showing all UI elements"), this.sendUIConfig({
687
- arEnabled: !0,
688
- fullscreenEnabled: !0,
689
- loadingIndicatorEnabled: !0
690
- });
691
- }
692
- sendUIConfig(e) {
693
- this.sendConfig({ ui: e });
694
- }
695
- sendConfig(e) {
696
- let t = {
697
- type: b.CONFIG,
698
- config: e
699
- };
700
- p.debug("Sending config", t), this.send(t);
701
- }
702
- }, L = class {
703
- attributes;
704
- readyStateTracker;
705
- messenger;
706
- viewerService;
707
- cameraApi;
708
- cameraPositionApi;
709
- snapshotApi;
710
- options;
711
- iframe;
712
- readyCallbacks = [];
713
- mutationFailedCallbacks = [];
714
- constructor(e) {
715
- this.options = e, this.initializeLogging(), this.messenger = new A(() => this.iframe?.contentWindow ?? null);
716
- let t = (e) => {
717
- this.messenger.send(e);
718
- };
719
- this.attributes = new j(t, (e, t) => {
720
- for (let n of this.mutationFailedCallbacks) n({
721
- attribute: e,
722
- value: t
723
- });
724
- }), this.readyStateTracker = new g(() => {
725
- let e = this.readyCallbacks;
726
- this.readyCallbacks = [];
727
- for (let t of e) t(this);
728
- }), this.viewerService = new I(t), this.cameraApi = new M(t), this.cameraPositionApi = new N(), this.snapshotApi = new F(t), this.registerHandlers(), this.messenger.start(), this.createAndConnectIframe();
729
- }
730
- registerHandlers() {
731
- this.messenger.on(b.READY, (e) => {
732
- C(e) && (p.info("Server ready — flushing publisher queue"), this.messenger.markReady(), this.viewerService.sendInitialConfig(this.options));
733
- }), this.messenger.on(b.MUTATION, (e) => {
734
- w(e) && (this.attributes.handleMutationResult(e), this.readyStateTracker.handleMutationResult(e));
735
- }), this.messenger.on(b.SNAPSHOT, (e) => {
736
- T(e) && this.snapshotApi.handleSnapshotResult(e);
737
- }), this.messenger.on(b.CAMERA, (e) => {
738
- E(e) && this.cameraPositionApi.handlePositionUpdate(e);
739
- }), this.messenger.on(b.MAPPING, (e) => {
740
- D(e) && (this.attributes.handleMappingReceived(e), this.readyStateTracker.handleMappingReceived());
741
- });
742
- }
743
- initializeLogging() {
744
- this.options.debug === !0 && m(), p.info("Initializing Virtualdisplay client", {
745
- model: this.options.model,
746
- debug: this.options.debug,
747
- language: this.options.language
748
- });
749
- }
750
- createAndConnectIframe() {
751
- p.debug("Creating iframe for Virtualdisplay viewer"), this.iframe = new n(this.options).create(), p.debug("Iframe created, waiting for server READY");
752
- }
753
- select(e, t) {
754
- this.attributes.select(e, t);
755
- }
756
- getValues(e) {
757
- return this.attributes.getValues(e);
758
- }
759
- getValue(e, t) {
760
- return this.attributes.getValue(e, t);
761
- }
762
- onChange(e, t) {
763
- this.attributes.onChange(e, t);
764
- }
765
- getAttribute(e) {
766
- return this.attributes.getAttribute(e);
767
- }
768
- get isReady() {
769
- return this.readyStateTracker.isReady;
770
- }
771
- onReady(e) {
772
- if (this.readyStateTracker.isReady) {
773
- e(this);
774
- return;
775
- }
776
- this.readyCallbacks.push(e);
777
- }
778
- onMutationFailed(e) {
779
- this.mutationFailedCallbacks.push(e);
780
- }
781
- get viewer() {
782
- return this.viewerService;
783
- }
784
- get camera() {
785
- return this.cameraApi;
786
- }
787
- get cameraPosition() {
788
- return this.cameraPositionApi;
789
- }
790
- get snapshot() {
791
- return this.snapshotApi;
792
- }
793
- destroy() {
794
- p.info("Destroying Virtualdisplay client"), this.readyStateTracker.clear(), this.readyCallbacks = [], this.messenger.destroy(), this.iframe?.parentElement !== null && this.iframe?.parentElement !== void 0 && this.iframe.parentElement.removeChild(this.iframe), this.iframe = void 0, p.debug("Virtualdisplay client destroyed");
795
- }
3225
+ function ao(n, t) {
3226
+ return (ro[n ?? ""] ?? (() => new Rt()))(t);
3227
+ }
3228
+ const Z = _.fontFamily.sans.join(", "), W = `
3229
+ position: absolute;
3230
+ display: flex;
3231
+ z-index: 10;
3232
+ transition: inset-inline-end 0.18s ease;
3233
+ `, lo = `
3234
+ /* Override browser's default black fullscreen backdrop */
3235
+ .vd-container:fullscreen {
3236
+ background: oklch(100% 0 0);
3237
+ }
3238
+
3239
+ /* Layout slots — four corners */
3240
+ .vd-slot-top-right {
3241
+ ${W}
3242
+ inset-block-start: ${l[3]};
3243
+ inset-inline-end: ${l[3]};
3244
+ flex-direction: column;
3245
+ align-items: flex-end;
3246
+ gap: ${l[1.5]};
3247
+ }
3248
+
3249
+ .vd-slot-bottom-left {
3250
+ ${W}
3251
+ inset-block-end: ${l[3]};
3252
+ inset-inline-start: ${l[3]};
3253
+ flex-direction: column;
3254
+ align-items: flex-start;
3255
+ gap: ${l[1.5]};
3256
+ }
3257
+
3258
+ .vd-slot-bottom-right {
3259
+ ${W}
3260
+ inset-block-end: ${l[3]};
3261
+ inset-inline-end: ${l[3]};
3262
+ flex-direction: row;
3263
+ gap: ${l[1.5]};
3264
+ }
3265
+
3266
+ /* Component styles — no positioning */
3267
+ .vd-camera-controls {
3268
+ display: flex;
3269
+ flex-direction: column;
3270
+ gap: ${l[1]};
3271
+ background: color-mix(in oklch, ${m.white} 85%, transparent);
3272
+ backdrop-filter: blur(8px);
3273
+ border-radius: ${l[2]};
3274
+ padding: ${l[2]};
3275
+ }
3276
+
3277
+ .vd-zoom {
3278
+ display: flex;
3279
+ gap: ${l[0.5]};
3280
+ }
3281
+
3282
+ .vd-dpad {
3283
+ display: grid;
3284
+ grid-template-columns: repeat(3, ${l[9]});
3285
+ grid-template-rows: repeat(3, ${l[9]});
3286
+ gap: ${l[0.5]};
3287
+ }
3288
+
3289
+ .vd-dpad-up { grid-column: 2; grid-row: 1; }
3290
+ .vd-dpad-left { grid-column: 1; grid-row: 2; }
3291
+ .vd-dpad-center { grid-column: 2; grid-row: 2; }
3292
+ .vd-dpad-right { grid-column: 3; grid-row: 2; }
3293
+ .vd-dpad-down { grid-column: 2; grid-row: 3; }
3294
+
3295
+ .vd-attributes {
3296
+ display: flex;
3297
+ flex-direction: column;
3298
+ gap: ${l[3]};
3299
+ background: color-mix(in oklch, ${m.white} 85%, transparent);
3300
+ backdrop-filter: blur(8px);
3301
+ border: 1px solid color-mix(in oklch, ${m.neutral[900]} 8%, transparent);
3302
+ border-radius: ${l[3]};
3303
+ padding: ${l[3]};
3304
+ box-shadow: 0 ${l[1]} ${l[4]} color-mix(in oklch, ${m.neutral[900]} 6%, transparent);
3305
+ font-family: ${Z};
3306
+ }
3307
+
3308
+ .vd-attribute {
3309
+ display: flex;
3310
+ flex-direction: column;
3311
+ gap: ${l[1.5]};
3312
+ }
3313
+
3314
+ /* Micro-label: recedes so the controls lead. */
3315
+ .vd-attribute-label {
3316
+ font-size: ${_.fontSize.xs};
3317
+ font-weight: ${_.fontWeight.medium};
3318
+ color: ${m.neutral[500]};
3319
+ text-transform: uppercase;
3320
+ letter-spacing: 0.05em;
3321
+ font-family: ${Z};
3322
+ }
3323
+
3324
+ /* sidebar-right layout — a docked, full-height right panel */
3325
+ .vd-container { --vd-sidebar-width: 300px; }
3326
+
3327
+ .vd-container > iframe {
3328
+ width: 100%;
3329
+ height: 100%;
3330
+ border: none;
3331
+ display: block;
3332
+ }
3333
+
3334
+ /* Permanently docked panel (wide embeds, not collapsible): the viewer fills the
3335
+ remaining width instead of the panel floating over it. On narrow embeds the
3336
+ panel is a temporary tap-to-reveal overlay instead (vd-layout-sidebar-right
3337
+ without -permanent), where covering part of the model briefly is expected. */
3338
+ .vd-container.vd-layout-sidebar-permanent > iframe {
3339
+ width: calc(100% - var(--vd-sidebar-width));
3340
+ }
3341
+
3342
+ .vd-hidden { display: none !important; }
3343
+
3344
+ /* the "open configurator" toggle joins the shared bottom-right slot as a flex child;
3345
+ order pins it after AR/fullscreen regardless of DOM insertion order (MAPPING data,
3346
+ which mounts this button, always arrives before the toolbar mounts on ready). */
3347
+ .vd-configurator-toggle { order: 1; }
3348
+
3349
+ .vd-sidebar {
3350
+ position: absolute;
3351
+ inset-block: 0;
3352
+ inset-inline-end: 0;
3353
+ inline-size: var(--vd-sidebar-width);
3354
+ z-index: 11;
3355
+ display: flex;
3356
+ flex-direction: column;
3357
+ background: color-mix(in oklch, ${m.white} 90%, transparent);
3358
+ backdrop-filter: blur(10px);
3359
+ border-inline-start: 1px solid color-mix(in oklch, ${m.neutral[900]} 8%, transparent);
3360
+ box-shadow: -${l[1]} 0 ${l[4]} color-mix(in oklch, ${m.neutral[900]} 5%, transparent);
3361
+ }
3362
+
3363
+ @media (prefers-reduced-motion: no-preference) {
3364
+ .vd-sidebar { transition: transform 0.18s ease; }
3365
+ }
3366
+
3367
+ .vd-sidebar-collapsed { transform: translateX(100%); }
3368
+
3369
+ .vd-sidebar-header {
3370
+ display: flex;
3371
+ justify-content: flex-end;
3372
+ padding: ${l[1.5]} ${l[1.5]} 0;
3373
+ }
3374
+
3375
+ .vd-sidebar-body {
3376
+ flex: 1;
3377
+ overflow-y: auto;
3378
+ padding: ${l[4]};
3379
+ }
3380
+
3381
+ /* attributes inside the sidebar: flat and full-width — the panel provides the frosting */
3382
+ .vd-attributes.vd-attributes-sidebar {
3383
+ background: transparent;
3384
+ backdrop-filter: none;
3385
+ border: none;
3386
+ border-radius: 0;
3387
+ padding: 0;
3388
+ box-shadow: none;
3389
+ gap: ${l[5]};
3390
+ }
3391
+
3392
+ /* while the panel is open, keep the corner controls clear of it */
3393
+ .vd-layout-sidebar-right .vd-slot-top-right { inset-inline-end: calc(var(--vd-sidebar-width) + ${l[3]}); }
3394
+ .vd-layout-sidebar-right .vd-slot-bottom-right { inset-inline-end: calc(var(--vd-sidebar-width) + ${l[3]}); }
3395
+
3396
+ /* Price + CTA — sit under the attribute controls, separated by a hairline */
3397
+ .vd-commerce {
3398
+ display: flex;
3399
+ flex-direction: column;
3400
+ gap: ${l[2]};
3401
+ padding-block-start: ${l[3]};
3402
+ border-block-start: 1px solid color-mix(in oklch, ${m.neutral[900]} 8%, transparent);
3403
+ font-family: ${Z};
3404
+ }
3405
+
3406
+ .vd-commerce-price {
3407
+ display: flex;
3408
+ align-items: baseline;
3409
+ gap: ${l[1.5]};
3410
+ }
3411
+
3412
+ .vd-price {
3413
+ font-size: ${_.fontSize.lg};
3414
+ font-weight: ${_.fontWeight.semibold};
3415
+ color: ${m.neutral[900]};
3416
+ }
3417
+
3418
+ .vd-vat {
3419
+ font-size: ${_.fontSize.xs};
3420
+ color: ${m.neutral[500]};
3421
+ }
3422
+
3423
+ .vd-cta {
3424
+ inline-size: 100%;
3425
+ justify-content: center;
3426
+ }
3427
+
3428
+ /* Failed submit is the one place red belongs: a clear, recoverable error state. */
3429
+ .vd-commerce[data-submit="failed"] .vd-cta {
3430
+ background: ${m.semantic.error};
3431
+ border-color: ${m.semantic.error};
3432
+ color: ${m.white};
3433
+ }
3434
+
3435
+ `;
3436
+ function co() {
3437
+ if (xt(), document.getElementById("vd-styles") !== null)
3438
+ return;
3439
+ const n = document.createElement("style");
3440
+ n.id = "vd-styles", n.textContent = lo, document.head.appendChild(n);
3441
+ }
3442
+ function uo(n, t) {
3443
+ const e = document.createElement("a");
3444
+ e.href = `data:image/png;base64,${n}`, e.download = t, e.click();
3445
+ }
3446
+ class ho {
3447
+ options;
3448
+ settings = {};
3449
+ container = null;
3450
+ loading = null;
3451
+ slots = { topRight: null, bottomRight: null };
3452
+ screenshotBtn = null;
3453
+ actionBar = null;
3454
+ cameraControls = null;
3455
+ attributes = null;
3456
+ /** Rendered attribute groups by name, so a selectable snapshot can reach their controls. */
3457
+ groupHandles = /* @__PURE__ */ new Map();
3458
+ /** The latest selectable snapshot, re-applied when the controls re-render. */
3459
+ lastSelectable = null;
3460
+ layout = null;
3461
+ commerce = null;
3462
+ constructor(t) {
3463
+ this.options = t;
3464
+ }
3465
+ attach(t) {
3466
+ co(), this.container = t, this.loading = new Vi(t), this.slots.topRight = this.createSlot(t, "vd-slot-top-right"), this.slots.bottomRight = this.createSlot(t, "vd-slot-bottom-right");
3467
+ }
3468
+ onReady() {
3469
+ this.loading?.hide(), this.loading = null, this.mountToolbar(), this.cameraControls !== null && (this.cameraControls.element.style.display = "flex"), this.layout?.setVisible(!0);
3470
+ }
3471
+ mountToolbar() {
3472
+ if (this.actionBar !== null || this.slots.bottomRight === null || this.container === null)
3473
+ return;
3474
+ const t = typeof this.container.requestFullscreen == "function";
3475
+ this.actionBar = new Hi({
3476
+ onAr: this.options.onAr,
3477
+ onFullscreen: t ? () => {
3478
+ this.toggleFullscreen();
3479
+ } : void 0,
3480
+ onAnnotationsToggle: this.options.onAnnotationsToggle
3481
+ }), this.actionBar.applySettings(this.settings), this.slots.bottomRight.appendChild(this.actionBar.element), this.syncScreenshotButton(), this.placeScreenshotButton();
3482
+ }
3483
+ toggleFullscreen() {
3484
+ this.container !== null && (document.fullscreenElement !== null ? document.exitFullscreen() : (this.options.onFullscreenEnter?.(), this.container.requestFullscreen()));
3485
+ }
3486
+ updateProgress(t, e) {
3487
+ this.loading?.updateProgress(t, e);
3488
+ }
3489
+ showError(t) {
3490
+ this.loading?.showError(t);
3491
+ }
3492
+ applyViewerSettings(t) {
3493
+ this.settings = t, t.language !== void 0 && St(t.language, { reload: !1 }), this.syncScreenshotButton(), this.actionBar?.applySettings(t), this.cameraControls !== null && (this.cameraControls.element.style.display = t.cameraControls === !0 ? "flex" : "none"), this.placeScreenshotButton();
3494
+ }
3495
+ renderAttributeControls(t, e) {
3496
+ if (!(this.container === null || this.slots.topRight === null || this.slots.bottomRight === null) && (this.cameraControls?.element.remove(), this.cameraControls = null, this.teardownConfigurator(), this.settings.cameraControls === !0 && (this.cameraControls = new P(
3497
+ this.options.cameraApi,
3498
+ this.options.cameraPositionApi
3499
+ ), this.cameraControls.element.style.display = "none", this.slots.topRight.appendChild(this.cameraControls.element)), this.placeScreenshotButton(), e.length !== 0)) {
3500
+ this.attributes = document.createElement("div"), this.attributes.className = "vd-attributes", this.groupHandles = /* @__PURE__ */ new Map();
3501
+ for (const i of e) {
3502
+ const o = Ki(
3503
+ i,
3504
+ t,
3505
+ this.options.eventBus,
3506
+ this.options.onSelect,
3507
+ this.options.onSelectTransient,
3508
+ this.options.onSubscribe
3509
+ );
3510
+ this.groupHandles.set(i.name, o), this.attributes.appendChild(o.element);
3511
+ }
3512
+ this.commerce = new eo(this.options.configuration), this.attributes.appendChild(this.commerce.element), this.layout = ao(
3513
+ this.settings.configuratorLayout,
3514
+ this.slots.bottomRight
3515
+ ), this.layout.attach(this.container, this.attributes), this.lastSelectable !== null && this.applySelectable(this.lastSelectable);
3516
+ }
3517
+ }
3518
+ /**
3519
+ * Grey out what the viewer reports as not pickable: per attribute in the
3520
+ * snapshot only the listed values stay enabled; attributes the snapshot
3521
+ * does not name are unrestricted. The interface renders, it never knows
3522
+ * the exclusion rules (kiesbaarheid live, binding-studio task 17).
3523
+ */
3524
+ applySelectable(t) {
3525
+ this.lastSelectable = t;
3526
+ for (const [e, i] of this.groupHandles) {
3527
+ const o = t.find((s) => s.name === e);
3528
+ i.setSelectable(o?.selectable ?? null), i.setHidden(o?.hidden === !0);
3529
+ }
3530
+ }
3531
+ get viewerSettings() {
3532
+ return this.settings;
3533
+ }
3534
+ /** Tear down the configurator panel: price + CTA, attribute controls, layout. */
3535
+ teardownConfigurator() {
3536
+ this.commerce?.destroy(), this.commerce = null, this.attributes?.remove(), this.attributes = null, this.groupHandles = /* @__PURE__ */ new Map(), this.layout?.destroy(), this.layout = null;
3537
+ }
3538
+ destroy() {
3539
+ this.loading?.destroy(), this.screenshotBtn?.element.remove(), this.actionBar?.destroy(), this.actionBar?.element.remove(), this.cameraControls?.element.remove(), this.teardownConfigurator();
3540
+ for (const t of Object.values(this.slots))
3541
+ t?.remove();
3542
+ this.container = null;
3543
+ }
3544
+ createSlot(t, e) {
3545
+ const i = document.createElement("div");
3546
+ return i.className = e, t.appendChild(i), i;
3547
+ }
3548
+ syncScreenshotButton() {
3549
+ const t = this.settings.screenshot === !0;
3550
+ t && this.screenshotBtn === null && (this.screenshotBtn = new v({
3551
+ variant: "outline",
3552
+ size: "icon",
3553
+ label: li(),
3554
+ icon: g.screenshot(),
3555
+ onClick: () => {
3556
+ this.options.snapshotApi.take("snapshot.png").onDeveloped(({ data: e, filename: i }) => {
3557
+ uo(e, i);
3558
+ });
3559
+ }
3560
+ })), !t && this.screenshotBtn !== null && (this.screenshotBtn.element.remove(), this.screenshotBtn = null);
3561
+ }
3562
+ placeScreenshotButton() {
3563
+ this.screenshotBtn === null || this.slots.topRight === null || (this.cameraControls !== null ? this.cameraControls.element.appendChild(this.screenshotBtn.element) : this.slots.topRight.appendChild(this.screenshotBtn.element));
3564
+ }
3565
+ }
3566
+ const po = {};
3567
+ class mo {
3568
+ defaultServer = "https://server.virtualdisplay.io";
3569
+ options;
3570
+ serverHost;
3571
+ extraParams;
3572
+ constructor(t, e, i) {
3573
+ this.options = t, this.serverHost = e, this.extraParams = i;
3574
+ }
3575
+ /**
3576
+ * Create container and iframe, attach to parent element
3577
+ */
3578
+ create() {
3579
+ const t = this.createContainer(), e = this.createIframe();
3580
+ return t.appendChild(e), this.attachToParent(t), { container: t, iframe: e };
3581
+ }
3582
+ createContainer() {
3583
+ const t = document.createElement("div");
3584
+ return t.className = "vd-container", Object.assign(t.style, {
3585
+ position: "relative",
3586
+ width: "100%",
3587
+ height: "100%",
3588
+ overflow: "hidden"
3589
+ }), t;
3590
+ }
3591
+ createIframe() {
3592
+ const t = document.createElement("iframe");
3593
+ return t.src = this.buildUrl(), t.setAttribute("allow", "xr-spatial-tracking; fullscreen"), t;
3594
+ }
3595
+ attachToParent(t) {
3596
+ this.getParentElement().appendChild(t);
3597
+ }
3598
+ buildUrl() {
3599
+ const t = this.getServerUrl(), e = new URL(t);
3600
+ this.setAddressParams(e), this.options.language !== void 0 && e.searchParams.set("language", this.options.language), this.options.session !== void 0 && e.searchParams.set("session", this.options.session), this.options.debug === !0 && e.searchParams.set("debug", "true"), this.options.traceparent !== void 0 && e.searchParams.set("traceparent", this.options.traceparent);
3601
+ for (const [i, o] of Object.entries(this.extraParams ?? {}))
3602
+ e.searchParams.set(i, o);
3603
+ return e.toString();
3604
+ }
3605
+ /**
3606
+ * Put the address form on the URL. `app` names an app row; the
3607
+ * `license`/`model` pair resolves to the model's default app. When both are
3608
+ * given, `app` wins and the pair is ignored with a warning, so adding `app`
3609
+ * to an existing integration is additive rather than a swap.
3610
+ */
3611
+ setAddressParams(t) {
3612
+ if (this.options.app === void 0) {
3613
+ t.searchParams.set("license", this.options.license), t.searchParams.set("model", this.options.model);
3614
+ return;
3615
+ }
3616
+ t.searchParams.set("app", this.options.app), (this.options.license !== void 0 || this.options.model !== void 0) && w.warn("Both `app` and `license`/`model` were supplied: `app` wins, the pair was ignored");
3617
+ }
3618
+ getServerUrl() {
3619
+ if (this.serverHost !== void 0)
3620
+ return this.serverHost;
3621
+ if (po?.VITE_VIRTUALDISPLAY_SERVER_HOST === void 0)
3622
+ return this.defaultServer;
3623
+ }
3624
+ getParentElement() {
3625
+ if (typeof this.options.parent != "string")
3626
+ return this.options.parent;
3627
+ const t = document.querySelector(this.options.parent);
3628
+ if (t === null)
3629
+ throw y.parentNotFound(this.options.parent);
3630
+ return t;
3631
+ }
3632
+ }
3633
+ function fo(n) {
3634
+ const { messenger: t } = n;
3635
+ t.on(c.HANDSHAKE, (e) => {
3636
+ Nt(e) && (w.info("Server handshake received, flushing publisher queue"), t.markReady());
3637
+ }), t.on(c.SESSION, (e) => {
3638
+ zt(e) && n.onSession(e.sessionId);
3639
+ }), t.on(c.MUTATION, (e) => {
3640
+ Ft(e) && (n.attributes.handleMutationResult(e), n.configuration.handleSelectionChanged(n.attributes.getSelection()));
3641
+ }), t.on(c.SNAPSHOT, (e) => {
3642
+ $t(e) && n.snapshot.handleSnapshotResult(e);
3643
+ }), t.on(c.CAMERA, (e) => {
3644
+ Dt(e) && n.cameraPosition.handlePositionUpdate(e);
3645
+ }), t.on(c.LOADING_INDICATOR, (e) => {
3646
+ Ut(e) && (e.error !== void 0 ? n.ui.showError(e.error) : n.ui.updateProgress(e.progress, e.phase));
3647
+ }), t.on(c.CONFIG, (e) => {
3648
+ jt(e) && n.applyConfig(e);
3649
+ }), t.on(c.MAPPING, (e) => {
3650
+ Bt(e) && (n.attributes.handleMappingReceived(e), n.configuration.handleSelectionChanged(n.attributes.getSelection()), n.ui.renderAttributeControls(n.attributes, e.attributes), n.readyStateTracker.handleMappingReceived());
3651
+ }), go(n);
3652
+ }
3653
+ function go(n) {
3654
+ const { messenger: t } = n;
3655
+ t.on(c.FOCUS, (e) => {
3656
+ qt(e) && n.focus.handleStateReceived(e);
3657
+ }), t.on(c.PRICING, (e) => {
3658
+ Gt(e) && n.configuration.handlePricingReceived(e.pricing);
3659
+ }), t.on(c.SYNC, (e) => {
3660
+ Vt(e) && n.onSync(e.connected);
3661
+ }), t.on(c.SELECTABLE, (e) => {
3662
+ Ht(e) && (n.selectable.handleSnapshot(e), n.ui.applySelectable(e.attributes));
3663
+ }), t.on(c.APP_RESOLVED, (e) => {
3664
+ Pt(e) && n.onAppResolved(e);
3665
+ });
3666
+ }
3667
+ class bo {
3668
+ ready = !1;
3669
+ onReady;
3670
+ constructor(t) {
3671
+ this.onReady = t;
3672
+ }
3673
+ handleMappingReceived() {
3674
+ this.ready || (this.ready = !0, w.info("Mapping received from server, client is now ready"), this.onReady?.());
3675
+ }
3676
+ get isReady() {
3677
+ return this.ready;
3678
+ }
3679
+ clear() {
3680
+ this.ready = !1;
3681
+ }
3682
+ }
3683
+ function vo(n) {
3684
+ if (n === void 0)
3685
+ return;
3686
+ const { annotations: t, ...e } = n;
3687
+ return t === void 0 ? e : t === !0 ? { ...e, annotations: "show" } : t === !1 ? { ...e, annotations: "hide" } : { ...e, annotations: t };
3688
+ }
3689
+ class yo {
3690
+ attributes;
3691
+ selectableState;
3692
+ readyStateTracker;
3693
+ messenger;
3694
+ cameraApi;
3695
+ cameraPositionApi;
3696
+ focusApi;
3697
+ configurationApi;
3698
+ snapshotApi;
3699
+ annotationsApi;
3700
+ eventBus;
3701
+ ui;
3702
+ options;
3703
+ serverHost;
3704
+ iframeParams;
3705
+ // The composed AR flow, or undefined in a build that composes none.
3706
+ arActivation;
3707
+ // The composed sync tracker, or undefined in a build that composes none.
3708
+ syncTracker;
3709
+ iframe;
3710
+ container;
3711
+ readyCallbacks = [];
3712
+ mutationFailedCallbacks = [];
3713
+ // The viewer's tracking session id, once the server announces it (SESSION).
3714
+ viewerSessionId;
3715
+ // Which app the viewer resolved, once reported (APP_RESOLVED).
3716
+ resolvedApp;
3717
+ appResolvedCallbacks = [];
3718
+ /** @internal */
3719
+ constructor(t, e) {
3720
+ this.options = t;
3721
+ const i = e ?? {};
3722
+ this.serverHost = i.serverHost, this.iframeParams = i.iframeParams, this.initializeLogging(), this.messenger = new Zt(() => this.iframe?.contentWindow ?? null, {
3723
+ onHandlerError: (s, r) => w.error(`Error handling ${s.type}`, r),
3724
+ ...i.messengerOptions
3725
+ });
3726
+ const o = (s) => {
3727
+ this.messenger.send(s);
3728
+ };
3729
+ this.attributes = new fe(o, L, (s, r) => {
3730
+ this.notifyMutationFailed(s, r);
3731
+ }), this.selectableState = new ge(), this.snapshotApi = new Ce(o), this.annotationsApi = new Qt(o, L), this.cameraApi = new be(o), this.cameraPositionApi = new ve(), this.focusApi = new _e(o, (s) => {
3732
+ this.dispatchFocusChange(s);
3733
+ }, L), this.configurationApi = new Se(
3734
+ (s) => {
3735
+ this.dispatchConfigurationChange(s);
3736
+ },
3737
+ (s) => {
3738
+ this.dispatchConfigurationSubmit(s);
3739
+ }
3740
+ ), this.eventBus = new Ae(), this.arActivation = i.createArActivation?.(o), this.syncTracker = i.createSyncTracker?.(), this.readyStateTracker = new bo(() => {
3741
+ this.notifyReady();
3742
+ }), this.ui = this.createStandardInterface(), this.wireIncomingMessages(), i.registerHandlers?.(this.messenger, o), this.messenger.start(), this.createAndConnectIframe();
3743
+ }
3744
+ // The configurator UI, every callback bridging it to the facade that owns the action.
3745
+ createStandardInterface() {
3746
+ const t = this.arActivation;
3747
+ return new ho({
3748
+ snapshotApi: this.snapshotApi,
3749
+ cameraApi: this.cameraApi,
3750
+ cameraPositionApi: this.cameraPositionApi,
3751
+ eventBus: this.eventBus,
3752
+ configuration: this.configurationApi,
3753
+ onSelect: (e, i) => this.attributes.select(e, i),
3754
+ onSelectTransient: (e, i) => this.attributes.selectTransient(e, i),
3755
+ onSubscribe: (e, i) => this.attributes.onChange(e, i),
3756
+ ...t !== void 0 && {
3757
+ onAr: () => {
3758
+ t.activate();
3759
+ }
3760
+ },
3761
+ onFullscreenEnter: () => {
3762
+ this.trackFullscreen();
3763
+ },
3764
+ onAnnotationsToggle: (e) => {
3765
+ this.sendControlAnnotationToggle(e);
3766
+ }
3767
+ });
3768
+ }
3769
+ // The visible toggle composes its message here because it is the one sender
3770
+ // allowed to stamp `origin: 'control'`: the viewer only counts
3771
+ // control-originated flips as Shopper use. The public `client.annotations`
3772
+ // facade stays origin-less on purpose. One send per member instance once
3773
+ // composition lands; today the host is the only one.
3774
+ sendControlAnnotationToggle(t) {
3775
+ this.messenger.send({
3776
+ type: c.ANNOTATION_TOGGLE,
3777
+ instance: L,
3778
+ enabled: t,
3779
+ origin: "control"
3780
+ });
3781
+ }
3782
+ // Wire the inbound viewer protocol to the facades.
3783
+ wireIncomingMessages() {
3784
+ fo({
3785
+ messenger: this.messenger,
3786
+ attributes: this.attributes,
3787
+ selectable: this.selectableState,
3788
+ configuration: this.configurationApi,
3789
+ snapshot: this.snapshotApi,
3790
+ cameraPosition: this.cameraPositionApi,
3791
+ focus: this.focusApi,
3792
+ ui: this.ui,
3793
+ readyStateTracker: this.readyStateTracker,
3794
+ applyConfig: (t) => {
3795
+ this.handleConfig(t);
3796
+ },
3797
+ onSession: (t) => {
3798
+ this.handleSession(t);
3799
+ },
3800
+ onSync: (t) => {
3801
+ this.syncTracker?.handleSync(t);
3802
+ },
3803
+ onAppResolved: (t) => {
3804
+ this.handleAppResolved({
3805
+ appType: t.appType,
3806
+ name: t.name,
3807
+ ...t.slug !== void 0 && { slug: t.slug },
3808
+ ...t.submit !== void 0 && { submit: t.submit }
3809
+ });
3810
+ }
3811
+ });
3812
+ }
3813
+ // Store the resolved app and notify every registered `onAppResolved` callback.
3814
+ handleAppResolved(t) {
3815
+ this.resolvedApp = t;
3816
+ for (const e of this.appResolvedCallbacks)
3817
+ e(t);
3818
+ }
3819
+ // Store the tracking session id and hand it to the AR flow, which waits on it for the QR.
3820
+ handleSession(t) {
3821
+ this.viewerSessionId = t, this.arActivation?.onSession(t);
3822
+ }
3823
+ // Forward a failed mutation to every registered `onMutationFailed` callback.
3824
+ notifyMutationFailed(t, e) {
3825
+ for (const i of this.mutationFailedCallbacks)
3826
+ i({ attribute: t, value: e });
3827
+ }
3828
+ // Flush the queued `onReady` callbacks once, then let the standard interface know.
3829
+ notifyReady() {
3830
+ const t = this.readyCallbacks;
3831
+ this.readyCallbacks = [];
3832
+ for (const e of t)
3833
+ e(this);
3834
+ this.ui.onReady();
3835
+ }
3836
+ // Dispatch a bubbling `vd:focus-change` event on the container so a page can
3837
+ // react to focus without registering an `onChange` callback.
3838
+ dispatchFocusChange(t) {
3839
+ this.container?.dispatchEvent(
3840
+ new CustomEvent(T.FOCUS_CHANGE, { detail: t, bubbles: !0 })
3841
+ );
3842
+ }
3843
+ // Dispatch a bubbling `vd:configuration-change` event on the container so a
3844
+ // page or tag manager can react to price changes without a client callback.
3845
+ dispatchConfigurationChange(t) {
3846
+ this.container?.dispatchEvent(
3847
+ new CustomEvent(T.CONFIGURATION_CHANGE, { detail: t ?? null, bubbles: !0 })
3848
+ );
3849
+ }
3850
+ // Dispatch a bubbling `vd:configuration-submit` event when the CTA is used.
3851
+ dispatchConfigurationSubmit(t) {
3852
+ this.container?.dispatchEvent(
3853
+ new CustomEvent(T.CONFIGURATION_SUBMIT, { detail: t, bubbles: !0 })
3854
+ );
3855
+ }
3856
+ handleConfig(t) {
3857
+ const e = t.viewerSettings, i = vo(this.options.viewerSettings);
3858
+ if (e !== void 0 || i !== void 0) {
3859
+ const o = i !== void 0 ? { ...e ?? {}, ...i } : e;
3860
+ if (i !== void 0 && e !== void 0) {
3861
+ const s = Object.entries(i).filter(([r, a]) => e[r] !== a).map(
3862
+ ([r, a]) => `${r} (metadata: ${e[r] ?? "undefined"}, override: ${a})`
3863
+ );
3864
+ s.length > 0 && w.warn(`Viewer settings override active: ${s.join(", ")}`);
3865
+ }
3866
+ this.ui.applyViewerSettings(o), this.echoAnnotationsDifference(e?.annotations, o.annotations);
3867
+ }
3868
+ this.cameraPositionApi.applyLimits(t.cameraLimits);
3869
+ }
3870
+ // The viewer already applied the announced mode itself, so speaking up when
3871
+ // the merged mode matches would toggle it back and forth for nothing. A boot
3872
+ // without overrides therefore sends no toggle at all.
3873
+ // Tell the viewer about the annotation mode only when the merged mode differs
3874
+ // from the one it announced.
3875
+ echoAnnotationsDifference(t, e) {
3876
+ e === void 0 || e === t || this.annotationsApi.setEnabled(e === "show" || e === "toggle-shown");
3877
+ }
3878
+ // Tell the viewer the user entered fullscreen, so it records the engagement.
3879
+ trackFullscreen() {
3880
+ this.messenger.send({ type: c.FULLSCREEN });
3881
+ }
3882
+ initializeLogging() {
3883
+ this.options.debug === !0 && me(), w.info("Initializing Virtualdisplay client", {
3884
+ ...this.options.app === void 0 ? { model: this.options.model } : { app: this.options.app }
3885
+ });
3886
+ }
3887
+ createAndConnectIframe() {
3888
+ const e = new mo(this.options, this.serverHost, this.iframeParams).create();
3889
+ this.container = e.container, this.iframe = e.iframe, this.eventBus.attach(this.container), this.ui.attach(this.container);
3890
+ try {
3891
+ const i = new URL(this.iframe.src).origin;
3892
+ this.messenger.setTargetOrigin(i);
3893
+ } catch {
3894
+ }
3895
+ }
3896
+ select(t, e) {
3897
+ this.attributes.select(t, e);
3898
+ }
3899
+ getValues(t) {
3900
+ return this.attributes.getValues(t);
3901
+ }
3902
+ getValue(t, e) {
3903
+ return this.attributes.getValue(t, e);
3904
+ }
3905
+ onChange(t, e) {
3906
+ this.attributes.onChange(t, e);
3907
+ }
3908
+ getAttribute(t) {
3909
+ return this.attributes.getAttribute(t);
3910
+ }
3911
+ get isReady() {
3912
+ return this.readyStateTracker.isReady;
3913
+ }
3914
+ onReady(t) {
3915
+ if (this.readyStateTracker.isReady) {
3916
+ t(this);
3917
+ return;
3918
+ }
3919
+ this.readyCallbacks.push(t);
3920
+ }
3921
+ onMutationFailed(t) {
3922
+ this.mutationFailedCallbacks.push(t);
3923
+ }
3924
+ /**
3925
+ * Subscribe to which values can be picked right now, per attribute. Fires
3926
+ * immediately when a snapshot already arrived. A model with no exclusions
3927
+ * never sends one, so this never fires and everything stays selectable.
3928
+ */
3929
+ onSelectableChange(t) {
3930
+ this.selectableState.onChange(t);
3931
+ }
3932
+ /**
3933
+ * Subscribe to which app this viewer resolved (type, name, public submit
3934
+ * settings). Reported once by the viewer after its resolve; fires immediately
3935
+ * when the report already arrived, so registration order does not matter.
3936
+ */
3937
+ onAppResolved(t) {
3938
+ this.appResolvedCallbacks.push(t), this.resolvedApp !== void 0 && t(this.resolvedApp);
3939
+ }
3940
+ /** The selectable values for one attribute right now, or null when unrestricted. */
3941
+ getSelectable(t) {
3942
+ return this.selectableState.selectableFor(t);
3943
+ }
3944
+ // Exists for a host page that hides the built-in button and owns its own AR
3945
+ // affordance: without this it would have no way into the same flow.
3946
+ /**
3947
+ * Start the AR flow directly, as the built-in AR button would. Does nothing
3948
+ * when `viewerSettings.ar` is off or the build composes no AR flow.
3949
+ */
3950
+ activateAr() {
3951
+ this.arActivation?.activate();
3952
+ }
3953
+ on(t, e) {
3954
+ this.eventBus.on(t, e);
3955
+ }
3956
+ get annotations() {
3957
+ return this.annotationsApi;
3958
+ }
3959
+ get camera() {
3960
+ return this.cameraApi;
3961
+ }
3962
+ get focus() {
3963
+ return this.focusApi;
3964
+ }
3965
+ get configuration() {
3966
+ return this.configurationApi;
3967
+ }
3968
+ get cameraPosition() {
3969
+ return this.cameraPositionApi;
3970
+ }
3971
+ get snapshot() {
3972
+ return this.snapshotApi;
3973
+ }
3974
+ /** The viewer's tracking session id, for a host page to correlate its own actions. */
3975
+ get sessionId() {
3976
+ return this.viewerSessionId;
3977
+ }
3978
+ get viewerSettings() {
3979
+ return this.ui.viewerSettings;
3980
+ }
3981
+ destroy() {
3982
+ this.readyStateTracker.clear(), this.readyCallbacks = [], this.mutationFailedCallbacks.length = 0, this.snapshotApi.clear(), this.messenger.destroy(), this.arActivation?.destroy(), this.ui.destroy(), this.container?.parentElement !== null && this.container?.parentElement !== void 0 && this.container.parentElement.removeChild(this.container), this.container = void 0, this.iframe = void 0;
3983
+ }
3984
+ }
3985
+ const wo = "ar.virtualdisplay.io";
3986
+ function So(n) {
3987
+ const t = new URL(`https://${wo}/go`);
3988
+ return n.sessionId !== void 0 ? t.searchParams.set("session", n.sessionId) : n.app !== void 0 ? t.searchParams.set("app", n.app) : n.license !== void 0 && n.model !== void 0 && (t.searchParams.set("license", n.license), t.searchParams.set("model", n.model)), t.toString();
3989
+ }
3990
+ const _o = 1500;
3991
+ class ko {
3992
+ options;
3993
+ qrModal = new Pi();
3994
+ /** The viewer's tracking session id, once the engine announces it (SESSION). */
3995
+ sessionId;
3996
+ /** Callbacks waiting on the SESSION message, resolved (or timed out) exactly once each. */
3997
+ waiters = [];
3998
+ /** Session-wait timers still pending, cleared on destroy so no wait outlives the client. */
3999
+ pendingTimers = /* @__PURE__ */ new Set();
4000
+ /** Set on destroy: an in-flight hand-off must not re-mount the modal or send QR_SHOWN. */
4001
+ destroyed = !1;
4002
+ constructor(t) {
4003
+ this.options = t;
4004
+ }
4005
+ /** Store the engine-announced tracking session id and resolve anyone waiting on it. */
4006
+ onSession(t) {
4007
+ this.sessionId = t;
4008
+ const e = this.waiters;
4009
+ this.waiters = [];
4010
+ for (const i of e)
4011
+ i(t);
4012
+ }
4013
+ /** The AR button handler: mobile activates AR in the engine, desktop shows the hand-off QR. */
4014
+ activate() {
4015
+ if (he()) {
4016
+ this.options.send({ type: c.AR_ACTIVATE });
4017
+ return;
4018
+ }
4019
+ this.showQrCode();
4020
+ }
4021
+ destroy() {
4022
+ this.destroyed = !0;
4023
+ for (const t of this.pendingTimers)
4024
+ clearTimeout(t);
4025
+ this.pendingTimers.clear(), this.waiters = [], this.qrModal.hide();
4026
+ }
4027
+ /**
4028
+ * Desktop hand-off: open the modal at once in its loading state (the click
4029
+ * must respond immediately, even when the session id is still resolving),
4030
+ * then swap the AR landing QR in as soon as the URL is known. Guarded after
4031
+ * each await: a client torn down during the session wait must not update
4032
+ * the modal or send a stray QR_SHOWN through a dead messenger.
4033
+ */
4034
+ async showQrCode() {
4035
+ this.qrModal.show();
4036
+ const t = await this.waitForSessionId();
4037
+ if (this.destroyed)
4038
+ return;
4039
+ const e = So({
4040
+ sessionId: t,
4041
+ app: this.options.app,
4042
+ license: this.options.license,
4043
+ model: this.options.model
4044
+ }), i = await this.qrModal.update(e);
4045
+ if (this.destroyed) {
4046
+ this.qrModal.hide();
4047
+ return;
4048
+ }
4049
+ i && this.options.send({ type: c.QR_SHOWN });
4050
+ }
4051
+ /**
4052
+ * Resolve once the SESSION message arrives, or `undefined` after
4053
+ * {@link SESSION_WAIT_TIMEOUT_MS} if it never does (e.g. tracking blocked by
4054
+ * an ad blocker). The QR then falls back to a working sessionless landing URL
4055
+ * rather than encoding a wrong one.
4056
+ */
4057
+ waitForSessionId() {
4058
+ return this.sessionId !== void 0 ? Promise.resolve(this.sessionId) : new Promise((t) => {
4059
+ const e = setTimeout(() => {
4060
+ this.pendingTimers.delete(e), t(void 0);
4061
+ }, _o);
4062
+ this.pendingTimers.add(e), this.waiters.push((i) => {
4063
+ this.pendingTimers.delete(e), clearTimeout(e), t(i);
4064
+ });
4065
+ });
4066
+ }
4067
+ }
4068
+ class Co {
4069
+ callbacks = [];
4070
+ lastKnown;
4071
+ handleSync(t) {
4072
+ this.lastKnown = t;
4073
+ for (const e of this.callbacks)
4074
+ e(t);
4075
+ }
4076
+ onChange(t) {
4077
+ this.callbacks.push(t), this.lastKnown !== void 0 && t(this.lastKnown);
4078
+ }
4079
+ }
4080
+ class xo extends yo {
4081
+ syncStatusTracker;
4082
+ constructor(t) {
4083
+ const e = new Co();
4084
+ super(t, {
4085
+ createArActivation: (i) => new ko({
4086
+ app: t.app,
4087
+ license: t.license,
4088
+ model: t.model,
4089
+ send: i
4090
+ }),
4091
+ createSyncTracker: () => e
4092
+ }), this.syncStatusTracker = e;
4093
+ }
4094
+ /** Subscribe to the live sync channel's connection state (ar-session-handoff). Latest-wins, no buffering. */
4095
+ onSyncChange(t) {
4096
+ this.syncStatusTracker.onChange(t);
4097
+ }
4098
+ }
4099
+ export {
4100
+ Ot as AVAILABILITY,
4101
+ Qt as Annotations,
4102
+ Kt as Attribute,
4103
+ Yt as AttributeValue,
4104
+ be as Camera,
4105
+ ve as CameraPosition,
4106
+ Se as Configuration,
4107
+ A as ERROR_CODES,
4108
+ _e as Focus,
4109
+ ke as Photo,
4110
+ Ce as Snapshot,
4111
+ T as VD_EVENTS,
4112
+ xo as VirtualdisplayClient,
4113
+ y as VirtualdisplayError,
4114
+ me as enableDebugMode,
4115
+ Ao as isDebugMode,
4116
+ w as logger
796
4117
  };
797
- //#endregion
798
- export { v as Attribute, _ as AttributeValue, M as Camera, e as ERROR_CODES, P as Photo, F as Snapshot, L as VirtualdisplayClient, t as VirtualdisplayError, I as VirtualdisplayViewerService, m as enableDebugMode, h as isDebugMode, p as logger };