glitchgrab 1.27.0 → 1.29.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,
@@ -781,6 +785,24 @@ function AnnotationCanvas({ imageSrc, onSave, onCancel }) {
781
785
  );
782
786
  }
783
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
+
784
806
  // src/report-dialog.tsx
785
807
  var import_jsx_runtime2 = require("react/jsx-runtime");
786
808
  function useIsDark() {
@@ -1410,6 +1432,9 @@ function ReportDialog({
1410
1432
  zIndex: 2147483647,
1411
1433
  width: "340px",
1412
1434
  maxWidth: "calc(100% - 32px)",
1435
+ maxHeight: "calc(100dvh - 32px)",
1436
+ display: "flex",
1437
+ flexDirection: "column",
1413
1438
  backgroundColor: t.bg,
1414
1439
  borderRadius: "12px",
1415
1440
  boxShadow: "0 20px 60px rgba(0, 0, 0, 0.2), 0 4px 16px rgba(0, 0, 0, 0.1)",
@@ -1423,6 +1448,7 @@ function ReportDialog({
1423
1448
  "div",
1424
1449
  {
1425
1450
  style: {
1451
+ flexShrink: 0,
1426
1452
  padding: "16px 16px 12px",
1427
1453
  borderBottom: `1px solid ${t.border}`
1428
1454
  },
@@ -1490,6 +1516,24 @@ function ReportDialog({
1490
1516
  },
1491
1517
  children: step === 1 ? "What's on your mind?" : "Tell us more"
1492
1518
  }
1519
+ ),
1520
+ step === 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1521
+ "span",
1522
+ {
1523
+ title: "Press this anywhere to open Glitchgrab",
1524
+ style: {
1525
+ fontSize: "10px",
1526
+ fontWeight: 600,
1527
+ lineHeight: 1,
1528
+ padding: "3px 6px",
1529
+ borderRadius: "4px",
1530
+ border: `1px solid ${t.inputBorder}`,
1531
+ background: t.inputBg,
1532
+ color: t.textMuted,
1533
+ whiteSpace: "nowrap"
1534
+ },
1535
+ children: getShortcutLabel()
1536
+ }
1493
1537
  )
1494
1538
  ]
1495
1539
  }
@@ -1580,824 +1624,835 @@ function ReportDialog({
1580
1624
  ]
1581
1625
  }
1582
1626
  ),
1583
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { padding: "16px" }, children: submitted ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1627
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1584
1628
  "div",
1585
1629
  {
1586
1630
  style: {
1587
- textAlign: "center",
1588
- padding: "20px 0",
1589
- color: t.accent,
1590
- fontSize: "14px",
1591
- fontWeight: 500
1631
+ padding: "16px",
1632
+ overflowY: "auto",
1633
+ flex: "1 1 auto",
1634
+ minHeight: 0
1592
1635
  },
1593
- children: [
1594
- getTypeLabel(reportType),
1595
- " sent. Thank you!"
1596
- ]
1597
- }
1598
- ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1599
- step === 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1600
- "div",
1601
- {
1602
- style: {
1603
- display: "grid",
1604
- gridTemplateColumns: "1fr 1fr",
1605
- gap: "8px"
1606
- },
1607
- children: availableTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1608
- "button",
1609
- {
1610
- type: "button",
1611
- onClick: () => {
1612
- setReportType(type);
1613
- setStep(2);
1614
- },
1615
- style: {
1616
- display: "flex",
1617
- flexDirection: "column",
1618
- alignItems: "center",
1619
- gap: "8px",
1620
- padding: "16px 8px",
1621
- borderRadius: "8px",
1622
- border: `1px solid ${t.inputBorder}`,
1623
- background: t.inputBg,
1624
- cursor: "pointer",
1625
- color: t.text,
1626
- fontFamily: "inherit",
1627
- transition: "border-color 0.15s ease"
1628
- },
1629
- onMouseEnter: (e) => {
1630
- e.currentTarget.style.borderColor = t.accent;
1631
- },
1632
- onMouseLeave: (e) => {
1633
- e.currentTarget.style.borderColor = t.inputBorder;
1634
- },
1635
- children: [
1636
- getTypeIcon(type, t.accent),
1637
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: "13px", fontWeight: 600 }, children: getTypeLabel(type) }),
1638
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1639
- "span",
1640
- {
1641
- style: {
1642
- fontSize: "11px",
1643
- color: t.textMuted,
1644
- lineHeight: "1.3"
1645
- },
1646
- children: getTypeSubtitle(type)
1647
- }
1648
- )
1649
- ]
1636
+ children: submitted ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1637
+ "div",
1638
+ {
1639
+ style: {
1640
+ textAlign: "center",
1641
+ padding: "20px 0",
1642
+ color: t.accent,
1643
+ fontSize: "14px",
1644
+ fontWeight: 500
1650
1645
  },
1651
- type
1652
- ))
1653
- }
1654
- ),
1655
- step === 2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1656
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative" }, children: [
1657
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1658
- "textarea",
1659
- {
1660
- ref: textareaRef,
1661
- value: description,
1662
- onChange: (e) => {
1663
- setDescription(e.target.value);
1664
- if (validationError) setValidationError(null);
1665
- if (isEnhanced) {
1666
- setIsEnhanced(false);
1667
- setOriginalDescription(null);
1668
- }
1669
- },
1670
- placeholder: isTranscribing ? "Transcribing your speech\u2026" : isListening ? "Listening\u2026 speak now" : getPlaceholder(reportType, placeholderIdx, !!transcribeAudio2),
1671
- style: {
1672
- width: "100%",
1673
- minHeight: "100px",
1674
- padding: enhanceText2 ? "28px 12px 36px" : transcribeAudio2 ? "10px 12px 36px" : "10px 12px",
1675
- borderRadius: "8px",
1676
- border: `1px solid ${isTranscribing ? "#f59e0b" : isListening ? t.accent : t.inputBorder}`,
1677
- fontSize: "14px",
1678
- fontFamily: "inherit",
1679
- resize: "none",
1680
- outline: "none",
1681
- boxSizing: "border-box",
1682
- color: t.text,
1683
- backgroundColor: t.inputBg,
1684
- transition: "border-color 0.2s ease"
1685
- },
1686
- onKeyDown: handleSpaceDown,
1687
- onKeyUp: handleSpaceUp,
1688
- onFocus: (e) => {
1689
- e.target.style.borderColor = t.accent;
1690
- },
1691
- onBlur: (e) => {
1692
- if (!isListening && !isTranscribing)
1693
- e.target.style.borderColor = t.inputBorder;
1694
- },
1695
- autoFocus: true
1696
- }
1697
- ),
1698
- (isListening || isTranscribing) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1699
- "span",
1646
+ children: [
1647
+ getTypeLabel(reportType),
1648
+ " sent. Thank you!"
1649
+ ]
1650
+ }
1651
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1652
+ step === 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1653
+ "div",
1700
1654
  {
1701
1655
  style: {
1702
- position: "absolute",
1703
- bottom: "10px",
1704
- left: "10px",
1705
- display: "inline-flex",
1706
- alignItems: "center",
1707
- gap: "4px",
1708
- fontSize: "10px",
1709
- fontWeight: 700,
1710
- letterSpacing: "0.04em",
1711
- color: isTranscribing ? "#f59e0b" : "#ef4444",
1712
- pointerEvents: "none",
1713
- zIndex: 1
1656
+ display: "grid",
1657
+ gridTemplateColumns: "1fr 1fr",
1658
+ gap: "8px"
1714
1659
  },
1715
- children: [
1716
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1717
- "span",
1718
- {
1719
- style: {
1720
- width: "6px",
1721
- height: "6px",
1722
- borderRadius: "50%",
1723
- backgroundColor: "currentColor",
1724
- animation: "gg-pulse 1.2s ease-in-out infinite",
1725
- display: "inline-block",
1726
- flexShrink: 0
1727
- }
1728
- }
1729
- ),
1730
- isTranscribing ? "Transcribing\u2026" : "REC",
1731
- isListening && !isTranscribing && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1732
- "span",
1733
- {
1734
- style: {
1735
- display: "inline-flex",
1736
- alignItems: "flex-end",
1737
- gap: "2px",
1738
- height: "14px",
1739
- marginLeft: "3px"
1740
- },
1741
- children: [
1742
- { anim: "gg-b1", dur: "0.6s", delay: "0s" },
1743
- {
1744
- anim: "gg-b2",
1745
- dur: "0.5s",
1746
- delay: "0.1s"
1747
- },
1748
- {
1749
- anim: "gg-b3",
1750
- dur: "0.7s",
1751
- delay: "0.05s"
1752
- },
1753
- {
1754
- anim: "gg-b4",
1755
- dur: "0.55s",
1756
- delay: "0.15s"
1757
- }
1758
- ].map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1660
+ children: availableTypes.map((type) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1661
+ "button",
1662
+ {
1663
+ type: "button",
1664
+ onClick: () => {
1665
+ setReportType(type);
1666
+ setStep(2);
1667
+ },
1668
+ style: {
1669
+ display: "flex",
1670
+ flexDirection: "column",
1671
+ alignItems: "center",
1672
+ gap: "8px",
1673
+ padding: "16px 8px",
1674
+ borderRadius: "8px",
1675
+ border: `1px solid ${t.inputBorder}`,
1676
+ background: t.inputBg,
1677
+ cursor: "pointer",
1678
+ color: t.text,
1679
+ fontFamily: "inherit",
1680
+ transition: "border-color 0.15s ease"
1681
+ },
1682
+ onMouseEnter: (e) => {
1683
+ e.currentTarget.style.borderColor = t.accent;
1684
+ },
1685
+ onMouseLeave: (e) => {
1686
+ e.currentTarget.style.borderColor = t.inputBorder;
1687
+ },
1688
+ children: [
1689
+ getTypeIcon(type, t.accent),
1690
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontSize: "13px", fontWeight: 600 }, children: getTypeLabel(type) }),
1691
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1759
1692
  "span",
1760
1693
  {
1761
1694
  style: {
1762
- width: "2.5px",
1763
- height: "4px",
1764
- borderRadius: "1px",
1765
- backgroundColor: "#ef4444",
1766
- animation: `${b.anim} ${b.dur} ease-in-out ${b.delay} infinite`,
1767
- display: "inline-block",
1768
- alignSelf: "center"
1769
- }
1770
- },
1771
- i
1772
- ))
1773
- }
1774
- )
1775
- ]
1776
- }
1777
- ),
1778
- enhanceText2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1779
- "button",
1780
- {
1781
- type: "button",
1782
- onClick: async () => {
1783
- var _a;
1784
- if (!description.trim() || isEnhancing) return;
1785
- setIsEnhancing(true);
1786
- try {
1787
- const polished = await enhanceText2(
1788
- description,
1789
- (_a = screenshots[0]) != null ? _a : null
1790
- );
1791
- if (polished && polished !== description) {
1792
- setOriginalDescription(description);
1793
- setDescription(polished);
1794
- setIsEnhanced(true);
1795
- if (validationError)
1796
- setValidationError(null);
1797
- }
1798
- } finally {
1799
- setIsEnhancing(false);
1800
- }
1801
- },
1802
- disabled: !description.trim() || isEnhancing || isEnhanced,
1803
- style: {
1804
- position: "absolute",
1805
- top: "6px",
1806
- right: "6px",
1807
- display: "inline-flex",
1808
- alignItems: "center",
1809
- gap: "4px",
1810
- background: t.inputBg,
1811
- border: "none",
1812
- padding: "2px 6px",
1813
- borderRadius: "4px",
1814
- fontFamily: "inherit",
1815
- fontSize: "11px",
1816
- color: t.textMuted,
1817
- cursor: !description.trim() || isEnhancing ? "default" : "pointer",
1818
- opacity: !description.trim() ? 0.5 : 1,
1819
- zIndex: 1
1820
- },
1821
- title: "Polish grammar \u2014 preserves your meaning",
1822
- children: [
1823
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1824
- "svg",
1825
- {
1826
- width: "11",
1827
- height: "11",
1828
- viewBox: "0 0 24 24",
1829
- fill: "none",
1830
- "aria-hidden": "true",
1831
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1832
- "path",
1833
- {
1834
- 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",
1835
- stroke: t.textMuted,
1836
- strokeWidth: "1.5",
1837
- strokeLinejoin: "round"
1695
+ fontSize: "11px",
1696
+ color: t.textMuted,
1697
+ lineHeight: "1.3"
1698
+ },
1699
+ children: getTypeSubtitle(type)
1838
1700
  }
1839
1701
  )
1840
- }
1841
- ),
1842
- isEnhancing ? "Enhancing..." : "AI enhance"
1843
- ]
1702
+ ]
1703
+ },
1704
+ type
1705
+ ))
1844
1706
  }
