@merittdev/horus-lens 0.0.1

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 (42) hide show
  1. package/dist/browser.cjs +31594 -0
  2. package/dist/browser.cjs.map +1 -0
  3. package/dist/browser.d.cts +125 -0
  4. package/dist/browser.d.ts +125 -0
  5. package/dist/browser.js +24 -0
  6. package/dist/browser.js.map +1 -0
  7. package/dist/chunk-DU7HNRF4.js +1644 -0
  8. package/dist/chunk-DU7HNRF4.js.map +1 -0
  9. package/dist/chunk-DWOH2ENF.js +17613 -0
  10. package/dist/chunk-DWOH2ENF.js.map +1 -0
  11. package/dist/chunk-PZ5AY32C.js +10 -0
  12. package/dist/chunk-PZ5AY32C.js.map +1 -0
  13. package/dist/chunk-S6S3ZZQA.js +17225 -0
  14. package/dist/chunk-S6S3ZZQA.js.map +1 -0
  15. package/dist/dashboard.cjs +19774 -0
  16. package/dist/dashboard.cjs.map +1 -0
  17. package/dist/dashboard.css +2233 -0
  18. package/dist/dashboard.d.cts +707 -0
  19. package/dist/dashboard.d.ts +707 -0
  20. package/dist/dashboard.js +2237 -0
  21. package/dist/dashboard.js.map +1 -0
  22. package/dist/index.cjs +30040 -0
  23. package/dist/index.cjs.map +1 -0
  24. package/dist/index.d-BA540Fae.d.ts +80 -0
  25. package/dist/index.d-Bzgtl7-B.d.cts +161 -0
  26. package/dist/index.d-Bzgtl7-B.d.ts +161 -0
  27. package/dist/index.d-DOCLVJGS.d.cts +80 -0
  28. package/dist/index.d.cts +906 -0
  29. package/dist/index.d.ts +906 -0
  30. package/dist/index.js +89 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/lens.min.js +770 -0
  33. package/dist/lens.min.js.map +1 -0
  34. package/dist/react.cjs +31683 -0
  35. package/dist/react.cjs.map +1 -0
  36. package/dist/react.d.cts +100 -0
  37. package/dist/react.d.ts +100 -0
  38. package/dist/react.js +108 -0
  39. package/dist/react.js.map +1 -0
  40. package/dist/rrweb-EYUUZIYR.js +30 -0
  41. package/dist/rrweb-EYUUZIYR.js.map +1 -0
  42. package/package.json +68 -0
