bugstash 0.1.6 → 0.1.8

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.cjs CHANGED
@@ -1,1328 +1,13 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- BugStash: () => BugStash,
34
- default: () => index_default
35
- });
36
- module.exports = __toCommonJS(index_exports);
37
-
38
- // src/breadcrumbs.ts
39
- var crumbs = [];
40
- var maxCrumbs = 50;
41
- var clickHandler = null;
42
- var inputHandler = null;
43
- var popstateHandler = null;
44
- var hashHandler = null;
45
- function getSelector(el) {
46
- if (el.id) return `#${el.id}`;
47
- const tag = el.tagName.toLowerCase();
48
- const cls = el.className && typeof el.className === "string" ? "." + el.className.trim().split(/\s+/).slice(0, 2).join(".") : "";
49
- const text = (el.textContent || "").trim().slice(0, 30);
50
- const label = text ? ` "${text}"` : "";
51
- return `${tag}${cls}${label}`;
52
- }
53
- function addBreadcrumb(crumb) {
54
- crumbs.push(crumb);
55
- if (crumbs.length > maxCrumbs) crumbs.shift();
56
- }
57
- function initBreadcrumbs(max) {
58
- if (max) maxCrumbs = max;
59
- clickHandler = (e) => {
60
- const target = e.target;
61
- if (!target || !target.tagName) return;
62
- addBreadcrumb({
63
- type: "click",
64
- category: "ui",
65
- message: `Clicked ${getSelector(target)}`,
66
- timestamp: Date.now(),
67
- data: { x: e.clientX, y: e.clientY, selector: getSelector(target) }
68
- });
69
- };
70
- document.addEventListener("click", clickHandler, true);
71
- const inputTimers = /* @__PURE__ */ new WeakMap();
72
- inputHandler = (e) => {
73
- const target = e.target;
74
- if (!target || !target.tagName) return;
75
- const tag = target.tagName.toLowerCase();
76
- if (tag !== "input" && tag !== "textarea" && tag !== "select") return;
77
- const existing = inputTimers.get(target);
78
- if (existing) clearTimeout(existing);
79
- inputTimers.set(
80
- target,
81
- window.setTimeout(() => {
82
- const isPassword = target instanceof HTMLInputElement && target.type === "password";
83
- addBreadcrumb({
84
- type: "input",
85
- category: "ui",
86
- message: `Input ${getSelector(target)}`,
87
- timestamp: Date.now(),
88
- data: {
89
- selector: getSelector(target),
90
- value: isPassword ? "[redacted]" : void 0
91
- }
92
- });
93
- }, 300)
94
- );
95
- };
96
- document.addEventListener("input", inputHandler, true);
97
- popstateHandler = () => {
98
- addBreadcrumb({
99
- type: "navigation",
100
- category: "navigation",
101
- message: `Navigated to ${window.location.pathname}`,
102
- timestamp: Date.now(),
103
- data: { url: window.location.href }
104
- });
105
- };
106
- window.addEventListener("popstate", popstateHandler);
107
- hashHandler = () => {
108
- addBreadcrumb({
109
- type: "navigation",
110
- category: "navigation",
111
- message: `Hash changed to ${window.location.hash}`,
112
- timestamp: Date.now(),
113
- data: { url: window.location.href }
114
- });
115
- };
116
- window.addEventListener("hashchange", hashHandler);
117
- addBreadcrumb({
118
- type: "navigation",
119
- category: "navigation",
120
- message: `Page loaded: ${window.location.pathname}`,
121
- timestamp: Date.now(),
122
- data: { url: window.location.href }
123
- });
124
- }
125
- function getBreadcrumbs() {
126
- return [...crumbs];
127
- }
128
- function clearBreadcrumbs() {
129
- crumbs = [];
130
- }
131
- function restoreBreadcrumbs() {
132
- if (clickHandler) document.removeEventListener("click", clickHandler, true);
133
- if (inputHandler) document.removeEventListener("input", inputHandler, true);
134
- if (popstateHandler) window.removeEventListener("popstate", popstateHandler);
135
- if (hashHandler) window.removeEventListener("hashchange", hashHandler);
136
- clickHandler = null;
137
- inputHandler = null;
138
- popstateHandler = null;
139
- hashHandler = null;
140
- }
141
-
142
- // src/redact.ts
143
- var REDACT_PLACEHOLDER = "[REDACTED]";
144
- var PATTERNS = [
145
- // Credit card numbers (Visa, MC, Amex, etc.)
146
- [/\b(?:\d[ -]*?){13,19}\b/g, "[CC_REDACTED]"],
147
- // SSN
148
- [/\b\d{3}-\d{2}-\d{4}\b/g, "[SSN_REDACTED]"],
149
- // Email addresses (in log strings, not for user context)
150
- // Intentionally NOT redacting emails since they're often needed for debugging
151
- // Bearer tokens
152
- [/Bearer\s+[A-Za-z0-9\-._~+/]+=*/g, "Bearer [TOKEN_REDACTED]"],
153
- // JWT tokens (xxx.yyy.zzz pattern)
154
- [/eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, "[JWT_REDACTED]"],
155
- // API keys (long hex/base64 strings that look like keys)
156
- [/(?:api[_-]?key|apikey|secret|token|password|passwd|authorization)['":\s=]+['"]?([A-Za-z0-9\-._~+/]{20,})['"]?/gi, (match) => {
157
- const eqIdx = match.search(/[=:]/);
158
- return match.slice(0, eqIdx + 1) + " " + REDACT_PLACEHOLDER;
159
- }],
160
- // AWS access keys
161
- [/AKIA[0-9A-Z]{16}/g, "[AWS_KEY_REDACTED]"],
162
- // Generic password fields in JSON
163
- [/"(?:password|passwd|secret|token|access_token|refresh_token|api_key|apiKey|private_key)":\s*"[^"]*"/gi, (match) => {
164
- const colonIdx = match.indexOf(":");
165
- return match.slice(0, colonIdx + 1) + ' "' + REDACT_PLACEHOLDER + '"';
166
- }]
167
- ];
168
- function redactString(input) {
169
- let result = input;
170
- for (const [pattern, replacement] of PATTERNS) {
171
- if (typeof replacement === "function") {
172
- result = result.replace(pattern, replacement);
173
- } else {
174
- result = result.replace(pattern, replacement);
175
- }
176
- }
177
- return result;
178
- }
179
- function redactLogArgs(args) {
180
- return args.map(redactString);
181
- }
182
- function redactObject(obj) {
183
- if (typeof obj === "string") return redactString(obj);
184
- if (Array.isArray(obj)) return obj.map(redactObject);
185
- if (obj && typeof obj === "object") {
186
- const result = {};
187
- for (const [key, value] of Object.entries(obj)) {
188
- const lk = key.toLowerCase();
189
- if (lk.includes("password") || lk.includes("secret") || lk.includes("token") || lk.includes("apikey") || lk.includes("api_key") || lk.includes("private")) {
190
- result[key] = REDACT_PLACEHOLDER;
191
- } else {
192
- result[key] = redactObject(value);
193
- }
194
- }
195
- return result;
196
- }
197
- return obj;
198
- }
199
-
200
- // src/logger.ts
201
- var originals = {
202
- log: console.log,
203
- warn: console.warn,
204
- error: console.error,
205
- debug: console.debug,
206
- info: console.info
207
- };
208
- var logs = [];
209
- var maxLogs = 50;
210
- function serialize(args) {
211
- return args.map((a) => {
212
- if (a instanceof Error) return `${a.name}: ${a.message}
213
- ${a.stack ?? ""}`;
214
- if (typeof a === "object") {
215
- try {
216
- return JSON.stringify(a, null, 2);
217
- } catch {
218
- return String(a);
219
- }
220
- }
221
- return String(a);
222
- });
223
- }
224
- function capture(level, args) {
225
- const entry = {
226
- level,
227
- args: redactLogArgs(serialize(args)),
228
- timestamp: Date.now()
229
- };
230
- if (level === "error") {
231
- entry.stack = new Error().stack?.split("\n").slice(2).join("\n");
232
- }
233
- logs.push(entry);
234
- if (logs.length > maxLogs) logs.shift();
235
- addBreadcrumb({
236
- type: "console",
237
- category: `console.${level}`,
238
- message: entry.args.join(" ").slice(0, 200),
239
- timestamp: entry.timestamp
240
- });
241
- }
242
- function initLogger(max) {
243
- if (max) maxLogs = max;
244
- for (const level of Object.keys(originals)) {
245
- console[level] = function(...args) {
246
- capture(level, args);
247
- originals[level].apply(console, args);
248
- };
249
- }
250
- }
251
- function getLogs() {
252
- return [...logs];
253
- }
254
- function clearLogs() {
255
- logs = [];
256
- }
257
- function restoreConsole() {
258
- for (const level of Object.keys(originals)) {
259
- console[level] = originals[level];
260
- }
261
- }
262
-
263
- // src/network.ts
264
- var captures = [];
265
- var maxCaptures = 50;
266
- var originalFetch;
267
- var originalXHROpen;
268
- var originalXHRSend;
269
- function record(entry) {
270
- captures.push(entry);
271
- if (captures.length > maxCaptures) captures.shift();
272
- addBreadcrumb({
273
- type: "network",
274
- category: entry.failed ? "network.error" : "network.ok",
275
- message: `${entry.method} ${entry.url} \u2192 ${entry.status} (${entry.duration}ms)`,
276
- timestamp: entry.timestamp,
277
- data: { status: entry.status, duration: entry.duration }
278
- });
279
- }
280
- function shortenUrl(url) {
281
- try {
282
- const u = new URL(url, window.location.origin);
283
- const params = new URLSearchParams(u.search);
284
- for (const key of params.keys()) {
285
- const lk = key.toLowerCase();
286
- if (lk.includes("token") || lk.includes("key") || lk.includes("secret") || lk.includes("password") || lk.includes("auth")) {
287
- params.set(key, "[REDACTED]");
288
- }
289
- }
290
- const qs = params.toString();
291
- return u.pathname + (qs ? "?" + qs : "");
292
- } catch {
293
- return redactString(url);
294
- }
295
- }
296
- function patchFetch() {
297
- originalFetch = window.fetch;
298
- window.fetch = async function(input, init) {
299
- const method = init?.method?.toUpperCase() ?? "GET";
300
- const rawUrl = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
301
- const url = shortenUrl(rawUrl);
302
- const start = Date.now();
303
- try {
304
- const response = await originalFetch.call(window, input, init);
305
- record({
306
- method,
307
- url,
308
- status: response.status,
309
- statusText: response.statusText,
310
- duration: Date.now() - start,
311
- responseType: response.headers.get("content-type") ?? void 0,
312
- timestamp: start,
313
- failed: response.status >= 400
314
- });
315
- return response;
316
- } catch (err) {
317
- record({
318
- method,
319
- url,
320
- status: 0,
321
- statusText: "Network Error",
322
- duration: Date.now() - start,
323
- timestamp: start,
324
- failed: true
325
- });
326
- throw err;
327
- }
328
- };
329
- }
330
- function patchXHR() {
331
- originalXHROpen = XMLHttpRequest.prototype.open;
332
- originalXHRSend = XMLHttpRequest.prototype.send;
333
- XMLHttpRequest.prototype.open = function(method, url, ...rest) {
334
- this.__bs_method = method.toUpperCase();
335
- this.__bs_url = shortenUrl(typeof url === "string" ? url : url.href);
336
- return originalXHROpen.apply(this, [method, url, ...rest]);
337
- };
338
- XMLHttpRequest.prototype.send = function(body) {
339
- const start = Date.now();
340
- this.addEventListener("loadend", function() {
341
- record({
342
- method: this.__bs_method ?? "GET",
343
- url: this.__bs_url ?? "",
344
- status: this.status,
345
- statusText: this.statusText,
346
- duration: Date.now() - start,
347
- responseType: this.getResponseHeader("content-type") ?? void 0,
348
- timestamp: start,
349
- failed: this.status >= 400 || this.status === 0
350
- });
351
- });
352
- return originalXHRSend.call(this, body);
353
- };
354
- }
355
- function initNetwork(max) {
356
- if (max) maxCaptures = max;
357
- patchFetch();
358
- patchXHR();
359
- }
360
- function getNetworkCaptures() {
361
- return [...captures];
362
- }
363
- function getFailedNetworkCaptures() {
364
- return captures.filter((c) => c.failed);
365
- }
366
- function clearNetworkCaptures() {
367
- captures = [];
368
- }
369
- function restoreNetwork() {
370
- if (originalFetch) window.fetch = originalFetch;
371
- if (originalXHROpen) XMLHttpRequest.prototype.open = originalXHROpen;
372
- if (originalXHRSend) XMLHttpRequest.prototype.send = originalXHRSend;
373
- }
374
-
375
- // src/errors.ts
376
- var errors = [];
377
- var onErrorHandler = null;
378
- var onRejectionHandler = null;
379
- function initErrors() {
380
- onErrorHandler = (event) => {
381
- const entry = {
382
- message: event.message || "Unknown error",
383
- source: event.filename,
384
- lineno: event.lineno,
385
- colno: event.colno,
386
- stack: event.error?.stack,
387
- type: "error",
388
- timestamp: Date.now()
389
- };
390
- errors.push(entry);
391
- addBreadcrumb({
392
- type: "error",
393
- category: "exception",
394
- message: entry.message,
395
- timestamp: entry.timestamp,
396
- data: { source: entry.source, lineno: entry.lineno }
397
- });
398
- };
399
- onRejectionHandler = (event) => {
400
- const reason = event.reason;
401
- const message = reason instanceof Error ? reason.message : typeof reason === "string" ? reason : "Unhandled promise rejection";
402
- const entry = {
403
- message,
404
- stack: reason instanceof Error ? reason.stack : void 0,
405
- type: "unhandledrejection",
406
- timestamp: Date.now()
407
- };
408
- errors.push(entry);
409
- addBreadcrumb({
410
- type: "error",
411
- category: "promise",
412
- message,
413
- timestamp: entry.timestamp
414
- });
415
- };
416
- window.addEventListener("error", onErrorHandler);
417
- window.addEventListener("unhandledrejection", onRejectionHandler);
418
- }
419
- function getErrors() {
420
- return [...errors];
421
- }
422
- function clearErrors() {
423
- errors = [];
424
- }
425
- function restoreErrors() {
426
- if (onErrorHandler) window.removeEventListener("error", onErrorHandler);
427
- if (onRejectionHandler) window.removeEventListener("unhandledrejection", onRejectionHandler);
428
- onErrorHandler = null;
429
- onRejectionHandler = null;
430
- }
431
-
432
- // src/performance.ts
433
- var metrics = null;
434
- var lcpObserver = null;
435
- var clsObserver = null;
436
- var fidObserver = null;
437
- function initPerformance() {
438
- metrics = { timestamp: Date.now() };
439
- if (performance.getEntriesByType) {
440
- const onLoad = () => {
441
- const [nav] = performance.getEntriesByType("navigation");
442
- if (nav && metrics) {
443
- metrics.pageLoadTime = Math.round(nav.loadEventEnd - nav.startTime);
444
- metrics.domContentLoaded = Math.round(nav.domContentLoadedEventEnd - nav.startTime);
445
- }
446
- const paints = performance.getEntriesByType("paint");
447
- for (const p of paints) {
448
- if (p.name === "first-paint" && metrics) metrics.firstPaint = Math.round(p.startTime);
449
- if (p.name === "first-contentful-paint" && metrics) metrics.firstContentfulPaint = Math.round(p.startTime);
450
- }
451
- if (metrics) {
452
- metrics.resourceCount = performance.getEntriesByType("resource").length;
453
- }
454
- const mem = performance.memory;
455
- if (mem && metrics) {
456
- metrics.memoryUsage = {
457
- usedJSHeapSize: mem.usedJSHeapSize,
458
- totalJSHeapSize: mem.totalJSHeapSize
459
- };
460
- }
461
- };
462
- if (document.readyState === "complete") {
463
- setTimeout(onLoad, 0);
464
- } else {
465
- window.addEventListener("load", () => setTimeout(onLoad, 100));
466
- }
467
- }
468
- if (typeof PerformanceObserver !== "undefined") {
469
- try {
470
- lcpObserver = new PerformanceObserver((list) => {
471
- const entries = list.getEntries();
472
- const last = entries[entries.length - 1];
473
- if (last && metrics) metrics.largestContentfulPaint = Math.round(last.startTime);
474
- });
475
- lcpObserver.observe({ type: "largest-contentful-paint", buffered: true });
476
- } catch {
477
- }
478
- try {
479
- let clsValue = 0;
480
- clsObserver = new PerformanceObserver((list) => {
481
- for (const entry of list.getEntries()) {
482
- if (!entry.hadRecentInput) {
483
- clsValue += entry.value;
484
- }
485
- }
486
- if (metrics) metrics.cumulativeLayoutShift = Math.round(clsValue * 1e3) / 1e3;
487
- });
488
- clsObserver.observe({ type: "layout-shift", buffered: true });
489
- } catch {
490
- }
491
- try {
492
- fidObserver = new PerformanceObserver((list) => {
493
- const [entry] = list.getEntries();
494
- if (entry && metrics) metrics.firstInputDelay = Math.round(entry.processingStart - entry.startTime);
495
- });
496
- fidObserver.observe({ type: "first-input", buffered: true });
497
- } catch {
498
- }
499
- }
500
- }
501
- function getPerformanceMetrics() {
502
- if (metrics) metrics.timestamp = Date.now();
503
- return metrics ? { ...metrics } : null;
504
- }
505
- function restorePerformance() {
506
- lcpObserver?.disconnect();
507
- clsObserver?.disconnect();
508
- fidObserver?.disconnect();
509
- lcpObserver = null;
510
- clsObserver = null;
511
- fidObserver = null;
512
- metrics = null;
513
- }
514
-
515
- // src/screenshot.ts
516
- async function captureScreenshot() {
517
- try {
518
- const html2canvas = window.html2canvas ?? (await import("html2canvas")).default;
519
- if (html2canvas) {
520
- const canvas2 = await html2canvas(document.body, {
521
- logging: false,
522
- useCORS: true,
523
- scale: 0.5,
524
- width: window.innerWidth,
525
- height: window.innerHeight
526
- });
527
- return canvas2.toDataURL("image/jpeg", 0.6);
528
- }
529
- } catch {
530
- }
531
- return null;
532
- }
533
-
534
- // src/api.ts
535
- var PRIMARY_ENDPOINT = "https://api.bugstash.com";
536
- var FALLBACK_ENDPOINT = "https://bugstash-backend.azurewebsites.net";
537
- var endpoint = PRIMARY_ENDPOINT;
538
- var _usingFallback = false;
539
- function setEndpoint(url) {
540
- endpoint = url.replace(/\/$/, "");
541
- _usingFallback = false;
542
- }
543
- function getEndpoint() {
544
- return endpoint;
545
- }
546
- async function fetchWithFallback(url, init) {
547
- try {
548
- const res = await fetch(url, init);
549
- return res;
550
- } catch (err) {
551
- if (_usingFallback) throw err;
552
- _usingFallback = true;
553
- const previousEndpoint = endpoint;
554
- endpoint = FALLBACK_ENDPOINT;
555
- const fallbackUrl = url.replace(previousEndpoint, FALLBACK_ENDPOINT);
556
- return fetch(fallbackUrl, init);
557
- }
558
- }
559
- var STORAGE_KEY = "bugstash_auth";
560
- function getStoredAuth() {
561
- try {
562
- const raw = localStorage.getItem(STORAGE_KEY);
563
- if (!raw) return null;
564
- const auth = JSON.parse(raw);
565
- if (auth.tokens.expiresAt < Date.now()) {
566
- return auth;
567
- }
568
- return auth;
569
- } catch {
570
- return null;
571
- }
572
- }
573
- function setStoredAuth(auth) {
574
- localStorage.setItem(STORAGE_KEY, JSON.stringify(auth));
575
- }
576
- function clearStoredAuth() {
577
- localStorage.removeItem(STORAGE_KEY);
578
- }
579
- function getCurrentUser() {
580
- return getStoredAuth()?.user || null;
581
- }
582
- function getAccessToken() {
583
- return getStoredAuth()?.tokens.accessToken || null;
584
- }
585
- async function authHeaders() {
586
- const auth = getStoredAuth();
587
- if (!auth) return { "Content-Type": "application/json" };
588
- if (auth.tokens.expiresAt < Date.now() + 6e4) {
589
- try {
590
- const res = await fetchWithFallback(`${endpoint}/api/auth/refresh`, {
591
- method: "POST",
592
- headers: { "Content-Type": "application/json" },
593
- body: JSON.stringify({ refreshToken: auth.tokens.refreshToken })
594
- });
595
- if (res.ok) {
596
- const data = await res.json();
597
- if (data.success) {
598
- auth.tokens = data.data;
599
- setStoredAuth(auth);
600
- }
601
- }
602
- } catch {
603
- }
604
- }
605
- return {
606
- "Content-Type": "application/json",
607
- "Authorization": `Bearer ${auth.tokens.accessToken}`
608
- };
609
- }
610
- async function login(email, password, projectId2) {
611
- try {
612
- const res = await fetchWithFallback(`${endpoint}/api/auth/login`, {
613
- method: "POST",
614
- headers: { "Content-Type": "application/json" },
615
- body: JSON.stringify({ email, password, projectId: projectId2 })
616
- });
617
- const data = await res.json();
618
- if (data.success) {
619
- setStoredAuth({ user: data.data.user, tokens: data.data.tokens });
620
- }
621
- return data;
622
- } catch {
623
- return { success: false, error: "Network error" };
624
- }
625
- }
626
- async function logout() {
627
- clearStoredAuth();
628
- }
629
- async function submitReport(report) {
630
- try {
631
- const headers = await authHeaders();
632
- const res = await fetchWithFallback(`${endpoint}/api/reports`, {
633
- method: "POST",
634
- headers,
635
- body: JSON.stringify(report)
636
- });
637
- return await res.json();
638
- } catch {
639
- return { success: false, error: "Network error - could not reach BugStash" };
640
- }
641
- }
642
- async function fetchPagePins(projectId2, pathname) {
643
- try {
644
- const headers = await authHeaders();
645
- const res = await fetchWithFallback(`${endpoint}/api/pins/by-page?projectId=${projectId2}&pathname=${encodeURIComponent(pathname)}`, { headers });
646
- return await res.json();
647
- } catch {
648
- return { success: false, error: "Network error" };
649
- }
650
- }
651
- async function createPin(pin) {
652
- try {
653
- const headers = await authHeaders();
654
- const res = await fetchWithFallback(`${endpoint}/api/pins`, {
655
- method: "POST",
656
- headers,
657
- body: JSON.stringify(pin)
658
- });
659
- return await res.json();
660
- } catch {
661
- return { success: false, error: "Network error" };
662
- }
663
- }
664
- async function updatePin(pinId, updates) {
665
- try {
666
- const headers = await authHeaders();
667
- const res = await fetchWithFallback(`${endpoint}/api/pins/${pinId}`, {
668
- method: "PUT",
669
- headers,
670
- body: JSON.stringify(updates)
671
- });
672
- return await res.json();
673
- } catch {
674
- return { success: false, error: "Network error" };
675
- }
676
- }
677
- async function deletePin(pinId) {
678
- try {
679
- const headers = await authHeaders();
680
- const res = await fetchWithFallback(`${endpoint}/api/pins/${pinId}`, {
681
- method: "DELETE",
682
- headers
683
- });
684
- return await res.json();
685
- } catch {
686
- return { success: false, error: "Network error" };
687
- }
688
- }
689
- async function fetchComments(pinId) {
690
- try {
691
- const headers = await authHeaders();
692
- const res = await fetchWithFallback(`${endpoint}/api/pins/${pinId}/comments`, { headers });
693
- return await res.json();
694
- } catch {
695
- return { success: false, error: "Network error" };
696
- }
697
- }
698
- async function createComment(pinId, body, mentions = []) {
699
- try {
700
- const headers = await authHeaders();
701
- const res = await fetchWithFallback(`${endpoint}/api/pins/${pinId}/comments`, {
702
- method: "POST",
703
- headers,
704
- body: JSON.stringify({ body, mentions })
705
- });
706
- return await res.json();
707
- } catch {
708
- return { success: false, error: "Network error" };
709
- }
710
- }
711
- async function fetchProjectMembers(projectId2) {
712
- try {
713
- const headers = await authHeaders();
714
- const res = await fetchWithFallback(`${endpoint}/api/members/for-project/${projectId2}`, { headers });
715
- return await res.json();
716
- } catch {
717
- return { success: false, error: "Network error" };
718
- }
719
- }
720
- var QUEUE_KEY = "bugstash_offline_queue";
721
- function queueOfflineAction(action) {
722
- const queue = getOfflineQueue();
723
- queue.push({
724
- ...action,
725
- id: Math.random().toString(36).slice(2),
726
- timestamp: Date.now()
727
- });
728
- localStorage.setItem(QUEUE_KEY, JSON.stringify(queue));
729
- }
730
- function getOfflineQueue() {
731
- try {
732
- return JSON.parse(localStorage.getItem(QUEUE_KEY) || "[]");
733
- } catch {
734
- return [];
735
- }
736
- }
737
- async function flushOfflineQueue() {
738
- const queue = getOfflineQueue();
739
- if (queue.length === 0) return 0;
740
- let flushed = 0;
741
- const remaining = [];
742
- for (const action of queue) {
743
- try {
744
- let result;
745
- switch (action.type) {
746
- case "create_pin":
747
- result = await createPin(action.data);
748
- break;
749
- case "create_comment":
750
- result = await createComment(action.data.pinId, action.data.body, action.data.mentions);
751
- break;
752
- case "update_pin":
753
- result = await updatePin(action.data.pinId, action.data.updates);
754
- break;
755
- case "submit_report":
756
- result = await submitReport(action.data);
757
- break;
758
- default:
759
- result = { success: false, error: "Unknown action" };
760
- }
761
- if (result.success) {
762
- flushed++;
763
- } else {
764
- remaining.push(action);
765
- }
766
- } catch {
767
- remaining.push(action);
768
- }
769
- }
770
- localStorage.setItem(QUEUE_KEY, JSON.stringify(remaining));
771
- return flushed;
772
- }
773
- if (typeof window !== "undefined") {
774
- window.addEventListener("online", () => {
775
- flushOfflineQueue().catch(() => {
776
- });
777
- });
778
- }
779
-
780
- // src/themes.ts
781
- var THEMES = [
782
- // ─── 1. Midnight ─────────────────────────────────
783
- {
784
- id: "midnight",
785
- name: "Midnight",
786
- preview: ["#1a1d2e", "#6E9ED0"],
787
- vars: {
788
- "--bs-bg": "#1a1d2e",
789
- "--bs-bg2": "#222639",
790
- "--bs-bg3": "#2a2e42",
791
- "--bs-text": "#e2e5ed",
792
- "--bs-muted": "#7c82a0",
793
- "--bs-border": "#333754",
794
- "--bs-accent": "#6E9ED0",
795
- "--bs-accent2": "#8FAFD6",
796
- "--bs-fab1": "#6E9ED0",
797
- "--bs-fab2": "#8FAFD6",
798
- "--bs-radius": "16px",
799
- "--bs-radius-sm": "10px",
800
- "--bs-red": "#f87171",
801
- "--bs-green": "#4ade80",
802
- "--bs-orange": "#fb923c",
803
- "--bs-yellow": "#fbbf24"
804
- }
805
- },
806
- // ─── 2. Deep Purple ──────────────────────────────
807
- {
808
- id: "purple",
809
- name: "Deep Purple",
810
- preview: ["#1c1628", "#a78bfa"],
811
- vars: {
812
- "--bs-bg": "#1c1628",
813
- "--bs-bg2": "#241e34",
814
- "--bs-bg3": "#2e2640",
815
- "--bs-text": "#e8e0f5",
816
- "--bs-muted": "#8b7faa",
817
- "--bs-border": "#3b3255",
818
- "--bs-accent": "#a78bfa",
819
- "--bs-accent2": "#c4b5fd",
820
- "--bs-fab1": "#a78bfa",
821
- "--bs-fab2": "#c084fc",
822
- "--bs-radius": "16px",
823
- "--bs-radius-sm": "10px",
824
- "--bs-red": "#fb7185",
825
- "--bs-green": "#6ee7b7",
826
- "--bs-orange": "#fdba74",
827
- "--bs-yellow": "#fde68a"
828
- }
829
- },
830
- // ─── 3. Cyberpunk ────────────────────────────────
831
- {
832
- id: "cyberpunk",
833
- name: "Cyberpunk",
834
- preview: ["#0a0a12", "#00fff0"],
835
- vars: {
836
- "--bs-bg": "#0a0a12",
837
- "--bs-bg2": "#12121f",
838
- "--bs-bg3": "#1a1a2c",
839
- "--bs-text": "#e0f7fa",
840
- "--bs-muted": "#5e7f88",
841
- "--bs-border": "#1e3a3f",
842
- "--bs-accent": "#00fff0",
843
- "--bs-accent2": "#00c8ff",
844
- "--bs-fab1": "#00fff0",
845
- "--bs-fab2": "#ff00c8",
846
- "--bs-radius": "4px",
847
- "--bs-radius-sm": "2px",
848
- "--bs-red": "#ff3860",
849
- "--bs-green": "#00ff88",
850
- "--bs-orange": "#ffaa00",
851
- "--bs-yellow": "#ffe600"
852
- }
853
- },
854
- // ─── 4. Sunset ───────────────────────────────────
855
- {
856
- id: "sunset",
857
- name: "Sunset",
858
- preview: ["#1f1318", "#f97316"],
859
- vars: {
860
- "--bs-bg": "#1f1318",
861
- "--bs-bg2": "#291a20",
862
- "--bs-bg3": "#33212a",
863
- "--bs-text": "#f5e6ea",
864
- "--bs-muted": "#a07880",
865
- "--bs-border": "#4a2a34",
866
- "--bs-accent": "#f97316",
867
- "--bs-accent2": "#fb923c",
868
- "--bs-fab1": "#f97316",
869
- "--bs-fab2": "#ef4444",
870
- "--bs-radius": "14px",
871
- "--bs-radius-sm": "8px",
872
- "--bs-red": "#ef4444",
873
- "--bs-green": "#4ade80",
874
- "--bs-orange": "#fb923c",
875
- "--bs-yellow": "#fbbf24"
876
- }
877
- },
878
- // ─── 5. Ocean ────────────────────────────────────
879
- {
880
- id: "ocean",
881
- name: "Ocean",
882
- preview: ["#0d1b2a", "#2dd4bf"],
883
- vars: {
884
- "--bs-bg": "#0d1b2a",
885
- "--bs-bg2": "#132638",
886
- "--bs-bg3": "#1a3146",
887
- "--bs-text": "#d6f0ee",
888
- "--bs-muted": "#5c8a8e",
889
- "--bs-border": "#1f4050",
890
- "--bs-accent": "#2dd4bf",
891
- "--bs-accent2": "#5eead4",
892
- "--bs-fab1": "#2dd4bf",
893
- "--bs-fab2": "#22d3ee",
894
- "--bs-radius": "16px",
895
- "--bs-radius-sm": "10px",
896
- "--bs-red": "#fca5a5",
897
- "--bs-green": "#6ee7b7",
898
- "--bs-orange": "#fdba74",
899
- "--bs-yellow": "#fde68a"
900
- }
901
- },
902
- // ─── 6. Rose ─────────────────────────────────────
903
- {
904
- id: "rose",
905
- name: "Rose Gold",
906
- preview: ["#1f1520", "#f472b6"],
907
- vars: {
908
- "--bs-bg": "#1f1520",
909
- "--bs-bg2": "#2a1c2a",
910
- "--bs-bg3": "#352434",
911
- "--bs-text": "#f5e0ec",
912
- "--bs-muted": "#a07090",
913
- "--bs-border": "#4a2a44",
914
- "--bs-accent": "#f472b6",
915
- "--bs-accent2": "#f9a8d4",
916
- "--bs-fab1": "#f472b6",
917
- "--bs-fab2": "#e879f9",
918
- "--bs-radius": "20px",
919
- "--bs-radius-sm": "12px",
920
- "--bs-red": "#fb7185",
921
- "--bs-green": "#86efac",
922
- "--bs-orange": "#fdba74",
923
- "--bs-yellow": "#fde68a"
924
- }
925
- },
926
- // ─── 7. Arctic (Light) ──────────────────────────
927
- {
928
- id: "arctic",
929
- name: "Arctic Light",
930
- preview: ["#f0f4f8", "#3b82f6"],
931
- vars: {
932
- "--bs-bg": "#f0f4f8",
933
- "--bs-bg2": "#e2e8f0",
934
- "--bs-bg3": "#ffffff",
935
- "--bs-text": "#1e293b",
936
- "--bs-muted": "#64748b",
937
- "--bs-border": "#cbd5e1",
938
- "--bs-accent": "#3b82f6",
939
- "--bs-accent2": "#60a5fa",
940
- "--bs-fab1": "#3b82f6",
941
- "--bs-fab2": "#6366f1",
942
- "--bs-radius": "16px",
943
- "--bs-radius-sm": "10px",
944
- "--bs-red": "#ef4444",
945
- "--bs-green": "#22c55e",
946
- "--bs-orange": "#f97316",
947
- "--bs-yellow": "#eab308"
948
- }
949
- },
950
- // ─── 8. Glass ────────────────────────────────────
951
- {
952
- id: "glass",
953
- name: "Glassmorphism",
954
- preview: ["rgba(30,30,50,0.6)", "#a5b4fc"],
955
- vars: {
956
- "--bs-bg": "rgba(22,22,40,0.75)",
957
- "--bs-bg2": "rgba(35,35,60,0.6)",
958
- "--bs-bg3": "rgba(45,45,75,0.5)",
959
- "--bs-text": "#e8eaff",
960
- "--bs-muted": "#8888bb",
961
- "--bs-border": "rgba(255,255,255,0.1)",
962
- "--bs-accent": "#a5b4fc",
963
- "--bs-accent2": "#c7d2fe",
964
- "--bs-fab1": "#818cf8",
965
- "--bs-fab2": "#a78bfa",
966
- "--bs-radius": "20px",
967
- "--bs-radius-sm": "12px",
968
- "--bs-red": "#fca5a5",
969
- "--bs-green": "#86efac",
970
- "--bs-orange": "#fdba74",
971
- "--bs-yellow": "#fde68a"
972
- }
973
- },
974
- // ─── 9. Neon Green ──────────────────────────────
975
- {
976
- id: "neon",
977
- name: "Neon",
978
- preview: ["#080c08", "#39ff14"],
979
- vars: {
980
- "--bs-bg": "#080c08",
981
- "--bs-bg2": "#0f150f",
982
- "--bs-bg3": "#161e16",
983
- "--bs-text": "#d0f0c0",
984
- "--bs-muted": "#5a7a50",
985
- "--bs-border": "#1e3018",
986
- "--bs-accent": "#39ff14",
987
- "--bs-accent2": "#7dff5e",
988
- "--bs-fab1": "#39ff14",
989
- "--bs-fab2": "#00ff88",
990
- "--bs-radius": "8px",
991
- "--bs-radius-sm": "4px",
992
- "--bs-red": "#ff4444",
993
- "--bs-green": "#39ff14",
994
- "--bs-orange": "#ffaa00",
995
- "--bs-yellow": "#e6ff00"
996
- }
997
- },
998
- // ─── 10. Forest ──────────────────────────────────
999
- {
1000
- id: "forest",
1001
- name: "Forest",
1002
- preview: ["#111c15", "#34d399"],
1003
- vars: {
1004
- "--bs-bg": "#111c15",
1005
- "--bs-bg2": "#18261d",
1006
- "--bs-bg3": "#203026",
1007
- "--bs-text": "#d1f0dd",
1008
- "--bs-muted": "#6a9a7a",
1009
- "--bs-border": "#2a4a33",
1010
- "--bs-accent": "#34d399",
1011
- "--bs-accent2": "#6ee7b7",
1012
- "--bs-fab1": "#34d399",
1013
- "--bs-fab2": "#2dd4bf",
1014
- "--bs-radius": "14px",
1015
- "--bs-radius-sm": "8px",
1016
- "--bs-red": "#fca5a5",
1017
- "--bs-green": "#4ade80",
1018
- "--bs-orange": "#fdba74",
1019
- "--bs-yellow": "#fde68a"
1020
- }
1021
- },
1022
- // ─── 11. Minimal Dark ───────────────────────────
1023
- {
1024
- id: "minimal-dark",
1025
- name: "Minimal Dark",
1026
- preview: ["#18181b", "#a1a1aa"],
1027
- vars: {
1028
- "--bs-bg": "#18181b",
1029
- "--bs-bg2": "#212124",
1030
- "--bs-bg3": "#2a2a2e",
1031
- "--bs-text": "#e4e4e7",
1032
- "--bs-muted": "#71717a",
1033
- "--bs-border": "#333338",
1034
- "--bs-accent": "#a1a1aa",
1035
- "--bs-accent2": "#d4d4d8",
1036
- "--bs-fab1": "#52525b",
1037
- "--bs-fab2": "#71717a",
1038
- "--bs-radius": "12px",
1039
- "--bs-radius-sm": "8px",
1040
- "--bs-red": "#f87171",
1041
- "--bs-green": "#4ade80",
1042
- "--bs-orange": "#fb923c",
1043
- "--bs-yellow": "#fbbf24"
1044
- }
1045
- },
1046
- // ─── 12. Minimal Light ──────────────────────────
1047
- {
1048
- id: "minimal-light",
1049
- name: "Minimal Light",
1050
- preview: ["#fafafa", "#52525b"],
1051
- vars: {
1052
- "--bs-bg": "#fafafa",
1053
- "--bs-bg2": "#f0f0f0",
1054
- "--bs-bg3": "#ffffff",
1055
- "--bs-text": "#18181b",
1056
- "--bs-muted": "#71717a",
1057
- "--bs-border": "#e4e4e7",
1058
- "--bs-accent": "#18181b",
1059
- "--bs-accent2": "#3f3f46",
1060
- "--bs-fab1": "#18181b",
1061
- "--bs-fab2": "#3f3f46",
1062
- "--bs-radius": "12px",
1063
- "--bs-radius-sm": "8px",
1064
- "--bs-red": "#dc2626",
1065
- "--bs-green": "#16a34a",
1066
- "--bs-orange": "#ea580c",
1067
- "--bs-yellow": "#ca8a04"
1068
- }
1069
- },
1070
- // ─── 13. Dracula ─────────────────────────────────
1071
- {
1072
- id: "dracula",
1073
- name: "Dracula",
1074
- preview: ["#282a36", "#bd93f9"],
1075
- vars: {
1076
- "--bs-bg": "#282a36",
1077
- "--bs-bg2": "#2e303e",
1078
- "--bs-bg3": "#363848",
1079
- "--bs-text": "#f8f8f2",
1080
- "--bs-muted": "#6272a4",
1081
- "--bs-border": "#44475a",
1082
- "--bs-accent": "#bd93f9",
1083
- "--bs-accent2": "#caa9fa",
1084
- "--bs-fab1": "#bd93f9",
1085
- "--bs-fab2": "#ff79c6",
1086
- "--bs-radius": "12px",
1087
- "--bs-radius-sm": "8px",
1088
- "--bs-red": "#ff5555",
1089
- "--bs-green": "#50fa7b",
1090
- "--bs-orange": "#ffb86c",
1091
- "--bs-yellow": "#f1fa8c"
1092
- }
1093
- },
1094
- // ─── 14. Nord ────────────────────────────────────
1095
- {
1096
- id: "nord",
1097
- name: "Nord",
1098
- preview: ["#2e3440", "#88c0d0"],
1099
- vars: {
1100
- "--bs-bg": "#2e3440",
1101
- "--bs-bg2": "#3b4252",
1102
- "--bs-bg3": "#434c5e",
1103
- "--bs-text": "#eceff4",
1104
- "--bs-muted": "#7b88a1",
1105
- "--bs-border": "#4c566a",
1106
- "--bs-accent": "#88c0d0",
1107
- "--bs-accent2": "#8fbcbb",
1108
- "--bs-fab1": "#88c0d0",
1109
- "--bs-fab2": "#81a1c1",
1110
- "--bs-radius": "10px",
1111
- "--bs-radius-sm": "6px",
1112
- "--bs-red": "#bf616a",
1113
- "--bs-green": "#a3be8c",
1114
- "--bs-orange": "#d08770",
1115
- "--bs-yellow": "#ebcb8b"
1116
- }
1117
- },
1118
- // ─── 15. Monokai ─────────────────────────────────
1119
- {
1120
- id: "monokai",
1121
- name: "Monokai",
1122
- preview: ["#272822", "#a6e22e"],
1123
- vars: {
1124
- "--bs-bg": "#272822",
1125
- "--bs-bg2": "#2f302a",
1126
- "--bs-bg3": "#383930",
1127
- "--bs-text": "#f8f8f2",
1128
- "--bs-muted": "#75715e",
1129
- "--bs-border": "#49483e",
1130
- "--bs-accent": "#a6e22e",
1131
- "--bs-accent2": "#c4f060",
1132
- "--bs-fab1": "#a6e22e",
1133
- "--bs-fab2": "#66d9ef",
1134
- "--bs-radius": "8px",
1135
- "--bs-radius-sm": "4px",
1136
- "--bs-red": "#f92672",
1137
- "--bs-green": "#a6e22e",
1138
- "--bs-orange": "#fd971f",
1139
- "--bs-yellow": "#e6db74"
1140
- }
1141
- },
1142
- // ─── 16. Solarized ──────────────────────────────
1143
- {
1144
- id: "solarized",
1145
- name: "Solarized Dark",
1146
- preview: ["#002b36", "#268bd2"],
1147
- vars: {
1148
- "--bs-bg": "#002b36",
1149
- "--bs-bg2": "#073642",
1150
- "--bs-bg3": "#0e4050",
1151
- "--bs-text": "#eee8d5",
1152
- "--bs-muted": "#839496",
1153
- "--bs-border": "#1a4f5c",
1154
- "--bs-accent": "#268bd2",
1155
- "--bs-accent2": "#2aa198",
1156
- "--bs-fab1": "#268bd2",
1157
- "--bs-fab2": "#2aa198",
1158
- "--bs-radius": "10px",
1159
- "--bs-radius-sm": "6px",
1160
- "--bs-red": "#dc322f",
1161
- "--bs-green": "#859900",
1162
- "--bs-orange": "#cb4b16",
1163
- "--bs-yellow": "#b58900"
1164
- }
1165
- },
1166
- // ─── 17. Candy ───────────────────────────────────
1167
- {
1168
- id: "candy",
1169
- name: "Candy",
1170
- preview: ["#fef1f8", "#ec4899"],
1171
- vars: {
1172
- "--bs-bg": "#fef1f8",
1173
- "--bs-bg2": "#fce7f3",
1174
- "--bs-bg3": "#ffffff",
1175
- "--bs-text": "#4a1942",
1176
- "--bs-muted": "#9f5090",
1177
- "--bs-border": "#f0c6e0",
1178
- "--bs-accent": "#ec4899",
1179
- "--bs-accent2": "#f472b6",
1180
- "--bs-fab1": "#ec4899",
1181
- "--bs-fab2": "#a855f7",
1182
- "--bs-radius": "20px",
1183
- "--bs-radius-sm": "14px",
1184
- "--bs-red": "#e11d48",
1185
- "--bs-green": "#059669",
1186
- "--bs-orange": "#ea580c",
1187
- "--bs-yellow": "#ca8a04"
1188
- }
1189
- },
1190
- // ─── 18. Slate ───────────────────────────────────
1191
- {
1192
- id: "slate",
1193
- name: "Slate",
1194
- preview: ["#1e2432", "#94a3b8"],
1195
- vars: {
1196
- "--bs-bg": "#1e2432",
1197
- "--bs-bg2": "#263040",
1198
- "--bs-bg3": "#2e384a",
1199
- "--bs-text": "#e2e8f0",
1200
- "--bs-muted": "#64748b",
1201
- "--bs-border": "#374462",
1202
- "--bs-accent": "#94a3b8",
1203
- "--bs-accent2": "#cbd5e1",
1204
- "--bs-fab1": "#475569",
1205
- "--bs-fab2": "#64748b",
1206
- "--bs-radius": "14px",
1207
- "--bs-radius-sm": "8px",
1208
- "--bs-red": "#f87171",
1209
- "--bs-green": "#4ade80",
1210
- "--bs-orange": "#fb923c",
1211
- "--bs-yellow": "#fbbf24"
1212
- }
1213
- },
1214
- // ─── 19. Ember ───────────────────────────────────
1215
- {
1216
- id: "ember",
1217
- name: "Ember",
1218
- preview: ["#1a0f0f", "#ef4444"],
1219
- vars: {
1220
- "--bs-bg": "#1a0f0f",
1221
- "--bs-bg2": "#241515",
1222
- "--bs-bg3": "#2e1c1c",
1223
- "--bs-text": "#fde8e8",
1224
- "--bs-muted": "#a06060",
1225
- "--bs-border": "#4a2222",
1226
- "--bs-accent": "#ef4444",
1227
- "--bs-accent2": "#f87171",
1228
- "--bs-fab1": "#ef4444",
1229
- "--bs-fab2": "#f97316",
1230
- "--bs-radius": "12px",
1231
- "--bs-radius-sm": "8px",
1232
- "--bs-red": "#fca5a5",
1233
- "--bs-green": "#86efac",
1234
- "--bs-orange": "#fdba74",
1235
- "--bs-yellow": "#fde68a"
1236
- }
1237
- },
1238
- // ─── 20. Lavender ───────────────────────────────
1239
- {
1240
- id: "lavender",
1241
- name: "Lavender",
1242
- preview: ["#f5f0ff", "#7c3aed"],
1243
- vars: {
1244
- "--bs-bg": "#f5f0ff",
1245
- "--bs-bg2": "#ede5ff",
1246
- "--bs-bg3": "#ffffff",
1247
- "--bs-text": "#2e1065",
1248
- "--bs-muted": "#7c6a9a",
1249
- "--bs-border": "#d8c8f0",
1250
- "--bs-accent": "#7c3aed",
1251
- "--bs-accent2": "#8b5cf6",
1252
- "--bs-fab1": "#7c3aed",
1253
- "--bs-fab2": "#a855f7",
1254
- "--bs-radius": "18px",
1255
- "--bs-radius-sm": "12px",
1256
- "--bs-red": "#dc2626",
1257
- "--bs-green": "#16a34a",
1258
- "--bs-orange": "#ea580c",
1259
- "--bs-yellow": "#ca8a04"
1260
- }
1261
- }
1262
- ];
1263
- function getThemes() {
1264
- return THEMES;
1265
- }
1266
- function getThemeById(id) {
1267
- return THEMES.find((t) => t.id === id);
1268
- }
1269
- function getDefaultTheme() {
1270
- return THEMES[0];
1271
- }
1272
-
1273
- // src/layouts.ts
1274
- var LAYOUTS = [
1275
- // 1
1276
- { id: "classic", name: "Classic Center", description: "Centered modal, top tabs", tabPosition: "top" },
1277
- // 2
1278
- { id: "drawer-right", name: "Right Drawer", description: "Slides in from the right", tabPosition: "top" },
1279
- // 3
1280
- { id: "drawer-left", name: "Left Drawer", description: "Slides in from the left", tabPosition: "top" },
1281
- // 4
1282
- { id: "bottom-sheet", name: "Bottom Sheet", description: "Slides up from the bottom", tabPosition: "top" },
1283
- // 5
1284
- { id: "top-bar", name: "Top Drop", description: "Drops down from the top", tabPosition: "top" },
1285
- // 6
1286
- { id: "compact", name: "Compact", description: "Small centered popup", tabPosition: "top" },
1287
- // 7
1288
- { id: "wide", name: "Wide", description: "Wide horizontal modal", tabPosition: "top" },
1289
- // 8
1290
- { id: "fullscreen", name: "Fullscreen", description: "Full screen overlay", tabPosition: "top" },
1291
- // 9
1292
- { id: "corner-br", name: "Corner Card", description: "Bottom-right corner card", tabPosition: "top" },
1293
- // 10
1294
- { id: "corner-bl", name: "Corner Left", description: "Bottom-left corner card", tabPosition: "top" },
1295
- // 11
1296
- { id: "pill-tabs", name: "Pill Tabs", description: "Centered with pill-style tabs", tabPosition: "top" },
1297
- // 12
1298
- { id: "sidebar-tabs", name: "Sidebar Tabs", description: "Vertical tabs on the left", tabPosition: "left" },
1299
- // 13
1300
- { id: "segmented", name: "Segmented", description: "Tabs as segmented control", tabPosition: "top" },
1301
- // 14
1302
- { id: "minimal", name: "Minimal", description: "Ultra clean, spacious", tabPosition: "top" },
1303
- // 15
1304
- { id: "dense", name: "Dense", description: "Compact, tight spacing", tabPosition: "top" },
1305
- // 16
1306
- { id: "rounded", name: "Bubble", description: "Extra rounded, playful", tabPosition: "top" },
1307
- // 17
1308
- { id: "sharp", name: "Sharp", description: "Square corners, industrial", tabPosition: "top" },
1309
- // 18
1310
- { id: "split", name: "Split View", description: "Two-column layout", tabPosition: "left" },
1311
- // 19
1312
- { id: "floating", name: "Floating", description: "Heavy shadow, borderless", tabPosition: "top" },
1313
- // 20
1314
- { id: "bottom-tabs", name: "Bottom Tabs", description: "Tabs at the bottom", tabPosition: "bottom" }
1315
- ];
1316
- function getLayouts() {
1317
- return LAYOUTS;
1318
- }
1319
- function getLayoutById(id) {
1320
- return LAYOUTS.find((l) => l.id === id);
1321
- }
1322
- function getDefaultLayout() {
1323
- return LAYOUTS[0];
1324
- }
1325
- var LAYOUT_CSS = `
1
+ "use strict";var At=Object.defineProperty;var mn=Object.getOwnPropertyDescriptor;var hn=Object.getOwnPropertyNames;var xn=Object.prototype.hasOwnProperty;var vn=(e,t)=>{for(var s in t)At(e,s,{get:t[s],enumerable:!0})},yn=(e,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of hn(t))!xn.call(e,r)&&r!==s&&At(e,r,{get:()=>t[r],enumerable:!(n=mn(t,r))||n.enumerable});return e};var wn=e=>yn(At({},"__esModule",{value:!0}),e);var lr={};vn(lr,{BugStash:()=>dn,default:()=>ir});module.exports=wn(lr);var ze=[],ns=50,Ae=null,Ie=null,He=null,Re=null;function Ze(e){if(e.id)return`#${e.id}`;let t=e.tagName.toLowerCase(),s=e.className&&typeof e.className=="string"?"."+e.className.trim().split(/\s+/).slice(0,2).join("."):"",n=(e.textContent||"").trim().slice(0,30),r=n?` "${n}"`:"";return`${t}${s}${r}`}function q(e){ze.push(e),ze.length>ns&&ze.shift()}function rs(e){e&&(ns=e),Ae=s=>{let n=s.target;!n||!n.tagName||q({type:"click",category:"ui",message:`Clicked ${Ze(n)}`,timestamp:Date.now(),data:{x:s.clientX,y:s.clientY,selector:Ze(n)}})},document.addEventListener("click",Ae,!0);let t=new WeakMap;Ie=s=>{let n=s.target;if(!n||!n.tagName)return;let r=n.tagName.toLowerCase();if(r!=="input"&&r!=="textarea"&&r!=="select")return;let i=t.get(n);i&&clearTimeout(i),t.set(n,window.setTimeout(()=>{let c=n instanceof HTMLInputElement&&n.type==="password";q({type:"input",category:"ui",message:`Input ${Ze(n)}`,timestamp:Date.now(),data:{selector:Ze(n),value:c?"[redacted]":void 0}})},300))},document.addEventListener("input",Ie,!0),He=()=>{q({type:"navigation",category:"navigation",message:`Navigated to ${window.location.pathname}`,timestamp:Date.now(),data:{url:window.location.href}})},window.addEventListener("popstate",He),Re=()=>{q({type:"navigation",category:"navigation",message:`Hash changed to ${window.location.hash}`,timestamp:Date.now(),data:{url:window.location.href}})},window.addEventListener("hashchange",Re),q({type:"navigation",category:"navigation",message:`Page loaded: ${window.location.pathname}`,timestamp:Date.now(),data:{url:window.location.href}})}function me(){return[...ze]}function os(){ze=[]}function as(){Ae&&document.removeEventListener("click",Ae,!0),Ie&&document.removeEventListener("input",Ie,!0),He&&window.removeEventListener("popstate",He),Re&&window.removeEventListener("hashchange",Re),Ae=null,Ie=null,He=null,Re=null}var It="[REDACTED]",kn=[[/\b(?:\d[ -]*?){13,19}\b/g,"[CC_REDACTED]"],[/\b\d{3}-\d{2}-\d{4}\b/g,"[SSN_REDACTED]"],[/Bearer\s+[A-Za-z0-9\-._~+/]+=*/g,"Bearer [TOKEN_REDACTED]"],[/eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,"[JWT_REDACTED]"],[/(?:api[_-]?key|apikey|secret|token|password|passwd|authorization)['":\s=]+['"]?([A-Za-z0-9\-._~+/]{20,})['"]?/gi,e=>{let t=e.search(/[=:]/);return e.slice(0,t+1)+" "+It}],[/AKIA[0-9A-Z]{16}/g,"[AWS_KEY_REDACTED]"],[/"(?:password|passwd|secret|token|access_token|refresh_token|api_key|apiKey|private_key)":\s*"[^"]*"/gi,e=>{let t=e.indexOf(":");return e.slice(0,t+1)+' "'+It+'"'}]];function he(e){let t=e;for(let[s,n]of kn)t=t.replace(s,n);return t}function is(e){return e.map(he)}function Qe(e){if(typeof e=="string")return he(e);if(Array.isArray(e))return e.map(Qe);if(e&&typeof e=="object"){let t={};for(let[s,n]of Object.entries(e)){let r=s.toLowerCase();r.includes("password")||r.includes("secret")||r.includes("token")||r.includes("apikey")||r.includes("api_key")||r.includes("private")?t[s]=It:t[s]=Qe(n)}return t}return e}var Ge={log:console.log,warn:console.warn,error:console.error,debug:console.debug,info:console.info},Ne=[],ls=50;function En(e){return e.map(t=>{if(t instanceof Error)return`${t.name}: ${t.message}
2
+ ${t.stack??""}`;if(typeof t=="object")try{return JSON.stringify(t,null,2)}catch{return String(t)}return String(t)})}function Ln(e,t){let s={level:e,args:is(En(t)),timestamp:Date.now()};e==="error"&&(s.stack=new Error().stack?.split(`
3
+ `).slice(2).join(`
4
+ `)),Ne.push(s),Ne.length>ls&&Ne.shift(),q({type:"console",category:`console.${e}`,message:s.args.join(" ").slice(0,200),timestamp:s.timestamp})}function cs(e){e&&(ls=e);for(let t of Object.keys(Ge))console[t]=function(...s){Ln(t,s),Ge[t].apply(console,s)}}function ee(){return[...Ne]}function ds(){Ne=[]}function bs(){for(let e of Object.keys(Ge))console[e]=Ge[e]}var xe=[],ps=50,et,tt,st;function Ht(e){xe.push(e),xe.length>ps&&xe.shift(),q({type:"network",category:e.failed?"network.error":"network.ok",message:`${e.method} ${e.url} \u2192 ${e.status} (${e.duration}ms)`,timestamp:e.timestamp,data:{status:e.status,duration:e.duration}})}function us(e){try{let t=new URL(e,window.location.origin),s=new URLSearchParams(t.search);for(let r of s.keys()){let i=r.toLowerCase();(i.includes("token")||i.includes("key")||i.includes("secret")||i.includes("password")||i.includes("auth"))&&s.set(r,"[REDACTED]")}let n=s.toString();return t.pathname+(n?"?"+n:"")}catch{return he(e)}}function Sn(){et=window.fetch,window.fetch=async function(e,t){let s=t?.method?.toUpperCase()??"GET",n=typeof e=="string"?e:e instanceof URL?e.href:e.url,r=us(n),i=Date.now();try{let c=await et.call(window,e,t);return Ht({method:s,url:r,status:c.status,statusText:c.statusText,duration:Date.now()-i,responseType:c.headers.get("content-type")??void 0,timestamp:i,failed:c.status>=400}),c}catch(c){throw Ht({method:s,url:r,status:0,statusText:"Network Error",duration:Date.now()-i,timestamp:i,failed:!0}),c}}}function Cn(){tt=XMLHttpRequest.prototype.open,st=XMLHttpRequest.prototype.send,XMLHttpRequest.prototype.open=function(e,t,...s){return this.__bs_method=e.toUpperCase(),this.__bs_url=us(typeof t=="string"?t:t.href),tt.apply(this,[e,t,...s])},XMLHttpRequest.prototype.send=function(e){let t=Date.now();return this.addEventListener("loadend",function(){Ht({method:this.__bs_method??"GET",url:this.__bs_url??"",status:this.status,statusText:this.statusText,duration:Date.now()-t,responseType:this.getResponseHeader("content-type")??void 0,timestamp:t,failed:this.status>=400||this.status===0})}),st.call(this,e)}}function fs(e){e&&(ps=e),Sn(),Cn()}function ve(){return[...xe]}function le(){return xe.filter(e=>e.failed)}function gs(){xe=[]}function ms(){et&&(window.fetch=et),tt&&(XMLHttpRequest.prototype.open=tt),st&&(XMLHttpRequest.prototype.send=st)}var nt=[],Be=null,De=null;function hs(){Be=e=>{let t={message:e.message||"Unknown error",source:e.filename,lineno:e.lineno,colno:e.colno,stack:e.error?.stack,type:"error",timestamp:Date.now()};nt.push(t),q({type:"error",category:"exception",message:t.message,timestamp:t.timestamp,data:{source:t.source,lineno:t.lineno}})},De=e=>{let t=e.reason,s=t instanceof Error?t.message:typeof t=="string"?t:"Unhandled promise rejection",n={message:s,stack:t instanceof Error?t.stack:void 0,type:"unhandledrejection",timestamp:Date.now()};nt.push(n),q({type:"error",category:"promise",message:s,timestamp:n.timestamp})},window.addEventListener("error",Be),window.addEventListener("unhandledrejection",De)}function te(){return[...nt]}function xs(){nt=[]}function vs(){Be&&window.removeEventListener("error",Be),De&&window.removeEventListener("unhandledrejection",De),Be=null,De=null}var M=null,rt=null,ot=null,at=null;function ys(){if(M={timestamp:Date.now()},performance.getEntriesByType){let e=()=>{let[t]=performance.getEntriesByType("navigation");t&&M&&(M.pageLoadTime=Math.round(t.loadEventEnd-t.startTime),M.domContentLoaded=Math.round(t.domContentLoadedEventEnd-t.startTime));let s=performance.getEntriesByType("paint");for(let r of s)r.name==="first-paint"&&M&&(M.firstPaint=Math.round(r.startTime)),r.name==="first-contentful-paint"&&M&&(M.firstContentfulPaint=Math.round(r.startTime));M&&(M.resourceCount=performance.getEntriesByType("resource").length);let n=performance.memory;n&&M&&(M.memoryUsage={usedJSHeapSize:n.usedJSHeapSize,totalJSHeapSize:n.totalJSHeapSize})};document.readyState==="complete"?setTimeout(e,0):window.addEventListener("load",()=>setTimeout(e,100))}if(typeof PerformanceObserver<"u"){try{rt=new PerformanceObserver(e=>{let t=e.getEntries(),s=t[t.length-1];s&&M&&(M.largestContentfulPaint=Math.round(s.startTime))}),rt.observe({type:"largest-contentful-paint",buffered:!0})}catch{}try{let e=0;ot=new PerformanceObserver(t=>{for(let s of t.getEntries())s.hadRecentInput||(e+=s.value);M&&(M.cumulativeLayoutShift=Math.round(e*1e3)/1e3)}),ot.observe({type:"layout-shift",buffered:!0})}catch{}try{at=new PerformanceObserver(e=>{let[t]=e.getEntries();t&&M&&(M.firstInputDelay=Math.round(t.processingStart-t.startTime))}),at.observe({type:"first-input",buffered:!0})}catch{}}}function ye(){return M&&(M.timestamp=Date.now()),M?{...M}:null}function ws(){rt?.disconnect(),ot?.disconnect(),at?.disconnect(),rt=null,ot=null,at=null,M=null}async function it(){try{let e=await Tn();if(e)return e}catch{}try{let e=await Mn();if(e)return e}catch{}return null}async function Tn(){let e=window.innerWidth,t=window.innerHeight,s=document.documentElement.cloneNode(!0),n=document.documentElement.querySelectorAll("*"),r=s.querySelectorAll("*");for(let g=0;g<n.length&&g<r.length;g++){let x=window.getComputedStyle(n[g]),y=r[g];if(y.style){let $=["background","background-color","background-image","color","font","font-size","font-family","font-weight","border","border-radius","padding","margin","display","flex-direction","align-items","justify-content","gap","width","height","max-width","max-height","overflow","position","top","left","right","bottom","box-shadow","text-shadow","opacity","transform","text-align","line-height","letter-spacing","text-decoration","visibility","z-index","grid-template-columns","grid-template-rows","box-sizing"];for(let ae of $)try{y.style.setProperty(ae,x.getPropertyValue(ae))}catch{}}}s.querySelectorAll("script, [data-bugstash], .bs-fab, .bs-modal, .bs-backdrop").forEach(g=>g.remove());let c=new XMLSerializer().serializeToString(s),p=`
5
+ <svg xmlns="http://www.w3.org/2000/svg" width="${e}" height="${t}">
6
+ <foreignObject width="100%" height="100%">
7
+ ${c}
8
+ </foreignObject>
9
+ </svg>
10
+ `,l=new Blob([p],{type:"image/svg+xml;charset=utf-8"}),h=URL.createObjectURL(l);return new Promise(g=>{let x=new Image;x.onload=()=>{let y=document.createElement("canvas");y.width=e*.5,y.height=t*.5;let $=y.getContext("2d");if(!$){URL.revokeObjectURL(h),g(null);return}$.scale(.5,.5),$.drawImage(x,0,0),URL.revokeObjectURL(h),g(y.toDataURL("image/jpeg",.6))},x.onerror=()=>{URL.revokeObjectURL(h),g(null)},x.src=h})}async function Mn(){if(!navigator.mediaDevices?.getDisplayMedia)return null;let e=await navigator.mediaDevices.getDisplayMedia({video:{displaySurface:"browser"}}),t=e.getVideoTracks()[0],s=document.createElement("video");return new Promise(n=>{s.onloadedmetadata=()=>{s.play();let r=document.createElement("canvas");r.width=s.videoWidth*.5,r.height=s.videoHeight*.5;let i=r.getContext("2d");if(!i){t.stop(),n(null);return}i.scale(.5,.5),i.drawImage(s,0,0),t.stop(),n(r.toDataURL("image/jpeg",.6))},s.onerror=()=>{t.stop(),n(null)},s.srcObject=e})}var $n="https://api.bugstash.com",ks="https://bugstash-backend.azurewebsites.net",U=$n,Rt=!1;function Es(e){U=e.replace(/\/$/,""),Rt=!1}function Ls(){return U}async function X(e,t){try{return await fetch(e,t)}catch(s){if(Rt)throw s;Rt=!0;let n=U;U=ks;let r=e.replace(n,ks);return fetch(r,t)}}var Nt="bugstash_auth";function Bt(){try{let e=localStorage.getItem(Nt);if(!e)return null;let t=JSON.parse(e);return t.tokens.expiresAt<Date.now(),t}catch{return null}}function Ss(e){localStorage.setItem(Nt,JSON.stringify(e))}function Pn(){localStorage.removeItem(Nt)}function V(){return Bt()?.user||null}function Cs(){return Bt()?.tokens.accessToken||null}async function ce(){let e=Bt();if(!e)return{"Content-Type":"application/json"};if(e.tokens.expiresAt<Date.now()+6e4)try{let t=await X(`${U}/api/auth/refresh`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:e.tokens.refreshToken})});if(t.ok){let s=await t.json();s.success&&(e.tokens=s.data,Ss(e))}}catch{}return{"Content-Type":"application/json",Authorization:`Bearer ${e.tokens.accessToken}`}}async function lt(e,t,s){try{let r=await(await X(`${U}/api/auth/login`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:e,password:t,projectId:s})})).json();return r.success&&Ss({user:r.data.user,tokens:r.data.tokens}),r}catch{return{success:!1,error:"Network error"}}}async function ct(){Pn()}async function Dt(e){try{let t=await ce();return await(await X(`${U}/api/reports`,{method:"POST",headers:t,body:JSON.stringify(e)})).json()}catch{return{success:!1,error:"Network error - could not reach BugStash"}}}async function Ts(e,t){try{let s=await ce();return await(await X(`${U}/api/pins/by-page?projectId=${e}&pathname=${encodeURIComponent(t)}`,{headers:s})).json()}catch{return{success:!1,error:"Network error"}}}async function Ot(e){try{let t=await ce();return await(await X(`${U}/api/pins`,{method:"POST",headers:t,body:JSON.stringify(e)})).json()}catch{return{success:!1,error:"Network error"}}}async function dt(e,t){try{let s=await ce();return await(await X(`${U}/api/pins/${e}`,{method:"PUT",headers:s,body:JSON.stringify(t)})).json()}catch{return{success:!1,error:"Network error"}}}async function Ms(e){try{let t=await ce();return await(await X(`${U}/api/pins/${e}`,{method:"DELETE",headers:t})).json()}catch{return{success:!1,error:"Network error"}}}async function $s(e){try{let t=await ce();return await(await X(`${U}/api/pins/${e}/comments`,{headers:t})).json()}catch{return{success:!1,error:"Network error"}}}async function jt(e,t,s=[]){try{let n=await ce();return await(await X(`${U}/api/pins/${e}/comments`,{method:"POST",headers:n,body:JSON.stringify({body:t,mentions:s})})).json()}catch{return{success:!1,error:"Network error"}}}async function Ps(e){try{let t=await ce();return await(await X(`${U}/api/members/for-project/${e}`,{headers:t})).json()}catch{return{success:!1,error:"Network error"}}}var qt="bugstash_offline_queue";function Ut(e){let t=zs();t.push({...e,id:Math.random().toString(36).slice(2),timestamp:Date.now()}),localStorage.setItem(qt,JSON.stringify(t))}function zs(){try{return JSON.parse(localStorage.getItem(qt)||"[]")}catch{return[]}}async function zn(){let e=zs();if(e.length===0)return 0;let t=0,s=[];for(let n of e)try{let r;switch(n.type){case"create_pin":r=await Ot(n.data);break;case"create_comment":r=await jt(n.data.pinId,n.data.body,n.data.mentions);break;case"update_pin":r=await dt(n.data.pinId,n.data.updates);break;case"submit_report":r=await Dt(n.data);break;default:r={success:!1,error:"Unknown action"}}r.success?t++:s.push(n)}catch{s.push(n)}return localStorage.setItem(qt,JSON.stringify(s)),t}typeof window<"u"&&window.addEventListener("online",()=>{zn().catch(()=>{})});var Wt=[{id:"midnight",name:"Midnight",preview:["#1a1d2e","#6E9ED0"],vars:{"--bs-bg":"#1a1d2e","--bs-bg2":"#222639","--bs-bg3":"#2a2e42","--bs-text":"#e2e5ed","--bs-muted":"#7c82a0","--bs-border":"#333754","--bs-accent":"#6E9ED0","--bs-accent2":"#8FAFD6","--bs-fab1":"#6E9ED0","--bs-fab2":"#8FAFD6","--bs-radius":"16px","--bs-radius-sm":"10px","--bs-red":"#f87171","--bs-green":"#4ade80","--bs-orange":"#fb923c","--bs-yellow":"#fbbf24"}},{id:"purple",name:"Deep Purple",preview:["#1c1628","#a78bfa"],vars:{"--bs-bg":"#1c1628","--bs-bg2":"#241e34","--bs-bg3":"#2e2640","--bs-text":"#e8e0f5","--bs-muted":"#8b7faa","--bs-border":"#3b3255","--bs-accent":"#a78bfa","--bs-accent2":"#c4b5fd","--bs-fab1":"#a78bfa","--bs-fab2":"#c084fc","--bs-radius":"16px","--bs-radius-sm":"10px","--bs-red":"#fb7185","--bs-green":"#6ee7b7","--bs-orange":"#fdba74","--bs-yellow":"#fde68a"}},{id:"cyberpunk",name:"Cyberpunk",preview:["#0a0a12","#00fff0"],vars:{"--bs-bg":"#0a0a12","--bs-bg2":"#12121f","--bs-bg3":"#1a1a2c","--bs-text":"#e0f7fa","--bs-muted":"#5e7f88","--bs-border":"#1e3a3f","--bs-accent":"#00fff0","--bs-accent2":"#00c8ff","--bs-fab1":"#00fff0","--bs-fab2":"#ff00c8","--bs-radius":"4px","--bs-radius-sm":"2px","--bs-red":"#ff3860","--bs-green":"#00ff88","--bs-orange":"#ffaa00","--bs-yellow":"#ffe600"}},{id:"sunset",name:"Sunset",preview:["#1f1318","#f97316"],vars:{"--bs-bg":"#1f1318","--bs-bg2":"#291a20","--bs-bg3":"#33212a","--bs-text":"#f5e6ea","--bs-muted":"#a07880","--bs-border":"#4a2a34","--bs-accent":"#f97316","--bs-accent2":"#fb923c","--bs-fab1":"#f97316","--bs-fab2":"#ef4444","--bs-radius":"14px","--bs-radius-sm":"8px","--bs-red":"#ef4444","--bs-green":"#4ade80","--bs-orange":"#fb923c","--bs-yellow":"#fbbf24"}},{id:"ocean",name:"Ocean",preview:["#0d1b2a","#2dd4bf"],vars:{"--bs-bg":"#0d1b2a","--bs-bg2":"#132638","--bs-bg3":"#1a3146","--bs-text":"#d6f0ee","--bs-muted":"#5c8a8e","--bs-border":"#1f4050","--bs-accent":"#2dd4bf","--bs-accent2":"#5eead4","--bs-fab1":"#2dd4bf","--bs-fab2":"#22d3ee","--bs-radius":"16px","--bs-radius-sm":"10px","--bs-red":"#fca5a5","--bs-green":"#6ee7b7","--bs-orange":"#fdba74","--bs-yellow":"#fde68a"}},{id:"rose",name:"Rose Gold",preview:["#1f1520","#f472b6"],vars:{"--bs-bg":"#1f1520","--bs-bg2":"#2a1c2a","--bs-bg3":"#352434","--bs-text":"#f5e0ec","--bs-muted":"#a07090","--bs-border":"#4a2a44","--bs-accent":"#f472b6","--bs-accent2":"#f9a8d4","--bs-fab1":"#f472b6","--bs-fab2":"#e879f9","--bs-radius":"20px","--bs-radius-sm":"12px","--bs-red":"#fb7185","--bs-green":"#86efac","--bs-orange":"#fdba74","--bs-yellow":"#fde68a"}},{id:"arctic",name:"Arctic Light",preview:["#f0f4f8","#3b82f6"],vars:{"--bs-bg":"#f0f4f8","--bs-bg2":"#e2e8f0","--bs-bg3":"#ffffff","--bs-text":"#1e293b","--bs-muted":"#64748b","--bs-border":"#cbd5e1","--bs-accent":"#3b82f6","--bs-accent2":"#60a5fa","--bs-fab1":"#3b82f6","--bs-fab2":"#6366f1","--bs-radius":"16px","--bs-radius-sm":"10px","--bs-red":"#ef4444","--bs-green":"#22c55e","--bs-orange":"#f97316","--bs-yellow":"#eab308"}},{id:"glass",name:"Glassmorphism",preview:["rgba(30,30,50,0.6)","#a5b4fc"],vars:{"--bs-bg":"rgba(22,22,40,0.75)","--bs-bg2":"rgba(35,35,60,0.6)","--bs-bg3":"rgba(45,45,75,0.5)","--bs-text":"#e8eaff","--bs-muted":"#8888bb","--bs-border":"rgba(255,255,255,0.1)","--bs-accent":"#a5b4fc","--bs-accent2":"#c7d2fe","--bs-fab1":"#818cf8","--bs-fab2":"#a78bfa","--bs-radius":"20px","--bs-radius-sm":"12px","--bs-red":"#fca5a5","--bs-green":"#86efac","--bs-orange":"#fdba74","--bs-yellow":"#fde68a"}},{id:"neon",name:"Neon",preview:["#080c08","#39ff14"],vars:{"--bs-bg":"#080c08","--bs-bg2":"#0f150f","--bs-bg3":"#161e16","--bs-text":"#d0f0c0","--bs-muted":"#5a7a50","--bs-border":"#1e3018","--bs-accent":"#39ff14","--bs-accent2":"#7dff5e","--bs-fab1":"#39ff14","--bs-fab2":"#00ff88","--bs-radius":"8px","--bs-radius-sm":"4px","--bs-red":"#ff4444","--bs-green":"#39ff14","--bs-orange":"#ffaa00","--bs-yellow":"#e6ff00"}},{id:"forest",name:"Forest",preview:["#111c15","#34d399"],vars:{"--bs-bg":"#111c15","--bs-bg2":"#18261d","--bs-bg3":"#203026","--bs-text":"#d1f0dd","--bs-muted":"#6a9a7a","--bs-border":"#2a4a33","--bs-accent":"#34d399","--bs-accent2":"#6ee7b7","--bs-fab1":"#34d399","--bs-fab2":"#2dd4bf","--bs-radius":"14px","--bs-radius-sm":"8px","--bs-red":"#fca5a5","--bs-green":"#4ade80","--bs-orange":"#fdba74","--bs-yellow":"#fde68a"}},{id:"minimal-dark",name:"Minimal Dark",preview:["#18181b","#a1a1aa"],vars:{"--bs-bg":"#18181b","--bs-bg2":"#212124","--bs-bg3":"#2a2a2e","--bs-text":"#e4e4e7","--bs-muted":"#71717a","--bs-border":"#333338","--bs-accent":"#a1a1aa","--bs-accent2":"#d4d4d8","--bs-fab1":"#52525b","--bs-fab2":"#71717a","--bs-radius":"12px","--bs-radius-sm":"8px","--bs-red":"#f87171","--bs-green":"#4ade80","--bs-orange":"#fb923c","--bs-yellow":"#fbbf24"}},{id:"minimal-light",name:"Minimal Light",preview:["#fafafa","#52525b"],vars:{"--bs-bg":"#fafafa","--bs-bg2":"#f0f0f0","--bs-bg3":"#ffffff","--bs-text":"#18181b","--bs-muted":"#71717a","--bs-border":"#e4e4e7","--bs-accent":"#18181b","--bs-accent2":"#3f3f46","--bs-fab1":"#18181b","--bs-fab2":"#3f3f46","--bs-radius":"12px","--bs-radius-sm":"8px","--bs-red":"#dc2626","--bs-green":"#16a34a","--bs-orange":"#ea580c","--bs-yellow":"#ca8a04"}},{id:"dracula",name:"Dracula",preview:["#282a36","#bd93f9"],vars:{"--bs-bg":"#282a36","--bs-bg2":"#2e303e","--bs-bg3":"#363848","--bs-text":"#f8f8f2","--bs-muted":"#6272a4","--bs-border":"#44475a","--bs-accent":"#bd93f9","--bs-accent2":"#caa9fa","--bs-fab1":"#bd93f9","--bs-fab2":"#ff79c6","--bs-radius":"12px","--bs-radius-sm":"8px","--bs-red":"#ff5555","--bs-green":"#50fa7b","--bs-orange":"#ffb86c","--bs-yellow":"#f1fa8c"}},{id:"nord",name:"Nord",preview:["#2e3440","#88c0d0"],vars:{"--bs-bg":"#2e3440","--bs-bg2":"#3b4252","--bs-bg3":"#434c5e","--bs-text":"#eceff4","--bs-muted":"#7b88a1","--bs-border":"#4c566a","--bs-accent":"#88c0d0","--bs-accent2":"#8fbcbb","--bs-fab1":"#88c0d0","--bs-fab2":"#81a1c1","--bs-radius":"10px","--bs-radius-sm":"6px","--bs-red":"#bf616a","--bs-green":"#a3be8c","--bs-orange":"#d08770","--bs-yellow":"#ebcb8b"}},{id:"monokai",name:"Monokai",preview:["#272822","#a6e22e"],vars:{"--bs-bg":"#272822","--bs-bg2":"#2f302a","--bs-bg3":"#383930","--bs-text":"#f8f8f2","--bs-muted":"#75715e","--bs-border":"#49483e","--bs-accent":"#a6e22e","--bs-accent2":"#c4f060","--bs-fab1":"#a6e22e","--bs-fab2":"#66d9ef","--bs-radius":"8px","--bs-radius-sm":"4px","--bs-red":"#f92672","--bs-green":"#a6e22e","--bs-orange":"#fd971f","--bs-yellow":"#e6db74"}},{id:"solarized",name:"Solarized Dark",preview:["#002b36","#268bd2"],vars:{"--bs-bg":"#002b36","--bs-bg2":"#073642","--bs-bg3":"#0e4050","--bs-text":"#eee8d5","--bs-muted":"#839496","--bs-border":"#1a4f5c","--bs-accent":"#268bd2","--bs-accent2":"#2aa198","--bs-fab1":"#268bd2","--bs-fab2":"#2aa198","--bs-radius":"10px","--bs-radius-sm":"6px","--bs-red":"#dc322f","--bs-green":"#859900","--bs-orange":"#cb4b16","--bs-yellow":"#b58900"}},{id:"candy",name:"Candy",preview:["#fef1f8","#ec4899"],vars:{"--bs-bg":"#fef1f8","--bs-bg2":"#fce7f3","--bs-bg3":"#ffffff","--bs-text":"#4a1942","--bs-muted":"#9f5090","--bs-border":"#f0c6e0","--bs-accent":"#ec4899","--bs-accent2":"#f472b6","--bs-fab1":"#ec4899","--bs-fab2":"#a855f7","--bs-radius":"20px","--bs-radius-sm":"14px","--bs-red":"#e11d48","--bs-green":"#059669","--bs-orange":"#ea580c","--bs-yellow":"#ca8a04"}},{id:"slate",name:"Slate",preview:["#1e2432","#94a3b8"],vars:{"--bs-bg":"#1e2432","--bs-bg2":"#263040","--bs-bg3":"#2e384a","--bs-text":"#e2e8f0","--bs-muted":"#64748b","--bs-border":"#374462","--bs-accent":"#94a3b8","--bs-accent2":"#cbd5e1","--bs-fab1":"#475569","--bs-fab2":"#64748b","--bs-radius":"14px","--bs-radius-sm":"8px","--bs-red":"#f87171","--bs-green":"#4ade80","--bs-orange":"#fb923c","--bs-yellow":"#fbbf24"}},{id:"ember",name:"Ember",preview:["#1a0f0f","#ef4444"],vars:{"--bs-bg":"#1a0f0f","--bs-bg2":"#241515","--bs-bg3":"#2e1c1c","--bs-text":"#fde8e8","--bs-muted":"#a06060","--bs-border":"#4a2222","--bs-accent":"#ef4444","--bs-accent2":"#f87171","--bs-fab1":"#ef4444","--bs-fab2":"#f97316","--bs-radius":"12px","--bs-radius-sm":"8px","--bs-red":"#fca5a5","--bs-green":"#86efac","--bs-orange":"#fdba74","--bs-yellow":"#fde68a"}},{id:"lavender",name:"Lavender",preview:["#f5f0ff","#7c3aed"],vars:{"--bs-bg":"#f5f0ff","--bs-bg2":"#ede5ff","--bs-bg3":"#ffffff","--bs-text":"#2e1065","--bs-muted":"#7c6a9a","--bs-border":"#d8c8f0","--bs-accent":"#7c3aed","--bs-accent2":"#8b5cf6","--bs-fab1":"#7c3aed","--bs-fab2":"#a855f7","--bs-radius":"18px","--bs-radius-sm":"12px","--bs-red":"#dc2626","--bs-green":"#16a34a","--bs-orange":"#ea580c","--bs-yellow":"#ca8a04"}}];function bt(){return Wt}function pt(e){return Wt.find(t=>t.id===e)}function As(){return Wt[0]}var _t=[{id:"classic",name:"Classic Center",description:"Centered modal, top tabs",tabPosition:"top"},{id:"drawer-right",name:"Right Drawer",description:"Slides in from the right",tabPosition:"top"},{id:"drawer-left",name:"Left Drawer",description:"Slides in from the left",tabPosition:"top"},{id:"bottom-sheet",name:"Bottom Sheet",description:"Slides up from the bottom",tabPosition:"top"},{id:"top-bar",name:"Top Drop",description:"Drops down from the top",tabPosition:"top"},{id:"compact",name:"Compact",description:"Small centered popup",tabPosition:"top"},{id:"wide",name:"Wide",description:"Wide horizontal modal",tabPosition:"top"},{id:"fullscreen",name:"Fullscreen",description:"Full screen overlay",tabPosition:"top"},{id:"corner-br",name:"Corner Card",description:"Bottom-right corner card",tabPosition:"top"},{id:"corner-bl",name:"Corner Left",description:"Bottom-left corner card",tabPosition:"top"},{id:"pill-tabs",name:"Pill Tabs",description:"Centered with pill-style tabs",tabPosition:"top"},{id:"sidebar-tabs",name:"Sidebar Tabs",description:"Vertical tabs on the left",tabPosition:"left"},{id:"segmented",name:"Segmented",description:"Tabs as segmented control",tabPosition:"top"},{id:"minimal",name:"Minimal",description:"Ultra clean, spacious",tabPosition:"top"},{id:"dense",name:"Dense",description:"Compact, tight spacing",tabPosition:"top"},{id:"rounded",name:"Bubble",description:"Extra rounded, playful",tabPosition:"top"},{id:"sharp",name:"Sharp",description:"Square corners, industrial",tabPosition:"top"},{id:"split",name:"Split View",description:"Two-column layout",tabPosition:"left"},{id:"floating",name:"Floating",description:"Heavy shadow, borderless",tabPosition:"top"},{id:"bottom-tabs",name:"Bottom Tabs",description:"Tabs at the bottom",tabPosition:"bottom"}];function ut(){return _t}function Oe(e){return _t.find(t=>t.id===e)}function Is(){return _t[0]}var Hs=`
1326
11
  /* \u2500\u2500 1. Classic (default \u2014 no overrides) \u2500\u2500 */
