glitchgrab 1.26.0 → 1.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/index.ts
32
32
  var index_exports = {};
33
33
  __export(index_exports, {
34
+ GLITCHGRAB_SHORTCUT: () => GLITCHGRAB_SHORTCUT,
35
+ GLITCHGRAB_SHORTCUT_MAC: () => GLITCHGRAB_SHORTCUT_MAC,
34
36
  GlitchgrabErrorBoundary: () => GlitchgrabErrorBoundary,
35
37
  GlitchgrabProvider: () => GlitchgrabProvider,
36
38
  ReportButton: () => ReportButton,
@@ -43,7 +45,9 @@ __export(index_exports, {
43
45
  enhanceText: () => enhanceText,
44
46
  fetchGlitchgrabReports: () => fetchGlitchgrabReports,
45
47
  getBreadcrumbs: () => getBreadcrumbs,
48
+ getShortcutLabel: () => getShortcutLabel,
46
49
  initBreadcrumbs: () => initBreadcrumbs,
50
+ matchesShortcut: () => matchesShortcut,
47
51
  sanitizeUrl: () => sanitizeUrl,
48
52
  sendReport: () => sendReport,
49
53
  shouldSkipDuplicate: () => shouldSkipDuplicate,
@@ -54,7 +58,7 @@ __export(index_exports, {
54
58
  module.exports = __toCommonJS(index_exports);
55
59
 
56
60
  // src/provider.tsx
57
- var import_react2 = require("react");
61
+ var import_react3 = require("react");
58
62
 
59
63
  // src/error-boundary.tsx
60
64
  var React = __toESM(require("react"));
@@ -499,9 +503,308 @@ var GlitchgrabErrorBoundary = class extends React.Component {
499
503
  };
500
504
 
501
505
  // src/report-dialog.tsx
502
- var import_react = require("react");
506
+ var import_react2 = require("react");
503
507
  var import_react_dom = require("react-dom");
508
+
509
+ // src/annotation-canvas.tsx
510
+ var import_react = require("react");
504
511
  var import_jsx_runtime = require("react/jsx-runtime");
512
+ var PALETTE = ["#ef4444", "#f59e0b", "#22c55e", "#3b82f6", "#a855f7", "#ffffff"];
513
+ function drawStroke(ctx, s) {
514
+ ctx.strokeStyle = s.color;
515
+ ctx.lineWidth = s.width;
516
+ ctx.lineCap = "round";
517
+ ctx.lineJoin = "round";
518
+ if (s.type === "freehand") {
519
+ if (s.points.length < 2) return;
520
+ ctx.beginPath();
521
+ ctx.moveTo(s.points[0].x, s.points[0].y);
522
+ for (let i = 1; i < s.points.length; i++) ctx.lineTo(s.points[i].x, s.points[i].y);
523
+ ctx.stroke();
524
+ } else {
525
+ ctx.beginPath();
526
+ ctx.arc(s.cx, s.cy, s.r, 0, Math.PI * 2);
527
+ ctx.stroke();
528
+ }
529
+ }
530
+ function AnnotationCanvas({ imageSrc, onSave, onCancel }) {
531
+ const canvasRef = (0, import_react.useRef)(null);
532
+ const imgRef = (0, import_react.useRef)(null);
533
+ const [ready, setReady] = (0, import_react.useState)(false);
534
+ const [tool, setTool] = (0, import_react.useState)("pen");
535
+ const [color, setColor] = (0, import_react.useState)(PALETTE[0]);
536
+ const [strokeWidth, setStrokeWidth] = (0, import_react.useState)(4);
537
+ const [strokes, setStrokes] = (0, import_react.useState)([]);
538
+ const [currentStroke, setCurrentStroke] = (0, import_react.useState)(null);
539
+ (0, import_react.useEffect)(() => {
540
+ const img = new Image();
541
+ img.onload = () => {
542
+ imgRef.current = img;
543
+ const canvas = canvasRef.current;
544
+ if (canvas) {
545
+ canvas.width = img.naturalWidth;
546
+ canvas.height = img.naturalHeight;
547
+ }
548
+ setReady(true);
549
+ };
550
+ img.src = imageSrc;
551
+ }, [imageSrc]);
552
+ (0, import_react.useEffect)(() => {
553
+ if (!ready) return;
554
+ const canvas = canvasRef.current;
555
+ const img = imgRef.current;
556
+ if (!canvas || !img) return;
557
+ const ctx = canvas.getContext("2d");
558
+ if (!ctx) return;
559
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
560
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
561
+ for (const s of strokes) drawStroke(ctx, s);
562
+ if (currentStroke) drawStroke(ctx, currentStroke);
563
+ }, [ready, strokes, currentStroke]);
564
+ const getPos = (e) => {
565
+ const canvas = e.currentTarget;
566
+ const rect = canvas.getBoundingClientRect();
567
+ return {
568
+ x: (e.clientX - rect.left) * canvas.width / rect.width,
569
+ y: (e.clientY - rect.top) * canvas.height / rect.height
570
+ };
571
+ };
572
+ const handlePointerDown = (e) => {
573
+ e.currentTarget.setPointerCapture(e.pointerId);
574
+ const { x, y } = getPos(e);
575
+ setCurrentStroke(
576
+ tool === "pen" ? { type: "freehand", color, width: strokeWidth, points: [{ x, y }] } : { type: "circle", color, width: strokeWidth, cx: x, cy: y, r: 0 }
577
+ );
578
+ };
579
+ const handlePointerMove = (e) => {
580
+ if (!currentStroke) return;
581
+ const { x, y } = getPos(e);
582
+ setCurrentStroke((prev) => {
583
+ if (!prev) return prev;
584
+ if (prev.type === "freehand") return { ...prev, points: [...prev.points, { x, y }] };
585
+ return { ...prev, r: Math.hypot(x - prev.cx, y - prev.cy) };
586
+ });
587
+ };
588
+ const handlePointerUp = () => {
589
+ setCurrentStroke((prev) => {
590
+ if (prev) setStrokes((s) => [...s, prev]);
591
+ return null;
592
+ });
593
+ };
594
+ const handleSave = () => {
595
+ const canvas = canvasRef.current;
596
+ if (!canvas) return;
597
+ onSave(canvas.toDataURL("image/jpeg", 0.85));
598
+ };
599
+ const iconBtn = (active = false) => ({
600
+ width: "30px",
601
+ height: "30px",
602
+ display: "flex",
603
+ alignItems: "center",
604
+ justifyContent: "center",
605
+ borderRadius: "6px",
606
+ border: `1.5px solid ${active ? "#22d3ee" : "rgba(255,255,255,0.15)"}`,
607
+ background: active ? "rgba(34,211,238,0.15)" : "transparent",
608
+ color: "#fff",
609
+ cursor: "pointer",
610
+ padding: 0
611
+ });
612
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
613
+ "div",
614
+ {
615
+ style: {
616
+ position: "fixed",
617
+ inset: 0,
618
+ zIndex: 2147483647,
619
+ backgroundColor: "rgba(0,0,0,0.9)",
620
+ display: "flex",
621
+ flexDirection: "column",
622
+ alignItems: "center",
623
+ justifyContent: "center",
624
+ padding: "16px"
625
+ },
626
+ onClick: (e) => {
627
+ e.stopPropagation();
628
+ if (e.target === e.currentTarget) onCancel();
629
+ },
630
+ onPointerDown: (e) => e.stopPropagation(),
631
+ children: [
632
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
633
+ "canvas",
634
+ {
635
+ ref: canvasRef,
636
+ onPointerDown: handlePointerDown,
637
+ onPointerMove: handlePointerMove,
638
+ onPointerUp: handlePointerUp,
639
+ style: {
640
+ maxWidth: "100%",
641
+ maxHeight: "calc(100vh - 140px)",
642
+ borderRadius: "8px",
643
+ touchAction: "none",
644
+ cursor: "crosshair",
645
+ display: "block"
646
+ }
647
+ }
648
+ ),
649
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
650
+ "div",
651
+ {
652
+ style: {
653
+ marginTop: "14px",
654
+ display: "flex",
655
+ alignItems: "center",
656
+ gap: "8px",
657
+ flexWrap: "wrap",
658
+ justifyContent: "center",
659
+ maxWidth: "100%"
660
+ },
661
+ children: [
662
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => setTool("pen"), style: iconBtn(tool === "pen"), title: "Freehand", "aria-label": "Freehand tool", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 20h9M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4L16.5 3.5z", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) }) }),
663
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: () => setTool("circle"), style: iconBtn(tool === "circle"), title: "Circle", "aria-label": "Circle tool", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "8", stroke: "currentColor", strokeWidth: "1.8" }) }) }),
664
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: "1px", height: "20px", background: "rgba(255,255,255,0.15)" } }),
665
+ PALETTE.map((c) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
666
+ "button",
667
+ {
668
+ type: "button",
669
+ onClick: () => setColor(c),
670
+ "aria-label": `Color ${c}`,
671
+ style: {
672
+ width: "22px",
673
+ height: "22px",
674
+ borderRadius: "50%",
675
+ background: c,
676
+ border: color === c ? "2px solid #22d3ee" : "2px solid rgba(255,255,255,0.3)",
677
+ cursor: "pointer",
678
+ padding: 0
679
+ }
680
+ },
681
+ c
682
+ )),
683
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
684
+ "input",
685
+ {
686
+ type: "color",
687
+ value: color,
688
+ onChange: (e) => setColor(e.target.value),
689
+ title: "Custom color",
690
+ "aria-label": "Custom color",
691
+ style: {
692
+ width: "22px",
693
+ height: "22px",
694
+ padding: 0,
695
+ border: "none",
696
+ background: "none",
697
+ cursor: "pointer"
698
+ }
699
+ }
700
+ ),
701
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: "1px", height: "20px", background: "rgba(255,255,255,0.15)" } }),
702
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
703
+ "input",
704
+ {
705
+ type: "range",
706
+ min: 2,
707
+ max: 12,
708
+ value: strokeWidth,
709
+ onChange: (e) => setStrokeWidth(Number(e.target.value)),
710
+ title: "Stroke width",
711
+ "aria-label": "Stroke width",
712
+ style: { width: "70px" }
713
+ }
714
+ ),
715
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { width: "1px", height: "20px", background: "rgba(255,255,255,0.15)" } }),
716
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
717
+ "button",
718
+ {
719
+ type: "button",
720
+ onClick: () => setStrokes((s) => s.slice(0, -1)),
721
+ disabled: strokes.length === 0,
722
+ style: { ...iconBtn(), opacity: strokes.length === 0 ? 0.35 : 1 },
723
+ title: "Undo",
724
+ "aria-label": "Undo",
725
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M9 14L4 9l5-5M4 9h10a6 6 0 010 12h-2", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) })
726
+ }
727
+ ),
728
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
729
+ "button",
730
+ {
731
+ type: "button",
732
+ onClick: () => setStrokes([]),
733
+ disabled: strokes.length === 0,
734
+ style: { ...iconBtn(), opacity: strokes.length === 0 ? 0.35 : 1 },
735
+ title: "Clear all",
736
+ "aria-label": "Clear all",
737
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M3 6h18M8 6V4a1 1 0 011-1h6a1 1 0 011 1v2m2 0v14a1 1 0 01-1 1H7a1 1 0 01-1-1V6h12z", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }) })
738
+ }
739
+ )
740
+ ]
741
+ }
742
+ ),
743
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: "12px", display: "flex", gap: "8px" }, children: [
744
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
745
+ "button",
746
+ {
747
+ type: "button",
748
+ onClick: onCancel,
749
+ style: {
750
+ padding: "8px 16px",
751
+ borderRadius: "8px",
752
+ border: "1.5px solid rgba(255,255,255,0.2)",
753
+ background: "transparent",
754
+ color: "#fff",
755
+ fontSize: "13px",
756
+ fontWeight: 600,
757
+ cursor: "pointer",
758
+ fontFamily: "inherit"
759
+ },
760
+ children: "Cancel"
761
+ }
762
+ ),
763
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
764
+ "button",
765
+ {
766
+ type: "button",
767
+ onClick: handleSave,
768
+ style: {
769
+ padding: "8px 16px",
770
+ borderRadius: "8px",
771
+ border: "none",
772
+ background: "#22d3ee",
773
+ color: "#09090b",
774
+ fontSize: "13px",
775
+ fontWeight: 600,
776
+ cursor: "pointer",
777
+ fontFamily: "inherit"
778
+ },
779
+ children: "Save annotation"
780
+ }
781
+ )
782
+ ] })
783
+ ]
784
+ }
785
+ );
786
+ }
787
+
788
+ // src/shortcut.ts
789
+ var GLITCHGRAB_SHORTCUT = "Ctrl+Shift+G";
790
+ var GLITCHGRAB_SHORTCUT_MAC = "\u2318\u21E7G";
791
+ function isMac() {
792
+ try {
793
+ if (typeof navigator === "undefined") return false;
794
+ return /Mac|iPhone|iPad|iPod/i.test(navigator.platform || navigator.userAgent);
795
+ } catch (e) {
796
+ return false;
797
+ }
798
+ }
799
+ function getShortcutLabel() {
800
+ return isMac() ? GLITCHGRAB_SHORTCUT_MAC : GLITCHGRAB_SHORTCUT;
801
+ }
802
+ function matchesShortcut(e) {
803
+ return (e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "g";
804
+ }
805
+
806
+ // src/report-dialog.tsx
807
+ var import_jsx_runtime2 = require("react/jsx-runtime");
505
808
  function useIsDark() {
506
809
  if (typeof window === "undefined") return true;
507
810
  try {
@@ -690,39 +993,40 @@ function ReportDialog({
690
993
  types,
691
994
  showSeverity = true
692
995
  }) {
693
- const [isEnhancing, setIsEnhancing] = (0, import_react.useState)(false);
694
- const [isEnhanced, setIsEnhanced] = (0, import_react.useState)(false);
695
- const [originalDescription, setOriginalDescription] = (0, import_react.useState)(
996
+ const [isEnhancing, setIsEnhancing] = (0, import_react2.useState)(false);
997
+ const [isEnhanced, setIsEnhanced] = (0, import_react2.useState)(false);
998
+ const [originalDescription, setOriginalDescription] = (0, import_react2.useState)(
696
999
  null
697
1000
  );
698
- const [isListening, setIsListening] = (0, import_react.useState)(false);
699
- const [isTranscribing, setIsTranscribing] = (0, import_react.useState)(false);
700
- const [voiceError, setVoiceError] = (0, import_react.useState)(null);
701
- const [screenshotDragOver, setScreenshotDragOver] = (0, import_react.useState)(false);
702
- const [mounted, setMounted] = (0, import_react.useState)(false);
703
- (0, import_react.useEffect)(() => {
1001
+ const [isListening, setIsListening] = (0, import_react2.useState)(false);
1002
+ const [isTranscribing, setIsTranscribing] = (0, import_react2.useState)(false);
1003
+ const [voiceError, setVoiceError] = (0, import_react2.useState)(null);
1004
+ const [screenshotDragOver, setScreenshotDragOver] = (0, import_react2.useState)(false);
1005
+ const [mounted, setMounted] = (0, import_react2.useState)(false);
1006
+ (0, import_react2.useEffect)(() => {
704
1007
  setMounted(true);
705
1008
  }, []);
706
- const [isOpen, setIsOpen] = (0, import_react.useState)(false);
707
- const [description, setDescription] = (0, import_react.useState)("");
708
- const [isSubmitting, setIsSubmitting] = (0, import_react.useState)(false);
709
- const [submitted, setSubmitted] = (0, import_react.useState)(false);
710
- const [screenshots, setScreenshots] = (0, import_react.useState)([]);
711
- const [previewIndex, setPreviewIndex] = (0, import_react.useState)(null);
712
- const fileInputRef = (0, import_react.useRef)(null);
713
- const modalRef = (0, import_react.useRef)(null);
714
- const previewImgRef = (0, import_react.useRef)(null);
715
- const textareaRef = (0, import_react.useRef)(null);
716
- const recognitionRef = (0, import_react.useRef)(null);
717
- const voiceBaseRef = (0, import_react.useRef)("");
718
- const usingWebSpeechRef = (0, import_react.useRef)(false);
719
- const textBeforeVoiceRef = (0, import_react.useRef)("");
720
- const mediaRecorderRef = (0, import_react.useRef)(null);
721
- const streamRef = (0, import_react.useRef)(null);
722
- const sarvamChunksRef = (0, import_react.useRef)([]);
723
- const spaceTimerRef = (0, import_react.useRef)(null);
724
- const isPushToTalkRef = (0, import_react.useRef)(false);
725
- (0, import_react.useEffect)(() => {
1009
+ const [isOpen, setIsOpen] = (0, import_react2.useState)(false);
1010
+ const [description, setDescription] = (0, import_react2.useState)("");
1011
+ const [isSubmitting, setIsSubmitting] = (0, import_react2.useState)(false);
1012
+ const [submitted, setSubmitted] = (0, import_react2.useState)(false);
1013
+ const [screenshots, setScreenshots] = (0, import_react2.useState)([]);
1014
+ const [previewIndex, setPreviewIndex] = (0, import_react2.useState)(null);
1015
+ const [annotatingIndex, setAnnotatingIndex] = (0, import_react2.useState)(null);
1016
+ const fileInputRef = (0, import_react2.useRef)(null);
1017
+ const modalRef = (0, import_react2.useRef)(null);
1018
+ const previewImgRef = (0, import_react2.useRef)(null);
1019
+ const textareaRef = (0, import_react2.useRef)(null);
1020
+ const recognitionRef = (0, import_react2.useRef)(null);
1021
+ const voiceBaseRef = (0, import_react2.useRef)("");
1022
+ const usingWebSpeechRef = (0, import_react2.useRef)(false);
1023
+ const textBeforeVoiceRef = (0, import_react2.useRef)("");
1024
+ const mediaRecorderRef = (0, import_react2.useRef)(null);
1025
+ const streamRef = (0, import_react2.useRef)(null);
1026
+ const sarvamChunksRef = (0, import_react2.useRef)([]);
1027
+ const spaceTimerRef = (0, import_react2.useRef)(null);
1028
+ const isPushToTalkRef = (0, import_react2.useRef)(false);
1029
+ (0, import_react2.useEffect)(() => {
726
1030
  if (!isOpen) return;
727
1031
  const dialogs = Array.from(
728
1032
  document.querySelectorAll(
@@ -732,14 +1036,17 @@ function ReportDialog({
732
1036
  dialogs.forEach((d) => d.setAttribute("inert", ""));
733
1037
  return () => dialogs.forEach((d) => d.removeAttribute("inert"));
734
1038
  }, [isOpen]);
735
- const [step, setStep] = (0, import_react.useState)(1);
736
- const [reportType, setReportType] = (0, import_react.useState)("BUG");
737
- const [severity, setSeverity] = (0, import_react.useState)("medium");
738
- const [validationError, setValidationError] = (0, import_react.useState)(null);
739
- const [placeholderIdx, setPlaceholderIdx] = (0, import_react.useState)(0);
1039
+ const [step, setStep] = (0, import_react2.useState)(1);
1040
+ const [reportType, setReportType] = (0, import_react2.useState)("BUG");
1041
+ const [severity, setSeverity] = (0, import_react2.useState)("medium");
1042
+ const [validationError, setValidationError] = (0, import_react2.useState)(null);
1043
+ const [placeholderIdx, setPlaceholderIdx] = (0, import_react2.useState)(0);
740
1044
  const availableTypes = types != null ? types : [
741
1045
  "BUG",
742
1046
  "FEATURE_REQUEST",
1047
+ "UI_IMPROVEMENT",
1048
+ "PERFORMANCE",
1049
+ "SECURITY",
743
1050
  "QUESTION",
744
1051
  "OTHER"
745
1052
  ];
@@ -768,7 +1075,13 @@ function ReportDialog({
768
1075
  scale: 0.5,
769
1076
  logging: false,
770
1077
  useCORS: true,
771
- allowTaint: true
1078
+ allowTaint: true,
1079
+ x: window.scrollX,
1080
+ y: window.scrollY,
1081
+ width: window.innerWidth,
1082
+ height: window.innerHeight,
1083
+ windowWidth: window.innerWidth,
1084
+ windowHeight: window.innerHeight
772
1085
  });
773
1086
  setScreenshots([canvas.toDataURL("image/jpeg", 0.6)]);
774
1087
  } catch (e) {
@@ -787,14 +1100,20 @@ function ReportDialog({
787
1100
  scale: 0.5,
788
1101
  logging: false,
789
1102
  useCORS: true,
790
- allowTaint: true
1103
+ allowTaint: true,
1104
+ x: window.scrollX,
1105
+ y: window.scrollY,
1106
+ width: window.innerWidth,
1107
+ height: window.innerHeight,
1108
+ windowWidth: window.innerWidth,
1109
+ windowHeight: window.innerHeight
791
1110
  });
792
1111
  setScreenshots([canvas.toDataURL("image/jpeg", 0.6)]);
793
1112
  } catch (e) {
794
1113
  }
795
1114
  setIsOpen(true);
796
1115
  };
797
- (0, import_react.useEffect)(() => {
1116
+ (0, import_react2.useEffect)(() => {
798
1117
  const handler = (e) => {
799
1118
  if (isOpen) return;
800
1119
  const detail = e.detail;
@@ -808,7 +1127,7 @@ function ReportDialog({
808
1127
  window.addEventListener("glitchgrab:open-report", handler);
809
1128
  return () => window.removeEventListener("glitchgrab:open-report", handler);
810
1129
  }, [isOpen, handleOpen]);
811
- (0, import_react.useEffect)(() => {
1130
+ (0, import_react2.useEffect)(() => {
812
1131
  if (!isOpen) return;
813
1132
  const handleEsc = (e) => {
814
1133
  if (e.key === "Escape") {
@@ -819,8 +1138,8 @@ function ReportDialog({
819
1138
  document.addEventListener("keydown", handleEsc);
820
1139
  return () => document.removeEventListener("keydown", handleEsc);
821
1140
  }, [isOpen, previewIndex]);
822
- (0, import_react.useEffect)(() => {
823
- if (previewIndex === null) return;
1141
+ (0, import_react2.useEffect)(() => {
1142
+ if (previewIndex === null || annotatingIndex !== null) return;
824
1143
  const handlePointerDown = (e) => {
825
1144
  var _a;
826
1145
  if (!((_a = previewImgRef.current) == null ? void 0 : _a.contains(e.target))) {
@@ -829,8 +1148,8 @@ function ReportDialog({
829
1148
  };
830
1149
  document.addEventListener("pointerdown", handlePointerDown, true);
831
1150
  return () => document.removeEventListener("pointerdown", handlePointerDown, true);
832
- }, [previewIndex]);
833
- (0, import_react.useEffect)(() => {
1151
+ }, [previewIndex, annotatingIndex]);
1152
+ (0, import_react2.useEffect)(() => {
834
1153
  if (!isOpen) return;
835
1154
  const handlePaste = (e) => {
836
1155
  var _a;
@@ -855,7 +1174,7 @@ function ReportDialog({
855
1174
  window.addEventListener("paste", handlePaste);
856
1175
  return () => window.removeEventListener("paste", handlePaste);
857
1176
  }, [isOpen]);
858
- (0, import_react.useEffect)(() => {
1177
+ (0, import_react2.useEffect)(() => {
859
1178
  if (!isOpen || !transcribeAudio2) return;
860
1179
  const id = setInterval(() => {
861
1180
  setPlaceholderIdx((i) => (i + 1) % 2);
@@ -1072,9 +1391,9 @@ function ReportDialog({
1072
1391
  }
1073
1392
  };
1074
1393
  if (!mounted) return null;
1075
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1394
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1076
1395
  isOpen && (0, import_react_dom.createPortal)(
1077
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
1396
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `
1078
1397
  @keyframes gg-pulse{0%,100%{opacity:1}50%{opacity:0.35}}
1079
1398
  @keyframes gg-b1{0%,100%{height:3px}40%{height:13px}}
1080
1399
  @keyframes gg-b2{0%,100%{height:8px}50%{height:3px}}
@@ -1084,7 +1403,7 @@ function ReportDialog({
1084
1403
  document.body
1085
1404
  ),
1086
1405
  isOpen && (0, import_react_dom.createPortal)(
1087
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1406
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1088
1407
  "div",
1089
1408
  {
1090
1409
  style: {
@@ -1103,7 +1422,7 @@ function ReportDialog({
1103
1422
  },
1104
1423
  onPointerDown: (e) => e.stopPropagation(),
1105
1424
  onFocus: (e) => e.stopPropagation(),
1106
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1425
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1107
1426
  "div",
1108
1427
  {
1109
1428
  ref: modalRef,
@@ -1122,7 +1441,7 @@ function ReportDialog({
1122
1441
  isolation: "isolate"
1123
1442
  },
1124
1443
  children: [
1125
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1444
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1126
1445
  "div",
1127
1446
  {
1128
1447
  style: {
@@ -1130,7 +1449,7 @@ function ReportDialog({
1130
1449
  borderBottom: `1px solid ${t.border}`
1131
1450
  },
1132
1451
  children: [
1133
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1452
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1134
1453
  "div",
1135
1454
  {
1136
1455
  style: {
@@ -1139,7 +1458,7 @@ function ReportDialog({
1139
1458
  alignItems: "center"
1140
1459
  },
1141
1460
  children: [
1142
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1461
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1143
1462
  "div",
1144
1463
  {
1145
1464
  style: {
@@ -1148,7 +1467,7 @@ function ReportDialog({
1148
1467
  gap: "8px"
1149
1468
  },
1150
1469
  children: [
1151
- step > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1470
+ step > 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1152
1471
  "button",
1153
1472
  {
1154
1473
  type: "button",
@@ -1162,14 +1481,14 @@ function ReportDialog({
1162
1481
  alignItems: "center"
1163
1482
  },
1164
1483
  "aria-label": "Back",
1165
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1484
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1166
1485
  "svg",
1167
1486
  {
1168
1487
  width: "16",
1169
1488
  height: "16",
1170
1489
  viewBox: "0 0 16 16",
1171
1490
  fill: "none",
1172
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1491
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1173
1492
  "path",
1174
1493
  {
1175
1494
  d: "M10 3L5 8L10 13",
@@ -1183,7 +1502,7 @@ function ReportDialog({
1183
1502
  )
1184
1503
  }
1185
1504
  ),
1186
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1505
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1187
1506
  "span",
1188
1507
  {
1189
1508
  style: {
@@ -1193,11 +1512,29 @@ function ReportDialog({
1193
1512
  },
1194
1513
  children: step === 1 ? "What's on your mind?" : "Tell us more"
1195
1514
  }
1515
+ ),
1516
+ step === 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1517
+ "span",
1518
+ {
1519
+ title: "Press this anywhere to open Glitchgrab",
1520
+ style: {
1521
+ fontSize: "10px",
1522
+ fontWeight: 600,
1523
+ lineHeight: 1,
1524
+ padding: "3px 6px",
1525
+ borderRadius: "4px",
1526
+ border: `1px solid ${t.inputBorder}`,
1527
+ background: t.inputBg,
1528
+ color: t.textMuted,
1529
+ whiteSpace: "nowrap"
1530
+ },
1531
+ children: getShortcutLabel()
1532
+ }
1196
1533
  )
1197
1534
  ]
1198
1535
  }
1199
1536
  ),
1200
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1537
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1201
1538
  "button",
1202
1539
  {
1203
1540
  type: "button",
@@ -1212,7 +1549,7 @@ function ReportDialog({
1212
1549
  justifyContent: "center"
1213
1550
  },
1214
1551
  "aria-label": "Close",
1215
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1552
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1216
1553
  "svg",
1217
1554
  {
1218
1555
  width: "16",
@@ -1220,7 +1557,7 @@ function ReportDialog({
1220
1557
  viewBox: "0 0 16 16",
1221
1558
  fill: "none",
1222
1559
  style: { display: "block" },
1223
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1560
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1224
1561
  "path",
1225
1562
  {
1226
1563
  d: "M4 4L12 12M12 4L4 12",
@@ -1236,7 +1573,7 @@ function ReportDialog({
1236
1573
  ]
1237
1574
  }
1238
1575
  ),
1239
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1576
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1240
1577
  "div",
1241
1578
  {
1242
1579
  style: {
@@ -1246,12 +1583,12 @@ function ReportDialog({
1246
1583
  gap: "0",
1247
1584
  marginTop: "12px"
1248
1585
  },
1249
- children: [1, 2].map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1586
+ children: [1, 2].map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1250
1587
  "div",
1251
1588
  {
1252
1589
  style: { display: "flex", alignItems: "center" },
1253
1590
  children: [
1254
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1591
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1255
1592
  "div",
1256
1593
  {
1257
1594
  style: {
@@ -1263,7 +1600,7 @@ function ReportDialog({
1263
1600
  }
1264
1601
  }
1265
1602
  ),
1266
- i < 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1603
+ i < 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1267
1604
  "div",
1268
1605
  {
1269
1606
  style: {
@@ -1283,7 +1620,7 @@ function ReportDialog({
1283
1620
  ]
1284
1621
  }
1285
1622
  ),
1286
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { padding: "16px" }, children: submitted ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1623
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { padding: "16px" }, children: submitted ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1287
1624
  "div",
1288
1625
  {
1289
1626
  style: {
@@ -1298,8 +1635,8 @@ function ReportDialog({
1298
1635
  " sent. Thank you!"
1299
1636
  ]
1300
1637
  }
1301
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1302
- step === 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1638
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1639
+ step === 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1303
1640
  "div",
1304
1641
  {
1305
1642
  style: {
@@ -1307,7 +1644,7 @@ function ReportDialog({
1307
1644
  gridTemplateColumns: "1fr 1fr",
1308
1645
  gap: "8px"
1309
1646
  },
1310
- children: availableTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1647
+ children: availableTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1311
1648
  "button",
1312
1649
  {
1313
1650
  type: "button",
@@ -1337,8 +1674,8 @@ function ReportDialog({
1337
1674
  },
1338
1675
  children: [
1339
1676
  getTypeIcon(type, t.accent),
1340
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "13px", fontWeight: 600 }, children: getTypeLabel(type) }),
1341
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1677
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: "13px", fontWeight: 600 }, children: getTypeLabel(type) }),
1678
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1342
1679
  "span",
1343
1680
  {
1344
1681
  style: {
@@ -1355,9 +1692,9 @@ function ReportDialog({
1355
1692
  ))
1356
1693
  }
1357
1694
  ),
1358
- step === 2 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1359
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "relative" }, children: [
1360
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1695
+ step === 2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1696
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative" }, children: [
1697
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1361
1698
  "textarea",
1362
1699
  {
1363
1700
  ref: textareaRef,
@@ -1398,7 +1735,7 @@ function ReportDialog({
1398
1735
  autoFocus: true
1399
1736
  }
1400
1737
  ),
1401
- (isListening || isTranscribing) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1738
+ (isListening || isTranscribing) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1402
1739
  "span",
1403
1740
  {
1404
1741
  style: {
@@ -1416,7 +1753,7 @@ function ReportDialog({
1416
1753
  zIndex: 1
1417
1754
  },
1418
1755
  children: [
1419
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1756
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1420
1757
  "span",
1421
1758
  {
1422
1759
  style: {
@@ -1431,7 +1768,7 @@ function ReportDialog({
1431
1768
  }
1432
1769
  ),
1433
1770
  isTranscribing ? "Transcribing\u2026" : "REC",
1434
- isListening && !isTranscribing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1771
+ isListening && !isTranscribing && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1435
1772
  "span",
1436
1773
  {
1437
1774
  style: {
@@ -1458,7 +1795,7 @@ function ReportDialog({
1458
1795
  dur: "0.55s",
1459
1796
  delay: "0.15s"
1460
1797
  }
1461
- ].map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1798
+ ].map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1462
1799
  "span",
1463
1800
  {
1464
1801
  style: {
@@ -1478,7 +1815,7 @@ function ReportDialog({
1478
1815
  ]
1479
1816
  }
1480
1817
  ),
1481
- enhanceText2 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1818
+ enhanceText2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1482
1819
  "button",
1483
1820
  {
1484
1821
  type: "button",
@@ -1523,7 +1860,7 @@ function ReportDialog({
1523
1860
  },
1524
1861
  title: "Polish grammar \u2014 preserves your meaning",
1525
1862
  children: [
1526
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1863
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1527
1864
  "svg",
1528
1865
  {
1529
1866
  width: "11",
@@ -1531,7 +1868,7 @@ function ReportDialog({
1531
1868
  viewBox: "0 0 24 24",
1532
1869
  fill: "none",
1533
1870
  "aria-hidden": "true",
1534
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1871
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1535
1872
  "path",
1536
1873
  {
1537
1874
  d: "M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5L12 3zM19 15l.75 2.25L22 18l-2.25.75L19 21l-.75-2.25L16 18l2.25-.75L19 15z",
@@ -1546,7 +1883,7 @@ function ReportDialog({
1546
1883
  ]
1547
1884
  }
1548
1885
  ),
1549
- transcribeAudio2 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1886
+ transcribeAudio2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1550
1887
  "button",
1551
1888
  {
1552
1889
  type: "button",
@@ -1572,7 +1909,7 @@ function ReportDialog({
1572
1909
  padding: 0,
1573
1910
  transition: "border-color 0.2s ease, color 0.2s ease"
1574
1911
  },
1575
- children: isListening ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1912
+ children: isListening ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1576
1913
  "svg",
1577
1914
  {
1578
1915
  width: "12",
@@ -1580,7 +1917,7 @@ function ReportDialog({
1580
1917
  viewBox: "0 0 24 24",
1581
1918
  fill: "currentColor",
1582
1919
  "aria-hidden": "true",
1583
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1920
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1584
1921
  "rect",
1585
1922
  {
1586
1923
  x: "6",
@@ -1591,7 +1928,7 @@ function ReportDialog({
1591
1928
  }
1592
1929
  )
1593
1930
  }
1594
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1931
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1595
1932
  "svg",
1596
1933
  {
1597
1934
  width: "13",
@@ -1600,7 +1937,7 @@ function ReportDialog({
1600
1937
  fill: "none",
1601
1938
  "aria-hidden": "true",
1602
1939
  children: [
1603
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1940
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1604
1941
  "rect",
1605
1942
  {
1606
1943
  x: "9",
@@ -1612,7 +1949,7 @@ function ReportDialog({
1612
1949
  strokeWidth: "1.8"
1613
1950
  }
1614
1951
  ),
1615
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1952
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1616
1953
  "path",
1617
1954
  {
1618
1955
  d: "M5 10a7 7 0 0014 0M12 19v3M9 22h6",
@@ -1627,7 +1964,7 @@ function ReportDialog({
1627
1964
  }
1628
1965
  )
1629
1966
  ] }),
1630
- transcribeAudio2 && !isListening && !isTranscribing && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1967
+ transcribeAudio2 && !isListening && !isTranscribing && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1631
1968
  "div",
1632
1969
  {
1633
1970
  style: {
@@ -1641,16 +1978,16 @@ function ReportDialog({
1641
1978
  userSelect: "none"
1642
1979
  },
1643
1980
  children: [
1644
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1645
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }),
1646
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
1647
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: "12", x2: "12", y1: "19", y2: "22" })
1981
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { width: "11", height: "11", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1982
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z" }),
1983
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
1984
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", x2: "12", y1: "19", y2: "22" })
1648
1985
  ] }),
1649
1986
  "Hold Space to speak \u2014 we'll transcribe it"
1650
1987
  ]
1651
1988
  }
1652
1989
  ),
1653
- isEnhanced && originalDescription !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
1990
+ isEnhanced && originalDescription !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1654
1991
  "div",
1655
1992
  {
1656
1993
  style: {
@@ -1664,7 +2001,7 @@ function ReportDialog({
1664
2001
  border: `1px solid ${t.accent}40`
1665
2002
  },
1666
2003
  children: [
1667
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2004
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1668
2005
  "svg",
1669
2006
  {
1670
2007
  width: "12",
@@ -1672,7 +2009,7 @@ function ReportDialog({
1672
2009
  viewBox: "0 0 24 24",
1673
2010
  fill: "none",
1674
2011
  style: { flexShrink: 0 },
1675
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2012
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1676
2013
  "path",
1677
2014
  {
1678
2015
  d: "M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5L12 3z",
@@ -1683,7 +2020,7 @@ function ReportDialog({
1683
2020
  )
1684
2021
  }
1685
2022
  ),
1686
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2023
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1687
2024
  "span",
1688
2025
  {
1689
2026
  style: {
@@ -1694,7 +2031,7 @@ function ReportDialog({
1694
2031
  children: "AI enhanced"
1695
2032
  }
1696
2033
  ),
1697
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2034
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1698
2035
  "button",
1699
2036
  {
1700
2037
  type: "button",
@@ -1716,7 +2053,7 @@ function ReportDialog({
1716
2053
  children: "Keep"
1717
2054
  }
1718
2055
  ),
1719
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2056
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1720
2057
  "button",
1721
2058
  {
1722
2059
  type: "button",
@@ -1742,7 +2079,7 @@ function ReportDialog({
1742
2079
  ]
1743
2080
  }
1744
2081
  ),
1745
- voiceError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2082
+ voiceError && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1746
2083
  "p",
1747
2084
  {
1748
2085
  style: {
@@ -1754,8 +2091,8 @@ function ReportDialog({
1754
2091
  children: voiceError
1755
2092
  }
1756
2093
  ),
1757
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: "10px" }, children: [
1758
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2094
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginTop: "10px" }, children: [
2095
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1759
2096
  "span",
1760
2097
  {
1761
2098
  style: {
@@ -1771,7 +2108,7 @@ function ReportDialog({
1771
2108
  ]
1772
2109
  }
1773
2110
  ),
1774
- screenshots.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2111
+ screenshots.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1775
2112
  "div",
1776
2113
  {
1777
2114
  style: {
@@ -1780,7 +2117,7 @@ function ReportDialog({
1780
2117
  gap: "8px",
1781
2118
  marginBottom: "8px"
1782
2119
  },
1783
- children: screenshots.map((src, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2120
+ children: screenshots.map((src, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1784
2121
  "div",
1785
2122
  {
1786
2123
  style: {
@@ -1789,7 +2126,7 @@ function ReportDialog({
1789
2126
  height: "56px"
1790
2127
  },
1791
2128
  children: [
1792
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2129
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1793
2130
  "img",
1794
2131
  {
1795
2132
  src,
@@ -1807,7 +2144,7 @@ function ReportDialog({
1807
2144
  }
1808
2145
  }
1809
2146
  ),
1810
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2147
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1811
2148
  "button",
1812
2149
  {
1813
2150
  type: "button",
@@ -1833,14 +2170,14 @@ function ReportDialog({
1833
2170
  alignItems: "center",
1834
2171
  justifyContent: "center"
1835
2172
  },
1836
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2173
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1837
2174
  "svg",
1838
2175
  {
1839
2176
  width: "8",
1840
2177
  height: "8",
1841
2178
  viewBox: "0 0 10 10",
1842
2179
  fill: "none",
1843
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2180
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1844
2181
  "path",
1845
2182
  {
1846
2183
  d: "M1 1L9 9M9 1L1 9",
@@ -1852,6 +2189,52 @@ function ReportDialog({
1852
2189
  }
1853
2190
  )
1854
2191
  }
2192
+ ),
2193
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2194
+ "button",
2195
+ {
2196
+ type: "button",
2197
+ onClick: () => setAnnotatingIndex(i),
2198
+ "aria-label": `Annotate screenshot ${i + 1}`,
2199
+ title: "Annotate",
2200
+ style: {
2201
+ position: "absolute",
2202
+ top: "50%",
2203
+ left: "50%",
2204
+ transform: "translate(-50%, -50%)",
2205
+ width: "24px",
2206
+ height: "24px",
2207
+ borderRadius: "50%",
2208
+ border: "none",
2209
+ background: t.accent,
2210
+ color: t.accentText,
2211
+ padding: 0,
2212
+ cursor: "pointer",
2213
+ display: "flex",
2214
+ alignItems: "center",
2215
+ justifyContent: "center",
2216
+ boxShadow: "0 2px 8px rgba(0,0,0,0.45)"
2217
+ },
2218
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2219
+ "svg",
2220
+ {
2221
+ width: "12",
2222
+ height: "12",
2223
+ viewBox: "0 0 24 24",
2224
+ fill: "none",
2225
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2226
+ "path",
2227
+ {
2228
+ d: "M12 20h9M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4L16.5 3.5z",
2229
+ stroke: "currentColor",
2230
+ strokeWidth: "2.2",
2231
+ strokeLinecap: "round",
2232
+ strokeLinejoin: "round"
2233
+ }
2234
+ )
2235
+ }
2236
+ )
2237
+ }
1855
2238
  )
1856
2239
  ]
1857
2240
  },
@@ -1859,7 +2242,7 @@ function ReportDialog({
1859
2242
  ))
1860
2243
  }
1861
2244
  ),
1862
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2245
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1863
2246
  "div",
1864
2247
  {
1865
2248
  onClick: () => {
@@ -1895,7 +2278,7 @@ function ReportDialog({
1895
2278
  background: screenshotDragOver ? isDark ? "rgba(34,211,238,0.06)" : "rgba(8,145,178,0.04)" : "transparent",
1896
2279
  transition: "border-color 0.15s ease, background 0.15s ease"
1897
2280
  },
1898
- children: screenshots.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2281
+ children: screenshots.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1899
2282
  "p",
1900
2283
  {
1901
2284
  style: {
@@ -1906,8 +2289,8 @@ function ReportDialog({
1906
2289
  },
1907
2290
  children: screenshotDragOver ? "Drop to attach" : "+ Add more \xB7 Drag & drop \xB7 Paste"
1908
2291
  }
1909
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1910
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2292
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2293
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1911
2294
  "svg",
1912
2295
  {
1913
2296
  width: "22",
@@ -1920,7 +2303,7 @@ function ReportDialog({
1920
2303
  opacity: 0.45
1921
2304
  },
1922
2305
  children: [
1923
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2306
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1924
2307
  "rect",
1925
2308
  {
1926
2309
  x: "3",
@@ -1932,7 +2315,7 @@ function ReportDialog({
1932
2315
  strokeWidth: "1.5"
1933
2316
  }
1934
2317
  ),
1935
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2318
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1936
2319
  "circle",
1937
2320
  {
1938
2321
  cx: "8.5",
@@ -1942,7 +2325,7 @@ function ReportDialog({
1942
2325
  strokeWidth: "1.5"
1943
2326
  }
1944
2327
  ),
1945
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2328
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1946
2329
  "path",
1947
2330
  {
1948
2331
  d: "M21 15l-5-5L5 21",
@@ -1955,7 +2338,7 @@ function ReportDialog({
1955
2338
  ]
1956
2339
  }
1957
2340
  ),
1958
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2341
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1959
2342
  "p",
1960
2343
  {
1961
2344
  style: {
@@ -1967,7 +2350,7 @@ function ReportDialog({
1967
2350
  children: screenshotDragOver ? "Drop to attach" : "Add screenshots"
1968
2351
  }
1969
2352
  ),
1970
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2353
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1971
2354
  "p",
1972
2355
  {
1973
2356
  style: {
@@ -1982,8 +2365,8 @@ function ReportDialog({
1982
2365
  }
1983
2366
  )
1984
2367
  ] }),
1985
- showSeverity && reportType === "BUG" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: "10px" }, children: [
1986
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2368
+ showSeverity && reportType === "BUG" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginTop: "10px" }, children: [
2369
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1987
2370
  "span",
1988
2371
  {
1989
2372
  style: {
@@ -1995,7 +2378,7 @@ function ReportDialog({
1995
2378
  children: "Severity"
1996
2379
  }
1997
2380
  ),
1998
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { display: "flex", gap: "6px" }, children: ["low", "medium", "high"].map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2381
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", gap: "6px" }, children: ["low", "medium", "high"].map((s) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1999
2382
  "button",
2000
2383
  {
2001
2384
  type: "button",
@@ -2019,7 +2402,7 @@ function ReportDialog({
2019
2402
  s
2020
2403
  )) })
2021
2404
  ] }),
2022
- validationError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2405
+ validationError && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2023
2406
  "p",
2024
2407
  {
2025
2408
  style: {
@@ -2030,7 +2413,7 @@ function ReportDialog({
2030
2413
  children: validationError
2031
2414
  }
2032
2415
  ),
2033
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2416
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2034
2417
  "button",
2035
2418
  {
2036
2419
  type: "button",
@@ -2055,7 +2438,7 @@ function ReportDialog({
2055
2438
  )
2056
2439
  ] })
2057
2440
  ] }) }),
2058
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2441
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2059
2442
  "div",
2060
2443
  {
2061
2444
  style: {
@@ -2063,7 +2446,7 @@ function ReportDialog({
2063
2446
  borderTop: `1px solid ${t.border}`,
2064
2447
  textAlign: "center"
2065
2448
  },
2066
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { fontSize: "11px", color: t.textMuted }, children: "Powered by Glitchgrab" })
2449
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: "11px", color: t.textMuted }, children: "Powered by Glitchgrab" })
2067
2450
  }
2068
2451
  )
2069
2452
  ]
@@ -2073,7 +2456,7 @@ function ReportDialog({
2073
2456
  ),
2074
2457
  document.body
2075
2458
  ),
2076
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2459
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2077
2460
  "input",
2078
2461
  {
2079
2462
  ref: fileInputRef,
@@ -2085,7 +2468,7 @@ function ReportDialog({
2085
2468
  }
2086
2469
  ),
2087
2470
  previewIndex !== null && screenshots[previewIndex] && (0, import_react_dom.createPortal)(
2088
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2471
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2089
2472
  "div",
2090
2473
  {
2091
2474
  style: {
@@ -2106,37 +2489,114 @@ function ReportDialog({
2106
2489
  },
2107
2490
  onMouseDown: (e) => e.stopPropagation(),
2108
2491
  children: [
2109
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2110
- "img",
2492
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2493
+ "div",
2111
2494
  {
2112
2495
  ref: previewImgRef,
2113
- src: screenshots[previewIndex],
2114
- alt: "Screenshot preview",
2496
+ style: { position: "relative", maxWidth: "100%" },
2115
2497
  onClick: (e) => e.stopPropagation(),
2116
- style: {
2117
- maxWidth: "100%",
2118
- maxHeight: "calc(100vh - 80px)",
2119
- borderRadius: "8px",
2120
- objectFit: "contain",
2121
- cursor: "default"
2122
- }
2498
+ children: [
2499
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2500
+ "img",
2501
+ {
2502
+ src: screenshots[previewIndex],
2503
+ alt: "Screenshot preview",
2504
+ style: {
2505
+ maxWidth: "100%",
2506
+ maxHeight: "calc(100vh - 80px)",
2507
+ borderRadius: "8px",
2508
+ objectFit: "contain",
2509
+ cursor: "default",
2510
+ display: "block"
2511
+ }
2512
+ }
2513
+ ),
2514
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2515
+ "button",
2516
+ {
2517
+ type: "button",
2518
+ onClick: (e) => {
2519
+ e.stopPropagation();
2520
+ setAnnotatingIndex(previewIndex);
2521
+ },
2522
+ style: {
2523
+ position: "absolute",
2524
+ top: "12px",
2525
+ right: "12px",
2526
+ display: "inline-flex",
2527
+ alignItems: "center",
2528
+ gap: "6px",
2529
+ padding: "8px 14px",
2530
+ borderRadius: "999px",
2531
+ border: "none",
2532
+ background: t.accent,
2533
+ color: t.accentText,
2534
+ fontSize: "13px",
2535
+ fontWeight: 700,
2536
+ cursor: "pointer",
2537
+ fontFamily: "inherit",
2538
+ boxShadow: "0 4px 14px rgba(0,0,0,0.35)",
2539
+ animation: "gg-pulse 2s ease-in-out infinite"
2540
+ },
2541
+ children: [
2542
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "13", height: "13", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2543
+ "path",
2544
+ {
2545
+ d: "M12 20h9M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4L16.5 3.5z",
2546
+ stroke: "currentColor",
2547
+ strokeWidth: "2",
2548
+ strokeLinecap: "round",
2549
+ strokeLinejoin: "round"
2550
+ }
2551
+ ) }),
2552
+ "Annotate"
2553
+ ]
2554
+ }
2555
+ )
2556
+ ]
2123
2557
  }
2124
2558
  ),
2125
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2126
- "span",
2559
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2560
+ "div",
2127
2561
  {
2128
2562
  style: {
2129
2563
  marginTop: "12px",
2130
- color: t.textMuted,
2131
- fontSize: "12px"
2564
+ display: "flex",
2565
+ alignItems: "center",
2566
+ gap: "12px"
2132
2567
  },
2133
- children: screenshots.length > 1 ? `${previewIndex + 1} / ${screenshots.length} \xB7 Click outside to close` : "Click outside to close"
2568
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2569
+ "span",
2570
+ {
2571
+ style: {
2572
+ color: t.textMuted,
2573
+ fontSize: "12px"
2574
+ },
2575
+ children: screenshots.length > 1 ? `${previewIndex + 1} / ${screenshots.length} \xB7 Click outside to close` : "Click outside to close"
2576
+ }
2577
+ )
2134
2578
  }
2135
2579
  )
2136
2580
  ]
2137
2581
  }
2138
2582
  ),
2139
2583
  document.body
2584
+ ),
2585
+ annotatingIndex !== null && screenshots[annotatingIndex] && (0, import_react_dom.createPortal)(
2586
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2587
+ AnnotationCanvas,
2588
+ {
2589
+ imageSrc: screenshots[annotatingIndex],
2590
+ onCancel: () => setAnnotatingIndex(null),
2591
+ onSave: (dataUrl) => {
2592
+ setScreenshots(
2593
+ (prev) => prev.map((s, i) => i === annotatingIndex ? dataUrl : s)
2594
+ );
2595
+ setAnnotatingIndex(null);
2596
+ }
2597
+ }
2598
+ ),
2599
+ document.body
2140
2600
  )
2141
2601
  ] });
2142
2602
  }
@@ -2146,6 +2606,12 @@ function getTypeLabel(type) {
2146
2606
  return "Bug Report";
2147
2607
  case "FEATURE_REQUEST":
2148
2608
  return "Feature Request";
2609
+ case "UI_IMPROVEMENT":
2610
+ return "UI Improvement";
2611
+ case "PERFORMANCE":
2612
+ return "Performance";
2613
+ case "SECURITY":
2614
+ return "Security";
2149
2615
  case "QUESTION":
2150
2616
  return "Question";
2151
2617
  case "OTHER":
@@ -2158,6 +2624,12 @@ function getTypeSubtitle(type) {
2158
2624
  return "Something isn't working";
2159
2625
  case "FEATURE_REQUEST":
2160
2626
  return "Suggest an improvement";
2627
+ case "UI_IMPROVEMENT":
2628
+ return "Something looks off";
2629
+ case "PERFORMANCE":
2630
+ return "Something is slow";
2631
+ case "SECURITY":
2632
+ return "Report a security concern";
2161
2633
  case "QUESTION":
2162
2634
  return "Ask a question";
2163
2635
  case "OTHER":
@@ -2171,6 +2643,12 @@ function getPlaceholder(type, idx = 0, hasVoice = false) {
2171
2643
  return "What went wrong? Describe it or paste an error...";
2172
2644
  case "FEATURE_REQUEST":
2173
2645
  return "Describe the feature you'd like...";
2646
+ case "UI_IMPROVEMENT":
2647
+ return "What looks off? Describe the visual issue...";
2648
+ case "PERFORMANCE":
2649
+ return "What's slow? Describe when it happens...";
2650
+ case "SECURITY":
2651
+ return "Describe the security concern...";
2174
2652
  case "QUESTION":
2175
2653
  return "What would you like to know?";
2176
2654
  case "OTHER":
@@ -2187,7 +2665,7 @@ function getTypeIcon(type, color, size = 24) {
2187
2665
  };
2188
2666
  switch (type) {
2189
2667
  case "BUG":
2190
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2668
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2191
2669
  "path",
2192
2670
  {
2193
2671
  d: "M8 2L6.5 3.5M16 2L17.5 3.5M3 9H7M17 9H21M12 2a5 5 0 015 5v4a5 5 0 01-10 0V7a5 5 0 015-5zM7 16l-2 3M17 16l2 3",
@@ -2197,7 +2675,7 @@ function getTypeIcon(type, color, size = 24) {
2197
2675
  }
2198
2676
  ) });
2199
2677
  case "FEATURE_REQUEST":
2200
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2678
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2201
2679
  "path",
2202
2680
  {
2203
2681
  d: "M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26L12 2z",
@@ -2206,10 +2684,55 @@ function getTypeIcon(type, color, size = 24) {
2206
2684
  strokeLinejoin: "round"
2207
2685
  }
2208
2686
  ) });
2687
+ case "UI_IMPROVEMENT":
2688
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { ...props, children: [
2689
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2690
+ "rect",
2691
+ {
2692
+ x: "3",
2693
+ y: "3",
2694
+ width: "18",
2695
+ height: "18",
2696
+ rx: "2",
2697
+ stroke: color,
2698
+ strokeWidth: "1.5"
2699
+ }
2700
+ ),
2701
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2702
+ "path",
2703
+ {
2704
+ d: "M3 9h18M9 21V9",
2705
+ stroke: color,
2706
+ strokeWidth: "1.5",
2707
+ strokeLinecap: "round"
2708
+ }
2709
+ )
2710
+ ] });
2711
+ case "PERFORMANCE":
2712
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2713
+ "path",
2714
+ {
2715
+ d: "M12 21a9 9 0 100-18 9 9 0 000 18zM12 12l4-4M12 7v.01",
2716
+ stroke: color,
2717
+ strokeWidth: "1.5",
2718
+ strokeLinecap: "round",
2719
+ strokeLinejoin: "round"
2720
+ }
2721
+ ) });
2722
+ case "SECURITY":
2723
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2724
+ "path",
2725
+ {
2726
+ d: "M12 3l7 3v5c0 4.5-3 8.5-7 10-4-1.5-7-5.5-7-10V6l7-3z",
2727
+ stroke: color,
2728
+ strokeWidth: "1.5",
2729
+ strokeLinejoin: "round"
2730
+ }
2731
+ ) });
2209
2732
  case "QUESTION":
2210
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("svg", { ...props, children: [
2211
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "12", cy: "12", r: "10", stroke: color, strokeWidth: "1.5" }),
2212
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2733
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("svg", { ...props, children: [
2734
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: "12", cy: "12", r: "10", stroke: color, strokeWidth: "1.5" }),
2735
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2213
2736
  "path",
2214
2737
  {
2215
2738
  d: "M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3M12 17h.01",
@@ -2221,7 +2744,7 @@ function getTypeIcon(type, color, size = 24) {
2221
2744
  )
2222
2745
  ] });
2223
2746
  case "OTHER":
2224
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2747
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2225
2748
  "path",
2226
2749
  {
2227
2750
  d: "M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2v10z",
@@ -2234,11 +2757,11 @@ function getTypeIcon(type, color, size = 24) {
2234
2757
  }
2235
2758
 
2236
2759
  // src/provider.tsx
2237
- var import_jsx_runtime2 = require("react/jsx-runtime");
2760
+ var import_jsx_runtime3 = require("react/jsx-runtime");
2238
2761
  var DEFAULT_BASE_URL2 = "https://glitchgrab.dev";
2239
- var GlitchgrabContext = (0, import_react2.createContext)(null);
2762
+ var GlitchgrabContext = (0, import_react3.createContext)(null);
2240
2763
  function useGlitchgrab() {
2241
- const ctx = (0, import_react2.useContext)(GlitchgrabContext);
2764
+ const ctx = (0, import_react3.useContext)(GlitchgrabContext);
2242
2765
  if (!ctx) {
2243
2766
  throw new Error("useGlitchgrab must be used within a GlitchgrabProvider");
2244
2767
  }
@@ -2258,13 +2781,13 @@ function GlitchgrabProviderInner({
2258
2781
  showSeverity,
2259
2782
  ignoreErrors
2260
2783
  }) {
2261
- const visitedPagesRef = (0, import_react2.useRef)([]);
2262
- (0, import_react2.useEffect)(() => {
2784
+ const visitedPagesRef = (0, import_react3.useRef)([]);
2785
+ (0, import_react3.useEffect)(() => {
2263
2786
  if (enableBreadcrumbs) {
2264
2787
  initBreadcrumbs(maxBreadcrumbs2);
2265
2788
  }
2266
2789
  }, [enableBreadcrumbs, maxBreadcrumbs2]);
2267
- (0, import_react2.useEffect)(() => {
2790
+ (0, import_react3.useEffect)(() => {
2268
2791
  try {
2269
2792
  if (typeof window === "undefined") return;
2270
2793
  const trackPage = () => {
@@ -2301,7 +2824,7 @@ function GlitchgrabProviderInner({
2301
2824
  } catch (e) {
2302
2825
  }
2303
2826
  }, []);
2304
- (0, import_react2.useEffect)(() => {
2827
+ (0, import_react3.useEffect)(() => {
2305
2828
  try {
2306
2829
  if (typeof window === "undefined") return;
2307
2830
  if (process.env.NODE_ENV === "development") return;
@@ -2424,7 +2947,7 @@ function GlitchgrabProviderInner({
2424
2947
  } catch (e) {
2425
2948
  }
2426
2949
  }, [token, baseUrl, onError, onReportSent, session, ignoreErrors]);
2427
- const report = (0, import_react2.useCallback)(
2950
+ const report = (0, import_react3.useCallback)(
2428
2951
  async (type, description, metadata) => {
2429
2952
  var _a;
2430
2953
  try {
@@ -2457,17 +2980,17 @@ function GlitchgrabProviderInner({
2457
2980
  },
2458
2981
  [token, baseUrl, onReportSent, session]
2459
2982
  );
2460
- const reportBug = (0, import_react2.useCallback)(
2983
+ const reportBug = (0, import_react3.useCallback)(
2461
2984
  (description, metadata) => report("BUG", description, metadata),
2462
2985
  [report]
2463
2986
  );
2464
- const addBreadcrumb2 = (0, import_react2.useCallback)(
2987
+ const addBreadcrumb2 = (0, import_react3.useCallback)(
2465
2988
  (message, data) => {
2466
2989
  addBreadcrumb("custom", message, data);
2467
2990
  },
2468
2991
  []
2469
2992
  );
2470
- const enhance = (0, import_react2.useCallback)(
2993
+ const enhance = (0, import_react3.useCallback)(
2471
2994
  async (text, screenshot) => {
2472
2995
  try {
2473
2996
  const ctx = captureContext(visitedPagesRef.current);
@@ -2483,7 +3006,7 @@ function GlitchgrabProviderInner({
2483
3006
  },
2484
3007
  [token, baseUrl]
2485
3008
  );
2486
- const transcribe = (0, import_react2.useCallback)(
3009
+ const transcribe = (0, import_react3.useCallback)(
2487
3010
  async (blob) => {
2488
3011
  try {
2489
3012
  return await transcribeAudio(blob, token, baseUrl);
@@ -2493,16 +3016,16 @@ function GlitchgrabProviderInner({
2493
3016
  },
2494
3017
  [token, baseUrl]
2495
3018
  );
2496
- const openReportDialog = (0, import_react2.useCallback)((options) => {
3019
+ const openReportDialog = (0, import_react3.useCallback)((options) => {
2497
3020
  if (typeof window !== "undefined") {
2498
3021
  window.dispatchEvent(new CustomEvent("glitchgrab:open-report", { detail: options }));
2499
3022
  }
2500
3023
  }, []);
2501
- (0, import_react2.useEffect)(() => {
3024
+ (0, import_react3.useEffect)(() => {
2502
3025
  try {
2503
3026
  if (typeof window === "undefined") return;
2504
3027
  const handleKeyDown = (e) => {
2505
- if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "g") {
3028
+ if (matchesShortcut(e)) {
2506
3029
  e.preventDefault();
2507
3030
  openReportDialog();
2508
3031
  }
@@ -2512,7 +3035,11 @@ function GlitchgrabProviderInner({
2512
3035
  } catch (e) {
2513
3036
  }
2514
3037
  }, [openReportDialog]);
2515
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
3038
+ const [shortcutLabel, setShortcutLabel] = (0, import_react3.useState)(GLITCHGRAB_SHORTCUT);
3039
+ (0, import_react3.useEffect)(() => {
3040
+ setShortcutLabel(getShortcutLabel());
3041
+ }, []);
3042
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2516
3043
  GlitchgrabContext.Provider,
2517
3044
  {
2518
3045
  value: {
@@ -2522,10 +3049,11 @@ function GlitchgrabProviderInner({
2522
3049
  report,
2523
3050
  addBreadcrumb: addBreadcrumb2,
2524
3051
  openReportDialog,
2525
- enhanceText: enhance
3052
+ enhanceText: enhance,
3053
+ shortcutLabel
2526
3054
  },
2527
3055
  children: [
2528
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
3056
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2529
3057
  GlitchgrabErrorBoundary,
2530
3058
  {
2531
3059
  token,
@@ -2537,27 +3065,27 @@ function GlitchgrabProviderInner({
2537
3065
  children
2538
3066
  }
2539
3067
  ),
2540
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ReportDialog, { report, enhanceText: enhance, transcribeAudio: transcribe, types, showSeverity })
3068
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ReportDialog, { report, enhanceText: enhance, transcribeAudio: transcribe, types, showSeverity })
2541
3069
  ]
2542
3070
  }
2543
3071
  );
2544
3072
  }
2545
3073
  function GlitchgrabProvider(props) {
2546
- if (!props.token) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: props.children });
3074
+ if (!props.token) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: props.children });
2547
3075
  const resolvedProps = {
2548
3076
  ...props,
2549
3077
  baseUrl: props.baseUrl || DEFAULT_BASE_URL2
2550
3078
  };
2551
3079
  try {
2552
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GlitchgrabProviderInner, { ...resolvedProps });
3080
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(GlitchgrabProviderInner, { ...resolvedProps });
2553
3081
  } catch (e) {
2554
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: props.children });
3082
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: props.children });
2555
3083
  }
2556
3084
  }
2557
3085
 
2558
3086
  // src/report-button.tsx
2559
- var import_react3 = require("react");
2560
- var import_jsx_runtime3 = require("react/jsx-runtime");
3087
+ var import_react4 = require("react");
3088
+ var import_jsx_runtime4 = require("react/jsx-runtime");
2561
3089
  function useGlitchgrabSafe() {
2562
3090
  try {
2563
3091
  return useGlitchgrab();
@@ -2572,7 +3100,7 @@ function ReportButton({
2572
3100
  children
2573
3101
  }) {
2574
3102
  var _a, _b;
2575
- const [capturing, setCapturing] = (0, import_react3.useState)(false);
3103
+ const [capturing, setCapturing] = (0, import_react4.useState)(false);
2576
3104
  const glitchgrab = useGlitchgrabSafe();
2577
3105
  if (!glitchgrab) return null;
2578
3106
  const handleClick = async () => {
@@ -2581,7 +3109,7 @@ function ReportButton({
2581
3109
  setCapturing(false);
2582
3110
  };
2583
3111
  if (children) {
2584
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: children({ onClick: handleClick, capturing }) });
3112
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: children({ onClick: handleClick, capturing }) });
2585
3113
  }
2586
3114
  const isTop = (_a = position == null ? void 0 : position.startsWith("top")) != null ? _a : false;
2587
3115
  const isLeft = (_b = position == null ? void 0 : position.endsWith("left")) != null ? _b : false;
@@ -2589,7 +3117,7 @@ function ReportButton({
2589
3117
  ...isLeft ? { left: "16px" } : { right: "16px" },
2590
3118
  ...isTop ? { top: "16px" } : { bottom: "16px" }
2591
3119
  };
2592
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
3120
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2593
3121
  "button",
2594
3122
  {
2595
3123
  type: "button",
@@ -2624,7 +3152,7 @@ function ReportButton({
2624
3152
  },
2625
3153
  "aria-label": label,
2626
3154
  children: [
2627
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3155
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2628
3156
  "svg",
2629
3157
  {
2630
3158
  width: "16",
@@ -2632,7 +3160,7 @@ function ReportButton({
2632
3160
  viewBox: "0 0 16 16",
2633
3161
  fill: "none",
2634
3162
  style: { flexShrink: 0 },
2635
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
3163
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2636
3164
  "path",
2637
3165
  {
2638
3166
  d: "M8 1C4.134 1 1 4.134 1 8s3.134 7 7 7 7-3.134 7-7-3.134-7-7-7zm0 12.5a.75.75 0 110-1.5.75.75 0 010 1.5zm.75-3.5a.75.75 0 01-1.5 0V5a.75.75 0 011.5 0v5z",
@@ -2648,18 +3176,18 @@ function ReportButton({
2648
3176
  }
2649
3177
 
2650
3178
  // src/use-reports.ts
2651
- var import_react4 = require("react");
3179
+ var import_react5 = require("react");
2652
3180
  var BASE_URL = "https://glitchgrab.dev";
2653
3181
  function useGlitchgrabReports({
2654
3182
  token,
2655
3183
  userId,
2656
3184
  limit = 100
2657
3185
  }) {
2658
- const [reports, setReports] = (0, import_react4.useState)([]);
2659
- const [isLoading, setIsLoading] = (0, import_react4.useState)(true);
2660
- const [isFetching, setIsFetching] = (0, import_react4.useState)(false);
2661
- const [error, setError] = (0, import_react4.useState)(null);
2662
- const fetchReports = (0, import_react4.useCallback)(async () => {
3186
+ const [reports, setReports] = (0, import_react5.useState)([]);
3187
+ const [isLoading, setIsLoading] = (0, import_react5.useState)(true);
3188
+ const [isFetching, setIsFetching] = (0, import_react5.useState)(false);
3189
+ const [error, setError] = (0, import_react5.useState)(null);
3190
+ const fetchReports = (0, import_react5.useCallback)(async () => {
2663
3191
  var _a, _b;
2664
3192
  if (!token || !userId) {
2665
3193
  setIsLoading(false);
@@ -2685,7 +3213,7 @@ function useGlitchgrabReports({
2685
3213
  setIsFetching(false);
2686
3214
  }
2687
3215
  }, [token, userId, limit]);
2688
- (0, import_react4.useEffect)(() => {
3216
+ (0, import_react5.useEffect)(() => {
2689
3217
  fetchReports();
2690
3218
  }, [fetchReports]);
2691
3219
  return { reports, isLoading, isFetching, error, refetch: fetchReports };
@@ -2722,9 +3250,9 @@ function useGlitchgrabActions({
2722
3250
  onSuccess,
2723
3251
  onError
2724
3252
  }) {
2725
- const [isPending, setIsPending] = (0, import_react4.useState)(false);
2726
- const [error, setError] = (0, import_react4.useState)(null);
2727
- const run = (0, import_react4.useCallback)(
3253
+ const [isPending, setIsPending] = (0, import_react5.useState)(false);
3254
+ const [error, setError] = (0, import_react5.useState)(null);
3255
+ const run = (0, import_react5.useCallback)(
2728
3256
  async (reportId, body) => {
2729
3257
  setIsPending(true);
2730
3258
  setError(null);
@@ -2754,6 +3282,8 @@ function useGlitchgrabActions({
2754
3282
  }
2755
3283
  // Annotate the CommonJS export names for ESM import in node:
2756
3284
  0 && (module.exports = {
3285
+ GLITCHGRAB_SHORTCUT,
3286
+ GLITCHGRAB_SHORTCUT_MAC,
2757
3287
  GlitchgrabErrorBoundary,
2758
3288
  GlitchgrabProvider,
2759
3289
  ReportButton,
@@ -2766,7 +3296,9 @@ function useGlitchgrabActions({
2766
3296
  enhanceText,
2767
3297
  fetchGlitchgrabReports,
2768
3298
  getBreadcrumbs,
3299
+ getShortcutLabel,
2769
3300
  initBreadcrumbs,
3301
+ matchesShortcut,
2770
3302
  sanitizeUrl,
2771
3303
  sendReport,
2772
3304
  shouldSkipDuplicate,