@guidelytech/guide-runtime 0.5.0 → 0.6.2

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.
@@ -0,0 +1,1336 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/capture.ts
21
+ var capture_exports = {};
22
+ __export(capture_exports, {
23
+ CAPTURE_INTERACTIVE_SELECTOR: () => CAPTURE_INTERACTIVE_SELECTOR,
24
+ DOUBLE_CLICK_MS: () => DOUBLE_CLICK_MS,
25
+ NAV_AFTER_ACTION_MS: () => NAV_AFTER_ACTION_MS,
26
+ NAV_SETTLE_MS: () => NAV_SETTLE_MS,
27
+ REDACT_SELECTOR: () => REDACT_SELECTOR,
28
+ actionTarget: () => actionTarget,
29
+ canCaptureScreen: () => canCaptureScreen,
30
+ captureLabel: () => captureLabel,
31
+ captureRole: () => captureRole,
32
+ classifyNumber: () => classifyNumber,
33
+ collectRedactions: () => collectRedactions,
34
+ describeCapturedElement: () => describeCapturedElement,
35
+ encodeScreenshot: () => encodeScreenshot,
36
+ fieldValue: () => fieldValue,
37
+ findSensitiveText: () => findSensitiveText,
38
+ grabFrame: () => grabFrame,
39
+ isMaskedElement: () => isMaskedElement,
40
+ isSensitiveField: () => isSensitiveField,
41
+ isTextEntry: () => isTextEntry,
42
+ labelControl: () => labelControl,
43
+ looksLikeSecretValue: () => looksLikeSecretValue,
44
+ normalizeRect: () => normalizeRect,
45
+ redactCanvas: () => redactCanvas,
46
+ requestTabCapture: () => requestTabCapture,
47
+ screenshotDataUrl: () => screenshotDataUrl,
48
+ selectedOptionText: () => selectedOptionText,
49
+ startCapture: () => startCapture,
50
+ toFrameRect: () => toFrameRect
51
+ });
52
+ module.exports = __toCommonJS(capture_exports);
53
+
54
+ // src/capture-styles.ts
55
+ var CAPTURE_STYLES = `
56
+ .g-cap{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);z-index:calc(var(--g-z) + 10);pointer-events:auto;display:flex;flex-direction:column;align-items:center;gap:6px;max-width:calc(100vw - 32px);font:500 13px/1.4 var(--g-font);color:#fff}
57
+ .g-cap-row{display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding:6px 6px 6px 14px;background:#111827;border-radius:999px;box-shadow:0 6px 24px rgba(0,0,0,.28)}
58
+ .g-cap-dot{position:relative;flex:none;width:10px;height:10px;border-radius:999px;background:#9ca3af}
59
+ .g-cap[data-state="recording"] .g-cap-dot{background:#ef4444}
60
+ .g-cap[data-state="recording"] .g-cap-dot::after{content:"";position:absolute;inset:0;border-radius:inherit;background:#ef4444;animation:g-pulse 1.4s ease-out infinite}
61
+ .g-cap-status{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:min(340px,60vw)}
62
+ .g-cap .g-btn{min-height:28px;padding:3px 12px;font-size:13px;border-radius:999px}
63
+ .g-cap .g-btn-secondary{background:rgba(255,255,255,.14);color:#fff;border-color:transparent}
64
+ .g-cap .g-btn-secondary:hover{background:rgba(255,255,255,.22)}
65
+ .g-cap .g-btn-ghost{color:#d1d5db}
66
+ .g-cap .g-btn-ghost:hover{background:rgba(255,255,255,.1);color:#fff}
67
+ .g-cap .g-cap-warn{background:#f59e0b;color:#111827}
68
+ .g-cap-note{max-width:min(420px,calc(100vw - 32px));padding:6px 12px;border-radius:10px;background:#7c2d12;box-shadow:0 6px 24px rgba(0,0,0,.28)}
69
+ .g-cap-count{display:none;font-variant-numeric:tabular-nums}
70
+ .g-cap[data-compact] .g-cap-row{gap:6px;padding:5px 10px;opacity:.85}
71
+ .g-cap[data-compact] .g-cap-row>:not(.g-cap-dot):not(.g-cap-count):not(.g-cap-warn){display:none}
72
+ .g-cap[data-compact] .g-cap-count{display:inline}
73
+ .g-cap[data-captured] .g-cap-dot,.g-cap[data-captured] .g-cap-dot::after{background:#22c55e}
74
+ `;
75
+
76
+ // src/selectors.ts
77
+ var import_runtime = require("@guidelytech/shared/runtime");
78
+ var HOST_ID = "guidely-root";
79
+ function normalizeText(text) {
80
+ return (text || "").replace(/\s+/g, " ").trim();
81
+ }
82
+ function isGuidelyElement(el) {
83
+ if (!el) return false;
84
+ const root = el.getRootNode();
85
+ if (root instanceof ShadowRoot && root.host.id === HOST_ID) return true;
86
+ return el.id === HOST_ID || !!el.closest?.(`#${HOST_ID}`);
87
+ }
88
+ function safeQueryAll(root, selector) {
89
+ try {
90
+ return Array.from(root.querySelectorAll(selector));
91
+ } catch {
92
+ return [];
93
+ }
94
+ }
95
+ function escapeCss(value) {
96
+ const g = globalThis;
97
+ if (g.CSS && typeof g.CSS.escape === "function") return g.CSS.escape(value);
98
+ return value.replace(/["\\]/g, "\\$&");
99
+ }
100
+ var GENERATED_ID = /(\d{4,}|:r[0-9a-z]+:|^radix-|^headlessui-|^mui-|^ember\d|^react-|^\w*-\d+$|__|^[a-f0-9]{8,}$|^svelte-)/i;
101
+ var GENERATED_CLASS = /(^css-|^sc-|^jss\d|^svelte-|^emotion-|^chakra-|^Mui|^_|[0-9a-f]{6,}|^\w{1,2}$|^[a-z]+-\d+$|[:[\]/])/;
102
+ var PREFERRED_DATA_ATTRS = ["data-testid", "data-tour", "data-test", "data-test-id", "data-cy", "data-qa", "data-id", "data-name", "data-guidely"];
103
+ var IGNORED_DATA_ATTRS = /^data-(guidely-id|state|v-|reactid|react|radix|headlessui|svelte|hydrated|orientation|highlighted|focus|key|index|rbd|dnd|rk-|sveltekit)/i;
104
+ function isStableId(id) {
105
+ return !!id && /^[A-Za-z][\w-]*$/.test(id) && !GENERATED_ID.test(id);
106
+ }
107
+ function isStableClass(cls) {
108
+ return !!cls && !GENERATED_CLASS.test(cls) && cls.length <= 40;
109
+ }
110
+ function uniqueIn(doc, selector, el) {
111
+ const all = safeQueryAll(doc, selector);
112
+ return all.length === 1 && all[0] === el;
113
+ }
114
+ function attrSelector(name, value) {
115
+ return `[${name}='${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}']`;
116
+ }
117
+ function cssPath(el, doc) {
118
+ const segments = [];
119
+ let cur = el;
120
+ while (cur && cur.nodeType === 1 && cur !== doc.body && cur !== doc.documentElement) {
121
+ const tag = cur.tagName.toLowerCase();
122
+ if (isStableId(cur.id)) {
123
+ segments.unshift(`#${cur.id}`);
124
+ break;
125
+ }
126
+ let seg = tag;
127
+ const parent = cur.parentElement;
128
+ if (parent) {
129
+ const sameTag = Array.from(parent.children).filter((c) => c.tagName === cur.tagName);
130
+ if (sameTag.length > 1) {
131
+ const stableCls = Array.from(cur.classList).find((c) => isStableClass(c) && sameTag.filter((s) => s.classList.contains(c)).length === 1);
132
+ seg += stableCls ? `.${escapeCss(stableCls)}` : `:nth-of-type(${sameTag.indexOf(cur) + 1})`;
133
+ }
134
+ }
135
+ segments.unshift(seg);
136
+ const candidate = segments.join(" > ");
137
+ if (uniqueIn(doc, candidate, el)) return candidate;
138
+ cur = parent;
139
+ }
140
+ const full = segments.join(" > ");
141
+ return uniqueIn(doc, full, el) ? full : null;
142
+ }
143
+ function textSnapshotOf(el) {
144
+ const he = el;
145
+ const own = normalizeText(he.innerText ?? el.textContent);
146
+ const alt = normalizeText(el.getAttribute("aria-label") || el.getAttribute("title") || el.getAttribute("placeholder") || el.getAttribute("alt") || el.value);
147
+ return (own || alt).slice(0, 200);
148
+ }
149
+ function generateSelectors(el) {
150
+ const doc = el.ownerDocument;
151
+ const out = [];
152
+ const seen = /* @__PURE__ */ new Set();
153
+ const push = (s) => {
154
+ const k = `${s.kind}:${s.value}`;
155
+ if (!seen.has(k)) {
156
+ seen.add(k);
157
+ out.push(s);
158
+ }
159
+ };
160
+ const tag = el.tagName.toLowerCase();
161
+ const guidelyId = el.getAttribute("data-guidely-id");
162
+ if (guidelyId && uniqueIn(doc, attrSelector("data-guidely-id", guidelyId), el)) push({ kind: "guidely-id", value: guidelyId, label: `Guidely id ${guidelyId}` });
163
+ if (isStableId(el.id) && uniqueIn(doc, `#${escapeCss(el.id)}`, el)) push({ kind: "id", value: `#${el.id}`, label: `id ${el.id}` });
164
+ const dataAttrs = Array.from(el.attributes).filter((a) => a.name.startsWith("data-") && !IGNORED_DATA_ATTRS.test(a.name) && a.value && a.value.length <= 120 && !/\d{5,}/.test(a.value));
165
+ dataAttrs.sort((a, b) => {
166
+ const ia = PREFERRED_DATA_ATTRS.indexOf(a.name);
167
+ const ib = PREFERRED_DATA_ATTRS.indexOf(b.name);
168
+ return (ia === -1 ? 99 : ia) - (ib === -1 ? 99 : ib);
169
+ });
170
+ for (const a of dataAttrs) {
171
+ const sel = attrSelector(a.name, a.value);
172
+ if (uniqueIn(doc, sel, el)) push({ kind: "data-attr", value: sel, label: `${a.name}=${a.value}` });
173
+ else if (uniqueIn(doc, tag + sel, el)) push({ kind: "data-attr", value: tag + sel, label: `${a.name}=${a.value}` });
174
+ }
175
+ const aria = el.getAttribute("aria-label");
176
+ if (aria && aria.length <= 120) {
177
+ const sel = attrSelector("aria-label", aria);
178
+ if (uniqueIn(doc, sel, el)) push({ kind: "aria", value: sel, label: aria });
179
+ else if (uniqueIn(doc, tag + sel, el)) push({ kind: "aria", value: tag + sel, label: aria });
180
+ }
181
+ const name = el.getAttribute("name");
182
+ if (name && uniqueIn(doc, tag + attrSelector("name", name), el)) push({ kind: "css", value: tag + attrSelector("name", name), label: `${tag} name=${name}` });
183
+ const role = el.getAttribute("role");
184
+ if (role) {
185
+ const title = el.getAttribute("title") || el.getAttribute("aria-labelledby");
186
+ const sel = title ? `[role='${role}']${attrSelector(el.getAttribute("title") ? "title" : "aria-labelledby", title)}` : `[role='${role}']`;
187
+ if (uniqueIn(doc, sel, el)) push({ kind: "css", value: sel, label: `role ${role}` });
188
+ }
189
+ if (tag === "a") {
190
+ const href = el.getAttribute("href");
191
+ if (href && href.length <= 200 && uniqueIn(doc, `a${attrSelector("href", href)}`, el)) push({ kind: "css", value: `a${attrSelector("href", href)}`, label: `link ${href}` });
192
+ }
193
+ const path = cssPath(el, doc);
194
+ if (path) push({ kind: "css", value: path, label: "css path" });
195
+ if (/^(button|a|h[1-6]|label|summary)$/.test(tag) || role === "button" || role === "link" || role === "tab" || role === "menuitem") {
196
+ const text = normalizeText(el.innerText ?? el.textContent);
197
+ if (text && text.length <= 80) push({ kind: "text", value: text, label: `text "${text}"` });
198
+ }
199
+ return out.slice(0, 10);
200
+ }
201
+ function describeElement(el) {
202
+ const r = el.getBoundingClientRect();
203
+ return {
204
+ selectors: generateSelectors(el),
205
+ textSnapshot: textSnapshotOf(el),
206
+ rect: { x: r.x, y: r.y, width: r.width, height: r.height },
207
+ tag: el.tagName.toLowerCase()
208
+ };
209
+ }
210
+
211
+ // src/capture-dom.ts
212
+ var CAPTURE_INTERACTIVE_SELECTOR = "a,button,input,select,textarea,label,summary,[role=button],[role=link],[role=tab],[role=menuitem],[role=menuitemcheckbox],[role=menuitemradio],[role=option],[role=checkbox],[role=radio],[role=switch],[role=combobox],[contenteditable]:not([contenteditable=false])";
213
+ var REDACT_SELECTOR = "[data-guidely-redact]";
214
+ var TEXT_INPUT_TYPES = /* @__PURE__ */ new Set(["", "text", "email", "search", "tel", "url", "number", "password", "date", "datetime-local", "month", "week", "time"]);
215
+ function closestSafe(el, selector) {
216
+ if (!selector) return null;
217
+ try {
218
+ return el.closest(selector);
219
+ } catch {
220
+ return null;
221
+ }
222
+ }
223
+ function inputType(el) {
224
+ return el.tagName === "INPUT" ? (el.getAttribute("type") || "").toLowerCase() : "";
225
+ }
226
+ function isEditableHost(el) {
227
+ const v = el.getAttribute("contenteditable");
228
+ return v != null && v.toLowerCase() !== "false";
229
+ }
230
+ function isTextEntry(el) {
231
+ if (el.tagName === "TEXTAREA") return true;
232
+ if (el.tagName === "INPUT") return TEXT_INPUT_TYPES.has(inputType(el));
233
+ return isEditableHost(el) || !!closestSafe(el, "[contenteditable]:not([contenteditable=false])");
234
+ }
235
+ function actionTarget(target) {
236
+ const hit = closestSafe(target, CAPTURE_INTERACTIVE_SELECTOR) ?? target;
237
+ if (!isEditableHost(hit) && isTextEntry(hit) && hit.tagName !== "INPUT" && hit.tagName !== "TEXTAREA") return closestSafe(hit, "[contenteditable]:not([contenteditable=false])") ?? hit;
238
+ return hit;
239
+ }
240
+ function labelControl(el) {
241
+ if (el.tagName !== "LABEL") return null;
242
+ const label = el;
243
+ if (label.control) return label.control;
244
+ const forId = label.getAttribute("for");
245
+ if (forId) return label.ownerDocument.getElementById(forId);
246
+ return label.querySelector("input,select,textarea");
247
+ }
248
+ function isMaskedElement(el, maskSelector) {
249
+ return !!closestSafe(el, REDACT_SELECTOR) || !!closestSafe(el, maskSelector);
250
+ }
251
+ var SENSITIVE_NAME = /pass|card|cvv|cvc|ssn|secret|token|otp/i;
252
+ var SENSITIVE_AUTOCOMPLETE = /cc-|one-time-code|current-password|new-password/i;
253
+ function isSensitiveField(el, maskSelector = "") {
254
+ if (inputType(el) === "password") return true;
255
+ if (SENSITIVE_AUTOCOMPLETE.test(el.getAttribute("autocomplete") || "")) return true;
256
+ if (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName) && (SENSITIVE_NAME.test(el.getAttribute("name") || "") || SENSITIVE_NAME.test(el.id || ""))) return true;
257
+ return isMaskedElement(el, maskSelector);
258
+ }
259
+ var EMAIL = /[A-Z0-9._%+-]+@[A-Z0-9-]+(?:\.[A-Z0-9-]+)*\.[A-Z]{2,}/gi;
260
+ var NUMBER_RUN = /(?:\+\d{1,3}[ .-]?)?(?:\(\d{2,4}\)[ .-]?)?\d+(?:[ .-]\d+)*/g;
261
+ function classifyNumber(run) {
262
+ const groups = run.match(/\d+/g) ?? [];
263
+ const lens = groups.map((g) => g.length);
264
+ const digits = lens.reduce((a, b) => a + b, 0);
265
+ const special = /[+(.]/.test(run);
266
+ if (lens.length === 3 && lens[0] === 3 && lens[1] === 2 && lens[2] === 4 && /^\d{3}[ -]\d{2}[ -]\d{4}$/.test(run)) return "ssn";
267
+ if (digits >= 13 && digits <= 19 && !special && lens.every((l) => l >= 4)) return "card";
268
+ if (digits >= 10 && digits <= 13 && (/[+(]/.test(run) || lens.length >= 3 && lens[lens.length - 1] === 4 && lens.every((l) => l >= 2) && !run.includes("."))) return "phone";
269
+ return null;
270
+ }
271
+ function findSensitiveText(text) {
272
+ const out = [];
273
+ if (text.length < 7) return out;
274
+ if (text.includes("@")) {
275
+ EMAIL.lastIndex = 0;
276
+ for (let m = EMAIL.exec(text); m && out.length < 200; m = EMAIL.exec(text)) out.push([m.index, m.index + m[0].length]);
277
+ }
278
+ if (/\d{3}/.test(text)) {
279
+ NUMBER_RUN.lastIndex = 0;
280
+ for (let m = NUMBER_RUN.exec(text); m && out.length < 200; m = NUMBER_RUN.exec(text)) {
281
+ if (m[0].length >= 9 && classifyNumber(m[0])) out.push([m.index, m.index + m[0].length]);
282
+ }
283
+ }
284
+ return out;
285
+ }
286
+ function looksLikeSecretValue(value) {
287
+ NUMBER_RUN.lastIndex = 0;
288
+ for (let m = NUMBER_RUN.exec(value); m; m = NUMBER_RUN.exec(value)) {
289
+ const kind = classifyNumber(m[0]);
290
+ if (kind === "card" || kind === "ssn") return true;
291
+ }
292
+ return false;
293
+ }
294
+ var ROLE_MAP = {
295
+ button: "button",
296
+ link: "link",
297
+ tab: "tab",
298
+ menuitem: "menuitem",
299
+ menuitemcheckbox: "menuitem",
300
+ menuitemradio: "menuitem",
301
+ checkbox: "checkbox",
302
+ radio: "radio",
303
+ switch: "switch",
304
+ option: "option",
305
+ combobox: "dropdown",
306
+ listbox: "dropdown",
307
+ textbox: "field",
308
+ searchbox: "field",
309
+ spinbutton: "field"
310
+ };
311
+ function captureRole(el) {
312
+ const explicit = (el.getAttribute("role") || "").trim().split(/\s+/)[0].toLowerCase();
313
+ if (ROLE_MAP[explicit]) return ROLE_MAP[explicit];
314
+ const tag = el.tagName.toLowerCase();
315
+ if (tag === "a") return el.hasAttribute("href") ? "link" : "element";
316
+ if (tag === "button" || tag === "summary") return "button";
317
+ if (tag === "select") return "dropdown";
318
+ if (tag === "option") return "option";
319
+ if (tag === "textarea") return "field";
320
+ if (tag === "input") {
321
+ const t = inputType(el);
322
+ if (t === "checkbox") return "checkbox";
323
+ if (t === "radio") return "radio";
324
+ if (t === "button" || t === "submit" || t === "reset" || t === "image") return "button";
325
+ return "field";
326
+ }
327
+ if (isEditableHost(el)) return "field";
328
+ return "element";
329
+ }
330
+ var clip = (s, max = 80) => s.length > max ? `${s.slice(0, max - 1).trimEnd()}\u2026` : s;
331
+ function textOf(el) {
332
+ return normalizeText(el.innerText ?? el.textContent);
333
+ }
334
+ function labelledBy(el) {
335
+ const ids = (el.getAttribute("aria-labelledby") || "").split(/\s+/).filter(Boolean);
336
+ return normalizeText(ids.map((id) => el.ownerDocument.getElementById(id)).filter((x) => !!x).map(textOf).join(" "));
337
+ }
338
+ function associatedLabel(el) {
339
+ const labels = el.labels;
340
+ if (labels && labels.length) return normalizeText(Array.from(labels).map(textOf).join(" "));
341
+ if (el.id) {
342
+ try {
343
+ const l = el.ownerDocument.querySelector(`label[for="${el.id.replace(/["\\]/g, "\\$&")}"]`);
344
+ if (l) return textOf(l);
345
+ } catch {
346
+ }
347
+ }
348
+ const wrap = el.parentElement?.closest("label");
349
+ return wrap ? textOf(wrap) : "";
350
+ }
351
+ function captureLabel(el) {
352
+ const attr = (name) => normalizeText(el.getAttribute(name));
353
+ const tag = el.tagName;
354
+ const field = tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || isEditableHost(el);
355
+ const candidates = [
356
+ () => attr("aria-label"),
357
+ () => labelledBy(el),
358
+ () => /^(INPUT|TEXTAREA|SELECT|METER|PROGRESS|OUTPUT)$/.test(tag) ? associatedLabel(el) : "",
359
+ () => tag === "INPUT" && /^(button|submit|reset)$/.test(inputType(el)) ? normalizeText(el.value) : "",
360
+ () => field ? "" : textOf(el),
361
+ () => attr("placeholder"),
362
+ () => attr("alt"),
363
+ () => normalizeText(el.querySelector?.("img[alt]")?.getAttribute("alt")),
364
+ () => attr("title"),
365
+ () => normalizeText(el.querySelector?.("svg title")?.textContent),
366
+ () => attr("name")
367
+ ];
368
+ for (const c of candidates) {
369
+ const v = c();
370
+ if (v) return clip(v);
371
+ }
372
+ return "";
373
+ }
374
+ function describeCapturedElement(el, maskSelector = "") {
375
+ return { label: isMaskedElement(el, maskSelector) ? "" : captureLabel(el), role: captureRole(el), tag: el.tagName.toLowerCase() };
376
+ }
377
+ function fieldValue(el) {
378
+ const raw = el.tagName === "INPUT" || el.tagName === "TEXTAREA" ? el.value : textOf(el);
379
+ return (raw || "").trim().slice(0, 200);
380
+ }
381
+ function selectedOptionText(el) {
382
+ return normalizeText(Array.from(el.selectedOptions ?? []).map((o) => o.text || o.value).join(", ")).slice(0, 200);
383
+ }
384
+ var clamp01 = (n) => Math.min(1, Math.max(0, n));
385
+ var round = (n) => Math.round(n * 1e4) / 1e4;
386
+ function normalizeRect(rect, viewport, pad = 4) {
387
+ if (!viewport.width || !viewport.height || rect.width <= 0 || rect.height <= 0) return null;
388
+ const x0 = clamp01((rect.x - pad) / viewport.width);
389
+ const y0 = clamp01((rect.y - pad) / viewport.height);
390
+ const x1 = clamp01((rect.x + rect.width + pad) / viewport.width);
391
+ const y1 = clamp01((rect.y + rect.height + pad) / viewport.height);
392
+ if (x1 <= x0 || y1 <= y0) return null;
393
+ return { x: round(x0), y: round(y0), width: round(x1 - x0), height: round(y1 - y0) };
394
+ }
395
+ function toFrameRect(rect, scaleX, scaleY, frame, pad = 0) {
396
+ const x0 = Math.max(0, Math.floor((rect.x - pad) * scaleX));
397
+ const y0 = Math.max(0, Math.floor((rect.y - pad) * scaleY));
398
+ const x1 = Math.min(frame.width, Math.ceil((rect.x + rect.width + pad) * scaleX));
399
+ const y1 = Math.min(frame.height, Math.ceil((rect.y + rect.height + pad) * scaleY));
400
+ return x1 > x0 && y1 > y0 ? { x: x0, y: y0, width: x1 - x0, height: y1 - y0 } : null;
401
+ }
402
+ function intersectsViewport(r, vw, vh) {
403
+ return r.width > 0 && r.height > 0 && r.x < vw && r.y < vh && r.x + r.width > 0 && r.y + r.height > 0;
404
+ }
405
+ var SKIP_TEXT_PARENTS = /^(SCRIPT|STYLE|NOSCRIPT|TEMPLATE|TEXTAREA|OPTION)$/;
406
+ function collectRedactions(doc, options = {}) {
407
+ const win = doc.defaultView;
408
+ const vw = win?.innerWidth ?? 0;
409
+ const vh = win?.innerHeight ?? 0;
410
+ const out = [];
411
+ const push = (r) => {
412
+ if (intersectsViewport(r, vw, vh)) out.push({ x: r.x, y: r.y, width: r.width, height: r.height });
413
+ };
414
+ const root = doc.body;
415
+ if (!root) return out;
416
+ const mask = options.maskSelector || "";
417
+ const blocks = /* @__PURE__ */ new Set();
418
+ try {
419
+ root.querySelectorAll(mask ? `${REDACT_SELECTOR},${mask}` : REDACT_SELECTOR).forEach((el) => blocks.add(el));
420
+ } catch {
421
+ root.querySelectorAll(REDACT_SELECTOR).forEach((el) => blocks.add(el));
422
+ }
423
+ root.querySelectorAll("input,textarea,select,[contenteditable]:not([contenteditable=false])").forEach((el) => {
424
+ if (blocks.has(el)) return;
425
+ const t = inputType(el);
426
+ if (t === "hidden" || t === "checkbox" || t === "radio" || t === "button" || t === "submit" || t === "reset") return;
427
+ if (isSensitiveField(el, mask)) blocks.add(el);
428
+ else if ((el.tagName === "INPUT" || el.tagName === "TEXTAREA") && findSensitiveText(el.value || "").length) blocks.add(el);
429
+ });
430
+ blocks.forEach((el) => push(el.getBoundingClientRect()));
431
+ const max = options.maxTextNodes ?? 5e3;
432
+ const walker = doc.createTreeWalker(
433
+ root,
434
+ 4
435
+ /* NodeFilter.SHOW_TEXT */
436
+ );
437
+ let range = null;
438
+ let seen = 0;
439
+ for (let node = walker.nextNode(); node && seen < max; node = walker.nextNode()) {
440
+ const text = node.nodeValue || "";
441
+ if (text.length < 7) continue;
442
+ seen++;
443
+ const parent = node.parentElement;
444
+ if (!parent || SKIP_TEXT_PARENTS.test(parent.tagName)) continue;
445
+ const hits = findSensitiveText(text);
446
+ if (!hits.length) continue;
447
+ if (Array.from(blocks).some((b) => b.contains(parent))) continue;
448
+ range ?? (range = doc.createRange());
449
+ for (const [start, end] of hits) {
450
+ try {
451
+ range.setStart(node, start);
452
+ range.setEnd(node, end);
453
+ Array.from(range.getClientRects()).forEach(push);
454
+ } catch {
455
+ }
456
+ }
457
+ }
458
+ return out;
459
+ }
460
+
461
+ // src/capture-screen.ts
462
+ function canCaptureScreen() {
463
+ return typeof navigator !== "undefined" && !!navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia === "function";
464
+ }
465
+ async function requestTabCapture(container, doc = document) {
466
+ if (!canCaptureScreen()) return { ok: false, reason: "unsupported" };
467
+ let stream;
468
+ try {
469
+ stream = await navigator.mediaDevices.getDisplayMedia({
470
+ video: { displaySurface: "browser", frameRate: 5 },
471
+ audio: false,
472
+ preferCurrentTab: true,
473
+ selfBrowserSurface: "include",
474
+ surfaceSwitching: "exclude"
475
+ });
476
+ } catch (err) {
477
+ return { ok: false, reason: err?.name === "NotAllowedError" ? "denied" : "error" };
478
+ }
479
+ const track = stream.getVideoTracks()[0];
480
+ if (!track) {
481
+ stream.getTracks().forEach((t) => t.stop());
482
+ return { ok: false, reason: "error" };
483
+ }
484
+ const surface = track.getSettings?.()?.displaySurface;
485
+ if (surface && surface !== "browser") {
486
+ stream.getTracks().forEach((t) => t.stop());
487
+ return { ok: false, reason: "wrong-surface" };
488
+ }
489
+ const video = doc.createElement("video");
490
+ video.muted = true;
491
+ video.playsInline = true;
492
+ video.setAttribute("aria-hidden", "true");
493
+ video.style.cssText = "position:fixed;left:0;top:0;width:2px;height:2px;opacity:0;pointer-events:none";
494
+ try {
495
+ video.srcObject = stream;
496
+ container.appendChild(video);
497
+ } catch {
498
+ stream.getTracks().forEach((t) => t.stop());
499
+ return { ok: false, reason: "error" };
500
+ }
501
+ try {
502
+ await video.play();
503
+ if (!video.videoWidth) await new Promise((resolve) => {
504
+ const done = () => resolve();
505
+ video.addEventListener("loadeddata", done, { once: true });
506
+ setTimeout(done, 1500);
507
+ });
508
+ } catch {
509
+ }
510
+ let ended = false;
511
+ const listeners = [];
512
+ const fv = video;
513
+ const rvfc = typeof fv.requestVideoFrameCallback === "function";
514
+ let frameAt = 0;
515
+ let waiters = [];
516
+ const onFrame = (now) => {
517
+ frameAt = now;
518
+ waiters.splice(0).forEach((w) => w());
519
+ if (!ended) fv.requestVideoFrameCallback(onFrame);
520
+ };
521
+ if (rvfc) fv.requestVideoFrameCallback(onFrame);
522
+ const end = () => {
523
+ if (ended) return;
524
+ ended = true;
525
+ stream.getTracks().forEach((t) => t.stop());
526
+ video.srcObject = null;
527
+ video.remove();
528
+ listeners.splice(0).forEach((cb) => cb());
529
+ };
530
+ track.addEventListener("ended", end);
531
+ return {
532
+ ok: true,
533
+ screen: {
534
+ live: () => !ended && track.readyState === "live",
535
+ onEnded: (cb) => ended ? cb() : listeners.push(cb),
536
+ grab: () => ended ? null : grabFrame(video, doc),
537
+ freshSince: (changedAt) => rvfc ? frameAt > changedAt : performance.now() - changedAt > 250,
538
+ nextFrame: (timeoutMs = 500) => new Promise((resolve) => {
539
+ const t = setTimeout(done, rvfc ? timeoutMs : 250);
540
+ function done() {
541
+ clearTimeout(t);
542
+ waiters = waiters.filter((w) => w !== done);
543
+ resolve();
544
+ }
545
+ if (rvfc) waiters.push(done);
546
+ }),
547
+ stop: () => {
548
+ listeners.length = 0;
549
+ end();
550
+ }
551
+ }
552
+ };
553
+ }
554
+ function grabFrame(video, doc = document) {
555
+ const w = video.videoWidth;
556
+ const h = video.videoHeight;
557
+ if (!w || !h) return null;
558
+ const canvas = doc.createElement("canvas");
559
+ canvas.width = w;
560
+ canvas.height = h;
561
+ const ctx = canvas.getContext("2d");
562
+ if (!ctx) return null;
563
+ ctx.drawImage(video, 0, 0, w, h);
564
+ return canvas;
565
+ }
566
+ function redactCanvas(canvas, rects, viewport) {
567
+ const ctx = canvas.getContext("2d");
568
+ if (!ctx || !rects.length || !viewport.width || !viewport.height) return 0;
569
+ const sx = canvas.width / viewport.width;
570
+ const sy = canvas.height / viewport.height;
571
+ const tmp = canvas.ownerDocument.createElement("canvas");
572
+ const tctx = tmp.getContext("2d");
573
+ if (!tctx) return 0;
574
+ let count = 0;
575
+ for (const rect of rects) {
576
+ const r = toFrameRect(rect, sx, sy, canvas, 4);
577
+ if (!r) continue;
578
+ const block = Math.max(16 * sy, Math.min(r.height, 48 * sy));
579
+ tmp.width = Math.max(1, Math.round(r.width / block));
580
+ tmp.height = Math.max(1, Math.round(r.height / block));
581
+ tctx.imageSmoothingEnabled = true;
582
+ tctx.drawImage(canvas, r.x, r.y, r.width, r.height, 0, 0, tmp.width, tmp.height);
583
+ ctx.imageSmoothingEnabled = true;
584
+ ctx.drawImage(tmp, 0, 0, tmp.width, tmp.height, r.x, r.y, r.width, r.height);
585
+ ctx.fillStyle = "rgba(120,120,120,.28)";
586
+ ctx.fillRect(r.x, r.y, r.width, r.height);
587
+ count++;
588
+ }
589
+ return count;
590
+ }
591
+ function scaled(canvas, maxWidth) {
592
+ if (canvas.width <= maxWidth) return canvas;
593
+ const out = canvas.ownerDocument.createElement("canvas");
594
+ out.width = maxWidth;
595
+ out.height = Math.round(canvas.height * maxWidth / canvas.width);
596
+ const ctx = out.getContext("2d");
597
+ if (!ctx) return canvas;
598
+ ctx.imageSmoothingQuality = "high";
599
+ ctx.drawImage(canvas, 0, 0, out.width, out.height);
600
+ return out;
601
+ }
602
+ var toBlob = (canvas, type, quality) => new Promise((resolve) => {
603
+ try {
604
+ canvas.toBlob((b) => resolve(b), type, quality);
605
+ } catch {
606
+ resolve(null);
607
+ }
608
+ });
609
+ async function encodeScreenshot(canvas, options = {}) {
610
+ const src = scaled(canvas, options.maxWidth ?? 2560);
611
+ const maxBytes = options.maxBytes ?? 8 * 1024 * 1024;
612
+ let blob = await toBlob(src, "image/webp", 0.85);
613
+ if (!blob || blob.type !== "image/webp") blob = await toBlob(src, "image/jpeg", 0.85);
614
+ if (blob && blob.size > maxBytes) blob = await toBlob(src, "image/jpeg", 0.6);
615
+ return blob && blob.size <= maxBytes ? blob : null;
616
+ }
617
+ function screenshotDataUrl(canvas) {
618
+ try {
619
+ return scaled(canvas, 1600).toDataURL("image/jpeg", 0.7);
620
+ } catch {
621
+ return null;
622
+ }
623
+ }
624
+
625
+ // src/styles.ts
626
+ var STYLES = `
627
+ :host{all:initial;position:static;display:block;contain:none}
628
+ *,*::before,*::after{box-sizing:border-box}
629
+ .g-scope [hidden]{display:none!important}
630
+ .g-scope{
631
+ --g-primary:#4f46e5;--g-bg:#ffffff;--g-text:#111827;--g-radius:12px;--g-font:Inter,system-ui,-apple-system,sans-serif;--g-z:2147483000;--g-overlay:.5;
632
+ --g-muted:color-mix(in srgb,var(--g-text) 62%,transparent);--g-border:color-mix(in srgb,var(--g-text) 12%,transparent);
633
+ --g-subtle:color-mix(in srgb,var(--g-text) 5%,transparent);--g-shadow:0 10px 40px rgba(0,0,0,.18),0 2px 8px rgba(0,0,0,.08);
634
+ --g-primary-text:#ffffff;
635
+ font-family:var(--g-font);font-size:14px;line-height:1.5;color:var(--g-text);-webkit-font-smoothing:antialiased;
636
+ }
637
+ .g-scope[data-theme="dark"]{--g-shadow:0 10px 40px rgba(0,0,0,.5),0 2px 8px rgba(0,0,0,.3)}
638
+ .g-live{position:fixed;width:1px;height:1px;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
639
+ .g-layer{position:fixed;inset:0;z-index:var(--g-z);pointer-events:none}
640
+ .g-overlay{position:fixed;inset:0;background:rgba(0,0,0,var(--g-overlay));z-index:calc(var(--g-z) + 3);pointer-events:auto}
641
+ .g-overlay.g-transparent{background:transparent}
642
+ .g-spot-rect{position:fixed;background:rgba(0,0,0,var(--g-overlay));z-index:calc(var(--g-z) + 3);pointer-events:auto}
643
+ .g-spot-ring{position:fixed;z-index:calc(var(--g-z) + 3);pointer-events:none;border-radius:6px;box-shadow:0 0 0 2px var(--g-primary),0 0 0 6px color-mix(in srgb,var(--g-primary) 30%,transparent)}
644
+ .g-card{
645
+ position:fixed;z-index:calc(var(--g-z) + 4);pointer-events:auto;background:var(--g-bg);color:var(--g-text);border-radius:var(--g-radius);
646
+ box-shadow:var(--g-shadow);border:1px solid var(--g-border);width:min(360px,calc(100vw - 32px));max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);overflow:auto;outline:none;
647
+ }
648
+ .g-card.g-modal{left:50%;top:50%;transform:translate(-50%,-50%);width:min(480px,calc(100vw - 32px))}
649
+ .g-card.g-tooltip,.g-card.g-popover,.g-card.g-spotlight-card,.g-card.g-hotspot-card{top:0;left:0;width:min(320px,calc(100vw - 32px))}
650
+ .g-card.g-slideout{width:min(360px,calc(100vw - 32px));margin:16px}
651
+ .g-pos-bottom-right{right:0;bottom:0}.g-pos-bottom-left{left:0;bottom:0}.g-pos-top-right{right:0;top:0}.g-pos-top-left{left:0;top:0}
652
+ .g-pos-center{left:50%;top:50%;transform:translate(-50%,-50%)}
653
+ .g-pos-top{top:0;left:0;right:0}.g-pos-bottom{bottom:0;left:0;right:0}
654
+ .g-banner{position:fixed;left:0;right:0;z-index:calc(var(--g-z) + 1);pointer-events:auto;background:var(--g-primary);color:var(--g-primary-text);padding:10px 48px 10px 16px;display:flex;align-items:center;gap:12px;font-size:14px;box-shadow:0 2px 10px rgba(0,0,0,.15);max-width:100vw}
655
+ .g-banner .g-body{flex:1;min-width:0}
656
+ .g-banner .g-body a{color:inherit;text-decoration:underline}
657
+ .g-banner .g-close{color:inherit;right:8px;top:50%;transform:translateY(-50%)}
658
+ .g-banner .g-btn{background:rgba(255,255,255,.18);color:inherit;border-color:transparent;min-height:36px}
659
+ .g-banner.g-pos-top{top:0}.g-banner.g-pos-bottom{bottom:0}
660
+ .g-inner{padding:20px}
661
+ .g-close{position:absolute;top:8px;right:8px;width:40px;height:40px;display:inline-flex;align-items:center;justify-content:center;border:0;background:transparent;color:var(--g-muted);border-radius:999px;cursor:pointer;font:inherit;font-size:20px;line-height:1}
662
+ .g-close:hover,.g-close:focus-visible{background:var(--g-subtle);color:var(--g-text);outline:none}
663
+ .g-close:focus-visible,.g-btn:focus-visible,.g-task:focus-visible,.g-choice:focus-visible,.g-scale button:focus-visible,.g-launcher:focus-visible,.g-input:focus-visible,.g-rc-item:focus-visible{outline:2px solid var(--g-primary);outline-offset:2px}
664
+ .g-image{display:block;width:100%;max-height:220px;object-fit:cover;border-radius:var(--g-radius) var(--g-radius) 0 0}
665
+ .g-title{margin:0 28px 6px 0;font-size:16px;font-weight:600;line-height:1.3}
666
+ .g-body{font-size:14px;color:var(--g-text)}
667
+ .g-body p{margin:0 0 8px}.g-body p:last-child{margin-bottom:0}
668
+ .g-body ul,.g-body ol{margin:6px 0;padding-left:20px}
669
+ .g-body a{color:var(--g-primary)}
670
+ .g-body img{max-width:100%;height:auto;border-radius:6px}
671
+ .g-body video,.g-body iframe{display:block;width:100%;max-width:100%;border:0;border-radius:6px;margin:8px 0;background:#000}
672
+ .g-body video{height:auto;max-height:60vh}
673
+ .g-body iframe{aspect-ratio:16/9;height:auto}
674
+ .g-body .gd-pdf{margin:10px 0;border:1px solid var(--g-border);border-radius:10px;background:var(--g-subtle);overflow:hidden}
675
+ .g-body a.gd-file{display:flex;align-items:center;gap:8px;padding:8px 10px;color:var(--g-text);font-size:13px;font-weight:500;text-decoration:none;min-height:40px;overflow-wrap:anywhere}
676
+ .g-body a.gd-file::before{content:"PDF";flex:none;font-size:9px;font-weight:700;letter-spacing:.04em;line-height:1;color:#fff;background:#e5484d;border-radius:4px;padding:4px 5px}
677
+ .g-body a.gd-file::after{content:"\u2922";margin-left:auto;padding-left:8px;color:var(--g-muted);font-size:15px}
678
+ .g-body a.gd-file:hover::after,.g-body a.gd-file:focus-visible::after{color:var(--g-primary)}
679
+ .g-body a.gd-file:focus-visible{outline:2px solid var(--g-primary);outline-offset:-2px;border-radius:10px}
680
+ .g-body > a.gd-file,.g-body p > a.gd-file{margin:10px 0;border:1px solid var(--g-border);border-radius:10px;background:var(--g-subtle)}
681
+ .g-body > a.gd-file::after,.g-body p > a.gd-file::after,.g-body .gd-pdf:not([data-g-ready]) > a.gd-file::after{content:"\u2197"}
682
+ .g-pdf-viewport{position:relative}
683
+ .g-body .gd-pdf-pages{display:flex;gap:10px;overflow-x:auto;scroll-snap-type:x mandatory;overscroll-behavior-x:contain;scrollbar-width:none;padding:0 10px 10px;scroll-padding:0 10px}
684
+ .g-body .gd-pdf-pages::-webkit-scrollbar{display:none}
685
+ .g-body .gd-pdf-pages:focus-visible{outline:2px solid var(--g-primary);outline-offset:-2px}
686
+ .g-body .gd-pdf-pages img{flex:0 0 100%;width:100%;height:auto;margin:0;scroll-snap-align:center;background:#fff;border-radius:4px;box-shadow:0 1px 3px rgba(0,0,0,.18);cursor:zoom-in}
687
+ .g-pdf-viewport .gd-pdf-pages{padding:0;scroll-padding:0;margin:0 10px 10px}
688
+ .g-pdf-count{position:absolute;right:18px;bottom:18px;padding:2px 8px;border-radius:999px;background:rgba(17,17,17,.72);color:#fff;font-size:11px;font-weight:500;pointer-events:none;font-variant-numeric:tabular-nums}
689
+ .g-pdf-nav{position:absolute;top:calc(50% - 21px);width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;border:0;border-radius:999px;background:rgba(17,17,17,.6);color:#fff;font:inherit;font-size:20px;line-height:1;cursor:pointer;opacity:0;transition:opacity .15s}
690
+ .g-pdf-prev{left:16px}.g-pdf-next{right:16px}
691
+ .g-pdf-viewport:hover .g-pdf-nav,.g-pdf-nav:focus-visible{opacity:1}
692
+ .g-pdf-nav:disabled,.g-pdf-single .g-pdf-nav,.g-pdf-single .g-pdf-count{display:none}
693
+ .g-pdf-nav:focus-visible{outline:2px solid #fff;outline-offset:1px}
694
+ @media (hover:none){.g-pdf-nav{display:none}}
695
+ .g-pdf-reader{position:fixed;inset:0;z-index:calc(var(--g-z) + 10);display:flex;flex-direction:column;background:#0b0b0d;pointer-events:auto;color:#fff}
696
+ .g-pdf-bar{display:flex;align-items:center;gap:10px;padding:8px 8px 8px 16px;min-height:56px}
697
+ .g-pdf-title{flex:1;min-width:0;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
698
+ .g-pdf-bar .g-btn{min-height:36px;color:#fff;background:rgba(255,255,255,.12);border-color:rgba(255,255,255,.18)}
699
+ .g-pdf-close{position:static;color:#fff}
700
+ .g-pdf-close:hover,.g-pdf-close:focus-visible{background:rgba(255,255,255,.14);color:#fff}
701
+ .g-pdf-scroll{position:relative;flex:1;overflow-y:auto;overscroll-behavior:contain;padding:4px 16px 24px;display:flex;flex-direction:column;align-items:center;gap:12px;outline:none}
702
+ .g-pdf-scroll img{display:block;width:100%;max-width:900px;height:auto;background:#fff;border-radius:2px;box-shadow:0 2px 12px rgba(0,0,0,.4)}
703
+ .g-body code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;background:var(--g-subtle);padding:1px 4px;border-radius:4px}
704
+ .g-body pre{background:var(--g-subtle);padding:10px;border-radius:6px;overflow:auto}
705
+ .g-body blockquote{margin:6px 0;padding-left:10px;border-left:3px solid var(--g-border);color:var(--g-muted)}
706
+ .g-footer{display:flex;align-items:center;gap:8px;margin-top:14px;flex-wrap:wrap}
707
+ .g-progress{font-size:12px;color:var(--g-muted);margin-right:auto}
708
+ .g-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:40px;padding:8px 14px;border-radius:calc(var(--g-radius) * .66);border:1px solid transparent;font:inherit;font-size:14px;font-weight:500;cursor:pointer;text-decoration:none;transition:background .15s,color .15s,border-color .15s}
709
+ .g-btn-primary{background:var(--g-primary);color:var(--g-primary-text)}
710
+ .g-btn-primary:hover{filter:brightness(1.08)}
711
+ .g-btn-secondary{background:var(--g-subtle);color:var(--g-text);border-color:var(--g-border)}
712
+ .g-btn-secondary:hover{background:color-mix(in srgb,var(--g-text) 10%,transparent)}
713
+ .g-btn-ghost{background:transparent;color:var(--g-muted)}
714
+ .g-btn-ghost:hover{background:var(--g-subtle);color:var(--g-text)}
715
+ .g-btn-link{background:transparent;color:var(--g-primary);padding-left:4px;padding-right:4px}
716
+ .g-btn-link:hover{text-decoration:underline}
717
+ .g-btn:disabled{opacity:.5;cursor:not-allowed}
718
+ .g-arrow{position:absolute;width:12px;height:12px;background:var(--g-bg);transform:rotate(45deg);border:1px solid var(--g-border);z-index:-1}
719
+ .g-hotspot{position:fixed;z-index:calc(var(--g-z) + 1);pointer-events:auto;border-radius:999px;background:var(--g-primary);border:2px solid #fff;box-shadow:0 0 0 2px color-mix(in srgb,var(--g-primary) 40%,transparent);cursor:pointer;padding:0;margin:0}
720
+ .g-hotspot::after{content:"";position:absolute;inset:-4px;border-radius:999px;border:2px solid var(--g-primary);opacity:0}
721
+ .g-hotspot.g-pulse::after{animation:g-pulse 1.8s ease-out infinite}
722
+ .g-hotspot-hit{position:absolute;inset:-14px}
723
+ .g-custom{position:fixed;z-index:calc(var(--g-z) + 4);pointer-events:auto;left:50%;top:50%;transform:translate(-50%,-50%);max-width:calc(100vw - 32px);max-height:calc(100vh - 32px);overflow:auto}
724
+ /* animations */
725
+ .g-scope[data-anim="fade"] .g-card,.g-scope[data-anim="fade"] .g-banner,.g-scope[data-anim="fade"] .g-panel{animation:g-fade .18s ease-out}
726
+ .g-scope[data-anim="slide"] .g-card,.g-scope[data-anim="slide"] .g-panel{animation:g-slide .22s ease-out}
727
+ .g-scope[data-anim="none"] *{animation:none!important;transition:none!important}
728
+ @keyframes g-fade{from{opacity:0}to{opacity:1}}
729
+ @keyframes g-slide{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
730
+ @keyframes g-pulse{0%{transform:scale(.6);opacity:.9}100%{transform:scale(2.2);opacity:0}}
731
+ @media (prefers-reduced-motion:reduce){.g-scope *{animation:none!important;transition:none!important}}
732
+ /* launcher + panel (checklist / resource center) */
733
+ .g-launcher{position:fixed;z-index:calc(var(--g-z) + 1);pointer-events:auto;display:inline-flex;align-items:center;gap:8px;min-height:44px;padding:10px 16px;border-radius:999px;border:0;background:var(--g-primary);color:var(--g-primary-text);font:inherit;font-size:14px;font-weight:600;cursor:pointer;box-shadow:var(--g-shadow);margin:16px}
734
+ .g-launcher .g-ring{width:22px;height:22px;border-radius:999px;background:conic-gradient(#fff var(--g-pct,0%),rgba(255,255,255,.3) 0);display:inline-flex;align-items:center;justify-content:center;font-size:10px;position:relative}
735
+ .g-launcher .g-ring::after{content:"";position:absolute;inset:3px;border-radius:999px;background:var(--g-primary)}
736
+ .g-launcher .g-count{position:relative;z-index:1}
737
+ .g-panel{position:fixed;z-index:calc(var(--g-z) + 2);pointer-events:auto;width:min(380px,calc(100vw - 32px));max-height:min(620px,calc(100vh - 100px));display:flex;flex-direction:column;background:var(--g-bg);color:var(--g-text);border-radius:var(--g-radius);box-shadow:var(--g-shadow);border:1px solid var(--g-border);margin:16px;bottom:60px;overflow:hidden}
738
+ .g-panel-head{padding:16px 48px 12px 20px;border-bottom:1px solid var(--g-border);position:relative}
739
+ .g-panel-title{margin:0;font-size:16px;font-weight:600}
740
+ .g-panel-sub{margin:4px 0 0;font-size:12px;color:var(--g-muted)}
741
+ .g-panel-body{overflow:auto;padding:8px 0;flex:1}
742
+ .g-help{display:flex;flex-direction:column;min-height:0;flex:1}
743
+ .g-bar{height:6px;border-radius:999px;background:var(--g-subtle);overflow:hidden;margin-top:10px}
744
+ .g-bar > span{display:block;height:100%;background:var(--g-primary);transition:width .3s}
745
+ .g-task{display:flex;gap:12px;align-items:flex-start;width:100%;padding:12px 20px;border:0;background:transparent;text-align:left;font:inherit;color:inherit;cursor:pointer;min-height:44px}
746
+ .g-task:hover{background:var(--g-subtle)}
747
+ .g-task[disabled]{cursor:not-allowed;opacity:.55}
748
+ .g-task .g-check{flex:none;width:22px;height:22px;border-radius:999px;border:2px solid var(--g-border);display:inline-flex;align-items:center;justify-content:center;font-size:13px;margin-top:1px;color:#fff}
749
+ .g-task.g-done .g-check{background:var(--g-primary);border-color:var(--g-primary)}
750
+ .g-task.g-done .g-task-title{text-decoration:line-through;color:var(--g-muted)}
751
+ .g-task-title{font-weight:500}
752
+ .g-task-desc{font-size:12px;color:var(--g-muted);margin-top:2px}
753
+ .g-panel-foot{padding:10px 20px;border-top:1px solid var(--g-border);display:flex;justify-content:flex-end;gap:8px}
754
+ .g-complete{padding:28px 20px;text-align:center}
755
+ .g-complete .g-title{margin-right:0;font-size:18px}
756
+ /* survey */
757
+ .g-question{margin-bottom:16px}
758
+ .g-q-title{font-weight:600;margin:0 0 4px}
759
+ .g-q-desc{font-size:12px;color:var(--g-muted);margin:0 0 8px}
760
+ .g-scale{display:flex;gap:4px;flex-wrap:wrap}
761
+ .g-scale button{min-width:34px;height:40px;flex:1;padding:0 4px;border-radius:6px;border:1px solid var(--g-border);background:var(--g-subtle);color:var(--g-text);font:inherit;cursor:pointer}
762
+ .g-scale button[aria-pressed="true"],.g-scale button:hover{background:var(--g-primary);color:var(--g-primary-text);border-color:var(--g-primary)}
763
+ .g-scale-labels{display:flex;justify-content:space-between;font-size:11px;color:var(--g-muted);margin-top:4px}
764
+ .g-stars button,.g-thumbs button{font-size:22px;background:transparent;border:0;cursor:pointer;color:var(--g-muted);min-width:40px;height:40px;font:inherit}
765
+ .g-stars button[aria-pressed="true"],.g-stars button.g-on{color:#f59e0b}
766
+ .g-thumbs button[aria-pressed="true"]{color:var(--g-primary)}
767
+ .g-choice{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--g-border);border-radius:8px;margin-bottom:6px;cursor:pointer;min-height:40px;font:inherit;background:transparent;color:inherit;width:100%;text-align:left}
768
+ .g-choice[aria-checked="true"]{border-color:var(--g-primary);background:color-mix(in srgb,var(--g-primary) 8%,transparent)}
769
+ .g-choice .g-dot{width:16px;height:16px;border-radius:999px;border:2px solid var(--g-border);flex:none}
770
+ .g-choice.g-multi .g-dot{border-radius:4px}
771
+ .g-choice[aria-checked="true"] .g-dot{background:var(--g-primary);border-color:var(--g-primary)}
772
+ .g-input{width:100%;min-height:40px;padding:8px 10px;border:1px solid var(--g-border);border-radius:8px;background:var(--g-bg);color:var(--g-text);font:inherit;resize:vertical}
773
+ .g-error{color:#dc2626;font-size:12px;margin-top:4px}
774
+ /* resource center */
775
+ .g-rc-search{padding:12px 20px 4px}
776
+ .g-rc-section{padding:8px 20px 4px;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--g-muted);font-weight:600}
777
+ .g-rc-section[data-depth]{text-transform:none;letter-spacing:0;font-size:12px}
778
+ .g-rc-item{display:flex;align-items:flex-start;gap:10px;width:100%;padding:10px 20px;border:0;background:transparent;font:inherit;color:inherit;text-align:left;cursor:pointer;min-height:44px;text-decoration:none}
779
+ .g-rc-item:hover{background:var(--g-subtle)}
780
+ .g-rc-item .g-rc-title{font-weight:500}
781
+ .g-rc-item .g-rc-sum{font-size:12px;color:var(--g-muted);margin-top:2px}
782
+ .g-rc-item .g-rc-icon{flex:none;width:20px;text-align:center;color:var(--g-primary)}
783
+ .g-rc-article{padding:16px 20px}
784
+ .g-rc-back{margin-bottom:8px}
785
+ .g-rc-empty{padding:24px 20px;color:var(--g-muted);text-align:center}
786
+ .g-faq-a{padding:0 20px 12px 50px;font-size:13px;color:var(--g-muted)}
787
+ .g-faq-a[hidden]{display:none}
788
+ /* authoring */
789
+ .g-auth-bar{position:fixed;top:0;left:0;right:0;z-index:calc(var(--g-z) + 10);pointer-events:auto;display:flex;align-items:center;gap:12px;padding:8px 16px;background:#111827;color:#fff;font:500 13px/1.4 var(--g-font);box-shadow:0 2px 10px rgba(0,0,0,.3)}
790
+ .g-auth-bar .g-dot-live{width:8px;height:8px;border-radius:999px;background:#22c55e;box-shadow:0 0 0 3px rgba(34,197,94,.3)}
791
+ .g-auth-bar .g-spacer{flex:1}
792
+ .g-auth-bar .g-btn{min-height:32px;padding:4px 12px;font-size:13px}
793
+ .g-auth-bar .g-btn-secondary{background:rgba(255,255,255,.12);color:#fff;border-color:transparent}
794
+ .g-auth-outline{position:fixed;z-index:calc(var(--g-z) + 9);pointer-events:none;border:2px solid var(--g-primary);background:color-mix(in srgb,var(--g-primary) 12%,transparent);border-radius:4px;transition:all .06s linear}
795
+ .g-auth-label{position:absolute;left:-2px;top:-24px;background:var(--g-primary);color:#fff;font:600 11px/1 var(--g-font);padding:5px 7px;border-radius:4px;white-space:nowrap;max-width:320px;overflow:hidden;text-overflow:ellipsis}
796
+ .g-auth-outline.g-picked{border-style:dashed;border-color:#22c55e;background:rgba(34,197,94,.12)}
797
+ .g-auth-outline.g-picked .g-auth-label{background:#16a34a}
798
+ .g-highlight{position:fixed;z-index:calc(var(--g-z) + 8);pointer-events:none;border:2px dashed #f59e0b;background:rgba(245,158,11,.12);border-radius:4px}
799
+ @media (max-width:480px){.g-card.g-modal,.g-panel{width:calc(100vw - 32px)}.g-inner{padding:16px}}
800
+ `;
801
+
802
+ // src/host.ts
803
+ var HOST_ID2 = "guidely-root";
804
+ var current = null;
805
+ var DEFAULT_THEME = {
806
+ primaryColor: "#4f46e5",
807
+ backgroundColor: "#ffffff",
808
+ textColor: "#111827",
809
+ borderRadius: 12,
810
+ fontFamily: "Inter, system-ui, -apple-system, sans-serif"
811
+ };
812
+ function ensureHost(doc = document) {
813
+ if (current && current.host.isConnected && current.host.ownerDocument === doc) return current;
814
+ let host = doc.getElementById(HOST_ID2);
815
+ if (!host) {
816
+ host = doc.createElement("div");
817
+ host.id = HOST_ID2;
818
+ host.setAttribute("data-guidely", "");
819
+ (doc.body || doc.documentElement).appendChild(host);
820
+ }
821
+ const shadow = host.shadowRoot ?? host.attachShadow({ mode: "open" });
822
+ if (!shadow.querySelector("style[data-g-base]")) {
823
+ const style = doc.createElement("style");
824
+ style.setAttribute("data-g-base", "");
825
+ style.textContent = STYLES;
826
+ shadow.appendChild(style);
827
+ }
828
+ let live = shadow.querySelector(".g-live");
829
+ if (!live) {
830
+ live = doc.createElement("div");
831
+ live.className = "g-live";
832
+ live.setAttribute("aria-live", "polite");
833
+ live.setAttribute("aria-atomic", "true");
834
+ shadow.appendChild(live);
835
+ }
836
+ const liveEl = live;
837
+ current = {
838
+ host,
839
+ shadow,
840
+ live: liveEl,
841
+ announce(text) {
842
+ liveEl.textContent = "";
843
+ setTimeout(() => {
844
+ liveEl.textContent = text;
845
+ }, 30);
846
+ },
847
+ destroy() {
848
+ host?.remove();
849
+ if (current?.host === host) current = null;
850
+ }
851
+ };
852
+ return current;
853
+ }
854
+ function prefersDark() {
855
+ try {
856
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches;
857
+ } catch {
858
+ return false;
859
+ }
860
+ }
861
+ function prefersReducedMotion() {
862
+ try {
863
+ return typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
864
+ } catch {
865
+ return false;
866
+ }
867
+ }
868
+ function resolveTheme(theme, appearance) {
869
+ const t = { ...DEFAULT_THEME, ...theme || {} };
870
+ const a = appearance || {};
871
+ const requested = a.theme ?? "auto";
872
+ const mode = requested === "auto" ? prefersDark() ? "dark" : "light" : requested;
873
+ const dark = mode === "dark";
874
+ return {
875
+ primary: a.primaryColor || t.primaryColor,
876
+ bg: a.backgroundColor || (dark ? "#1f2937" : t.backgroundColor),
877
+ text: a.textColor || (dark ? "#f9fafb" : t.textColor),
878
+ radius: a.borderRadius ?? t.borderRadius,
879
+ font: a.fontFamily || t.fontFamily,
880
+ z: a.zIndex ?? 2147483e3,
881
+ overlay: a.overlayOpacity ?? 0.5,
882
+ mode,
883
+ animation: prefersReducedMotion() ? "none" : a.animation ?? "fade"
884
+ };
885
+ }
886
+ function applyTheme(el, vars) {
887
+ el.style.setProperty("--g-primary", vars.primary);
888
+ el.style.setProperty("--g-bg", vars.bg);
889
+ el.style.setProperty("--g-text", vars.text);
890
+ el.style.setProperty("--g-radius", `${vars.radius}px`);
891
+ el.style.setProperty("--g-font", vars.font);
892
+ el.style.setProperty("--g-z", String(vars.z));
893
+ el.style.setProperty("--g-overlay", String(vars.overlay));
894
+ el.setAttribute("data-theme", vars.mode);
895
+ el.setAttribute("data-anim", vars.animation);
896
+ }
897
+ function createScope(theme, appearance, doc = document) {
898
+ const handle = ensureHost(doc);
899
+ const el = doc.createElement("div");
900
+ el.className = "g-scope";
901
+ const vars = resolveTheme(theme, appearance);
902
+ applyTheme(el, vars);
903
+ handle.shadow.appendChild(el);
904
+ let mq = null;
905
+ const onChange = () => applyTheme(el, resolveTheme(theme, appearance));
906
+ if ((appearance?.theme ?? "auto") === "auto" && typeof window !== "undefined" && typeof window.matchMedia === "function") {
907
+ try {
908
+ mq = window.matchMedia("(prefers-color-scheme: dark)");
909
+ mq.addEventListener?.("change", onChange);
910
+ } catch {
911
+ mq = null;
912
+ }
913
+ }
914
+ return {
915
+ el,
916
+ vars,
917
+ destroy() {
918
+ mq?.removeEventListener?.("change", onChange);
919
+ el.remove();
920
+ }
921
+ };
922
+ }
923
+
924
+ // src/capture.ts
925
+ var DOUBLE_CLICK_MS = 400;
926
+ var NAV_AFTER_ACTION_MS = 1500;
927
+ var NAV_SETTLE_MS = 600;
928
+ var NOTICES = {
929
+ "wrong-surface": "Share this tab (not a window or the whole screen) so screenshots match the page.",
930
+ denied: "Screen sharing was cancelled. Start again, or record without screenshots.",
931
+ error: "Screen capture could not start. Try again, or record without screenshots.",
932
+ unsupported: "This browser cannot capture screenshots; steps are recorded without them.",
933
+ lost: "Screenshots paused: sharing stopped or the page reloaded."
934
+ };
935
+ function button(doc, text, cls, attr) {
936
+ const b = doc.createElement("button");
937
+ b.type = "button";
938
+ b.className = `g-btn ${cls}`;
939
+ b.textContent = text;
940
+ b.setAttribute(attr, "");
941
+ return b;
942
+ }
943
+ function eventTarget(e) {
944
+ const path = typeof e.composedPath === "function" ? e.composedPath() : [];
945
+ const first = path[0] ?? e.target;
946
+ if (!first) return null;
947
+ return first.nodeType === 1 ? first : first.parentElement;
948
+ }
949
+ function deepActiveElementIn(el) {
950
+ const root = el.getRootNode();
951
+ return root.activeElement ?? null;
952
+ }
953
+ function deepActiveElement(doc) {
954
+ let el = doc.activeElement;
955
+ while (el?.shadowRoot?.activeElement) el = el.shadowRoot.activeElement;
956
+ return el;
957
+ }
958
+ function startCapture(options) {
959
+ const doc = options.doc ?? document;
960
+ const win = doc.defaultView ?? window;
961
+ const mask = options.maskSelector ?? "";
962
+ const shotsAllowed = options.screenshots !== false;
963
+ const scope = createScope(null, { theme: "light", animation: "fade" }, doc);
964
+ const style = doc.createElement("style");
965
+ style.textContent = CAPTURE_STYLES;
966
+ scope.el.appendChild(style);
967
+ const bar = doc.createElement("div");
968
+ bar.className = "g-cap";
969
+ bar.setAttribute("data-guidely-capture-bar", "");
970
+ bar.setAttribute("role", "toolbar");
971
+ bar.setAttribute("aria-label", "Guidely how-to recorder");
972
+ const note = doc.createElement("div");
973
+ note.className = "g-cap-note";
974
+ note.setAttribute("data-guidely-capture-notice", "");
975
+ note.hidden = true;
976
+ const row = doc.createElement("div");
977
+ row.className = "g-cap-row";
978
+ const dot = doc.createElement("span");
979
+ dot.className = "g-cap-dot";
980
+ const status = doc.createElement("span");
981
+ status.className = "g-cap-status";
982
+ status.setAttribute("aria-live", "polite");
983
+ const count_ = doc.createElement("span");
984
+ count_.className = "g-cap-count";
985
+ count_.setAttribute("aria-hidden", "true");
986
+ const start = button(doc, "Start recording", "g-btn-primary", "data-guidely-capture-start");
987
+ const noShots = button(doc, "Record without screenshots", "g-btn-secondary", "data-guidely-capture-no-screenshots");
988
+ const resumeShots = button(doc, "Screenshots paused \u2014 Resume", "g-cap-warn", "data-guidely-capture-resume-screenshots");
989
+ const pause = button(doc, "Pause", "g-btn-secondary", "data-guidely-capture-pause");
990
+ const done = button(doc, "Done", "g-btn-primary", "data-guidely-capture-done");
991
+ const cancel = button(doc, "Cancel", "g-btn-ghost", "data-guidely-capture-cancel");
992
+ row.append(dot, count_, status, start, noShots, resumeShots, pause, done, cancel);
993
+ bar.append(note, row);
994
+ scope.el.appendChild(bar);
995
+ let phase = "ready";
996
+ let count = options.stepCount ?? 0;
997
+ let screen = null;
998
+ let wantShots = false;
999
+ let requesting = false;
1000
+ let stopped = false;
1001
+ let lastAction = 0;
1002
+ let movedAt = -1;
1003
+ let lastPress = null;
1004
+ let lastUrl = win.location.href;
1005
+ let navTimer = null;
1006
+ let pulseTimer = null;
1007
+ let typedAt = -1;
1008
+ let expanded = false;
1009
+ const baseline = /* @__PURE__ */ new WeakMap();
1010
+ const recording = () => phase === "recording" && !stopped;
1011
+ const steps = (n) => `${n} step${n === 1 ? "" : "s"}`;
1012
+ const render = () => {
1013
+ bar.setAttribute("data-state", phase);
1014
+ bar.toggleAttribute("data-compact", phase === "recording" && !expanded);
1015
+ const ready = phase === "ready";
1016
+ const active = phase === "recording" || phase === "paused";
1017
+ start.hidden = !ready;
1018
+ start.disabled = requesting;
1019
+ noShots.hidden = !ready || !shotsAllowed || !canCaptureScreen();
1020
+ noShots.disabled = requesting;
1021
+ resumeShots.hidden = !active || !wantShots || !!screen?.live();
1022
+ resumeShots.disabled = requesting;
1023
+ pause.hidden = !active;
1024
+ pause.textContent = phase === "paused" ? "Resume" : "Pause";
1025
+ done.hidden = !active;
1026
+ cancel.hidden = phase === "saving";
1027
+ if (ready) status.textContent = requesting ? "Choose this tab to share\u2026" : `Ready to record \u201C${options.title}\u201D`;
1028
+ else if (phase === "saving") status.textContent = "Saving steps\u2026";
1029
+ else status.textContent = `${phase === "paused" ? "Paused" : "Recording"} \xB7 ${steps(count)}`;
1030
+ count_.textContent = String(count);
1031
+ };
1032
+ const showNotice = (text) => {
1033
+ note.textContent = text ?? "";
1034
+ note.hidden = !text;
1035
+ };
1036
+ const pulse = () => {
1037
+ bar.setAttribute("data-captured", "");
1038
+ if (pulseTimer) clearTimeout(pulseTimer);
1039
+ pulseTimer = setTimeout(() => bar.removeAttribute("data-captured"), 700);
1040
+ };
1041
+ const shoot = (step, highlightEl) => {
1042
+ const frame = screen?.live() ? screen.grab() : null;
1043
+ if (!frame) return null;
1044
+ const viewport = { width: win.innerWidth, height: win.innerHeight };
1045
+ try {
1046
+ step.redactions = redactCanvas(frame, collectRedactions(doc, { maskSelector: mask }), viewport);
1047
+ } catch {
1048
+ return null;
1049
+ }
1050
+ const highlight = highlightEl?.isConnected ? normalizeRect(highlightEl.getBoundingClientRect(), viewport) : null;
1051
+ if (highlight) step.highlight = highlight;
1052
+ else delete step.highlight;
1053
+ return frame;
1054
+ };
1055
+ const record = (action, el, extra = {}) => {
1056
+ const step = { action, url: win.location.href, pageTitle: (doc.title || "").slice(0, 300), redactions: 0, occurredAt: (/* @__PURE__ */ new Date()).toISOString() };
1057
+ let highlightEl = null;
1058
+ if (el) {
1059
+ const masked = isMaskedElement(el, mask);
1060
+ step.element = describeCapturedElement(el, mask);
1061
+ step.selectors = describeElement(el).selectors.filter((s) => !masked || s.kind !== "text");
1062
+ const box = extra.highlightEl?.getBoundingClientRect();
1063
+ highlightEl = extra.highlightEl && box && (box.width || box.height) ? extra.highlightEl : el;
1064
+ }
1065
+ let frame = null;
1066
+ if (screen?.live()) {
1067
+ const s = screen;
1068
+ const changedAt = action === "type" || action === "select" ? Math.max(movedAt, typedAt) : movedAt;
1069
+ if (s.freshSince(changedAt)) frame = shoot(step, highlightEl);
1070
+ else frame = s.nextFrame().then(() => shoot(step, highlightEl));
1071
+ }
1072
+ if (extra.value !== void 0) step.value = extra.value;
1073
+ if (extra.valueRedacted) step.valueRedacted = true;
1074
+ if (action !== "navigate") lastAction = Date.now();
1075
+ count++;
1076
+ render();
1077
+ pulse();
1078
+ options.onStep({ step, frame });
1079
+ };
1080
+ const flushField = (el) => {
1081
+ if (!recording() || !isTextEntry(el)) return;
1082
+ const host = el.tagName === "INPUT" || el.tagName === "TEXTAREA" ? el : actionTarget(el);
1083
+ const value = fieldValue(host);
1084
+ if (value === (baseline.get(host) ?? "")) return;
1085
+ baseline.set(host, value);
1086
+ if (!value) return;
1087
+ if (isSensitiveField(host, mask) || looksLikeSecretValue(value)) record("type", host, { valueRedacted: true });
1088
+ else record("type", host, { value });
1089
+ };
1090
+ const onPointerDown = (e) => {
1091
+ if (!recording() || e.button !== 0) return;
1092
+ const target = eventTarget(e);
1093
+ if (!target || isGuidelyElement(target)) return;
1094
+ const active = deepActiveElement(doc);
1095
+ if (active && active !== target && !active.contains(target) && isTextEntry(active)) flushField(active);
1096
+ let el = actionTarget(target);
1097
+ let highlightEl = null;
1098
+ const control = labelControl(el);
1099
+ if (control) {
1100
+ if (isTextEntry(control) || control.tagName === "SELECT") return;
1101
+ highlightEl = el;
1102
+ el = control;
1103
+ }
1104
+ if (isTextEntry(el) || el.tagName === "SELECT" || el.closest("select")) return;
1105
+ if (el.disabled) return;
1106
+ const now = Date.now();
1107
+ if (lastPress && lastPress.el === el && now - lastPress.at < DOUBLE_CLICK_MS) {
1108
+ lastPress.at = now;
1109
+ return;
1110
+ }
1111
+ lastPress = { el, at: now };
1112
+ record("click", el, { highlightEl });
1113
+ };
1114
+ const onChange = (e) => {
1115
+ if (!recording()) return;
1116
+ const el = eventTarget(e);
1117
+ if (!el || isGuidelyElement(el)) return;
1118
+ if (el.tagName === "SELECT") {
1119
+ const sensitive = isSensitiveField(el, mask);
1120
+ const value = sensitive ? "" : selectedOptionText(el);
1121
+ record("select", el, sensitive ? { valueRedacted: true } : value ? { value } : {});
1122
+ return;
1123
+ }
1124
+ const type = el.tagName === "INPUT" ? (el.getAttribute("type") || "").toLowerCase() : "";
1125
+ if (type === "checkbox" || type === "radio") {
1126
+ const pressed = lastPress && Date.now() - lastPress.at < 1e3 && lastPress.el === el;
1127
+ if (!pressed) record("click", el);
1128
+ return;
1129
+ }
1130
+ if (isTextEntry(el)) flushField(el);
1131
+ };
1132
+ const onFocusIn = (e) => {
1133
+ const el = eventTarget(e);
1134
+ if (el && !isGuidelyElement(el) && isTextEntry(el)) {
1135
+ const host = el.tagName === "INPUT" || el.tagName === "TEXTAREA" ? el : actionTarget(el);
1136
+ baseline.set(host, fieldValue(host));
1137
+ }
1138
+ };
1139
+ const onFocusOut = (e) => {
1140
+ const el = eventTarget(e);
1141
+ if (el && el.tagName !== "INPUT" && el.tagName !== "TEXTAREA" && isTextEntry(el)) flushField(el);
1142
+ };
1143
+ const onKeyDown = (e) => {
1144
+ if (e.key !== "Enter" || e.isComposing) return;
1145
+ const el = eventTarget(e);
1146
+ if (el && (el.tagName === "INPUT" || (e.metaKey || e.ctrlKey) && isTextEntry(el))) flushField(el);
1147
+ };
1148
+ const checkUrl = () => {
1149
+ const url = win.location.href;
1150
+ if (url === lastUrl) return;
1151
+ lastUrl = url;
1152
+ if (!recording() || Date.now() - lastAction < NAV_AFTER_ACTION_MS) return;
1153
+ if (navTimer) clearTimeout(navTimer);
1154
+ navTimer = setTimeout(() => {
1155
+ navTimer = null;
1156
+ if (recording()) record("navigate", null);
1157
+ }, NAV_SETTLE_MS);
1158
+ };
1159
+ const poll = setInterval(checkUrl, 300);
1160
+ const onMove = () => {
1161
+ movedAt = performance.now();
1162
+ };
1163
+ const onInput = (e) => {
1164
+ const el = eventTarget(e);
1165
+ if (el && !isGuidelyElement(el)) typedAt = performance.now();
1166
+ };
1167
+ const expand = (open) => {
1168
+ if (expanded === open) return;
1169
+ expanded = open;
1170
+ render();
1171
+ };
1172
+ const onBarEnter = () => expand(true);
1173
+ const onBarLeave = () => expand(bar.contains(deepActiveElementIn(bar)));
1174
+ const onBarFocusOut = (e) => expand(!!e.relatedTarget && bar.contains(e.relatedTarget));
1175
+ bar.addEventListener("pointerenter", onBarEnter);
1176
+ bar.addEventListener("pointerleave", onBarLeave);
1177
+ bar.addEventListener("focusin", onBarEnter);
1178
+ bar.addEventListener("focusout", onBarFocusOut);
1179
+ const listen = { capture: true, passive: true };
1180
+ doc.addEventListener("scroll", onMove, listen);
1181
+ win.addEventListener("resize", onMove);
1182
+ doc.addEventListener("pointerdown", onPointerDown, listen);
1183
+ doc.addEventListener("change", onChange, listen);
1184
+ doc.addEventListener("input", onInput, listen);
1185
+ doc.addEventListener("focusin", onFocusIn, listen);
1186
+ doc.addEventListener("focusout", onFocusOut, listen);
1187
+ doc.addEventListener("keydown", onKeyDown, listen);
1188
+ win.addEventListener("popstate", checkUrl);
1189
+ win.addEventListener("hashchange", checkUrl);
1190
+ const onLost = () => {
1191
+ screen = null;
1192
+ if (!stopped && wantShots) showNotice(NOTICES.lost);
1193
+ render();
1194
+ };
1195
+ const requestScreen = async () => {
1196
+ requesting = true;
1197
+ showNotice(null);
1198
+ render();
1199
+ const res = await requestTabCapture(scope.el, doc);
1200
+ requesting = false;
1201
+ if (stopped) {
1202
+ if (res.ok) res.screen.stop();
1203
+ return false;
1204
+ }
1205
+ if (res.ok) {
1206
+ screen = res.screen;
1207
+ screen.onEnded(onLost);
1208
+ } else showNotice(NOTICES[res.reason]);
1209
+ render();
1210
+ return res.ok;
1211
+ };
1212
+ const begin = (shots) => {
1213
+ wantShots = shots;
1214
+ phase = "recording";
1215
+ expanded = false;
1216
+ movedAt = performance.now();
1217
+ primeActive();
1218
+ lastUrl = win.location.href;
1219
+ render();
1220
+ options.onStart?.({ screenshots: shots });
1221
+ record("navigate", null);
1222
+ };
1223
+ start.addEventListener("click", () => {
1224
+ if (requesting) return;
1225
+ if (!shotsAllowed || !canCaptureScreen()) {
1226
+ if (shotsAllowed) showNotice(NOTICES.unsupported);
1227
+ return begin(false);
1228
+ }
1229
+ void requestScreen().then((ok) => {
1230
+ if (ok) begin(true);
1231
+ });
1232
+ });
1233
+ noShots.addEventListener("click", () => {
1234
+ showNotice(null);
1235
+ begin(false);
1236
+ });
1237
+ resumeShots.addEventListener("click", () => {
1238
+ if (!requesting) void requestScreen();
1239
+ });
1240
+ pause.addEventListener("click", () => {
1241
+ if (phase === "recording") {
1242
+ flushActive();
1243
+ phase = "paused";
1244
+ } else if (phase === "paused") {
1245
+ phase = "recording";
1246
+ lastUrl = win.location.href;
1247
+ }
1248
+ render();
1249
+ });
1250
+ const flushActive = () => {
1251
+ const active = deepActiveElement(doc);
1252
+ if (active && !isGuidelyElement(active) && isTextEntry(active)) flushField(active);
1253
+ };
1254
+ const primeActive = () => {
1255
+ const active = deepActiveElement(doc);
1256
+ if (active && !isGuidelyElement(active) && isTextEntry(active)) baseline.set(active, fieldValue(active));
1257
+ };
1258
+ done.addEventListener("click", () => {
1259
+ if (phase === "saving") return;
1260
+ flushActive();
1261
+ options.onFinish();
1262
+ });
1263
+ cancel.addEventListener("click", () => {
1264
+ stop();
1265
+ options.onCancel();
1266
+ });
1267
+ const stop = () => {
1268
+ if (stopped) return;
1269
+ stopped = true;
1270
+ clearInterval(poll);
1271
+ if (navTimer) clearTimeout(navTimer);
1272
+ if (pulseTimer) clearTimeout(pulseTimer);
1273
+ doc.removeEventListener("scroll", onMove, listen);
1274
+ win.removeEventListener("resize", onMove);
1275
+ doc.removeEventListener("pointerdown", onPointerDown, listen);
1276
+ doc.removeEventListener("change", onChange, listen);
1277
+ doc.removeEventListener("input", onInput, listen);
1278
+ doc.removeEventListener("focusin", onFocusIn, listen);
1279
+ doc.removeEventListener("focusout", onFocusOut, listen);
1280
+ doc.removeEventListener("keydown", onKeyDown, listen);
1281
+ win.removeEventListener("popstate", checkUrl);
1282
+ win.removeEventListener("hashchange", checkUrl);
1283
+ screen?.stop();
1284
+ screen = null;
1285
+ scope.destroy();
1286
+ };
1287
+ if (options.resume) {
1288
+ wantShots = shotsAllowed && options.resume.screenshots && canCaptureScreen();
1289
+ phase = "recording";
1290
+ if (wantShots) showNotice(NOTICES.lost);
1291
+ primeActive();
1292
+ render();
1293
+ if (options.resume.navigate) record("navigate", null);
1294
+ } else render();
1295
+ return {
1296
+ setSaving: (saving) => {
1297
+ if (stopped) return;
1298
+ phase = saving ? "saving" : "recording";
1299
+ render();
1300
+ },
1301
+ isRecording: recording,
1302
+ stop
1303
+ };
1304
+ }
1305
+ // Annotate the CommonJS export names for ESM import in node:
1306
+ 0 && (module.exports = {
1307
+ CAPTURE_INTERACTIVE_SELECTOR,
1308
+ DOUBLE_CLICK_MS,
1309
+ NAV_AFTER_ACTION_MS,
1310
+ NAV_SETTLE_MS,
1311
+ REDACT_SELECTOR,
1312
+ actionTarget,
1313
+ canCaptureScreen,
1314
+ captureLabel,
1315
+ captureRole,
1316
+ classifyNumber,
1317
+ collectRedactions,
1318
+ describeCapturedElement,
1319
+ encodeScreenshot,
1320
+ fieldValue,
1321
+ findSensitiveText,
1322
+ grabFrame,
1323
+ isMaskedElement,
1324
+ isSensitiveField,
1325
+ isTextEntry,
1326
+ labelControl,
1327
+ looksLikeSecretValue,
1328
+ normalizeRect,
1329
+ redactCanvas,
1330
+ requestTabCapture,
1331
+ screenshotDataUrl,
1332
+ selectedOptionText,
1333
+ startCapture,
1334
+ toFrameRect
1335
+ });
1336
+ //# sourceMappingURL=capture.cjs.map