1327
12
 
1328
13
  /* \u2500\u2500 2. Drawer Right \u2500\u2500 */
@@ -1649,173 +334,7 @@ var LAYOUT_CSS = `
1649
334
  }
1650
335
  .bs-ly-bottom-tabs .bs-tab-divider { order: 98; }
1651
336
  .bs-ly-bottom-tabs .bs-scroll { order: 1; }
1652
- `;
1653
-
1654
- // src/realtime.ts
1655
- var socket = null;
1656
- var reconnectTimer = null;
1657
- var reconnectAttempts = 0;
1658
- var MAX_RECONNECT = 10;
1659
- var handlers = /* @__PURE__ */ new Map();
1660
- var currentProjectId = null;
1661
- var sioConnected = false;
1662
- var pingTimer = null;
1663
- function connectRealtime(projectId2) {
1664
- currentProjectId = projectId2;
1665
- const token = getAccessToken();
1666
- if (!token) return;
1667
- doConnect(projectId2, token);
1668
- }
1669
- function doConnect(projectId2, token) {
1670
- if (socket?.readyState === WebSocket.OPEN) return;
1671
- const endpoint2 = getEndpoint();
1672
- sioConnected = false;
1673
- try {
1674
- const wsUrl = `${endpoint2.replace(/^http/, "ws")}/socket.io/?EIO=4&transport=websocket`;
1675
- socket = new WebSocket(wsUrl);
1676
- socket.onopen = () => {
1677
- reconnectAttempts = 0;
1678
- };
1679
- socket.onmessage = (e) => {
1680
- const data = e.data;
1681
- if (data.startsWith("0") && !sioConnected) {
1682
- try {
1683
- const payload = JSON.parse(data.slice(1));
1684
- const interval = payload.pingInterval || 25e3;
1685
- if (pingTimer) clearInterval(pingTimer);
1686
- pingTimer = setInterval(() => {
1687
- if (socket?.readyState === WebSocket.OPEN) socket.send("2");
1688
- }, interval);
1689
- } catch {
1690
- }
1691
- socket.send("40" + JSON.stringify({ token }));
1692
- return;
1693
- }
1694
- if (data === "2") {
1695
- socket.send("3");
1696
- return;
1697
- }
1698
- if (data === "3") return;
1699
- if (data.startsWith("40") && !sioConnected) {
1700
- sioConnected = true;
1701
- socket.send("42" + JSON.stringify(["join:project", projectId2]));
1702
- return;
1703
- }
1704
- if (data.startsWith("44")) {
1705
- socket?.close();
1706
- return;
1707
- }
1708
- if (data.startsWith("42")) {
1709
- try {
1710
- const parsed = JSON.parse(data.slice(2));
1711
- if (Array.isArray(parsed) && parsed.length >= 2) {
1712
- const [eventType, eventData] = parsed;
1713
- const event = typeof eventData === "object" && eventData !== null ? eventData : { type: eventType, data: eventData, projectId: projectId2, userId: "", timestamp: Date.now() };
1714
- emit(eventType, event);
1715
- }
1716
- } catch {
1717
- }
1718
- }
1719
- };
1720
- socket.onclose = () => {
1721
- sioConnected = false;
1722
- if (pingTimer) {
1723
- clearInterval(pingTimer);
1724
- pingTimer = null;
1725
- }
1726
- scheduleReconnect(projectId2, token);
1727
- };
1728
- socket.onerror = () => {
1729
- socket?.close();
1730
- };
1731
- } catch {
1732
- scheduleReconnect(projectId2, token);
1733
- }
1734
- }
1735
- function scheduleReconnect(projectId2, token) {
1736
- if (reconnectAttempts >= MAX_RECONNECT) return;
1737
- reconnectAttempts++;
1738
- const delay = Math.min(1e3 * Math.pow(2, reconnectAttempts), 3e4);
1739
- reconnectTimer = setTimeout(() => doConnect(projectId2, token), delay);
1740
- }
1741
- function disconnectRealtime() {
1742
- if (reconnectTimer) clearTimeout(reconnectTimer);
1743
- if (pingTimer) clearInterval(pingTimer);
1744
- reconnectTimer = null;
1745
- pingTimer = null;
1746
- reconnectAttempts = MAX_RECONNECT;
1747
- sioConnected = false;
1748
- if (socket) {
1749
- socket.close();
1750
- socket = null;
1751
- }
1752
- currentProjectId = null;
1753
- }
1754
- function onRealtimeEvent(type, handler) {
1755
- if (!handlers.has(type)) handlers.set(type, /* @__PURE__ */ new Set());
1756
- handlers.get(type).add(handler);
1757
- return () => {
1758
- handlers.get(type)?.delete(handler);
1759
- };
1760
- }
1761
- function emit(type, event) {
1762
- handlers.get(type)?.forEach((h) => h(event));
1763
- handlers.get("*")?.forEach((h) => h(event));
1764
- }
1765
- function sendPageNavigate(pageUrl) {
1766
- if (!socket || socket.readyState !== WebSocket.OPEN || !sioConnected || !currentProjectId) return;
1767
- socket.send("42" + JSON.stringify(["page:navigate", { projectId: currentProjectId, pageUrl }]));
1768
- }
1769
- function isConnected() {
1770
- return sioConnected && socket?.readyState === WebSocket.OPEN;
1771
- }
1772
-
1773
- // src/livepins.ts
1774
- var projectId;
1775
- var container = null;
1776
- var pins = [];
1777
- var members = [];
1778
- var pinMode = false;
1779
- var activePopup = null;
1780
- var currentPathname = "";
1781
- var PIN_COLORS = {
1782
- open: "#f97316",
1783
- in_progress: "#3b82f6",
1784
- resolved: "#22c55e",
1785
- closed: "#6b7280"
1786
- };
1787
- function initLivePins(projId) {
1788
- projectId = projId;
1789
- currentPathname = window.location.pathname;
1790
- createOverlay();
1791
- loadPins();
1792
- loadMembers();
1793
- setupRealtimeListeners();
1794
- watchNavigation();
1795
- }
1796
- function destroyLivePins() {
1797
- if (container) {
1798
- container.remove();
1799
- container = null;
1800
- }
1801
- pins = [];
1802
- }
1803
- function togglePinMode(enabled) {
1804
- pinMode = enabled !== void 0 ? enabled : !pinMode;
1805
- if (container) {
1806
- container.classList.toggle("bs-pin-mode", pinMode);
1807
- }
1808
- return pinMode;
1809
- }
1810
- function isPinModeActive() {
1811
- return pinMode;
1812
- }
1813
- function createOverlay() {
1814
- if (container) return;
1815
- container = document.createElement("div");
1816
- container.id = "bugstash-live-pins";
1817
- const style = document.createElement("style");
1818
- style.textContent = `
337
+ `;var A=null,ft=null,je=0,Ns=10,we=new Map,gt=null,de=!1,se=null;function mt(e){gt=e;let t=Cs();t&&Bs(e,t)}function Bs(e,t){if(A?.readyState===WebSocket.OPEN)return;let s=Ls();de=!1;try{let n=`${s.replace(/^http/,"ws")}/socket.io/?EIO=4&transport=websocket`;A=new WebSocket(n),A.onopen=()=>{je=0},A.onmessage=r=>{let i=r.data;if(i.startsWith("0")&&!de){try{let p=JSON.parse(i.slice(1)).pingInterval||25e3;se&&clearInterval(se),se=setInterval(()=>{A?.readyState===WebSocket.OPEN&&A.send("2")},p)}catch{}A.send("40"+JSON.stringify({token:t}));return}if(i==="2"){A.send("3");return}if(i!=="3"){if(i.startsWith("40")&&!de){de=!0,A.send("42"+JSON.stringify(["join:project",e]));return}if(i.startsWith("44")){A?.close();return}if(i.startsWith("42"))try{let c=JSON.parse(i.slice(2));if(Array.isArray(c)&&c.length>=2){let[p,l]=c,h=typeof l=="object"&&l!==null?l:{type:p,data:l,projectId:e,userId:"",timestamp:Date.now()};An(p,h)}}catch{}}},A.onclose=()=>{de=!1,se&&(clearInterval(se),se=null),Rs(e,t)},A.onerror=()=>{A?.close()}}catch{Rs(e,t)}}function Rs(e,t){if(je>=Ns)return;je++;let s=Math.min(1e3*Math.pow(2,je),3e4);ft=setTimeout(()=>Bs(e,t),s)}function Ds(){ft&&clearTimeout(ft),se&&clearInterval(se),ft=null,se=null,je=Ns,de=!1,A&&(A.close(),A=null),gt=null}function qe(e,t){return we.has(e)||we.set(e,new Set),we.get(e).add(t),()=>{we.get(e)?.delete(t)}}function An(e,t){we.get(e)?.forEach(s=>s(t)),we.get("*")?.forEach(s=>s(t))}function Os(e){!A||A.readyState!==WebSocket.OPEN||!de||!gt||A.send("42"+JSON.stringify(["page:navigate",{projectId:gt,pageUrl:e}]))}function js(){return de&&A?.readyState===WebSocket.OPEN}var ht,I=null,W=[],_s=[],ke=!1,Ue=null,In="",qs={open:"#f97316",in_progress:"#3b82f6",resolved:"#22c55e",closed:"#6b7280"};function xt(e){ht=e,In=window.location.pathname,Hn(),Ys(),Rn(),On(),jn()}function Fs(){I&&(I.remove(),I=null),W=[]}function vt(e){return ke=e!==void 0?e:!ke,I&&I.classList.toggle("bs-pin-mode",ke),ke}function yt(){return ke}function Hn(){if(I)return;I=document.createElement("div"),I.id="bugstash-live-pins";let e=document.createElement("style");e.textContent=`
1819
338
  #bugstash-live-pins {
1820
339
  position: fixed;
1821
340
  inset: 0;
@@ -2045,98 +564,27 @@ function createOverlay() {
2045
564
  background: #333754;
2046
565
  color: #a0a4b8;
2047
566
  }
2048
- `;
2049
- container.appendChild(style);
2050
- document.body.appendChild(container);
2051
- container.addEventListener("click", (e) => {
2052
- if (!pinMode) return;
2053
- if (e.target.closest(".bs-lp, .bs-lp-popup, .bs-lp-newform")) return;
2054
- const x = e.clientX + window.scrollX;
2055
- const y = e.clientY + window.scrollY;
2056
- container.style.pointerEvents = "none";
2057
- const el = document.elementFromPoint(e.clientX, e.clientY);
2058
- container.style.pointerEvents = "";
2059
- showNewPinForm(x, y, el);
2060
- });
2061
- }
2062
- async function loadPins() {
2063
- const result = await fetchPagePins(projectId, window.location.pathname);
2064
- if (result.success && result.data) {
2065
- pins = result.data;
2066
- renderPins();
2067
- }
2068
- }
2069
- async function loadMembers() {
2070
- const result = await fetchProjectMembers(projectId);
2071
- if (result.success && result.data) {
2072
- members = result.data;
2073
- }
2074
- }
2075
- function renderPins() {
2076
- if (!container) return;
2077
- container.querySelectorAll(".bs-lp").forEach((el) => el.remove());
2078
- pins.forEach((pin, i) => {
2079
- const dot = document.createElement("div");
2080
- dot.className = "bs-lp";
2081
- dot.style.left = `${pin.pageX}px`;
2082
- dot.style.top = `${pin.pageY}px`;
2083
- dot.dataset.pinId = pin.id;
2084
- const color = PIN_COLORS[pin.status] || PIN_COLORS.open;
2085
- dot.innerHTML = `
2086
- <div class="bs-lp-dot" style="background:${color}">
2087
- <span>${i + 1}</span>
567
+ `,I.appendChild(e),document.body.appendChild(I),I.addEventListener("click",t=>{if(!ke||t.target.closest(".bs-lp, .bs-lp-popup, .bs-lp-newform"))return;let s=t.clientX+window.scrollX,n=t.clientY+window.scrollY;I.style.pointerEvents="none";let r=document.elementFromPoint(t.clientX,t.clientY);I.style.pointerEvents="",Dn(s,n,r)})}async function Ys(){let e=await Ts(ht,window.location.pathname);e.success&&e.data&&(W=e.data,re())}async function Rn(){let e=await Ps(ht);e.success&&e.data&&(_s=e.data)}function re(){I&&(I.querySelectorAll(".bs-lp").forEach(e=>e.remove()),W.forEach((e,t)=>{let s=document.createElement("div");s.className="bs-lp",s.style.left=`${e.pageX}px`,s.style.top=`${e.pageY}px`,s.dataset.pinId=e.id;let n=qs[e.status]||qs.open;s.innerHTML=`
568
+ <div class="bs-lp-dot" style="background:${n}">
569
+ <span>${t+1}</span>
2088
570
  </div>
2089
- `;
2090
- dot.addEventListener("click", (e) => {
2091
- e.stopPropagation();
2092
- showPinPopup(pin, dot);
2093
- });
2094
- container.appendChild(dot);
2095
- });
2096
- }
2097
- function timeAgo(ts) {
2098
- const diff = Date.now() - ts;
2099
- const mins = Math.floor(diff / 6e4);
2100
- if (mins < 1) return "just now";
2101
- if (mins < 60) return `${mins}m ago`;
2102
- const hrs = Math.floor(mins / 60);
2103
- if (hrs < 24) return `${hrs}h ago`;
2104
- return `${Math.floor(hrs / 24)}d ago`;
2105
- }
2106
- function statusColor(status) {
2107
- const colors = {
2108
- open: "#f97316",
2109
- in_progress: "#3b82f6",
2110
- resolved: "#22c55e",
2111
- closed: "#6b7280"
2112
- };
2113
- return colors[status] || "#6b7280";
2114
- }
2115
- async function showPinPopup(pin, dotEl) {
2116
- closePopup();
2117
- const popup = document.createElement("div");
2118
- popup.className = "bs-lp-popup";
2119
- const commentsRes = await fetchComments(pin.id);
2120
- const comments = commentsRes.success && commentsRes.data ? commentsRes.data : [];
2121
- const user = getCurrentUser();
2122
- const canManage = user && (user.role === "owner" || user.role === "admin" || user.id === pin.createdBy);
2123
- popup.innerHTML = `
571
+ `,s.addEventListener("click",r=>{r.stopPropagation(),Nn(e,s)}),I.appendChild(s)}))}function Us(e){let t=Date.now()-e,s=Math.floor(t/6e4);if(s<1)return"just now";if(s<60)return`${s}m ago`;let n=Math.floor(s/60);return n<24?`${n}h ago`:`${Math.floor(n/24)}d ago`}function Ws(e){return{open:"#f97316",in_progress:"#3b82f6",resolved:"#22c55e",closed:"#6b7280"}[e]||"#6b7280"}async function Nn(e,t){pe();let s=document.createElement("div");s.className="bs-lp-popup";let n=await $s(e.id),r=n.success&&n.data?n.data:[],i=V(),c=i&&(i.role==="owner"||i.role==="admin"||i.id===e.createdBy);s.innerHTML=`
2124
572
  <div class="bs-lp-popup-header">
2125
- <div class="bs-lp-popup-title">${escapeHtml(pin.title)}</div>
2126
- <span class="bs-lp-popup-status" style="background:${statusColor(pin.status)}22;color:${statusColor(pin.status)}">${pin.status.replace("_", " ")}</span>
573
+ <div class="bs-lp-popup-title">${ne(e.title)}</div>
574
+ <span class="bs-lp-popup-status" style="background:${Ws(e.status)}22;color:${Ws(e.status)}">${e.status.replace("_"," ")}</span>
2127
575
  <button class="bs-lp-close">&times;</button>
2128
576
  </div>
2129
- ${pin.description ? `<div class="bs-lp-popup-body">${escapeHtml(pin.description)}</div>` : ""}
577
+ ${e.description?`<div class="bs-lp-popup-body">${ne(e.description)}</div>`:""}
2130
578
  <div class="bs-lp-popup-meta">
2131
- <span>${pin.creatorName || "Unknown"} &middot; ${timeAgo(pin.createdAt)}</span>
2132
- <span>${pin.assigneeName ? `Assigned: ${pin.assigneeName}` : "Unassigned"}</span>
579
+ <span>${e.creatorName||"Unknown"} &middot; ${Us(e.createdAt)}</span>
580
+ <span>${e.assigneeName?`Assigned: ${e.assigneeName}`:"Unassigned"}</span>
2133
581
  </div>
2134
582
  <div class="bs-lp-comments" id="bs-lp-comments">
2135
- ${comments.map((c) => `
583
+ ${r.map(l=>`
2136
584
  <div class="bs-lp-comment">
2137
- <div class="bs-lp-comment-author">${escapeHtml(c.author?.name || c.authorName || "Unknown")}</div>
2138
- <div class="bs-lp-comment-body">${escapeHtml(c.body)}</div>
2139
- <div class="bs-lp-comment-time">${timeAgo(new Date(c.createdAt).getTime())}</div>
585
+ <div class="bs-lp-comment-author">${ne(l.author?.name||l.authorName||"Unknown")}</div>
586
+ <div class="bs-lp-comment-body">${ne(l.body)}</div>
587
+ <div class="bs-lp-comment-time">${Us(new Date(l.createdAt).getTime())}</div>
2140
588
  </div>
2141
589
  `).join("")}
2142
590
  </div>
@@ -2145,123 +593,16 @@ async function showPinPopup(pin, dotEl) {
2145
593
  <button id="bs-lp-comment-send">Send</button>
2146
594
  </div>
2147
595
  <div class="bs-lp-actions">
2148
- ${pin.status !== "resolved" ? `<button class="bs-lp-btn resolve" id="bs-lp-resolve">Resolve</button>` : `<button class="bs-lp-btn" id="bs-lp-reopen">Reopen</button>`}
2149
- ${canManage ? `<button class="bs-lp-btn delete" id="bs-lp-delete">Delete</button>` : ""}
596
+ ${e.status!=="resolved"?'<button class="bs-lp-btn resolve" id="bs-lp-resolve">Resolve</button>':'<button class="bs-lp-btn" id="bs-lp-reopen">Reopen</button>'}
597
+ ${c?'<button class="bs-lp-btn delete" id="bs-lp-delete">Delete</button>':""}
2150
598
  </div>
2151
- `;
2152
- dotEl.appendChild(popup);
2153
- activePopup = popup;
2154
- popup.querySelector(".bs-lp-close").addEventListener("click", (e) => {
2155
- e.stopPropagation();
2156
- closePopup();
2157
- });
2158
- popup.querySelector("#bs-lp-comment-send")?.addEventListener("click", async () => {
2159
- const input = popup.querySelector("#bs-lp-comment-input");
2160
- const body = input.value.trim();
2161
- if (!body) return;
2162
- input.value = "";
2163
- if (navigator.onLine) {
2164
- const res = await createComment(pin.id, body);
2165
- if (res.success && res.data) {
2166
- const commentsEl = popup.querySelector("#bs-lp-comments");
2167
- const c = res.data;
2168
- commentsEl.innerHTML += `
599
+ `,t.appendChild(s),Ue=s,s.querySelector(".bs-lp-close").addEventListener("click",l=>{l.stopPropagation(),pe()}),s.querySelector("#bs-lp-comment-send")?.addEventListener("click",async()=>{let l=s.querySelector("#bs-lp-comment-input"),h=l.value.trim();if(h)if(l.value="",navigator.onLine){let g=await jt(e.id,h);if(g.success&&g.data){let x=s.querySelector("#bs-lp-comments"),y=g.data;x.innerHTML+=`
2169
600
  <div class="bs-lp-comment">
2170
- <div class="bs-lp-comment-author">${escapeHtml(c.authorName || user?.name || "You")}</div>
2171
- <div class="bs-lp-comment-body">${escapeHtml(c.body)}</div>
601
+ <div class="bs-lp-comment-author">${ne(y.authorName||i?.name||"You")}</div>
602
+ <div class="bs-lp-comment-body">${ne(y.body)}</div>
2172
603
  <div class="bs-lp-comment-time">just now</div>
2173
604
  </div>
2174
- `;
2175
- commentsEl.scrollTop = commentsEl.scrollHeight;
2176
- }
2177
- } else {
2178
- queueOfflineAction({ type: "create_comment", data: { pinId: pin.id, body } });
2179
- }
2180
- });
2181
- const commentInput = popup.querySelector("#bs-lp-comment-input");
2182
- commentInput?.addEventListener("keydown", (e) => {
2183
- if (e.key === "Enter") {
2184
- popup.querySelector("#bs-lp-comment-send")?.dispatchEvent(new Event("click"));
2185
- }
2186
- });
2187
- popup.querySelector("#bs-lp-resolve")?.addEventListener("click", async () => {
2188
- await updatePin(pin.id, { status: "resolved" });
2189
- pin.status = "resolved";
2190
- closePopup();
2191
- renderPins();
2192
- });
2193
- popup.querySelector("#bs-lp-reopen")?.addEventListener("click", async () => {
2194
- await updatePin(pin.id, { status: "open" });
2195
- pin.status = "open";
2196
- closePopup();
2197
- renderPins();
2198
- });
2199
- popup.querySelector("#bs-lp-delete")?.addEventListener("click", async () => {
2200
- if (!confirm("Delete this pin?")) return;
2201
- await deletePin(pin.id);
2202
- pins = pins.filter((p) => p.id !== pin.id);
2203
- closePopup();
2204
- renderPins();
2205
- });
2206
- popup.addEventListener("click", (e) => e.stopPropagation());
2207
- }
2208
- function closePopup() {
2209
- activePopup?.remove();
2210
- activePopup = null;
2211
- }
2212
- function getSelector2(el) {
2213
- if (!el || el === document.body || el === document.documentElement) return "body";
2214
- if (el.id) return `#${el.id}`;
2215
- let selector = el.tagName.toLowerCase();
2216
- if (el.className && typeof el.className === "string") {
2217
- const classes = el.className.trim().split(/\s+/).filter((c) => !c.startsWith("bs-")).slice(0, 3);
2218
- if (classes.length) selector += "." + classes.join(".");
2219
- }
2220
- const parent = el.parentElement;
2221
- if (parent && parent !== document.body) {
2222
- const siblings = Array.from(parent.children).filter((c) => c.tagName === el.tagName);
2223
- if (siblings.length > 1) {
2224
- const idx = siblings.indexOf(el);
2225
- selector += `:nth-child(${idx + 1})`;
2226
- }
2227
- return getSelector2(parent) + " > " + selector;
2228
- }
2229
- return selector;
2230
- }
2231
- function getXPath(el) {
2232
- if (!el) return "";
2233
- const parts = [];
2234
- let current = el;
2235
- while (current && current !== document.body) {
2236
- let idx = 1;
2237
- let sib = current.previousElementSibling;
2238
- while (sib) {
2239
- if (sib.tagName === current.tagName) idx++;
2240
- sib = sib.previousElementSibling;
2241
- }
2242
- parts.unshift(`${current.tagName.toLowerCase()}[${idx}]`);
2243
- current = current.parentElement;
2244
- }
2245
- return "/body/" + parts.join("/");
2246
- }
2247
- function showNewPinForm(pageX, pageY, targetEl) {
2248
- closePopup();
2249
- container?.querySelectorAll(".bs-lp-newform").forEach((e) => e.remove());
2250
- const form = document.createElement("div");
2251
- form.className = "bs-lp-newform";
2252
- form.style.left = `${pageX + 16}px`;
2253
- form.style.top = `${pageY - 8}px`;
2254
- requestAnimationFrame(() => {
2255
- const rect = form.getBoundingClientRect();
2256
- if (rect.right > window.innerWidth - 16) {
2257
- form.style.left = `${pageX - 336}px`;
2258
- }
2259
- if (rect.bottom > window.innerHeight - 16) {
2260
- form.style.top = `${pageY - rect.height}px`;
2261
- }
2262
- });
2263
- const memberOptions = members.filter((m) => m.userId !== getCurrentUser()?.id).map((m) => `<option value="${m.userId}">${escapeHtml(m.name)}</option>`).join("");
2264
- form.innerHTML = `
605
+ `,x.scrollTop=x.scrollHeight}}else Ut({type:"create_comment",data:{pinId:e.id,body:h}})}),s.querySelector("#bs-lp-comment-input")?.addEventListener("keydown",l=>{l.key==="Enter"&&s.querySelector("#bs-lp-comment-send")?.dispatchEvent(new Event("click"))}),s.querySelector("#bs-lp-resolve")?.addEventListener("click",async()=>{await dt(e.id,{status:"resolved"}),e.status="resolved",pe(),re()}),s.querySelector("#bs-lp-reopen")?.addEventListener("click",async()=>{await dt(e.id,{status:"open"}),e.status="open",pe(),re()}),s.querySelector("#bs-lp-delete")?.addEventListener("click",async()=>{confirm("Delete this pin?")&&(await Ms(e.id),W=W.filter(l=>l.id!==e.id),pe(),re())}),s.addEventListener("click",l=>l.stopPropagation())}function pe(){Ue?.remove(),Ue=null}function Js(e){if(!e||e===document.body||e===document.documentElement)return"body";if(e.id)return`#${e.id}`;let t=e.tagName.toLowerCase();if(e.className&&typeof e.className=="string"){let n=e.className.trim().split(/\s+/).filter(r=>!r.startsWith("bs-")).slice(0,3);n.length&&(t+="."+n.join("."))}let s=e.parentElement;if(s&&s!==document.body){let n=Array.from(s.children).filter(r=>r.tagName===e.tagName);if(n.length>1){let r=n.indexOf(e);t+=`:nth-child(${r+1})`}return Js(s)+" > "+t}return t}function Bn(e){if(!e)return"";let t=[],s=e;for(;s&&s!==document.body;){let n=1,r=s.previousElementSibling;for(;r;)r.tagName===s.tagName&&n++,r=r.previousElementSibling;t.unshift(`${s.tagName.toLowerCase()}[${n}]`),s=s.parentElement}return"/body/"+t.join("/")}function Dn(e,t,s){pe(),I?.querySelectorAll(".bs-lp-newform").forEach(i=>i.remove());let n=document.createElement("div");n.className="bs-lp-newform",n.style.left=`${e+16}px`,n.style.top=`${t-8}px`,requestAnimationFrame(()=>{let i=n.getBoundingClientRect();i.right>window.innerWidth-16&&(n.style.left=`${e-336}px`),i.bottom>window.innerHeight-16&&(n.style.top=`${t-i.height}px`)});let r=_s.filter(i=>i.userId!==V()?.id).map(i=>`<option value="${i.userId}">${ne(i.name)}</option>`).join("");n.innerHTML=`
2265
606
  <label>Title *</label>
2266
607
  <input type="text" id="bs-np-title" placeholder="What's the issue?" autofocus />
2267
608
  <label>Description</label>
@@ -2281,188 +622,24 @@ function showNewPinForm(pageX, pageY, targetEl) {
2281
622
  <option value="content">Content</option>
2282
623
  <option value="other" selected>Other</option>
2283
624
  </select>
2284
- ${memberOptions ? `
625
+ ${r?`
2285
626
  <label>Assign to</label>
2286
627
  <select id="bs-np-assignee">
2287
628
  <option value="">Unassigned</option>
2288
- ${memberOptions}
629
+ ${r}
2289
630
  </select>
2290
- ` : ""}
631
+ `:""}
2291
632
  <div class="bs-lp-newform-actions">
2292
633
  <button class="bs-lp-newform-cancel" id="bs-np-cancel">Cancel</button>
2293
634
  <button class="bs-lp-newform-submit" id="bs-np-submit">Create Pin</button>
2294
635
  </div>
2295
- `;
2296
- container.appendChild(form);
2297
- form.addEventListener("click", (e) => e.stopPropagation());
2298
- form.querySelector("#bs-np-cancel").addEventListener("click", () => form.remove());
2299
- form.querySelector("#bs-np-submit").addEventListener("click", async () => {
2300
- const title = form.querySelector("#bs-np-title").value.trim();
2301
- if (!title) {
2302
- form.querySelector("#bs-np-title").style.borderColor = "#ef4444";
2303
- return;
2304
- }
2305
- const desc = form.querySelector("#bs-np-desc").value.trim();
2306
- const priority = form.querySelector("#bs-np-priority").value;
2307
- const category = form.querySelector("#bs-np-category").value;
2308
- const assigneeEl = form.querySelector("#bs-np-assignee");
2309
- const assigneeId = assigneeEl?.value || void 0;
2310
- const selector = getSelector2(targetEl);
2311
- const xpath = getXPath(targetEl);
2312
- const logs2 = getLogs().slice(-20).map((l) => `[${l.level}] ${l.args.join(" ")}`);
2313
- const errors2 = getErrors().slice(-10).map((e) => `${e.message} at ${e.source}:${e.lineno}`);
2314
- const netErrors = getFailedNetworkCaptures().slice(-10).map((n) => `${n.method} ${n.url} \u2192 ${n.status}`);
2315
- let screenshot;
2316
- try {
2317
- form.style.display = "none";
2318
- container.style.display = "none";
2319
- screenshot = await captureScreenshot() ?? void 0;
2320
- container.style.display = "";
2321
- form.style.display = "";
2322
- } catch {
2323
- }
2324
- const pinData = {
2325
- projectId,
2326
- pageUrl: window.location.href,
2327
- pathname: window.location.pathname,
2328
- elementSelector: selector,
2329
- elementXPath: xpath,
2330
- xPercent: 0,
2331
- yPercent: 0,
2332
- pageX,
2333
- pageY,
2334
- title,
2335
- description: desc,
2336
- screenshot,
2337
- priority,
2338
- category,
2339
- assigneeId,
2340
- browserInfo: navigator.userAgent,
2341
- screenSize: `${screen.width}x${screen.height}`,
2342
- viewportSize: `${window.innerWidth}x${window.innerHeight}`,
2343
- devicePixelRatio: window.devicePixelRatio,
2344
- consoleLogs: logs2,
2345
- networkErrors: netErrors,
2346
- jsErrors: errors2
2347
- };
2348
- form.remove();
2349
- if (navigator.onLine) {
2350
- const res = await createPin(pinData);
2351
- if (res.success && res.data) {
2352
- pins.push(res.data);
2353
- renderPins();
2354
- }
2355
- } else {
2356
- queueOfflineAction({ type: "create_pin", data: pinData });
2357
- pins.push({
2358
- ...pinData,
2359
- id: "local-" + Date.now(),
2360
- orgId: "",
2361
- status: "open",
2362
- tags: [],
2363
- createdById: getCurrentUser()?.id || "",
2364
- creatorName: getCurrentUser()?.name || "",
2365
- commentCount: 0,
2366
- createdAt: Date.now(),
2367
- updatedAt: Date.now()
2368
- });
2369
- renderPins();
2370
- }
2371
- });
2372
- setTimeout(() => form.querySelector("#bs-np-title")?.focus(), 50);
2373
- }
2374
- function setupRealtimeListeners() {
2375
- onRealtimeEvent("pin:created", (event) => {
2376
- const pin = event.data;
2377
- if (pin.pathname === window.location.pathname) {
2378
- if (!pins.find((p) => p.id === pin.id)) {
2379
- pins.push(pin);
2380
- renderPins();
2381
- }
2382
- }
2383
- });
2384
- onRealtimeEvent("pin:updated", (event) => {
2385
- const updated = event.data;
2386
- const idx = pins.findIndex((p) => p.id === updated.id);
2387
- if (idx >= 0) {
2388
- pins[idx] = { ...pins[idx], ...updated };
2389
- renderPins();
2390
- }
2391
- });
2392
- onRealtimeEvent("pin:deleted", (event) => {
2393
- const { id } = event.data;
2394
- pins = pins.filter((p) => p.id !== id);
2395
- closePopup();
2396
- renderPins();
2397
- });
2398
- onRealtimeEvent("comment:created", (event) => {
2399
- const comment = event.data;
2400
- if (activePopup) {
2401
- const commentsEl = activePopup.querySelector("#bs-lp-comments");
2402
- if (commentsEl) {
2403
- commentsEl.innerHTML += `
636
+ `,I.appendChild(n),n.addEventListener("click",i=>i.stopPropagation()),n.querySelector("#bs-np-cancel").addEventListener("click",()=>n.remove()),n.querySelector("#bs-np-submit").addEventListener("click",async()=>{let i=n.querySelector("#bs-np-title").value.trim();if(!i){n.querySelector("#bs-np-title").style.borderColor="#ef4444";return}let c=n.querySelector("#bs-np-desc").value.trim(),p=n.querySelector("#bs-np-priority").value,l=n.querySelector("#bs-np-category").value,g=n.querySelector("#bs-np-assignee")?.value||void 0,x=Js(s),y=Bn(s),$=ee().slice(-20).map(k=>`[${k.level}] ${k.args.join(" ")}`),ae=te().slice(-10).map(k=>`${k.message} at ${k.source}:${k.lineno}`),Te=le().slice(-10).map(k=>`${k.method} ${k.url} \u2192 ${k.status}`),N;try{n.style.display="none",I.style.display="none",N=await it()??void 0,I.style.display="",n.style.display=""}catch{}let P={projectId:ht,pageUrl:window.location.href,pathname:window.location.pathname,elementSelector:x,elementXPath:y,xPercent:0,yPercent:0,pageX:e,pageY:t,title:i,description:c,screenshot:N,priority:p,category:l,assigneeId:g,browserInfo:navigator.userAgent,screenSize:`${screen.width}x${screen.height}`,viewportSize:`${window.innerWidth}x${window.innerHeight}`,devicePixelRatio:window.devicePixelRatio,consoleLogs:$,networkErrors:Te,jsErrors:ae};if(n.remove(),navigator.onLine){let k=await Ot(P);k.success&&k.data&&(W.push(k.data),re())}else Ut({type:"create_pin",data:P}),W.push({...P,id:"local-"+Date.now(),orgId:"",status:"open",tags:[],createdById:V()?.id||"",creatorName:V()?.name||"",commentCount:0,createdAt:Date.now(),updatedAt:Date.now()}),re()}),setTimeout(()=>n.querySelector("#bs-np-title")?.focus(),50)}function On(){qe("pin:created",e=>{let t=e.data;t.pathname===window.location.pathname&&(W.find(s=>s.id===t.id)||(W.push(t),re()))}),qe("pin:updated",e=>{let t=e.data,s=W.findIndex(n=>n.id===t.id);s>=0&&(W[s]={...W[s],...t},re())}),qe("pin:deleted",e=>{let{id:t}=e.data;W=W.filter(s=>s.id!==t),pe(),re()}),qe("comment:created",e=>{let t=e.data;if(Ue){let s=Ue.querySelector("#bs-lp-comments");s&&(s.innerHTML+=`
2404
637
  <div class="bs-lp-comment">
2405
- <div class="bs-lp-comment-author">${escapeHtml(comment.author?.name || "Someone")}</div>
2406
- <div class="bs-lp-comment-body">${escapeHtml(comment.body)}</div>
638
+ <div class="bs-lp-comment-author">${ne(t.author?.name||"Someone")}</div>
639
+ <div class="bs-lp-comment-body">${ne(t.body)}</div>
2407
640
  <div class="bs-lp-comment-time">just now</div>
2408
641
  </div>
2409
- `;
2410
- commentsEl.scrollTop = commentsEl.scrollHeight;
2411
- }
2412
- }
2413
- });
2414
- }
2415
- function watchNavigation() {
2416
- let lastPath = window.location.pathname;
2417
- const check = () => {
2418
- if (window.location.pathname !== lastPath) {
2419
- lastPath = window.location.pathname;
2420
- sendPageNavigate(window.location.href);
2421
- loadPins();
2422
- }
2423
- };
2424
- const origPush = history.pushState;
2425
- const origReplace = history.replaceState;
2426
- history.pushState = function(...args) {
2427
- origPush.apply(this, args);
2428
- check();
2429
- };
2430
- history.replaceState = function(...args) {
2431
- origReplace.apply(this, args);
2432
- check();
2433
- };
2434
- window.addEventListener("popstate", check);
2435
- }
2436
- function escapeHtml(str) {
2437
- const div = document.createElement("div");
2438
- div.textContent = str;
2439
- return div.innerHTML;
2440
- }
2441
-
2442
- // src/panel.ts
2443
- var config;
2444
- var fab = null;
2445
- var modal = null;
2446
- var backdrop = null;
2447
- var styleEl = null;
2448
- var keyHandler = null;
2449
- var isOpen = false;
2450
- var activeTab = "report";
2451
- var currentTheme = getDefaultTheme();
2452
- var currentLayout = getDefaultLayout();
2453
- var I = {
2454
- bug: `<svg width="28" height="28" viewBox="55 38 60 105" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M95.8 44h-29c-3 0-5.4 2.4-5.4 5.4v29c0 3 2.4 5.4 5.4 5.4h19.5c13 0 23.5-10.5 23.5-23.5v-2.8c0-7.5-6-13.5-13.5-13.5h-.5zm-6.2 28.2H74.8V57.4h14.8c5.8 0 10.4 4.7 10.4 10.4s-4.7 10.4-10.4 10.4z" fill="rgba(255,255,255,0.85)"/><path d="M100.4 96h-33.6c-3 0-5.4 2.4-5.4 5.4v33.6c0 3 2.4 5.4 5.4 5.4h22c15 0 27-12 27-27v-3.9c0-7.5-6-13.5-13.5-13.5h-1.9zm-7.8 32.2H74.8v-20h17.8c6.7 0 12.2 5.5 12.2 12.2 0 4.3-3.5 7.8-7.8 7.8h-4.6z" fill="rgba(255,255,255,1)"/></svg>`,
2455
- x: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`,
2456
- cam: `<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>`,
2457
- check: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><polyline points="20 6 9 17 4 12"/></svg>`,
2458
- report: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>`,
2459
- console: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>`,
2460
- network: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>`,
2461
- ctx: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>`,
2462
- settings: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/></svg>`,
2463
- history: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>`
2464
- };
2465
- var STYLES = `
642
+ `,s.scrollTop=s.scrollHeight)}})}function jn(){let e=window.location.pathname,t=()=>{window.location.pathname!==e&&(e=window.location.pathname,Os(window.location.href),Ys())},s=history.pushState,n=history.replaceState;history.pushState=function(...r){s.apply(this,r),t()},history.replaceState=function(...r){n.apply(this,r),t()},window.addEventListener("popstate",t)}function ne(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}var Y,j=null,m=null,F=null,We=null,_e=null,fe=!1,Ee="report";var Et=As(),ue=Is(),K={bug:'<svg width="28" height="28" viewBox="55 38 60 105" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M95.8 44h-29c-3 0-5.4 2.4-5.4 5.4v29c0 3 2.4 5.4 5.4 5.4h19.5c13 0 23.5-10.5 23.5-23.5v-2.8c0-7.5-6-13.5-13.5-13.5h-.5zm-6.2 28.2H74.8V57.4h14.8c5.8 0 10.4 4.7 10.4 10.4s-4.7 10.4-10.4 10.4z" fill="rgba(255,255,255,0.85)"/><path d="M100.4 96h-33.6c-3 0-5.4 2.4-5.4 5.4v33.6c0 3 2.4 5.4 5.4 5.4h22c15 0 27-12 27-27v-3.9c0-7.5-6-13.5-13.5-13.5h-1.9zm-7.8 32.2H74.8v-20h17.8c6.7 0 12.2 5.5 12.2 12.2 0 4.3-3.5 7.8-7.8 7.8h-4.6z" fill="rgba(255,255,255,1)"/></svg>',x:'<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',cam:'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>',check:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><polyline points="20 6 9 17 4 12"/></svg>',report:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>',console:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>',network:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>',ctx:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>',settings:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/></svg>',history:'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>'},qn=`
2466
643
  @import url('https://fonts.googleapis.com/css2?family=Comfortaa:wght@400;600;700&family=Inter:wght@300;400;500;600;700&display=swap');
2467
644
 
2468
645
  /* \u2500\u2500 FAB \u2500\u2500 */
@@ -3417,9 +1594,7 @@ var STYLES = `
3417
1594
  .bs-set-ly.bs-picked { border-color: var(--bs-accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--bs-accent) 25%, transparent); }
