instruckt 0.4.13 → 0.4.16

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.
@@ -79,15 +79,6 @@ var InstrucktApi = class {
79
79
  if (!res.ok) throw new Error(`instruckt: failed to update annotation (${res.status})`);
80
80
  return toCamelCase(await res.json());
81
81
  }
82
- async addReply(annotationId, content, role = "human") {
83
- const res = await fetch(`${this.endpoint}/annotations/${annotationId}/reply`, {
84
- method: "POST",
85
- headers: headers(),
86
- body: JSON.stringify({ role, content })
87
- });
88
- if (!res.ok) throw new Error(`instruckt: failed to add reply (${res.status})`);
89
- return toCamelCase(await res.json());
90
- }
91
82
  };
92
83
 
93
84
  // src/ui/styles.ts
@@ -163,6 +154,24 @@ var TOOLBAR_CSS = (
163
154
  }
164
155
  .btn svg { display: block; }
165
156
  .btn:hover { background: var(--ik-bg2); color: var(--ik-text); }
157
+ .btn[data-tooltip]::before {
158
+ content: attr(data-tooltip);
159
+ position: absolute;
160
+ right: calc(100% + 8px);
161
+ top: 50%;
162
+ transform: translateY(-50%);
163
+ white-space: nowrap;
164
+ font-size: 11px;
165
+ padding: 4px 8px;
166
+ border-radius: 6px;
167
+ background: var(--ik-text);
168
+ color: var(--ik-bg);
169
+ pointer-events: none;
170
+ opacity: 0;
171
+ transition: opacity .1s ease;
172
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
173
+ }
174
+ .btn[data-tooltip]:hover::before { opacity: 1; }
166
175
  .btn.active { background: var(--ik-accent); color: #fff; }
167
176
  .btn.active:hover { background: var(--ik-accent-h); }
168
177
 
@@ -202,24 +211,7 @@ var TOOLBAR_CSS = (
202
211
  box-shadow: var(--ik-shadow);
203
212
  border-radius: 8px;
204
213
  }
205
- /* Instant tooltip */
206
- .clear-all-btn::before {
207
- content: attr(data-tooltip);
208
- position: absolute;
209
- right: calc(100% + 6px);
210
- top: 50%;
211
- transform: translateY(-50%);
212
- white-space: nowrap;
213
- font-size: 11px;
214
- padding: 4px 8px;
215
- border-radius: 6px;
216
- background: var(--ik-text);
217
- color: var(--ik-bg);
218
- pointer-events: none;
219
- opacity: 0;
220
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
221
- }
222
- .clear-all-btn:hover::before { opacity: 1; }
214
+ /* clear-all tooltip inherits from .btn[data-tooltip]::before */
223
215
  /* Invisible bridge so hover doesn't break crossing the gap */
224
216
  .clear-all-btn::after {
225
217
  content: '';
@@ -358,6 +350,61 @@ var POPUP_CSS = (
358
350
  .chip.important.sel { background:#f97316; border-color:#f97316; }
359
351
  .chip.suggestion.sel{ background:#22c55e; border-color:#22c55e; }
360
352
 
353
+ .screenshot-slot { margin-bottom: 10px; }
354
+
355
+ .btn-capture {
356
+ display: flex;
357
+ align-items: center;
358
+ gap: 6px;
359
+ width: 100%;
360
+ padding: 8px 10px;
361
+ border: 1px dashed var(--ik-border);
362
+ border-radius: 6px;
363
+ background: var(--ik-bg2);
364
+ color: var(--ik-muted);
365
+ font-size: 12px;
366
+ font-family: inherit;
367
+ cursor: pointer;
368
+ transition: border-color .15s, color .15s;
369
+ }
370
+ .btn-capture:hover {
371
+ border-color: var(--ik-accent);
372
+ color: var(--ik-accent);
373
+ }
374
+ .btn-capture svg { flex-shrink: 0; }
375
+
376
+ .screenshot-preview {
377
+ position: relative;
378
+ border-radius: 6px;
379
+ overflow: hidden;
380
+ border: 1px solid var(--ik-border);
381
+ margin-bottom: 10px;
382
+ }
383
+ .screenshot-preview img {
384
+ display: block;
385
+ width: 100%;
386
+ max-height: 200px;
387
+ object-fit: contain;
388
+ background: var(--ik-bg2);
389
+ }
390
+ .screenshot-remove {
391
+ position: absolute;
392
+ top: 4px; right: 4px;
393
+ width: 20px; height: 20px;
394
+ border-radius: 50%;
395
+ border: none;
396
+ background: rgba(0,0,0,.6);
397
+ color: #fff;
398
+ font-size: 12px;
399
+ line-height: 1;
400
+ cursor: pointer;
401
+ display: flex;
402
+ align-items: center;
403
+ justify-content: center;
404
+ padding: 0;
405
+ }
406
+ .screenshot-remove:hover { background: #ef4444; }
407
+
361
408
  textarea {
362
409
  width:100%; min-height:80px; resize:vertical;
363
410
  border:1px solid var(--ik-border); border-radius:6px;
@@ -408,7 +455,6 @@ textarea::placeholder { color:var(--ik-muted); }
408
455
  border-radius:4px; padding:2px 6px;
409
456
  }
410
457
  .status-badge.pending { background:rgba(99,102,241,.15); color:var(--ik-accent); }
411
- .status-badge.acknowledged { background:rgba(249,115,22,.15); color:#f97316; }
412
458
  .status-badge.resolved { background:rgba(34,197,94,.15); color:#22c55e; }
413
459
  .status-badge.dismissed { background:var(--ik-bg2); color:var(--ik-muted); }
414
460
  `
@@ -421,28 +467,29 @@ var MARKER_CSS = (
421
467
  z-index: 2147483645;
422
468
  width: 24px; height: 24px;
423
469
  border-radius: 50%;
424
- background: #6366f1;
470
+ background: var(--ik-marker-default, #6366f1);
425
471
  color: #fff;
426
472
  font-size: 11px; font-weight: 700;
427
473
  display: flex; align-items: center; justify-content: center;
428
474
  cursor: pointer;
429
- box-shadow: 0 2px 8px rgba(99,102,241,.4);
475
+ box-shadow: 0 2px 8px color-mix(in srgb, var(--ik-marker-default, #6366f1) 40%, transparent);
430
476
  transition: transform .15s ease;
431
477
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
432
478
  pointer-events: all;
433
479
  user-select: none;
434
480
  }
435
481
  .ik-marker:hover { transform: scale(1.15); }
436
- .ik-marker.resolved { background: #22c55e; box-shadow: 0 2px 8px rgba(34,197,94,.4); }
437
- .ik-marker.dismissed { background: #71717a; box-shadow: 0 2px 8px rgba(0,0,0,.2); }
438
- .ik-marker.acknowledged { background: #f97316; box-shadow: 0 2px 8px rgba(249,115,22,.4); }
482
+ .ik-marker.has-screenshot { background: var(--ik-marker-screenshot, #22c55e); box-shadow: 0 2px 8px color-mix(in srgb, var(--ik-marker-screenshot, #22c55e) 40%, transparent); }
483
+ .ik-marker.dismissed { background: var(--ik-marker-dismissed, #71717a); box-shadow: 0 2px 8px rgba(0,0,0,.2); }
439
484
  `
440
485
  );
441
- function injectGlobalStyles() {
486
+ function injectGlobalStyles(colors) {
442
487
  if (document.getElementById("instruckt-global")) return;
488
+ const vars = colors ? `:root {${colors.default ? ` --ik-marker-default: ${colors.default};` : ""}${colors.screenshot ? ` --ik-marker-screenshot: ${colors.screenshot};` : ""}${colors.dismissed ? ` --ik-marker-dismissed: ${colors.dismissed};` : ""} }
489
+ ` : "";
443
490
  const style = document.createElement("style");
444
491
  style.id = "instruckt-global";
445
- style.textContent = GLOBAL_CSS + MARKER_CSS;
492
+ style.textContent = vars + GLOBAL_CSS + MARKER_CSS;
446
493
  document.head.appendChild(style);
447
494
  }
448
495
 
@@ -454,10 +501,11 @@ var ICONS = {
454
501
  check: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`,
455
502
  clear: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`,
456
503
  minimize: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="7 13 12 18 17 13"/><line x1="12" y1="6" x2="12" y2="18"/></svg>`,
504
+ screenshot: `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>`,
457
505
  logo: `<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z"/></svg>`
458
506
  };
459
507
  var Toolbar = class {
460
- constructor(position, callbacks) {
508
+ constructor(position, callbacks, keys) {
461
509
  this.position = position;
462
510
  this.callbacks = callbacks;
463
511
  this.fabBadge = null;
@@ -467,11 +515,12 @@ var Toolbar = class {
467
515
  this.totalCount = 0;
468
516
  this.dragging = false;
469
517
  this.dragOffset = { x: 0, y: 0 };
518
+ this.keys = keys != null ? keys : {};
470
519
  this.build();
471
520
  this.setupDrag();
472
521
  }
473
522
  build() {
474
- var _a;
523
+ var _a, _b, _c, _d, _e;
475
524
  this.host = document.createElement("div");
476
525
  this.host.setAttribute("data-instruckt", "toolbar");
477
526
  this.shadow = this.host.attachShadow({ mode: "open" });
@@ -480,16 +529,20 @@ var Toolbar = class {
480
529
  this.shadow.appendChild(style);
481
530
  this.toolbarEl = document.createElement("div");
482
531
  this.toolbarEl.className = "toolbar";
483
- this.annotateBtn = this.makeBtn(ICONS.annotate, "Annotate elements (A)", () => {
532
+ const k = this.keys;
533
+ this.annotateBtn = this.makeBtn(ICONS.annotate, `Annotate elements (${((_a = k.annotate) != null ? _a : "A").toUpperCase()})`, () => {
484
534
  const next = !this.annotateActive;
485
535
  this.setAnnotateActive(next);
486
536
  this.callbacks.onToggleAnnotate(next);
487
537
  });
488
- this.freezeBtn = this.makeBtn(ICONS.freeze, "Freeze page (F)", () => {
538
+ this.freezeBtn = this.makeBtn(ICONS.freeze, `Freeze page (${((_b = k.freeze) != null ? _b : "F").toUpperCase()})`, () => {
489
539
  const next = !this.freezeActive;
490
540
  this.setFreezeActive(next);
491
541
  this.callbacks.onFreezeAnimations(next);
492
542
  });
543
+ const screenshotBtn = this.makeBtn(ICONS.screenshot, `Screenshot region (${((_c = k.screenshot) != null ? _c : "C").toUpperCase()})`, () => {
544
+ this.callbacks.onScreenshot();
545
+ });
493
546
  this.copyBtn = this.makeBtn(ICONS.copy, "Copy annotations as markdown", () => {
494
547
  this.callbacks.onCopy();
495
548
  this.copyBtn.innerHTML = ICONS.check;
@@ -499,22 +552,20 @@ var Toolbar = class {
499
552
  });
500
553
  const clearWrap = document.createElement("div");
501
554
  clearWrap.className = "clear-wrap";
502
- const clearBtn = this.makeBtn(ICONS.clear, "Clear this page (X)", () => {
503
- var _a2, _b;
504
- (_b = (_a2 = this.callbacks).onClearPage) == null ? void 0 : _b.call(_a2);
555
+ const clearBtn = this.makeBtn(ICONS.clear, `Clear this page (${((_d = k.clearPage) != null ? _d : "X").toUpperCase()})`, () => {
556
+ var _a2, _b2;
557
+ (_b2 = (_a2 = this.callbacks).onClearPage) == null ? void 0 : _b2.call(_a2);
505
558
  });
506
559
  clearBtn.classList.add("danger-btn");
507
560
  const clearAllBtn = this.makeBtn(
508
561
  `<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10 10-4.5 10-10S17.5 2 12 2"/><line x1="4.93" y1="4.93" x2="19.07" y2="19.07"/></svg>`,
509
562
  "Delete all instructions.",
510
563
  () => {
511
- var _a2, _b;
512
- return (_b = (_a2 = this.callbacks).onClearAll) == null ? void 0 : _b.call(_a2);
564
+ var _a2, _b2;
565
+ return (_b2 = (_a2 = this.callbacks).onClearAll) == null ? void 0 : _b2.call(_a2);
513
566
  }
514
567
  );
515
568
  clearAllBtn.classList.add("danger-btn", "clear-all-btn");
516
- clearAllBtn.removeAttribute("title");
517
- clearAllBtn.setAttribute("data-tooltip", "Delete all instructions.");
518
569
  clearWrap.appendChild(clearBtn);
519
570
  clearWrap.appendChild(clearAllBtn);
520
571
  const minimizeBtn = this.makeBtn(ICONS.minimize, "Minimize toolbar", () => {
@@ -528,6 +579,7 @@ var Toolbar = class {
528
579
  };
529
580
  this.toolbarEl.append(
530
581
  this.annotateBtn,
582
+ screenshotBtn,
531
583
  mkDiv(),
532
584
  this.freezeBtn,
533
585
  mkDiv(),
@@ -552,14 +604,14 @@ var Toolbar = class {
552
604
  this.host.addEventListener("mousedown", (e) => e.stopPropagation());
553
605
  this.host.addEventListener("pointerdown", (e) => e.stopPropagation());
554
606
  this.applyPosition();
555
- const root = (_a = document.getElementById("instruckt-root")) != null ? _a : document.body;
607
+ const root = (_e = document.getElementById("instruckt-root")) != null ? _e : document.body;
556
608
  root.appendChild(this.host);
557
609
  }
558
- makeBtn(iconHtml, title, onClick) {
610
+ makeBtn(iconHtml, tooltip, onClick) {
559
611
  const btn = document.createElement("button");
560
612
  btn.className = "btn";
561
- btn.title = title;
562
- btn.setAttribute("aria-label", title);
613
+ btn.setAttribute("data-tooltip", tooltip);
614
+ btn.setAttribute("aria-label", tooltip);
563
615
  btn.innerHTML = iconHtml;
564
616
  btn.addEventListener("click", (e) => {
565
617
  e.stopPropagation();
@@ -710,7 +762,953 @@ var ElementHighlight = class {
710
762
  }
711
763
  };
712
764
 
765
+ // node_modules/html-to-image/es/util.js
766
+ function resolveUrl(url, baseUrl) {
767
+ if (url.match(/^[a-z]+:\/\//i)) {
768
+ return url;
769
+ }
770
+ if (url.match(/^\/\//)) {
771
+ return window.location.protocol + url;
772
+ }
773
+ if (url.match(/^[a-z]+:/i)) {
774
+ return url;
775
+ }
776
+ const doc = document.implementation.createHTMLDocument();
777
+ const base = doc.createElement("base");
778
+ const a = doc.createElement("a");
779
+ doc.head.appendChild(base);
780
+ doc.body.appendChild(a);
781
+ if (baseUrl) {
782
+ base.href = baseUrl;
783
+ }
784
+ a.href = url;
785
+ return a.href;
786
+ }
787
+ var uuid = /* @__PURE__ */ (() => {
788
+ let counter = 0;
789
+ const random = () => (
790
+ // eslint-disable-next-line no-bitwise
791
+ `0000${(Math.random() * 36 ** 4 << 0).toString(36)}`.slice(-4)
792
+ );
793
+ return () => {
794
+ counter += 1;
795
+ return `u${random()}${counter}`;
796
+ };
797
+ })();
798
+ function toArray(arrayLike) {
799
+ const arr = [];
800
+ for (let i = 0, l = arrayLike.length; i < l; i++) {
801
+ arr.push(arrayLike[i]);
802
+ }
803
+ return arr;
804
+ }
805
+ var styleProps = null;
806
+ function getStyleProperties(options = {}) {
807
+ if (styleProps) {
808
+ return styleProps;
809
+ }
810
+ if (options.includeStyleProperties) {
811
+ styleProps = options.includeStyleProperties;
812
+ return styleProps;
813
+ }
814
+ styleProps = toArray(window.getComputedStyle(document.documentElement));
815
+ return styleProps;
816
+ }
817
+ function px(node, styleProperty) {
818
+ const win = node.ownerDocument.defaultView || window;
819
+ const val = win.getComputedStyle(node).getPropertyValue(styleProperty);
820
+ return val ? parseFloat(val.replace("px", "")) : 0;
821
+ }
822
+ function getNodeWidth(node) {
823
+ const leftBorder = px(node, "border-left-width");
824
+ const rightBorder = px(node, "border-right-width");
825
+ return node.clientWidth + leftBorder + rightBorder;
826
+ }
827
+ function getNodeHeight(node) {
828
+ const topBorder = px(node, "border-top-width");
829
+ const bottomBorder = px(node, "border-bottom-width");
830
+ return node.clientHeight + topBorder + bottomBorder;
831
+ }
832
+ function getImageSize(targetNode, options = {}) {
833
+ const width = options.width || getNodeWidth(targetNode);
834
+ const height = options.height || getNodeHeight(targetNode);
835
+ return { width, height };
836
+ }
837
+ function getPixelRatio() {
838
+ let ratio;
839
+ let FINAL_PROCESS;
840
+ try {
841
+ FINAL_PROCESS = process;
842
+ } catch (e) {
843
+ }
844
+ const val = FINAL_PROCESS && FINAL_PROCESS.env ? FINAL_PROCESS.env.devicePixelRatio : null;
845
+ if (val) {
846
+ ratio = parseInt(val, 10);
847
+ if (Number.isNaN(ratio)) {
848
+ ratio = 1;
849
+ }
850
+ }
851
+ return ratio || window.devicePixelRatio || 1;
852
+ }
853
+ var canvasDimensionLimit = 16384;
854
+ function checkCanvasDimensions(canvas) {
855
+ if (canvas.width > canvasDimensionLimit || canvas.height > canvasDimensionLimit) {
856
+ if (canvas.width > canvasDimensionLimit && canvas.height > canvasDimensionLimit) {
857
+ if (canvas.width > canvas.height) {
858
+ canvas.height *= canvasDimensionLimit / canvas.width;
859
+ canvas.width = canvasDimensionLimit;
860
+ } else {
861
+ canvas.width *= canvasDimensionLimit / canvas.height;
862
+ canvas.height = canvasDimensionLimit;
863
+ }
864
+ } else if (canvas.width > canvasDimensionLimit) {
865
+ canvas.height *= canvasDimensionLimit / canvas.width;
866
+ canvas.width = canvasDimensionLimit;
867
+ } else {
868
+ canvas.width *= canvasDimensionLimit / canvas.height;
869
+ canvas.height = canvasDimensionLimit;
870
+ }
871
+ }
872
+ }
873
+ function createImage(url) {
874
+ return new Promise((resolve, reject) => {
875
+ const img = new Image();
876
+ img.onload = () => {
877
+ img.decode().then(() => {
878
+ requestAnimationFrame(() => resolve(img));
879
+ });
880
+ };
881
+ img.onerror = reject;
882
+ img.crossOrigin = "anonymous";
883
+ img.decoding = "async";
884
+ img.src = url;
885
+ });
886
+ }
887
+ async function svgToDataURL(svg) {
888
+ return Promise.resolve().then(() => new XMLSerializer().serializeToString(svg)).then(encodeURIComponent).then((html) => `data:image/svg+xml;charset=utf-8,${html}`);
889
+ }
890
+ async function nodeToDataURL(node, width, height) {
891
+ const xmlns = "http://www.w3.org/2000/svg";
892
+ const svg = document.createElementNS(xmlns, "svg");
893
+ const foreignObject = document.createElementNS(xmlns, "foreignObject");
894
+ svg.setAttribute("width", `${width}`);
895
+ svg.setAttribute("height", `${height}`);
896
+ svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
897
+ foreignObject.setAttribute("width", "100%");
898
+ foreignObject.setAttribute("height", "100%");
899
+ foreignObject.setAttribute("x", "0");
900
+ foreignObject.setAttribute("y", "0");
901
+ foreignObject.setAttribute("externalResourcesRequired", "true");
902
+ svg.appendChild(foreignObject);
903
+ foreignObject.appendChild(node);
904
+ return svgToDataURL(svg);
905
+ }
906
+ var isInstanceOfElement = (node, instance) => {
907
+ if (node instanceof instance)
908
+ return true;
909
+ const nodePrototype = Object.getPrototypeOf(node);
910
+ if (nodePrototype === null)
911
+ return false;
912
+ return nodePrototype.constructor.name === instance.name || isInstanceOfElement(nodePrototype, instance);
913
+ };
914
+
915
+ // node_modules/html-to-image/es/clone-pseudos.js
916
+ function formatCSSText(style) {
917
+ const content = style.getPropertyValue("content");
918
+ return `${style.cssText} content: '${content.replace(/'|"/g, "")}';`;
919
+ }
920
+ function formatCSSProperties(style, options) {
921
+ return getStyleProperties(options).map((name) => {
922
+ const value = style.getPropertyValue(name);
923
+ const priority = style.getPropertyPriority(name);
924
+ return `${name}: ${value}${priority ? " !important" : ""};`;
925
+ }).join(" ");
926
+ }
927
+ function getPseudoElementStyle(className, pseudo, style, options) {
928
+ const selector = `.${className}:${pseudo}`;
929
+ const cssText = style.cssText ? formatCSSText(style) : formatCSSProperties(style, options);
930
+ return document.createTextNode(`${selector}{${cssText}}`);
931
+ }
932
+ function clonePseudoElement(nativeNode, clonedNode, pseudo, options) {
933
+ const style = window.getComputedStyle(nativeNode, pseudo);
934
+ const content = style.getPropertyValue("content");
935
+ if (content === "" || content === "none") {
936
+ return;
937
+ }
938
+ const className = uuid();
939
+ try {
940
+ clonedNode.className = `${clonedNode.className} ${className}`;
941
+ } catch (err) {
942
+ return;
943
+ }
944
+ const styleElement = document.createElement("style");
945
+ styleElement.appendChild(getPseudoElementStyle(className, pseudo, style, options));
946
+ clonedNode.appendChild(styleElement);
947
+ }
948
+ function clonePseudoElements(nativeNode, clonedNode, options) {
949
+ clonePseudoElement(nativeNode, clonedNode, ":before", options);
950
+ clonePseudoElement(nativeNode, clonedNode, ":after", options);
951
+ }
952
+
953
+ // node_modules/html-to-image/es/mimes.js
954
+ var WOFF = "application/font-woff";
955
+ var JPEG = "image/jpeg";
956
+ var mimes = {
957
+ woff: WOFF,
958
+ woff2: WOFF,
959
+ ttf: "application/font-truetype",
960
+ eot: "application/vnd.ms-fontobject",
961
+ png: "image/png",
962
+ jpg: JPEG,
963
+ jpeg: JPEG,
964
+ gif: "image/gif",
965
+ tiff: "image/tiff",
966
+ svg: "image/svg+xml",
967
+ webp: "image/webp"
968
+ };
969
+ function getExtension(url) {
970
+ const match = /\.([^./]*?)$/g.exec(url);
971
+ return match ? match[1] : "";
972
+ }
973
+ function getMimeType(url) {
974
+ const extension = getExtension(url).toLowerCase();
975
+ return mimes[extension] || "";
976
+ }
977
+
978
+ // node_modules/html-to-image/es/dataurl.js
979
+ function getContentFromDataUrl(dataURL) {
980
+ return dataURL.split(/,/)[1];
981
+ }
982
+ function isDataUrl(url) {
983
+ return url.search(/^(data:)/) !== -1;
984
+ }
985
+ function makeDataUrl(content, mimeType) {
986
+ return `data:${mimeType};base64,${content}`;
987
+ }
988
+ async function fetchAsDataURL(url, init2, process2) {
989
+ const res = await fetch(url, init2);
990
+ if (res.status === 404) {
991
+ throw new Error(`Resource "${res.url}" not found`);
992
+ }
993
+ const blob = await res.blob();
994
+ return new Promise((resolve, reject) => {
995
+ const reader = new FileReader();
996
+ reader.onerror = reject;
997
+ reader.onloadend = () => {
998
+ try {
999
+ resolve(process2({ res, result: reader.result }));
1000
+ } catch (error) {
1001
+ reject(error);
1002
+ }
1003
+ };
1004
+ reader.readAsDataURL(blob);
1005
+ });
1006
+ }
1007
+ var cache = {};
1008
+ function getCacheKey(url, contentType, includeQueryParams) {
1009
+ let key = url.replace(/\?.*/, "");
1010
+ if (includeQueryParams) {
1011
+ key = url;
1012
+ }
1013
+ if (/ttf|otf|eot|woff2?/i.test(key)) {
1014
+ key = key.replace(/.*\//, "");
1015
+ }
1016
+ return contentType ? `[${contentType}]${key}` : key;
1017
+ }
1018
+ async function resourceToDataURL(resourceUrl, contentType, options) {
1019
+ const cacheKey = getCacheKey(resourceUrl, contentType, options.includeQueryParams);
1020
+ if (cache[cacheKey] != null) {
1021
+ return cache[cacheKey];
1022
+ }
1023
+ if (options.cacheBust) {
1024
+ resourceUrl += (/\?/.test(resourceUrl) ? "&" : "?") + (/* @__PURE__ */ new Date()).getTime();
1025
+ }
1026
+ let dataURL;
1027
+ try {
1028
+ const content = await fetchAsDataURL(resourceUrl, options.fetchRequestInit, ({ res, result }) => {
1029
+ if (!contentType) {
1030
+ contentType = res.headers.get("Content-Type") || "";
1031
+ }
1032
+ return getContentFromDataUrl(result);
1033
+ });
1034
+ dataURL = makeDataUrl(content, contentType);
1035
+ } catch (error) {
1036
+ dataURL = options.imagePlaceholder || "";
1037
+ let msg = `Failed to fetch resource: ${resourceUrl}`;
1038
+ if (error) {
1039
+ msg = typeof error === "string" ? error : error.message;
1040
+ }
1041
+ if (msg) {
1042
+ console.warn(msg);
1043
+ }
1044
+ }
1045
+ cache[cacheKey] = dataURL;
1046
+ return dataURL;
1047
+ }
1048
+
1049
+ // node_modules/html-to-image/es/clone-node.js
1050
+ async function cloneCanvasElement(canvas) {
1051
+ const dataURL = canvas.toDataURL();
1052
+ if (dataURL === "data:,") {
1053
+ return canvas.cloneNode(false);
1054
+ }
1055
+ return createImage(dataURL);
1056
+ }
1057
+ async function cloneVideoElement(video, options) {
1058
+ if (video.currentSrc) {
1059
+ const canvas = document.createElement("canvas");
1060
+ const ctx = canvas.getContext("2d");
1061
+ canvas.width = video.clientWidth;
1062
+ canvas.height = video.clientHeight;
1063
+ ctx === null || ctx === void 0 ? void 0 : ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
1064
+ const dataURL2 = canvas.toDataURL();
1065
+ return createImage(dataURL2);
1066
+ }
1067
+ const poster = video.poster;
1068
+ const contentType = getMimeType(poster);
1069
+ const dataURL = await resourceToDataURL(poster, contentType, options);
1070
+ return createImage(dataURL);
1071
+ }
1072
+ async function cloneIFrameElement(iframe, options) {
1073
+ var _a;
1074
+ try {
1075
+ if ((_a = iframe === null || iframe === void 0 ? void 0 : iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.body) {
1076
+ return await cloneNode(iframe.contentDocument.body, options, true);
1077
+ }
1078
+ } catch (_b) {
1079
+ }
1080
+ return iframe.cloneNode(false);
1081
+ }
1082
+ async function cloneSingleNode(node, options) {
1083
+ if (isInstanceOfElement(node, HTMLCanvasElement)) {
1084
+ return cloneCanvasElement(node);
1085
+ }
1086
+ if (isInstanceOfElement(node, HTMLVideoElement)) {
1087
+ return cloneVideoElement(node, options);
1088
+ }
1089
+ if (isInstanceOfElement(node, HTMLIFrameElement)) {
1090
+ return cloneIFrameElement(node, options);
1091
+ }
1092
+ return node.cloneNode(isSVGElement(node));
1093
+ }
1094
+ var isSlotElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SLOT";
1095
+ var isSVGElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SVG";
1096
+ async function cloneChildren(nativeNode, clonedNode, options) {
1097
+ var _a, _b;
1098
+ if (isSVGElement(clonedNode)) {
1099
+ return clonedNode;
1100
+ }
1101
+ let children = [];
1102
+ if (isSlotElement(nativeNode) && nativeNode.assignedNodes) {
1103
+ children = toArray(nativeNode.assignedNodes());
1104
+ } else if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && ((_a = nativeNode.contentDocument) === null || _a === void 0 ? void 0 : _a.body)) {
1105
+ children = toArray(nativeNode.contentDocument.body.childNodes);
1106
+ } else {
1107
+ children = toArray(((_b = nativeNode.shadowRoot) !== null && _b !== void 0 ? _b : nativeNode).childNodes);
1108
+ }
1109
+ if (children.length === 0 || isInstanceOfElement(nativeNode, HTMLVideoElement)) {
1110
+ return clonedNode;
1111
+ }
1112
+ await children.reduce((deferred, child) => deferred.then(() => cloneNode(child, options)).then((clonedChild) => {
1113
+ if (clonedChild) {
1114
+ clonedNode.appendChild(clonedChild);
1115
+ }
1116
+ }), Promise.resolve());
1117
+ return clonedNode;
1118
+ }
1119
+ function cloneCSSStyle(nativeNode, clonedNode, options) {
1120
+ const targetStyle = clonedNode.style;
1121
+ if (!targetStyle) {
1122
+ return;
1123
+ }
1124
+ const sourceStyle = window.getComputedStyle(nativeNode);
1125
+ if (sourceStyle.cssText) {
1126
+ targetStyle.cssText = sourceStyle.cssText;
1127
+ targetStyle.transformOrigin = sourceStyle.transformOrigin;
1128
+ } else {
1129
+ getStyleProperties(options).forEach((name) => {
1130
+ let value = sourceStyle.getPropertyValue(name);
1131
+ if (name === "font-size" && value.endsWith("px")) {
1132
+ const reducedFont = Math.floor(parseFloat(value.substring(0, value.length - 2))) - 0.1;
1133
+ value = `${reducedFont}px`;
1134
+ }
1135
+ if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && name === "display" && value === "inline") {
1136
+ value = "block";
1137
+ }
1138
+ if (name === "d" && clonedNode.getAttribute("d")) {
1139
+ value = `path(${clonedNode.getAttribute("d")})`;
1140
+ }
1141
+ targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
1142
+ });
1143
+ }
1144
+ }
1145
+ function cloneInputValue(nativeNode, clonedNode) {
1146
+ if (isInstanceOfElement(nativeNode, HTMLTextAreaElement)) {
1147
+ clonedNode.innerHTML = nativeNode.value;
1148
+ }
1149
+ if (isInstanceOfElement(nativeNode, HTMLInputElement)) {
1150
+ clonedNode.setAttribute("value", nativeNode.value);
1151
+ }
1152
+ }
1153
+ function cloneSelectValue(nativeNode, clonedNode) {
1154
+ if (isInstanceOfElement(nativeNode, HTMLSelectElement)) {
1155
+ const clonedSelect = clonedNode;
1156
+ const selectedOption = Array.from(clonedSelect.children).find((child) => nativeNode.value === child.getAttribute("value"));
1157
+ if (selectedOption) {
1158
+ selectedOption.setAttribute("selected", "");
1159
+ }
1160
+ }
1161
+ }
1162
+ function decorate(nativeNode, clonedNode, options) {
1163
+ if (isInstanceOfElement(clonedNode, Element)) {
1164
+ cloneCSSStyle(nativeNode, clonedNode, options);
1165
+ clonePseudoElements(nativeNode, clonedNode, options);
1166
+ cloneInputValue(nativeNode, clonedNode);
1167
+ cloneSelectValue(nativeNode, clonedNode);
1168
+ }
1169
+ return clonedNode;
1170
+ }
1171
+ async function ensureSVGSymbols(clone, options) {
1172
+ const uses = clone.querySelectorAll ? clone.querySelectorAll("use") : [];
1173
+ if (uses.length === 0) {
1174
+ return clone;
1175
+ }
1176
+ const processedDefs = {};
1177
+ for (let i = 0; i < uses.length; i++) {
1178
+ const use = uses[i];
1179
+ const id = use.getAttribute("xlink:href");
1180
+ if (id) {
1181
+ const exist = clone.querySelector(id);
1182
+ const definition = document.querySelector(id);
1183
+ if (!exist && definition && !processedDefs[id]) {
1184
+ processedDefs[id] = await cloneNode(definition, options, true);
1185
+ }
1186
+ }
1187
+ }
1188
+ const nodes = Object.values(processedDefs);
1189
+ if (nodes.length) {
1190
+ const ns = "http://www.w3.org/1999/xhtml";
1191
+ const svg = document.createElementNS(ns, "svg");
1192
+ svg.setAttribute("xmlns", ns);
1193
+ svg.style.position = "absolute";
1194
+ svg.style.width = "0";
1195
+ svg.style.height = "0";
1196
+ svg.style.overflow = "hidden";
1197
+ svg.style.display = "none";
1198
+ const defs = document.createElementNS(ns, "defs");
1199
+ svg.appendChild(defs);
1200
+ for (let i = 0; i < nodes.length; i++) {
1201
+ defs.appendChild(nodes[i]);
1202
+ }
1203
+ clone.appendChild(svg);
1204
+ }
1205
+ return clone;
1206
+ }
1207
+ async function cloneNode(node, options, isRoot) {
1208
+ if (!isRoot && options.filter && !options.filter(node)) {
1209
+ return null;
1210
+ }
1211
+ return Promise.resolve(node).then((clonedNode) => cloneSingleNode(clonedNode, options)).then((clonedNode) => cloneChildren(node, clonedNode, options)).then((clonedNode) => decorate(node, clonedNode, options)).then((clonedNode) => ensureSVGSymbols(clonedNode, options));
1212
+ }
1213
+
1214
+ // node_modules/html-to-image/es/embed-resources.js
1215
+ var URL_REGEX = /url\((['"]?)([^'"]+?)\1\)/g;
1216
+ var URL_WITH_FORMAT_REGEX = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g;
1217
+ var FONT_SRC_REGEX = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g;
1218
+ function toRegex(url) {
1219
+ const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
1220
+ return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, "g");
1221
+ }
1222
+ function parseURLs(cssText) {
1223
+ const urls = [];
1224
+ cssText.replace(URL_REGEX, (raw, quotation, url) => {
1225
+ urls.push(url);
1226
+ return raw;
1227
+ });
1228
+ return urls.filter((url) => !isDataUrl(url));
1229
+ }
1230
+ async function embed(cssText, resourceURL, baseURL, options, getContentFromUrl) {
1231
+ try {
1232
+ const resolvedURL = baseURL ? resolveUrl(resourceURL, baseURL) : resourceURL;
1233
+ const contentType = getMimeType(resourceURL);
1234
+ let dataURL;
1235
+ if (getContentFromUrl) {
1236
+ const content = await getContentFromUrl(resolvedURL);
1237
+ dataURL = makeDataUrl(content, contentType);
1238
+ } else {
1239
+ dataURL = await resourceToDataURL(resolvedURL, contentType, options);
1240
+ }
1241
+ return cssText.replace(toRegex(resourceURL), `$1${dataURL}$3`);
1242
+ } catch (error) {
1243
+ }
1244
+ return cssText;
1245
+ }
1246
+ function filterPreferredFontFormat(str, { preferredFontFormat }) {
1247
+ return !preferredFontFormat ? str : str.replace(FONT_SRC_REGEX, (match) => {
1248
+ while (true) {
1249
+ const [src, , format] = URL_WITH_FORMAT_REGEX.exec(match) || [];
1250
+ if (!format) {
1251
+ return "";
1252
+ }
1253
+ if (format === preferredFontFormat) {
1254
+ return `src: ${src};`;
1255
+ }
1256
+ }
1257
+ });
1258
+ }
1259
+ function shouldEmbed(url) {
1260
+ return url.search(URL_REGEX) !== -1;
1261
+ }
1262
+ async function embedResources(cssText, baseUrl, options) {
1263
+ if (!shouldEmbed(cssText)) {
1264
+ return cssText;
1265
+ }
1266
+ const filteredCSSText = filterPreferredFontFormat(cssText, options);
1267
+ const urls = parseURLs(filteredCSSText);
1268
+ return urls.reduce((deferred, url) => deferred.then((css) => embed(css, url, baseUrl, options)), Promise.resolve(filteredCSSText));
1269
+ }
1270
+
1271
+ // node_modules/html-to-image/es/embed-images.js
1272
+ async function embedProp(propName, node, options) {
1273
+ var _a;
1274
+ const propValue = (_a = node.style) === null || _a === void 0 ? void 0 : _a.getPropertyValue(propName);
1275
+ if (propValue) {
1276
+ const cssString = await embedResources(propValue, null, options);
1277
+ node.style.setProperty(propName, cssString, node.style.getPropertyPriority(propName));
1278
+ return true;
1279
+ }
1280
+ return false;
1281
+ }
1282
+ async function embedBackground(clonedNode, options) {
1283
+ ;
1284
+ await embedProp("background", clonedNode, options) || await embedProp("background-image", clonedNode, options);
1285
+ await embedProp("mask", clonedNode, options) || await embedProp("-webkit-mask", clonedNode, options) || await embedProp("mask-image", clonedNode, options) || await embedProp("-webkit-mask-image", clonedNode, options);
1286
+ }
1287
+ async function embedImageNode(clonedNode, options) {
1288
+ const isImageElement = isInstanceOfElement(clonedNode, HTMLImageElement);
1289
+ if (!(isImageElement && !isDataUrl(clonedNode.src)) && !(isInstanceOfElement(clonedNode, SVGImageElement) && !isDataUrl(clonedNode.href.baseVal))) {
1290
+ return;
1291
+ }
1292
+ const url = isImageElement ? clonedNode.src : clonedNode.href.baseVal;
1293
+ const dataURL = await resourceToDataURL(url, getMimeType(url), options);
1294
+ await new Promise((resolve, reject) => {
1295
+ clonedNode.onload = resolve;
1296
+ clonedNode.onerror = options.onImageErrorHandler ? (...attributes) => {
1297
+ try {
1298
+ resolve(options.onImageErrorHandler(...attributes));
1299
+ } catch (error) {
1300
+ reject(error);
1301
+ }
1302
+ } : reject;
1303
+ const image = clonedNode;
1304
+ if (image.decode) {
1305
+ image.decode = resolve;
1306
+ }
1307
+ if (image.loading === "lazy") {
1308
+ image.loading = "eager";
1309
+ }
1310
+ if (isImageElement) {
1311
+ clonedNode.srcset = "";
1312
+ clonedNode.src = dataURL;
1313
+ } else {
1314
+ clonedNode.href.baseVal = dataURL;
1315
+ }
1316
+ });
1317
+ }
1318
+ async function embedChildren(clonedNode, options) {
1319
+ const children = toArray(clonedNode.childNodes);
1320
+ const deferreds = children.map((child) => embedImages(child, options));
1321
+ await Promise.all(deferreds).then(() => clonedNode);
1322
+ }
1323
+ async function embedImages(clonedNode, options) {
1324
+ if (isInstanceOfElement(clonedNode, Element)) {
1325
+ await embedBackground(clonedNode, options);
1326
+ await embedImageNode(clonedNode, options);
1327
+ await embedChildren(clonedNode, options);
1328
+ }
1329
+ }
1330
+
1331
+ // node_modules/html-to-image/es/apply-style.js
1332
+ function applyStyle(node, options) {
1333
+ const { style } = node;
1334
+ if (options.backgroundColor) {
1335
+ style.backgroundColor = options.backgroundColor;
1336
+ }
1337
+ if (options.width) {
1338
+ style.width = `${options.width}px`;
1339
+ }
1340
+ if (options.height) {
1341
+ style.height = `${options.height}px`;
1342
+ }
1343
+ const manual = options.style;
1344
+ if (manual != null) {
1345
+ Object.keys(manual).forEach((key) => {
1346
+ style[key] = manual[key];
1347
+ });
1348
+ }
1349
+ return node;
1350
+ }
1351
+
1352
+ // node_modules/html-to-image/es/embed-webfonts.js
1353
+ var cssFetchCache = {};
1354
+ async function fetchCSS(url) {
1355
+ let cache2 = cssFetchCache[url];
1356
+ if (cache2 != null) {
1357
+ return cache2;
1358
+ }
1359
+ const res = await fetch(url);
1360
+ const cssText = await res.text();
1361
+ cache2 = { url, cssText };
1362
+ cssFetchCache[url] = cache2;
1363
+ return cache2;
1364
+ }
1365
+ async function embedFonts(data, options) {
1366
+ let cssText = data.cssText;
1367
+ const regexUrl = /url\(["']?([^"')]+)["']?\)/g;
1368
+ const fontLocs = cssText.match(/url\([^)]+\)/g) || [];
1369
+ const loadFonts = fontLocs.map(async (loc) => {
1370
+ let url = loc.replace(regexUrl, "$1");
1371
+ if (!url.startsWith("https://")) {
1372
+ url = new URL(url, data.url).href;
1373
+ }
1374
+ return fetchAsDataURL(url, options.fetchRequestInit, ({ result }) => {
1375
+ cssText = cssText.replace(loc, `url(${result})`);
1376
+ return [loc, result];
1377
+ });
1378
+ });
1379
+ return Promise.all(loadFonts).then(() => cssText);
1380
+ }
1381
+ function parseCSS(source) {
1382
+ if (source == null) {
1383
+ return [];
1384
+ }
1385
+ const result = [];
1386
+ const commentsRegex = /(\/\*[\s\S]*?\*\/)/gi;
1387
+ let cssText = source.replace(commentsRegex, "");
1388
+ const keyframesRegex = new RegExp("((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})", "gi");
1389
+ while (true) {
1390
+ const matches = keyframesRegex.exec(cssText);
1391
+ if (matches === null) {
1392
+ break;
1393
+ }
1394
+ result.push(matches[0]);
1395
+ }
1396
+ cssText = cssText.replace(keyframesRegex, "");
1397
+ const importRegex = /@import[\s\S]*?url\([^)]*\)[\s\S]*?;/gi;
1398
+ const combinedCSSRegex = "((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})";
1399
+ const unifiedRegex = new RegExp(combinedCSSRegex, "gi");
1400
+ while (true) {
1401
+ let matches = importRegex.exec(cssText);
1402
+ if (matches === null) {
1403
+ matches = unifiedRegex.exec(cssText);
1404
+ if (matches === null) {
1405
+ break;
1406
+ } else {
1407
+ importRegex.lastIndex = unifiedRegex.lastIndex;
1408
+ }
1409
+ } else {
1410
+ unifiedRegex.lastIndex = importRegex.lastIndex;
1411
+ }
1412
+ result.push(matches[0]);
1413
+ }
1414
+ return result;
1415
+ }
1416
+ async function getCSSRules(styleSheets, options) {
1417
+ const ret = [];
1418
+ const deferreds = [];
1419
+ styleSheets.forEach((sheet) => {
1420
+ if ("cssRules" in sheet) {
1421
+ try {
1422
+ toArray(sheet.cssRules || []).forEach((item, index) => {
1423
+ if (item.type === CSSRule.IMPORT_RULE) {
1424
+ let importIndex = index + 1;
1425
+ const url = item.href;
1426
+ const deferred = fetchCSS(url).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => {
1427
+ try {
1428
+ sheet.insertRule(rule, rule.startsWith("@import") ? importIndex += 1 : sheet.cssRules.length);
1429
+ } catch (error) {
1430
+ console.error("Error inserting rule from remote css", {
1431
+ rule,
1432
+ error
1433
+ });
1434
+ }
1435
+ })).catch((e) => {
1436
+ console.error("Error loading remote css", e.toString());
1437
+ });
1438
+ deferreds.push(deferred);
1439
+ }
1440
+ });
1441
+ } catch (e) {
1442
+ const inline = styleSheets.find((a) => a.href == null) || document.styleSheets[0];
1443
+ if (sheet.href != null) {
1444
+ deferreds.push(fetchCSS(sheet.href).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => {
1445
+ inline.insertRule(rule, inline.cssRules.length);
1446
+ })).catch((err) => {
1447
+ console.error("Error loading remote stylesheet", err);
1448
+ }));
1449
+ }
1450
+ console.error("Error inlining remote css file", e);
1451
+ }
1452
+ }
1453
+ });
1454
+ return Promise.all(deferreds).then(() => {
1455
+ styleSheets.forEach((sheet) => {
1456
+ if ("cssRules" in sheet) {
1457
+ try {
1458
+ toArray(sheet.cssRules || []).forEach((item) => {
1459
+ ret.push(item);
1460
+ });
1461
+ } catch (e) {
1462
+ console.error(`Error while reading CSS rules from ${sheet.href}`, e);
1463
+ }
1464
+ }
1465
+ });
1466
+ return ret;
1467
+ });
1468
+ }
1469
+ function getWebFontRules(cssRules) {
1470
+ return cssRules.filter((rule) => rule.type === CSSRule.FONT_FACE_RULE).filter((rule) => shouldEmbed(rule.style.getPropertyValue("src")));
1471
+ }
1472
+ async function parseWebFontRules(node, options) {
1473
+ if (node.ownerDocument == null) {
1474
+ throw new Error("Provided element is not within a Document");
1475
+ }
1476
+ const styleSheets = toArray(node.ownerDocument.styleSheets);
1477
+ const cssRules = await getCSSRules(styleSheets, options);
1478
+ return getWebFontRules(cssRules);
1479
+ }
1480
+ function normalizeFontFamily(font) {
1481
+ return font.trim().replace(/["']/g, "");
1482
+ }
1483
+ function getUsedFonts(node) {
1484
+ const fonts = /* @__PURE__ */ new Set();
1485
+ function traverse(node2) {
1486
+ const fontFamily = node2.style.fontFamily || getComputedStyle(node2).fontFamily;
1487
+ fontFamily.split(",").forEach((font) => {
1488
+ fonts.add(normalizeFontFamily(font));
1489
+ });
1490
+ Array.from(node2.children).forEach((child) => {
1491
+ if (child instanceof HTMLElement) {
1492
+ traverse(child);
1493
+ }
1494
+ });
1495
+ }
1496
+ traverse(node);
1497
+ return fonts;
1498
+ }
1499
+ async function getWebFontCSS(node, options) {
1500
+ const rules = await parseWebFontRules(node, options);
1501
+ const usedFonts = getUsedFonts(node);
1502
+ const cssTexts = await Promise.all(rules.filter((rule) => usedFonts.has(normalizeFontFamily(rule.style.fontFamily))).map((rule) => {
1503
+ const baseUrl = rule.parentStyleSheet ? rule.parentStyleSheet.href : null;
1504
+ return embedResources(rule.cssText, baseUrl, options);
1505
+ }));
1506
+ return cssTexts.join("\n");
1507
+ }
1508
+ async function embedWebFonts(clonedNode, options) {
1509
+ const cssText = options.fontEmbedCSS != null ? options.fontEmbedCSS : options.skipFonts ? null : await getWebFontCSS(clonedNode, options);
1510
+ if (cssText) {
1511
+ const styleNode = document.createElement("style");
1512
+ const sytleContent = document.createTextNode(cssText);
1513
+ styleNode.appendChild(sytleContent);
1514
+ if (clonedNode.firstChild) {
1515
+ clonedNode.insertBefore(styleNode, clonedNode.firstChild);
1516
+ } else {
1517
+ clonedNode.appendChild(styleNode);
1518
+ }
1519
+ }
1520
+ }
1521
+
1522
+ // node_modules/html-to-image/es/index.js
1523
+ async function toSvg(node, options = {}) {
1524
+ const { width, height } = getImageSize(node, options);
1525
+ const clonedNode = await cloneNode(node, options, true);
1526
+ await embedWebFonts(clonedNode, options);
1527
+ await embedImages(clonedNode, options);
1528
+ applyStyle(clonedNode, options);
1529
+ const datauri = await nodeToDataURL(clonedNode, width, height);
1530
+ return datauri;
1531
+ }
1532
+ async function toCanvas(node, options = {}) {
1533
+ const { width, height } = getImageSize(node, options);
1534
+ const svg = await toSvg(node, options);
1535
+ const img = await createImage(svg);
1536
+ const canvas = document.createElement("canvas");
1537
+ const context = canvas.getContext("2d");
1538
+ const ratio = options.pixelRatio || getPixelRatio();
1539
+ const canvasWidth = options.canvasWidth || width;
1540
+ const canvasHeight = options.canvasHeight || height;
1541
+ canvas.width = canvasWidth * ratio;
1542
+ canvas.height = canvasHeight * ratio;
1543
+ if (!options.skipAutoScale) {
1544
+ checkCanvasDimensions(canvas);
1545
+ }
1546
+ canvas.style.width = `${canvasWidth}`;
1547
+ canvas.style.height = `${canvasHeight}`;
1548
+ if (options.backgroundColor) {
1549
+ context.fillStyle = options.backgroundColor;
1550
+ context.fillRect(0, 0, canvas.width, canvas.height);
1551
+ }
1552
+ context.drawImage(img, 0, 0, canvas.width, canvas.height);
1553
+ return canvas;
1554
+ }
1555
+ async function toPng(node, options = {}) {
1556
+ const canvas = await toCanvas(node, options);
1557
+ return canvas.toDataURL();
1558
+ }
1559
+
1560
+ // src/ui/screenshot.ts
1561
+ async function captureElement(el) {
1562
+ try {
1563
+ return await toPng(el, {
1564
+ cacheBust: true,
1565
+ pixelRatio: 2,
1566
+ skipFonts: true,
1567
+ filter: (node) => {
1568
+ var _a, _b;
1569
+ if ((_a = node.getAttribute) == null ? void 0 : _a.call(node, "data-instruckt")) return false;
1570
+ if (node.tagName === "LINK" && node.getAttribute("rel") === "stylesheet") {
1571
+ const href = (_b = node.getAttribute("href")) != null ? _b : "";
1572
+ if (href.startsWith("http") && !href.startsWith(window.location.origin)) return false;
1573
+ }
1574
+ return true;
1575
+ }
1576
+ });
1577
+ } catch (e) {
1578
+ return null;
1579
+ }
1580
+ }
1581
+ async function captureRegion(rect) {
1582
+ try {
1583
+ const full = await toPng(document.body, {
1584
+ cacheBust: true,
1585
+ pixelRatio: 2,
1586
+ skipFonts: true,
1587
+ filter: (node) => {
1588
+ var _a, _b;
1589
+ if ((_a = node.getAttribute) == null ? void 0 : _a.call(node, "data-instruckt")) return false;
1590
+ if (node.tagName === "LINK" && node.getAttribute("rel") === "stylesheet") {
1591
+ const href = (_b = node.getAttribute("href")) != null ? _b : "";
1592
+ if (href.startsWith("http") && !href.startsWith(window.location.origin)) return false;
1593
+ }
1594
+ return true;
1595
+ }
1596
+ });
1597
+ return await cropImage(full, rect);
1598
+ } catch (e) {
1599
+ return null;
1600
+ }
1601
+ }
1602
+ function cropImage(dataUrl, rect) {
1603
+ return new Promise((resolve, reject) => {
1604
+ const img = new Image();
1605
+ img.onload = () => {
1606
+ const ratio = 2;
1607
+ const canvas = document.createElement("canvas");
1608
+ canvas.width = rect.width * ratio;
1609
+ canvas.height = rect.height * ratio;
1610
+ const ctx = canvas.getContext("2d");
1611
+ ctx.drawImage(
1612
+ img,
1613
+ rect.x * ratio,
1614
+ rect.y * ratio,
1615
+ rect.width * ratio,
1616
+ rect.height * ratio,
1617
+ 0,
1618
+ 0,
1619
+ rect.width * ratio,
1620
+ rect.height * ratio
1621
+ );
1622
+ resolve(canvas.toDataURL("image/png"));
1623
+ };
1624
+ img.onerror = reject;
1625
+ img.src = dataUrl;
1626
+ });
1627
+ }
1628
+ function selectRegion() {
1629
+ return new Promise((resolve) => {
1630
+ const overlay = document.createElement("div");
1631
+ Object.assign(overlay.style, {
1632
+ position: "fixed",
1633
+ inset: "0",
1634
+ zIndex: "2147483647",
1635
+ cursor: "crosshair",
1636
+ background: "rgba(0,0,0,0.1)"
1637
+ });
1638
+ overlay.setAttribute("data-instruckt", "region-select");
1639
+ const box = document.createElement("div");
1640
+ Object.assign(box.style, {
1641
+ position: "fixed",
1642
+ border: "2px dashed #6366f1",
1643
+ background: "rgba(99,102,241,0.08)",
1644
+ borderRadius: "4px",
1645
+ display: "none",
1646
+ pointerEvents: "none"
1647
+ });
1648
+ overlay.appendChild(box);
1649
+ let startX = 0, startY = 0, dragging = false;
1650
+ const onMouseDown = (e) => {
1651
+ startX = e.clientX;
1652
+ startY = e.clientY;
1653
+ dragging = true;
1654
+ box.style.display = "block";
1655
+ updateBox(e);
1656
+ };
1657
+ const onMouseMove = (e) => {
1658
+ if (!dragging) return;
1659
+ updateBox(e);
1660
+ };
1661
+ const updateBox = (e) => {
1662
+ const x = Math.min(startX, e.clientX);
1663
+ const y = Math.min(startY, e.clientY);
1664
+ const w = Math.abs(e.clientX - startX);
1665
+ const h = Math.abs(e.clientY - startY);
1666
+ Object.assign(box.style, {
1667
+ left: `${x}px`,
1668
+ top: `${y}px`,
1669
+ width: `${w}px`,
1670
+ height: `${h}px`
1671
+ });
1672
+ };
1673
+ const onMouseUp = (e) => {
1674
+ if (!dragging) return;
1675
+ dragging = false;
1676
+ const x = Math.min(startX, e.clientX);
1677
+ const y = Math.min(startY, e.clientY);
1678
+ const w = Math.abs(e.clientX - startX);
1679
+ const h = Math.abs(e.clientY - startY);
1680
+ cleanup();
1681
+ if (w < 10 || h < 10) {
1682
+ resolve(null);
1683
+ } else {
1684
+ resolve(new DOMRect(x, y, w, h));
1685
+ }
1686
+ };
1687
+ const onKeyDown = (e) => {
1688
+ if (e.key === "Escape") {
1689
+ cleanup();
1690
+ resolve(null);
1691
+ }
1692
+ };
1693
+ const cleanup = () => {
1694
+ overlay.remove();
1695
+ document.removeEventListener("keydown", onKeyDown, true);
1696
+ };
1697
+ overlay.addEventListener("mousedown", onMouseDown);
1698
+ overlay.addEventListener("mousemove", onMouseMove);
1699
+ overlay.addEventListener("mouseup", onMouseUp);
1700
+ document.addEventListener("keydown", onKeyDown, true);
1701
+ document.body.appendChild(overlay);
1702
+ });
1703
+ }
1704
+
713
1705
  // src/ui/popup.ts
1706
+ function screenshotUrl(screenshot, endpoint) {
1707
+ if (!screenshot) return null;
1708
+ if (screenshot.startsWith("data:")) return screenshot;
1709
+ const base = endpoint != null ? endpoint : "/instruckt";
1710
+ return `${base}/${screenshot}`;
1711
+ }
714
1712
  function esc(s) {
715
1713
  return String(s != null ? s : "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
716
1714
  }
@@ -726,7 +1724,7 @@ var AnnotationPopup = class {
726
1724
  }
727
1725
  // ── New annotation popup ──────────────────────────────────────
728
1726
  showNew(pending, callbacks) {
729
- var _a;
1727
+ var _a, _b;
730
1728
  this.destroy();
731
1729
  this.host = document.createElement("div");
732
1730
  this.host.setAttribute("data-instruckt", "popup");
@@ -739,23 +1737,56 @@ var AnnotationPopup = class {
739
1737
  popup.className = "popup";
740
1738
  const fwBadge = pending.framework ? `<div class="fw-badge">${esc(pending.framework.component)}</div>` : "";
741
1739
  const selText = pending.selectedText ? `<div class="selected-text">"${esc(pending.selectedText.slice(0, 80))}"</div>` : "";
1740
+ const hasScreenshot = !!pending.screenshot;
742
1741
  popup.innerHTML = `
743
1742
  <div class="header">
744
1743
  <span class="element-tag" title="${esc(pending.elementPath)}">${esc(pending.elementLabel)}</span>
745
1744
  <button class="close-btn" title="Cancel (Esc)">\u2715</button>
746
1745
  </div>
747
1746
  ${fwBadge}${selText}
748
- <textarea placeholder="What needs to change here?" rows="3"></textarea>
1747
+ <div class="screenshot-slot">${hasScreenshot ? `<div class="screenshot-preview"><img src="${pending.screenshot}" alt="Screenshot" /><button class="screenshot-remove" title="Remove screenshot">\u2715</button></div>` : `<button class="btn-capture" data-action="capture"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg> Capture screenshot</button>`}</div>
1748
+ <textarea placeholder="${hasScreenshot ? "Add a note (optional)" : "What needs to change here?"}" rows="3"></textarea>
749
1749
  <div class="actions">
750
1750
  <button class="btn-secondary" data-action="cancel">Cancel</button>
751
- <button class="btn-primary" data-action="submit" disabled>Add note</button>
1751
+ <button class="btn-primary" data-action="submit" ${hasScreenshot ? "" : "disabled"}>Add note</button>
752
1752
  </div>
753
1753
  `;
1754
+ let currentScreenshot = (_a = pending.screenshot) != null ? _a : null;
754
1755
  const textarea = popup.querySelector("textarea");
755
1756
  const submitBtn = popup.querySelector('[data-action="submit"]');
756
- textarea.addEventListener("input", () => {
757
- submitBtn.disabled = textarea.value.trim().length === 0;
758
- });
1757
+ const screenshotSlot = popup.querySelector(".screenshot-slot");
1758
+ const updateSubmitState = () => {
1759
+ submitBtn.disabled = !currentScreenshot && textarea.value.trim().length === 0;
1760
+ };
1761
+ const attachScreenshotEvents = () => {
1762
+ const captureBtn = screenshotSlot.querySelector('[data-action="capture"]');
1763
+ captureBtn == null ? void 0 : captureBtn.addEventListener("click", async () => {
1764
+ captureBtn.textContent = "Capturing...";
1765
+ const dataUrl = await captureElement(pending.element);
1766
+ if (dataUrl) {
1767
+ currentScreenshot = dataUrl;
1768
+ screenshotSlot.innerHTML = `<div class="screenshot-preview"><img src="${dataUrl}" alt="Screenshot" /><button class="screenshot-remove" title="Remove screenshot">\u2715</button></div>`;
1769
+ textarea.placeholder = "Add a note (optional)";
1770
+ attachScreenshotEvents();
1771
+ updateSubmitState();
1772
+ } else {
1773
+ captureBtn.textContent = "Capture failed";
1774
+ setTimeout(() => {
1775
+ if (captureBtn.parentElement) captureBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg> Capture screenshot`;
1776
+ }, 1500);
1777
+ }
1778
+ });
1779
+ const removeBtn = screenshotSlot.querySelector(".screenshot-remove");
1780
+ removeBtn == null ? void 0 : removeBtn.addEventListener("click", () => {
1781
+ currentScreenshot = null;
1782
+ screenshotSlot.innerHTML = `<button class="btn-capture" data-action="capture"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg> Capture screenshot</button>`;
1783
+ textarea.placeholder = "What needs to change here?";
1784
+ attachScreenshotEvents();
1785
+ updateSubmitState();
1786
+ });
1787
+ };
1788
+ attachScreenshotEvents();
1789
+ textarea.addEventListener("input", updateSubmitState);
759
1790
  textarea.addEventListener("keydown", (e) => {
760
1791
  e.stopPropagation();
761
1792
  if (e.key === "Enter" && !e.shiftKey) {
@@ -777,18 +1808,18 @@ var AnnotationPopup = class {
777
1808
  });
778
1809
  submitBtn.addEventListener("click", () => {
779
1810
  const comment = textarea.value.trim();
780
- if (!comment) return;
781
- callbacks.onSubmit({ comment });
1811
+ if (!comment && !currentScreenshot) return;
1812
+ callbacks.onSubmit({ comment: comment || "(screenshot)", screenshot: currentScreenshot != null ? currentScreenshot : void 0 });
782
1813
  this.destroy();
783
1814
  });
784
1815
  this.shadow.appendChild(popup);
785
- ((_a = document.getElementById("instruckt-root")) != null ? _a : document.body).appendChild(this.host);
1816
+ ((_b = document.getElementById("instruckt-root")) != null ? _b : document.body).appendChild(this.host);
786
1817
  this.positionHost(pending.x, pending.y);
787
1818
  this.setupOutsideClick();
788
1819
  textarea.focus();
789
1820
  }
790
1821
  // ── Edit existing annotation ──────────────────────────────────
791
- showEdit(annotation, callbacks) {
1822
+ showEdit(annotation, callbacks, endpoint) {
792
1823
  var _a;
793
1824
  this.destroy();
794
1825
  this.host = document.createElement("div");
@@ -801,19 +1832,28 @@ var AnnotationPopup = class {
801
1832
  const popup = document.createElement("div");
802
1833
  popup.className = "popup";
803
1834
  const fwBadge = annotation.framework ? `<div class="fw-badge">${esc(annotation.framework.component)}</div>` : "";
1835
+ const ssUrl = screenshotUrl(annotation.screenshot, endpoint);
1836
+ const screenshotPreview = ssUrl ? `<div class="screenshot-preview screenshot-slot"><img src="${ssUrl}" alt="Screenshot" /><button class="screenshot-remove" title="Remove screenshot">\u2715</button></div>` : "";
1837
+ const commentText = annotation.comment === "(screenshot)" ? "" : annotation.comment;
804
1838
  popup.innerHTML = `
805
1839
  <div class="header">
806
1840
  <span class="element-tag" title="${esc(annotation.elementPath)}">${esc(annotation.element)}</span>
807
1841
  <button class="close-btn">\u2715</button>
808
1842
  </div>
809
- ${fwBadge}
810
- <textarea rows="3">${esc(annotation.comment)}</textarea>
1843
+ ${fwBadge}${screenshotPreview}
1844
+ <textarea rows="3">${esc(commentText)}</textarea>
811
1845
  <div class="actions">
812
1846
  <button class="btn-danger" data-action="delete">Remove</button>
813
1847
  <button class="btn-primary" data-action="save">Save</button>
814
1848
  </div>
815
1849
  `;
816
1850
  popup.querySelector(".close-btn").addEventListener("click", () => this.destroy());
1851
+ const ssRemoveBtn = popup.querySelector(".screenshot-remove");
1852
+ ssRemoveBtn == null ? void 0 : ssRemoveBtn.addEventListener("click", () => {
1853
+ callbacks.onSave(annotation, annotation.comment);
1854
+ const slot = popup.querySelector(".screenshot-slot");
1855
+ if (slot) slot.remove();
1856
+ });
817
1857
  const textarea = popup.querySelector("textarea");
818
1858
  const saveBtn = popup.querySelector('[data-action="save"]');
819
1859
  const deleteBtn = popup.querySelector('[data-action="delete"]');
@@ -908,9 +1948,10 @@ var AnnotationMarkers = class {
908
1948
  return;
909
1949
  }
910
1950
  const el = document.createElement("div");
911
- el.className = `ik-marker ${this.statusClass(annotation.status)}`;
1951
+ const ssClass = annotation.screenshot ? " has-screenshot" : "";
1952
+ el.className = `ik-marker ${this.statusClass(annotation.status)}${ssClass}`;
912
1953
  el.textContent = String(index);
913
- el.title = annotation.comment.slice(0, 60);
1954
+ el.title = annotation.comment === "(screenshot)" ? "Screenshot" : annotation.comment.slice(0, 60);
914
1955
  el.style.pointerEvents = "all";
915
1956
  el.style.left = `${annotation.x / 100 * window.innerWidth}px`;
916
1957
  el.style.top = `${annotation.y - window.scrollY}px`;
@@ -928,13 +1969,13 @@ var AnnotationMarkers = class {
928
1969
  this.updateStyle(marker.el, annotation);
929
1970
  }
930
1971
  updateStyle(el, annotation) {
931
- el.className = `ik-marker ${this.statusClass(annotation.status)}`;
932
- el.title = annotation.comment.slice(0, 60);
1972
+ const ssClass = annotation.screenshot ? " has-screenshot" : "";
1973
+ el.className = `ik-marker ${this.statusClass(annotation.status)}${ssClass}`;
1974
+ el.title = annotation.comment === "(screenshot)" ? "Screenshot" : annotation.comment.slice(0, 60);
933
1975
  }
934
1976
  statusClass(status) {
935
1977
  if (status === "resolved") return "resolved";
936
1978
  if (status === "dismissed") return "dismissed";
937
- if (status === "acknowledged") return "acknowledged";
938
1979
  return "";
939
1980
  }
940
1981
  /** Reposition all markers (e.g. after scroll or resize) */
@@ -1269,7 +2310,7 @@ var _Instruckt = class _Instruckt {
1269
2310
  e.stopImmediatePropagation();
1270
2311
  };
1271
2312
  this.boundClick = (e) => {
1272
- var _a, _b, _c, _d;
2313
+ var _a, _b, _c;
1273
2314
  const target = e.target;
1274
2315
  if (this.isInstruckt(target)) return;
1275
2316
  e.preventDefault();
@@ -1283,6 +2324,8 @@ var _Instruckt = class _Instruckt {
1283
2324
  const nearbyText = getNearbyText(target) || void 0;
1284
2325
  const boundingBox = getPageBoundingBox(target);
1285
2326
  const framework = (_b = this.detectFramework(target)) != null ? _b : void 0;
2327
+ (_c = this.highlight) == null ? void 0 : _c.show(target);
2328
+ this.highlightLocked = true;
1286
2329
  const pending = {
1287
2330
  element: target,
1288
2331
  elementPath,
@@ -1296,21 +2339,7 @@ var _Instruckt = class _Instruckt {
1296
2339
  nearbyText,
1297
2340
  framework
1298
2341
  };
1299
- (_c = this.highlight) == null ? void 0 : _c.show(target);
1300
- this.highlightLocked = true;
1301
- (_d = this.popup) == null ? void 0 : _d.showNew(pending, {
1302
- onSubmit: (result) => {
1303
- var _a2;
1304
- this.highlightLocked = false;
1305
- (_a2 = this.highlight) == null ? void 0 : _a2.hide();
1306
- this.submitAnnotation(pending, result.comment);
1307
- },
1308
- onCancel: () => {
1309
- var _a2;
1310
- this.highlightLocked = false;
1311
- (_a2 = this.highlight) == null ? void 0 : _a2.hide();
1312
- }
1313
- });
2342
+ this.showAnnotationPopup(pending);
1314
2343
  };
1315
2344
  this.config = __spreadValues({
1316
2345
  adapters: ["livewire", "vue", "svelte", "react"],
@@ -1322,7 +2351,7 @@ var _Instruckt = class _Instruckt {
1322
2351
  this.init();
1323
2352
  }
1324
2353
  init() {
1325
- injectGlobalStyles();
2354
+ injectGlobalStyles(this.config.colors);
1326
2355
  if (this.config.theme !== "auto") {
1327
2356
  document.documentElement.setAttribute("data-instruckt-theme", this.config.theme);
1328
2357
  }
@@ -1333,11 +2362,12 @@ var _Instruckt = class _Instruckt {
1333
2362
  onFreezeAnimations: (frozen) => {
1334
2363
  this.setFrozen(frozen);
1335
2364
  },
2365
+ onScreenshot: () => this.startRegionCapture(),
1336
2366
  onCopy: () => this.copyToClipboard(true),
1337
2367
  onClearPage: () => this.clearPage(),
1338
2368
  onClearAll: () => this.clearEverything(),
1339
2369
  onMinimize: (min) => this.onMinimize(min)
1340
- });
2370
+ }, this.config.keys);
1341
2371
  this.highlight = new ElementHighlight();
1342
2372
  this.popup = new AnnotationPopup();
1343
2373
  this.markers = new AnnotationMarkers((annotation) => this.onMarkerClick(annotation));
@@ -1350,7 +2380,6 @@ var _Instruckt = class _Instruckt {
1350
2380
  setTimeout(() => this.reattach(), 0);
1351
2381
  });
1352
2382
  this.loadAnnotations();
1353
- this.pollTimer = setInterval(() => this.pollForChanges(), 3e3);
1354
2383
  this.syncMarkers();
1355
2384
  }
1356
2385
  makeToolbarCallbacks() {
@@ -1361,6 +2390,7 @@ var _Instruckt = class _Instruckt {
1361
2390
  onFreezeAnimations: (frozen) => {
1362
2391
  this.setFrozen(frozen);
1363
2392
  },
2393
+ onScreenshot: () => this.startRegionCapture(),
1364
2394
  onCopy: () => this.copyToClipboard(true),
1365
2395
  onClearPage: () => this.clearPage(),
1366
2396
  onClearAll: () => this.clearEverything(),
@@ -1384,7 +2414,7 @@ var _Instruckt = class _Instruckt {
1384
2414
  if (wasMinimized) this.markers.setVisible(false);
1385
2415
  const existing = document.getElementById("instruckt-global");
1386
2416
  if (existing) existing.remove();
1387
- injectGlobalStyles();
2417
+ injectGlobalStyles(this.config.colors);
1388
2418
  this.syncMarkers();
1389
2419
  if (wasAnnotating && !wasMinimized) this.setAnnotating(true);
1390
2420
  }
@@ -1429,6 +2459,16 @@ var _Instruckt = class _Instruckt {
1429
2459
  } catch (e) {
1430
2460
  }
1431
2461
  }
2462
+ /** Start or stop polling based on whether there are active annotations */
2463
+ updatePolling() {
2464
+ const hasActive = this.totalActiveCount() > 0;
2465
+ if (hasActive && !this.pollTimer) {
2466
+ this.pollTimer = setInterval(() => this.pollForChanges(), 3e3);
2467
+ } else if (!hasActive && this.pollTimer) {
2468
+ clearInterval(this.pollTimer);
2469
+ this.pollTimer = null;
2470
+ }
2471
+ }
1432
2472
  /** Poll API for status changes (e.g. agent resolved via MCP) */
1433
2473
  async pollForChanges() {
1434
2474
  try {
@@ -1465,6 +2505,7 @@ var _Instruckt = class _Instruckt {
1465
2505
  }
1466
2506
  (_c = this.toolbar) == null ? void 0 : _c.setAnnotationCount(this.pageAnnotations().length);
1467
2507
  (_d = this.toolbar) == null ? void 0 : _d.setTotalCount(this.totalActiveCount());
2508
+ this.updatePolling();
1468
2509
  }
1469
2510
  annotationPageKey(a) {
1470
2511
  try {
@@ -1594,6 +2635,22 @@ var _Instruckt = class _Instruckt {
1594
2635
  `;
1595
2636
  document.head.appendChild(this.frozenStyleEl);
1596
2637
  }
2638
+ showAnnotationPopup(pending) {
2639
+ var _a;
2640
+ (_a = this.popup) == null ? void 0 : _a.showNew(pending, {
2641
+ onSubmit: (result) => {
2642
+ var _a2;
2643
+ this.highlightLocked = false;
2644
+ (_a2 = this.highlight) == null ? void 0 : _a2.hide();
2645
+ this.submitAnnotation(pending, result.comment, result.screenshot);
2646
+ },
2647
+ onCancel: () => {
2648
+ var _a2;
2649
+ this.highlightLocked = false;
2650
+ (_a2 = this.highlight) == null ? void 0 : _a2.hide();
2651
+ }
2652
+ });
2653
+ }
1597
2654
  attachAnnotateListeners() {
1598
2655
  document.addEventListener("mousemove", this.boundMouseMove);
1599
2656
  document.addEventListener("mouseleave", this.boundMouseLeave);
@@ -1614,6 +2671,39 @@ var _Instruckt = class _Instruckt {
1614
2671
  if (!el || !(el instanceof Element)) return false;
1615
2672
  return el.closest("[data-instruckt]") !== null;
1616
2673
  }
2674
+ // ── Region screenshot ────────────────────────────────────────
2675
+ async startRegionCapture() {
2676
+ var _a, _b;
2677
+ const wasAnnotating = this.isAnnotating;
2678
+ if (wasAnnotating) this.setAnnotating(false);
2679
+ const rect = await selectRegion();
2680
+ if (!rect) {
2681
+ if (wasAnnotating) this.setAnnotating(true);
2682
+ return;
2683
+ }
2684
+ const screenshot = await captureRegion(rect);
2685
+ if (!screenshot) {
2686
+ if (wasAnnotating) this.setAnnotating(true);
2687
+ return;
2688
+ }
2689
+ const centerX = rect.x + rect.width / 2;
2690
+ const centerY = rect.y + rect.height / 2;
2691
+ const target = (_a = document.elementFromPoint(centerX, centerY)) != null ? _a : document.body;
2692
+ const pending = {
2693
+ element: target,
2694
+ elementPath: getElementSelector(target),
2695
+ elementName: getElementName(target),
2696
+ elementLabel: getElementLabel(target),
2697
+ cssClasses: getCssClasses(target),
2698
+ boundingBox: getPageBoundingBox(target),
2699
+ x: centerX,
2700
+ y: centerY,
2701
+ nearbyText: getNearbyText(target) || void 0,
2702
+ screenshot,
2703
+ framework: (_b = this.detectFramework(target)) != null ? _b : void 0
2704
+ };
2705
+ this.showAnnotationPopup(pending);
2706
+ }
1617
2707
  // ── Framework detection ───────────────────────────────────────
1618
2708
  detectFramework(el) {
1619
2709
  var _a;
@@ -1637,7 +2727,7 @@ var _Instruckt = class _Instruckt {
1637
2727
  return null;
1638
2728
  }
1639
2729
  // ── Submit ────────────────────────────────────────────────────
1640
- async submitAnnotation(pending, comment) {
2730
+ async submitAnnotation(pending, comment, screenshot) {
1641
2731
  var _a, _b;
1642
2732
  const payload = {
1643
2733
  x: pending.x / window.innerWidth * 100,
@@ -1649,6 +2739,7 @@ var _Instruckt = class _Instruckt {
1649
2739
  boundingBox: pending.boundingBox,
1650
2740
  selectedText: pending.selectedText,
1651
2741
  nearbyText: pending.nearbyText,
2742
+ screenshot,
1652
2743
  intent: "fix",
1653
2744
  severity: "important",
1654
2745
  framework: pending.framework,
@@ -1661,7 +2752,6 @@ var _Instruckt = class _Instruckt {
1661
2752
  annotation = __spreadProps(__spreadValues({}, payload), {
1662
2753
  id: crypto.randomUUID(),
1663
2754
  status: "pending",
1664
- thread: [],
1665
2755
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1666
2756
  });
1667
2757
  }
@@ -1690,7 +2780,7 @@ var _Instruckt = class _Instruckt {
1690
2780
  }
1691
2781
  this.removeAnnotation(a.id);
1692
2782
  }
1693
- });
2783
+ }, this.config.endpoint);
1694
2784
  }
1695
2785
  onAnnotationUpdated(updated) {
1696
2786
  const idx = this.annotations.findIndex((a) => a.id === updated.id);
@@ -1732,19 +2822,24 @@ var _Instruckt = class _Instruckt {
1732
2822
  }
1733
2823
  // ── Keyboard ──────────────────────────────────────────────────
1734
2824
  onKeydown(e) {
1735
- var _a;
2825
+ var _a, _b, _c, _d, _e, _f;
1736
2826
  if ((_a = this.toolbar) == null ? void 0 : _a.isMinimized()) return;
1737
2827
  const target = e.target;
1738
2828
  if (["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName)) return;
1739
2829
  if (target.closest('[contenteditable="true"]')) return;
1740
2830
  if (this.isInstruckt(target)) return;
1741
- if (e.key === "a" && !e.metaKey && !e.ctrlKey && !e.altKey) {
2831
+ const keys = (_b = this.config.keys) != null ? _b : {};
2832
+ const noMod = !e.metaKey && !e.ctrlKey && !e.altKey;
2833
+ if (e.key === ((_c = keys.annotate) != null ? _c : "a") && noMod) {
1742
2834
  this.setAnnotating(!this.isAnnotating);
1743
2835
  }
1744
- if (e.key === "f" && !e.metaKey && !e.ctrlKey && !e.altKey) {
2836
+ if (e.key === ((_d = keys.freeze) != null ? _d : "f") && noMod) {
1745
2837
  this.setFrozen(!this.isFrozen);
1746
2838
  }
1747
- if (e.key === "x" && !e.metaKey && !e.ctrlKey && !e.altKey) {
2839
+ if (e.key === ((_e = keys.screenshot) != null ? _e : "c") && noMod) {
2840
+ this.startRegionCapture();
2841
+ }
2842
+ if (e.key === ((_f = keys.clearPage) != null ? _f : "x") && noMod) {
1748
2843
  this.clearPage();
1749
2844
  }
1750
2845
  if (e.key === "Escape") {
@@ -1819,6 +2914,13 @@ No open annotations.`;
1819
2914
  } else if (a.nearbyText) {
1820
2915
  lines.push(`- Text: "${a.nearbyText.slice(0, 100)}"`);
1821
2916
  }
2917
+ if (a.screenshot) {
2918
+ if (!a.screenshot.startsWith("data:")) {
2919
+ lines.push(`- Screenshot: \`storage/app/_instruckt/${a.screenshot}\``);
2920
+ } else {
2921
+ lines.push(`- Screenshot: attached`);
2922
+ }
2923
+ }
1822
2924
  lines.push("");
1823
2925
  });
1824
2926
  }