@launchsecure/launch-beacon 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.
@@ -0,0 +1,722 @@
1
+ const N = ':host{--beacon-accent: #0ea5e9;--beacon-bg: #ffffff;--beacon-fg: #0f172a;--beacon-muted: #64748b;--beacon-border: #e2e8f0;--beacon-radius: 10px;--beacon-z-index: 2147483645;--beacon-shadow: 0 10px 30px rgba(0, 0, 0, .15), 0 2px 6px rgba(0, 0, 0, .08);--beacon-bug: #ef4444;--beacon-idea: #22c55e;--beacon-ux: #a855f7;--beacon-a11y: #06b6d4;position:fixed;z-index:var(--beacon-z-index);font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:14px;color:var(--beacon-fg);line-height:1.5}:host([theme="dark"]){--beacon-bg: #0f172a;--beacon-fg: #f1f5f9;--beacon-muted: #94a3b8;--beacon-border: #334155}@media (prefers-color-scheme: dark){:host([theme="auto"]){--beacon-bg: #0f172a;--beacon-fg: #f1f5f9;--beacon-muted: #94a3b8;--beacon-border: #334155}}:host([position="bottom-right"]){right:20px;bottom:20px;left:auto;top:auto}:host([position="bottom-left"]){left:20px;bottom:20px;right:auto;top:auto}:host([position="hidden"]){position:static;display:contents}:host([position="hidden"]) .beacon-default-trigger{display:none}.beacon-default-trigger{display:inline-flex;align-items:center;gap:6px;background:var(--beacon-accent);color:#fff;border:0;border-radius:999px;padding:10px 16px;font:inherit;font-weight:500;cursor:pointer;box-shadow:var(--beacon-shadow);transition:transform .15s ease,box-shadow .15s ease}.beacon-default-trigger:hover{transform:translateY(-1px);box-shadow:0 14px 36px #0003}.beacon-default-trigger svg{width:18px;height:18px}.beacon-drawer{position:fixed;right:20px;bottom:20px;width:380px;max-width:calc(100vw - 24px);max-height:calc(100vh - 24px);background:var(--beacon-bg);color:var(--beacon-fg);border:1px solid var(--beacon-border);border-radius:var(--beacon-radius);box-shadow:var(--beacon-shadow);display:none;flex-direction:column;overflow:hidden;z-index:var(--beacon-z-index)}:host([position="bottom-left"]) .beacon-drawer{left:20px;right:auto}.beacon-drawer.open{display:flex}.beacon-drawer.minimized{display:none}.beacon-drawer-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--beacon-border)}.beacon-drawer-title{font-weight:600}.beacon-icon-btn{background:transparent;border:0;color:var(--beacon-muted);cursor:pointer;padding:4px;border-radius:4px;display:inline-flex}.beacon-icon-btn:hover{background:var(--beacon-border);color:var(--beacon-fg)}.beacon-icon-btn svg{width:16px;height:16px}.beacon-drawer-body{padding:16px;overflow-y:auto;display:flex;flex-direction:column;gap:14px}.beacon-field{display:flex;flex-direction:column;gap:6px}.beacon-label{font-size:12px;color:var(--beacon-muted);font-weight:500}.beacon-textarea{width:100%;min-height:80px;padding:8px 10px;background:var(--beacon-bg);color:var(--beacon-fg);border:1px solid var(--beacon-border);border-radius:6px;font:inherit;resize:vertical;box-sizing:border-box}.beacon-textarea:focus{outline:2px solid var(--beacon-accent);outline-offset:-1px;border-color:var(--beacon-accent)}.beacon-severity{display:grid;grid-template-columns:repeat(4,1fr);gap:6px}.beacon-severity-opt{position:relative;display:flex;align-items:center;justify-content:center;padding:6px 4px;background:transparent;border:1px solid var(--beacon-border);border-radius:6px;cursor:pointer;font-size:12px;font-weight:500;color:var(--beacon-fg);text-transform:capitalize}.beacon-severity-opt input{position:absolute;opacity:0;pointer-events:none}.beacon-severity-opt:hover{background:var(--beacon-border)}.beacon-severity-opt.selected[data-sev=bug]{border-color:var(--beacon-bug);color:var(--beacon-bug)}.beacon-severity-opt.selected[data-sev=idea]{border-color:var(--beacon-idea);color:var(--beacon-idea)}.beacon-severity-opt.selected[data-sev=ux]{border-color:var(--beacon-ux);color:var(--beacon-ux)}.beacon-severity-opt.selected[data-sev=a11y]{border-color:var(--beacon-a11y);color:var(--beacon-a11y)}.beacon-actions{display:flex;gap:8px;align-items:center}.beacon-btn{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border-radius:6px;border:0;cursor:pointer;font:inherit;font-weight:500;font-size:13px;transition:opacity .15s ease}.beacon-btn[disabled]{opacity:.5;cursor:not-allowed}.beacon-btn svg{width:14px;height:14px}.beacon-btn.primary{background:var(--beacon-accent);color:#fff}.beacon-btn.secondary{background:transparent;color:var(--beacon-fg);border:1px solid var(--beacon-border)}.beacon-btn.secondary:hover{background:var(--beacon-border)}.beacon-pin-list{display:flex;flex-direction:column;gap:6px;max-height:200px;overflow-y:auto}.beacon-pin-item{display:flex;align-items:flex-start;gap:8px;padding:8px;background:var(--beacon-border);border-radius:6px}.beacon-pin-num{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;background:var(--beacon-accent);color:#fff;font-size:11px;font-weight:600;flex-shrink:0}.beacon-pin-meta{flex:1;min-width:0}.beacon-pin-selector{font:11px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;color:var(--beacon-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.beacon-pin-note{margin-top:4px;font-size:12px;color:var(--beacon-fg)}.beacon-pin-note-input{margin-top:4px;width:100%;padding:4px 6px;background:var(--beacon-bg);color:var(--beacon-fg);border:1px solid var(--beacon-border);border-radius:4px;font:inherit;font-size:12px;box-sizing:border-box}.beacon-thumb{width:100%;border:1px solid var(--beacon-border);border-radius:6px;display:block}.beacon-status{font-size:12px;color:var(--beacon-muted);text-align:center;padding:4px}.beacon-status.error{color:var(--beacon-bug)}.beacon-status.success{color:var(--beacon-idea)}.beacon-pin-popover{position:fixed;z-index:2147483647;width:240px;background:var(--beacon-bg);color:var(--beacon-fg);border:1px solid var(--beacon-border);border-radius:6px;box-shadow:var(--beacon-shadow);padding:10px;display:none;flex-direction:column;gap:8px}.beacon-pin-popover.open{display:flex}.beacon-pin-popover-actions{display:flex;gap:6px;justify-content:flex-end}', b = {
2
+ feedback: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',
3
+ pin: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 17v5"/><path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"/></svg>',
4
+ close: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',
5
+ trash: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 6h18"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>',
6
+ send: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m22 2-7 20-4-9-9-4Z"/><path d="M22 2 11 13"/></svg>',
7
+ check: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"/></svg>'
8
+ };
9
+ function D(o = "Feedback") {
10
+ const e = document.createElement("button");
11
+ return e.type = "button", e.className = "beacon-default-trigger", e.setAttribute("aria-label", o), e.innerHTML = `${b.feedback}<span>${o}</span>`, e;
12
+ }
13
+ const z = ["bug", "idea", "ux", "a11y"];
14
+ class I {
15
+ constructor(e, t) {
16
+ this.state = {
17
+ description: "",
18
+ severity: "bug",
19
+ pins: [],
20
+ annotatedScreenshot: void 0,
21
+ submitting: !1,
22
+ status: "",
23
+ statusKind: ""
24
+ }, this.severities = e.length > 0 ? e : z, this.callbacks = t, this.root = document.createElement("div"), this.root.className = "beacon-drawer", this.root.innerHTML = `
25
+ <div class="beacon-drawer-header">
26
+ <div class="beacon-drawer-title">Send feedback</div>
27
+ <button type="button" class="beacon-icon-btn" data-action="close" aria-label="Close">${b.close}</button>
28
+ </div>
29
+ <div class="beacon-drawer-body"></div>
30
+ `, this.bodyEl = this.root.querySelector(".beacon-drawer-body"), this.root.querySelector('[data-action="close"]').addEventListener("click", () => this.callbacks.onClose()), this.render();
31
+ }
32
+ open() {
33
+ this.root.classList.remove("minimized"), this.root.classList.add("open");
34
+ }
35
+ close() {
36
+ this.root.classList.remove("open");
37
+ }
38
+ minimize() {
39
+ this.root.classList.add("minimized"), this.root.classList.remove("open");
40
+ }
41
+ setPins(e, t) {
42
+ this.state.pins = e, t !== void 0 && (this.state.annotatedScreenshot = t), this.render();
43
+ }
44
+ setSubmitting(e) {
45
+ this.state.submitting = e, this.render();
46
+ }
47
+ setStatus(e, t = "") {
48
+ this.state.status = e, this.state.statusKind = t, this.render();
49
+ }
50
+ reset() {
51
+ this.state = {
52
+ description: "",
53
+ severity: "bug",
54
+ pins: [],
55
+ annotatedScreenshot: void 0,
56
+ submitting: !1,
57
+ status: "",
58
+ statusKind: ""
59
+ }, this.render();
60
+ }
61
+ getDescription() {
62
+ return this.state.description;
63
+ }
64
+ getSeverity() {
65
+ return this.state.severity;
66
+ }
67
+ render() {
68
+ var n, a;
69
+ const e = this.state.description.trim().length > 0 && !this.state.submitting;
70
+ this.bodyEl.innerHTML = `
71
+ <div class="beacon-field">
72
+ <label class="beacon-label" for="beacon-desc">What's the issue?</label>
73
+ <textarea id="beacon-desc" class="beacon-textarea" placeholder="Describe what you saw, what you expected, anything that helps reproduce…">${m(this.state.description)}</textarea>
74
+ </div>
75
+
76
+ <div class="beacon-field">
77
+ <span class="beacon-label">Severity</span>
78
+ <div class="beacon-severity" role="radiogroup">
79
+ ${this.severities.map((i) => `
80
+ <label class="beacon-severity-opt ${i === this.state.severity ? "selected" : ""}" data-sev="${i}">
81
+ <input type="radio" name="beacon-severity" value="${i}" ${i === this.state.severity ? "checked" : ""}>
82
+ ${i}
83
+ </label>
84
+ `).join("")}
85
+ </div>
86
+ </div>
87
+
88
+ ${this.state.annotatedScreenshot ? `
89
+ <div class="beacon-field">
90
+ <span class="beacon-label">Annotated screenshot</span>
91
+ <img src="${this.state.annotatedScreenshot}" class="beacon-thumb" alt="Annotated screenshot preview">
92
+ </div>
93
+ ` : ""}
94
+
95
+ ${this.state.pins.length > 0 ? `
96
+ <div class="beacon-field">
97
+ <span class="beacon-label">Pins (${this.state.pins.length})</span>
98
+ <div class="beacon-pin-list">
99
+ ${this.state.pins.map((i) => `
100
+ <div class="beacon-pin-item" data-pin="${i.number}">
101
+ <span class="beacon-pin-num">${i.number}</span>
102
+ <div class="beacon-pin-meta">
103
+ <div class="beacon-pin-selector" title="${m(i.selector)}">${m(i.selector)}</div>
104
+ <input type="text" class="beacon-pin-note-input" placeholder="Add a note (optional)" value="${m(i.note ?? "")}">
105
+ </div>
106
+ <button type="button" class="beacon-icon-btn" data-pin-delete="${i.number}" aria-label="Remove pin ${i.number}">${b.trash}</button>
107
+ </div>
108
+ `).join("")}
109
+ </div>
110
+ </div>
111
+ ` : ""}
112
+
113
+ <div class="beacon-actions">
114
+ <button type="button" class="beacon-btn secondary" data-action="annotate" ${this.state.submitting ? "disabled" : ""}>
115
+ ${b.pin} ${this.state.pins.length > 0 ? "Add another pin" : "Annotate elements"}
116
+ </button>
117
+ <span style="flex:1"></span>
118
+ <button type="button" class="beacon-btn primary" data-action="submit" ${e ? "" : "disabled"}>
119
+ ${b.send} ${this.state.submitting ? "Sending…" : "Send"}
120
+ </button>
121
+ </div>
122
+
123
+ ${this.state.status ? `<div class="beacon-status ${this.state.statusKind}">${m(this.state.status)}</div>` : ""}
124
+ `;
125
+ const t = this.bodyEl.querySelector("#beacon-desc");
126
+ t.addEventListener("input", () => {
127
+ this.state.description = t.value, this.callbacks.onDescriptionChange(t.value);
128
+ const i = this.bodyEl.querySelector('[data-action="submit"]');
129
+ i && (i.disabled = !(t.value.trim().length > 0) || this.state.submitting);
130
+ }), this.bodyEl.querySelectorAll('input[name="beacon-severity"]').forEach((i) => {
131
+ i.addEventListener("change", () => {
132
+ i.checked && (this.state.severity = i.value, this.callbacks.onSeverityChange(this.state.severity), this.render());
133
+ });
134
+ }), (n = this.bodyEl.querySelector('[data-action="annotate"]')) == null || n.addEventListener("click", () => this.callbacks.onAnnotate()), (a = this.bodyEl.querySelector('[data-action="submit"]')) == null || a.addEventListener("click", () => this.callbacks.onSubmit()), this.bodyEl.querySelectorAll("[data-pin-delete]").forEach((i) => {
135
+ i.addEventListener("click", () => {
136
+ const s = Number(i.getAttribute("data-pin-delete"));
137
+ this.callbacks.onPinDelete(s);
138
+ });
139
+ }), this.bodyEl.querySelectorAll(".beacon-pin-note-input").forEach((i) => {
140
+ const s = i.closest("[data-pin]"), r = Number((s == null ? void 0 : s.getAttribute("data-pin")) ?? 0);
141
+ i.addEventListener("input", () => this.callbacks.onPinNoteChange(r, i.value));
142
+ });
143
+ }
144
+ }
145
+ function m(o) {
146
+ return o.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
147
+ }
148
+ function j(o) {
149
+ return new Promise((e) => {
150
+ const t = document.createElement("div");
151
+ t.className = "beacon-pin-popover open", t.innerHTML = `
152
+ <div style="display:flex;align-items:center;gap:6px;font-weight:600;font-size:13px;">
153
+ <span class="beacon-pin-num">${o.pinNumber}</span>
154
+ <span>Note for this pin</span>
155
+ </div>
156
+ <textarea class="beacon-textarea" rows="2" placeholder="Optional — describe what's wrong here…" style="min-height:48px;"></textarea>
157
+ <div class="beacon-pin-popover-actions">
158
+ <button type="button" class="beacon-btn secondary" data-action="skip">Skip</button>
159
+ <button type="button" class="beacon-btn primary" data-action="save">${b.check} Save</button>
160
+ </div>
161
+ `;
162
+ const n = 240, a = 160, i = Math.min(Math.max(8, o.anchor.x), window.innerWidth - n - 8), s = Math.min(Math.max(8, o.anchor.y + 12), window.innerHeight - a - 8);
163
+ t.style.left = `${i}px`, t.style.top = `${s}px`, o.shadowRoot.appendChild(t);
164
+ const r = t.querySelector("textarea");
165
+ r.focus();
166
+ function p(l) {
167
+ t.remove(), e(l);
168
+ }
169
+ t.addEventListener("click", (l) => {
170
+ var f;
171
+ const v = (f = l.target.closest("[data-action]")) == null ? void 0 : f.getAttribute("data-action");
172
+ if (v === "skip" && p({ cancelled: !1 }), v === "save") {
173
+ const g = r.value.trim();
174
+ p({ ...g ? { note: g } : {}, cancelled: !1 });
175
+ }
176
+ }), r.addEventListener("keydown", (l) => {
177
+ if (l.key === "Enter" && (l.metaKey || l.ctrlKey)) {
178
+ const u = r.value.trim();
179
+ p({ ...u ? { note: u } : {}, cancelled: !1 });
180
+ }
181
+ l.key === "Escape" && p({ cancelled: !0 });
182
+ });
183
+ });
184
+ }
185
+ function H(o) {
186
+ const { shadowRoot: e, onHover: t } = o, n = document.createElement("div");
187
+ n.style.cssText = [
188
+ "position: fixed",
189
+ "pointer-events: none",
190
+ "z-index: 2147483646",
191
+ "border: 2px solid #0ea5e9",
192
+ "background: rgba(14, 165, 233, 0.08)",
193
+ "transition: all 60ms ease-out",
194
+ "box-sizing: border-box",
195
+ "border-radius: 2px"
196
+ ].join("; "), document.body.appendChild(n);
197
+ const a = document.createElement("div");
198
+ a.style.cssText = [
199
+ "position: fixed",
200
+ "pointer-events: none",
201
+ "z-index: 2147483647",
202
+ "background: #0f172a",
203
+ "color: #fff",
204
+ "font: 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace",
205
+ "padding: 3px 6px",
206
+ "border-radius: 3px",
207
+ "max-width: 280px",
208
+ "overflow: hidden",
209
+ "text-overflow: ellipsis",
210
+ "white-space: nowrap"
211
+ ].join("; "), document.body.appendChild(a);
212
+ const i = document.body.style.cursor;
213
+ document.body.style.cursor = "crosshair";
214
+ let s = !1, r = () => {
215
+ };
216
+ const p = new Promise((c) => {
217
+ r = c;
218
+ });
219
+ function l(c) {
220
+ if (!c || !(c instanceof Node)) return !1;
221
+ let d = c;
222
+ for (; d; ) {
223
+ if (d === e || d === e.host) return !0;
224
+ d = d.parentNode ?? d.host ?? null;
225
+ }
226
+ return !1;
227
+ }
228
+ function u(c, d) {
229
+ n.style.display = "none", a.style.display = "none";
230
+ const w = document.elementsFromPoint(c, d);
231
+ n.style.display = "block", a.style.display = "block";
232
+ for (const y of w)
233
+ if (!l(y)) return y;
234
+ return null;
235
+ }
236
+ function v(c) {
237
+ const d = c.getBoundingClientRect();
238
+ n.style.left = `${d.left}px`, n.style.top = `${d.top}px`, n.style.width = `${d.width}px`, n.style.height = `${d.height}px`;
239
+ const w = c.tagName.toLowerCase(), y = c.classList.length > 0 ? "." + Array.from(c.classList).slice(0, 2).join(".") : "";
240
+ a.textContent = w + y;
241
+ const S = d.top - 22;
242
+ a.style.left = `${d.left}px`, a.style.top = `${S >= 0 ? S : d.bottom + 4}px`;
243
+ }
244
+ function f(c) {
245
+ const d = u(c.clientX, c.clientY);
246
+ d ? (v(d), t == null || t(d)) : (n.style.display = "none", a.style.display = "none", t == null || t(null));
247
+ }
248
+ function g(c) {
249
+ c.preventDefault(), c.stopPropagation();
250
+ const d = u(c.clientX, c.clientY);
251
+ d && x(d);
252
+ }
253
+ function E(c) {
254
+ c.key === "Escape" && (c.preventDefault(), x(null));
255
+ }
256
+ function x(c) {
257
+ s || (s = !0, document.removeEventListener("mousemove", f, !0), document.removeEventListener("click", g, !0), document.removeEventListener("keydown", E, !0), n.remove(), a.remove(), document.body.style.cursor = i, r(c));
258
+ }
259
+ return document.addEventListener("mousemove", f, !0), document.addEventListener("click", g, !0), document.addEventListener("keydown", E, !0), {
260
+ cancel: () => x(null),
261
+ promise: p
262
+ };
263
+ }
264
+ const O = 8;
265
+ function A(o, e = document) {
266
+ if (!o) return !1;
267
+ try {
268
+ return e.querySelectorAll(`#${CSS.escape(o)}`).length === 1;
269
+ } catch {
270
+ return !1;
271
+ }
272
+ }
273
+ function B(o) {
274
+ const e = o.tagName.toLowerCase(), t = Array.from(o.classList).filter((n) => n.length > 0 && n.length < 40).slice(0, 3).map((n) => `.${CSS.escape(n)}`).join("");
275
+ return e + t;
276
+ }
277
+ function W(o) {
278
+ const e = o.parentElement;
279
+ return e ? Array.from(e.children).filter((n) => n.tagName === o.tagName).indexOf(o) + 1 : 1;
280
+ }
281
+ function q(o) {
282
+ if (!o || o.nodeType !== Node.ELEMENT_NODE) return "";
283
+ if (o.id && A(o.id))
284
+ return `#${CSS.escape(o.id)}`;
285
+ const e = [];
286
+ let t = o, n = 0;
287
+ for (; t && t.tagName.toLowerCase() !== "body" && n < O; ) {
288
+ const a = t;
289
+ if (a.id && A(a.id)) {
290
+ e.unshift(`#${CSS.escape(a.id)}`);
291
+ break;
292
+ }
293
+ const i = B(a), s = a.parentElement;
294
+ let r = i;
295
+ if (s)
296
+ try {
297
+ const p = Array.from(a.classList).join(" ");
298
+ Array.from(s.children).filter(
299
+ (u) => u.tagName === a.tagName && Array.from(u.classList).join(" ") === p
300
+ ).length > 1 && (r = `${a.tagName.toLowerCase()}:nth-of-type(${W(a)})`);
301
+ } catch {
302
+ }
303
+ e.unshift(r), t = s, n++;
304
+ }
305
+ return e.join(" > ");
306
+ }
307
+ function F(o) {
308
+ const e = Object.keys(o).filter((n) => n.startsWith("__reactFiber$") || n.startsWith("__reactInternalInstance$"));
309
+ if (e.length === 0) return null;
310
+ let t = o[e[0]];
311
+ for (let n = 0; n < 10 && t; n++) {
312
+ const a = t, i = a.type;
313
+ if (typeof i == "function") {
314
+ const s = i.displayName ?? i.name;
315
+ if (s && s !== "_default") return { lib: "react", name: s };
316
+ }
317
+ if (typeof i == "object" && i !== null) {
318
+ const s = i.displayName ?? i.name;
319
+ if (s) return { lib: "react", name: s };
320
+ }
321
+ t = a.return;
322
+ }
323
+ return { lib: "react" };
324
+ }
325
+ function V(o) {
326
+ var n, a;
327
+ const e = o, t = e.__vueParentComponent;
328
+ if (t) {
329
+ const i = ((n = t.type) == null ? void 0 : n.name) ?? ((a = t.type) == null ? void 0 : a.__name);
330
+ return { lib: "vue", ...i ? { name: i } : {} };
331
+ }
332
+ return e.__vue__ ? { lib: "vue" } : null;
333
+ }
334
+ function K(o) {
335
+ const e = window.ng;
336
+ if (!(e != null && e.getComponent)) return null;
337
+ try {
338
+ const t = e.getComponent(o);
339
+ if (!t) return null;
340
+ const n = t.constructor;
341
+ return { lib: "angular", ...n != null && n.name ? { name: n.name } : {} };
342
+ } catch {
343
+ return null;
344
+ }
345
+ }
346
+ function U(o) {
347
+ return "__svelte_meta" in o ? { lib: "svelte" } : null;
348
+ }
349
+ function C(o) {
350
+ return F(o) ?? V(o) ?? K(o) ?? U(o) ?? void 0;
351
+ }
352
+ const X = 5e3, Y = 1e3, G = [
353
+ "display",
354
+ "position",
355
+ "top",
356
+ "right",
357
+ "bottom",
358
+ "left",
359
+ "width",
360
+ "height",
361
+ "min-width",
362
+ "min-height",
363
+ "max-width",
364
+ "max-height",
365
+ "margin",
366
+ "padding",
367
+ "font-family",
368
+ "font-size",
369
+ "font-weight",
370
+ "line-height",
371
+ "text-align",
372
+ "color",
373
+ "background-color",
374
+ "background-image",
375
+ "border",
376
+ "border-radius",
377
+ "opacity",
378
+ "visibility",
379
+ "overflow",
380
+ "z-index",
381
+ "transform",
382
+ "transition",
383
+ "flex",
384
+ "flex-direction",
385
+ "justify-content",
386
+ "align-items",
387
+ "gap",
388
+ "grid-template-columns",
389
+ "grid-template-rows"
390
+ ];
391
+ function L(o, e) {
392
+ return o.length <= e ? o : o.slice(0, e) + `
393
+
394
+ /* … truncated, original was ${o.length} chars */`;
395
+ }
396
+ function J(o) {
397
+ const e = getComputedStyle(o), t = {};
398
+ for (const n of G) {
399
+ const a = e.getPropertyValue(n);
400
+ a && a !== "normal" && a !== "none" && a !== "auto" && a !== "0px" && (t[n] = a.trim());
401
+ }
402
+ return t;
403
+ }
404
+ function Z(o) {
405
+ const e = o.getBoundingClientRect();
406
+ return {
407
+ selector: q(o),
408
+ tagName: o.tagName.toLowerCase(),
409
+ id: o.id || null,
410
+ classList: Array.from(o.classList),
411
+ outerHTML: L(o.outerHTML, X),
412
+ ...o.parentElement ? { parentOuterHTML: L(o.parentElement.outerHTML, Y) } : {},
413
+ computedStyles: J(o),
414
+ boundingRect: {
415
+ x: Math.round(e.x),
416
+ y: Math.round(e.y),
417
+ w: Math.round(e.width),
418
+ h: Math.round(e.height)
419
+ },
420
+ ...C(o) ? { framework: C(o) } : {}
421
+ };
422
+ }
423
+ function $() {
424
+ var t, n;
425
+ const o = navigator;
426
+ return o.userAgentData ? {
427
+ brand: ((t = o.userAgentData.brands.find((a) => !/Not[.\-]?A.?Brand/i.test(a.brand))) == null ? void 0 : t.brand) ?? ((n = o.userAgentData.brands[0]) == null ? void 0 : n.brand) ?? "unknown",
428
+ mobile: o.userAgentData.mobile,
429
+ platform: o.userAgentData.platform
430
+ } : void 0;
431
+ }
432
+ function M() {
433
+ if (window.matchMedia("(prefers-color-scheme: dark)").matches) return "dark";
434
+ if (window.matchMedia("(prefers-color-scheme: light)").matches) return "light";
435
+ }
436
+ function T() {
437
+ return {
438
+ url: window.location.href,
439
+ ...document.referrer ? { referrer: document.referrer } : {},
440
+ userAgent: navigator.userAgent,
441
+ ...$() ? { uaData: $() } : {},
442
+ viewport: {
443
+ w: window.innerWidth,
444
+ h: window.innerHeight,
445
+ dpr: window.devicePixelRatio || 1
446
+ },
447
+ screen: {
448
+ w: window.screen.width,
449
+ h: window.screen.height
450
+ },
451
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
452
+ locale: navigator.language,
453
+ ...M() ? { theme: M() } : {},
454
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
455
+ };
456
+ }
457
+ let k = null;
458
+ async function Q() {
459
+ return k || (k = await import("./index-DAIDnjfR.js")), k;
460
+ }
461
+ async function R(o = {}) {
462
+ const { quality: e = 0.7, pixelRatio: t = Math.min(window.devicePixelRatio || 1, 2), excludeShadowRoot: n } = o, a = await Q(), i = n ? (s) => {
463
+ let r = s;
464
+ for (; r; ) {
465
+ if (r === n.host) return !1;
466
+ r = r.parentNode;
467
+ }
468
+ return !0;
469
+ } : void 0;
470
+ return a.toJpeg(document.documentElement, {
471
+ quality: e,
472
+ pixelRatio: t,
473
+ cacheBust: !0,
474
+ ...i ? { filter: i } : {},
475
+ // Cap dimensions to viewport so we don't accidentally capture full document height.
476
+ width: window.innerWidth,
477
+ height: window.innerHeight,
478
+ style: {
479
+ // Avoid scrolling artifacts.
480
+ transform: "none"
481
+ }
482
+ });
483
+ }
484
+ const ee = 14, te = "#0ea5e9", ne = "#ffffff", oe = "#ffffff";
485
+ async function _(o, e, t) {
486
+ const n = await ie(o), a = document.createElement("canvas");
487
+ a.width = n.naturalWidth, a.height = n.naturalHeight;
488
+ const i = a.getContext("2d");
489
+ if (!i) throw new Error("Could not get 2D canvas context");
490
+ i.drawImage(n, 0, 0);
491
+ const s = a.width / t.w, r = a.height / t.h;
492
+ for (const p of e) {
493
+ const l = (p.boundingRect.x + 8) * s, u = (p.boundingRect.y + 8) * r;
494
+ i.beginPath(), i.fillStyle = te, i.arc(l, u, ee, 0, Math.PI * 2), i.fill(), i.lineWidth = 3, i.strokeStyle = ne, i.stroke(), i.fillStyle = oe, i.font = "bold 16px system-ui, -apple-system, sans-serif", i.textAlign = "center", i.textBaseline = "middle", i.fillText(String(p.number), l, u + 1);
495
+ }
496
+ return a.toDataURL("image/jpeg", 0.85);
497
+ }
498
+ function ie(o) {
499
+ return new Promise((e, t) => {
500
+ const n = new Image();
501
+ n.onload = () => e(n), n.onerror = (a) => t(a), n.src = o;
502
+ });
503
+ }
504
+ async function ae(o, e, t) {
505
+ const n = await fetch(o, {
506
+ method: "POST",
507
+ credentials: "include",
508
+ headers: {
509
+ "Content-Type": "application/json",
510
+ ...t ?? {}
511
+ },
512
+ body: JSON.stringify(e)
513
+ });
514
+ let a = null;
515
+ if ((n.headers.get("Content-Type") ?? "").includes("application/json"))
516
+ try {
517
+ a = await n.json();
518
+ } catch {
519
+ a = null;
520
+ }
521
+ else
522
+ try {
523
+ a = await n.text();
524
+ } catch {
525
+ a = null;
526
+ }
527
+ return {
528
+ ok: n.ok,
529
+ status: n.status,
530
+ body: a
531
+ };
532
+ }
533
+ const se = ["endpoint", "position", "theme", "severities"];
534
+ class re extends HTMLElement {
535
+ constructor() {
536
+ super(...arguments), this._config = null, this.pins = [], this.description = "", this.severity = "bug";
537
+ }
538
+ static get observedAttributes() {
539
+ return se;
540
+ }
541
+ /** Public — wrappers can set `widget.config = {...}` for dynamic endpoint/headers/context. */
542
+ set config(e) {
543
+ this._config = e;
544
+ }
545
+ get config() {
546
+ if (this._config) return this._config;
547
+ const e = this.getAttribute("endpoint");
548
+ return e ? { endpoint: e } : null;
549
+ }
550
+ connectedCallback() {
551
+ if (!this.shadow) {
552
+ this.shadow = this.attachShadow({ mode: "open" });
553
+ const e = document.createElement("style");
554
+ e.textContent = N, this.shadow.appendChild(e), this.slotEl = document.createElement("slot"), this.slotEl.name = "trigger", this.shadow.appendChild(this.slotEl), this.trigger = D(), this.shadow.appendChild(this.trigger), this.trigger.addEventListener("click", () => this.open()), this.slotEl.addEventListener("slotchange", () => {
555
+ const t = this.slotEl.assignedElements();
556
+ t.length > 0 && (this.trigger && (this.trigger.style.display = "none"), t.forEach((n) => n.addEventListener("click", () => this.open())));
557
+ }), this.drawer = new I(this.parseSeverities(), {
558
+ onClose: () => this.close(),
559
+ onAnnotate: () => this.startAnnotate(),
560
+ onSubmit: () => this.handleSubmit(),
561
+ onPinNoteChange: (t, n) => this.updatePinNote(t, n),
562
+ onPinDelete: (t) => this.deletePin(t),
563
+ onDescriptionChange: (t) => {
564
+ this.description = t;
565
+ },
566
+ onSeverityChange: (t) => {
567
+ this.severity = t;
568
+ }
569
+ }), this.shadow.appendChild(this.drawer.root);
570
+ }
571
+ this.hasAttribute("position") || this.setAttribute("position", "bottom-right"), this.hasAttribute("theme") || this.setAttribute("theme", "auto");
572
+ }
573
+ attributeChangedCallback(e, t, n) {
574
+ e === "severities" && this.drawer && console.warn("[launchkit-beacon] severities attribute changed after mount; not yet hot-reloaded.");
575
+ }
576
+ // ── Public API ──────────────────────────────────────────
577
+ open() {
578
+ var e;
579
+ if (this.drawer) {
580
+ if (this.getAttribute("position") === "hidden") {
581
+ const t = (e = this.slotEl) == null ? void 0 : e.assignedElements()[0];
582
+ t && le(this.drawer.root, t);
583
+ }
584
+ this.drawer.open();
585
+ }
586
+ }
587
+ close() {
588
+ var e;
589
+ (e = this.drawer) == null || e.close();
590
+ }
591
+ async openWithPicker() {
592
+ this.open(), setTimeout(() => this.startAnnotate(), 0);
593
+ }
594
+ // ── Annotate flow ──────────────────────────────────────────
595
+ async startAnnotate() {
596
+ if (!this.drawer) return;
597
+ this.drawer.minimize();
598
+ let e = !0;
599
+ for (; e; ) {
600
+ const n = await H({ shadowRoot: this.shadow }).promise;
601
+ if (!n) break;
602
+ const a = n.getBoundingClientRect(), i = await j({
603
+ shadowRoot: this.shadow,
604
+ anchor: { x: a.left, y: a.bottom },
605
+ pinNumber: this.pins.length + 1
606
+ });
607
+ if (i.cancelled) break;
608
+ const s = Z(n), r = {
609
+ number: this.pins.length + 1,
610
+ ...s,
611
+ ...i.note ? { note: i.note } : {}
612
+ };
613
+ this.pins.push(r);
614
+ }
615
+ if (this.pins.length > 0)
616
+ try {
617
+ const t = T(), n = await R({ excludeShadowRoot: this.shadow }), a = await _(n, this.pins, t.viewport);
618
+ this.drawer.setPins(this.pins, a);
619
+ } catch (t) {
620
+ console.warn("[launchkit-beacon] screenshot capture failed:", t), this.drawer.setPins(this.pins);
621
+ }
622
+ this.drawer.open();
623
+ }
624
+ updatePinNote(e, t) {
625
+ const n = this.pins.find((a) => a.number === e);
626
+ n && (t.trim() ? n.note = t : delete n.note);
627
+ }
628
+ deletePin(e) {
629
+ var t;
630
+ this.pins = this.pins.filter((n) => n.number !== e), this.pins.forEach((n, a) => {
631
+ n.number = a + 1;
632
+ }), (t = this.drawer) == null || t.setPins(this.pins, void 0);
633
+ }
634
+ // ── Submit flow ──────────────────────────────────────────
635
+ async handleSubmit() {
636
+ if (!this.drawer) return;
637
+ const e = this.config;
638
+ if (!(e != null && e.endpoint)) {
639
+ this.drawer.setStatus("Missing endpoint configuration", "error");
640
+ return;
641
+ }
642
+ const t = this.drawer.getDescription().trim(), n = this.drawer.getSeverity();
643
+ if (!t) {
644
+ this.drawer.setStatus("Description is required", "error");
645
+ return;
646
+ }
647
+ const a = T();
648
+ let i;
649
+ if (this.pins.length > 0)
650
+ try {
651
+ const l = await R({ excludeShadowRoot: this.shadow });
652
+ i = await _(l, this.pins, a.viewport);
653
+ } catch {
654
+ }
655
+ const s = {
656
+ description: t,
657
+ severity: n,
658
+ ...i ? { screenshot: { dataUrl: i, mime: "image/jpeg" } } : {},
659
+ metadata: a,
660
+ pins: this.pins,
661
+ ...this.resolveContext() ? { context: this.resolveContext() } : {}
662
+ }, r = new CustomEvent("beacon-before-submit", {
663
+ detail: { payload: s },
664
+ cancelable: !0,
665
+ bubbles: !0,
666
+ composed: !0
667
+ });
668
+ if (!this.dispatchEvent(r)) {
669
+ this.drawer.setStatus("Submission cancelled", "error");
670
+ return;
671
+ }
672
+ this.drawer.setSubmitting(!0), this.drawer.setStatus("Sending…");
673
+ try {
674
+ const l = this.resolveHeaders(), u = await ae(e.endpoint, s, l);
675
+ this.drawer.setSubmitting(!1), u.ok ? (this.drawer.setStatus("Sent — thanks!", "success"), this.dispatchEvent(new CustomEvent("beacon-after-submit", {
676
+ detail: { response: u },
677
+ bubbles: !0,
678
+ composed: !0
679
+ })), setTimeout(() => {
680
+ this.pins = [], this.description = "", this.severity = "bug", this.drawer.reset(), this.drawer.close();
681
+ }, 1500)) : this.drawer.setStatus(`Failed: ${u.status}`, "error");
682
+ } catch (l) {
683
+ this.drawer.setSubmitting(!1);
684
+ const u = l instanceof Error ? l.message : "Network error";
685
+ this.drawer.setStatus(`Failed: ${u}`, "error");
686
+ }
687
+ }
688
+ // ── Helpers ──────────────────────────────────────────
689
+ resolveHeaders() {
690
+ var t;
691
+ const e = (t = this.config) == null ? void 0 : t.headers;
692
+ if (e)
693
+ return typeof e == "function" ? e() : e;
694
+ }
695
+ resolveContext() {
696
+ var t;
697
+ const e = (t = this.config) == null ? void 0 : t.context;
698
+ if (e)
699
+ return typeof e == "function" ? e() : e;
700
+ }
701
+ parseSeverities() {
702
+ const e = this.getAttribute("severities");
703
+ return e ? e.split(",").map((t) => t.trim()).filter(Boolean) : ["bug", "idea", "ux", "a11y"];
704
+ }
705
+ }
706
+ function ce(o = "launchkit-beacon") {
707
+ typeof window > "u" || customElements.get(o) || customElements.define(o, re);
708
+ }
709
+ const P = 380, h = 8, de = 520;
710
+ function le(o, e) {
711
+ const t = e.getBoundingClientRect(), n = window.innerWidth, a = window.innerHeight;
712
+ let i = Math.max(h, n - t.right);
713
+ n - i - P < h && (i = Math.max(h, n - P - h));
714
+ const r = a - t.bottom - h, p = t.top - h;
715
+ o.style.left = "auto", o.style.right = `${i}px`, r >= de || r >= p ? (o.style.top = `${t.bottom + h}px`, o.style.bottom = "auto") : (o.style.top = "auto", o.style.bottom = `${a - t.top + h}px`);
716
+ }
717
+ ce();
718
+ export {
719
+ re as LaunchkitBeacon,
720
+ ce as defineBeacon
721
+ };
722
+ //# sourceMappingURL=beacon.es.js.map