1845
1707
  ),
1846
- transcribeAudio2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1847
- "button",
1848
- {
1849
- type: "button",
1850
- onClick: () => {
1851
- void toggleVoice();
1852
- },
1853
- title: isListening ? "Stop recording" : "Speak your report",
1854
- style: {
1855
- position: "absolute",
1856
- bottom: "14px",
1857
- right: "7px",
1858
- width: "26px",
1859
- height: "26px",
1860
- display: "flex",
1861
- alignItems: "center",
1862
- justifyContent: "center",
1863
- background: "transparent",
1864
- border: `1.5px solid ${isListening ? "#ef4444" : t.inputBorder}`,
1865
- borderRadius: "6px",
1866
- color: isListening ? "#ef4444" : t.textMuted,
1867
- cursor: "pointer",
1868
- zIndex: 1,
1869
- padding: 0,
1870
- transition: "border-color 0.2s ease, color 0.2s ease"
1871
- },
1872
- children: isListening ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1873
- "svg",
1708
+ step === 2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
1709
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { position: "relative" }, children: [
1710
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1711
+ "textarea",
1874
1712
  {
1875
- width: "12",
1876
- height: "12",
1877
- viewBox: "0 0 24 24",
1878
- fill: "currentColor",
1879
- "aria-hidden": "true",
1880
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1881
- "rect",
1882
- {
1883
- x: "6",
1884
- y: "6",
1885
- width: "12",
1886
- height: "12",
1887
- rx: "2"
1713
+ ref: textareaRef,
1714
+ value: description,
1715
+ onChange: (e) => {
1716
+ setDescription(e.target.value);
1717
+ if (validationError) setValidationError(null);
1718
+ if (isEnhanced) {
1719
+ setIsEnhanced(false);
1720
+ setOriginalDescription(null);
1888
1721
  }
1889
- )
1722
+ },
1723
+ placeholder: isTranscribing ? "Transcribing your speech\u2026" : isListening ? "Listening\u2026 speak now" : getPlaceholder(reportType, placeholderIdx, !!transcribeAudio2),
1724
+ style: {
1725
+ width: "100%",
1726
+ minHeight: "100px",
1727
+ padding: enhanceText2 ? "28px 12px 36px" : transcribeAudio2 ? "10px 12px 36px" : "10px 12px",
1728
+ borderRadius: "8px",
1729
+ border: `1px solid ${isTranscribing ? "#f59e0b" : isListening ? t.accent : t.inputBorder}`,
1730
+ fontSize: "14px",
1731
+ fontFamily: "inherit",
1732
+ resize: "none",
1733
+ outline: "none",
1734
+ boxSizing: "border-box",
1735
+ color: t.text,
1736
+ backgroundColor: t.inputBg,
1737
+ transition: "border-color 0.2s ease"
1738
+ },
1739
+ onKeyDown: handleSpaceDown,
1740
+ onKeyUp: handleSpaceUp,
1741
+ onFocus: (e) => {
1742
+ e.target.style.borderColor = t.accent;
1743
+ },
1744
+ onBlur: (e) => {
1745
+ if (!isListening && !isTranscribing)
1746
+ e.target.style.borderColor = t.inputBorder;
1747
+ },
1748
+ autoFocus: true
1890
1749
  }
1891
- ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1892
- "svg",
1750
+ ),
1751
+ (isListening || isTranscribing) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1752
+ "span",
1893
1753
  {
1894
- width: "13",
1895
- height: "13",
1896
- viewBox: "0 0 24 24",
1897
- fill: "none",
1898
- "aria-hidden": "true",
1754
+ style: {
1755
+ position: "absolute",
1756
+ bottom: "10px",
1757
+ left: "10px",
1758
+ display: "inline-flex",
1759
+ alignItems: "center",
1760
+ gap: "4px",
1761
+ fontSize: "10px",
1762
+ fontWeight: 700,
1763
+ letterSpacing: "0.04em",
1764
+ color: isTranscribing ? "#f59e0b" : "#ef4444",
1765
+ pointerEvents: "none",
1766
+ zIndex: 1
1767
+ },
1899
1768
  children: [
1900
1769
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1901
- "rect",
1770
+ "span",
1902
1771
  {
1903
- x: "9",
1904
- y: "2",
1905
- width: "6",
1906
- height: "12",
1907
- rx: "3",
1908
- stroke: "currentColor",
1909
- strokeWidth: "1.8"
1772
+ style: {
1773
+ width: "6px",
1774
+ height: "6px",
1775
+ borderRadius: "50%",
1776
+ backgroundColor: "currentColor",
1777
+ animation: "gg-pulse 1.2s ease-in-out infinite",
1778
+ display: "inline-block",
1779
+ flexShrink: 0
1780
+ }
1910
1781
  }
1911
1782
  ),
1912
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1913
- "path",
1783
+ isTranscribing ? "Transcribing\u2026" : "REC",
1784
+ isListening && !isTranscribing && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1785
+ "span",
1914
1786
  {
1915
- d: "M5 10a7 7 0 0014 0M12 19v3M9 22h6",
1916
- stroke: "currentColor",
1917
- strokeWidth: "1.8",
1918
- strokeLinecap: "round"
1787
+ style: {
1788
+ display: "inline-flex",
1789
+ alignItems: "flex-end",
1790
+ gap: "2px",
1791
+ height: "14px",
1792
+ marginLeft: "3px"
1793
+ },
1794
+ children: [
1795
+ { anim: "gg-b1", dur: "0.6s", delay: "0s" },
1796
+ {
1797
+ anim: "gg-b2",
1798
+ dur: "0.5s",
1799
+ delay: "0.1s"
1800
+ },
1801
+ {
1802
+ anim: "gg-b3",
1803
+ dur: "0.7s",
1804
+ delay: "0.05s"
1805
+ },
1806
+ {
1807
+ anim: "gg-b4",
1808
+ dur: "0.55s",
1809
+ delay: "0.15s"
1810
+ }
1811
+ ].map((b, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1812
+ "span",
1813
+ {
1814
+ style: {
1815
+ width: "2.5px",
1816
+ height: "4px",
1817
+ borderRadius: "1px",
1818
+ backgroundColor: "#ef4444",
1819
+ animation: `${b.anim} ${b.dur} ease-in-out ${b.delay} infinite`,
1820
+ display: "inline-block",
1821
+ alignSelf: "center"
1822
+ }
1823
+ },
1824
+ i
1825
+ ))
1919
1826
  }
1920
1827
  )
1921
1828
  ]