@@ -0,0 +1,1644 @@
1
+ import {
2
+ LENS_OVERLAY_HOST_ID,
3
+ LensClient
4
+ } from "./chunk-S6S3ZZQA.js";
5
+
6
+ // ../browser/dist/index.js
7
+ var DEFAULT_PARAM_NAME = "lens";
8
+ var GATE_STORAGE_KEY = "lens:active";
9
+ var LEGACY_PARAM_NAME = "lense";
10
+ var LEGACY_STORAGE_KEY = "lense:active";
11
+ var TRUTHY = /* @__PURE__ */ new Set(["", "true", "1", "on", "yes"]);
12
+ var FALSY = /* @__PURE__ */ new Set(["false", "0", "off", "no"]);
13
+ function hasWindow() {
14
+ return typeof window !== "undefined" && typeof document !== "undefined";
15
+ }
16
+ function readParam(paramName) {
17
+ try {
18
+ const params = new URLSearchParams(window.location.search);
19
+ const value = params.get(paramName);
20
+ if (value !== null) return value;
21
+ if (paramName === DEFAULT_PARAM_NAME) return params.get(LEGACY_PARAM_NAME);
22
+ return null;
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+ function readStorage() {
28
+ try {
29
+ const s = window.sessionStorage;
30
+ return s.getItem(GATE_STORAGE_KEY) === "1" || s.getItem(LEGACY_STORAGE_KEY) === "1";
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+ function writeStorage(active) {
36
+ try {
37
+ if (active) window.sessionStorage.setItem(GATE_STORAGE_KEY, "1");
38
+ else window.sessionStorage.removeItem(GATE_STORAGE_KEY);
39
+ } catch {
40
+ }
41
+ }
42
+ function evaluateGate(paramName = DEFAULT_PARAM_NAME) {
43
+ if (!hasWindow()) return false;
44
+ const raw = readParam(paramName);
45
+ if (raw !== null) {
46
+ const value = raw.trim().toLowerCase();
47
+ if (FALSY.has(value)) {
48
+ writeStorage(false);
49
+ return false;
50
+ }
51
+ if (TRUTHY.has(value)) {
52
+ writeStorage(true);
53
+ return true;
54
+ }
55
+ }
56
+ return readStorage();
57
+ }
58
+ function isGateActive(paramName = DEFAULT_PARAM_NAME) {
59
+ if (!hasWindow()) return false;
60
+ const raw = readParam(paramName);
61
+ if (raw !== null) {
62
+ const value = raw.trim().toLowerCase();
63
+ if (FALSY.has(value)) return false;
64
+ if (TRUTHY.has(value)) return true;
65
+ }
66
+ return readStorage();
67
+ }
68
+ function clearGate() {
69
+ if (!hasWindow()) return;
70
+ writeStorage(false);
71
+ }
72
+ function detectBrowser(ua = navigator.userAgent) {
73
+ if (/Edg\//.test(ua)) return "Edge";
74
+ if (/OPR\/|Opera/.test(ua)) return "Opera";
75
+ if (/Firefox\//.test(ua)) return "Firefox";
76
+ if (/Chrome\//.test(ua) && !/Chromium/.test(ua)) return "Chrome";
77
+ if (/Chromium\//.test(ua)) return "Chromium";
78
+ if (/Safari\//.test(ua) && /Version\//.test(ua)) return "Safari";
79
+ return "Browser";
80
+ }
81
+ function detectOs(ua = navigator.userAgent) {
82
+ if (/Windows NT/.test(ua)) return "Windows";
83
+ if (/Mac OS X|Macintosh/.test(ua)) return "macOS";
84
+ if (/Android/.test(ua)) return "Android";
85
+ if (/iPhone|iPad|iPod/.test(ua)) return "iOS";
86
+ if (/Linux/.test(ua)) return "Linux";
87
+ return "Unknown";
88
+ }
89
+ function viewport() {
90
+ return {
91
+ width: window.innerWidth || document.documentElement.clientWidth || 0,
92
+ height: window.innerHeight || document.documentElement.clientHeight || 0
93
+ };
94
+ }
95
+ function currentRoute() {
96
+ return (window.location.pathname || "/") + (window.location.search || "");
97
+ }
98
+ function el(tag, props = {}, children = []) {
99
+ const node = document.createElement(tag);
100
+ if (props.class) node.className = props.class;
101
+ if (props.text !== void 0) node.textContent = props.text;
102
+ if (props.html !== void 0) node.innerHTML = props.html;
103
+ if (props.title) node.title = props.title;
104
+ if (props.ariaLabel) node.setAttribute("aria-label", props.ariaLabel);
105
+ if (props.type && "type" in node) node.type = props.type;
106
+ if (props.placeholder && "placeholder" in node) {
107
+ node.placeholder = props.placeholder;
108
+ }
109
+ if (props.attrs) {
110
+ for (const [k, v] of Object.entries(props.attrs)) node.setAttribute(k, v);
111
+ }
112
+ if (props.style) Object.assign(node.style, props.style);
113
+ if (props.on) {
114
+ for (const [event, handler] of Object.entries(props.on)) {
115
+ if (handler) node.addEventListener(event, handler);
116
+ }
117
+ }
118
+ for (const child of children) {
119
+ if (child === null || child === void 0 || child === false) continue;
120
+ node.append(child);
121
+ }
122
+ return node;
123
+ }
124
+ function clear(node) {
125
+ while (node.firstChild) node.removeChild(node.firstChild);
126
+ }
127
+ var Annotator = class {
128
+ constructor(host) {
129
+ this.host = host;
130
+ }
131
+ host;
132
+ layer = null;
133
+ chips = null;
134
+ draft = null;
135
+ startX = 0;
136
+ startY = 0;
137
+ active = false;
138
+ dragging = false;
139
+ annotations = [];
140
+ renderedRects = [];
141
+ sessionBase = 0;
142
+ onExit = null;
143
+ get isActive() {
144
+ return this.active;
145
+ }
146
+ get count() {
147
+ return this.annotations.length;
148
+ }
149
+ list() {
150
+ return this.annotations.map((a) => ({ ...a, viewport: { ...a.viewport } }));
151
+ }
152
+ clear() {
153
+ this.annotations = [];
154
+ for (const rect of this.renderedRects) rect.remove();
155
+ this.renderedRects = [];
156
+ }
157
+ start(onExit) {
158
+ if (this.active) return;
159
+ this.active = true;
160
+ this.onExit = onExit;
161
+ this.sessionBase = this.annotations.length;
162
+ this.layer = el("div", { class: "lens-annotate" });
163
+ this.layer.addEventListener("pointerdown", this.onDown);
164
+ this.chips = el("div", { class: "lens-annotate-chips" }, [
165
+ el("span", { class: "lens-annotate-chips__label", text: "Drag to mark an area" }),
166
+ el("button", {
167
+ class: "lens-pick-chip__btn",
168
+ text: "Cancel",
169
+ on: { click: () => this.cancel() }
170
+ }),
171
+ el("button", {
172
+ class: "lens-pick-chip__btn lens-pick-chip__btn--primary",
173
+ text: "Done",
174
+ on: { click: () => this.exit() }
175
+ })
176
+ ]);
177
+ this.host.append(this.layer, this.chips);
178
+ window.addEventListener("keydown", this.onKey, true);
179
+ }
180
+ /** Discard rectangles drawn during this session, then exit. */
181
+ cancel() {
182
+ const removed = this.renderedRects.splice(this.sessionBase);
183
+ for (const rect of removed) rect.remove();
184
+ this.annotations.splice(this.sessionBase);
185
+ this.exit();
186
+ }
187
+ exit() {
188
+ if (!this.active) return;
189
+ this.active = false;
190
+ this.dragging = false;
191
+ window.removeEventListener("keydown", this.onKey, true);
192
+ window.removeEventListener("pointermove", this.onMove, true);
193
+ window.removeEventListener("pointerup", this.onUp, true);
194
+ if (this.draft) {
195
+ this.draft.remove();
196
+ this.draft = null;
197
+ }
198
+ if (this.layer) {
199
+ this.layer.removeEventListener("pointerdown", this.onDown);
200
+ this.layer.remove();
201
+ this.layer = null;
202
+ }
203
+ if (this.chips) {
204
+ this.chips.remove();
205
+ this.chips = null;
206
+ }
207
+ const exit = this.onExit;
208
+ this.onExit = null;
209
+ exit?.();
210
+ }
211
+ onDown = (e) => {
212
+ e.preventDefault();
213
+ this.dragging = true;
214
+ this.startX = e.clientX;
215
+ this.startY = e.clientY;
216
+ this.draft = el("div", { class: "lens-rect" });
217
+ this.host.append(this.draft);
218
+ this.position(this.draft, e.clientX, e.clientY);
219
+ window.addEventListener("pointermove", this.onMove, true);
220
+ window.addEventListener("pointerup", this.onUp, true);
221
+ };
222
+ onMove = (e) => {
223
+ if (this.dragging && this.draft) this.position(this.draft, e.clientX, e.clientY);
224
+ };
225
+ onUp = (e) => {
226
+ window.removeEventListener("pointermove", this.onMove, true);
227
+ window.removeEventListener("pointerup", this.onUp, true);
228
+ this.dragging = false;
229
+ const x = Math.min(this.startX, e.clientX);
230
+ const y = Math.min(this.startY, e.clientY);
231
+ const width = Math.abs(e.clientX - this.startX);
232
+ const height = Math.abs(e.clientY - this.startY);
233
+ if (this.draft) {
234
+ this.draft.remove();
235
+ this.draft = null;
236
+ }
237
+ if (width < 6 || height < 6) return;
238
+ const rect = el("div", { class: "lens-rect" });
239
+ Object.assign(rect.style, {
240
+ left: `${x}px`,
241
+ top: `${y}px`,
242
+ width: `${width}px`,
243
+ height: `${height}px`
244
+ });
245
+ this.host.append(rect);
246
+ this.renderedRects.push(rect);
247
+ this.annotations.push({
248
+ kind: "rect",
249
+ x,
250
+ y,
251
+ width,
252
+ height,
253
+ viewport: { width: window.innerWidth, height: window.innerHeight }
254
+ });
255
+ };
256
+ position(node, curX, curY) {
257
+ const x = Math.min(this.startX, curX);
258
+ const y = Math.min(this.startY, curY);
259
+ Object.assign(node.style, {
260
+ left: `${x}px`,
261
+ top: `${y}px`,
262
+ width: `${Math.abs(curX - this.startX)}px`,
263
+ height: `${Math.abs(curY - this.startY)}px`
264
+ });
265
+ }
266
+ onKey = (e) => {
267
+ if (e.key !== "Escape") return;
268
+ e.preventDefault();
269
+ e.stopPropagation();
270
+ this.exit();
271
+ };
272
+ };
273
+ var svg = (inner) => `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${inner}</svg>`;
274
+ var iconEye = svg(
275
+ '<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>'
276
+ );
277
+ var iconCrosshair = svg(
278
+ '<circle cx="12" cy="12" r="8"/><line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/>'
279
+ );
280
+ var iconClose = svg('<line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/>');
281
+ var iconMark = svg(
282
+ '<rect x="3" y="3" width="18" height="18" rx="2" stroke-dasharray="4 3"/>'
283
+ );
284
+ var iconCopy = svg(
285
+ '<rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V5a2 2 0 0 1 2-2h10"/>'
286
+ );
287
+ var iconCheck = svg('<polyline points="20 6 9 17 4 12"/>');
288
+ var iconSend = svg('<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/>');
289
+ var iconRecord = `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true"><circle cx="12" cy="12" r="6" fill="#ef4444"/></svg>`;
290
+ var iconStop = `<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>`;
291
+ var INCLUDE_ITEMS = [
292
+ { key: "replay", label: "Replay" },
293
+ { key: "console", label: "Console" },
294
+ { key: "network", label: "Network" },
295
+ { key: "errors", label: "Errors" },
296
+ { key: "screenshot", label: "Screenshot" }
297
+ ];
298
+ var Panel = class {
299
+ constructor(cb) {
300
+ this.cb = cb;
301
+ this.body = el("div", { class: "lens-panel__body" });
302
+ this.root = el("div", { class: "lens-panel", attrs: { role: "dialog", "aria-label": "Report an issue" } }, [
303
+ el("div", { class: "lens-panel__head" }, [
304
+ el("div", { class: "lens-panel__title", text: "Report an issue" }),
305
+ el("button", {
306
+ class: "lens-panel__x",
307
+ ariaLabel: "Close",
308
+ html: iconClose,
309
+ on: { click: () => this.cb.onClose() }
310
+ })
311
+ ]),
312
+ this.body
313
+ ]);
314
+ this.renderForm();
315
+ }
316
+ cb;
317
+ root;
318
+ body;
319
+ textarea;
320
+ submitBtn;
321
+ autoCloseTimer = null;
322
+ submitting = false;
323
+ /** Selected capture toggles; every flag starts ON. Persists across refresh. */
324
+ include = {
325
+ replay: true,
326
+ console: true,
327
+ network: true,
328
+ errors: true,
329
+ screenshot: true
330
+ };
331
+ /** Re-render the compose view (chips reflect latest target / annotations). */
332
+ refresh() {
333
+ if (!this.submitting) this.renderForm();
334
+ }
335
+ focus() {
336
+ this.textarea?.focus();
337
+ }
338
+ destroy() {
339
+ if (this.autoCloseTimer) clearTimeout(this.autoCloseTimer);
340
+ this.root.remove();
341
+ }
342
+ renderForm() {
343
+ const prior = this.textarea?.value ?? "";
344
+ clear(this.body);
345
+ this.textarea = el("textarea", {
346
+ class: "lens-textarea",
347
+ placeholder: "What's wrong? Describe the issue\u2026",
348
+ ariaLabel: "Issue description"
349
+ });
350
+ this.textarea.value = prior;
351
+ this.body.append(
352
+ this.textarea,
353
+ this.buildContext(),
354
+ this.buildIncludes(),
355
+ this.buildChips(),
356
+ this.buildActions()
357
+ );
358
+ }
359
+ /** Static, non-interactive page context: route · browser · os · viewport. */
360
+ buildContext() {
361
+ const s = this.cb.getStats();
362
+ const parts = [
363
+ truncate(s.route || s.url, 40),
364
+ `${s.browser} \xB7 ${s.os}`,
365
+ `${s.viewport.width}\xD7${s.viewport.height}`
366
+ ];
367
+ const nodes = [];
368
+ parts.forEach((part, i) => {
369
+ if (i > 0) nodes.push(el("span", { class: "lens-context__dot", text: "\xB7" }));
370
+ nodes.push(el("span", { class: "lens-context__item", text: part }));
371
+ });
372
+ return el("div", { class: "lens-context", title: s.url }, nodes);
373
+ }
374
+ /** Real toggles ("switch"): which captured data to attach. All ON by default. */
375
+ buildIncludes() {
376
+ const s = this.cb.getStats();
377
+ const counts = {
378
+ console: s.console,
379
+ network: s.network,
380
+ errors: s.errors
381
+ };
382
+ const toggles = INCLUDE_ITEMS.map((item) => this.buildToggle(item.key, item.label, counts[item.key]));
383
+ return el("div", { class: "lens-includes" }, [
384
+ el("span", { class: "lens-includes__label", text: "Include:" }),
385
+ ...toggles
386
+ ]);
387
+ }
388
+ buildToggle(key, label, count) {
389
+ const on = this.include[key];
390
+ const btn = el("button", {
391
+ class: `lens-tgl${on ? " is-on" : ""}`,
392
+ attrs: { type: "button", role: "switch", "aria-checked": String(on) },
393
+ html: `<span class="lens-tgl__check" aria-hidden="true">${iconCheck}</span>`
394
+ });
395
+ btn.append(el("span", { class: "lens-tgl__label", text: label }));
396
+ if (typeof count === "number" && count > 0) {
397
+ btn.append(el("span", { class: "lens-tgl__count", text: String(count) }));
398
+ }
399
+ btn.addEventListener("click", () => {
400
+ const next = !this.include[key];
401
+ this.include[key] = next;
402
+ btn.classList.toggle("is-on", next);
403
+ btn.setAttribute("aria-checked", String(next));
404
+ });
405
+ return btn;
406
+ }
407
+ /** Removable context chips: annotations, retained recording, picked element. */
408
+ buildChips() {
409
+ const row = el("div", { class: "lens-chips" });
410
+ const annCount = this.cb.getAnnotationCount();
411
+ if (annCount > 0) {
412
+ row.append(chip(`${annCount} annotation${annCount === 1 ? "" : "s"}`));
413
+ }
414
+ const recording = this.cb.getRecording();
415
+ if (recording) {
416
+ const recChip = el("span", { class: "lens-chip lens-chip--ok lens-chip--rec" }, [
417
+ el("span", { text: `recording \u2713 ${formatDuration(recording.durationMs)}` }),
418
+ el("button", {
419
+ class: "lens-chip__x",
420
+ ariaLabel: "Discard recording",
421
+ text: "\xD7",
422
+ on: { click: () => this.cb.onDiscardRecording() }
423
+ })
424
+ ]);
425
+ row.append(recChip);
426
+ }
427
+ const target = this.cb.getTarget();
428
+ if (target) {
429
+ const label = target.domPath || target.tagName || "element";
430
+ const targetChip = el("span", { class: "lens-chip lens-chip--target", title: label }, [
431
+ el("span", { class: "lens-chip__code", text: truncate(label, 40) }),
432
+ el("button", {
433
+ class: "lens-chip__x",
434
+ ariaLabel: "Remove element",
435
+ text: "\xD7",
436
+ on: { click: () => this.cb.onRemoveTarget() }
437
+ })
438
+ ]);
439
+ row.append(targetChip);
440
+ }
441
+ row.style.display = row.childElementCount > 0 ? "" : "none";
442
+ return row;
443
+ }
444
+ buildActions() {
445
+ const mark = el("button", {
446
+ class: "lens-abtn lens-abtn--ghost lens-abtn--compact",
447
+ html: iconMark + "<span>Mark area</span>",
448
+ on: { click: () => this.cb.onMarkArea() }
449
+ });
450
+ const record = el("button", {
451
+ class: "lens-abtn lens-abtn--ghost lens-abtn--compact",
452
+ html: iconRecord + "<span>Record steps</span>",
453
+ on: { click: () => this.cb.onRecordSteps() }
454
+ });
455
+ const cancel = el("button", {
456
+ class: "lens-abtn lens-abtn--ghost",
457
+ text: "Cancel",
458
+ on: { click: () => this.cb.onClose() }
459
+ });
460
+ this.submitBtn = el("button", {
461
+ class: "lens-abtn lens-abtn--primary lens-abtn--submit",
462
+ html: iconSend + "<span>Submit</span>",
463
+ on: { click: () => void this.submit() }
464
+ });
465
+ return el("div", { class: "lens-actions" }, [
466
+ el("div", { class: "lens-actions__row lens-actions__secondary" }, [mark, record]),
467
+ el("div", { class: "lens-actions__row lens-actions__primary" }, [cancel, this.submitBtn])
468
+ ]);
469
+ }
470
+ async submit() {
471
+ if (this.submitting) return;
472
+ const comment = this.textarea.value.trim();
473
+ this.submitting = true;
474
+ this.submitBtn.disabled = true;
475
+ this.submitBtn.innerHTML = "<span>Submitting\u2026</span>";
476
+ try {
477
+ const { reportId } = await this.cb.onSubmit(comment, { ...this.include });
478
+ this.renderSuccess(reportId);
479
+ } catch (err) {
480
+ this.renderError(err instanceof Error ? err.message : "Something went wrong");
481
+ } finally {
482
+ this.submitting = false;
483
+ }
484
+ }
485
+ renderSuccess(reportId) {
486
+ clear(this.body);
487
+ const idPill = el("div", { class: "lens-idpill" }, [
488
+ el("span", { text: reportId }),
489
+ el("button", {
490
+ ariaLabel: "Copy report id",
491
+ html: iconCopy,
492
+ on: {
493
+ click: (e) => {
494
+ const btn = e.currentTarget;
495
+ void copy(reportId).then(() => {
496
+ btn.innerHTML = iconCheck;
497
+ setTimeout(() => btn.innerHTML = iconCopy, 1200);
498
+ });
499
+ }
500
+ }
501
+ })
502
+ ]);
503
+ this.body.append(
504
+ el("div", { class: "lens-state" }, [
505
+ el("div", { class: "lens-state__badge lens-state__badge--ok", html: iconCheck }),
506
+ el("div", { class: "lens-state__title", text: "Report submitted" }),
507
+ el("div", { class: "lens-state__msg", text: "Thanks \u2014 the team can see the full context." }),
508
+ idPill
509
+ ])
510
+ );
511
+ this.autoCloseTimer = setTimeout(() => this.cb.onClose(), 2500);
512
+ }
513
+ renderError(message) {
514
+ clear(this.body);
515
+ this.body.append(
516
+ el("div", { class: "lens-state" }, [
517
+ el("div", { class: "lens-state__badge lens-state__badge--err", html: iconClose }),
518
+ el("div", { class: "lens-state__title", text: "Submission failed" }),
519
+ el("div", { class: "lens-state__msg", text: truncate(message, 120) }),
520
+ el("div", { class: "lens-actions" }, [
521
+ el("button", {
522
+ class: "lens-abtn lens-abtn--ghost",
523
+ text: "Cancel",
524
+ on: { click: () => this.cb.onClose() }
525
+ }),
526
+ el("button", {
527
+ class: "lens-abtn lens-abtn--primary",
528
+ text: "Retry",
529
+ on: { click: () => this.renderForm() }
530
+ })
531
+ ])
532
+ ])
533
+ );
534
+ }
535
+ };
536
+ function chip(text, title, extra) {
537
+ return el("span", { class: `lens-chip${extra ? " " + extra : ""}`, text, title: title ?? text });
538
+ }
539
+ function truncate(value, max) {
540
+ return value.length > max ? value.slice(0, max - 1) + "\u2026" : value;
541
+ }
542
+ function formatDuration(ms) {
543
+ const total = Math.max(0, Math.round(ms / 1e3));
544
+ const m = Math.floor(total / 60);
545
+ const s = total % 60;
546
+ return `${m}:${s.toString().padStart(2, "0")}`;
547
+ }
548
+ async function copy(text) {
549
+ try {
550
+ await navigator.clipboard.writeText(text);
551
+ } catch {
552
+ }
553
+ }
554
+ var ElementPicker = class {
555
+ constructor(layer, rootHost) {
556
+ this.layer = layer;
557
+ this.rootHost = rootHost;
558
+ this.tag = el("div", { class: "lens-highlight__tag" });
559
+ this.highlight = el("div", { class: "lens-highlight" }, [this.tag]);
560
+ this.hint = el("div", { class: "lens-pick-chip" }, [
561
+ el("span", { class: "lens-pick-chip__label", text: "Tap an element" }),
562
+ el("button", {
563
+ class: "lens-pick-chip__btn",
564
+ text: "Cancel",
565
+ on: { click: () => this.cancel() }
566
+ })
567
+ ]);
568
+ this.layer.append(this.highlight, this.hint);
569
+ this.hide();
570
+ }
571
+ layer;
572
+ rootHost;
573
+ highlight;
574
+ tag;
575
+ hint;
576
+ current = null;
577
+ active = false;
578
+ onPick = null;
579
+ onCancel = null;
580
+ get isActive() {
581
+ return this.active;
582
+ }
583
+ start(onPick, onCancel) {
584
+ if (this.active) return;
585
+ this.active = true;
586
+ this.onPick = onPick;
587
+ this.onCancel = onCancel;
588
+ this.current = null;
589
+ this.hint.style.display = "";
590
+ window.addEventListener("pointermove", this.onMove, true);
591
+ window.addEventListener("pointerdown", this.onDown, true);
592
+ window.addEventListener("pointerup", this.onUp, true);
593
+ window.addEventListener("click", this.onClick, true);
594
+ window.addEventListener("keydown", this.onKey, true);
595
+ }
596
+ stop() {
597
+ if (!this.active) return;
598
+ this.active = false;
599
+ this.current = null;
600
+ this.onPick = null;
601
+ this.onCancel = null;
602
+ this.hide();
603
+ window.removeEventListener("pointermove", this.onMove, true);
604
+ window.removeEventListener("pointerdown", this.onDown, true);
605
+ window.removeEventListener("pointerup", this.onUp, true);
606
+ window.removeEventListener("click", this.onClick, true);
607
+ window.removeEventListener("keydown", this.onKey, true);
608
+ }
609
+ cancel() {
610
+ const cancel = this.onCancel;
611
+ this.stop();
612
+ cancel?.();
613
+ }
614
+ hide() {
615
+ this.highlight.style.display = "none";
616
+ this.hint.style.display = "none";
617
+ }
618
+ isOverlayEvent(e) {
619
+ return e.composedPath().includes(this.rootHost);
620
+ }
621
+ isCoarse() {
622
+ return typeof window.matchMedia === "function" && window.matchMedia("(pointer: coarse)").matches;
623
+ }
624
+ elementAt(x, y) {
625
+ const found = document.elementFromPoint(x, y);
626
+ if (!found || found === this.rootHost) return null;
627
+ return found;
628
+ }
629
+ highlightElement(target) {
630
+ this.current = target;
631
+ const rect = target.getBoundingClientRect();
632
+ Object.assign(this.highlight.style, {
633
+ display: "block",
634
+ left: `${rect.left}px`,
635
+ top: `${rect.top}px`,
636
+ width: `${rect.width}px`,
637
+ height: `${rect.height}px`
638
+ });
639
+ this.tag.textContent = describe(target);
640
+ }
641
+ confirm(target) {
642
+ const pick = this.onPick;
643
+ this.suppressNextClick();
644
+ this.stop();
645
+ pick?.(target);
646
+ }
647
+ suppressNextClick() {
648
+ const swallow = (e) => {
649
+ e.preventDefault();
650
+ e.stopImmediatePropagation();
651
+ cleanup();
652
+ };
653
+ const cleanup = () => {
654
+ window.removeEventListener("click", swallow, true);
655
+ clearTimeout(timer);
656
+ };
657
+ window.addEventListener("click", swallow, { capture: true });
658
+ const timer = setTimeout(cleanup, 500);
659
+ }
660
+ onMove = (e) => {
661
+ if (this.isOverlayEvent(e)) return;
662
+ if (this.isCoarse()) return;
663
+ const target = this.elementAt(e.clientX, e.clientY);
664
+ if (!target || target === this.current) return;
665
+ this.highlightElement(target);
666
+ };
667
+ onDown = (e) => {
668
+ if (this.isOverlayEvent(e)) return;
669
+ e.preventDefault();
670
+ e.stopImmediatePropagation();
671
+ };
672
+ onUp = (e) => {
673
+ if (this.isOverlayEvent(e)) return;
674
+ e.preventDefault();
675
+ e.stopImmediatePropagation();
676
+ const target = this.elementAt(e.clientX, e.clientY);
677
+ if (this.isCoarse()) {
678
+ if (target && target === this.current) {
679
+ this.confirm(target);
680
+ } else if (target) {
681
+ this.highlightElement(target);
682
+ }
683
+ return;
684
+ }
685
+ const chosen = target ?? this.current;
686
+ if (chosen) this.confirm(chosen);
687
+ };
688
+ onClick = (e) => {
689
+ if (this.isOverlayEvent(e)) return;
690
+ e.preventDefault();
691
+ e.stopImmediatePropagation();
692
+ };
693
+ onKey = (e) => {
694
+ if (e.key !== "Escape") return;
695
+ e.preventDefault();
696
+ e.stopPropagation();
697
+ this.cancel();
698
+ };
699
+ };
700
+ function describe(elem) {
701
+ const tag = elem.tagName.toLowerCase();
702
+ const id = elem.id ? `#${elem.id}` : "";
703
+ const cls = typeof elem.className === "string" && elem.className.trim() ? "." + elem.className.trim().split(/\s+/).slice(0, 2).join(".") : "";
704
+ return `${tag}${id}${cls}`;
705
+ }
706
+ var OVERLAY_CSS = (
707
+ /* css */
708
+ `
709
+ :host {
710
+ all: initial;
711
+ --lens-accent: #6366f1;
712
+ --lens-accent-strong: #4f46e5;
713
+ --lens-radius: 8px;
714
+ --lens-font: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
715
+ --lens-z: 2147483000;
716
+ font-family: var(--lens-font);
717
+ line-height: 1.4;
718
+ }
719
+
720
+ *, *::before, *::after { box-sizing: border-box; }
721
+
722
+ .lens-layer {
723
+ position: fixed;
724
+ inset: 0;
725
+ z-index: var(--lens-z);
726
+ pointer-events: none;
727
+ font-family: var(--lens-font);
728
+ }
729
+ .lens-layer > * { pointer-events: auto; }
730
+
731
+ /* ---------------- Pill ---------------- */
732
+ .lens-pill {
733
+ position: fixed;
734
+ bottom: calc(12px + env(safe-area-inset-bottom));
735
+ left: 50%;
736
+ transform: translateX(-50%);
737
+ max-width: calc(100vw - 16px);
738
+ flex-wrap: wrap;
739
+ justify-content: center;
740
+ display: inline-flex;
741
+ align-items: center;
742
+ gap: 4px;
743
+ padding: 6px;
744
+ border-radius: 999px;
745
+ background: rgba(20, 20, 23, 0.82);
746
+ -webkit-backdrop-filter: blur(12px) saturate(180%);
747
+ backdrop-filter: blur(12px) saturate(180%);
748
+ border: 1px solid rgba(255, 255, 255, 0.12);
749
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.06);
750
+ color: #f4f4f5;
751
+ user-select: none;
752
+ transition: opacity 150ms ease, transform 150ms ease;
753
+ }
754
+ .lens-pill__dot {
755
+ display: inline-flex;
756
+ align-items: center;
757
+ justify-content: center;
758
+ width: 26px;
759
+ height: 26px;
760
+ margin-left: 4px;
761
+ border-radius: 999px;
762
+ color: #a5b4fc;
763
+ }
764
+ .lens-btn {
765
+ display: inline-flex;
766
+ align-items: center;
767
+ gap: 6px;
768
+ height: 30px;
769
+ padding: 0 12px;
770
+ border: 0;
771
+ border-radius: 999px;
772
+ background: transparent;
773
+ color: #e4e4e7;
774
+ font-family: var(--lens-font);
775
+ font-size: 13px;
776
+ font-weight: 500;
777
+ cursor: pointer;
778
+ transition: background 150ms ease, color 150ms ease;
779
+ }
780
+ .lens-btn:hover { background: rgba(255, 255, 255, 0.1); }
781
+ .lens-btn:focus-visible { outline: 2px solid var(--lens-accent); outline-offset: 1px; }
782
+ .lens-btn--primary { background: var(--lens-accent); color: #fff; }
783
+ .lens-btn--primary:hover { background: var(--lens-accent-strong); }
784
+ .lens-btn--icon { width: 30px; padding: 0; justify-content: center; }
785
+ .lens-btn--icon.is-active { background: var(--lens-accent); color: #fff; }
786
+ .lens-pill__sep {
787
+ width: 1px;
788
+ height: 18px;
789
+ background: rgba(255, 255, 255, 0.14);
790
+ margin: 0 2px;
791
+ }
792
+
793
+ /* ---------------- Picker highlight ---------------- */
794
+ .lens-highlight {
795
+ position: fixed;
796
+ z-index: calc(var(--lens-z) - 2);
797
+ pointer-events: none;
798
+ border: 2px solid var(--lens-accent);
799
+ background: rgba(99, 102, 241, 0.14);
800
+ border-radius: 3px;
801
+ transition: all 60ms linear;
802
+ display: none;
803
+ }
804
+ .lens-highlight__tag {
805
+ position: absolute;
806
+ top: -22px;
807
+ left: -2px;
808
+ padding: 1px 6px;
809
+ border-radius: 4px;
810
+ background: var(--lens-accent);
811
+ color: #fff;
812
+ font: 500 11px/1.5 var(--lens-font);
813
+ white-space: nowrap;
814
+ }
815
+ .lens-hint {
816
+ position: fixed;
817
+ bottom: 64px;
818
+ left: 50%;
819
+ transform: translateX(-50%);
820
+ padding: 6px 12px;
821
+ border-radius: 999px;
822
+ background: rgba(20, 20, 23, 0.9);
823
+ color: #e4e4e7;
824
+ font-size: 12px;
825
+ font-weight: 500;
826
+ border: 1px solid rgba(255, 255, 255, 0.12);
827
+ box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
828
+ pointer-events: none;
829
+ }
830
+
831
+ /* ---------------- Annotation layer ---------------- */
832
+ .lens-annotate {
833
+ position: fixed;
834
+ inset: 0;
835
+ z-index: calc(var(--lens-z) - 1);
836
+ cursor: crosshair;
837
+ background: rgba(15, 15, 20, 0.04);
838
+ touch-action: none;
839
+ }
840
+ .lens-rect {
841
+ position: fixed;
842
+ border: 2px solid var(--lens-accent);
843
+ background: rgba(99, 102, 241, 0.1);
844
+ border-radius: 3px;
845
+ pointer-events: none;
846
+ }
847
+
848
+ /* ---------------- Panel ---------------- */
849
+ .lens-panel {
850
+ position: fixed;
851
+ right: 20px;
852
+ bottom: 64px;
853
+ width: min(520px, calc(100vw - 40px));
854
+ max-width: 520px;
855
+ max-height: calc(100vh - 96px);
856
+ display: flex;
857
+ flex-direction: column;
858
+ border-radius: 14px;
859
+ background: #ffffff;
860
+ color: #18181b;
861
+ border: 1px solid rgba(0, 0, 0, 0.08);
862
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.22), 0 2px 8px rgba(0, 0, 0, 0.08);
863
+ overflow: hidden;
864
+ transform-origin: bottom right;
865
+ animation: lens-in 150ms ease;
866
+ }
867
+ @keyframes lens-in {
868
+ from { opacity: 0; transform: translateY(6px) scale(0.98); }
869
+ to { opacity: 1; transform: none; }
870
+ }
871
+ .lens-panel__head {
872
+ display: flex;
873
+ align-items: center;
874
+ justify-content: space-between;
875
+ padding: 14px 16px;
876
+ border-bottom: 1px solid rgba(0, 0, 0, 0.07);
877
+ }
878
+ .lens-panel__title { font-size: 14px; font-weight: 600; }
879
+ .lens-panel__x {
880
+ display: inline-flex;
881
+ align-items: center;
882
+ justify-content: center;
883
+ width: 26px;
884
+ height: 26px;
885
+ border: 0;
886
+ border-radius: 6px;
887
+ background: transparent;
888
+ color: #52525b;
889
+ cursor: pointer;
890
+ transition: background 150ms ease;
891
+ }
892
+ .lens-panel__x:hover { background: rgba(0, 0, 0, 0.06); }
893
+ .lens-panel__body {
894
+ padding: 14px 16px;
895
+ overflow-y: auto;
896
+ display: flex;
897
+ flex-direction: column;
898
+ gap: 12px;
899
+ }
900
+ .lens-textarea {
901
+ width: 100%;
902
+ min-height: 120px;
903
+ resize: vertical;
904
+ padding: 10px 12px;
905
+ border-radius: var(--lens-radius);
906
+ border: 1px solid rgba(0, 0, 0, 0.14);
907
+ background: #fafafa;
908
+ color: inherit;
909
+ font-family: var(--lens-font);
910
+ font-size: 13px;
911
+ transition: border-color 150ms ease, box-shadow 150ms ease;
912
+ }
913
+ .lens-textarea:focus {
914
+ outline: none;
915
+ border-color: var(--lens-accent);
916
+ box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.18);
917
+ }
918
+ /* ---- Context row: static, non-interactive page facts (muted, dot-joined) ---- */
919
+ .lens-context {
920
+ display: flex;
921
+ flex-wrap: wrap;
922
+ align-items: center;
923
+ gap: 6px;
924
+ font-size: 11.5px;
925
+ line-height: 1.4;
926
+ color: #71717a;
927
+ }
928
+ .lens-context__item { white-space: nowrap; }
929
+ .lens-context__dot { opacity: 0.55; }
930
+
931
+ /* ---- Include row: real toggles (role=switch) for which data to attach ---- */
932
+ .lens-includes {
933
+ display: flex;
934
+ flex-wrap: wrap;
935
+ align-items: center;
936
+ gap: 6px;
937
+ }
938
+ .lens-includes__label {
939
+ font-size: 11.5px;
940
+ font-weight: 600;
941
+ color: #71717a;
942
+ margin-right: 2px;
943
+ }
944
+ .lens-tgl {
945
+ display: inline-flex;
946
+ align-items: center;
947
+ gap: 5px;
948
+ padding: 4px 10px;
949
+ border-radius: 999px;
950
+ border: 1px solid rgba(0, 0, 0, 0.14);
951
+ background: #fafafa;
952
+ color: #a1a1aa;
953
+ font-family: var(--lens-font);
954
+ font-size: 11.5px;
955
+ font-weight: 600;
956
+ white-space: nowrap;
957
+ cursor: pointer;
958
+ opacity: 0.75;
959
+ transition: background 150ms ease, border-color 150ms ease, color 150ms ease, opacity 150ms ease;
960
+ }
961
+ .lens-tgl:hover { opacity: 1; }
962
+ .lens-tgl:focus-visible { outline: 2px solid var(--lens-accent); outline-offset: 1px; }
963
+ .lens-tgl__check { display: none; align-items: center; }
964
+ .lens-tgl__check svg { width: 13px; height: 13px; }
965
+ .lens-tgl__count { font-weight: 500; opacity: 0.75; font-variant-numeric: tabular-nums; }
966
+ .lens-tgl.is-on {
967
+ opacity: 1;
968
+ color: var(--lens-accent-strong);
969
+ border-color: rgba(99, 102, 241, 0.5);
970
+ background: #eef2ff;
971
+ }
972
+ .lens-tgl.is-on .lens-tgl__check { display: inline-flex; }
973
+
974
+ .lens-chips { display: flex; flex-wrap: wrap; gap: 6px; }
975
+ .lens-chip {
976
+ display: inline-flex;
977
+ align-items: center;
978
+ gap: 5px;
979
+ max-width: 100%;
980
+ padding: 3px 9px;
981
+ border-radius: 999px;
982
+ background: #f4f4f5;
983
+ border: 1px solid rgba(0, 0, 0, 0.06);
984
+ color: #3f3f46;
985
+ font-size: 11.5px;
986
+ font-weight: 500;
987
+ white-space: nowrap;
988
+ overflow: hidden;
989
+ text-overflow: ellipsis;
990
+ }
991
+ .lens-chip--ok { color: #15803d; background: #f0fdf4; border-color: rgba(21, 128, 61, 0.18); }
992
+ .lens-chip--target {
993
+ max-width: 100%;
994
+ background: #eef2ff;
995
+ border-color: rgba(99, 102, 241, 0.25);
996
+ color: var(--lens-accent-strong);
997
+ }
998
+ .lens-chip__code {
999
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1000
+ font-size: 11px;
1001
+ overflow: hidden;
1002
+ text-overflow: ellipsis;
1003
+ }
1004
+ .lens-chip__x {
1005
+ display: inline-flex;
1006
+ border: 0;
1007
+ background: transparent;
1008
+ color: inherit;
1009
+ cursor: pointer;
1010
+ padding: 0;
1011
+ opacity: 0.7;
1012
+ font-size: 13px;
1013
+ line-height: 1;
1014
+ }
1015
+ .lens-chip__x:hover { opacity: 1; }
1016
+ /* ---- Actions: secondary row (mark / record) then primary row (cancel / submit) ---- */
1017
+ .lens-actions { display: flex; flex-direction: column; gap: 8px; }
1018
+ .lens-actions__row { display: flex; align-items: center; gap: 8px; }
1019
+ /* Mark area / Record steps split the row evenly, one line, never wrap. */
1020
+ .lens-actions__secondary .lens-abtn { flex: 1; min-width: 0; justify-content: center; }
1021
+ /* Submit always fills remaining width; Cancel stays intrinsic. */
1022
+ .lens-actions__primary .lens-abtn--submit { flex: 1; }
1023
+ .lens-abtn {
1024
+ display: inline-flex;
1025
+ align-items: center;
1026
+ justify-content: center;
1027
+ gap: 6px;
1028
+ height: 34px;
1029
+ padding: 0 14px;
1030
+ border-radius: var(--lens-radius);
1031
+ border: 1px solid transparent;
1032
+ font-family: var(--lens-font);
1033
+ font-size: 13px;
1034
+ font-weight: 600;
1035
+ white-space: nowrap;
1036
+ cursor: pointer;
1037
+ transition: background 150ms ease, border-color 150ms ease, opacity 150ms ease;
1038
+ }
1039
+ .lens-abtn span { white-space: nowrap; }
1040
+ .lens-abtn--compact { padding: 0 10px; font-size: 12px; }
1041
+ .lens-abtn svg { flex: none; }
1042
+ .lens-abtn--ghost { background: transparent; border-color: rgba(0, 0, 0, 0.14); color: #3f3f46; }
1043
+ .lens-abtn--ghost:hover { background: rgba(0, 0, 0, 0.04); }
1044
+ .lens-abtn--primary { background: var(--lens-accent); color: #fff; }
1045
+ .lens-abtn--primary:hover { background: var(--lens-accent-strong); }
1046
+ .lens-abtn:disabled { opacity: 0.55; cursor: default; }
1047
+ .lens-abtn:focus-visible { outline: 2px solid var(--lens-accent); outline-offset: 1px; }
1048
+
1049
+ .lens-state {
1050
+ display: flex;
1051
+ flex-direction: column;
1052
+ align-items: center;
1053
+ gap: 10px;
1054
+ padding: 26px 18px;
1055
+ text-align: center;
1056
+ }
1057
+ .lens-state__badge {
1058
+ display: inline-flex;
1059
+ align-items: center;
1060
+ justify-content: center;
1061
+ width: 44px;
1062
+ height: 44px;
1063
+ border-radius: 999px;
1064
+ }
1065
+ .lens-state__badge--ok { background: #dcfce7; color: #16a34a; }
1066
+ .lens-state__badge--err { background: #fee2e2; color: #dc2626; }
1067
+ .lens-state__title { font-size: 14px; font-weight: 600; }
1068
+ .lens-state__msg { font-size: 12.5px; color: #52525b; }
1069
+ .lens-idpill {
1070
+ display: inline-flex;
1071
+ align-items: center;
1072
+ gap: 6px;
1073
+ padding: 5px 10px;
1074
+ border-radius: 8px;
1075
+ background: #f4f4f5;
1076
+ border: 1px solid rgba(0, 0, 0, 0.08);
1077
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
1078
+ font-size: 12px;
1079
+ }
1080
+ .lens-idpill button {
1081
+ border: 0;
1082
+ background: transparent;
1083
+ color: #52525b;
1084
+ cursor: pointer;
1085
+ display: inline-flex;
1086
+ padding: 2px;
1087
+ border-radius: 4px;
1088
+ }
1089
+ .lens-idpill button:hover { background: rgba(0, 0, 0, 0.06); color: #18181b; }
1090
+
1091
+ @media (prefers-color-scheme: dark) {
1092
+ .lens-panel { background: #1c1c1f; color: #f4f4f5; border-color: rgba(255, 255, 255, 0.1); }
1093
+ .lens-panel__head { border-bottom-color: rgba(255, 255, 255, 0.08); }
1094
+ .lens-panel__x { color: #a1a1aa; }
1095
+ .lens-panel__x:hover { background: rgba(255, 255, 255, 0.08); }
1096
+ .lens-textarea { background: #26262a; border-color: rgba(255, 255, 255, 0.14); color: #f4f4f5; }
1097
+ .lens-context { color: #a1a1aa; }
1098
+ .lens-includes__label { color: #a1a1aa; }
1099
+ .lens-tgl { background: #26262a; border-color: rgba(255, 255, 255, 0.12); color: #a1a1aa; }
1100
+ .lens-tgl.is-on { background: rgba(99, 102, 241, 0.18); border-color: rgba(99, 102, 241, 0.55); color: #c7d2fe; }
1101
+ .lens-chip { background: #26262a; border-color: rgba(255, 255, 255, 0.08); color: #d4d4d8; }
1102
+ .lens-chip--ok { color: #4ade80; background: rgba(34, 197, 94, 0.12); border-color: rgba(74, 222, 128, 0.22); }
1103
+ .lens-chip--target { background: rgba(99, 102, 241, 0.16); color: #c7d2fe; }
1104
+ .lens-abtn--ghost { border-color: rgba(255, 255, 255, 0.16); color: #d4d4d8; }
1105
+ .lens-abtn--ghost:hover { background: rgba(255, 255, 255, 0.06); }
1106
+ .lens-state__msg { color: #a1a1aa; }
1107
+ .lens-idpill { background: #26262a; border-color: rgba(255, 255, 255, 0.1); }
1108
+ .lens-idpill button { color: #a1a1aa; }
1109
+ .lens-idpill button:hover { background: rgba(255, 255, 255, 0.08); color: #f4f4f5; }
1110
+ }
1111
+
1112
+ /* ---------------- Floating action chips (picker / annotator) ---------------- */
1113
+ .lens-pick-chip, .lens-annotate-chips {
1114
+ position: fixed;
1115
+ top: calc(16px + env(safe-area-inset-top));
1116
+ left: 50%;
1117
+ transform: translateX(-50%);
1118
+ z-index: var(--lens-z);
1119
+ display: inline-flex;
1120
+ align-items: center;
1121
+ gap: 10px;
1122
+ max-width: calc(100vw - 24px);
1123
+ padding: 6px 6px 6px 14px;
1124
+ border-radius: 999px;
1125
+ background: rgba(20, 20, 23, 0.92);
1126
+ -webkit-backdrop-filter: blur(12px) saturate(180%);
1127
+ backdrop-filter: blur(12px) saturate(180%);
1128
+ border: 1px solid rgba(255, 255, 255, 0.12);
1129
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.35);
1130
+ color: #f4f4f5;
1131
+ font-size: 13px;
1132
+ font-weight: 500;
1133
+ }
1134
+ .lens-annotate-chips { z-index: var(--lens-z); }
1135
+
1136
+ /* Stacking inside the shadow layer: the panel, pill and floating chips always
1137
+ sit above the annotation layer/rects and picker highlight, so marked areas
1138
+ never paint over interactive UI. */
1139
+ .lens-panel, .lens-pill, .lens-recchip { z-index: var(--lens-z); }
1140
+ .lens-pick-chip__label, .lens-annotate-chips__label { white-space: nowrap; }
1141
+ .lens-pick-chip__btn {
1142
+ display: inline-flex;
1143
+ align-items: center;
1144
+ justify-content: center;
1145
+ height: 30px;
1146
+ padding: 0 14px;
1147
+ border: 0;
1148
+ border-radius: 999px;
1149
+ background: rgba(255, 255, 255, 0.12);
1150
+ color: #f4f4f5;
1151
+ font-family: var(--lens-font);
1152
+ font-size: 13px;
1153
+ font-weight: 600;
1154
+ cursor: pointer;
1155
+ transition: background 150ms ease;
1156
+ }
1157
+ .lens-pick-chip__btn:hover { background: rgba(255, 255, 255, 0.2); }
1158
+ .lens-pick-chip__btn--primary { background: var(--lens-accent); }
1159
+ .lens-pick-chip__btn--primary:hover { background: var(--lens-accent-strong); }
1160
+
1161
+ /* ---------------- Recording chip ---------------- */
1162
+ .lens-rec-chip {
1163
+ position: fixed;
1164
+ top: calc(16px + env(safe-area-inset-top));
1165
+ left: 50%;
1166
+ transform: translateX(-50%);
1167
+ z-index: var(--lens-z);
1168
+ display: inline-flex;
1169
+ align-items: center;
1170
+ gap: 10px;
1171
+ max-width: calc(100vw - 24px);
1172
+ padding: 6px 6px 6px 14px;
1173
+ border-radius: 999px;
1174
+ background: rgba(20, 20, 23, 0.92);
1175
+ -webkit-backdrop-filter: blur(12px) saturate(180%);
1176
+ backdrop-filter: blur(12px) saturate(180%);
1177
+ border: 1px solid rgba(239, 68, 68, 0.4);
1178
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.35);
1179
+ color: #f4f4f5;
1180
+ font-size: 13px;
1181
+ font-weight: 600;
1182
+ }
1183
+ .lens-rec-chip__dot {
1184
+ width: 10px;
1185
+ height: 10px;
1186
+ border-radius: 999px;
1187
+ background: #ef4444;
1188
+ animation: lens-pulse 1.2s ease-in-out infinite;
1189
+ }
1190
+ @keyframes lens-pulse {
1191
+ 0%, 100% { opacity: 1; transform: scale(1); }
1192
+ 50% { opacity: 0.4; transform: scale(0.7); }
1193
+ }
1194
+ .lens-rec-chip__time { font-variant-numeric: tabular-nums; min-width: 34px; }
1195
+ .lens-rec-chip__stop {
1196
+ display: inline-flex;
1197
+ align-items: center;
1198
+ gap: 6px;
1199
+ height: 30px;
1200
+ padding: 0 14px;
1201
+ border: 0;
1202
+ border-radius: 999px;
1203
+ background: #ef4444;
1204
+ color: #fff;
1205
+ font-family: var(--lens-font);
1206
+ font-size: 13px;
1207
+ font-weight: 600;
1208
+ cursor: pointer;
1209
+ transition: background 150ms ease;
1210
+ }
1211
+ .lens-rec-chip__stop:hover { background: #dc2626; }
1212
+ .lens-chip--rec { gap: 6px; }
1213
+
1214
+ /* ---------------- Coarse pointers (touch): \u226544px targets ---------------- */
1215
+ @media (pointer: coarse) {
1216
+ .lens-btn { height: 44px; padding: 0 16px; }
1217
+ .lens-btn--icon { width: 44px; }
1218
+ .lens-pill__dot { width: 40px; height: 40px; }
1219
+ .lens-abtn { height: 44px; }
1220
+ .lens-tgl { min-height: 40px; padding: 4px 14px; }
1221
+ .lens-panel__x { width: 44px; height: 44px; }
1222
+ .lens-chip__x { min-width: 32px; min-height: 32px; align-items: center; justify-content: center; }
1223
+ .lens-pick-chip__btn, .lens-rec-chip__stop { height: 40px; padding: 0 18px; }
1224
+ }
1225
+
1226
+ /* ---------------- Bottom sheet under 640px ---------------- */
1227
+ @media (max-width: 640px) {
1228
+ .lens-panel {
1229
+ left: 0;
1230
+ right: 0;
1231
+ bottom: 0;
1232
+ width: 100%;
1233
+ max-width: 100%;
1234
+ max-height: 85dvh;
1235
+ border-radius: 16px 16px 0 0;
1236
+ overflow-y: auto;
1237
+ padding-bottom: env(safe-area-inset-bottom);
1238
+ animation: lens-sheet-in 180ms ease;
1239
+ }
1240
+ @keyframes lens-sheet-in {
1241
+ from { opacity: 0; transform: translateY(16px); }
1242
+ to { opacity: 1; transform: none; }
1243
+ }
1244
+ .lens-textarea { font-size: 16px; }
1245
+ }
1246
+ `
1247
+ );
1248
+ var ROOT_ID = LENS_OVERLAY_HOST_ID;
1249
+ var RECORDING_MAX_MS = 12e4;
1250
+ var Overlay = class {
1251
+ constructor(deps) {
1252
+ this.deps = deps;
1253
+ this.host = el("div", { attrs: { id: ROOT_ID } });
1254
+ this.shadow = this.host.attachShadow({ mode: "open" });
1255
+ const style = document.createElement("style");
1256
+ style.textContent = OVERLAY_CSS;
1257
+ this.shadow.append(style);
1258
+ this.layer = el("div", { class: "lens-layer" });
1259
+ this.shadow.append(this.layer);
1260
+ this.picker = new ElementPicker(this.layer, this.host);
1261
+ this.annotator = new Annotator(this.layer);
1262
+ this.layer.append(this.buildPill());
1263
+ document.documentElement.append(this.host);
1264
+ window.addEventListener("keydown", this.onGlobalKey, true);
1265
+ }
1266
+ deps;
1267
+ host;
1268
+ shadow;
1269
+ layer;
1270
+ pickBtn;
1271
+ picker;
1272
+ annotator;
1273
+ panel = null;
1274
+ target = null;
1275
+ destroyed = false;
1276
+ recordingChip = null;
1277
+ recordingTimerEl = null;
1278
+ recordingInterval = null;
1279
+ recordingStartTs = 0;
1280
+ /** Duration of a retained (stopped, not yet submitted/discarded) recording. */
1281
+ recordedDurationMs = null;
1282
+ buildPill() {
1283
+ this.pickBtn = el("button", {
1284
+ class: "lens-btn lens-btn--icon",
1285
+ ariaLabel: "Pick an element",
1286
+ title: "Pick an element",
1287
+ html: iconCrosshair,
1288
+ on: { click: () => this.togglePicker() }
1289
+ });
1290
+ return el("div", { class: "lens-pill", attrs: { role: "toolbar", "aria-label": "Lens" } }, [
1291
+ el("span", { class: "lens-pill__dot", html: iconEye }),
1292
+ el("button", {
1293
+ class: "lens-btn",
1294
+ text: "Report issue",
1295
+ on: { click: () => this.open() }
1296
+ }),
1297
+ this.pickBtn,
1298
+ el("span", { class: "lens-pill__sep" }),
1299
+ el("button", {
1300
+ class: "lens-btn lens-btn--icon",
1301
+ ariaLabel: "Close Lens",
1302
+ title: "Close Lens",
1303
+ html: iconClose,
1304
+ on: { click: () => this.dismiss() }
1305
+ })
1306
+ ]);
1307
+ }
1308
+ open() {
1309
+ if (this.destroyed) return;
1310
+ this.stopPicker();
1311
+ if (!this.panel) {
1312
+ this.panel = new Panel({
1313
+ getStats: () => this.deps.snapshotStats(),
1314
+ getTarget: () => this.target,
1315
+ getAnnotationCount: () => this.annotator.count,
1316
+ getRecording: () => this.recordedDurationMs !== null ? { durationMs: this.recordedDurationMs } : null,
1317
+ onRemoveTarget: () => {
1318
+ this.target = null;
1319
+ this.panel?.refresh();
1320
+ },
1321
+ onMarkArea: () => this.startAnnotating(),
1322
+ onRecordSteps: () => this.startRecording(),
1323
+ onDiscardRecording: () => this.discardRecording(),
1324
+ onClose: () => this.close(),
1325
+ onSubmit: (comment, include) => this.submit(comment, include)
1326
+ });
1327
+ this.layer.append(this.panel.root);
1328
+ }
1329
+ this.panel.root.style.display = "";
1330
+ this.panel.refresh();
1331
+ this.panel.focus();
1332
+ }
1333
+ close() {
1334
+ if (this.panel) {
1335
+ this.panel.destroy();
1336
+ this.panel = null;
1337
+ }
1338
+ }
1339
+ toggle() {
1340
+ if (this.panel && this.panel.root.style.display !== "none") this.close();
1341
+ else this.open();
1342
+ }
1343
+ togglePicker() {
1344
+ if (this.picker.isActive) {
1345
+ this.stopPicker();
1346
+ return;
1347
+ }
1348
+ this.close();
1349
+ this.pickBtn.classList.add("is-active");
1350
+ this.picker.start(
1351
+ (elem) => {
1352
+ this.pickBtn.classList.remove("is-active");
1353
+ this.target = this.deps.client.captureTarget(elem);
1354
+ this.open();
1355
+ },
1356
+ () => this.pickBtn.classList.remove("is-active")
1357
+ );
1358
+ }
1359
+ stopPicker() {
1360
+ if (this.picker.isActive) this.picker.stop();
1361
+ this.pickBtn.classList.remove("is-active");
1362
+ }
1363
+ startAnnotating() {
1364
+ if (this.panel) this.panel.root.style.display = "none";
1365
+ this.annotator.start(() => {
1366
+ if (this.panel) {
1367
+ this.panel.root.style.display = "";
1368
+ this.panel.refresh();
1369
+ }
1370
+ });
1371
+ }
1372
+ startRecording() {
1373
+ if (this.deps.client.isRecording()) return;
1374
+ this.recordedDurationMs = null;
1375
+ this.deps.client.startRecording();
1376
+ this.recordingStartTs = Date.now();
1377
+ if (this.panel) this.panel.root.style.display = "none";
1378
+ this.showRecordingChip();
1379
+ }
1380
+ stopRecording() {
1381
+ const result = this.deps.client.stopRecording();
1382
+ this.recordedDurationMs = result?.durationMs ?? Date.now() - this.recordingStartTs;
1383
+ this.hideRecordingChip();
1384
+ if (this.panel) {
1385
+ this.panel.root.style.display = "";
1386
+ this.panel.refresh();
1387
+ } else {
1388
+ this.open();
1389
+ }
1390
+ }
1391
+ discardRecording() {
1392
+ this.deps.client.discardRecording();
1393
+ this.recordedDurationMs = null;
1394
+ this.panel?.refresh();
1395
+ }
1396
+ showRecordingChip() {
1397
+ this.recordingTimerEl = el("span", { class: "lens-rec-chip__time", text: "0:00" });
1398
+ this.recordingChip = el("div", { class: "lens-rec-chip" }, [
1399
+ el("span", { class: "lens-rec-chip__dot" }),
1400
+ el("span", { class: "lens-rec-chip__label", text: "Recording" }),
1401
+ this.recordingTimerEl,
1402
+ el("button", {
1403
+ class: "lens-rec-chip__stop",
1404
+ html: iconStop + "<span>Stop</span>",
1405
+ on: { click: () => this.stopRecording() }
1406
+ })
1407
+ ]);
1408
+ this.layer.append(this.recordingChip);
1409
+ this.recordingInterval = setInterval(() => this.tickRecording(), 250);
1410
+ }
1411
+ tickRecording() {
1412
+ const elapsed = Date.now() - this.recordingStartTs;
1413
+ if (this.recordingTimerEl) this.recordingTimerEl.textContent = formatDuration(elapsed);
1414
+ if (elapsed >= RECORDING_MAX_MS) this.stopRecording();
1415
+ }
1416
+ hideRecordingChip() {
1417
+ if (this.recordingInterval) {
1418
+ clearInterval(this.recordingInterval);
1419
+ this.recordingInterval = null;
1420
+ }
1421
+ if (this.recordingChip) {
1422
+ this.recordingChip.remove();
1423
+ this.recordingChip = null;
1424
+ }
1425
+ this.recordingTimerEl = null;
1426
+ }
1427
+ async submit(comment, include) {
1428
+ const annotations = this.annotator.list();
1429
+ const result = await this.deps.client.submitReport({
1430
+ comment: comment || void 0,
1431
+ annotations,
1432
+ target: this.target ?? void 0,
1433
+ excludeFromScreenshot: this.host,
1434
+ include
1435
+ });
1436
+ this.target = null;
1437
+ this.annotator.clear();
1438
+ this.recordedDurationMs = null;
1439
+ this.deps.client.discardRecording();
1440
+ return result;
1441
+ }
1442
+ dismiss() {
1443
+ this.deps.onDismiss();
1444
+ this.destroy();
1445
+ }
1446
+ onGlobalKey = (e) => {
1447
+ if ((e.metaKey || e.ctrlKey) && e.shiftKey && (e.key === "l" || e.key === "L")) {
1448
+ e.preventDefault();
1449
+ this.toggle();
1450
+ }
1451
+ };
1452
+ destroy() {
1453
+ if (this.destroyed) return;
1454
+ this.destroyed = true;
1455
+ window.removeEventListener("keydown", this.onGlobalKey, true);
1456
+ this.stopPicker();
1457
+ if (this.deps.client.isRecording()) this.deps.client.stopRecording();
1458
+ this.hideRecordingChip();
1459
+ if (this.annotator.isActive) this.annotator.exit();
1460
+ this.annotator.clear();
1461
+ this.close();
1462
+ this.host.remove();
1463
+ }
1464
+ };
1465
+ function mountOverlay(deps) {
1466
+ return new Overlay(deps);
1467
+ }
1468
+ function processQueue() {
1469
+ const result = { open: false };
1470
+ if (typeof window === "undefined") return result;
1471
+ const queue = window.Lens?.q;
1472
+ if (!queue) return result;
1473
+ for (const entry of queue) {
1474
+ const args = Array.from(entry);
1475
+ const command = args[0];
1476
+ const payload = args[1];
1477
+ switch (command) {
1478
+ case "init":
1479
+ if (typeof payload === "string" || payload && typeof payload === "object") {
1480
+ result.init = payload;
1481
+ }
1482
+ break;
1483
+ case "identify":
1484
+ if (payload && typeof payload === "object") {
1485
+ result.identify = payload;
1486
+ }
1487
+ break;
1488
+ case "app":
1489
+ if (payload && typeof payload === "object") {
1490
+ result.app = payload;
1491
+ }
1492
+ break;
1493
+ case "open":
1494
+ result.open = true;
1495
+ break;
1496
+ }
1497
+ }
1498
+ if (window.Lens) window.Lens.q = [];
1499
+ return result;
1500
+ }
1501
+ function installGlobal(browser) {
1502
+ if (typeof window === "undefined") return;
1503
+ const dispatch = (...args) => {
1504
+ if (!browser) return;
1505
+ const command = args[0];
1506
+ const payload = args[1];
1507
+ switch (command) {
1508
+ case "identify":
1509
+ if (payload && typeof payload === "object") {
1510
+ browser.client.setApp({ user: payload });
1511
+ }
1512
+ break;
1513
+ case "app":
1514
+ if (payload && typeof payload === "object") {
1515
+ browser.client.setApp(payload);
1516
+ }
1517
+ break;
1518
+ case "open":
1519
+ browser.open();
1520
+ break;
1521
+ case "init":
1522
+ break;
1523
+ }
1524
+ };
1525
+ window.Lens = dispatch;
1526
+ }
1527
+ function applyContext(browser, commands) {
1528
+ if (commands.identify) browser.client.setApp({ user: commands.identify });
1529
+ if (commands.app) browser.client.setApp(commands.app);
1530
+ if (commands.open) browser.open();
1531
+ }
1532
+ var DEFAULT_CDN_URL = "https://cdn.lens.dev/browser.js";
1533
+ function loaderSnippet(options) {
1534
+ const cdn = options.cdnUrl ?? DEFAULT_CDN_URL;
1535
+ return `<script>
1536
+ (function(w,d,s,u,i){
1537
+ w.Lens=w.Lens||function(){(w.Lens.q=w.Lens.q||[]).push(arguments)};
1538
+ w.Lens('init', i);
1539
+ var g=d.createElement(s); g.async=1; g.src=u;
1540
+ var f=d.getElementsByTagName(s)[0]; f.parentNode.insertBefore(g,f);
1541
+ })(window,document,'script','${cdn}','${options.accessId}');
1542
+ </script>`;
1543
+ }
1544
+ var LOADER_SNIPPET = loaderSnippet({ accessId: "lai_YOUR_ACCESS_ID" });
1545
+ function hasDom() {
1546
+ return typeof window !== "undefined" && typeof document !== "undefined";
1547
+ }
1548
+ function scriptAccessId() {
1549
+ const tag = document.querySelector("script[data-lens-id]");
1550
+ return tag?.getAttribute("data-lens-id") ?? void 0;
1551
+ }
1552
+ function resolveConfig(options, commands) {
1553
+ const { accessId: optAccessId, paramName: _p, force: _f, ...rest } = options;
1554
+ let queuedConfig = {};
1555
+ let queuedAccessId;
1556
+ if (typeof commands.init === "string") {
1557
+ queuedAccessId = commands.init;
1558
+ } else if (commands.init && typeof commands.init === "object") {
1559
+ queuedConfig = commands.init;
1560
+ if (typeof commands.init.accessId === "string") queuedAccessId = commands.init.accessId;
1561
+ }
1562
+ const accessId = optAccessId ?? scriptAccessId() ?? queuedAccessId;
1563
+ if (!accessId) return null;
1564
+ const config = { ...queuedConfig, ...rest, accessId };
1565
+ return { accessId, config };
1566
+ }
1567
+ function boot(options = {}) {
1568
+ if (!hasDom()) return null;
1569
+ const commands = processQueue();
1570
+ const paramName = options.paramName ?? DEFAULT_PARAM_NAME;
1571
+ const active = options.force === true || evaluateGate(paramName);
1572
+ if (!active) {
1573
+ installGlobal(null);
1574
+ return null;
1575
+ }
1576
+ const resolved = resolveConfig(options, commands);
1577
+ if (!resolved) {
1578
+ if (options.debug) console.warn("[lens] no accessId \u2014 overlay not booted");
1579
+ installGlobal(null);
1580
+ return null;
1581
+ }
1582
+ const client = new LensClient(resolved.config);
1583
+ client.start();
1584
+ const snapshotStats = () => ({
1585
+ url: window.location.href,
1586
+ route: currentRoute(),
1587
+ browser: detectBrowser(),
1588
+ os: detectOs(),
1589
+ viewport: viewport(),
1590
+ replay: resolved.config.replay !== false,
1591
+ // Console / network / error counts are not exposed by the public core API.
1592
+ console: null,
1593
+ network: null,
1594
+ errors: null
1595
+ });
1596
+ let overlay = null;
1597
+ const browser = {
1598
+ client,
1599
+ open: () => overlay?.open(),
1600
+ close: () => overlay?.close(),
1601
+ destroy: () => {
1602
+ overlay?.destroy();
1603
+ overlay = null;
1604
+ client.stop();
1605
+ clearGate();
1606
+ installGlobal(null);
1607
+ }
1608
+ };
1609
+ overlay = mountOverlay({
1610
+ client,
1611
+ snapshotStats,
1612
+ onDismiss: () => {
1613
+ client.stop();
1614
+ clearGate();
1615
+ installGlobal(null);
1616
+ }
1617
+ });
1618
+ installGlobal(browser);
1619
+ applyContext(browser, commands);
1620
+ return browser;
1621
+ }
1622
+ function autoBoot() {
1623
+ if (!hasDom()) return null;
1624
+ const hasQueuedInit = Array.from(window.Lens?.q ?? []).some(
1625
+ (entry) => entry[0] === "init"
1626
+ );
1627
+ const hasScriptId = !!document.querySelector("script[data-lens-id]");
1628
+ if (!hasQueuedInit && !hasScriptId) return null;
1629
+ if (!isGateActive()) return null;
1630
+ return boot();
1631
+ }
1632
+ if (hasDom()) autoBoot();
1633
+
1634
+ export {
1635
+ isGateActive,
1636
+ processQueue,
1637
+ installGlobal,
1638
+ DEFAULT_CDN_URL,
1639
+ loaderSnippet,
1640
+ LOADER_SNIPPET,
1641
+ boot,
1642
+ autoBoot
1643
+ };
1644
+ //# sourceMappingURL=chunk-DU7HNRF4.js.map