@outcode/bug-reporter-web 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1258 @@
1
+ import { useRef, useState, useCallback, useEffect, useMemo } from 'react';
2
+ import { ANNOTATION_TOOLS, ANNOTATION_COLORS, SEVERITIES, REPORT_TYPES, resolveTheme, ReportQueue, normalizeContext, submitReport } from '@outcode/bug-reporter-core';
3
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
+ import html2canvas from 'html2canvas';
5
+
6
+ // src/BugReporterButton.tsx
7
+
8
+ // src/theme.ts
9
+ function cssVars(t) {
10
+ return {
11
+ "--canvas": t.canvas,
12
+ "--surface": t.surface,
13
+ "--panel": t.panel,
14
+ "--border": t.border,
15
+ "--border-strong": t.borderStrong,
16
+ "--text": t.text,
17
+ "--muted": t.muted,
18
+ "--faint": t.faint,
19
+ "--accent": t.accent,
20
+ "--accent-press": t.accentPress,
21
+ "--on-accent": t.onAccent,
22
+ "--ring": t.ring,
23
+ "--ok": t.ok,
24
+ "--ok-bg": t.okBg,
25
+ "--shadow": t.shadow
26
+ };
27
+ }
28
+ var FONT = "'Geist','Geist Fallback',-apple-system,system-ui,sans-serif";
29
+ var MONO = "'Geist Mono',ui-monospace,monospace";
30
+ var KEYFRAMES = `
31
+ @keyframes ocbr-spin{to{transform:rotate(360deg);}}
32
+ @keyframes ocbr-flash{0%{opacity:0;}18%{opacity:.92;}100%{opacity:0;}}
33
+ @keyframes ocbr-ping{0%{transform:scale(1);opacity:.55;}80%,100%{transform:scale(1.9);opacity:0;}}
34
+ @keyframes ocbr-float{0%,100%{transform:translateY(0);}50%{transform:translateY(-4px);}}
35
+ @keyframes ocbr-panel-in{from{transform:translateX(24px);opacity:0;}to{transform:translateX(0);opacity:1;}}
36
+ @keyframes ocbr-fade{from{opacity:0;}to{opacity:1;}}
37
+ @keyframes ocbr-pop{from{transform:scale(.92);opacity:0;}to{transform:scale(1);opacity:1;}}
38
+ `;
39
+ var injected = false;
40
+ function ensureKeyframes() {
41
+ if (injected || typeof document === "undefined") return;
42
+ injected = true;
43
+ const el = document.createElement("style");
44
+ el.setAttribute("data-ocbr", "");
45
+ el.textContent = KEYFRAMES;
46
+ document.head.appendChild(el);
47
+ }
48
+ function AnnotateToolbar({
49
+ tool,
50
+ color,
51
+ onTool,
52
+ onColor,
53
+ onUndo,
54
+ onClear,
55
+ onContinue
56
+ }) {
57
+ return /* @__PURE__ */ jsxs(
58
+ "div",
59
+ {
60
+ style: {
61
+ display: "flex",
62
+ flexWrap: "wrap",
63
+ alignItems: "center",
64
+ justifyContent: "center",
65
+ gap: 8,
66
+ padding: 7,
67
+ background: "var(--panel)",
68
+ border: "1px solid var(--border)",
69
+ borderRadius: 14,
70
+ boxShadow: "var(--shadow)",
71
+ fontFamily: FONT,
72
+ width: "100%",
73
+ boxSizing: "border-box"
74
+ },
75
+ children: [
76
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", alignItems: "center", gap: 2 }, children: ANNOTATION_TOOLS.map((t) => {
77
+ const active = tool === t.id;
78
+ return /* @__PURE__ */ jsx(
79
+ "button",
80
+ {
81
+ type: "button",
82
+ title: t.label,
83
+ onClick: () => onTool(t.id),
84
+ style: {
85
+ width: 36,
86
+ height: 36,
87
+ display: "flex",
88
+ alignItems: "center",
89
+ justifyContent: "center",
90
+ border: "none",
91
+ borderRadius: 10,
92
+ background: active ? "var(--ring)" : "transparent",
93
+ color: active ? "var(--accent)" : "var(--muted)",
94
+ cursor: "pointer",
95
+ padding: 0
96
+ },
97
+ children: /* @__PURE__ */ jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx(
98
+ "path",
99
+ {
100
+ d: t.iconPath,
101
+ stroke: "currentColor",
102
+ strokeWidth: "2",
103
+ strokeLinecap: "round",
104
+ strokeLinejoin: "round"
105
+ }
106
+ ) })
107
+ },
108
+ t.id
109
+ );
110
+ }) }),
111
+ /* @__PURE__ */ jsx(
112
+ "div",
113
+ {
114
+ style: {
115
+ display: "flex",
116
+ alignItems: "center",
117
+ gap: 7,
118
+ padding: "0 4px",
119
+ borderLeft: "1px solid var(--border)",
120
+ borderRight: "1px solid var(--border)"
121
+ },
122
+ children: ANNOTATION_COLORS.map((c) => {
123
+ const active = color === c;
124
+ return /* @__PURE__ */ jsx(
125
+ "button",
126
+ {
127
+ type: "button",
128
+ "aria-label": "color",
129
+ onClick: () => onColor(c),
130
+ style: {
131
+ width: active ? 20 : 18,
132
+ height: active ? 20 : 18,
133
+ borderRadius: "50%",
134
+ border: "2px solid var(--panel)",
135
+ background: c,
136
+ cursor: "pointer",
137
+ padding: 0,
138
+ boxShadow: active ? "0 0 0 2px var(--accent)" : "0 0 0 1px var(--border)"
139
+ }
140
+ },
141
+ c
142
+ );
143
+ })
144
+ }
145
+ ),
146
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 2 }, children: [
147
+ /* @__PURE__ */ jsx(
148
+ "button",
149
+ {
150
+ type: "button",
151
+ title: "Undo",
152
+ onClick: onUndo,
153
+ style: iconBtn,
154
+ children: /* @__PURE__ */ jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx(
155
+ "path",
156
+ {
157
+ d: "M9 14L4 9l5-5M4 9h11a5 5 0 010 10h-3",
158
+ stroke: "currentColor",
159
+ strokeWidth: "2",
160
+ strokeLinecap: "round",
161
+ strokeLinejoin: "round"
162
+ }
163
+ ) })
164
+ }
165
+ ),
166
+ /* @__PURE__ */ jsx("button", { type: "button", title: "Clear all", onClick: onClear, style: iconBtn, children: /* @__PURE__ */ jsx("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx(
167
+ "path",
168
+ {
169
+ d: "M5 7h14M9 7V5h6v2M7 7l1 12h8l1-12",
170
+ stroke: "currentColor",
171
+ strokeWidth: "2",
172
+ strokeLinecap: "round",
173
+ strokeLinejoin: "round"
174
+ }
175
+ ) }) })
176
+ ] }),
177
+ /* @__PURE__ */ jsxs(
178
+ "button",
179
+ {
180
+ type: "button",
181
+ onClick: onContinue,
182
+ style: {
183
+ display: "flex",
184
+ alignItems: "center",
185
+ gap: 7,
186
+ height: 36,
187
+ padding: "0 14px",
188
+ border: "none",
189
+ borderRadius: 10,
190
+ background: "var(--accent)",
191
+ color: "var(--on-accent)",
192
+ fontFamily: "inherit",
193
+ fontSize: 13.5,
194
+ fontWeight: 600,
195
+ cursor: "pointer"
196
+ },
197
+ children: [
198
+ "Continue",
199
+ /* @__PURE__ */ jsx("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx(
200
+ "path",
201
+ {
202
+ d: "M5 12h14M13 6l6 6-6 6",
203
+ stroke: "currentColor",
204
+ strokeWidth: "2.2",
205
+ strokeLinecap: "round",
206
+ strokeLinejoin: "round"
207
+ }
208
+ ) })
209
+ ]
210
+ }
211
+ )
212
+ ]
213
+ }
214
+ );
215
+ }
216
+ var iconBtn = {
217
+ width: 36,
218
+ height: 36,
219
+ display: "flex",
220
+ alignItems: "center",
221
+ justifyContent: "center",
222
+ border: "none",
223
+ borderRadius: 10,
224
+ background: "transparent",
225
+ color: "var(--muted)",
226
+ cursor: "pointer",
227
+ padding: 0
228
+ };
229
+
230
+ // src/flatten.ts
231
+ function rectOf(b, sx, sy) {
232
+ const x = Math.min(b.x0, b.x1) * sx;
233
+ const y = Math.min(b.y0, b.y1) * sy;
234
+ const w = Math.abs(b.x1 - b.x0) * sx;
235
+ const h = Math.abs(b.y1 - b.y0) * sy;
236
+ return { x, y, w, h };
237
+ }
238
+ function flattenAnnotations(src, shapes, dispW, dispH) {
239
+ return new Promise((resolve) => {
240
+ if (typeof document === "undefined") return resolve(src);
241
+ const img = new Image();
242
+ img.onerror = () => resolve(src);
243
+ img.onload = () => {
244
+ const natW = img.naturalWidth || dispW;
245
+ const natH = img.naturalHeight || dispH;
246
+ const sx = dispW ? natW / dispW : 1;
247
+ const sy = dispH ? natH / dispH : 1;
248
+ const canvas = document.createElement("canvas");
249
+ canvas.width = natW;
250
+ canvas.height = natH;
251
+ const ctx = canvas.getContext("2d");
252
+ if (!ctx) return resolve(src);
253
+ ctx.drawImage(img, 0, 0, natW, natH);
254
+ shapes.forEach((s) => {
255
+ if (s.type !== "blur") return;
256
+ const { x, y, w, h } = rectOf(s, sx, sy);
257
+ ctx.fillStyle = "#7A7D88";
258
+ ctx.fillRect(x, y, w, h);
259
+ });
260
+ const lw = 3.5 * ((sx + sy) / 2);
261
+ ctx.lineCap = "round";
262
+ ctx.lineJoin = "round";
263
+ shapes.forEach((s) => {
264
+ ctx.strokeStyle = s.color;
265
+ ctx.fillStyle = s.color;
266
+ ctx.lineWidth = lw;
267
+ if (s.type === "pen") {
268
+ if (s.points.length < 2) return;
269
+ ctx.beginPath();
270
+ ctx.moveTo(s.points[0].x * sx, s.points[0].y * sy);
271
+ s.points.slice(1).forEach((p) => ctx.lineTo(p.x * sx, p.y * sy));
272
+ ctx.stroke();
273
+ } else if (s.type === "rect") {
274
+ const { x, y, w, h } = rectOf(s, sx, sy);
275
+ ctx.strokeRect(x, y, w, h);
276
+ } else if (s.type === "arrow") {
277
+ const x0 = s.x0 * sx;
278
+ const y0 = s.y0 * sy;
279
+ const x1 = s.x1 * sx;
280
+ const y1 = s.y1 * sy;
281
+ const ang = Math.atan2(y1 - y0, x1 - x0);
282
+ const hl = 15 * ((sx + sy) / 2);
283
+ ctx.beginPath();
284
+ ctx.moveTo(x0, y0);
285
+ ctx.lineTo(x1, y1);
286
+ ctx.stroke();
287
+ ctx.beginPath();
288
+ ctx.moveTo(x1 - hl * Math.cos(ang - Math.PI / 7), y1 - hl * Math.sin(ang - Math.PI / 7));
289
+ ctx.lineTo(x1, y1);
290
+ ctx.lineTo(x1 - hl * Math.cos(ang + Math.PI / 7), y1 - hl * Math.sin(ang + Math.PI / 7));
291
+ ctx.stroke();
292
+ } else if (s.type === "text" && s.text.trim()) {
293
+ const fs = 13 * sy;
294
+ ctx.font = `600 ${fs}px 'Geist',sans-serif`;
295
+ const padX = 8 * sx;
296
+ const padY = 4 * sy;
297
+ const tw = ctx.measureText(s.text).width;
298
+ const bx = s.x * sx;
299
+ const by = s.y * sy - (fs + padY * 2) / 2;
300
+ ctx.fillStyle = s.color;
301
+ const r = 6 * sy;
302
+ roundRect(ctx, bx, by, tw + padX * 2, fs + padY * 2, r);
303
+ ctx.fill();
304
+ ctx.fillStyle = "#ffffff";
305
+ ctx.textBaseline = "middle";
306
+ ctx.fillText(s.text, bx + padX, by + (fs + padY * 2) / 2);
307
+ }
308
+ });
309
+ resolve(canvas.toDataURL("image/png"));
310
+ };
311
+ img.src = src;
312
+ });
313
+ }
314
+ function roundRect(ctx, x, y, w, h, r) {
315
+ const rad = Math.min(r, w / 2, h / 2);
316
+ ctx.beginPath();
317
+ ctx.moveTo(x + rad, y);
318
+ ctx.arcTo(x + w, y, x + w, y + h, rad);
319
+ ctx.arcTo(x + w, y + h, x, y + h, rad);
320
+ ctx.arcTo(x, y + h, x, y, rad);
321
+ ctx.arcTo(x, y, x + w, y, rad);
322
+ ctx.closePath();
323
+ }
324
+ var PADDING = 24;
325
+ var TOP_RESERVE = 150;
326
+ var BOTTOM_RESERVE = 40;
327
+ function AnnotateOverlay({ src, onCancel, onContinue }) {
328
+ const stageRef = useRef(null);
329
+ const idRef = useRef(0);
330
+ const [shapes, setShapes] = useState([]);
331
+ const [draft, setDraft] = useState(null);
332
+ const [tool, setTool] = useState("arrow");
333
+ const [color, setColor] = useState(ANNOTATION_COLORS[0]);
334
+ const [editingId, setEditingId] = useState(null);
335
+ const [dims, setDims] = useState({ width: 800, height: 480 });
336
+ const uid = () => `s${++idRef.current}`;
337
+ const recompute = useCallback((natW, natH) => {
338
+ const maxW = window.innerWidth - PADDING * 2;
339
+ const maxH = window.innerHeight - TOP_RESERVE - BOTTOM_RESERVE - PADDING * 2;
340
+ const aspect = natW / natH || 1.6;
341
+ let w = maxW;
342
+ let h = Math.round(maxW / aspect);
343
+ if (h > maxH) {
344
+ h = maxH;
345
+ w = Math.round(maxH * aspect);
346
+ }
347
+ setDims({ width: w, height: h });
348
+ }, []);
349
+ useEffect(() => {
350
+ const img = new Image();
351
+ img.onload = () => recompute(img.naturalWidth, img.naturalHeight);
352
+ img.onerror = () => recompute(1440, 900);
353
+ img.src = src;
354
+ }, [src, recompute]);
355
+ const getPt = (e) => {
356
+ const r = stageRef.current.getBoundingClientRect();
357
+ return {
358
+ x: Math.max(0, Math.min(r.width, e.clientX - r.left)),
359
+ y: Math.max(0, Math.min(r.height, e.clientY - r.top))
360
+ };
361
+ };
362
+ const onDown = (e) => {
363
+ if (!stageRef.current) return;
364
+ const p = getPt(e);
365
+ if (tool === "text") {
366
+ for (let i = shapes.length - 1; i >= 0; i--) {
367
+ const sh = shapes[i];
368
+ if (sh.type !== "text") continue;
369
+ const w = Math.max(48, sh.text.length * 8 + 24);
370
+ if (p.x >= sh.x - 8 && p.x <= sh.x + w && p.y >= sh.y - 20 && p.y <= sh.y + 10) {
371
+ setEditingId(sh.id);
372
+ return;
373
+ }
374
+ }
375
+ const id = uid();
376
+ setShapes((s) => [...s, { id, type: "text", x: p.x, y: p.y, color, text: "" }]);
377
+ setEditingId(id);
378
+ return;
379
+ }
380
+ try {
381
+ e.currentTarget.setPointerCapture(e.pointerId);
382
+ } catch {
383
+ }
384
+ const d = tool === "pen" ? { id: uid(), type: "pen", color, points: [p] } : { id: uid(), type: tool, color, x0: p.x, y0: p.y, x1: p.x, y1: p.y };
385
+ setDraft(d);
386
+ setEditingId(null);
387
+ };
388
+ const onMove = (e) => {
389
+ setDraft((prev) => {
390
+ if (!prev) return prev;
391
+ const p = getPt(e);
392
+ if (prev.type === "pen") return { ...prev, points: [...prev.points, p] };
393
+ return { ...prev, x1: p.x, y1: p.y };
394
+ });
395
+ };
396
+ const onUp = () => {
397
+ setDraft((d) => {
398
+ if (!d) return null;
399
+ const keep = d.type === "pen" ? d.points.length > 1 : d.type === "text" ? false : Math.abs(d.x1 - d.x0) > 5 || Math.abs(d.y1 - d.y0) > 5;
400
+ if (keep) setShapes((s) => [...s, d]);
401
+ return null;
402
+ });
403
+ };
404
+ const undo = () => {
405
+ setShapes((s) => s.slice(0, -1));
406
+ setDraft(null);
407
+ };
408
+ const clearAll = () => {
409
+ setShapes([]);
410
+ setDraft(null);
411
+ setEditingId(null);
412
+ };
413
+ const updateText = (id, val) => setShapes((s) => s.map((sh) => sh.id === id && sh.type === "text" ? { ...sh, text: val } : sh));
414
+ const commitText = () => setShapes((s) => {
415
+ setEditingId(null);
416
+ return s.filter((sh) => !(sh.type === "text" && !sh.text.trim()));
417
+ });
418
+ const handleContinue = async () => {
419
+ const flattened = await flattenAnnotations(src, shapes, dims.width, dims.height);
420
+ onContinue(flattened, shapes.length);
421
+ };
422
+ const all = draft ? [...shapes, draft] : shapes;
423
+ const svgEls = [];
424
+ const blurEls = [];
425
+ const labelEls = [];
426
+ all.forEach((sh) => {
427
+ if (sh.type === "blur") {
428
+ const x = Math.min(sh.x0, sh.x1);
429
+ const y = Math.min(sh.y0, sh.y1);
430
+ const w = Math.abs(sh.x1 - sh.x0);
431
+ const h = Math.abs(sh.y1 - sh.y0);
432
+ blurEls.push(
433
+ /* @__PURE__ */ jsx(
434
+ "div",
435
+ {
436
+ style: {
437
+ position: "absolute",
438
+ left: x,
439
+ top: y,
440
+ width: w,
441
+ height: h,
442
+ backdropFilter: "blur(10px)",
443
+ WebkitBackdropFilter: "blur(10px)",
444
+ background: "#7A7D88",
445
+ border: `1.5px dashed ${sh.color}`,
446
+ borderRadius: 5
447
+ }
448
+ },
449
+ sh.id
450
+ )
451
+ );
452
+ } else if (sh.type === "rect") {
453
+ const x = Math.min(sh.x0, sh.x1);
454
+ const y = Math.min(sh.y0, sh.y1);
455
+ svgEls.push(
456
+ /* @__PURE__ */ jsx(
457
+ "rect",
458
+ {
459
+ x,
460
+ y,
461
+ width: Math.abs(sh.x1 - sh.x0),
462
+ height: Math.abs(sh.y1 - sh.y0),
463
+ rx: 4,
464
+ fill: "none",
465
+ stroke: sh.color,
466
+ strokeWidth: 3
467
+ },
468
+ sh.id
469
+ )
470
+ );
471
+ } else if (sh.type === "pen") {
472
+ svgEls.push(
473
+ /* @__PURE__ */ jsx(
474
+ "polyline",
475
+ {
476
+ points: sh.points.map((p) => `${p.x},${p.y}`).join(" "),
477
+ fill: "none",
478
+ stroke: sh.color,
479
+ strokeWidth: 3.5,
480
+ strokeLinecap: "round",
481
+ strokeLinejoin: "round"
482
+ },
483
+ sh.id
484
+ )
485
+ );
486
+ } else if (sh.type === "arrow") {
487
+ const { x0, y0, x1, y1 } = sh;
488
+ const ang = Math.atan2(y1 - y0, x1 - x0);
489
+ const hl = 15;
490
+ svgEls.push(
491
+ /* @__PURE__ */ jsxs("g", { children: [
492
+ /* @__PURE__ */ jsx("line", { x1: x0, y1: y0, x2: x1, y2: y1, stroke: sh.color, strokeWidth: 3.5, strokeLinecap: "round" }),
493
+ /* @__PURE__ */ jsx(
494
+ "polyline",
495
+ {
496
+ points: `${x1 - hl * Math.cos(ang - Math.PI / 7)},${y1 - hl * Math.sin(ang - Math.PI / 7)} ${x1},${y1} ${x1 - hl * Math.cos(ang + Math.PI / 7)},${y1 - hl * Math.sin(ang + Math.PI / 7)}`,
497
+ fill: "none",
498
+ stroke: sh.color,
499
+ strokeWidth: 3.5,
500
+ strokeLinecap: "round",
501
+ strokeLinejoin: "round"
502
+ }
503
+ )
504
+ ] }, sh.id)
505
+ );
506
+ } else if (sh.type === "text") {
507
+ if (editingId === sh.id || !sh.text.trim()) return;
508
+ labelEls.push(
509
+ /* @__PURE__ */ jsx(
510
+ "div",
511
+ {
512
+ style: {
513
+ position: "absolute",
514
+ left: sh.x,
515
+ top: sh.y,
516
+ transform: "translateY(-50%)",
517
+ background: sh.color,
518
+ color: "#fff",
519
+ padding: "3px 8px",
520
+ borderRadius: 6,
521
+ fontSize: 13,
522
+ fontWeight: 600,
523
+ fontFamily: FONT,
524
+ whiteSpace: "nowrap",
525
+ boxShadow: "0 1px 4px rgba(0,0,0,0.28)"
526
+ },
527
+ children: sh.text
528
+ },
529
+ sh.id
530
+ )
531
+ );
532
+ }
533
+ });
534
+ const editingShape = editingId != null ? shapes.find((s) => s.id === editingId && s.type === "text") : void 0;
535
+ return /* @__PURE__ */ jsxs(
536
+ "div",
537
+ {
538
+ style: {
539
+ position: "fixed",
540
+ inset: 0,
541
+ zIndex: 2147483e3,
542
+ display: "flex",
543
+ flexDirection: "column",
544
+ alignItems: "center",
545
+ justifyContent: "center",
546
+ background: "rgba(8,10,18,0.92)",
547
+ fontFamily: FONT
548
+ },
549
+ children: [
550
+ /* @__PURE__ */ jsx(
551
+ "button",
552
+ {
553
+ type: "button",
554
+ "aria-label": "Cancel",
555
+ onClick: onCancel,
556
+ style: {
557
+ position: "absolute",
558
+ top: 18,
559
+ left: 18,
560
+ width: 36,
561
+ height: 36,
562
+ borderRadius: 9,
563
+ border: "1px solid rgba(255,255,255,0.18)",
564
+ background: "rgba(255,255,255,0.06)",
565
+ color: "#fff",
566
+ cursor: "pointer",
567
+ display: "flex",
568
+ alignItems: "center",
569
+ justifyContent: "center"
570
+ },
571
+ children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6L6 18", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round" }) })
572
+ }
573
+ ),
574
+ /* @__PURE__ */ jsx(
575
+ "div",
576
+ {
577
+ style: {
578
+ position: "absolute",
579
+ top: 24,
580
+ left: "50%",
581
+ transform: "translateX(-50%)",
582
+ background: "rgba(8,10,18,0.82)",
583
+ color: "#fff",
584
+ fontSize: 12,
585
+ fontWeight: 550,
586
+ padding: "6px 13px",
587
+ borderRadius: 999,
588
+ backdropFilter: "blur(6px)"
589
+ },
590
+ children: "Mark up the screenshot \u2014 draw, point, box, or blur sensitive data"
591
+ }
592
+ ),
593
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", top: 62, left: "50%", transform: "translateX(-50%)", width: 600, maxWidth: "92vw", zIndex: 5 }, children: /* @__PURE__ */ jsx(
594
+ AnnotateToolbar,
595
+ {
596
+ tool,
597
+ color,
598
+ onTool: setTool,
599
+ onColor: setColor,
600
+ onUndo: undo,
601
+ onClear: clearAll,
602
+ onContinue: handleContinue
603
+ }
604
+ ) }),
605
+ /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: dims.width, height: dims.height, marginTop: 70, borderRadius: 10, overflow: "hidden", boxShadow: "0 24px 70px rgba(0,0,0,0.5)" }, children: [
606
+ /* @__PURE__ */ jsx(
607
+ "img",
608
+ {
609
+ src,
610
+ alt: "Screenshot",
611
+ draggable: false,
612
+ style: { position: "absolute", inset: 0, width: dims.width, height: dims.height, objectFit: "contain", pointerEvents: "none" }
613
+ }
614
+ ),
615
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, pointerEvents: "none" }, children: blurEls }),
616
+ /* @__PURE__ */ jsx("svg", { width: "100%", height: "100%", style: { position: "absolute", inset: 0, overflow: "visible", pointerEvents: "none" }, children: svgEls }),
617
+ /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: 0, pointerEvents: "none" }, children: labelEls }),
618
+ editingShape && editingShape.type === "text" && /* @__PURE__ */ jsx(
619
+ "input",
620
+ {
621
+ ref: (el) => {
622
+ if (el && document.activeElement !== el) el.focus();
623
+ },
624
+ value: editingShape.text,
625
+ placeholder: "Label\u2026",
626
+ onChange: (e) => updateText(editingShape.id, e.target.value),
627
+ onKeyDown: (e) => {
628
+ if (e.key === "Enter" || e.key === "Escape") {
629
+ e.preventDefault();
630
+ commitText();
631
+ }
632
+ },
633
+ onBlur: commitText,
634
+ style: {
635
+ position: "absolute",
636
+ left: editingShape.x,
637
+ top: editingShape.y,
638
+ transform: "translateY(-50%)",
639
+ background: editingShape.color,
640
+ color: "#fff",
641
+ padding: "3px 8px",
642
+ borderRadius: 6,
643
+ fontSize: 13,
644
+ fontWeight: 600,
645
+ fontFamily: FONT,
646
+ border: "none",
647
+ outline: "none",
648
+ minWidth: 64,
649
+ boxShadow: "0 0 0 2px rgba(255,255,255,0.7),0 2px 10px rgba(0,0,0,0.3)",
650
+ zIndex: 30
651
+ }
652
+ }
653
+ ),
654
+ /* @__PURE__ */ jsx(
655
+ "div",
656
+ {
657
+ ref: stageRef,
658
+ onPointerDown: onDown,
659
+ onPointerMove: onMove,
660
+ onPointerUp: onUp,
661
+ onPointerCancel: onUp,
662
+ style: { position: "absolute", inset: 0, touchAction: "none", userSelect: "none", cursor: tool === "text" ? "text" : "crosshair" }
663
+ }
664
+ )
665
+ ] })
666
+ ]
667
+ }
668
+ );
669
+ }
670
+ function BugReportForm(props) {
671
+ const [metaOpen, setMetaOpen] = useState(true);
672
+ const {
673
+ screenshotUrl,
674
+ annotationCount,
675
+ title,
676
+ description,
677
+ severity,
678
+ type,
679
+ meta,
680
+ backendName,
681
+ submitting,
682
+ error
683
+ } = props;
684
+ return /* @__PURE__ */ jsxs(
685
+ "div",
686
+ {
687
+ style: {
688
+ display: "flex",
689
+ flexDirection: "column",
690
+ height: "100%",
691
+ width: "100%",
692
+ background: "var(--panel)",
693
+ color: "var(--text)",
694
+ fontFamily: FONT,
695
+ overflow: "hidden",
696
+ boxSizing: "border-box"
697
+ },
698
+ children: [
699
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "16px 16px 14px", borderBottom: "1px solid var(--border)", flexShrink: 0 }, children: [
700
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Back", onClick: props.onBack, style: hdrBtn, children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M15 5l-7 7 7 7", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }),
701
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1 }, children: [
702
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 15, fontWeight: 600, letterSpacing: "-0.01em" }, children: "New bug report" }),
703
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 12, color: "var(--muted)", marginTop: 1 }, children: "Review & describe the issue" })
704
+ ] }),
705
+ /* @__PURE__ */ jsx("button", { type: "button", "aria-label": "Close", onClick: props.onClose, style: hdrBtn, children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6L6 18", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round" }) }) })
706
+ ] }),
707
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, overflowY: "auto", padding: 16, display: "flex", flexDirection: "column", gap: 18 }, children: [
708
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12, padding: 10, border: "1px solid var(--border)", borderRadius: 10, background: "var(--surface)" }, children: [
709
+ /* @__PURE__ */ jsx("div", { style: { width: 54, height: 38, borderRadius: 6, background: "var(--canvas)", border: "1px solid var(--border)", overflow: "hidden", flexShrink: 0 }, children: screenshotUrl && /* @__PURE__ */ jsx("img", { src: screenshotUrl, alt: "", style: { width: "100%", height: "100%", objectFit: "cover" } }) }),
710
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
711
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 13, fontWeight: 550 }, children: "Screenshot attached" }),
712
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 12, color: "var(--muted)", marginTop: 1, fontFamily: MONO }, children: [
713
+ annotationCount,
714
+ " annotation",
715
+ annotationCount === 1 ? "" : "s"
716
+ ] })
717
+ ] }),
718
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: props.onEditAnnotations, style: { fontSize: 12, fontWeight: 550, color: "var(--accent)", background: "none", border: "none", cursor: "pointer", padding: "6px 8px", borderRadius: 7 }, children: "Edit" })
719
+ ] }),
720
+ /* @__PURE__ */ jsx(Field, { label: "Summary", children: /* @__PURE__ */ jsx(
721
+ "input",
722
+ {
723
+ value: title,
724
+ onChange: (e) => props.onTitle(e.target.value),
725
+ placeholder: "Chart tooltip overflows on hover",
726
+ style: inputStyle
727
+ }
728
+ ) }),
729
+ /* @__PURE__ */ jsx(Field, { label: "What happened?", children: /* @__PURE__ */ jsx(
730
+ "textarea",
731
+ {
732
+ value: description,
733
+ onChange: (e) => props.onDescription(e.target.value),
734
+ placeholder: "Steps to reproduce, expected vs actual\u2026",
735
+ style: { ...inputStyle, resize: "none", minHeight: 78, lineHeight: 1.5 }
736
+ }
737
+ ) }),
738
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
739
+ /* @__PURE__ */ jsx(Label, { children: "Severity" }),
740
+ /* @__PURE__ */ jsx("div", { style: { display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 6 }, children: SEVERITIES.map((s) => {
741
+ const active = severity === s.id;
742
+ return /* @__PURE__ */ jsxs(
743
+ "button",
744
+ {
745
+ type: "button",
746
+ onClick: () => props.onSeverity(s.id),
747
+ style: {
748
+ display: "flex",
749
+ flexDirection: "column",
750
+ alignItems: "center",
751
+ gap: 5,
752
+ padding: "9px 4px",
753
+ borderRadius: 9,
754
+ border: `1px solid ${active ? "var(--accent)" : "var(--border)"}`,
755
+ background: active ? "var(--ring)" : "var(--surface)",
756
+ cursor: "pointer",
757
+ fontFamily: "inherit"
758
+ },
759
+ children: [
760
+ /* @__PURE__ */ jsx("span", { style: { width: 9, height: 9, borderRadius: "50%", background: s.color, opacity: active ? 1 : 0.55, boxShadow: active ? `0 0 0 3px color-mix(in srgb,${s.color} 22%,transparent)` : "none" } }),
761
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 12, fontWeight: active ? 600 : 550, color: active ? "var(--text)" : "var(--muted)" }, children: s.label })
762
+ ]
763
+ },
764
+ s.id
765
+ );
766
+ }) })
767
+ ] }),
768
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
769
+ /* @__PURE__ */ jsx(Label, { children: "Type" }),
770
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6 }, children: REPORT_TYPES.map((t) => {
771
+ const active = type === t.id;
772
+ return /* @__PURE__ */ jsx(
773
+ "button",
774
+ {
775
+ type: "button",
776
+ onClick: () => props.onType(t.id),
777
+ style: {
778
+ padding: "7px 13px",
779
+ borderRadius: 999,
780
+ border: `1px solid ${active ? "var(--accent)" : "var(--border)"}`,
781
+ background: active ? "var(--accent)" : "var(--surface)",
782
+ color: active ? "var(--on-accent)" : "var(--muted)",
783
+ fontSize: 12.5,
784
+ fontWeight: active ? 600 : 550,
785
+ cursor: "pointer",
786
+ fontFamily: "inherit"
787
+ },
788
+ children: t.label
789
+ },
790
+ t.id
791
+ );
792
+ }) })
793
+ ] }),
794
+ meta.length > 0 && /* @__PURE__ */ jsxs("div", { style: { border: "1px solid var(--border)", borderRadius: 10, overflow: "hidden", background: "var(--surface)" }, children: [
795
+ /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => setMetaOpen((o) => !o), style: { width: "100%", display: "flex", alignItems: "center", gap: 8, padding: "11px 12px", background: "none", border: "none", cursor: "pointer", fontFamily: "inherit", color: "var(--text)" }, children: [
796
+ /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", style: { color: "var(--muted)" }, children: /* @__PURE__ */ jsx("path", { d: "M4 7h16M4 12h16M4 17h10", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }),
797
+ /* @__PURE__ */ jsx("span", { style: { flex: 1, textAlign: "left", fontSize: 12.5, fontWeight: 600 }, children: "Auto-captured context" }),
798
+ /* @__PURE__ */ jsxs("span", { style: { fontSize: 11, color: "var(--muted)", fontFamily: MONO }, children: [
799
+ meta.length,
800
+ " fields"
801
+ ] }),
802
+ /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", style: { color: "var(--muted)", transform: metaOpen ? "rotate(180deg)" : "rotate(0deg)", transition: "transform .18s" }, children: /* @__PURE__ */ jsx("path", { d: "M6 9l6 6 6-6", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) })
803
+ ] }),
804
+ metaOpen && /* @__PURE__ */ jsx("div", { style: { borderTop: "1px solid var(--border)", padding: "4px 12px 8px" }, children: meta.map((m, i) => /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "6px 0", borderBottom: "1px solid var(--border)", fontFamily: MONO, fontSize: 11.5 }, children: [
805
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--muted)", width: 62, flexShrink: 0 }, children: m.label }),
806
+ /* @__PURE__ */ jsx("span", { style: { flex: 1, color: m.flag === "err" ? "#E5484D" : m.flag === "warn" ? "#F5A524" : "var(--text)", textAlign: "right", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: m.value })
807
+ ] }, i)) })
808
+ ] })
809
+ ] }),
810
+ /* @__PURE__ */ jsxs("div", { style: { flexShrink: 0, borderTop: "1px solid var(--border)", padding: "12px 16px 14px", display: "flex", flexDirection: "column", gap: 11, background: "var(--panel)" }, children: [
811
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, fontSize: 12, color: "var(--muted)" }, children: [
812
+ /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx("path", { d: "M4 13l4 4L20 5", stroke: "var(--ok)", strokeWidth: "2.4", strokeLinecap: "round", strokeLinejoin: "round" }) }),
813
+ /* @__PURE__ */ jsxs("span", { style: { flex: 1 }, children: [
814
+ "Filing to ",
815
+ /* @__PURE__ */ jsx("b", { style: { color: "var(--text)", fontWeight: 600 }, children: backendName })
816
+ ] }),
817
+ /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 5, fontFamily: MONO, fontSize: 11, color: "var(--ok)", background: "var(--ok-bg)", padding: "3px 8px", borderRadius: 999 }, children: [
818
+ /* @__PURE__ */ jsx("span", { style: { width: 6, height: 6, borderRadius: "50%", background: "var(--ok)" } }),
819
+ "connected"
820
+ ] })
821
+ ] }),
822
+ error && /* @__PURE__ */ jsx("div", { style: { fontSize: 12.5, color: "#E5484D", fontWeight: 550 }, children: error }),
823
+ /* @__PURE__ */ jsxs(
824
+ "button",
825
+ {
826
+ type: "button",
827
+ disabled: submitting || !title.trim(),
828
+ onClick: props.onSubmit,
829
+ style: {
830
+ width: "100%",
831
+ display: "flex",
832
+ alignItems: "center",
833
+ justifyContent: "center",
834
+ gap: 9,
835
+ padding: 12,
836
+ borderRadius: 10,
837
+ border: "none",
838
+ background: "var(--accent)",
839
+ color: "var(--on-accent)",
840
+ fontSize: 14,
841
+ fontWeight: 600,
842
+ fontFamily: "inherit",
843
+ cursor: submitting || !title.trim() ? "default" : "pointer",
844
+ opacity: submitting || !title.trim() ? 0.85 : 1
845
+ },
846
+ children: [
847
+ submitting && /* @__PURE__ */ jsx("span", { style: { width: 15, height: 15, borderRadius: "50%", border: "2px solid var(--on-accent)", borderTopColor: "transparent", animation: "ocbr-spin .7s linear infinite", display: "inline-block" } }),
848
+ submitting ? "Filing report\u2026" : "Submit report"
849
+ ]
850
+ }
851
+ )
852
+ ] })
853
+ ]
854
+ }
855
+ );
856
+ }
857
+ function Field({ label, children }) {
858
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 7 }, children: [
859
+ /* @__PURE__ */ jsx(Label, { children: label }),
860
+ children
861
+ ] });
862
+ }
863
+ function Label({ children }) {
864
+ return /* @__PURE__ */ jsx("label", { style: { fontSize: 12, fontWeight: 600, color: "var(--muted)", letterSpacing: "0.01em" }, children });
865
+ }
866
+ var hdrBtn = {
867
+ width: 30,
868
+ height: 30,
869
+ display: "flex",
870
+ alignItems: "center",
871
+ justifyContent: "center",
872
+ border: "1px solid var(--border)",
873
+ background: "var(--surface)",
874
+ borderRadius: 8,
875
+ cursor: "pointer",
876
+ color: "var(--muted)",
877
+ padding: 0
878
+ };
879
+ var inputStyle = {
880
+ width: "100%",
881
+ boxSizing: "border-box",
882
+ padding: "10px 12px",
883
+ fontSize: 14,
884
+ fontFamily: "inherit",
885
+ color: "var(--text)",
886
+ background: "var(--surface)",
887
+ border: "1px solid var(--border)",
888
+ borderRadius: 9,
889
+ outline: "none"
890
+ };
891
+ var DEFAULTS = {
892
+ maxWidth: 1280,
893
+ format: "png",
894
+ quality: 0.92
895
+ };
896
+ function downscale(source, maxWidth) {
897
+ if (source.width <= maxWidth) return source;
898
+ const scale = maxWidth / source.width;
899
+ const target = document.createElement("canvas");
900
+ target.width = Math.round(source.width * scale);
901
+ target.height = Math.round(source.height * scale);
902
+ const ctx = target.getContext("2d");
903
+ if (!ctx) return source;
904
+ ctx.drawImage(source, 0, 0, target.width, target.height);
905
+ return target;
906
+ }
907
+ async function captureViewport(options) {
908
+ if (typeof document === "undefined" || typeof window === "undefined") return void 0;
909
+ const { maxWidth, format, quality } = { ...DEFAULTS, ...options };
910
+ try {
911
+ const canvas = await html2canvas(document.body, {
912
+ x: window.scrollX,
913
+ y: window.scrollY,
914
+ width: window.innerWidth,
915
+ height: window.innerHeight,
916
+ useCORS: true,
917
+ allowTaint: true,
918
+ logging: false
919
+ });
920
+ const out = downscale(canvas, maxWidth);
921
+ const mime = format === "jpeg" ? "image/jpeg" : "image/png";
922
+ return out.toDataURL(mime, format === "jpeg" ? quality : void 0);
923
+ } catch {
924
+ return void 0;
925
+ }
926
+ }
927
+ function dataUrlToBase64(dataUrl) {
928
+ return dataUrl.replace(/^data:image\/\w+;base64,/, "");
929
+ }
930
+
931
+ // src/storage.ts
932
+ function getDefaultWebStorage() {
933
+ try {
934
+ if (typeof localStorage === "undefined") return void 0;
935
+ const probe = "__outcode_bug_reporter_probe__";
936
+ localStorage.setItem(probe, "1");
937
+ localStorage.removeItem(probe);
938
+ return {
939
+ getItem: (key) => localStorage.getItem(key),
940
+ setItem: (key, value) => localStorage.setItem(key, value),
941
+ removeItem: (key) => localStorage.removeItem(key)
942
+ };
943
+ } catch {
944
+ return void 0;
945
+ }
946
+ }
947
+ function SuccessCard({ ticket, ticketTitle, backendName, onDone }) {
948
+ return /* @__PURE__ */ jsx(
949
+ "div",
950
+ {
951
+ style: {
952
+ position: "fixed",
953
+ inset: 0,
954
+ zIndex: 2147483100,
955
+ display: "flex",
956
+ alignItems: "center",
957
+ justifyContent: "center",
958
+ background: "rgba(8,10,18,0.5)",
959
+ animation: "ocbr-fade .25s",
960
+ fontFamily: FONT
961
+ },
962
+ children: /* @__PURE__ */ jsxs(
963
+ "div",
964
+ {
965
+ style: {
966
+ width: 344,
967
+ maxWidth: "90vw",
968
+ background: "var(--panel)",
969
+ border: "1px solid var(--border)",
970
+ borderRadius: 18,
971
+ boxShadow: "var(--shadow)",
972
+ padding: 28,
973
+ textAlign: "center",
974
+ color: "var(--text)",
975
+ animation: "ocbr-pop .35s cubic-bezier(.2,1.1,.3,1)"
976
+ },
977
+ children: [
978
+ /* @__PURE__ */ jsx("div", { style: { width: 56, height: 56, borderRadius: "50%", background: "var(--ok-bg)", display: "flex", alignItems: "center", justifyContent: "center", margin: "0 auto 16px" }, children: /* @__PURE__ */ jsx("svg", { width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M4 13l5 5L20 6", stroke: "var(--ok)", strokeWidth: "2.6", strokeLinecap: "round", strokeLinejoin: "round" }) }) }),
979
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 18, fontWeight: 660, letterSpacing: "-0.01em" }, children: "Report filed" }),
980
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 13.5, color: "var(--muted)", marginTop: 5, lineHeight: 1.45 }, children: [
981
+ "Tracked in ",
982
+ backendName,
983
+ " and routed to the right team automatically."
984
+ ] }),
985
+ ticket && /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "center", gap: 9, margin: "18px 0", padding: 11, background: "var(--surface)", border: "1px solid var(--border)", borderRadius: 10, fontFamily: MONO, fontSize: 13 }, children: [
986
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--accent)", fontWeight: 600 }, children: ticket }),
987
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--faint)" }, children: "\xB7" }),
988
+ /* @__PURE__ */ jsx("span", { style: { color: "var(--muted)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", maxWidth: 180 }, children: ticketTitle })
989
+ ] }),
990
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: 8, marginTop: ticket ? 0 : 18 }, children: /* @__PURE__ */ jsx("button", { type: "button", onClick: onDone, style: { flex: 1, padding: 11, borderRadius: 10, border: "1px solid var(--border)", background: "var(--surface)", color: "var(--text)", fontFamily: "inherit", fontSize: 13.5, fontWeight: 600, cursor: "pointer" }, children: "Done" }) })
991
+ ]
992
+ }
993
+ )
994
+ }
995
+ );
996
+ }
997
+ function shortUA() {
998
+ if (typeof navigator === "undefined") return "\u2014";
999
+ const ua = navigator.userAgent;
1000
+ const m = ua.match(/(Chrome|Firefox|Safari|Edg)\/[\d.]+/);
1001
+ const os = /Mac/.test(ua) ? "macOS" : /Win/.test(ua) ? "Windows" : /Linux/.test(ua) ? "Linux" : "";
1002
+ return [m ? m[0].replace("Edg", "Edge") : "Browser", os].filter(Boolean).join(" \xB7 ");
1003
+ }
1004
+ function buildMeta(config) {
1005
+ const rows = [];
1006
+ if (typeof location !== "undefined") rows.push({ label: "Route", value: location.pathname + location.search });
1007
+ rows.push({ label: "Browser", value: shortUA() });
1008
+ if (typeof window !== "undefined") rows.push({ label: "Viewport", value: `${window.innerWidth} \xD7 ${window.innerHeight} @${window.devicePixelRatio || 1}x` });
1009
+ if (typeof navigator !== "undefined") {
1010
+ let tz = "";
1011
+ try {
1012
+ tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
1013
+ } catch {
1014
+ }
1015
+ rows.push({ label: "Locale", value: [navigator.language, tz].filter(Boolean).join(" \xB7 ") });
1016
+ }
1017
+ rows.push({ label: "Release", value: config.appVersion ? `${config.appName}@${config.appVersion}` : config.appName });
1018
+ if (typeof navigator !== "undefined") {
1019
+ if (navigator.language) rows.push({ label: "Language", value: navigator.language });
1020
+ const conn = navigator.connection;
1021
+ if (conn) {
1022
+ const parts = [conn.type, conn.effectiveType, conn.saveData ? "data-saver" : void 0].filter(Boolean);
1023
+ if (parts.length) rows.push({ label: "Connection", value: parts.join(" \xB7 ") });
1024
+ }
1025
+ if (typeof navigator.onLine === "boolean") rows.push({ label: "Online", value: navigator.onLine ? "online" : "offline" });
1026
+ const mem = navigator.deviceMemory;
1027
+ if (mem) rows.push({ label: "Memory", value: `${mem} GB` });
1028
+ if (navigator.hardwareConcurrency) rows.push({ label: "CPU", value: `${navigator.hardwareConcurrency} cores` });
1029
+ }
1030
+ if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
1031
+ const dark = window.matchMedia("(prefers-color-scheme: dark)").matches;
1032
+ const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1033
+ rows.push({ label: "Scheme", value: [dark ? "dark" : "light", reduced ? "reduced-motion" : void 0].filter(Boolean).join(" \xB7 ") });
1034
+ }
1035
+ if (typeof screen !== "undefined" && screen.width) {
1036
+ rows.push({ label: "Screen", value: `${screen.width} \xD7 ${screen.height} \xB7 ${screen.colorDepth}-bit` });
1037
+ }
1038
+ const diag = config.collectDiagnostics?.();
1039
+ const crumbs = diag?.breadcrumbs ?? [];
1040
+ const errs = crumbs.filter((b) => b.level === "error").length;
1041
+ const warns = crumbs.filter((b) => b.level === "warn").length;
1042
+ if (errs || warns) rows.push({ label: "Console", value: `${errs} error${errs === 1 ? "" : "s"} \xB7 ${warns} warning${warns === 1 ? "" : "s"}`, flag: errs ? "err" : "warn" });
1043
+ if (diag?.lastFailedApiCall) rows.push({ label: "Network", value: "1 failed request", flag: "warn" });
1044
+ return rows;
1045
+ }
1046
+ function BugReporterButton({ config, label = "Report a bug", style }) {
1047
+ const theme = useMemo(() => resolveTheme(config.theme), [config.theme]);
1048
+ const backendName = config.backendName ?? "ClickUp";
1049
+ const [step, setStep] = useState("idle");
1050
+ const [screenshot, setScreenshot] = useState();
1051
+ const [edited, setEdited] = useState();
1052
+ const [annCount, setAnnCount] = useState(0);
1053
+ const [title, setTitle] = useState("");
1054
+ const [desc, setDesc] = useState("");
1055
+ const [severity, setSeverity] = useState("medium");
1056
+ const [type, setType] = useState("bug");
1057
+ const [ticket, setTicket] = useState();
1058
+ const [error, setError] = useState();
1059
+ const [meta, setMeta] = useState([]);
1060
+ useEffect(() => {
1061
+ ensureKeyframes();
1062
+ const storage = config.storage ?? getDefaultWebStorage();
1063
+ if (storage) void new ReportQueue(storage).flush(config);
1064
+ }, []);
1065
+ const queue = useMemo(() => {
1066
+ const storage = config.storage ?? getDefaultWebStorage();
1067
+ return storage ? new ReportQueue(storage) : void 0;
1068
+ }, [config.storage]);
1069
+ const reset = () => {
1070
+ setStep("idle");
1071
+ setScreenshot(void 0);
1072
+ setEdited(void 0);
1073
+ setAnnCount(0);
1074
+ setTitle("");
1075
+ setDesc("");
1076
+ setSeverity("medium");
1077
+ setType("bug");
1078
+ setTicket(void 0);
1079
+ setError(void 0);
1080
+ };
1081
+ const open = async () => {
1082
+ if (step !== "idle") return;
1083
+ setStep("capturing");
1084
+ const shot = await captureViewport(config.screenshot);
1085
+ setScreenshot(shot);
1086
+ setStep("annotate");
1087
+ };
1088
+ const onContinue = (flattened, count) => {
1089
+ setEdited(flattened);
1090
+ setAnnCount(count);
1091
+ setMeta(buildMeta(config));
1092
+ setStep("form");
1093
+ if (config.collectContext) {
1094
+ void (async () => {
1095
+ try {
1096
+ const extra = normalizeContext(await config.collectContext());
1097
+ if (extra.length) setMeta((m) => [...m, ...extra]);
1098
+ } catch {
1099
+ }
1100
+ })();
1101
+ }
1102
+ };
1103
+ const submit = async () => {
1104
+ if (!title.trim()) return;
1105
+ setStep("submitting");
1106
+ setError(void 0);
1107
+ const shot = edited ?? screenshot;
1108
+ const options = {
1109
+ title: title.trim(),
1110
+ description: desc.trim(),
1111
+ screenshotBase64: shot ? dataUrlToBase64(shot) : void 0,
1112
+ isReportingProblem: true,
1113
+ severity,
1114
+ type,
1115
+ context: meta.map((m) => ({ label: m.label, value: m.value })),
1116
+ metadata: { platform: "web", userAgent: typeof navigator !== "undefined" ? navigator.userAgent : void 0 }
1117
+ };
1118
+ try {
1119
+ const res = await submitReport(config, options);
1120
+ if (res.success) {
1121
+ setTicket(res.id);
1122
+ setStep("done");
1123
+ } else if (queue) {
1124
+ await queue.enqueue(options);
1125
+ setTicket(void 0);
1126
+ setStep("done");
1127
+ } else {
1128
+ setError(res.message || "Submission failed.");
1129
+ setStep("form");
1130
+ }
1131
+ } catch (e) {
1132
+ if (queue) {
1133
+ await queue.enqueue(options);
1134
+ setStep("done");
1135
+ } else {
1136
+ setError(e instanceof Error ? e.message : "Submission failed.");
1137
+ setStep("form");
1138
+ }
1139
+ }
1140
+ };
1141
+ const submitting = step === "submitting";
1142
+ return /* @__PURE__ */ jsxs("div", { style: { ...cssVars(theme), fontFamily: FONT }, children: [
1143
+ step === "idle" && /* @__PURE__ */ jsxs(Fragment, { children: [
1144
+ /* @__PURE__ */ jsx(
1145
+ "div",
1146
+ {
1147
+ style: {
1148
+ position: "fixed",
1149
+ bottom: 86,
1150
+ right: 26,
1151
+ background: "var(--panel)",
1152
+ border: "1px solid var(--border)",
1153
+ borderRadius: 11,
1154
+ padding: "8px 13px",
1155
+ fontSize: 12.5,
1156
+ fontWeight: 600,
1157
+ color: "var(--text)",
1158
+ boxShadow: "var(--shadow)",
1159
+ zIndex: 2147482900,
1160
+ whiteSpace: "nowrap",
1161
+ animation: "ocbr-float 3s ease-in-out infinite",
1162
+ pointerEvents: "none"
1163
+ },
1164
+ children: "Spotted a bug? Tap \u2192"
1165
+ }
1166
+ ),
1167
+ /* @__PURE__ */ jsxs(
1168
+ "button",
1169
+ {
1170
+ type: "button",
1171
+ "aria-label": label,
1172
+ onClick: open,
1173
+ style: {
1174
+ position: "fixed",
1175
+ bottom: 26,
1176
+ right: 26,
1177
+ width: 56,
1178
+ height: 56,
1179
+ borderRadius: "50%",
1180
+ background: "var(--accent)",
1181
+ border: "none",
1182
+ cursor: "pointer",
1183
+ display: "flex",
1184
+ alignItems: "center",
1185
+ justifyContent: "center",
1186
+ boxShadow: "0 10px 28px rgba(40,40,90,0.34)",
1187
+ zIndex: 2147482950,
1188
+ ...style
1189
+ },
1190
+ children: [
1191
+ /* @__PURE__ */ jsx("span", { style: { position: "absolute", inset: 0, borderRadius: "50%", border: "2px solid var(--accent)", animation: "ocbr-ping 1.9s ease-out infinite" } }),
1192
+ /* @__PURE__ */ jsxs("svg", { width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", children: [
1193
+ /* @__PURE__ */ jsx("path", { d: "M8 11a4 4 0 018 0v3a4 4 0 01-8 0z", stroke: "#fff", strokeWidth: "1.7" }),
1194
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "7.2", r: "2", stroke: "#fff", strokeWidth: "1.6" }),
1195
+ /* @__PURE__ */ jsx("path", { d: "M10.4 5.6L9 4.2M13.6 5.6L15 4.2M8 12H5.4M8 15H5.6M16 12h2.6M16 15h2.4M8.4 17.6l-1.8 1.8M15.6 17.6l1.8 1.8", stroke: "#fff", strokeWidth: "1.5", strokeLinecap: "round" })
1196
+ ] })
1197
+ ]
1198
+ }
1199
+ )
1200
+ ] }),
1201
+ step === "capturing" && /* @__PURE__ */ jsx("div", { style: { position: "fixed", inset: 0, background: "#fff", zIndex: 2147483200, animation: "ocbr-flash .7s ease-out forwards", pointerEvents: "none" } }),
1202
+ step === "annotate" && screenshot && /* @__PURE__ */ jsx(AnnotateOverlay, { src: screenshot, onCancel: reset, onContinue }),
1203
+ (step === "form" || step === "submitting") && /* @__PURE__ */ jsxs("div", { style: { position: "fixed", inset: 0, zIndex: 2147483050, fontFamily: FONT }, children: [
1204
+ /* @__PURE__ */ jsx("div", { onClick: reset, style: { position: "absolute", inset: 0, background: "rgba(8,10,18,0.5)", animation: "ocbr-fade .25s" }, children: (edited ?? screenshot) && /* @__PURE__ */ jsx("img", { src: edited ?? screenshot, alt: "", style: { width: "100%", height: "100%", objectFit: "cover", opacity: 0.25, filter: "blur(1px)" } }) }),
1205
+ /* @__PURE__ */ jsx(
1206
+ "div",
1207
+ {
1208
+ style: {
1209
+ position: "absolute",
1210
+ top: 0,
1211
+ right: 0,
1212
+ bottom: 0,
1213
+ width: "min(400px, 100vw)",
1214
+ boxShadow: "-14px 0 44px rgba(10,12,30,0.20)",
1215
+ borderLeft: "1px solid var(--border)",
1216
+ animation: "ocbr-panel-in .3s cubic-bezier(.2,.8,.2,1)"
1217
+ },
1218
+ children: /* @__PURE__ */ jsx(
1219
+ BugReportForm,
1220
+ {
1221
+ screenshotUrl: edited ?? screenshot,
1222
+ annotationCount: annCount,
1223
+ title,
1224
+ description: desc,
1225
+ severity,
1226
+ type,
1227
+ meta,
1228
+ backendName,
1229
+ submitting,
1230
+ error,
1231
+ onTitle: setTitle,
1232
+ onDescription: setDesc,
1233
+ onSeverity: setSeverity,
1234
+ onType: setType,
1235
+ onBack: () => setStep("annotate"),
1236
+ onClose: reset,
1237
+ onEditAnnotations: () => setStep("annotate"),
1238
+ onSubmit: submit
1239
+ }
1240
+ )
1241
+ }
1242
+ )
1243
+ ] }),
1244
+ step === "done" && /* @__PURE__ */ jsx(
1245
+ SuccessCard,
1246
+ {
1247
+ ticket,
1248
+ ticketTitle: title.trim() || "Untitled report",
1249
+ backendName,
1250
+ onDone: reset
1251
+ }
1252
+ )
1253
+ ] });
1254
+ }
1255
+
1256
+ export { AnnotateOverlay, AnnotateToolbar, BugReportForm, BugReporterButton, SuccessCard, captureViewport, cssVars, dataUrlToBase64, ensureKeyframes, flattenAnnotations, getDefaultWebStorage };
1257
+ //# sourceMappingURL=index.mjs.map
1258
+ //# sourceMappingURL=index.mjs.map