1922
1829
  }
1923
- )
1924
- }
1925
- )
1926
- ] }),
1927
- transcribeAudio2 && !isListening && !isTranscribing && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1928
- "div",
1929
- {
1930
- style: {
1931
- display: "flex",
1932
- alignItems: "center",
1933
- gap: "5px",
1934
- marginTop: "5px",
1935
- fontSize: "11px",
1936
- color: t.text,
1937
- opacity: 0.55,
1938
- userSelect: "none"
1939
- },
1940
- children: [
1941
- /* @__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: [
1942
- /* @__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" }),
1943
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
1944
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", x2: "12", y1: "19", y2: "22" })
1945
- ] }),
1946
- "Hold Space to speak \u2014 we'll transcribe it"
1947
- ]
1948
- }
1949
- ),
1950
- isEnhanced && originalDescription !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1951
- "div",
1952
- {
1953
- style: {
1954
- display: "flex",
1955
- alignItems: "center",
1956
- gap: "6px",
1957
- marginTop: "6px",
1958
- padding: "6px 8px",
1959
- borderRadius: "6px",
1960
- background: isDark ? "rgba(34,211,238,0.06)" : "rgba(8,145,178,0.05)",
1961
- border: `1px solid ${t.accent}40`
1962
- },
1963
- children: [
1964
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1965
- "svg",
1966
- {
1967
- width: "12",
1968
- height: "12",
1969
- viewBox: "0 0 24 24",
1970
- fill: "none",
1971
- style: { flexShrink: 0 },
1972
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1973
- "path",
1974
- {
1975
- d: "M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5L12 3z",
1976
- stroke: t.accent,
1977
- strokeWidth: "1.5",
1978
- strokeLinejoin: "round"
1979
- }
1980
- )
1981
- }
1982
1830
  ),
1983
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1984
- "span",
1985
- {
1986
- style: {
1987
- fontSize: "11px",
1988
- color: t.textMuted,
1989
- flex: 1
1990
- },
1991
- children: "AI enhanced"
1992
- }
1993
- ),
1994
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1831
+ enhanceText2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1995
1832
  "button",
1996
1833
  {
1997
1834
  type: "button",
1998
- onClick: () => {
1999
- setIsEnhanced(false);
2000
- setOriginalDescription(null);
1835
+ onClick: async () => {
1836
+ var _a;
1837
+ if (!description.trim() || isEnhancing) return;
1838
+ setIsEnhancing(true);
1839
+ try {
1840
+ const polished = await enhanceText2(
1841
+ description,
1842
+ (_a = screenshots[0]) != null ? _a : null
1843
+ );
1844
+ if (polished && polished !== description) {
1845
+ setOriginalDescription(description);
1846
+ setDescription(polished);
1847
+ setIsEnhanced(true);
1848
+ if (validationError)
1849
+ setValidationError(null);
1850
+ }
1851
+ } finally {
1852
+ setIsEnhancing(false);
1853
+ }
2001
1854
  },
1855
+ disabled: !description.trim() || isEnhancing || isEnhanced,
2002
1856
  style: {
2003
- padding: "2px 8px",
1857
+ position: "absolute",
1858
+ top: "6px",
1859
+ right: "6px",
1860
+ display: "inline-flex",
1861
+ alignItems: "center",
1862
+ gap: "4px",
1863
+ background: t.inputBg,
1864
+ border: "none",
1865
+ padding: "2px 6px",
2004
1866
  borderRadius: "4px",
2005
- border: `1px solid ${t.accent}`,
2006
- background: t.accent,
2007
- color: t.accentText,
1867
+ fontFamily: "inherit",
2008
1868
  fontSize: "11px",
2009
- fontWeight: 600,
2010
- cursor: "pointer",
2011
- fontFamily: "inherit"
1869
+ color: t.textMuted,
1870
+ cursor: !description.trim() || isEnhancing ? "default" : "pointer",
1871
+ opacity: !description.trim() ? 0.5 : 1,
1872
+ zIndex: 1
2012
1873
  },
2013
- children: "Keep"
1874
+ title: "Polish grammar \u2014 preserves your meaning",
1875
+ children: [
1876
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1877
+ "svg",
1878
+ {
1879
+ width: "11",
1880
+ height: "11",
1881
+ viewBox: "0 0 24 24",
1882
+ fill: "none",
1883
+ "aria-hidden": "true",
1884
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1885
+ "path",
1886
+ {
1887
+ 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",
1888
+ stroke: t.textMuted,
1889
+ strokeWidth: "1.5",
1890
+ strokeLinejoin: "round"
1891
+ }
1892
+ )
1893
+ }
1894
+ ),
1895
+ isEnhancing ? "Enhancing..." : "AI enhance"
1896
+ ]
2014
1897
  }
2015
1898
  ),
2016
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1899
+ transcribeAudio2 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2017
1900
  "button",
2018
1901
  {
2019
1902
  type: "button",
2020
1903
  onClick: () => {
2021
- setDescription(originalDescription);
2022
- setIsEnhanced(false);
2023
- setOriginalDescription(null);
1904
+ void toggleVoice();
2024
1905
  },
1906
+ title: isListening ? "Stop recording" : "Speak your report",
2025
1907
  style: {
2026
- padding: "2px 8px",
2027
- borderRadius: "4px",
2028
- border: `1px solid ${t.inputBorder}`,
1908
+ position: "absolute",
1909
+ bottom: "14px",
1910
+ right: "7px",
1911
+ width: "26px",
1912
+ height: "26px",
1913
+ display: "flex",
1914
+ alignItems: "center",
1915
+ justifyContent: "center",
2029
1916
  background: "transparent",
2030
- color: t.textMuted,
2031
- fontSize: "11px",
2032
- fontWeight: 600,
1917
+ border: `1.5px solid ${isListening ? "#ef4444" : t.inputBorder}`,
1918
+ borderRadius: "6px",
1919
+ color: isListening ? "#ef4444" : t.textMuted,
2033
1920
  cursor: "pointer",
2034
- fontFamily: "inherit"
1921
+ zIndex: 1,
1922
+ padding: 0,
1923
+ transition: "border-color 0.2s ease, color 0.2s ease"
2035
1924
  },
2036
- children: "Restore"
1925
+ children: isListening ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1926
+ "svg",
1927
+ {
1928
+ width: "12",
1929
+ height: "12",
1930
+ viewBox: "0 0 24 24",
1931
+ fill: "currentColor",
1932
+ "aria-hidden": "true",
1933
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1934
+ "rect",
1935
+ {
1936
+ x: "6",
1937
+ y: "6",
1938
+ width: "12",
1939
+ height: "12",
1940
+ rx: "2"
1941
+ }
1942
+ )
1943
+ }
1944
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1945
+ "svg",
1946
+ {
1947
+ width: "13",
1948
+ height: "13",
1949
+ viewBox: "0 0 24 24",
1950
+ fill: "none",
1951
+ "aria-hidden": "true",
1952
+ children: [
1953
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1954
+ "rect",
1955
+ {
1956
+ x: "9",
1957
+ y: "2",
1958
+ width: "6",
1959
+ height: "12",
1960
+ rx: "3",
1961
+ stroke: "currentColor",
1962
+ strokeWidth: "1.8"
1963
+ }
1964
+ ),
1965
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1966
+ "path",
1967
+ {
1968
+ d: "M5 10a7 7 0 0014 0M12 19v3M9 22h6",
1969
+ stroke: "currentColor",
1970
+ strokeWidth: "1.8",
1971
+ strokeLinecap: "round"
1972
+ }
1973
+ )
1974
+ ]
1975
+ }
1976
+ )
2037
1977
  }
2038
1978
  )
2039
- ]
2040
- }
2041
- ),
2042
- voiceError && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2043
- "p",
2044
- {
2045
- style: {
2046
- color: "#ef4444",
2047
- fontSize: "11px",
2048
- marginTop: "4px",
2049
- marginBottom: 0
2050
- },
2051
- children: voiceError
2052
- }
2053
- ),
2054
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginTop: "10px" }, children: [
2055
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2056
- "span",
2057
- {
2058
- style: {
2059
- fontSize: "12px",
2060
- color: t.textMuted,
2061
- marginBottom: "6px",
2062
- display: "block",
2063
- fontWeight: 500
2064
- },
2065
- children: [
2066
- "Screenshots",
2067
- screenshots.length > 0 ? ` (${screenshots.length})` : ""
2068
- ]
2069
- }
2070
- ),
2071
- screenshots.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2072
- "div",
2073
- {
2074
- style: {
2075
- display: "flex",
2076
- flexWrap: "wrap",
2077
- gap: "8px",
2078
- marginBottom: "8px"
2079
- },
2080
- children: screenshots.map((src, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2081
- "div",
1979
+ ] }),
1980
+ transcribeAudio2 && !isListening && !isTranscribing && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1981
+ "div",
1982
+ {
1983
+ style: {
1984
+ display: "flex",
1985
+ alignItems: "center",
1986
+ gap: "5px",
1987
+ marginTop: "5px",
1988
+ fontSize: "11px",
1989
+ color: t.text,
1990
+ opacity: 0.55,
1991
+ userSelect: "none"
1992
+ },
1993
+ children: [
1994
+ /* @__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: [
1995
+ /* @__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" }),
1996
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
1997
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: "12", x2: "12", y1: "19", y2: "22" })
1998
+ ] }),
1999
+ "Hold Space to speak \u2014 we'll transcribe it"
2000
+ ]
2001
+ }
2002
+ ),
2003
+ isEnhanced && originalDescription !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2004
+ "div",
2005
+ {
2006
+ style: {
2007
+ display: "flex",
2008
+ alignItems: "center",
2009
+ gap: "6px",
2010
+ marginTop: "6px",
2011
+ padding: "6px 8px",
2012
+ borderRadius: "6px",
2013
+ background: isDark ? "rgba(34,211,238,0.06)" : "rgba(8,145,178,0.05)",
2014
+ border: `1px solid ${t.accent}40`
2015
+ },
2016
+ children: [
2017
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2018
+ "svg",
2019
+ {
2020
+ width: "12",
2021
+ height: "12",
2022
+ viewBox: "0 0 24 24",
2023
+ fill: "none",
2024
+ style: { flexShrink: 0 },
2025
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2026
+ "path",
2027
+ {
2028
+ d: "M12 3l1.5 4.5L18 9l-4.5 1.5L12 15l-1.5-4.5L6 9l4.5-1.5L12 3z",
2029
+ stroke: t.accent,
2030
+ strokeWidth: "1.5",
2031
+ strokeLinejoin: "round"
2032
+ }
2033
+ )
2034
+ }
2035
+ ),
2036
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2037
+ "span",
2038
+ {
2039
+ style: {
2040
+ fontSize: "11px",
2041
+ color: t.textMuted,
2042
+ flex: 1
2043
+ },
2044
+ children: "AI enhanced"
2045
+ }
2046
+ ),
2047
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2048
+ "button",
2049
+ {
2050
+ type: "button",
2051
+ onClick: () => {
2052
+ setIsEnhanced(false);
2053
+ setOriginalDescription(null);
2054
+ },
2055
+ style: {
2056
+ padding: "2px 8px",
2057
+ borderRadius: "4px",
2058
+ border: `1px solid ${t.accent}`,
2059
+ background: t.accent,
2060
+ color: t.accentText,
2061
+ fontSize: "11px",
2062
+ fontWeight: 600,
2063
+ cursor: "pointer",
2064
+ fontFamily: "inherit"
2065
+ },
2066
+ children: "Keep"
2067
+ }
2068
+ ),
2069
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2070
+ "button",
2071
+ {
2072
+ type: "button",
2073
+ onClick: () => {
2074
+ setDescription(originalDescription);
2075
+ setIsEnhanced(false);
2076
+ setOriginalDescription(null);
2077
+ },
2078
+ style: {
2079
+ padding: "2px 8px",
2080
+ borderRadius: "4px",
2081
+ border: `1px solid ${t.inputBorder}`,
2082
+ background: "transparent",
2083
+ color: t.textMuted,
2084
+ fontSize: "11px",
2085
+ fontWeight: 600,
2086
+ cursor: "pointer",
2087
+ fontFamily: "inherit"
2088
+ },
2089
+ children: "Restore"
2090
+ }
2091
+ )
2092
+ ]
2093
+ }
2094
+ ),
2095
+ voiceError && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2096
+ "p",
2097
+ {
2098
+ style: {
2099
+ color: "#ef4444",
2100
+ fontSize: "11px",
2101
+ marginTop: "4px",
2102
+ marginBottom: 0
2103
+ },
2104
+ children: voiceError
2105
+ }
2106
+ ),
2107
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginTop: "10px" }, children: [
2108
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2109
+ "span",
2082
2110
  {
2083
2111
  style: {
2084
- position: "relative",
2085
- width: "56px",
2086
- height: "56px"
2112
+ fontSize: "12px",
2113
+ color: t.textMuted,
2114
+ marginBottom: "6px",
2115
+ display: "block",
2116
+ fontWeight: 500
2087
2117
  },
2088
2118
  children: [
2089
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2090
- "img",
2091
- {
2092
- src,
2093
- alt: `Screenshot ${i + 1}`,
2094
- onClick: () => setPreviewIndex(i),
2095
- style: {
2096
- width: "100%",
2097
- height: "100%",
2098
- borderRadius: "6px",
2099
- border: `1px solid ${t.border}`,
2100
- objectFit: "cover",
2101
- objectPosition: "top",
2102
- cursor: "zoom-in",
2103
- display: "block"
2104
- }
2105
- }
2106
- ),
2107
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2108
- "button",
2109
- {
2110
- type: "button",
2111
- onClick: () => setScreenshots(
2112
- (prev) => prev.filter((_, idx) => idx !== i)
2119
+ "Screenshots",
2120
+ screenshots.length > 0 ? ` (${screenshots.length})` : ""
2121
+ ]
2122
+ }
2123
+ ),
2124
+ screenshots.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2125
+ "div",
2126
+ {
2127
+ style: {
2128
+ display: "flex",
2129
+ flexWrap: "wrap",
2130
+ gap: "8px",
2131
+ marginBottom: "8px"
2132
+ },
2133
+ children: screenshots.map((src, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2134
+ "div",
2135
+ {
2136
+ style: {
2137
+ position: "relative",
2138
+ width: "56px",
2139
+ height: "56px"
2140
+ },
2141
+ children: [
2142
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2143
+ "img",
2144
+ {
2145
+ src,
2146
+ alt: `Screenshot ${i + 1}`,
2147
+ onClick: () => setPreviewIndex(i),
2148
+ style: {
2149
+ width: "100%",
2150
+ height: "100%",
2151
+ borderRadius: "6px",
2152
+ border: `1px solid ${t.border}`,
2153
+ objectFit: "cover",
2154
+ objectPosition: "top",
2155
+ cursor: "zoom-in",
2156
+ display: "block"
2157
+ }
2158
+ }
2113
2159
  ),
2114
- "aria-label": `Remove screenshot ${i + 1}`,
2115
- style: {
2116
- position: "absolute",
2117
- top: "-6px",
2118
- right: "-6px",
2119
- width: "17px",
2120
- height: "17px",
2121
- borderRadius: "50%",
2122
- border: "none",
2123
- background: "#ef4444",
2124
- color: "#fff",
2125
- fontSize: "11px",
2126
- lineHeight: 1,
2127
- padding: 0,
2128
- cursor: "pointer",
2129
- display: "flex",
2130
- alignItems: "center",
2131
- justifyContent: "center"
2132
- },
2133
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2134
- "svg",
2160
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2161
+ "button",
2135
2162
  {
2136
- width: "8",
2137
- height: "8",
2138
- viewBox: "0 0 10 10",
2139
- fill: "none",
2163
+ type: "button",
2164
+ onClick: () => setScreenshots(
2165
+ (prev) => prev.filter((_, idx) => idx !== i)
2166
+ ),
2167
+ "aria-label": `Remove screenshot ${i + 1}`,
2168
+ style: {
2169
+ position: "absolute",
2170
+ top: "-6px",
2171
+ right: "-6px",
2172
+ width: "17px",
2173
+ height: "17px",
2174
+ borderRadius: "50%",
2175
+ border: "none",
2176
+ background: "#ef4444",
2177
+ color: "#fff",
2178
+ fontSize: "11px",
2179
+ lineHeight: 1,
2180
+ padding: 0,
2181
+ cursor: "pointer",
2182
+ display: "flex",
2183
+ alignItems: "center",
2184
+ justifyContent: "center"
2185
+ },
2140
2186
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2141
- "path",
2187
+ "svg",
2142
2188
  {
2143
- d: "M1 1L9 9M9 1L1 9",
2144
- stroke: "currentColor",
2145
- strokeWidth: "1.5",
2146
- strokeLinecap: "round"
2189
+ width: "8",
2190
+ height: "8",
2191
+ viewBox: "0 0 10 10",
2192
+ fill: "none",
2193
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2194
+ "path",
2195
+ {
2196
+ d: "M1 1L9 9M9 1L1 9",
2197
+ stroke: "currentColor",
2198
+ strokeWidth: "1.5",
2199
+ strokeLinecap: "round"
2200
+ }
2201
+ )
2147
2202
  }
2148
2203
  )
2149
2204
  }
2150
- )
2151
- }
2152
- ),
2153
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2154
- "button",
2155
- {
2156
- type: "button",
2157
- onClick: () => setAnnotatingIndex(i),
2158
- "aria-label": `Annotate screenshot ${i + 1}`,
2159
- title: "Annotate",
2160
- style: {
2161
- position: "absolute",
2162
- top: "50%",
2163
- left: "50%",
2164
- transform: "translate(-50%, -50%)",
2165
- width: "24px",
2166
- height: "24px",
2167
- borderRadius: "50%",
2168
- border: "none",
2169
- background: t.accent,
2170
- color: t.accentText,
2171
- padding: 0,
2172
- cursor: "pointer",
2173
- display: "flex",
2174
- alignItems: "center",
2175
- justifyContent: "center",
2176
- boxShadow: "0 2px 8px rgba(0,0,0,0.45)"
2177
- },
2178
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2179
- "svg",
2205
+ ),
2206
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2207
+ "button",
2180
2208
  {
2181
- width: "12",
2182
- height: "12",
2183
- viewBox: "0 0 24 24",
2184
- fill: "none",
2209
+ type: "button",
2210
+ onClick: () => setAnnotatingIndex(i),
2211
+ "aria-label": `Annotate screenshot ${i + 1}`,
2212
+ title: "Annotate",
2213
+ style: {
2214
+ position: "absolute",
2215
+ top: "50%",
2216
+ left: "50%",
2217
+ transform: "translate(-50%, -50%)",
2218
+ width: "24px",
2219
+ height: "24px",
2220
+ borderRadius: "50%",
2221
+ border: "none",
2222
+ background: t.accent,
2223
+ color: t.accentText,
2224
+ padding: 0,
2225
+ cursor: "pointer",
2226
+ display: "flex",
2227
+ alignItems: "center",
2228
+ justifyContent: "center",
2229
+ boxShadow: "0 2px 8px rgba(0,0,0,0.45)"
2230
+ },
2185
2231
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2186
- "path",
2232
+ "svg",
2187
2233
  {
2188
- d: "M12 20h9M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4L16.5 3.5z",
2189
- stroke: "currentColor",
2190
- strokeWidth: "2.2",
2191
- strokeLinecap: "round",
2192
- strokeLinejoin: "round"
2234
+ width: "12",
2235
+ height: "12",
2236
+ viewBox: "0 0 24 24",
2237
+ fill: "none",
2238
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2239
+ "path",
2240
+ {
2241
+ d: "M12 20h9M16.5 3.5a2.12 2.12 0 013 3L7 19l-4 1 1-4L16.5 3.5z",
2242
+ stroke: "currentColor",
2243
+ strokeWidth: "2.2",
2244
+ strokeLinecap: "round",
2245
+ strokeLinejoin: "round"
2246
+ }
2247
+ )
2193
2248
  }
2194
2249
  )
2195
2250
  }
2196
2251
  )
2252
+ ]
2253
+ },
2254
+ i
2255
+ ))
2256
+ }
2257
+ ),
2258
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2259
+ "div",
2260
+ {
2261
+ onClick: () => {
2262
+ var _a;
2263
+ return (_a = fileInputRef.current) == null ? void 0 : _a.click();
2264
+ },
2265
+ onDrop: (e) => {
2266
+ e.preventDefault();
2267
+ setScreenshotDragOver(false);
2268
+ const files = Array.from(
2269
+ e.dataTransfer.files
2270
+ ).filter((f) => f.type.startsWith("image/"));
2271
+ files.forEach((file) => {
2272
+ const reader = new FileReader();
2273
+ reader.onload = () => setScreenshots((prev) => [
2274
+ ...prev,
2275
+ reader.result
2276
+ ]);
2277
+ reader.readAsDataURL(file);
2278
+ });
2279
+ },
2280
+ onDragOver: (e) => {
2281
+ e.preventDefault();
2282
+ setScreenshotDragOver(true);
2283
+ },
2284
+ onDragLeave: () => setScreenshotDragOver(false),
2285
+ style: {
2286
+ border: `1.5px dashed ${screenshotDragOver ? t.accent : t.inputBorder}`,
2287
+ borderRadius: "8px",
2288
+ padding: screenshots.length > 0 ? "8px 12px" : "18px 12px",
2289
+ textAlign: "center",
2290
+ cursor: "pointer",
2291
+ background: screenshotDragOver ? isDark ? "rgba(34,211,238,0.06)" : "rgba(8,145,178,0.04)" : "transparent",
2292
+ transition: "border-color 0.15s ease, background 0.15s ease"
2293
+ },
2294
+ children: screenshots.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2295
+ "p",
2296
+ {
2297
+ style: {
2298
+ margin: 0,
2299
+ fontSize: "11px",
2300
+ fontWeight: 500,
2301
+ color: t.textMuted
2302
+ },
2303
+ children: screenshotDragOver ? "Drop to attach" : "+ Add more \xB7 Drag & drop \xB7 Paste"
2304
+ }
2305
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2306
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2307
+ "svg",
2308
+ {
2309
+ width: "22",
2310
+ height: "22",
2311
+ viewBox: "0 0 24 24",
2312
+ fill: "none",
2313
+ style: {
2314
+ margin: "0 auto 6px",
2315
+ display: "block",
2316
+ opacity: 0.45
2317
+ },
2318
+ children: [
2319
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2320
+ "rect",
2321
+ {
2322
+ x: "3",
2323
+ y: "3",
2324
+ width: "18",
2325
+ height: "18",
2326
+ rx: "2",
2327
+ stroke: t.textMuted,
2328
+ strokeWidth: "1.5"
2329
+ }
2330
+ ),
2331
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2332
+ "circle",
2333
+ {
2334
+ cx: "8.5",
2335
+ cy: "8.5",
2336
+ r: "1.5",
2337
+ stroke: t.textMuted,
2338
+ strokeWidth: "1.5"
2339
+ }
2340
+ ),
2341
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2342
+ "path",
2343
+ {
2344
+ d: "M21 15l-5-5L5 21",
2345
+ stroke: t.textMuted,
2346
+ strokeWidth: "1.5",
2347
+ strokeLinecap: "round",
2348
+ strokeLinejoin: "round"
2349
+ }
2350
+ )
2351
+ ]
2352
+ }
2353
+ ),
2354
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2355
+ "p",
2356
+ {
2357
+ style: {
2358
+ margin: 0,
2359
+ fontSize: "12px",
2360
+ fontWeight: 600,
2361
+ color: t.text
2362
+ },
2363
+ children: screenshotDragOver ? "Drop to attach" : "Add screenshots"
2364
+ }
2365
+ ),
2366
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2367
+ "p",
2368
+ {
2369
+ style: {
2370
+ margin: "3px 0 0",
2371
+ fontSize: "11px",
2372
+ color: t.textMuted
2373
+ },
2374
+ children: "Drag & drop \xB7 Paste \u2318V / Ctrl+V \xB7 Click to browse"
2197
2375
  }
2198
2376
  )
2199
- ]
2200
- },
2201
- i
2202
- ))
2203
- }
2204
- ),
2205
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2206
- "div",
2207
- {
2208
- onClick: () => {
2209
- var _a;
2210
- return (_a = fileInputRef.current) == null ? void 0 : _a.click();
2211
- },
2212
- onDrop: (e) => {
2213
- e.preventDefault();
2214
- setScreenshotDragOver(false);
2215
- const files = Array.from(
2216
- e.dataTransfer.files
2217
- ).filter((f) => f.type.startsWith("image/"));
2218
- files.forEach((file) => {
2219
- const reader = new FileReader();
2220
- reader.onload = () => setScreenshots((prev) => [
2221
- ...prev,
2222
- reader.result
2223
- ]);
2224
- reader.readAsDataURL(file);
2225
- });
2226
- },
2227
- onDragOver: (e) => {
2228
- e.preventDefault();
2229
- setScreenshotDragOver(true);
2230
- },
2231
- onDragLeave: () => setScreenshotDragOver(false),
2232
- style: {
2233
- border: `1.5px dashed ${screenshotDragOver ? t.accent : t.inputBorder}`,
2234
- borderRadius: "8px",
2235
- padding: screenshots.length > 0 ? "8px 12px" : "18px 12px",
2236
- textAlign: "center",
2237
- cursor: "pointer",
2238
- background: screenshotDragOver ? isDark ? "rgba(34,211,238,0.06)" : "rgba(8,145,178,0.04)" : "transparent",
2239
- transition: "border-color 0.15s ease, background 0.15s ease"
2240
- },
2241
- children: screenshots.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2242
- "p",
2377
+ ] })
2378
+ }
2379
+ )
2380
+ ] }),
2381
+ showSeverity && reportType === "BUG" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginTop: "10px" }, children: [
2382
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2383
+ "span",
2243
2384
  {
2244
2385
  style: {
2245
- margin: 0,
2246
- fontSize: "11px",
2247
- fontWeight: 500,
2248
- color: t.textMuted
2386
+ fontSize: "12px",
2387
+ color: t.textMuted,
2388
+ marginBottom: "6px",
2389
+ display: "block"
2249
2390
  },
2250
- children: screenshotDragOver ? "Drop to attach" : "+ Add more \xB7 Drag & drop \xB7 Paste"
2391
+ children: "Severity"
2251
2392
  }
2252
- ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
2253
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
2254
- "svg",
2255
- {
2256
- width: "22",
2257
- height: "22",
2258
- viewBox: "0 0 24 24",
2259
- fill: "none",
2260
- style: {
2261
- margin: "0 auto 6px",
2262
- display: "block",
2263
- opacity: 0.45
2264
- },
2265
- children: [
2266
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2267
- "rect",
2268
- {
2269
- x: "3",
2270
- y: "3",
2271
- width: "18",
2272
- height: "18",
2273
- rx: "2",
2274
- stroke: t.textMuted,
2275
- strokeWidth: "1.5"
2276
- }
2277
- ),
2278
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2279
- "circle",
2280
- {
2281
- cx: "8.5",
2282
- cy: "8.5",
2283
- r: "1.5",
2284
- stroke: t.textMuted,
2285
- strokeWidth: "1.5"
2286
- }
2287
- ),
2288
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2289
- "path",
2290
- {
2291
- d: "M21 15l-5-5L5 21",
2292
- stroke: t.textMuted,
2293
- strokeWidth: "1.5",
2294
- strokeLinecap: "round",
2295
- strokeLinejoin: "round"
2296
- }
2297
- )
2298
- ]
2299
- }
2300
- ),
2301
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2302
- "p",
2303
- {
2304
- style: {
2305
- margin: 0,
2306
- fontSize: "12px",
2307
- fontWeight: 600,
2308
- color: t.text
2309
- },
2310
- children: screenshotDragOver ? "Drop to attach" : "Add screenshots"
2311
- }
2312
- ),
2313
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2314
- "p",
2315
- {
2316
- style: {
2317
- margin: "3px 0 0",
2318
- fontSize: "11px",
2319
- color: t.textMuted
2320
- },
2321
- children: "Drag & drop \xB7 Paste \u2318V / Ctrl+V \xB7 Click to browse"
2322
- }
2323
- )
2324
- ] })
2325
- }
2326
- )
2327
- ] }),
2328
- showSeverity && reportType === "BUG" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginTop: "10px" }, children: [
2329
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2330
- "span",
2331
- {
2332
- style: {
2333
- fontSize: "12px",
2334
- color: t.textMuted,
2335
- marginBottom: "6px",
2336
- display: "block"
2337
- },
2338
- children: "Severity"
2339
- }
2340
- ),
2341
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", gap: "6px" }, children: ["low", "medium", "high"].map((s) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2342
- "button",
2343
- {
2344
- type: "button",
2345
- onClick: () => setSeverity(s),
2346
- style: {
2347
- flex: 1,
2348
- padding: "6px 0",
2349
- borderRadius: "6px",
2350
- border: `1px solid ${severity === s ? t.accent : t.inputBorder}`,
2351
- background: severity === s ? t.accent : "transparent",
2352
- color: severity === s ? t.accentText : t.text,
2353
- fontSize: "12px",
2354
- fontWeight: 600,
2355
- cursor: "pointer",
2356
- fontFamily: "inherit",
2357
- textTransform: "capitalize",
2358
- transition: "all 0.15s ease"
2359
- },
2360
- children: s
2361
- },
2362
- s
2363
- )) })
2364
- ] }),
2365
- validationError && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2366
- "p",
2367
- {
2368
- style: {
2369
- color: "#ef4444",
2370
- fontSize: "12px",
2371
- marginTop: "6px"
2372
- },
2373
- children: validationError
2374
- }
2375
- ),
2376
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2377
- "button",
2378
- {
2379
- type: "button",
2380
- onClick: handleSubmit,
2381
- disabled: !description.trim() || isSubmitting,
2382
- style: {
2383
- marginTop: "12px",
2384
- width: "100%",
2385
- padding: "10px",
2386
- borderRadius: "8px",
2387
- border: "none",
2388
- backgroundColor: !description.trim() || isSubmitting ? t.bgSecondary : t.accent,
2389
- color: !description.trim() || isSubmitting ? t.textMuted : t.accentText,
2390
- fontSize: "14px",
2391
- fontWeight: 600,
2392
- cursor: !description.trim() || isSubmitting ? "not-allowed" : "pointer",
2393
- fontFamily: "inherit",
2394
- transition: "background-color 0.15s ease"
2395
- },
2396
- children: isSubmitting ? "Sending..." : "Send Report"
2397
- }
2398
- )
2399
- ] })
2400
- ] }) }),
2393
+ ),
2394
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", gap: "6px" }, children: ["low", "medium", "high"].map((s) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2395
+ "button",
2396
+ {
2397
+ type: "button",
2398
+ onClick: () => setSeverity(s),
2399
+ style: {
2400
+ flex: 1,
2401
+ padding: "6px 0",
2402
+ borderRadius: "6px",
2403
+ border: `1px solid ${severity === s ? t.accent : t.inputBorder}`,
2404
+ background: severity === s ? t.accent : "transparent",
2405
+ color: severity === s ? t.accentText : t.text,
2406
+ fontSize: "12px",
2407
+ fontWeight: 600,
2408
+ cursor: "pointer",
2409
+ fontFamily: "inherit",
2410
+ textTransform: "capitalize",
2411
+ transition: "all 0.15s ease"
2412
+ },
2413
+ children: s
2414
+ },
2415
+ s
2416
+ )) })
2417
+ ] }),
2418
+ validationError && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2419
+ "p",
2420
+ {
2421
+ style: {
2422
+ color: "#ef4444",
2423
+ fontSize: "12px",
2424
+ marginTop: "6px"
2425
+ },
2426
+ children: validationError
2427
+ }
2428
+ ),
2429
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2430
+ "button",
2431
+ {
2432
+ type: "button",
2433
+ onClick: handleSubmit,
2434
+ disabled: !description.trim() || isSubmitting,
2435
+ style: {
2436
+ marginTop: "12px",
2437
+ width: "100%",
2438
+ padding: "10px",
2439
+ borderRadius: "8px",
2440
+ border: "none",
2441
+ backgroundColor: !description.trim() || isSubmitting ? t.bgSecondary : t.accent,
2442
+ color: !description.trim() || isSubmitting ? t.textMuted : t.accentText,
2443
+ fontSize: "14px",
2444
+ fontWeight: 600,
2445
+ cursor: !description.trim() || isSubmitting ? "not-allowed" : "pointer",
2446
+ fontFamily: "inherit",
2447
+ transition: "background-color 0.15s ease"
2448
+ },
2449
+ children: isSubmitting ? "Sending..." : "Send Report"
2450
+ }
2451
+ )
2452
+ ] })
2453
+ ] })
2454
+ }
2455
+ ),
2401
2456
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2402
2457
  "div",
2403
2458
  {
@@ -2985,7 +3040,7 @@ function GlitchgrabProviderInner({
2985
3040
  try {
2986
3041
  if (typeof window === "undefined") return;
2987
3042
  const handleKeyDown = (e) => {
2988
- if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key.toLowerCase() === "g") {
3043
+ if (matchesShortcut(e)) {
2989
3044
  e.preventDefault();
2990
3045
  openReportDialog();
2991
3046
  }
@@ -2995,6 +3050,10 @@ function GlitchgrabProviderInner({
2995
3050
  } catch (e) {
2996
3051
  }
2997
3052
  }, [openReportDialog]);
3053
+ const [shortcutLabel, setShortcutLabel] = (0, import_react3.useState)(GLITCHGRAB_SHORTCUT);
3054
+ (0, import_react3.useEffect)(() => {
3055
+ setShortcutLabel(getShortcutLabel());
3056
+ }, []);
2998
3057
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
2999
3058
  GlitchgrabContext.Provider,
3000
3059
  {
@@ -3005,7 +3064,8 @@ function GlitchgrabProviderInner({
3005
3064
  report,
3006
3065
  addBreadcrumb: addBreadcrumb2,
3007
3066
  openReportDialog,
3008
- enhanceText: enhance
3067
+ enhanceText: enhance,
3068
+ shortcutLabel
3009
3069
  },
3010
3070
  children: [
3011
3071
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
@@ -3237,6 +3297,8 @@ function useGlitchgrabActions({
3237
3297
  }
3238
3298
  // Annotate the CommonJS export names for ESM import in node:
3239
3299
  0 && (module.exports = {
3300
+ GLITCHGRAB_SHORTCUT,
3301
+ GLITCHGRAB_SHORTCUT_MAC,
3240
3302
  GlitchgrabErrorBoundary,
3241
3303
  GlitchgrabProvider,
3242
3304
  ReportButton,
@@ -3249,7 +3311,9 @@ function useGlitchgrabActions({
3249
3311
  enhanceText,
3250
3312
  fetchGlitchgrabReports,
3251
3313
  getBreadcrumbs,
3314
+ getShortcutLabel,
3252
3315
  initBreadcrumbs,
3316
+ matchesShortcut,
3253
3317
  sanitizeUrl,
3254
3318
  sendReport,
3255
3319
  shouldSkipDuplicate,