3418
1595
  .bs-set-ly-name { font-size: 12px; font-weight: 600; color: var(--bs-text); margin-bottom: 2px; }
3419
1596
  .bs-set-ly-desc { font-size: 10px; color: var(--bs-muted); }
3420
- `;
3421
- function tabLogin() {
3422
- return `
1597
+ `;function Un(){return`
3423
1598
  <div class="bs-login-form">
3424
1599
  <div class="bs-login-logo">BugStash</div>
3425
1600
  <div class="bs-login-subtitle">Sign in to report bugs & collaborate</div>
@@ -3429,116 +1604,13 @@ function tabLogin() {
3429
1604
  <button class="bs-login-btn" id="bs-login-submit">Sign In</button>
3430
1605
  <div style="font-size:11px;color:var(--bs-muted);margin-top:4px">Contact your admin for an account</div>
3431
1606
  </div>
3432
- `;
3433
- }
3434
- function esc(s) {
3435
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3436
- }
3437
- function timeAgo2(ts) {
3438
- const s = Math.floor((Date.now() - ts) / 1e3);
3439
- if (s < 5) return "now";
3440
- if (s < 60) return `${s}s ago`;
3441
- if (s < 3600) return `${Math.floor(s / 60)}m ago`;
3442
- return new Date(ts).toLocaleTimeString();
3443
- }
3444
- function fmtTime(ts) {
3445
- return new Date(ts).toLocaleTimeString("en", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
3446
- }
3447
- var HISTORY_KEY = "bugstash_history";
3448
- function getReportHistory() {
3449
- try {
3450
- return JSON.parse(localStorage.getItem(HISTORY_KEY) || "[]");
3451
- } catch {
3452
- return [];
3453
- }
3454
- }
3455
- function saveReportHistory(entries) {
3456
- try {
3457
- localStorage.setItem(HISTORY_KEY, JSON.stringify(entries));
3458
- } catch {
3459
- }
3460
- }
3461
- function addToHistory(entry) {
3462
- const entries = getReportHistory();
3463
- const id = Date.now();
3464
- entries.unshift({ ...entry, id });
3465
- if (entries.length > 50) entries.length = 50;
3466
- saveReportHistory(entries);
3467
- }
3468
- function deleteFromHistory(id) {
3469
- const entries = getReportHistory().filter((e) => e.id !== id);
3470
- saveReportHistory(entries);
3471
- }
3472
- function updateInHistory(id, updates) {
3473
- const entries = getReportHistory();
3474
- const idx = entries.findIndex((e) => e.id === id);
3475
- if (idx >= 0) {
3476
- entries[idx] = { ...entries[idx], ...updates };
3477
- saveReportHistory(entries);
3478
- }
3479
- }
3480
- function buildContext() {
3481
- return {
3482
- url: window.location.href,
3483
- userAgent: navigator.userAgent,
3484
- platform: navigator.platform,
3485
- language: navigator.language,
3486
- cookiesEnabled: navigator.cookieEnabled,
3487
- online: navigator.onLine,
3488
- screenWidth: screen.width,
3489
- screenHeight: screen.height,
3490
- viewportWidth: window.innerWidth,
3491
- viewportHeight: window.innerHeight,
3492
- devicePixelRatio: window.devicePixelRatio,
3493
- timestamp: Date.now(),
3494
- environment: config.environment ?? "development",
3495
- commitHash: config.commitHash,
3496
- user: config.user
3497
- };
3498
- }
3499
- function autoDetect() {
3500
- const errors2 = getErrors();
3501
- const logs2 = getLogs();
3502
- const net = getNetworkCaptures();
3503
- const failedNet = getFailedNetworkCaptures();
3504
- const crumbs2 = getBreadcrumbs();
3505
- const perf = getPerformanceMetrics();
3506
- const consoleErrors = logs2.filter((l) => l.level === "error");
3507
- let severity = "low";
3508
- if (errors2.length >= 3 || failedNet.length >= 3) severity = "critical";
3509
- else if (errors2.length >= 1 || failedNet.length >= 2) severity = "high";
3510
- else if (consoleErrors.length > 0 || failedNet.length >= 1) severity = "medium";
3511
- const tags = [];
3512
- if (errors2.length) tags.push("has-errors");
3513
- if (failedNet.length) tags.push("network-failures");
3514
- if (consoleErrors.length) tags.push("console-errors");
3515
- if (perf?.pageLoadTime && perf.pageLoadTime > 3e3) tags.push("slow-load");
3516
- if (perf?.cumulativeLayoutShift && perf.cumulativeLayoutShift > 0.25) tags.push("layout-shift");
3517
- if (!navigator.onLine) tags.push("offline");
3518
- if (window.innerWidth < 768) tags.push("mobile");
3519
- return {
3520
- severity,
3521
- tags,
3522
- counts: { logs: logs2.length, network: net.length, failedNet: failedNet.length, errors: errors2.length, crumbs: crumbs2.length }
3523
- };
3524
- }
3525
- function tabReport() {
3526
- const d = autoDetect();
3527
- const categories = [
3528
- { id: "ui", label: "UI Bug" },
3529
- { id: "functionality", label: "Broken Feature" },
3530
- { id: "performance", label: "Slow / Laggy" },
3531
- { id: "crash", label: "Crash" },
3532
- { id: "security", label: "Security" },
3533
- { id: "other", label: "Other" }
3534
- ];
3535
- return `<div class="bs-view">
1607
+ `}function H(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function Wn(e){let t=Math.floor((Date.now()-e)/1e3);return t<5?"now":t<60?`${t}s ago`:t<3600?`${Math.floor(t/60)}m ago`:new Date(e).toLocaleTimeString()}function Xs(e){return new Date(e).toLocaleTimeString("en",{hour12:!1,hour:"2-digit",minute:"2-digit",second:"2-digit"})}var Vs="bugstash_history";function Se(){try{return JSON.parse(localStorage.getItem(Vs)||"[]")}catch{return[]}}function Ft(e){try{localStorage.setItem(Vs,JSON.stringify(e))}catch{}}function _n(e){let t=Se(),s=Date.now();t.unshift({...e,id:s}),t.length>50&&(t.length=50),Ft(t)}function Fn(e){let t=Se().filter(s=>s.id!==e);Ft(t)}function Yn(e,t){let s=Se(),n=s.findIndex(r=>r.id===e);n>=0&&(s[n]={...s[n],...t},Ft(s))}function Ks(){return{url:window.location.href,userAgent:navigator.userAgent,platform:navigator.platform,language:navigator.language,cookiesEnabled:navigator.cookieEnabled,online:navigator.onLine,screenWidth:screen.width,screenHeight:screen.height,viewportWidth:window.innerWidth,viewportHeight:window.innerHeight,devicePixelRatio:window.devicePixelRatio,timestamp:Date.now(),environment:Y.environment??"development",commitHash:Y.commitHash,user:Y.user}}function Zs(){let e=te(),t=ee(),s=ve(),n=le(),r=me(),i=ye(),c=t.filter(h=>h.level==="error"),p="low";e.length>=3||n.length>=3?p="critical":e.length>=1||n.length>=2?p="high":(c.length>0||n.length>=1)&&(p="medium");let l=[];return e.length&&l.push("has-errors"),n.length&&l.push("network-failures"),c.length&&l.push("console-errors"),i?.pageLoadTime&&i.pageLoadTime>3e3&&l.push("slow-load"),i?.cumulativeLayoutShift&&i.cumulativeLayoutShift>.25&&l.push("layout-shift"),navigator.onLine||l.push("offline"),window.innerWidth<768&&l.push("mobile"),{severity:p,tags:l,counts:{logs:t.length,network:s.length,failedNet:n.length,errors:e.length,crumbs:r.length}}}function Qs(){let e=Zs(),t=[{id:"ui",label:"UI Bug"},{id:"functionality",label:"Broken Feature"},{id:"performance",label:"Slow / Laggy"},{id:"crash",label:"Crash"},{id:"security",label:"Security"},{id:"other",label:"Other"}];return`<div class="bs-view">
3536
1608
  <form data-bs-form>
3537
1609
  <div class="bs-ctx-bar">
3538
- <div class="bs-ctx-chip${d.counts.logs ? " bs-has" : ""}"><span class="bs-ctx-n">${d.counts.logs}</span><span class="bs-ctx-l">Logs</span></div>
3539
- <div class="bs-ctx-chip${d.counts.failedNet ? " bs-alert" : d.counts.network ? " bs-has" : ""}"><span class="bs-ctx-n">${d.counts.network}</span><span class="bs-ctx-l">Network</span></div>
3540
- <div class="bs-ctx-chip${d.counts.errors ? " bs-alert" : ""}"><span class="bs-ctx-n">${d.counts.errors}</span><span class="bs-ctx-l">Errors</span></div>
3541
- <div class="bs-ctx-chip${d.counts.crumbs ? " bs-has" : ""}"><span class="bs-ctx-n">${d.counts.crumbs}</span><span class="bs-ctx-l">Actions</span></div>
1610
+ <div class="bs-ctx-chip${e.counts.logs?" bs-has":""}"><span class="bs-ctx-n">${e.counts.logs}</span><span class="bs-ctx-l">Logs</span></div>
1611
+ <div class="bs-ctx-chip${e.counts.failedNet?" bs-alert":e.counts.network?" bs-has":""}"><span class="bs-ctx-n">${e.counts.network}</span><span class="bs-ctx-l">Network</span></div>
1612
+ <div class="bs-ctx-chip${e.counts.errors?" bs-alert":""}"><span class="bs-ctx-n">${e.counts.errors}</span><span class="bs-ctx-l">Errors</span></div>
1613
+ <div class="bs-ctx-chip${e.counts.crumbs?" bs-has":""}"><span class="bs-ctx-n">${e.counts.crumbs}</span><span class="bs-ctx-l">Actions</span></div>
3542
1614
  </div>
3543
1615
  <div class="bs-field">
3544
1616
  <div class="bs-field-label">Bug title <span class="bs-field-hint">(required)</span></div>
@@ -3552,21 +1624,21 @@ function tabReport() {
3552
1624
  <div class="bs-field-label">Category</div>
3553
1625
  <input type="hidden" name="category" value="functionality" />
3554
1626
  <div class="bs-cat-row">
3555
- ${categories.map((c) => `<button type="button" class="bs-cat-btn${c.id === "functionality" ? " bs-picked" : ""}" data-cat="${c.id}">${c.label}</button>`).join("")}
1627
+ ${t.map(s=>`<button type="button" class="bs-cat-btn${s.id==="functionality"?" bs-picked":""}" data-cat="${s.id}">${s.label}</button>`).join("")}
3556
1628
  </div>
3557
1629
  </div>
3558
1630
  <div class="bs-field">
3559
- <div class="bs-field-label">Severity <span class="bs-field-hint">(suggested: ${d.severity})</span></div>
3560
- <input type="hidden" name="severity" value="${d.severity}" />
1631
+ <div class="bs-field-label">Severity <span class="bs-field-hint">(suggested: ${e.severity})</span></div>
1632
+ <input type="hidden" name="severity" value="${e.severity}" />
3561
1633
  <div class="bs-sev-row">
3562
- <button type="button" class="bs-sev-btn bs-sev-low${d.severity === "low" ? " bs-picked" : ""}" data-sev="low">Low</button>
3563
- <button type="button" class="bs-sev-btn bs-sev-medium${d.severity === "medium" ? " bs-picked" : ""}" data-sev="medium">Medium</button>
3564
- <button type="button" class="bs-sev-btn bs-sev-high${d.severity === "high" ? " bs-picked" : ""}" data-sev="high">High</button>
3565
- <button type="button" class="bs-sev-btn bs-sev-critical${d.severity === "critical" ? " bs-picked" : ""}" data-sev="critical">Critical</button>
1634
+ <button type="button" class="bs-sev-btn bs-sev-low${e.severity==="low"?" bs-picked":""}" data-sev="low">Low</button>
1635
+ <button type="button" class="bs-sev-btn bs-sev-medium${e.severity==="medium"?" bs-picked":""}" data-sev="medium">Medium</button>
1636
+ <button type="button" class="bs-sev-btn bs-sev-high${e.severity==="high"?" bs-picked":""}" data-sev="high">High</button>
1637
+ <button type="button" class="bs-sev-btn bs-sev-critical${e.severity==="critical"?" bs-picked":""}" data-sev="critical">Critical</button>
3566
1638
  </div>
3567
1639
  </div>
3568
1640
  <div class="bs-shot-area" data-bs-screenshot>
3569
- <div class="bs-shot-icon">${I.cam}</div>
1641
+ <div class="bs-shot-icon">${K.cam}</div>
3570
1642
  <div class="bs-shot-text">
3571
1643
  <div class="bs-shot-title" data-bs-shot-title>Attach screenshot</div>
3572
1644
  <div class="bs-shot-sub" data-bs-shot-sub>Captures & lets you annotate the current view</div>
@@ -3576,1269 +1648,93 @@ function tabReport() {
3576
1648
  <button type="submit" class="bs-submit-btn">Submit Report</button>
3577
1649
  <div data-bs-msg></div>
3578
1650
  </form>
3579
- </div>`;
3580
- }
3581
- function tabConsole() {
3582
- const logs2 = getLogs();
3583
- const lvMap = { error: "bs-le", warn: "bs-lw", log: "bs-ll", info: "bs-li", debug: "bs-ld" };
3584
- if (!logs2.length) {
3585
- return `<div class="bs-view"><div class="bs-empty"><p>No console logs captured yet.<br>Use the app \u2014 logs will appear here automatically.</p></div></div>`;
3586
- }
3587
- return `<div class="bs-view">${logs2.slice().reverse().map(
3588
- (l) => `<div class="bs-log"><span class="bs-log-lv ${lvMap[l.level] || "bs-ll"}">${l.level}</span><span class="bs-log-m">${esc(l.args.join(" ")).slice(0, 500)}</span><span class="bs-log-t">${fmtTime(l.timestamp)}</span></div>`
3589
- ).join("")}</div>`;
3590
- }
3591
- function tabNetwork() {
3592
- const caps = getNetworkCaptures();
3593
- if (!caps.length) {
3594
- return `<div class="bs-view"><div class="bs-empty"><p>No network requests captured yet.<br>API calls will show up here automatically.</p></div></div>`;
3595
- }
3596
- return `<div class="bs-view">${caps.slice().reverse().map(
3597
- (n) => `<div class="bs-net"><span class="bs-net-m">${n.method}</span><span class="bs-net-s ${n.failed ? "bs-fail" : "bs-ok"}">${n.status || "ERR"}</span><span class="bs-net-u" title="${esc(n.url)}">${esc(n.url)}</span><span class="bs-net-d">${n.duration}ms</span></div>`
3598
- ).join("")}</div>`;
3599
- }
3600
- function tabContext() {
3601
- let html = `<div class="bs-view">`;
3602
- const errs = getErrors();
3603
- if (errs.length) {
3604
- html += `<div class="bs-sec">Errors <span class="bs-sec-n">${errs.length}</span></div>`;
3605
- html += errs.slice().reverse().map(
3606
- (e) => `<div class="bs-err-card"><div class="bs-err-m">${esc(e.message)}</div>${e.stack ? `<div class="bs-err-stack">${esc(e.stack)}</div>` : ""}<div class="bs-err-meta">${e.type} &middot; ${fmtTime(e.timestamp)}${e.source ? ` &middot; ${esc(e.source)}:${e.lineno}` : ""}</div></div>`
3607
- ).join("");
3608
- }
3609
- const perf = getPerformanceMetrics();
3610
- if (perf) {
3611
- html += `<div class="bs-sec">Performance</div>`;
3612
- const bars = [
3613
- ["Page Load", perf.pageLoadTime, 5e3],
3614
- ["DOM Ready", perf.domContentLoaded, 3e3],
3615
- ["First Paint", perf.firstPaint, 2e3],
3616
- ["FCP", perf.firstContentfulPaint, 2500],
3617
- ["LCP", perf.largestContentfulPaint, 4e3],
3618
- ["FID", perf.firstInputDelay, 300]
3619
- ];
3620
- for (const [label, val, max] of bars) {
3621
- if (val === void 0) continue;
3622
- const pct = Math.min(100, val / max * 100);
3623
- html += `<div class="bs-pf"><span class="bs-pf-l">${label}</span><div class="bs-pf-tr"><div class="bs-pf-fl${pct > 75 ? " bs-slow" : ""}" style="width:${pct}%"></div></div><span class="bs-pf-v">${val}ms</span></div>`;
3624
- }
3625
- if (perf.cumulativeLayoutShift !== void 0) {
3626
- const p = Math.min(100, perf.cumulativeLayoutShift * 400);
3627
- html += `<div class="bs-pf"><span class="bs-pf-l">CLS</span><div class="bs-pf-tr"><div class="bs-pf-fl${p > 40 ? " bs-slow" : ""}" style="width:${p}%"></div></div><span class="bs-pf-v">${perf.cumulativeLayoutShift}</span></div>`;
3628
- }
3629
- }
3630
- const crumbs2 = getBreadcrumbs();
3631
- if (crumbs2.length) {
3632
- html += `<div class="bs-sec">Your activity trail <span class="bs-sec-n">${crumbs2.length}</span></div>`;
3633
- html += crumbs2.slice().reverse().map(
3634
- (b) => `<div class="bs-bc"><span class="bs-bc-t bs-t-${b.type}">${b.type}</span><span class="bs-bc-m">${esc(b.message).slice(0, 120)}</span><span class="bs-bc-time">${timeAgo2(b.timestamp)}</span></div>`
3635
- ).join("");
3636
- }
3637
- const ctx2 = buildContext();
3638
- html += `<div class="bs-sec">Environment</div>`;
3639
- html += `<div class="bs-kv">
3640
- <span class="bs-kv-k">URL</span><span class="bs-kv-v">${esc(ctx2.url)}</span>
3641
- <span class="bs-kv-k">Viewport</span><span class="bs-kv-v">${ctx2.viewportWidth}&times;${ctx2.viewportHeight} @${ctx2.devicePixelRatio}x</span>
3642
- <span class="bs-kv-k">Screen</span><span class="bs-kv-v">${ctx2.screenWidth}&times;${ctx2.screenHeight}</span>
3643
- <span class="bs-kv-k">Platform</span><span class="bs-kv-v">${esc(ctx2.platform)}</span>
3644
- <span class="bs-kv-k">Language</span><span class="bs-kv-v">${ctx2.language}</span>
3645
- ${config.commitHash ? `<span class="bs-kv-k">Commit</span><span class="bs-kv-v">${esc(config.commitHash)}</span>` : ""}
3646
- ${config.user?.email ? `<span class="bs-kv-k">User</span><span class="bs-kv-v">${esc(config.user.email)}</span>` : ""}
3647
- </div>`;
3648
- html += `</div>`;
3649
- return html;
3650
- }
3651
- function tabHistory() {
3652
- const entries = getReportHistory();
3653
- if (!entries.length) {
3654
- return `<div class="bs-view"><div class="bs-empty"><p>No reports yet.<br>Submitted bugs will appear here.</p></div></div>`;
3655
- }
3656
- const catLabels = { ui: "UI", functionality: "Feature", performance: "Perf", crash: "Crash", security: "Security", other: "Other" };
3657
- const fmtDate = (ts) => {
3658
- const d = new Date(ts);
3659
- const now = /* @__PURE__ */ new Date();
3660
- const diffMs = now.getTime() - d.getTime();
3661
- const diffMin = Math.floor(diffMs / 6e4);
3662
- if (diffMin < 1) return "Just now";
3663
- if (diffMin < 60) return `${diffMin}m ago`;
3664
- const diffH = Math.floor(diffMin / 60);
3665
- if (diffH < 24) return `${diffH}h ago`;
3666
- const diffD = Math.floor(diffH / 24);
3667
- if (diffD < 7) return `${diffD}d ago`;
3668
- return d.toLocaleDateString("en", { month: "short", day: "numeric" });
3669
- };
3670
- let html = `<div class="bs-view">`;
3671
- for (const e of entries) {
3672
- const pinsHtml = e.pins?.length ? `<div class="bs-hist-pins">${e.pins.slice(0, 5).map((p) => `<div class="bs-hist-pin-dot" style="background:${["#ef4444", "#3b82f6", "#f59e0b", "#10b981", "#8b5cf6"][p.number % 5]}">${p.number}</div>`).join("")}${e.pins.length > 5 ? `<span>+${e.pins.length - 5}</span>` : ""}</div>` : "";
3673
- html += `<div class="bs-hist-card" data-hist-id="${e.id}">
1651
+ </div>`}function Jn(){let e=ee(),t={error:"bs-le",warn:"bs-lw",log:"bs-ll",info:"bs-li",debug:"bs-ld"};return e.length?`<div class="bs-view">${e.slice().reverse().map(s=>`<div class="bs-log"><span class="bs-log-lv ${t[s.level]||"bs-ll"}">${s.level}</span><span class="bs-log-m">${H(s.args.join(" ")).slice(0,500)}</span><span class="bs-log-t">${Xs(s.timestamp)}</span></div>`).join("")}</div>`:'<div class="bs-view"><div class="bs-empty"><p>No console logs captured yet.<br>Use the app \u2014 logs will appear here automatically.</p></div></div>'}function Xn(){let e=ve();return e.length?`<div class="bs-view">${e.slice().reverse().map(t=>`<div class="bs-net"><span class="bs-net-m">${t.method}</span><span class="bs-net-s ${t.failed?"bs-fail":"bs-ok"}">${t.status||"ERR"}</span><span class="bs-net-u" title="${H(t.url)}">${H(t.url)}</span><span class="bs-net-d">${t.duration}ms</span></div>`).join("")}</div>`:'<div class="bs-view"><div class="bs-empty"><p>No network requests captured yet.<br>API calls will show up here automatically.</p></div></div>'}function Vn(){let e='<div class="bs-view">',t=te();t.length&&(e+=`<div class="bs-sec">Errors <span class="bs-sec-n">${t.length}</span></div>`,e+=t.slice().reverse().map(i=>`<div class="bs-err-card"><div class="bs-err-m">${H(i.message)}</div>${i.stack?`<div class="bs-err-stack">${H(i.stack)}</div>`:""}<div class="bs-err-meta">${i.type} &middot; ${Xs(i.timestamp)}${i.source?` &middot; ${H(i.source)}:${i.lineno}`:""}</div></div>`).join(""));let s=ye();if(s){e+='<div class="bs-sec">Performance</div>';let i=[["Page Load",s.pageLoadTime,5e3],["DOM Ready",s.domContentLoaded,3e3],["First Paint",s.firstPaint,2e3],["FCP",s.firstContentfulPaint,2500],["LCP",s.largestContentfulPaint,4e3],["FID",s.firstInputDelay,300]];for(let[c,p,l]of i){if(p===void 0)continue;let h=Math.min(100,p/l*100);e+=`<div class="bs-pf"><span class="bs-pf-l">${c}</span><div class="bs-pf-tr"><div class="bs-pf-fl${h>75?" bs-slow":""}" style="width:${h}%"></div></div><span class="bs-pf-v">${p}ms</span></div>`}if(s.cumulativeLayoutShift!==void 0){let c=Math.min(100,s.cumulativeLayoutShift*400);e+=`<div class="bs-pf"><span class="bs-pf-l">CLS</span><div class="bs-pf-tr"><div class="bs-pf-fl${c>40?" bs-slow":""}" style="width:${c}%"></div></div><span class="bs-pf-v">${s.cumulativeLayoutShift}</span></div>`}}let n=me();n.length&&(e+=`<div class="bs-sec">Your activity trail <span class="bs-sec-n">${n.length}</span></div>`,e+=n.slice().reverse().map(i=>`<div class="bs-bc"><span class="bs-bc-t bs-t-${i.type}">${i.type}</span><span class="bs-bc-m">${H(i.message).slice(0,120)}</span><span class="bs-bc-time">${Wn(i.timestamp)}</span></div>`).join(""));let r=Ks();return e+='<div class="bs-sec">Environment</div>',e+=`<div class="bs-kv">
1652
+ <span class="bs-kv-k">URL</span><span class="bs-kv-v">${H(r.url)}</span>
1653
+ <span class="bs-kv-k">Viewport</span><span class="bs-kv-v">${r.viewportWidth}&times;${r.viewportHeight} @${r.devicePixelRatio}x</span>
1654
+ <span class="bs-kv-k">Screen</span><span class="bs-kv-v">${r.screenWidth}&times;${r.screenHeight}</span>
1655
+ <span class="bs-kv-k">Platform</span><span class="bs-kv-v">${H(r.platform)}</span>
1656
+ <span class="bs-kv-k">Language</span><span class="bs-kv-v">${r.language}</span>
1657
+ ${Y.commitHash?`<span class="bs-kv-k">Commit</span><span class="bs-kv-v">${H(Y.commitHash)}</span>`:""}
1658
+ ${Y.user?.email?`<span class="bs-kv-k">User</span><span class="bs-kv-v">${H(Y.user.email)}</span>`:""}
1659
+ </div>`,e+="</div>",e}function Kn(){let e=Se();if(!e.length)return'<div class="bs-view"><div class="bs-empty"><p>No reports yet.<br>Submitted bugs will appear here.</p></div></div>';let t={ui:"UI",functionality:"Feature",performance:"Perf",crash:"Crash",security:"Security",other:"Other"},s=r=>{let i=new Date(r),p=new Date().getTime()-i.getTime(),l=Math.floor(p/6e4);if(l<1)return"Just now";if(l<60)return`${l}m ago`;let h=Math.floor(l/60);if(h<24)return`${h}h ago`;let g=Math.floor(h/24);return g<7?`${g}d ago`:i.toLocaleDateString("en",{month:"short",day:"numeric"})},n='<div class="bs-view">';for(let r of e){let i=r.pins?.length?`<div class="bs-hist-pins">${r.pins.slice(0,5).map(c=>`<div class="bs-hist-pin-dot" style="background:${["#ef4444","#3b82f6","#f59e0b","#10b981","#8b5cf6"][c.number%5]}">${c.number}</div>`).join("")}${r.pins.length>5?`<span>+${r.pins.length-5}</span>`:""}</div>`:"";n+=`<div class="bs-hist-card" data-hist-id="${r.id}">
3674
1660
  <div class="bs-hist-top">
3675
- <div class="bs-hist-title">${esc(e.title)}</div>
3676
- ${e.screenshot ? `<img class="bs-hist-thumb" src="${e.screenshot}" alt=""/>` : ""}
1661
+ <div class="bs-hist-title">${H(r.title)}</div>
1662
+ ${r.screenshot?`<img class="bs-hist-thumb" src="${r.screenshot}" alt=""/>`:""}
3677
1663
  <div class="bs-hist-actions">
3678
- <button class="bs-hist-btn" data-hist-edit="${e.id}" title="Edit"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg></button>
3679
- <button class="bs-hist-btn bs-del" data-hist-del="${e.id}" title="Delete"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg></button>
1664
+ <button class="bs-hist-btn" data-hist-edit="${r.id}" title="Edit"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg></button>
1665
+ <button class="bs-hist-btn bs-del" data-hist-del="${r.id}" title="Delete"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/></svg></button>
3680
1666
  </div>
3681
1667
  </div>
3682
1668
  <div class="bs-hist-meta">
3683
- <span class="bs-hist-badge bs-cat">${catLabels[e.category] || e.category}</span>
3684
- <span class="bs-hist-badge bs-sev-${e.severity}">${e.severity}</span>
3685
- <span class="bs-hist-badge bs-status-${e.status}">${e.status}</span>
1669
+ <span class="bs-hist-badge bs-cat">${t[r.category]||r.category}</span>
1670
+ <span class="bs-hist-badge bs-sev-${r.severity}">${r.severity}</span>
1671
+ <span class="bs-hist-badge bs-status-${r.status}">${r.status}</span>
3686
1672
  </div>
3687
- ${e.description ? `<div class="bs-hist-desc">${esc(e.description)}</div>` : ""}
1673
+ ${r.description?`<div class="bs-hist-desc">${H(r.description)}</div>`:""}
3688
1674
  <div class="bs-hist-foot">
3689
- <span>${fmtDate(e.createdAt)} &middot; ${esc(e.url.replace(/^https?:\/\//, "").slice(0, 40))}</span>
3690
- ${pinsHtml}
1675
+ <span>${s(r.createdAt)} &middot; ${H(r.url.replace(/^https?:\/\//,"").slice(0,40))}</span>
1676
+ ${i}
3691
1677
  </div>
3692
- </div>`;
3693
- }
3694
- html += `</div>`;
3695
- return html;
3696
- }
3697
- function tabSettings() {
3698
- const themes = getThemes();
3699
- const layouts = getLayouts();
3700
- let html = `<div class="bs-view">`;
3701
- html += `<div class="bs-set-sec">Layout</div>`;
3702
- html += `<div class="bs-set-ly-grid">`;
3703
- for (const l of layouts) {
3704
- html += `<button class="bs-set-ly${l.id === currentLayout.id ? " bs-picked" : ""}" data-set-layout="${l.id}"><div class="bs-set-ly-name">${esc(l.name)}</div><div class="bs-set-ly-desc">${esc(l.description)}</div></button>`;
3705
- }
3706
- html += `</div>`;
3707
- html += `<div class="bs-set-sec">Theme</div>`;
3708
- html += `<div class="bs-set-grid">`;
3709
- for (const t of themes) {
3710
- html += `<button class="bs-set-card${t.id === currentTheme.id ? " bs-picked" : ""}" data-set-theme="${t.id}"><div class="bs-set-preview" style="background:${t.preview[0]}"><div class="bs-set-dot" style="background:${t.preview[1]}"></div><div class="bs-set-dot" style="background:${t.vars["--bs-accent2"] || t.preview[1]};opacity:0.6"></div></div><div class="bs-set-name">${esc(t.name)}</div></button>`;
3711
- }
3712
- html += `</div>`;
3713
- html += `</div>`;
3714
- return html;
3715
- }
3716
- function setupAnnotation(container2, screenshotData) {
3717
- const COLORS2 = ["#f87171", "#3b82f6", "#fb923c", "#4ade80", "#a78bfa", "#facc15"];
3718
- let currentColor2 = COLORS2[0];
3719
- let currentTool2 = "draw";
3720
- let brushSize = 4;
3721
- let zoom = 1;
3722
- let isDark = false;
3723
- const shapes2 = [];
3724
- const pins2 = [];
3725
- let selectedIdx = -1;
3726
- let hoveredIdx = -1;
3727
- let dragging = false;
3728
- let drawing = false;
3729
- let dragOffX = 0, dragOffY = 0;
3730
- let startX = 0, startY = 0;
3731
- let currentDraw = [];
3732
- const PIN_COLORS2 = ["#ef4444", "#3b82f6", "#f59e0b", "#10b981", "#8b5cf6", "#ec4899"];
3733
- const img = new Image();
3734
- img.src = screenshotData;
3735
- const wrap = document.createElement("div");
3736
- wrap.className = "bs-ann-wrap";
3737
- const viewport = document.createElement("div");
3738
- viewport.className = "bs-ann-viewport";
3739
- const canvas2 = document.createElement("canvas");
3740
- canvas2.className = "bs-ann-canvas bs-draw";
3741
- const toolbar = document.createElement("div");
3742
- toolbar.className = "bs-ann-toolbar";
3743
- const tIcons = {
3744
- select: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"/><path d="M13 13l6 6"/></svg>`,
3745
- draw: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/></svg>`,
3746
- arrow: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>`,
3747
- rect: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>`,
3748
- circle: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/></svg>`,
3749
- text: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9.5" y1="20" x2="14.5" y2="20"/><line x1="12" y1="4" x2="12" y2="20"/></svg>`,
3750
- highlight: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M9 11l-6 6v3h9l3-3"/><path d="M22 12l-4.6 4.6a2 2 0 01-2.8 0l-5.2-5.2a2 2 0 010-2.8L14 4"/></svg>`,
3751
- pin: `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z"/><circle cx="12" cy="10" r="3"/></svg>`
3752
- };
3753
- const toolNames = { select: "Select & Move", draw: "Draw", arrow: "Arrow", rect: "Rectangle", circle: "Circle", text: "Text", highlight: "Highlight", pin: "Pin Issue" };
3754
- const toolBtns = [];
3755
- const setTool = (t) => {
3756
- currentTool2 = t;
3757
- selectedIdx = -1;
3758
- canvas2.className = `bs-ann-canvas bs-${t}`;
3759
- toolBtns.forEach((b) => b.classList.remove("bs-sel"));
3760
- toolBtns.find((b) => b.dataset.tool === t)?.classList.add("bs-sel");
3761
- };
3762
- Object.keys(tIcons).forEach((t) => {
3763
- const btn = document.createElement("button");
3764
- btn.type = "button";
3765
- btn.dataset.tool = t;
3766
- btn.className = `bs-ann-btn${t === currentTool2 ? " bs-sel" : ""}`;
3767
- btn.title = toolNames[t];
3768
- btn.innerHTML = tIcons[t];
3769
- btn.addEventListener("click", () => setTool(t));
3770
- toolBtns.push(btn);
3771
- toolbar.appendChild(btn);
3772
- });
3773
- toolbar.appendChild(Object.assign(document.createElement("div"), { className: "bs-ann-sep" }));
3774
- COLORS2.forEach((color, i) => {
3775
- const d = document.createElement("button");
3776
- d.type = "button";
3777
- d.className = `bs-ann-dot${i === 0 ? " bs-sel" : ""}`;
3778
- d.style.background = color;
3779
- d.addEventListener("click", () => {
3780
- currentColor2 = color;
3781
- toolbar.querySelectorAll(".bs-ann-dot").forEach((t) => t.classList.remove("bs-sel"));
3782
- d.classList.add("bs-sel");
3783
- });
3784
- toolbar.appendChild(d);
3785
- });
3786
- toolbar.appendChild(Object.assign(document.createElement("div"), { className: "bs-ann-sep" }));
3787
- const sizeSlider = document.createElement("input");
3788
- sizeSlider.type = "range";
3789
- sizeSlider.min = "1";
3790
- sizeSlider.max = "12";
3791
- sizeSlider.value = "4";
3792
- sizeSlider.className = "bs-ann-size";
3793
- sizeSlider.title = "Brush size";
3794
- sizeSlider.addEventListener("input", () => {
3795
- brushSize = parseInt(sizeSlider.value);
3796
- });
3797
- toolbar.appendChild(sizeSlider);
3798
- const right = document.createElement("div");
3799
- right.className = "bs-ann-right";
3800
- const mkBtn = (title, svg) => {
3801
- const b = document.createElement("button");
3802
- b.type = "button";
3803
- b.className = "bs-ann-btn";
3804
- b.title = title;
3805
- b.innerHTML = svg;
3806
- return b;
3807
- };
3808
- const zoomOut = mkBtn("Zoom out", `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="8" y1="11" x2="14" y2="11"/></svg>`);
3809
- const zoomLabel = Object.assign(document.createElement("span"), { className: "bs-ann-zoom-label", textContent: "100%" });
3810
- const zoomIn = mkBtn("Zoom in", `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>`);
3811
- const zoomReset = mkBtn("Fit", `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M15 3h6v6"/><path d="M9 21H3v-6"/><path d="M21 3l-7 7"/><path d="M3 21l7-7"/></svg>`);
3812
- const undoBtn = mkBtn("Undo", `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 105.42-8.49L1 10"/></svg>`);
3813
- const clearBtn = Object.assign(document.createElement("button"), { type: "button", className: "bs-ann-clr-btn", textContent: "Clear" });
3814
- const applyZoom = () => {
3815
- canvas2.style.transform = `scale(${zoom})`;
3816
- zoomLabel.textContent = `${Math.round(zoom * 100)}%`;
3817
- };
3818
- zoomIn.addEventListener("click", () => {
3819
- if (zoom < 3) {
3820
- zoom = Math.min(3, zoom + 0.25);
3821
- applyZoom();
3822
- }
3823
- });
3824
- zoomOut.addEventListener("click", () => {
3825
- if (zoom > 0.5) {
3826
- zoom = Math.max(0.5, zoom - 0.25);
3827
- applyZoom();
3828
- }
3829
- });
3830
- zoomReset.addEventListener("click", () => {
3831
- zoom = 1;
3832
- applyZoom();
3833
- });
3834
- right.appendChild(Object.assign(document.createElement("div"), { className: "bs-ann-sep" }));
3835
- [zoomOut, zoomLabel, zoomIn, zoomReset, undoBtn, clearBtn].forEach((el) => right.appendChild(el));
3836
- toolbar.appendChild(right);
3837
- const pinLayer = document.createElement("div");
3838
- pinLayer.className = "bs-ann-pins";
3839
- const pinList = document.createElement("div");
3840
- pinList.className = "bs-ann-pin-list";
3841
- pinList.style.display = "none";
3842
- viewport.appendChild(canvas2);
3843
- viewport.appendChild(pinLayer);
3844
- wrap.appendChild(viewport);
3845
- wrap.appendChild(toolbar);
3846
- wrap.appendChild(pinList);
3847
- container2.innerHTML = "";
3848
- container2.appendChild(wrap);
3849
- const renderPins2 = () => {
3850
- pinLayer.innerHTML = "";
3851
- pinList.innerHTML = "";
3852
- pinList.style.display = pins2.length ? "" : "none";
3853
- pins2.forEach((pin, i) => {
3854
- const el = document.createElement("div");
3855
- el.className = "bs-ann-pin";
3856
- el.style.background = pin.color;
3857
- el.style.color = pin.color;
3858
- const pctX = pin.x / (canvas2.width || 1) * 100;
3859
- const pctY = pin.y / (canvas2.height || 1) * 100;
3860
- el.style.left = `${pctX}%`;
3861
- el.style.top = `${pctY}%`;
3862
- el.innerHTML = `<span class="bs-ann-pin-n">${i + 1}</span><span class="bs-ann-pin-pulse"></span>`;
3863
- el.title = `#${i + 1}: ${pin.note}`;
3864
- let pinDragging = false;
3865
- el.addEventListener("mousedown", (e) => {
3866
- e.stopPropagation();
3867
- pinDragging = true;
3868
- el.classList.add("bs-dragging");
3869
- const onMove = (me) => {
3870
- if (!pinDragging) return;
3871
- const r = viewport.getBoundingClientRect();
3872
- pin.x = (me.clientX - r.left) / r.width * canvas2.width;
3873
- pin.y = (me.clientY - r.top) / r.height * canvas2.height;
3874
- el.style.left = `${(me.clientX - r.left) / r.width * 100}%`;
3875
- el.style.top = `${(me.clientY - r.top) / r.height * 100}%`;
3876
- };
3877
- const onUp = () => {
3878
- pinDragging = false;
3879
- el.classList.remove("bs-dragging");
3880
- document.removeEventListener("mousemove", onMove);
3881
- document.removeEventListener("mouseup", onUp);
3882
- renderPins2();
3883
- };
3884
- document.addEventListener("mousemove", onMove);
3885
- document.addEventListener("mouseup", onUp);
3886
- });
3887
- pinLayer.appendChild(el);
3888
- const item = document.createElement("div");
3889
- item.className = "bs-ann-pin-item";
3890
- item.innerHTML = `
3891
- <div class="bs-ann-pin-num" style="background:${pin.color}">${i + 1}</div>
1678
+ </div>`}return n+="</div>",n}function Zn(){let e=bt(),t=ut(),s='<div class="bs-view">';s+='<div class="bs-set-sec">Layout</div>',s+='<div class="bs-set-ly-grid">';for(let n of t)s+=`<button class="bs-set-ly${n.id===ue.id?" bs-picked":""}" data-set-layout="${n.id}"><div class="bs-set-ly-name">${H(n.name)}</div><div class="bs-set-ly-desc">${H(n.description)}</div></button>`;s+="</div>",s+='<div class="bs-set-sec">Theme</div>',s+='<div class="bs-set-grid">';for(let n of e)s+=`<button class="bs-set-card${n.id===Et.id?" bs-picked":""}" data-set-theme="${n.id}"><div class="bs-set-preview" style="background:${n.preview[0]}"><div class="bs-set-dot" style="background:${n.preview[1]}"></div><div class="bs-set-dot" style="background:${n.vars["--bs-accent2"]||n.preview[1]};opacity:0.6"></div></div><div class="bs-set-name">${H(n.name)}</div></button>`;return s+="</div>",s+="</div>",s}function Qn(e,t){let s=["#f87171","#3b82f6","#fb923c","#4ade80","#a78bfa","#facc15"],n=s[0],r="draw",i=4,c=1,p=!1,l=[],h=[],g=-1,x=-1,y=!1,$=!1,ae=0,Te=0,N=0,P=0,k=[],Xt=["#ef4444","#3b82f6","#f59e0b","#10b981","#8b5cf6","#ec4899"],ie=new Image;ie.src=t;let Me=document.createElement("div");Me.className="bs-ann-wrap";let $e=document.createElement("div");$e.className="bs-ann-viewport";let w=document.createElement("canvas");w.className="bs-ann-canvas bs-draw";let Q=document.createElement("div");Q.className="bs-ann-toolbar";let Vt={select:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M3 3l7.07 16.97 2.51-7.39 7.39-2.51L3 3z"/><path d="M13 13l6 6"/></svg>',draw:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/></svg>',arrow:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="5" y1="12" x2="19" y2="12"/><polyline points="12 5 19 12 12 19"/></svg>',rect:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/></svg>',circle:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/></svg>',text:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><polyline points="4 7 4 4 20 4 20 7"/><line x1="9.5" y1="20" x2="14.5" y2="20"/><line x1="12" y1="4" x2="12" y2="20"/></svg>',highlight:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M9 11l-6 6v3h9l3-3"/><path d="M22 12l-4.6 4.6a2 2 0 01-2.8 0l-5.2-5.2a2 2 0 010-2.8L14 4"/></svg>',pin:'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z"/><circle cx="12" cy="10" r="3"/></svg>'},bn={select:"Select & Move",draw:"Draw",arrow:"Arrow",rect:"Rectangle",circle:"Circle",text:"Text",highlight:"Highlight",pin:"Pin Issue"},$t=[],pn=o=>{r=o,g=-1,w.className=`bs-ann-canvas bs-${o}`,$t.forEach(u=>u.classList.remove("bs-sel")),$t.find(u=>u.dataset.tool===o)?.classList.add("bs-sel")};Object.keys(Vt).forEach(o=>{let u=document.createElement("button");u.type="button",u.dataset.tool=o,u.className=`bs-ann-btn${o===r?" bs-sel":""}`,u.title=bn[o],u.innerHTML=Vt[o],u.addEventListener("click",()=>pn(o)),$t.push(u),Q.appendChild(u)}),Q.appendChild(Object.assign(document.createElement("div"),{className:"bs-ann-sep"})),s.forEach((o,u)=>{let f=document.createElement("button");f.type="button",f.className=`bs-ann-dot${u===0?" bs-sel":""}`,f.style.background=o,f.addEventListener("click",()=>{n=o,Q.querySelectorAll(".bs-ann-dot").forEach(C=>C.classList.remove("bs-sel")),f.classList.add("bs-sel")}),Q.appendChild(f)}),Q.appendChild(Object.assign(document.createElement("div"),{className:"bs-ann-sep"}));let G=document.createElement("input");G.type="range",G.min="1",G.max="12",G.value="4",G.className="bs-ann-size",G.title="Brush size",G.addEventListener("input",()=>{i=parseInt(G.value)}),Q.appendChild(G);let Ye=document.createElement("div");Ye.className="bs-ann-right";let Je=(o,u)=>{let f=document.createElement("button");return f.type="button",f.className="bs-ann-btn",f.title=o,f.innerHTML=u,f},Kt=Je("Zoom out",'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="8" y1="11" x2="14" y2="11"/></svg>'),Zt=Object.assign(document.createElement("span"),{className:"bs-ann-zoom-label",textContent:"100%"}),Qt=Je("Zoom in",'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/><line x1="11" y1="8" x2="11" y2="14"/><line x1="8" y1="11" x2="14" y2="11"/></svg>'),Gt=Je("Fit",'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M15 3h6v6"/><path d="M9 21H3v-6"/><path d="M21 3l-7 7"/><path d="M3 21l7-7"/></svg>'),es=Je("Undo",'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><polyline points="1 4 1 10 7 10"/><path d="M3.51 15a9 9 0 105.42-8.49L1 10"/></svg>'),ts=Object.assign(document.createElement("button"),{type:"button",className:"bs-ann-clr-btn",textContent:"Clear"}),Pt=()=>{w.style.transform=`scale(${c})`,Zt.textContent=`${Math.round(c*100)}%`};Qt.addEventListener("click",()=>{c<3&&(c=Math.min(3,c+.25),Pt())}),Kt.addEventListener("click",()=>{c>.5&&(c=Math.max(.5,c-.25),Pt())}),Gt.addEventListener("click",()=>{c=1,Pt()}),Ye.appendChild(Object.assign(document.createElement("div"),{className:"bs-ann-sep"})),[Kt,Zt,Qt,Gt,es,ts].forEach(o=>Ye.appendChild(o)),Q.appendChild(Ye);let Xe=document.createElement("div");Xe.className="bs-ann-pins";let ge=document.createElement("div");ge.className="bs-ann-pin-list",ge.style.display="none",$e.appendChild(w),$e.appendChild(Xe),Me.appendChild($e),Me.appendChild(Q),Me.appendChild(ge),e.innerHTML="",e.appendChild(Me);let Ve=()=>{Xe.innerHTML="",ge.innerHTML="",ge.style.display=h.length?"":"none",h.forEach((o,u)=>{let f=document.createElement("div");f.className="bs-ann-pin",f.style.background=o.color,f.style.color=o.color;let C=o.x/(w.width||1)*100,b=o.y/(w.height||1)*100;f.style.left=`${C}%`,f.style.top=`${b}%`,f.innerHTML=`<span class="bs-ann-pin-n">${u+1}</span><span class="bs-ann-pin-pulse"></span>`,f.title=`#${u+1}: ${o.note}`;let T=!1;f.addEventListener("mousedown",z=>{z.stopPropagation(),T=!0,f.classList.add("bs-dragging");let d=L=>{if(!T)return;let S=$e.getBoundingClientRect();o.x=(L.clientX-S.left)/S.width*w.width,o.y=(L.clientY-S.top)/S.height*w.height,f.style.left=`${(L.clientX-S.left)/S.width*100}%`,f.style.top=`${(L.clientY-S.top)/S.height*100}%`},v=()=>{T=!1,f.classList.remove("bs-dragging"),document.removeEventListener("mousemove",d),document.removeEventListener("mouseup",v),Ve()};document.addEventListener("mousemove",d),document.addEventListener("mouseup",v)}),Xe.appendChild(f);let B=document.createElement("div");B.className="bs-ann-pin-item",B.innerHTML=`
1679
+ <div class="bs-ann-pin-num" style="background:${o.color}">${u+1}</div>
3892
1680
  <div class="bs-ann-pin-body">
3893
- <div class="bs-ann-pin-note">${esc(pin.note)}</div>
3894
- <div class="bs-ann-pin-loc">${Math.round(pctX)}% \xD7 ${Math.round(pctY)}%</div>
3895
- </div>`;
3896
- const del = document.createElement("button");
3897
- del.type = "button";
3898
- del.className = "bs-ann-pin-del";
3899
- del.innerHTML = "&times;";
3900
- del.title = "Remove pin";
3901
- del.addEventListener("click", () => {
3902
- pins2.splice(i, 1);
3903
- renderPins2();
3904
- });
3905
- item.appendChild(del);
3906
- pinList.appendChild(item);
3907
- });
3908
- };
3909
- let ctx2;
3910
- const outlineColor = () => isDark ? "rgba(255,255,255,0.5)" : "rgba(0,0,0,0.35)";
3911
- const drawOutline = (fn, lw) => {
3912
- ctx2.save();
3913
- ctx2.strokeStyle = outlineColor();
3914
- ctx2.lineWidth = lw + 3;
3915
- ctx2.lineCap = "round";
3916
- ctx2.lineJoin = "round";
3917
- ctx2.globalAlpha = 0.5;
3918
- fn();
3919
- ctx2.restore();
3920
- };
3921
- const renderShape = (s, preview = false) => {
3922
- if (s.type === "draw" || s.type === "highlight") {
3923
- if (s.points.length < 2) return;
3924
- const alpha = s.type === "highlight" ? 0.25 : 1;
3925
- const lw = s.type === "highlight" ? s.size * 4 + 10 : s.size;
3926
- const stroke = () => {
3927
- ctx2.beginPath();
3928
- ctx2.moveTo(s.points[0].x, s.points[0].y);
3929
- for (let i = 1; i < s.points.length; i++) ctx2.lineTo(s.points[i].x, s.points[i].y);
3930
- ctx2.stroke();
3931
- };
3932
- if (s.type !== "highlight") drawOutline(stroke, lw);
3933
- ctx2.strokeStyle = s.color;
3934
- ctx2.lineWidth = lw;
3935
- ctx2.lineCap = "round";
3936
- ctx2.lineJoin = "round";
3937
- ctx2.globalAlpha = alpha;
3938
- stroke();
3939
- ctx2.globalAlpha = 1;
3940
- } else if (s.type === "rect") {
3941
- const stroke = () => ctx2.strokeRect(s.x, s.y, s.w, s.h);
3942
- drawOutline(stroke, s.size);
3943
- ctx2.strokeStyle = s.color;
3944
- ctx2.lineWidth = s.size;
3945
- ctx2.lineCap = "round";
3946
- ctx2.lineJoin = "round";
3947
- stroke();
3948
- } else if (s.type === "circle") {
3949
- const stroke = () => {
3950
- ctx2.beginPath();
3951
- ctx2.ellipse(s.cx, s.cy, Math.abs(s.rx), Math.abs(s.ry), 0, 0, Math.PI * 2);
3952
- ctx2.stroke();
3953
- };
3954
- drawOutline(stroke, s.size);
3955
- ctx2.strokeStyle = s.color;
3956
- ctx2.lineWidth = s.size;
3957
- stroke();
3958
- } else if (s.type === "arrow") {
3959
- const angle = Math.atan2(s.y2 - s.y1, s.x2 - s.x1);
3960
- const headLen = 12 + s.size * 2;
3961
- const stroke = () => {
3962
- ctx2.beginPath();
3963
- ctx2.moveTo(s.x1, s.y1);
3964
- ctx2.lineTo(s.x2, s.y2);
3965
- ctx2.stroke();
3966
- ctx2.beginPath();
3967
- ctx2.moveTo(s.x2, s.y2);
3968
- ctx2.lineTo(s.x2 - headLen * Math.cos(angle - 0.45), s.y2 - headLen * Math.sin(angle - 0.45));
3969
- ctx2.moveTo(s.x2, s.y2);
3970
- ctx2.lineTo(s.x2 - headLen * Math.cos(angle + 0.45), s.y2 - headLen * Math.sin(angle + 0.45));
3971
- ctx2.stroke();
3972
- };
3973
- drawOutline(stroke, s.size);
3974
- ctx2.strokeStyle = s.color;
3975
- ctx2.lineWidth = s.size;
3976
- ctx2.lineCap = "round";
3977
- ctx2.lineJoin = "round";
3978
- stroke();
3979
- } else if (s.type === "text") {
3980
- const fontSize = s.size * 5 + 12;
3981
- ctx2.font = `bold ${fontSize}px Inter, -apple-system, sans-serif`;
3982
- ctx2.fillStyle = outlineColor();
3983
- ctx2.globalAlpha = 0.6;
3984
- ctx2.fillText(s.text, s.x + 1, s.y + 1);
3985
- ctx2.globalAlpha = 1;
3986
- ctx2.fillStyle = s.color;
3987
- ctx2.fillText(s.text, s.x, s.y);
3988
- }
3989
- const isSelected = !preview && selectedIdx >= 0 && shapes2[selectedIdx] === s;
3990
- const isHovered = !preview && !isSelected && hoveredIdx >= 0 && shapes2[hoveredIdx] === s;
3991
- if (isSelected || isHovered) {
3992
- const b = shapeBounds(s);
3993
- ctx2.save();
3994
- ctx2.setLineDash([6, 4]);
3995
- ctx2.strokeStyle = isDark ? "#fff" : "#000";
3996
- ctx2.lineWidth = isSelected ? 1.5 : 1;
3997
- ctx2.globalAlpha = isSelected ? 0.7 : 0.45;
3998
- ctx2.strokeRect(b.x - 4, b.y - 4, b.w + 8, b.h + 8);
3999
- if (s.type === "rect" || s.type === "circle") {
4000
- const hSize = 5;
4001
- ctx2.setLineDash([]);
4002
- ctx2.globalAlpha = isSelected ? 0.9 : 0.6;
4003
- const handles = s.type === "rect" ? [[b.x, b.y], [b.x + b.w, b.y], [b.x, b.y + b.h], [b.x + b.w, b.y + b.h]] : [[b.x + b.w / 2, b.y], [b.x + b.w, b.y + b.h / 2], [b.x + b.w / 2, b.y + b.h], [b.x, b.y + b.h / 2]];
4004
- for (const [hx, hy] of handles) {
4005
- ctx2.fillStyle = "#fff";
4006
- ctx2.fillRect(hx - hSize, hy - hSize, hSize * 2, hSize * 2);
4007
- ctx2.strokeStyle = isDark ? "#aaa" : "#333";
4008
- ctx2.lineWidth = 1.2;
4009
- ctx2.strokeRect(hx - hSize, hy - hSize, hSize * 2, hSize * 2);
4010
- }
4011
- }
4012
- ctx2.restore();
4013
- }
4014
- };
4015
- const render = () => {
4016
- ctx2.clearRect(0, 0, canvas2.width, canvas2.height);
4017
- ctx2.drawImage(img, 0, 0);
4018
- for (const s of shapes2) renderShape(s);
4019
- };
4020
- const shapeBounds = (s) => {
4021
- if (s.type === "rect") return { x: Math.min(s.x, s.x + s.w), y: Math.min(s.y, s.y + s.h), w: Math.abs(s.w), h: Math.abs(s.h) };
4022
- if (s.type === "circle") return { x: s.cx - Math.abs(s.rx), y: s.cy - Math.abs(s.ry), w: Math.abs(s.rx) * 2, h: Math.abs(s.ry) * 2 };
4023
- if (s.type === "arrow") {
4024
- const x = Math.min(s.x1, s.x2), y = Math.min(s.y1, s.y2);
4025
- return { x, y, w: Math.abs(s.x2 - s.x1) || 20, h: Math.abs(s.y2 - s.y1) || 20 };
4026
- }
4027
- if (s.type === "text") return { x: s.x, y: s.y - (s.size * 5 + 12), w: s.text.length * (s.size * 3 + 8), h: s.size * 5 + 16 };
4028
- if (s.points.length === 0) return { x: 0, y: 0, w: 0, h: 0 };
4029
- let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
4030
- for (const p of s.points) {
4031
- minX = Math.min(minX, p.x);
4032
- minY = Math.min(minY, p.y);
4033
- maxX = Math.max(maxX, p.x);
4034
- maxY = Math.max(maxY, p.y);
4035
- }
4036
- return { x: minX, y: minY, w: maxX - minX || 10, h: maxY - minY || 10 };
4037
- };
4038
- const hitTest = (x, y) => {
4039
- for (let i = shapes2.length - 1; i >= 0; i--) {
4040
- const b = shapeBounds(shapes2[i]);
4041
- const pad = 8;
4042
- if (x >= b.x - pad && x <= b.x + b.w + pad && y >= b.y - pad && y <= b.y + b.h + pad) return i;
4043
- }
4044
- return -1;
4045
- };
4046
- const moveShape = (s, dx, dy) => {
4047
- if (s.type === "draw" || s.type === "highlight") {
4048
- for (const p of s.points) {
4049
- p.x += dx;
4050
- p.y += dy;
4051
- }
4052
- } else if (s.type === "rect") {
4053
- s.x += dx;
4054
- s.y += dy;
4055
- } else if (s.type === "circle") {
4056
- s.cx += dx;
4057
- s.cy += dy;
4058
- } else if (s.type === "arrow") {
4059
- s.x1 += dx;
4060
- s.y1 += dy;
4061
- s.x2 += dx;
4062
- s.y2 += dy;
4063
- } else if (s.type === "text") {
4064
- s.x += dx;
4065
- s.y += dy;
4066
- }
4067
- };
4068
- img.onload = () => {
4069
- const maxW = container2.clientWidth || 450;
4070
- const scale = maxW / img.width;
4071
- canvas2.width = img.width;
4072
- canvas2.height = img.height;
4073
- canvas2.style.height = `${img.height * scale}px`;
4074
- ctx2 = canvas2.getContext("2d");
4075
- ctx2.drawImage(img, 0, 0);
4076
- const samplePoints = [[10, 10], [canvas2.width - 10, 10], [10, canvas2.height - 10], [canvas2.width - 10, canvas2.height - 10], [canvas2.width / 2, canvas2.height / 2]];
4077
- let totalBrightness = 0;
4078
- for (const [sx, sy] of samplePoints) {
4079
- const px = ctx2.getImageData(sx, sy, 1, 1).data;
4080
- totalBrightness += (px[0] * 299 + px[1] * 587 + px[2] * 114) / 1e3;
4081
- }
4082
- isDark = totalBrightness / samplePoints.length < 128;
4083
- const coords = (e) => {
4084
- const r = canvas2.getBoundingClientRect();
4085
- return { x: (e.clientX - r.left) * (canvas2.width / r.width), y: (e.clientY - r.top) * (canvas2.height / r.height) };
4086
- };
4087
- let autoSelectDrag = false;
4088
- canvas2.addEventListener("mousedown", (e) => {
4089
- const c = coords(e);
4090
- if (currentTool2 === "select") {
4091
- const hit = hitTest(c.x, c.y);
4092
- if (hit >= 0) {
4093
- selectedIdx = hit;
4094
- dragging = true;
4095
- const b = shapeBounds(shapes2[hit]);
4096
- dragOffX = c.x - b.x;
4097
- dragOffY = c.y - b.y;
4098
- canvas2.classList.add("bs-grabbing");
4099
- canvas2.classList.remove("bs-grab");
4100
- render();
4101
- } else {
4102
- selectedIdx = -1;
4103
- render();
4104
- }
4105
- return;
4106
- }
4107
- if (currentTool2 !== "pin" && currentTool2 !== "text") {
4108
- const hit = hitTest(c.x, c.y);
4109
- if (hit >= 0) {
4110
- selectedIdx = hit;
4111
- dragging = true;
4112
- autoSelectDrag = true;
4113
- const b = shapeBounds(shapes2[hit]);
4114
- dragOffX = c.x - b.x;
4115
- dragOffY = c.y - b.y;
4116
- canvas2.classList.add("bs-grabbing");
4117
- render();
4118
- return;
4119
- }
4120
- }
4121
- drawing = true;
4122
- startX = c.x;
4123
- startY = c.y;
4124
- if (currentTool2 === "pin") {
4125
- drawing = false;
4126
- const note = prompt("Describe the issue at this spot:");
4127
- if (note) {
4128
- const color = PIN_COLORS2[pins2.length % PIN_COLORS2.length];
4129
- pins2.push({ x: c.x, y: c.y, note, color });
4130
- renderPins2();
4131
- }
4132
- return;
4133
- }
4134
- if (currentTool2 === "text") {
4135
- drawing = false;
4136
- const input = prompt("Enter text:");
4137
- if (input) {
4138
- shapes2.push({ type: "text", color: currentColor2, size: brushSize, x: c.x, y: c.y, text: input });
4139
- render();
4140
- }
4141
- return;
4142
- }
4143
- if (currentTool2 === "draw" || currentTool2 === "highlight") {
4144
- currentDraw = [{ x: c.x, y: c.y }];
4145
- }
4146
- });
4147
- canvas2.addEventListener("mousemove", (e) => {
4148
- const c = coords(e);
4149
- if (dragging && selectedIdx >= 0) {
4150
- const b = shapeBounds(shapes2[selectedIdx]);
4151
- const dx = c.x - dragOffX - b.x;
4152
- const dy = c.y - dragOffY - b.y;
4153
- moveShape(shapes2[selectedIdx], dx, dy);
4154
- render();
4155
- return;
4156
- }
4157
- if (currentTool2 === "select") {
4158
- const hit = hitTest(c.x, c.y);
4159
- canvas2.classList.toggle("bs-grab", hit >= 0);
4160
- if (hoveredIdx !== hit) {
4161
- hoveredIdx = hit;
4162
- render();
4163
- }
4164
- return;
4165
- }
4166
- if (currentTool2 !== "pin" && currentTool2 !== "text" && !drawing) {
4167
- const hit = hitTest(c.x, c.y);
4168
- canvas2.classList.toggle("bs-grab", hit >= 0);
4169
- if (hoveredIdx !== hit) {
4170
- hoveredIdx = hit;
4171
- render();
4172
- }
4173
- }
4174
- if (!drawing) return;
4175
- if (currentTool2 === "draw" || currentTool2 === "highlight") {
4176
- currentDraw.push({ x: c.x, y: c.y });
4177
- render();
4178
- const alpha = currentTool2 === "highlight" ? 0.25 : 1;
4179
- const lw = currentTool2 === "highlight" ? brushSize * 4 + 10 : brushSize;
4180
- if (currentTool2 !== "highlight") {
4181
- ctx2.save();
4182
- ctx2.strokeStyle = outlineColor();
4183
- ctx2.lineWidth = lw + 3;
4184
- ctx2.lineCap = "round";
4185
- ctx2.lineJoin = "round";
4186
- ctx2.globalAlpha = 0.5;
4187
- ctx2.beginPath();
4188
- ctx2.moveTo(currentDraw[0].x, currentDraw[0].y);
4189
- for (let i = 1; i < currentDraw.length; i++) ctx2.lineTo(currentDraw[i].x, currentDraw[i].y);
4190
- ctx2.stroke();
4191
- ctx2.restore();
4192
- }
4193
- ctx2.strokeStyle = currentColor2;
4194
- ctx2.lineWidth = lw;
4195
- ctx2.lineCap = "round";
4196
- ctx2.lineJoin = "round";
4197
- ctx2.globalAlpha = alpha;
4198
- ctx2.beginPath();
4199
- ctx2.moveTo(currentDraw[0].x, currentDraw[0].y);
4200
- for (let i = 1; i < currentDraw.length; i++) ctx2.lineTo(currentDraw[i].x, currentDraw[i].y);
4201
- ctx2.stroke();
4202
- ctx2.globalAlpha = 1;
4203
- } else {
4204
- render();
4205
- ctx2.strokeStyle = currentColor2;
4206
- ctx2.lineWidth = brushSize;
4207
- ctx2.lineCap = "round";
4208
- ctx2.lineJoin = "round";
4209
- if (currentTool2 === "rect") {
4210
- drawOutline(() => ctx2.strokeRect(startX, startY, c.x - startX, c.y - startY), brushSize);
4211
- ctx2.strokeStyle = currentColor2;
4212
- ctx2.lineWidth = brushSize;
4213
- ctx2.strokeRect(startX, startY, c.x - startX, c.y - startY);
4214
- } else if (currentTool2 === "circle") {
4215
- const rx = Math.abs(c.x - startX) / 2, ry = Math.abs(c.y - startY) / 2;
4216
- const cx2 = startX + (c.x - startX) / 2, cy2 = startY + (c.y - startY) / 2;
4217
- drawOutline(() => {
4218
- ctx2.beginPath();
4219
- ctx2.ellipse(cx2, cy2, rx, ry, 0, 0, Math.PI * 2);
4220
- ctx2.stroke();
4221
- }, brushSize);
4222
- ctx2.strokeStyle = currentColor2;
4223
- ctx2.lineWidth = brushSize;
4224
- ctx2.beginPath();
4225
- ctx2.ellipse(cx2, cy2, rx, ry, 0, 0, Math.PI * 2);
4226
- ctx2.stroke();
4227
- } else if (currentTool2 === "arrow") {
4228
- const angle = Math.atan2(c.y - startY, c.x - startX);
4229
- const headLen = 12 + brushSize * 2;
4230
- const stroke = () => {
4231
- ctx2.beginPath();
4232
- ctx2.moveTo(startX, startY);
4233
- ctx2.lineTo(c.x, c.y);
4234
- ctx2.stroke();
4235
- ctx2.beginPath();
4236
- ctx2.moveTo(c.x, c.y);
4237
- ctx2.lineTo(c.x - headLen * Math.cos(angle - 0.45), c.y - headLen * Math.sin(angle - 0.45));
4238
- ctx2.moveTo(c.x, c.y);
4239
- ctx2.lineTo(c.x - headLen * Math.cos(angle + 0.45), c.y - headLen * Math.sin(angle + 0.45));
4240
- ctx2.stroke();
4241
- };
4242
- drawOutline(stroke, brushSize);
4243
- ctx2.strokeStyle = currentColor2;
4244
- ctx2.lineWidth = brushSize;
4245
- ctx2.lineCap = "round";
4246
- ctx2.lineJoin = "round";
4247
- stroke();
4248
- }
4249
- }
4250
- });
4251
- const endDraw = (e) => {
4252
- if (dragging) {
4253
- dragging = false;
4254
- autoSelectDrag = false;
4255
- selectedIdx = -1;
4256
- canvas2.classList.remove("bs-grabbing");
4257
- canvas2.classList.remove("bs-grab");
4258
- render();
4259
- if (currentTool2 === "select") return;
4260
- return;
4261
- }
4262
- if (!drawing) return;
4263
- drawing = false;
4264
- const c = coords(e);
4265
- if (currentTool2 === "draw" || currentTool2 === "highlight") {
4266
- if (currentDraw.length > 1) {
4267
- shapes2.push({ type: currentTool2, color: currentColor2, size: brushSize, alpha: currentTool2 === "highlight" ? 0.25 : 1, points: [...currentDraw] });
4268
- }
4269
- currentDraw = [];
4270
- } else if (currentTool2 === "rect") {
4271
- shapes2.push({ type: "rect", color: currentColor2, size: brushSize, x: startX, y: startY, w: c.x - startX, h: c.y - startY });
4272
- } else if (currentTool2 === "circle") {
4273
- const rx = Math.abs(c.x - startX) / 2, ry = Math.abs(c.y - startY) / 2;
4274
- shapes2.push({ type: "circle", color: currentColor2, size: brushSize, cx: startX + (c.x - startX) / 2, cy: startY + (c.y - startY) / 2, rx, ry });
4275
- } else if (currentTool2 === "arrow") {
4276
- shapes2.push({ type: "arrow", color: currentColor2, size: brushSize, x1: startX, y1: startY, x2: c.x, y2: c.y });
4277
- }
4278
- render();
4279
- };
4280
- canvas2.addEventListener("mouseup", endDraw);
4281
- canvas2.addEventListener("mouseleave", (e) => {
4282
- if (dragging) {
4283
- dragging = false;
4284
- autoSelectDrag = false;
4285
- canvas2.classList.remove("bs-grabbing");
4286
- canvas2.classList.remove("bs-grab");
4287
- render();
4288
- } else if (drawing) endDraw(e);
4289
- });
4290
- const keyHandler2 = (e) => {
4291
- if (selectedIdx >= 0 && (e.key === "Delete" || e.key === "Backspace")) {
4292
- shapes2.splice(selectedIdx, 1);
4293
- selectedIdx = -1;
4294
- render();
4295
- }
4296
- };
4297
- document.addEventListener("keydown", keyHandler2);
4298
- undoBtn.addEventListener("click", () => {
4299
- if (shapes2.length) {
4300
- shapes2.pop();
4301
- selectedIdx = -1;
4302
- render();
4303
- }
4304
- });
4305
- clearBtn.addEventListener("click", () => {
4306
- shapes2.length = 0;
4307
- pins2.length = 0;
4308
- selectedIdx = -1;
4309
- render();
4310
- renderPins2();
4311
- });
4312
- };
4313
- const bakePins = () => {
4314
- for (let i = 0; i < pins2.length; i++) {
4315
- const p = pins2[i];
4316
- const r = 16;
4317
- ctx2.beginPath();
4318
- ctx2.arc(p.x, p.y - r, r, 0, Math.PI * 2);
4319
- ctx2.fillStyle = p.color;
4320
- ctx2.fill();
4321
- ctx2.strokeStyle = "#fff";
4322
- ctx2.lineWidth = 2;
4323
- ctx2.stroke();
4324
- ctx2.beginPath();
4325
- ctx2.moveTo(p.x - 8, p.y - 6);
4326
- ctx2.lineTo(p.x, p.y + 4);
4327
- ctx2.lineTo(p.x + 8, p.y - 6);
4328
- ctx2.fillStyle = p.color;
4329
- ctx2.fill();
4330
- ctx2.fillStyle = "#fff";
4331
- ctx2.font = "bold 14px Inter, sans-serif";
4332
- ctx2.textAlign = "center";
4333
- ctx2.textBaseline = "middle";
4334
- ctx2.fillText(`${i + 1}`, p.x, p.y - r);
4335
- ctx2.textAlign = "start";
4336
- ctx2.textBaseline = "alphabetic";
4337
- }
4338
- };
4339
- return {
4340
- getAnnotation: () => {
4341
- try {
4342
- selectedIdx = -1;
4343
- render();
4344
- bakePins();
4345
- return canvas2.toDataURL("image/jpeg", 0.7);
4346
- } catch {
4347
- return null;
4348
- }
4349
- },
4350
- getPins: () => pins2.map((p, i) => ({ number: i + 1, x: Math.round(p.x), y: Math.round(p.y), note: p.note }))
4351
- };
4352
- }
4353
- function switchTab(tab) {
4354
- if (!modal) return;
4355
- activeTab = tab;
4356
- modal.querySelectorAll(".bs-tab").forEach((el) => {
4357
- el.classList.toggle("bs-active", el.dataset.tab === tab);
4358
- });
4359
- const scroll = modal.querySelector(".bs-scroll");
4360
- const renderers = {
4361
- report: tabReport,
4362
- console: tabConsole,
4363
- network: tabNetwork,
4364
- context: tabContext,
4365
- history: tabHistory,
4366
- settings: tabSettings
4367
- };
4368
- scroll.innerHTML = renderers[tab]();
4369
- scroll.scrollTop = 0;
4370
- bindTabContent();
4371
- }
4372
- function bindTabContent() {
4373
- if (!modal) return;
4374
- if (activeTab === "history") {
4375
- modal.querySelectorAll("[data-hist-del]").forEach((btn) => {
4376
- btn.addEventListener("click", () => {
4377
- const id = parseInt(btn.dataset.histDel);
4378
- const card = modal.querySelector(`[data-hist-id="${id}"]`);
4379
- if (!card) return;
4380
- const existing = card.querySelector(".bs-hist-confirm");
4381
- if (existing) {
4382
- existing.remove();
4383
- return;
4384
- }
4385
- const confirm2 = document.createElement("div");
4386
- confirm2.className = "bs-hist-confirm";
4387
- confirm2.innerHTML = `<span>Delete this report?</span>`;
4388
- const yes = document.createElement("button");
4389
- yes.type = "button";
4390
- yes.className = "bs-hist-confirm-yes";
4391
- yes.textContent = "Delete";
4392
- const no = document.createElement("button");
4393
- no.type = "button";
4394
- no.className = "bs-hist-confirm-no";
4395
- no.textContent = "Cancel";
4396
- yes.addEventListener("click", () => {
4397
- deleteFromHistory(id);
4398
- switchTab("history");
4399
- });
4400
- no.addEventListener("click", () => confirm2.remove());
4401
- confirm2.appendChild(yes);
4402
- confirm2.appendChild(no);
4403
- card.appendChild(confirm2);
4404
- });
4405
- });
4406
- modal.querySelectorAll("[data-hist-edit]").forEach((btn) => {
4407
- btn.addEventListener("click", () => {
4408
- const id = parseInt(btn.dataset.histEdit);
4409
- const card = modal.querySelector(`[data-hist-id="${id}"]`);
4410
- if (!card) return;
4411
- const existing = card.querySelector(".bs-hist-edit-wrap");
4412
- if (existing) {
4413
- existing.remove();
4414
- return;
4415
- }
4416
- card.querySelector(".bs-hist-confirm")?.remove();
4417
- const entry = getReportHistory().find((e) => e.id === id);
4418
- if (!entry) return;
4419
- const wrap = document.createElement("div");
4420
- wrap.className = "bs-hist-edit-wrap";
4421
- wrap.innerHTML = `
1681
+ <div class="bs-ann-pin-note">${H(o.note)}</div>
1682
+ <div class="bs-ann-pin-loc">${Math.round(C)}% \xD7 ${Math.round(b)}%</div>
1683
+ </div>`;let O=document.createElement("button");O.type="button",O.className="bs-ann-pin-del",O.innerHTML="&times;",O.title="Remove pin",O.addEventListener("click",()=>{h.splice(u,1),Ve()}),B.appendChild(O),ge.appendChild(B)})},a,zt=()=>p?"rgba(255,255,255,0.5)":"rgba(0,0,0,0.35)",be=(o,u)=>{a.save(),a.strokeStyle=zt(),a.lineWidth=u+3,a.lineCap="round",a.lineJoin="round",a.globalAlpha=.5,o(),a.restore()},un=(o,u=!1)=>{if(o.type==="draw"||o.type==="highlight"){if(o.points.length<2)return;let b=o.type==="highlight"?.25:1,T=o.type==="highlight"?o.size*4+10:o.size,B=()=>{a.beginPath(),a.moveTo(o.points[0].x,o.points[0].y);for(let O=1;O<o.points.length;O++)a.lineTo(o.points[O].x,o.points[O].y);a.stroke()};o.type!=="highlight"&&be(B,T),a.strokeStyle=o.color,a.lineWidth=T,a.lineCap="round",a.lineJoin="round",a.globalAlpha=b,B(),a.globalAlpha=1}else if(o.type==="rect"){let b=()=>a.strokeRect(o.x,o.y,o.w,o.h);be(b,o.size),a.strokeStyle=o.color,a.lineWidth=o.size,a.lineCap="round",a.lineJoin="round",b()}else if(o.type==="circle"){let b=()=>{a.beginPath(),a.ellipse(o.cx,o.cy,Math.abs(o.rx),Math.abs(o.ry),0,0,Math.PI*2),a.stroke()};be(b,o.size),a.strokeStyle=o.color,a.lineWidth=o.size,b()}else if(o.type==="arrow"){let b=Math.atan2(o.y2-o.y1,o.x2-o.x1),T=12+o.size*2,B=()=>{a.beginPath(),a.moveTo(o.x1,o.y1),a.lineTo(o.x2,o.y2),a.stroke(),a.beginPath(),a.moveTo(o.x2,o.y2),a.lineTo(o.x2-T*Math.cos(b-.45),o.y2-T*Math.sin(b-.45)),a.moveTo(o.x2,o.y2),a.lineTo(o.x2-T*Math.cos(b+.45),o.y2-T*Math.sin(b+.45)),a.stroke()};be(B,o.size),a.strokeStyle=o.color,a.lineWidth=o.size,a.lineCap="round",a.lineJoin="round",B()}else if(o.type==="text"){let b=o.size*5+12;a.font=`bold ${b}px Inter, -apple-system, sans-serif`,a.fillStyle=zt(),a.globalAlpha=.6,a.fillText(o.text,o.x+1,o.y+1),a.globalAlpha=1,a.fillStyle=o.color,a.fillText(o.text,o.x,o.y)}let f=!u&&g>=0&&l[g]===o,C=!u&&!f&&x>=0&&l[x]===o;if(f||C){let b=Pe(o);if(a.save(),a.setLineDash([6,4]),a.strokeStyle=p?"#fff":"#000",a.lineWidth=f?1.5:1,a.globalAlpha=f?.7:.45,a.strokeRect(b.x-4,b.y-4,b.w+8,b.h+8),o.type==="rect"||o.type==="circle"){a.setLineDash([]),a.globalAlpha=f?.9:.6;let B=o.type==="rect"?[[b.x,b.y],[b.x+b.w,b.y],[b.x,b.y+b.h],[b.x+b.w,b.y+b.h]]:[[b.x+b.w/2,b.y],[b.x+b.w,b.y+b.h/2],[b.x+b.w/2,b.y+b.h],[b.x,b.y+b.h/2]];for(let[O,z]of B)a.fillStyle="#fff",a.fillRect(O-5,z-5,10,10),a.strokeStyle=p?"#aaa":"#333",a.lineWidth=1.2,a.strokeRect(O-5,z-5,10,10)}a.restore()}},D=()=>{a.clearRect(0,0,w.width,w.height),a.drawImage(ie,0,0);for(let o of l)un(o)},Pe=o=>{if(o.type==="rect")return{x:Math.min(o.x,o.x+o.w),y:Math.min(o.y,o.y+o.h),w:Math.abs(o.w),h:Math.abs(o.h)};if(o.type==="circle")return{x:o.cx-Math.abs(o.rx),y:o.cy-Math.abs(o.ry),w:Math.abs(o.rx)*2,h:Math.abs(o.ry)*2};if(o.type==="arrow"){let T=Math.min(o.x1,o.x2),B=Math.min(o.y1,o.y2);return{x:T,y:B,w:Math.abs(o.x2-o.x1)||20,h:Math.abs(o.y2-o.y1)||20}}if(o.type==="text")return{x:o.x,y:o.y-(o.size*5+12),w:o.text.length*(o.size*3+8),h:o.size*5+16};if(o.points.length===0)return{x:0,y:0,w:0,h:0};let u=1/0,f=1/0,C=-1/0,b=-1/0;for(let T of o.points)u=Math.min(u,T.x),f=Math.min(f,T.y),C=Math.max(C,T.x),b=Math.max(b,T.y);return{x:u,y:f,w:C-u||10,h:b-f||10}},Ke=(o,u)=>{for(let f=l.length-1;f>=0;f--){let C=Pe(l[f]),b=8;if(o>=C.x-b&&o<=C.x+C.w+b&&u>=C.y-b&&u<=C.y+C.h+b)return f}return-1},fn=(o,u,f)=>{if(o.type==="draw"||o.type==="highlight")for(let C of o.points)C.x+=u,C.y+=f;else o.type==="rect"?(o.x+=u,o.y+=f):o.type==="circle"?(o.cx+=u,o.cy+=f):o.type==="arrow"?(o.x1+=u,o.y1+=f,o.x2+=u,o.y2+=f):o.type==="text"&&(o.x+=u,o.y+=f)};ie.onload=()=>{let u=(e.clientWidth||450)/ie.width;w.width=ie.width,w.height=ie.height,w.style.height=`${ie.height*u}px`,a=w.getContext("2d"),a.drawImage(ie,0,0);let f=[[10,10],[w.width-10,10],[10,w.height-10],[w.width-10,w.height-10],[w.width/2,w.height/2]],C=0;for(let[z,d]of f){let v=a.getImageData(z,d,1,1).data;C+=(v[0]*299+v[1]*587+v[2]*114)/1e3}p=C/f.length<128;let b=z=>{let d=w.getBoundingClientRect();return{x:(z.clientX-d.left)*(w.width/d.width),y:(z.clientY-d.top)*(w.height/d.height)}},T=!1;w.addEventListener("mousedown",z=>{let d=b(z);if(r==="select"){let v=Ke(d.x,d.y);if(v>=0){g=v,y=!0;let L=Pe(l[v]);ae=d.x-L.x,Te=d.y-L.y,w.classList.add("bs-grabbing"),w.classList.remove("bs-grab"),D()}else g=-1,D();return}if(r!=="pin"&&r!=="text"){let v=Ke(d.x,d.y);if(v>=0){g=v,y=!0,T=!0;let L=Pe(l[v]);ae=d.x-L.x,Te=d.y-L.y,w.classList.add("bs-grabbing"),D();return}}if($=!0,N=d.x,P=d.y,r==="pin"){$=!1;let v=prompt("Describe the issue at this spot:");if(v){let L=Xt[h.length%Xt.length];h.push({x:d.x,y:d.y,note:v,color:L}),Ve()}return}if(r==="text"){$=!1;let v=prompt("Enter text:");v&&(l.push({type:"text",color:n,size:i,x:d.x,y:d.y,text:v}),D());return}(r==="draw"||r==="highlight")&&(k=[{x:d.x,y:d.y}])}),w.addEventListener("mousemove",z=>{let d=b(z);if(y&&g>=0){let v=Pe(l[g]),L=d.x-ae-v.x,S=d.y-Te-v.y;fn(l[g],L,S),D();return}if(r==="select"){let v=Ke(d.x,d.y);w.classList.toggle("bs-grab",v>=0),x!==v&&(x=v,D());return}if(r!=="pin"&&r!=="text"&&!$){let v=Ke(d.x,d.y);w.classList.toggle("bs-grab",v>=0),x!==v&&(x=v,D())}if($){if(r==="draw"||r==="highlight"){k.push({x:d.x,y:d.y}),D();let v=r==="highlight"?.25:1,L=r==="highlight"?i*4+10:i;if(r!=="highlight"){a.save(),a.strokeStyle=zt(),a.lineWidth=L+3,a.lineCap="round",a.lineJoin="round",a.globalAlpha=.5,a.beginPath(),a.moveTo(k[0].x,k[0].y);for(let S=1;S<k.length;S++)a.lineTo(k[S].x,k[S].y);a.stroke(),a.restore()}a.strokeStyle=n,a.lineWidth=L,a.lineCap="round",a.lineJoin="round",a.globalAlpha=v,a.beginPath(),a.moveTo(k[0].x,k[0].y);for(let S=1;S<k.length;S++)a.lineTo(k[S].x,k[S].y);a.stroke(),a.globalAlpha=1}else if(D(),a.strokeStyle=n,a.lineWidth=i,a.lineCap="round",a.lineJoin="round",r==="rect")be(()=>a.strokeRect(N,P,d.x-N,d.y-P),i),a.strokeStyle=n,a.lineWidth=i,a.strokeRect(N,P,d.x-N,d.y-P);else if(r==="circle"){let v=Math.abs(d.x-N)/2,L=Math.abs(d.y-P)/2,S=N+(d.x-N)/2,ss=P+(d.y-P)/2;be(()=>{a.beginPath(),a.ellipse(S,ss,v,L,0,0,Math.PI*2),a.stroke()},i),a.strokeStyle=n,a.lineWidth=i,a.beginPath(),a.ellipse(S,ss,v,L,0,0,Math.PI*2),a.stroke()}else if(r==="arrow"){let v=Math.atan2(d.y-P,d.x-N),L=12+i*2,S=()=>{a.beginPath(),a.moveTo(N,P),a.lineTo(d.x,d.y),a.stroke(),a.beginPath(),a.moveTo(d.x,d.y),a.lineTo(d.x-L*Math.cos(v-.45),d.y-L*Math.sin(v-.45)),a.moveTo(d.x,d.y),a.lineTo(d.x-L*Math.cos(v+.45),d.y-L*Math.sin(v+.45)),a.stroke()};be(S,i),a.strokeStyle=n,a.lineWidth=i,a.lineCap="round",a.lineJoin="round",S()}}});let B=z=>{if(y)return y=!1,T=!1,g=-1,w.classList.remove("bs-grabbing"),w.classList.remove("bs-grab"),D(),void 0;if(!$)return;$=!1;let d=b(z);if(r==="draw"||r==="highlight")k.length>1&&l.push({type:r,color:n,size:i,alpha:r==="highlight"?.25:1,points:[...k]}),k=[];else if(r==="rect")l.push({type:"rect",color:n,size:i,x:N,y:P,w:d.x-N,h:d.y-P});else if(r==="circle"){let v=Math.abs(d.x-N)/2,L=Math.abs(d.y-P)/2;l.push({type:"circle",color:n,size:i,cx:N+(d.x-N)/2,cy:P+(d.y-P)/2,rx:v,ry:L})}else r==="arrow"&&l.push({type:"arrow",color:n,size:i,x1:N,y1:P,x2:d.x,y2:d.y});D()};w.addEventListener("mouseup",B),w.addEventListener("mouseleave",z=>{y?(y=!1,T=!1,w.classList.remove("bs-grabbing"),w.classList.remove("bs-grab"),D()):$&&B(z)});let O=z=>{g>=0&&(z.key==="Delete"||z.key==="Backspace")&&(l.splice(g,1),g=-1,D())};document.addEventListener("keydown",O),es.addEventListener("click",()=>{l.length&&(l.pop(),g=-1,D())}),ts.addEventListener("click",()=>{l.length=0,h.length=0,g=-1,D(),Ve()})};let gn=()=>{for(let o=0;o<h.length;o++){let u=h[o],f=16;a.beginPath(),a.arc(u.x,u.y-f,f,0,Math.PI*2),a.fillStyle=u.color,a.fill(),a.strokeStyle="#fff",a.lineWidth=2,a.stroke(),a.beginPath(),a.moveTo(u.x-8,u.y-6),a.lineTo(u.x,u.y+4),a.lineTo(u.x+8,u.y-6),a.fillStyle=u.color,a.fill(),a.fillStyle="#fff",a.font="bold 14px Inter, sans-serif",a.textAlign="center",a.textBaseline="middle",a.fillText(`${o+1}`,u.x,u.y-f),a.textAlign="start",a.textBaseline="alphabetic"}};return{getAnnotation:()=>{try{return g=-1,D(),gn(),w.toDataURL("image/jpeg",.7)}catch{return null}},getPins:()=>h.map((o,u)=>({number:u+1,x:Math.round(o.x),y:Math.round(o.y),note:o.note}))}}function wt(e){if(!m)return;Ee=e,m.querySelectorAll(".bs-tab").forEach(n=>{n.classList.toggle("bs-active",n.dataset.tab===e)});let t=m.querySelector(".bs-scroll"),s={report:Qs,console:Jn,network:Xn,context:Vn,history:Kn,settings:Zn};t.innerHTML=s[e](),t.scrollTop=0,Gs()}function Gs(){if(!m)return;if(Ee==="history"){m.querySelectorAll("[data-hist-del]").forEach(s=>{s.addEventListener("click",()=>{let n=parseInt(s.dataset.histDel),r=m.querySelector(`[data-hist-id="${n}"]`);if(!r)return;let i=r.querySelector(".bs-hist-confirm");if(i){i.remove();return}let c=document.createElement("div");c.className="bs-hist-confirm",c.innerHTML="<span>Delete this report?</span>";let p=document.createElement("button");p.type="button",p.className="bs-hist-confirm-yes",p.textContent="Delete";let l=document.createElement("button");l.type="button",l.className="bs-hist-confirm-no",l.textContent="Cancel",p.addEventListener("click",()=>{Fn(n),wt("history")}),l.addEventListener("click",()=>c.remove()),c.appendChild(p),c.appendChild(l),r.appendChild(c)})}),m.querySelectorAll("[data-hist-edit]").forEach(s=>{s.addEventListener("click",()=>{let n=parseInt(s.dataset.histEdit),r=m.querySelector(`[data-hist-id="${n}"]`);if(!r)return;let i=r.querySelector(".bs-hist-edit-wrap");if(i){i.remove();return}r.querySelector(".bs-hist-confirm")?.remove();let c=Se().find(y=>y.id===n);if(!c)return;let p=document.createElement("div");p.className="bs-hist-edit-wrap",p.innerHTML=`
4422
1684
  <div class="bs-field" style="margin-bottom:8px">
4423
- <input class="bs-input" data-edit-title value="${esc(entry.title)}" />
1685
+ <input class="bs-input" data-edit-title value="${H(c.title)}" />
4424
1686
  </div>
4425
1687
  <div class="bs-field" style="margin-bottom:8px">
4426
- <textarea class="bs-textarea" data-edit-desc style="min-height:56px">${esc(entry.description)}</textarea>
1688
+ <textarea class="bs-textarea" data-edit-desc style="min-height:56px">${H(c.description)}</textarea>
4427
1689
  </div>
4428
1690
  <div class="bs-field" style="margin-bottom:8px">
4429
1691
  <div class="bs-sev-row">
4430
- <button type="button" class="bs-sev-btn bs-sev-low${entry.severity === "low" ? " bs-picked" : ""}" data-edit-sev="low">Low</button>
4431
- <button type="button" class="bs-sev-btn bs-sev-medium${entry.severity === "medium" ? " bs-picked" : ""}" data-edit-sev="medium">Medium</button>
4432
- <button type="button" class="bs-sev-btn bs-sev-high${entry.severity === "high" ? " bs-picked" : ""}" data-edit-sev="high">High</button>
4433
- <button type="button" class="bs-sev-btn bs-sev-critical${entry.severity === "critical" ? " bs-picked" : ""}" data-edit-sev="critical">Critical</button>
1692
+ <button type="button" class="bs-sev-btn bs-sev-low${c.severity==="low"?" bs-picked":""}" data-edit-sev="low">Low</button>
1693
+ <button type="button" class="bs-sev-btn bs-sev-medium${c.severity==="medium"?" bs-picked":""}" data-edit-sev="medium">Medium</button>
1694
+ <button type="button" class="bs-sev-btn bs-sev-high${c.severity==="high"?" bs-picked":""}" data-edit-sev="high">High</button>
1695
+ <button type="button" class="bs-sev-btn bs-sev-critical${c.severity==="critical"?" bs-picked":""}" data-edit-sev="critical">Critical</button>
4434
1696
  </div>
4435
1697
  </div>
4436
- <div class="bs-hist-edit-row"></div>`;
4437
- let editSev = entry.severity;
4438
- card.appendChild(wrap);
4439
- wrap.querySelectorAll("[data-edit-sev]").forEach((sb) => {
4440
- sb.addEventListener("click", () => {
4441
- editSev = sb.dataset.editSev;
4442
- wrap.querySelectorAll(".bs-sev-btn").forEach((b) => b.classList.remove("bs-picked"));
4443
- sb.classList.add("bs-picked");
4444
- });
4445
- });
4446
- const row = wrap.querySelector(".bs-hist-edit-row");
4447
- const save = document.createElement("button");
4448
- save.type = "button";
4449
- save.className = "bs-hist-save";
4450
- save.textContent = "Save";
4451
- const cancel = document.createElement("button");
4452
- cancel.type = "button";
4453
- cancel.className = "bs-hist-cancel";
4454
- cancel.textContent = "Cancel";
4455
- save.addEventListener("click", () => {
4456
- const newTitle = wrap.querySelector("[data-edit-title]").value.trim();
4457
- const newDesc = wrap.querySelector("[data-edit-desc]").value.trim();
4458
- if (!newTitle) return;
4459
- updateInHistory(id, { title: newTitle, description: newDesc, severity: editSev });
4460
- switchTab("history");
4461
- });
4462
- cancel.addEventListener("click", () => wrap.remove());
4463
- row.appendChild(save);
4464
- row.appendChild(cancel);
4465
- });
4466
- });
4467
- return;
4468
- }
4469
- if (activeTab === "settings") {
4470
- modal.querySelectorAll("[data-set-theme]").forEach((btn) => {
4471
- btn.addEventListener("click", () => {
4472
- const id = btn.dataset.setTheme;
4473
- setTheme(id);
4474
- modal.querySelectorAll("[data-set-theme]").forEach((b) => b.classList.remove("bs-picked"));
4475
- btn.classList.add("bs-picked");
4476
- });
4477
- });
4478
- modal.querySelectorAll("[data-set-layout]").forEach((btn) => {
4479
- btn.addEventListener("click", () => {
4480
- const id = btn.dataset.setLayout;
4481
- const layout = getLayoutById(id);
4482
- if (!layout) return;
4483
- currentLayout = layout;
4484
- close();
4485
- setTimeout(() => {
4486
- activeTab = "settings";
4487
- open();
4488
- setTimeout(() => switchTab("settings"), 50);
4489
- }, 400);
4490
- });
4491
- });
4492
- return;
4493
- }
4494
- if (activeTab !== "report") return;
4495
- modal.querySelectorAll(".bs-cat-btn").forEach((btn) => {
4496
- btn.addEventListener("click", () => {
4497
- const cat = btn.dataset.cat;
4498
- modal.querySelector('input[name="category"]').value = cat;
4499
- modal.querySelectorAll(".bs-cat-btn").forEach((b) => b.classList.remove("bs-picked"));
4500
- btn.classList.add("bs-picked");
4501
- });
4502
- });
4503
- modal.querySelectorAll(".bs-sev-btn").forEach((btn) => {
4504
- btn.addEventListener("click", () => {
4505
- const sev = btn.dataset.sev;
4506
- modal.querySelector('input[name="severity"]').value = sev;
4507
- modal.querySelectorAll(".bs-sev-btn").forEach((b) => b.classList.remove("bs-picked"));
4508
- btn.classList.add("bs-picked");
4509
- });
4510
- });
4511
- let screenshotData = null;
4512
- let annotator = null;
4513
- modal.querySelector("[data-bs-screenshot]")?.addEventListener("click", async () => {
4514
- const titleEl = modal?.querySelector("[data-bs-shot-title]");
4515
- const subEl = modal?.querySelector("[data-bs-shot-sub]");
4516
- const area = modal?.querySelector("[data-bs-screenshot]");
4517
- if (titleEl) titleEl.textContent = "Capturing...";
4518
- if (modal) modal.style.visibility = "hidden";
4519
- if (backdrop) backdrop.style.visibility = "hidden";
4520
- await new Promise((r) => setTimeout(r, 200));
4521
- screenshotData = await captureScreenshot();
4522
- if (modal) modal.style.visibility = "";
4523
- if (backdrop) backdrop.style.visibility = "";
4524
- if (screenshotData) {
4525
- if (titleEl) titleEl.textContent = "Screenshot captured!";
4526
- if (subEl) subEl.textContent = "Draw on the image below to highlight the issue";
4527
- area?.classList.add("bs-captured");
4528
- const container2 = modal?.querySelector("[data-bs-annotate]");
4529
- if (container2) annotator = setupAnnotation(container2, screenshotData);
4530
- } else {
4531
- if (titleEl) titleEl.textContent = "Screenshot unavailable";
4532
- if (subEl) subEl.textContent = "Install html2canvas package for screenshot support";
4533
- }
4534
- });
4535
- modal.querySelector("[data-bs-form]")?.addEventListener("submit", async (e) => {
4536
- e.preventDefault();
4537
- const form = e.target;
4538
- const btn = form.querySelector(".bs-submit-btn");
4539
- const msgEl = form.querySelector("[data-bs-msg]");
4540
- const title = form.elements.namedItem("title").value.trim();
4541
- const description = form.elements.namedItem("description").value.trim();
4542
- const severity = form.elements.namedItem("severity").value;
4543
- const category = form.elements.namedItem("category").value;
4544
- if (!title) return;
4545
- btn.disabled = true;
4546
- btn.textContent = "Sending...";
4547
- const detected = autoDetect();
4548
- const report = {
4549
- projectId: config.projectId,
4550
- title,
4551
- description,
4552
- category,
4553
- severity,
4554
- tags: detected.tags.length ? detected.tags : void 0,
4555
- context: buildContext(),
4556
- consoleLogs: getLogs(),
4557
- errors: getErrors(),
4558
- networkCaptures: getFailedNetworkCaptures(),
4559
- breadcrumbs: getBreadcrumbs(),
4560
- performance: getPerformanceMetrics() ?? void 0,
4561
- screenshot: screenshotData ?? void 0,
4562
- annotation: annotator?.getAnnotation() ?? void 0,
4563
- pins: annotator?.getPins().length ? annotator.getPins() : void 0,
4564
- createdAt: Date.now()
4565
- };
4566
- const result = await submitReport(report);
4567
- addToHistory({
4568
- title: report.title,
4569
- description: report.description,
4570
- category: report.category,
4571
- severity: report.severity,
4572
- tags: report.tags,
4573
- screenshot: report.screenshot,
4574
- pins: report.pins,
4575
- url: window.location.href,
4576
- createdAt: report.createdAt,
4577
- status: result.success ? "sent" : "draft"
4578
- });
4579
- if (result.success) {
4580
- msgEl.className = "bs-msg bs-msg-ok";
4581
- msgEl.innerHTML = `${I.check} Bug reported \u2014 thank you!`;
4582
- setTimeout(close, 1200);
4583
- } else {
4584
- msgEl.className = "bs-msg bs-msg-err";
4585
- msgEl.textContent = result.error ?? "Could not send. Saved as draft.";
4586
- btn.disabled = false;
4587
- btn.textContent = "Submit Report";
4588
- }
4589
- });
4590
- }
4591
- function open() {
4592
- if (isOpen) return;
4593
- isOpen = true;
4594
- activeTab = "report";
4595
- backdrop = document.createElement("div");
4596
- backdrop.className = "bs-backdrop";
4597
- document.body.appendChild(backdrop);
4598
- const fails = getFailedNetworkCaptures().length;
4599
- const errs = getErrors().length;
4600
- modal = document.createElement("div");
4601
- modal.className = `bs-modal bs-ly-${currentLayout.id}`;
4602
- applyThemeVars(modal);
4603
- const tabsHtml = `
1698
+ <div class="bs-hist-edit-row"></div>`;let l=c.severity;r.appendChild(p),p.querySelectorAll("[data-edit-sev]").forEach(y=>{y.addEventListener("click",()=>{l=y.dataset.editSev,p.querySelectorAll(".bs-sev-btn").forEach($=>$.classList.remove("bs-picked")),y.classList.add("bs-picked")})});let h=p.querySelector(".bs-hist-edit-row"),g=document.createElement("button");g.type="button",g.className="bs-hist-save",g.textContent="Save";let x=document.createElement("button");x.type="button",x.className="bs-hist-cancel",x.textContent="Cancel",g.addEventListener("click",()=>{let y=p.querySelector("[data-edit-title]").value.trim(),$=p.querySelector("[data-edit-desc]").value.trim();y&&(Yn(n,{title:y,description:$,severity:l}),wt("history"))}),x.addEventListener("click",()=>p.remove()),h.appendChild(g),h.appendChild(x)})});return}if(Ee==="settings"){m.querySelectorAll("[data-set-theme]").forEach(s=>{s.addEventListener("click",()=>{let n=s.dataset.setTheme;Yt(n),m.querySelectorAll("[data-set-theme]").forEach(r=>r.classList.remove("bs-picked")),s.classList.add("bs-picked")})}),m.querySelectorAll("[data-set-layout]").forEach(s=>{s.addEventListener("click",()=>{let n=s.dataset.setLayout,r=Oe(n);r&&(ue=r,J(),setTimeout(()=>{Ee="settings",Le(),setTimeout(()=>wt("settings"),50)},400))})});return}if(Ee!=="report")return;m.querySelectorAll(".bs-cat-btn").forEach(s=>{s.addEventListener("click",()=>{let n=s.dataset.cat;m.querySelector('input[name="category"]').value=n,m.querySelectorAll(".bs-cat-btn").forEach(r=>r.classList.remove("bs-picked")),s.classList.add("bs-picked")})}),m.querySelectorAll(".bs-sev-btn").forEach(s=>{s.addEventListener("click",()=>{let n=s.dataset.sev;m.querySelector('input[name="severity"]').value=n,m.querySelectorAll(".bs-sev-btn").forEach(r=>r.classList.remove("bs-picked")),s.classList.add("bs-picked")})});let e=null,t=null;m.querySelector("[data-bs-screenshot]")?.addEventListener("click",async()=>{let s=m?.querySelector("[data-bs-shot-title]"),n=m?.querySelector("[data-bs-shot-sub]"),r=m?.querySelector("[data-bs-screenshot]");if(s&&(s.textContent="Capturing..."),m&&(m.style.visibility="hidden"),F&&(F.style.visibility="hidden"),await new Promise(i=>setTimeout(i,200)),e=await it(),m&&(m.style.visibility=""),F&&(F.style.visibility=""),e){s&&(s.textContent="Screenshot captured!"),n&&(n.textContent="Draw on the image below to highlight the issue"),r?.classList.add("bs-captured");let i=m?.querySelector("[data-bs-annotate]");i&&(t=Qn(i,e))}else s&&(s.textContent="Screenshot unavailable"),n&&(n.textContent="Could not capture screenshot on this page")}),m.querySelector("[data-bs-form]")?.addEventListener("submit",async s=>{s.preventDefault();let n=s.target,r=n.querySelector(".bs-submit-btn"),i=n.querySelector("[data-bs-msg]"),c=n.elements.namedItem("title").value.trim(),p=n.elements.namedItem("description").value.trim(),l=n.elements.namedItem("severity").value,h=n.elements.namedItem("category").value;if(!c)return;r.disabled=!0,r.textContent="Sending...";let g=Zs(),x={projectId:Y.projectId,title:c,description:p,category:h,severity:l,tags:g.tags.length?g.tags:void 0,context:Ks(),consoleLogs:ee(),errors:te(),networkCaptures:le(),breadcrumbs:me(),performance:ye()??void 0,screenshot:e??void 0,annotation:t?.getAnnotation()??void 0,pins:t?.getPins().length?t.getPins():void 0,createdAt:Date.now()},y=await Dt(x);_n({title:x.title,description:x.description,category:x.category,severity:x.severity,tags:x.tags,screenshot:x.screenshot,pins:x.pins,url:window.location.href,createdAt:x.createdAt,status:y.success?"sent":"draft"}),y.success?(i.className="bs-msg bs-msg-ok",i.innerHTML=`${K.check} Bug reported \u2014 thank you!`,setTimeout(J,1200)):(i.className="bs-msg bs-msg-err",i.textContent=y.error??"Could not send. Saved as draft.",r.disabled=!1,r.textContent="Submit Report")})}function Le(){if(fe)return;fe=!0,Ee="report",F=document.createElement("div"),F.className="bs-backdrop",document.body.appendChild(F);let e=le().length,t=te().length;m=document.createElement("div"),m.className=`bs-modal bs-ly-${ue.id}`,kt(m);let s=`
4604
1699
  <div class="bs-tabs">
4605
- <button class="bs-tab bs-active" data-tab="report">${I.report} Report</button>
4606
- <button class="bs-tab" data-tab="console">${I.console} Console <span class="bs-tab-badge">${getLogs().length}</span></button>
4607
- <button class="bs-tab" data-tab="network">${I.network} Network ${fails ? `<span class="bs-tab-badge bs-warn">${fails}</span>` : `<span class="bs-tab-badge">${getNetworkCaptures().length}</span>`}</button>
4608
- <button class="bs-tab" data-tab="context">${I.ctx} Context ${errs ? `<span class="bs-tab-badge bs-warn">${errs}</span>` : ""}</button>
4609
- <button class="bs-tab" data-tab="history">${I.history} History <span class="bs-tab-badge">${getReportHistory().length}</span></button>
4610
- <button class="bs-tab" data-tab="settings" style="margin-left:auto">${I.settings}</button>
4611
- </div>`;
4612
- const user = getCurrentUser();
4613
- const initials = user ? user.name.split(" ").map((w) => w[0]).join("").toUpperCase().slice(0, 2) : "";
4614
- const pinActive = isPinModeActive();
4615
- const headerHtml = `
1700
+ <button class="bs-tab bs-active" data-tab="report">${K.report} Report</button>
1701
+ <button class="bs-tab" data-tab="console">${K.console} Console <span class="bs-tab-badge">${ee().length}</span></button>
1702
+ <button class="bs-tab" data-tab="network">${K.network} Network ${e?`<span class="bs-tab-badge bs-warn">${e}</span>`:`<span class="bs-tab-badge">${ve().length}</span>`}</button>
1703
+ <button class="bs-tab" data-tab="context">${K.ctx} Context ${t?`<span class="bs-tab-badge bs-warn">${t}</span>`:""}</button>
1704
+ <button class="bs-tab" data-tab="history">${K.history} History <span class="bs-tab-badge">${Se().length}</span></button>
1705
+ <button class="bs-tab" data-tab="settings" style="margin-left:auto">${K.settings}</button>
1706
+ </div>`,n=V(),r=n?n.name.split(" ").map(x=>x[0]).join("").toUpperCase().slice(0,2):"",i=yt(),c=`
4616
1707
  <div class="bs-hdr">
4617
1708
  <div class="bs-logo">BugStash</div>
4618
1709
  <div class="bs-hdr-right">
4619
- ${user ? `
4620
- <button class="bs-pin-toggle${pinActive ? " active" : ""}" data-bs-pin-toggle title="Toggle pin mode">
1710
+ ${n?`
1711
+ <button class="bs-pin-toggle${i?" active":""}" data-bs-pin-toggle title="Toggle pin mode">
4621
1712
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
4622
- Pins ${pinActive ? "ON" : "OFF"}
1713
+ Pins ${i?"ON":"OFF"}
4623
1714
  </button>
4624
1715
  <div class="bs-user-badge">
4625
- <div class="bs-user-avatar">${initials}</div>
4626
- <span>${user.name.split(" ")[0]}</span>
1716
+ <div class="bs-user-avatar">${r}</div>
1717
+ <span>${n.name.split(" ")[0]}</span>
4627
1718
  </div>
4628
1719
  <button class="bs-login-logout" data-bs-logout title="Sign out">Logout</button>
4629
- ` : ""}
4630
- <button class="bs-close-btn" data-bs-close title="Close">${I.x}</button>
1720
+ `:""}
1721
+ <button class="bs-close-btn" data-bs-close title="Close">${K.x}</button>
4631
1722
  </div>
4632
- </div>`;
4633
- const useSidebar = currentLayout.tabPosition === "left";
4634
- const useBottomTabs = currentLayout.tabPosition === "bottom";
4635
- const contentHtml = user ? tabReport() : tabLogin();
4636
- if (useSidebar) {
4637
- modal.innerHTML = `
4638
- ${headerHtml}
1723
+ </div>`,p=ue.tabPosition==="left",l=ue.tabPosition==="bottom",h=n?Qs():Un();p?m.innerHTML=`
1724
+ ${c}
4639
1725
  <div class="bs-body-wrap">
4640
- ${user ? tabsHtml : ""}
4641
- <div class="bs-scroll">${contentHtml}</div>
4642
- </div>`;
4643
- } else if (useBottomTabs) {
4644
- modal.innerHTML = `
4645
- ${headerHtml}
4646
- <div class="bs-scroll">${contentHtml}</div>
4647
- ${user ? `<div class="bs-tab-divider"></div>${tabsHtml}` : ""}`;
4648
- } else {
4649
- modal.innerHTML = `
4650
- ${headerHtml}
4651
- ${user ? `${tabsHtml}<div class="bs-tab-divider"></div>` : ""}
4652
- <div class="bs-scroll">${contentHtml}</div>`;
4653
- }
4654
- document.body.appendChild(modal);
4655
- if (fab) fab.classList.add("bs-open");
4656
- requestAnimationFrame(() => {
4657
- backdrop?.classList.add("bs-in");
4658
- modal?.classList.add("bs-in");
4659
- });
4660
- modal.querySelectorAll(".bs-tab").forEach((tab) => {
4661
- tab.addEventListener("click", () => switchTab(tab.dataset.tab));
4662
- });
4663
- modal.querySelector("[data-bs-close]")?.addEventListener("click", close);
4664
- backdrop.addEventListener("click", close);
4665
- modal.querySelector("[data-bs-pin-toggle]")?.addEventListener("click", () => {
4666
- const active = togglePinMode();
4667
- const btn = modal?.querySelector("[data-bs-pin-toggle]");
4668
- if (btn) {
4669
- btn.classList.toggle("active", active);
4670
- btn.innerHTML = `
1726
+ ${n?s:""}
1727
+ <div class="bs-scroll">${h}</div>
1728
+ </div>`:l?m.innerHTML=`
1729
+ ${c}
1730
+ <div class="bs-scroll">${h}</div>
1731
+ ${n?`<div class="bs-tab-divider"></div>${s}`:""}`:m.innerHTML=`
1732
+ ${c}
1733
+ ${n?`${s}<div class="bs-tab-divider"></div>`:""}
1734
+ <div class="bs-scroll">${h}</div>`,document.body.appendChild(m),j&&j.classList.add("bs-open"),requestAnimationFrame(()=>{F?.classList.add("bs-in"),m?.classList.add("bs-in")}),m.querySelectorAll(".bs-tab").forEach(x=>{x.addEventListener("click",()=>wt(x.dataset.tab))}),m.querySelector("[data-bs-close]")?.addEventListener("click",J),F.addEventListener("click",J),m.querySelector("[data-bs-pin-toggle]")?.addEventListener("click",()=>{let x=vt(),y=m?.querySelector("[data-bs-pin-toggle]");y&&(y.classList.toggle("active",x),y.innerHTML=`
4671
1735
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z"/><circle cx="12" cy="10" r="3"/></svg>
4672
- Pins ${active ? "ON" : "OFF"}
4673
- `;
4674
- }
4675
- });
4676
- modal.querySelector("[data-bs-logout]")?.addEventListener("click", () => {
4677
- logout();
4678
- close();
4679
- setTimeout(() => open(), 300);
4680
- });
4681
- if (!user) {
4682
- bindLoginForm();
4683
- } else {
4684
- bindTabContent();
4685
- }
4686
- const escHandler = (e) => {
4687
- if (e.key === "Escape") {
4688
- close();
4689
- document.removeEventListener("keydown", escHandler);
4690
- }
4691
- };
4692
- document.addEventListener("keydown", escHandler);
4693
- }
4694
- function bindLoginForm() {
4695
- if (!modal) return;
4696
- const submitBtn = modal.querySelector("#bs-login-submit");
4697
- const emailInput = modal.querySelector("#bs-login-email");
4698
- const passInput = modal.querySelector("#bs-login-pass");
4699
- const errorEl = modal.querySelector("#bs-login-error");
4700
- if (!submitBtn || !emailInput || !passInput) return;
4701
- const doLogin = async () => {
4702
- const email = emailInput.value.trim();
4703
- const password = passInput.value;
4704
- if (!email || !password) {
4705
- errorEl.textContent = "Please enter email and password";
4706
- errorEl.style.display = "block";
4707
- return;
4708
- }
4709
- submitBtn.setAttribute("disabled", "true");
4710
- submitBtn.textContent = "Signing in...";
4711
- errorEl.style.display = "none";
4712
- const result = await login(email, password, config.projectId);
4713
- if (result.success) {
4714
- initLivePins(config.projectId);
4715
- connectRealtime(config.projectId);
4716
- close();
4717
- setTimeout(() => open(), 300);
4718
- } else {
4719
- errorEl.textContent = result.error || "Invalid credentials";
4720
- errorEl.style.display = "block";
4721
- submitBtn.removeAttribute("disabled");
4722
- submitBtn.textContent = "Sign In";
4723
- }
4724
- };
4725
- submitBtn.addEventListener("click", doLogin);
4726
- passInput.addEventListener("keydown", (e) => {
4727
- if (e.key === "Enter") doLogin();
4728
- });
4729
- emailInput.addEventListener("keydown", (e) => {
4730
- if (e.key === "Enter") passInput.focus();
4731
- });
4732
- setTimeout(() => emailInput.focus(), 100);
4733
- }
4734
- function close() {
4735
- if (!isOpen) return;
4736
- if (fab) fab.classList.remove("bs-open");
4737
- if (modal) {
4738
- modal.classList.remove("bs-in");
4739
- modal.classList.add("bs-out");
4740
- }
4741
- if (backdrop) backdrop.classList.remove("bs-in");
4742
- setTimeout(() => {
4743
- modal?.remove();
4744
- backdrop?.remove();
4745
- modal = null;
4746
- backdrop = null;
4747
- isOpen = false;
4748
- }, 350);
4749
- }
4750
- function applyThemeVars(el) {
4751
- for (const [key, val] of Object.entries(currentTheme.vars)) {
4752
- el.style.setProperty(key, val);
4753
- }
4754
- }
4755
- function setTheme(themeId) {
4756
- const theme = getThemeById(themeId);
4757
- if (!theme) return;
4758
- currentTheme = theme;
4759
- if (modal) applyThemeVars(modal);
4760
- if (fab) applyThemeVars(fab);
4761
- }
4762
- function getCurrentThemeId() {
4763
- return currentTheme.id;
4764
- }
4765
- function setLayout(layoutId) {
4766
- const layout = getLayoutById(layoutId);
4767
- if (!layout) return;
4768
- currentLayout = layout;
4769
- if (isOpen) {
4770
- close();
4771
- setTimeout(open, 400);
4772
- }
4773
- }
4774
- function getCurrentLayoutId() {
4775
- return currentLayout.id;
4776
- }
4777
- function initPanel(cfg) {
4778
- config = cfg;
4779
- styleEl = document.createElement("style");
4780
- styleEl.textContent = STYLES + LAYOUT_CSS;
4781
- document.head.appendChild(styleEl);
4782
- fab = document.createElement("button");
4783
- fab.className = "bs-fab";
4784
- applyThemeVars(fab);
4785
- fab.innerHTML = `${I.bug}<span class="bs-fab-label">Report a bug</span>`;
4786
- fab.style[cfg.panelPosition === "bottom-left" ? "left" : "right"] = "24px";
4787
- fab.addEventListener("click", () => isOpen ? close() : open());
4788
- document.body.appendChild(fab);
4789
- keyHandler = (e) => {
4790
- if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === "B") {
4791
- e.preventDefault();
4792
- isOpen ? close() : open();
4793
- }
4794
- };
4795
- document.addEventListener("keydown", keyHandler);
4796
- }
4797
- function destroyPanel() {
4798
- close();
4799
- fab?.remove();
4800
- fab = null;
4801
- styleEl?.remove();
4802
- styleEl = null;
4803
- if (keyHandler) document.removeEventListener("keydown", keyHandler);
4804
- keyHandler = null;
4805
- }
4806
-
4807
- // src/annotation.ts
4808
- var overlay = null;
4809
- var canvas = null;
4810
- var ctx = null;
4811
- var screenshotImg = null;
4812
- var currentTool = "freehand";
4813
- var currentColor = "#ef4444";
4814
- var lineWidth = 3;
4815
- var shapes = [];
4816
- var currentShape = null;
4817
- var isDrawing = false;
4818
- var resolvePromise = null;
4819
- var TOOLS = [
4820
- { id: "freehand", label: "Draw", icon: "M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25z" },
4821
- { id: "rectangle", label: "Rect", icon: "M3 3h18v18H3V3zm2 2v14h14V5H5z" },
4822
- { id: "circle", label: "Circle", icon: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" },
4823
- { id: "arrow", label: "Arrow", icon: "M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" }
4824
- ];
4825
- var COLORS = ["#ef4444", "#f97316", "#eab308", "#22c55e", "#3b82f6", "#8b5cf6", "#ffffff", "#000000"];
4826
- function openAnnotationEditor(screenshotDataUrl) {
4827
- return new Promise((resolve) => {
4828
- resolvePromise = resolve;
4829
- shapes = [];
4830
- currentShape = null;
4831
- currentTool = "freehand";
4832
- currentColor = "#ef4444";
4833
- createOverlay2(screenshotDataUrl);
4834
- });
4835
- }
4836
- function createOverlay2(screenshotDataUrl) {
4837
- if (overlay) overlay.remove();
4838
- overlay = document.createElement("div");
4839
- overlay.id = "bs-annotation-overlay";
4840
- const style = document.createElement("style");
4841
- style.textContent = `
1736
+ Pins ${x?"ON":"OFF"}
1737
+ `)}),m.querySelector("[data-bs-logout]")?.addEventListener("click",()=>{ct(),J(),setTimeout(()=>Le(),300)}),n?Gs():Gn();let g=x=>{x.key==="Escape"&&(J(),document.removeEventListener("keydown",g))};document.addEventListener("keydown",g)}function Gn(){if(!m)return;let e=m.querySelector("#bs-login-submit"),t=m.querySelector("#bs-login-email"),s=m.querySelector("#bs-login-pass"),n=m.querySelector("#bs-login-error");if(!e||!t||!s)return;let r=async()=>{let i=t.value.trim(),c=s.value;if(!i||!c){n.textContent="Please enter email and password",n.style.display="block";return}e.setAttribute("disabled","true"),e.textContent="Signing in...",n.style.display="none";let p=await lt(i,c,Y.projectId);p.success?(xt(Y.projectId),mt(Y.projectId),J(),setTimeout(()=>Le(),300)):(n.textContent=p.error||"Invalid credentials",n.style.display="block",e.removeAttribute("disabled"),e.textContent="Sign In")};e.addEventListener("click",r),s.addEventListener("keydown",i=>{i.key==="Enter"&&r()}),t.addEventListener("keydown",i=>{i.key==="Enter"&&s.focus()}),setTimeout(()=>t.focus(),100)}function J(){fe&&(j&&j.classList.remove("bs-open"),m&&(m.classList.remove("bs-in"),m.classList.add("bs-out")),F&&F.classList.remove("bs-in"),setTimeout(()=>{m?.remove(),F?.remove(),m=null,F=null,fe=!1},350))}function kt(e){for(let[t,s]of Object.entries(Et.vars))e.style.setProperty(t,s)}function Yt(e){let t=pt(e);t&&(Et=t,m&&kt(m),j&&kt(j))}function en(){return Et.id}function tn(e){let t=Oe(e);t&&(ue=t,fe&&(J(),setTimeout(Le,400)))}function sn(){return ue.id}function nn(e){Y=e,We=document.createElement("style"),We.textContent=qn+Hs,document.head.appendChild(We),j=document.createElement("button"),j.className="bs-fab",kt(j),j.innerHTML=`${K.bug}<span class="bs-fab-label">Report a bug</span>`,j.style[e.panelPosition==="bottom-left"?"left":"right"]="24px",j.addEventListener("click",()=>fe?J():Le()),document.body.appendChild(j),_e=t=>{(t.ctrlKey||t.metaKey)&&t.shiftKey&&t.key==="B"&&(t.preventDefault(),fe?J():Le())},document.addEventListener("keydown",_e)}function rn(){J(),j?.remove(),j=null,We?.remove(),We=null,_e&&document.removeEventListener("keydown",_e),_e=null}var Z=null,R=null,E=null,oe=null,Lt="freehand",St="#ef4444",er=3,Ce=[],_=null,Fe=!1,Ct=null,tr=[{id:"freehand",label:"Draw",icon:"M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25z"},{id:"rectangle",label:"Rect",icon:"M3 3h18v18H3V3zm2 2v14h14V5H5z"},{id:"circle",label:"Circle",icon:"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"},{id:"arrow",label:"Arrow",icon:"M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"}],sr=["#ef4444","#f97316","#eab308","#22c55e","#3b82f6","#8b5cf6","#ffffff","#000000"];function ln(e){return new Promise(t=>{Ct=t,Ce=[],_=null,Lt="freehand",St="#ef4444",nr(e)})}function nr(e){Z&&Z.remove(),Z=document.createElement("div"),Z.id="bs-annotation-overlay";let t=document.createElement("style");t.textContent=`
4842
1738
  #bs-annotation-overlay {
4843
1739
  position: fixed; inset: 0; z-index: 2147483647;
4844
1740
  background: rgba(0,0,0,0.85);
@@ -4883,304 +1779,5 @@ function createOverlay2(screenshotDataUrl) {
4883
1779
  overflow: hidden; padding: 16px;
4884
1780
  }
4885
1781
  .bs-ann-canvas-wrap canvas { cursor: crosshair; border-radius: 4px; }
4886
- `;
4887
- overlay.appendChild(style);
4888
- const toolbar = document.createElement("div");
4889
- toolbar.className = "bs-ann-toolbar";
4890
- for (const tool of TOOLS) {
4891
- const btn = document.createElement("button");
4892
- btn.className = `bs-ann-tool${tool.id === currentTool ? " active" : ""}`;
4893
- btn.title = tool.label;
4894
- btn.innerHTML = `<svg viewBox="0 0 24 24"><path d="${tool.icon}"/></svg>`;
4895
- btn.addEventListener("click", () => {
4896
- currentTool = tool.id;
4897
- toolbar.querySelectorAll(".bs-ann-tool").forEach((b) => b.classList.remove("active"));
4898
- btn.classList.add("active");
4899
- });
4900
- toolbar.appendChild(btn);
4901
- }
4902
- toolbar.appendChild(createSep());
4903
- for (const color of COLORS) {
4904
- const btn = document.createElement("button");
4905
- btn.className = `bs-ann-color${color === currentColor ? " active" : ""}`;
4906
- btn.style.background = color;
4907
- btn.addEventListener("click", () => {
4908
- currentColor = color;
4909
- toolbar.querySelectorAll(".bs-ann-color").forEach((b) => b.classList.remove("active"));
4910
- btn.classList.add("active");
4911
- });
4912
- toolbar.appendChild(btn);
4913
- }
4914
- toolbar.appendChild(createSep());
4915
- const undoBtn = document.createElement("button");
4916
- undoBtn.className = "bs-ann-btn bs-ann-undo";
4917
- undoBtn.textContent = "Undo";
4918
- undoBtn.addEventListener("click", () => {
4919
- shapes.pop();
4920
- redrawCanvas();
4921
- });
4922
- toolbar.appendChild(undoBtn);
4923
- const saveBtn = document.createElement("button");
4924
- saveBtn.className = "bs-ann-btn bs-ann-save";
4925
- saveBtn.textContent = "Save";
4926
- saveBtn.addEventListener("click", saveAnnotation);
4927
- const cancelBtn = document.createElement("button");
4928
- cancelBtn.className = "bs-ann-btn bs-ann-cancel";
4929
- cancelBtn.textContent = "Cancel";
4930
- cancelBtn.addEventListener("click", () => {
4931
- cleanup();
4932
- resolvePromise?.(null);
4933
- });
4934
- toolbar.appendChild(saveBtn);
4935
- toolbar.appendChild(cancelBtn);
4936
- overlay.appendChild(toolbar);
4937
- const canvasWrap = document.createElement("div");
4938
- canvasWrap.className = "bs-ann-canvas-wrap";
4939
- canvas = document.createElement("canvas");
4940
- ctx = canvas.getContext("2d");
4941
- canvasWrap.appendChild(canvas);
4942
- overlay.appendChild(canvasWrap);
4943
- document.body.appendChild(overlay);
4944
- screenshotImg = new Image();
4945
- screenshotImg.onload = () => {
4946
- if (!canvas || !ctx || !screenshotImg) return;
4947
- const maxW = window.innerWidth - 32;
4948
- const maxH = window.innerHeight - 100;
4949
- let w = screenshotImg.width;
4950
- let h = screenshotImg.height;
4951
- const scale = Math.min(1, maxW / w, maxH / h);
4952
- w = Math.round(w * scale);
4953
- h = Math.round(h * scale);
4954
- canvas.width = w;
4955
- canvas.height = h;
4956
- redrawCanvas();
4957
- setupCanvasEvents();
4958
- };
4959
- screenshotImg.src = screenshotDataUrl;
4960
- }
4961
- function createSep() {
4962
- const sep = document.createElement("div");
4963
- sep.className = "bs-ann-sep";
4964
- return sep;
4965
- }
4966
- function setupCanvasEvents() {
4967
- if (!canvas) return;
4968
- canvas.addEventListener("mousedown", (e) => {
4969
- isDrawing = true;
4970
- const { x, y } = getCanvasPos(e);
4971
- currentShape = {
4972
- type: currentTool,
4973
- color: currentColor,
4974
- lineWidth,
4975
- points: [{ x, y }],
4976
- x,
4977
- y,
4978
- width: 0,
4979
- height: 0
4980
- };
4981
- });
4982
- canvas.addEventListener("mousemove", (e) => {
4983
- if (!isDrawing || !currentShape) return;
4984
- const { x, y } = getCanvasPos(e);
4985
- if (currentShape.type === "freehand") {
4986
- currentShape.points.push({ x, y });
4987
- } else {
4988
- currentShape.width = x - currentShape.x;
4989
- currentShape.height = y - currentShape.y;
4990
- }
4991
- redrawCanvas();
4992
- drawShape(currentShape);
4993
- });
4994
- const endDraw = () => {
4995
- if (isDrawing && currentShape) {
4996
- shapes.push(currentShape);
4997
- currentShape = null;
4998
- }
4999
- isDrawing = false;
5000
- redrawCanvas();
5001
- };
5002
- canvas.addEventListener("mouseup", endDraw);
5003
- canvas.addEventListener("mouseleave", endDraw);
5004
- }
5005
- function getCanvasPos(e) {
5006
- const rect = canvas.getBoundingClientRect();
5007
- return {
5008
- x: e.clientX - rect.left,
5009
- y: e.clientY - rect.top
5010
- };
5011
- }
5012
- function redrawCanvas() {
5013
- if (!ctx || !canvas || !screenshotImg) return;
5014
- ctx.clearRect(0, 0, canvas.width, canvas.height);
5015
- ctx.drawImage(screenshotImg, 0, 0, canvas.width, canvas.height);
5016
- for (const shape of shapes) {
5017
- drawShape(shape);
5018
- }
5019
- }
5020
- function drawShape(shape) {
5021
- if (!ctx) return;
5022
- ctx.strokeStyle = shape.color;
5023
- ctx.fillStyle = shape.color;
5024
- ctx.lineWidth = shape.lineWidth;
5025
- ctx.lineCap = "round";
5026
- ctx.lineJoin = "round";
5027
- switch (shape.type) {
5028
- case "freehand": {
5029
- if (!shape.points || shape.points.length < 2) return;
5030
- ctx.beginPath();
5031
- ctx.moveTo(shape.points[0].x, shape.points[0].y);
5032
- for (let i = 1; i < shape.points.length; i++) {
5033
- ctx.lineTo(shape.points[i].x, shape.points[i].y);
5034
- }
5035
- ctx.stroke();
5036
- break;
5037
- }
5038
- case "rectangle": {
5039
- ctx.strokeRect(shape.x, shape.y, shape.width, shape.height);
5040
- break;
5041
- }
5042
- case "circle": {
5043
- const cx = shape.x + shape.width / 2;
5044
- const cy = shape.y + shape.height / 2;
5045
- const rx = Math.abs(shape.width) / 2;
5046
- const ry = Math.abs(shape.height) / 2;
5047
- ctx.beginPath();
5048
- ctx.ellipse(cx, cy, rx, ry, 0, 0, Math.PI * 2);
5049
- ctx.stroke();
5050
- break;
5051
- }
5052
- case "arrow": {
5053
- const startX = shape.x;
5054
- const startY = shape.y;
5055
- const endX = shape.x + shape.width;
5056
- const endY = shape.y + shape.height;
5057
- const headLen = 14;
5058
- const angle = Math.atan2(endY - startY, endX - startX);
5059
- ctx.beginPath();
5060
- ctx.moveTo(startX, startY);
5061
- ctx.lineTo(endX, endY);
5062
- ctx.stroke();
5063
- ctx.beginPath();
5064
- ctx.moveTo(endX, endY);
5065
- ctx.lineTo(endX - headLen * Math.cos(angle - Math.PI / 6), endY - headLen * Math.sin(angle - Math.PI / 6));
5066
- ctx.lineTo(endX - headLen * Math.cos(angle + Math.PI / 6), endY - headLen * Math.sin(angle + Math.PI / 6));
5067
- ctx.closePath();
5068
- ctx.fill();
5069
- break;
5070
- }
5071
- }
5072
- }
5073
- function saveAnnotation() {
5074
- if (!canvas) {
5075
- cleanup();
5076
- resolvePromise?.(null);
5077
- return;
5078
- }
5079
- const dataUrl = canvas.toDataURL("image/png");
5080
- const result = { dataUrl, annotations: shapes };
5081
- cleanup();
5082
- resolvePromise?.(result);
5083
- }
5084
- function cleanup() {
5085
- overlay?.remove();
5086
- overlay = null;
5087
- canvas = null;
5088
- ctx = null;
5089
- screenshotImg = null;
5090
- shapes = [];
5091
- currentShape = null;
5092
- isDrawing = false;
5093
- }
5094
-
5095
- // src/index.ts
5096
- var initialized = false;
5097
- function detectEnvironment() {
5098
- if (typeof window === "undefined") return "production";
5099
- const host = window.location.hostname;
5100
- if (host === "localhost" || host === "127.0.0.1" || host === "0.0.0.0" || host.endsWith(".local") || /^192\.168\./.test(host) || /^10\./.test(host) || /^172\.(1[6-9]|2\d|3[01])\./.test(host)) return "development";
5101
- if (host.includes("staging") || host.includes("stage") || host.includes("preview") || host.includes("preprod") || host.includes("pre-prod") || host.includes("qa.") || host.includes(".qa") || host.includes("test.") || host.includes(".dev.") || host.includes("vercel.app") || // Vercel preview deployments
5102
- host.includes("netlify.app") || // Netlify preview deployments
5103
- host.includes("pages.dev") || // Cloudflare Pages previews
5104
- host.includes("ngrok.io") || // ngrok tunnels
5105
- host.includes("ngrok-free.app") || // ngrok free tier
5106
- host.includes("localhost.run") || // localhost.run tunnels
5107
- host.includes("loca.lt")) return "staging";
5108
- return "production";
5109
- }
5110
- var BugStash = {
5111
- init(options) {
5112
- if (initialized) return;
5113
- if (typeof window === "undefined") return;
5114
- const env = options.environment ?? detectEnvironment();
5115
- if (env === "production") return;
5116
- initialized = true;
5117
- if (options.endpoint) setEndpoint(options.endpoint);
5118
- initBreadcrumbs(options.maxBreadcrumbs);
5119
- initLogger(options.maxLogs);
5120
- initNetwork(options.maxNetworkCaptures);
5121
- initErrors();
5122
- if (options.enablePerformance !== false) initPerformance();
5123
- initPanel(options);
5124
- if (options.enableLivePins !== false) {
5125
- const user = getCurrentUser();
5126
- if (user) {
5127
- initLivePins(options.projectId);
5128
- connectRealtime(options.projectId);
5129
- }
5130
- }
5131
- },
5132
- destroy() {
5133
- if (!initialized) return;
5134
- destroyPanel();
5135
- destroyLivePins();
5136
- disconnectRealtime();
5137
- restoreConsole();
5138
- restoreNetwork();
5139
- restoreErrors();
5140
- restoreBreadcrumbs();
5141
- restorePerformance();
5142
- initialized = false;
5143
- },
5144
- // Programmatic API
5145
- getLogs,
5146
- clearLogs,
5147
- getNetworkCaptures,
5148
- getFailedNetworkCaptures,
5149
- clearNetworkCaptures,
5150
- getErrors,
5151
- clearErrors,
5152
- getBreadcrumbs,
5153
- clearBreadcrumbs,
5154
- getPerformanceMetrics,
5155
- addBreadcrumb,
5156
- // Theme API
5157
- getThemes,
5158
- getThemeById,
5159
- setTheme,
5160
- getCurrentThemeId,
5161
- // Layout API
5162
- getLayouts,
5163
- getLayoutById,
5164
- setLayout,
5165
- getCurrentLayoutId,
5166
- // Auth API
5167
- getCurrentUser,
5168
- login,
5169
- logout,
5170
- // Live Pins API
5171
- togglePinMode,
5172
- isPinModeActive,
5173
- // Real-time
5174
- isConnected,
5175
- // Annotation
5176
- openAnnotationEditor,
5177
- // PII Redaction
5178
- redactString,
5179
- redactObject
5180
- };
5181
- var index_default = BugStash;
5182
- // Annotate the CommonJS export names for ESM import in node:
5183
- 0 && (module.exports = {
5184
- BugStash
5185
- });
1782
+ `,Z.appendChild(t);let s=document.createElement("div");s.className="bs-ann-toolbar";for(let p of tr){let l=document.createElement("button");l.className=`bs-ann-tool${p.id===Lt?" active":""}`,l.title=p.label,l.innerHTML=`<svg viewBox="0 0 24 24"><path d="${p.icon}"/></svg>`,l.addEventListener("click",()=>{Lt=p.id,s.querySelectorAll(".bs-ann-tool").forEach(h=>h.classList.remove("active")),l.classList.add("active")}),s.appendChild(l)}s.appendChild(on());for(let p of sr){let l=document.createElement("button");l.className=`bs-ann-color${p===St?" active":""}`,l.style.background=p,l.addEventListener("click",()=>{St=p,s.querySelectorAll(".bs-ann-color").forEach(h=>h.classList.remove("active")),l.classList.add("active")}),s.appendChild(l)}s.appendChild(on());let n=document.createElement("button");n.className="bs-ann-btn bs-ann-undo",n.textContent="Undo",n.addEventListener("click",()=>{Ce.pop(),Tt()}),s.appendChild(n);let r=document.createElement("button");r.className="bs-ann-btn bs-ann-save",r.textContent="Save",r.addEventListener("click",or);let i=document.createElement("button");i.className="bs-ann-btn bs-ann-cancel",i.textContent="Cancel",i.addEventListener("click",()=>{Jt(),Ct?.(null)}),s.appendChild(r),s.appendChild(i),Z.appendChild(s);let c=document.createElement("div");c.className="bs-ann-canvas-wrap",R=document.createElement("canvas"),E=R.getContext("2d"),c.appendChild(R),Z.appendChild(c),document.body.appendChild(Z),oe=new Image,oe.onload=()=>{if(!R||!E||!oe)return;let p=window.innerWidth-32,l=window.innerHeight-100,h=oe.width,g=oe.height,x=Math.min(1,p/h,l/g);h=Math.round(h*x),g=Math.round(g*x),R.width=h,R.height=g,Tt(),rr()},oe.src=e}function on(){let e=document.createElement("div");return e.className="bs-ann-sep",e}function rr(){if(!R)return;R.addEventListener("mousedown",t=>{Fe=!0;let{x:s,y:n}=an(t);_={type:Lt,color:St,lineWidth:er,points:[{x:s,y:n}],x:s,y:n,width:0,height:0}}),R.addEventListener("mousemove",t=>{if(!Fe||!_)return;let{x:s,y:n}=an(t);_.type==="freehand"?_.points.push({x:s,y:n}):(_.width=s-_.x,_.height=n-_.y),Tt(),cn(_)});let e=()=>{Fe&&_&&(Ce.push(_),_=null),Fe=!1,Tt()};R.addEventListener("mouseup",e),R.addEventListener("mouseleave",e)}function an(e){let t=R.getBoundingClientRect();return{x:e.clientX-t.left,y:e.clientY-t.top}}function Tt(){if(!(!E||!R||!oe)){E.clearRect(0,0,R.width,R.height),E.drawImage(oe,0,0,R.width,R.height);for(let e of Ce)cn(e)}}function cn(e){if(E)switch(E.strokeStyle=e.color,E.fillStyle=e.color,E.lineWidth=e.lineWidth,E.lineCap="round",E.lineJoin="round",e.type){case"freehand":{if(!e.points||e.points.length<2)return;E.beginPath(),E.moveTo(e.points[0].x,e.points[0].y);for(let t=1;t<e.points.length;t++)E.lineTo(e.points[t].x,e.points[t].y);E.stroke();break}case"rectangle":{E.strokeRect(e.x,e.y,e.width,e.height);break}case"circle":{let t=e.x+e.width/2,s=e.y+e.height/2,n=Math.abs(e.width)/2,r=Math.abs(e.height)/2;E.beginPath(),E.ellipse(t,s,n,r,0,0,Math.PI*2),E.stroke();break}case"arrow":{let t=e.x,s=e.y,n=e.x+e.width,r=e.y+e.height,i=14,c=Math.atan2(r-s,n-t);E.beginPath(),E.moveTo(t,s),E.lineTo(n,r),E.stroke(),E.beginPath(),E.moveTo(n,r),E.lineTo(n-i*Math.cos(c-Math.PI/6),r-i*Math.sin(c-Math.PI/6)),E.lineTo(n-i*Math.cos(c+Math.PI/6),r-i*Math.sin(c+Math.PI/6)),E.closePath(),E.fill();break}}}function or(){if(!R){Jt(),Ct?.(null);return}let t={dataUrl:R.toDataURL("image/png"),annotations:Ce};Jt(),Ct?.(t)}function Jt(){Z?.remove(),Z=null,R=null,E=null,oe=null,Ce=[],_=null,Fe=!1}var Mt=!1;function ar(){if(typeof window>"u")return"production";let e=window.location.hostname;return e==="localhost"||e==="127.0.0.1"||e==="0.0.0.0"||e.endsWith(".local")||/^192\.168\./.test(e)||/^10\./.test(e)||/^172\.(1[6-9]|2\d|3[01])\./.test(e)?"development":e.includes("staging")||e.includes("stage")||e.includes("preview")||e.includes("preprod")||e.includes("pre-prod")||e.includes("qa.")||e.includes(".qa")||e.includes("test.")||e.includes(".dev.")||e.includes("vercel.app")||e.includes("netlify.app")||e.includes("pages.dev")||e.includes("ngrok.io")||e.includes("ngrok-free.app")||e.includes("localhost.run")||e.includes("loca.lt")?"staging":"production"}var dn={init(e){Mt||typeof window>"u"||(e.environment??ar())==="production"||(Mt=!0,e.endpoint&&Es(e.endpoint),rs(e.maxBreadcrumbs),cs(e.maxLogs),fs(e.maxNetworkCaptures),hs(),e.enablePerformance!==!1&&ys(),nn(e),e.enableLivePins!==!1&&V()&&(xt(e.projectId),mt(e.projectId)))},destroy(){Mt&&(rn(),Fs(),Ds(),bs(),ms(),vs(),as(),ws(),Mt=!1)},getLogs:ee,clearLogs:ds,getNetworkCaptures:ve,getFailedNetworkCaptures:le,clearNetworkCaptures:gs,getErrors:te,clearErrors:xs,getBreadcrumbs:me,clearBreadcrumbs:os,getPerformanceMetrics:ye,addBreadcrumb:q,getThemes:bt,getThemeById:pt,setTheme:Yt,getCurrentThemeId:en,getLayouts:ut,getLayoutById:Oe,setLayout:tn,getCurrentLayoutId:sn,getCurrentUser:V,login:lt,logout:ct,togglePinMode:vt,isPinModeActive:yt,isConnected:js,openAnnotationEditor:ln,redactString:he,redactObject:Qe},ir=dn;0&&(module.exports={BugStash});
5186
1783
  //# sourceMappingURL=index.cjs.map