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.
@@ -103,15 +103,6 @@ var InstrucktApi = class {
103
103
  if (!res.ok) throw new Error(`instruckt: failed to update annotation (${res.status})`);
104
104
  return toCamelCase(await res.json());
105
105
  }
106
- async addReply(annotationId, content, role = "human") {
107
- const res = await fetch(`${this.endpoint}/annotations/${annotationId}/reply`, {
108
- method: "POST",
109
- headers: headers(),
110
- body: JSON.stringify({ role, content })
111
- });
112
- if (!res.ok) throw new Error(`instruckt: failed to add reply (${res.status})`);
113
- return toCamelCase(await res.json());
114
- }
115
106
  };
116
107
 
117
108
  // src/ui/styles.ts
@@ -187,6 +178,24 @@ var TOOLBAR_CSS = (
187
178
  }
188
179
  .btn svg { display: block; }
189
180
  .btn:hover { background: var(--ik-bg2); color: var(--ik-text); }
181
+ .btn[data-tooltip]::before {
182
+ content: attr(data-tooltip);
183
+ position: absolute;
184
+ right: calc(100% + 8px);
185
+ top: 50%;
186
+ transform: translateY(-50%);
187
+ white-space: nowrap;
188
+ font-size: 11px;
189
+ padding: 4px 8px;
190
+ border-radius: 6px;
191
+ background: var(--ik-text);
192
+ color: var(--ik-bg);
193
+ pointer-events: none;
194
+ opacity: 0;
195
+ transition: opacity .1s ease;
196
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
197
+ }
198
+ .btn[data-tooltip]:hover::before { opacity: 1; }
190
199
  .btn.active { background: var(--ik-accent); color: #fff; }
191
200
  .btn.active:hover { background: var(--ik-accent-h); }
192
201
 
@@ -226,24 +235,7 @@ var TOOLBAR_CSS = (
226
235
  box-shadow: var(--ik-shadow);
227
236
  border-radius: 8px;
228
237
  }
229
- /* Instant tooltip */
230
- .clear-all-btn::before {
231
- content: attr(data-tooltip);
232
- position: absolute;
233
- right: calc(100% + 6px);
234
- top: 50%;
235
- transform: translateY(-50%);
236
- white-space: nowrap;
237
- font-size: 11px;
238
- padding: 4px 8px;
239
- border-radius: 6px;
240
- background: var(--ik-text);
241
- color: var(--ik-bg);
242
- pointer-events: none;
243
- opacity: 0;
244
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
245
- }
246
- .clear-all-btn:hover::before { opacity: 1; }
238
+ /* clear-all tooltip inherits from .btn[data-tooltip]::before */
247
239
  /* Invisible bridge so hover doesn't break crossing the gap */
248
240
  .clear-all-btn::after {
249
241
  content: '';
@@ -382,6 +374,61 @@ var POPUP_CSS = (
382
374
  .chip.important.sel { background:#f97316; border-color:#f97316; }
383
375
  .chip.suggestion.sel{ background:#22c55e; border-color:#22c55e; }
384
376
 
377
+ .screenshot-slot { margin-bottom: 10px; }
378
+
379
+ .btn-capture {
380
+ display: flex;
381
+ align-items: center;
382
+ gap: 6px;
383
+ width: 100%;
384
+ padding: 8px 10px;
385
+ border: 1px dashed var(--ik-border);
386
+ border-radius: 6px;
387
+ background: var(--ik-bg2);
388
+ color: var(--ik-muted);
389
+ font-size: 12px;
390
+ font-family: inherit;
391
+ cursor: pointer;
392
+ transition: border-color .15s, color .15s;
393
+ }
394
+ .btn-capture:hover {
395
+ border-color: var(--ik-accent);
396
+ color: var(--ik-accent);
397
+ }
398
+ .btn-capture svg { flex-shrink: 0; }
399
+
400
+ .screenshot-preview {
401
+ position: relative;
402
+ border-radius: 6px;
403
+ overflow: hidden;
404
+ border: 1px solid var(--ik-border);
405
+ margin-bottom: 10px;
406
+ }
407
+ .screenshot-preview img {
408
+ display: block;
409
+ width: 100%;
410
+ max-height: 200px;
411
+ object-fit: contain;
412
+ background: var(--ik-bg2);
413
+ }
414
+ .screenshot-remove {
415
+ position: absolute;
416
+ top: 4px; right: 4px;
417
+ width: 20px; height: 20px;
418
+ border-radius: 50%;
419
+ border: none;
420
+ background: rgba(0,0,0,.6);
421
+ color: #fff;
422
+ font-size: 12px;
423
+ line-height: 1;
424
+ cursor: pointer;
425
+ display: flex;
426
+ align-items: center;
427
+ justify-content: center;
428
+ padding: 0;
429
+ }
430
+ .screenshot-remove:hover { background: #ef4444; }
431
+
385
432
  textarea {
386
433
  width:100%; min-height:80px; resize:vertical;
387
434
  border:1px solid var(--ik-border); border-radius:6px;
@@ -432,7 +479,6 @@ textarea::placeholder { color:var(--ik-muted); }
432
479
  border-radius:4px; padding:2px 6px;
433
480
  }
434
481
  .status-badge.pending { background:rgba(99,102,241,.15); color:var(--ik-accent); }
435
- .status-badge.acknowledged { background:rgba(249,115,22,.15); color:#f97316; }
436
482
  .status-badge.resolved { background:rgba(34,197,94,.15); color:#22c55e; }
437
483
  .status-badge.dismissed { background:var(--ik-bg2); color:var(--ik-muted); }
438
484
  `
@@ -445,28 +491,29 @@ var MARKER_CSS = (
445
491
  z-index: 2147483645;
446
492
  width: 24px; height: 24px;
447
493
  border-radius: 50%;
448
- background: #6366f1;
494
+ background: var(--ik-marker-default, #6366f1);
449
495
  color: #fff;
450
496
  font-size: 11px; font-weight: 700;
451
497
  display: flex; align-items: center; justify-content: center;
452
498
  cursor: pointer;
453
- box-shadow: 0 2px 8px rgba(99,102,241,.4);
499
+ box-shadow: 0 2px 8px color-mix(in srgb, var(--ik-marker-default, #6366f1) 40%, transparent);
454
500
  transition: transform .15s ease;
455
501
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
456
502
  pointer-events: all;
457
503
  user-select: none;
458
504
  }
459
505
  .ik-marker:hover { transform: scale(1.15); }
460
- .ik-marker.resolved { background: #22c55e; box-shadow: 0 2px 8px rgba(34,197,94,.4); }
461
- .ik-marker.dismissed { background: #71717a; box-shadow: 0 2px 8px rgba(0,0,0,.2); }
462
- .ik-marker.acknowledged { background: #f97316; box-shadow: 0 2px 8px rgba(249,115,22,.4); }
506
+ .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); }
507
+ .ik-marker.dismissed { background: var(--ik-marker-dismissed, #71717a); box-shadow: 0 2px 8px rgba(0,0,0,.2); }
463
508
  `
464
509
  );
465
- function injectGlobalStyles() {
510
+ function injectGlobalStyles(colors) {
466
511
  if (document.getElementById("instruckt-global")) return;
512
+ 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};` : ""} }
513
+ ` : "";
467
514
  const style = document.createElement("style");
468
515
  style.id = "instruckt-global";
469
- style.textContent = GLOBAL_CSS + MARKER_CSS;
516
+ style.textContent = vars + GLOBAL_CSS + MARKER_CSS;
470
517
  document.head.appendChild(style);
471
518
  }
472
519
 
@@ -478,10 +525,11 @@ var ICONS = {
478
525
  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>`,
479
526
  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>`,
480
527
  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>`,
528
+ 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>`,
481
529
  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>`
482
530
  };
483
531
  var Toolbar = class {
484
- constructor(position, callbacks) {
532
+ constructor(position, callbacks, keys) {
485
533
  this.position = position;
486
534
  this.callbacks = callbacks;
487
535
  this.fabBadge = null;
@@ -491,11 +539,12 @@ var Toolbar = class {
491
539
  this.totalCount = 0;
492
540
  this.dragging = false;
493
541
  this.dragOffset = { x: 0, y: 0 };
542
+ this.keys = keys != null ? keys : {};
494
543
  this.build();
495
544
  this.setupDrag();
496
545
  }
497
546
  build() {
498
- var _a;
547
+ var _a, _b, _c, _d, _e;
499
548
  this.host = document.createElement("div");
500
549
  this.host.setAttribute("data-instruckt", "toolbar");
501
550
  this.shadow = this.host.attachShadow({ mode: "open" });
@@ -504,16 +553,20 @@ var Toolbar = class {
504
553
  this.shadow.appendChild(style);
505
554
  this.toolbarEl = document.createElement("div");
506
555
  this.toolbarEl.className = "toolbar";
507
- this.annotateBtn = this.makeBtn(ICONS.annotate, "Annotate elements (A)", () => {
556
+ const k = this.keys;
557
+ this.annotateBtn = this.makeBtn(ICONS.annotate, `Annotate elements (${((_a = k.annotate) != null ? _a : "A").toUpperCase()})`, () => {
508
558
  const next = !this.annotateActive;
509
559
  this.setAnnotateActive(next);
510
560
  this.callbacks.onToggleAnnotate(next);
511
561
  });
512
- this.freezeBtn = this.makeBtn(ICONS.freeze, "Freeze page (F)", () => {
562
+ this.freezeBtn = this.makeBtn(ICONS.freeze, `Freeze page (${((_b = k.freeze) != null ? _b : "F").toUpperCase()})`, () => {
513
563
  const next = !this.freezeActive;
514
564
  this.setFreezeActive(next);
515
565
  this.callbacks.onFreezeAnimations(next);
516
566
  });
567
+ const screenshotBtn = this.makeBtn(ICONS.screenshot, `Screenshot region (${((_c = k.screenshot) != null ? _c : "C").toUpperCase()})`, () => {
568
+ this.callbacks.onScreenshot();
569
+ });
517
570
  this.copyBtn = this.makeBtn(ICONS.copy, "Copy annotations as markdown", () => {
518
571
  this.callbacks.onCopy();
519
572
  this.copyBtn.innerHTML = ICONS.check;
@@ -523,22 +576,20 @@ var Toolbar = class {
523
576
  });
524
577
  const clearWrap = document.createElement("div");
525
578
  clearWrap.className = "clear-wrap";
526
- const clearBtn = this.makeBtn(ICONS.clear, "Clear this page (X)", () => {
527
- var _a2, _b;
528
- (_b = (_a2 = this.callbacks).onClearPage) == null ? void 0 : _b.call(_a2);
579
+ const clearBtn = this.makeBtn(ICONS.clear, `Clear this page (${((_d = k.clearPage) != null ? _d : "X").toUpperCase()})`, () => {
580
+ var _a2, _b2;
581
+ (_b2 = (_a2 = this.callbacks).onClearPage) == null ? void 0 : _b2.call(_a2);
529
582
  });
530
583
  clearBtn.classList.add("danger-btn");
531
584
  const clearAllBtn = this.makeBtn(
532
585
  `<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>`,
533
586
  "Delete all instructions.",
534
587
  () => {
535
- var _a2, _b;
536
- return (_b = (_a2 = this.callbacks).onClearAll) == null ? void 0 : _b.call(_a2);
588
+ var _a2, _b2;
589
+ return (_b2 = (_a2 = this.callbacks).onClearAll) == null ? void 0 : _b2.call(_a2);
537
590
  }
538
591
  );
539
592
  clearAllBtn.classList.add("danger-btn", "clear-all-btn");
540
- clearAllBtn.removeAttribute("title");
541
- clearAllBtn.setAttribute("data-tooltip", "Delete all instructions.");
542
593
  clearWrap.appendChild(clearBtn);
543
594
  clearWrap.appendChild(clearAllBtn);
544
595
  const minimizeBtn = this.makeBtn(ICONS.minimize, "Minimize toolbar", () => {
@@ -552,6 +603,7 @@ var Toolbar = class {
552
603
  };
553
604
  this.toolbarEl.append(
554
605
  this.annotateBtn,
606
+ screenshotBtn,
555
607
  mkDiv(),
556
608
  this.freezeBtn,
557
609
  mkDiv(),
@@ -576,14 +628,14 @@ var Toolbar = class {
576
628
  this.host.addEventListener("mousedown", (e) => e.stopPropagation());
577
629
  this.host.addEventListener("pointerdown", (e) => e.stopPropagation());
578
630
  this.applyPosition();
579
- const root = (_a = document.getElementById("instruckt-root")) != null ? _a : document.body;
631
+ const root = (_e = document.getElementById("instruckt-root")) != null ? _e : document.body;
580
632
  root.appendChild(this.host);
581
633
  }
582
- makeBtn(iconHtml, title, onClick) {
634
+ makeBtn(iconHtml, tooltip, onClick) {
583
635
  const btn = document.createElement("button");
584
636
  btn.className = "btn";
585
- btn.title = title;
586
- btn.setAttribute("aria-label", title);
637
+ btn.setAttribute("data-tooltip", tooltip);
638
+ btn.setAttribute("aria-label", tooltip);
587
639
  btn.innerHTML = iconHtml;
588
640
  btn.addEventListener("click", (e) => {
589
641
  e.stopPropagation();
@@ -734,7 +786,953 @@ var ElementHighlight = class {
734
786
  }
735
787
  };
736
788
 
789
+ // node_modules/html-to-image/es/util.js
790
+ function resolveUrl(url, baseUrl) {
791
+ if (url.match(/^[a-z]+:\/\//i)) {
792
+ return url;
793
+ }
794
+ if (url.match(/^\/\//)) {
795
+ return window.location.protocol + url;
796
+ }
797
+ if (url.match(/^[a-z]+:/i)) {
798
+ return url;
799
+ }
800
+ const doc = document.implementation.createHTMLDocument();
801
+ const base = doc.createElement("base");
802
+ const a = doc.createElement("a");
803
+ doc.head.appendChild(base);
804
+ doc.body.appendChild(a);
805
+ if (baseUrl) {
806
+ base.href = baseUrl;
807
+ }
808
+ a.href = url;
809
+ return a.href;
810
+ }
811
+ var uuid = /* @__PURE__ */ (() => {
812
+ let counter = 0;
813
+ const random = () => (
814
+ // eslint-disable-next-line no-bitwise
815
+ `0000${(Math.random() * 36 ** 4 << 0).toString(36)}`.slice(-4)
816
+ );
817
+ return () => {
818
+ counter += 1;
819
+ return `u${random()}${counter}`;
820
+ };
821
+ })();
822
+ function toArray(arrayLike) {
823
+ const arr = [];
824
+ for (let i = 0, l = arrayLike.length; i < l; i++) {
825
+ arr.push(arrayLike[i]);
826
+ }
827
+ return arr;
828
+ }
829
+ var styleProps = null;
830
+ function getStyleProperties(options = {}) {
831
+ if (styleProps) {
832
+ return styleProps;
833
+ }
834
+ if (options.includeStyleProperties) {
835
+ styleProps = options.includeStyleProperties;
836
+ return styleProps;
837
+ }
838
+ styleProps = toArray(window.getComputedStyle(document.documentElement));
839
+ return styleProps;
840
+ }
841
+ function px(node, styleProperty) {
842
+ const win = node.ownerDocument.defaultView || window;
843
+ const val = win.getComputedStyle(node).getPropertyValue(styleProperty);
844
+ return val ? parseFloat(val.replace("px", "")) : 0;
845
+ }
846
+ function getNodeWidth(node) {
847
+ const leftBorder = px(node, "border-left-width");
848
+ const rightBorder = px(node, "border-right-width");
849
+ return node.clientWidth + leftBorder + rightBorder;
850
+ }
851
+ function getNodeHeight(node) {
852
+ const topBorder = px(node, "border-top-width");
853
+ const bottomBorder = px(node, "border-bottom-width");
854
+ return node.clientHeight + topBorder + bottomBorder;
855
+ }
856
+ function getImageSize(targetNode, options = {}) {
857
+ const width = options.width || getNodeWidth(targetNode);
858
+ const height = options.height || getNodeHeight(targetNode);
859
+ return { width, height };
860
+ }
861
+ function getPixelRatio() {
862
+ let ratio;
863
+ let FINAL_PROCESS;
864
+ try {
865
+ FINAL_PROCESS = process;
866
+ } catch (e) {
867
+ }
868
+ const val = FINAL_PROCESS && FINAL_PROCESS.env ? FINAL_PROCESS.env.devicePixelRatio : null;
869
+ if (val) {
870
+ ratio = parseInt(val, 10);
871
+ if (Number.isNaN(ratio)) {
872
+ ratio = 1;
873
+ }
874
+ }
875
+ return ratio || window.devicePixelRatio || 1;
876
+ }
877
+ var canvasDimensionLimit = 16384;
878
+ function checkCanvasDimensions(canvas) {
879
+ if (canvas.width > canvasDimensionLimit || canvas.height > canvasDimensionLimit) {
880
+ if (canvas.width > canvasDimensionLimit && canvas.height > canvasDimensionLimit) {
881
+ if (canvas.width > canvas.height) {
882
+ canvas.height *= canvasDimensionLimit / canvas.width;
883
+ canvas.width = canvasDimensionLimit;
884
+ } else {
885
+ canvas.width *= canvasDimensionLimit / canvas.height;
886
+ canvas.height = canvasDimensionLimit;
887
+ }
888
+ } else if (canvas.width > canvasDimensionLimit) {
889
+ canvas.height *= canvasDimensionLimit / canvas.width;
890
+ canvas.width = canvasDimensionLimit;
891
+ } else {
892
+ canvas.width *= canvasDimensionLimit / canvas.height;
893
+ canvas.height = canvasDimensionLimit;
894
+ }
895
+ }
896
+ }
897
+ function createImage(url) {
898
+ return new Promise((resolve, reject) => {
899
+ const img = new Image();
900
+ img.onload = () => {
901
+ img.decode().then(() => {
902
+ requestAnimationFrame(() => resolve(img));
903
+ });
904
+ };
905
+ img.onerror = reject;
906
+ img.crossOrigin = "anonymous";
907
+ img.decoding = "async";
908
+ img.src = url;
909
+ });
910
+ }
911
+ async function svgToDataURL(svg) {
912
+ return Promise.resolve().then(() => new XMLSerializer().serializeToString(svg)).then(encodeURIComponent).then((html) => `data:image/svg+xml;charset=utf-8,${html}`);
913
+ }
914
+ async function nodeToDataURL(node, width, height) {
915
+ const xmlns = "http://www.w3.org/2000/svg";
916
+ const svg = document.createElementNS(xmlns, "svg");
917
+ const foreignObject = document.createElementNS(xmlns, "foreignObject");
918
+ svg.setAttribute("width", `${width}`);
919
+ svg.setAttribute("height", `${height}`);
920
+ svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
921
+ foreignObject.setAttribute("width", "100%");
922
+ foreignObject.setAttribute("height", "100%");
923
+ foreignObject.setAttribute("x", "0");
924
+ foreignObject.setAttribute("y", "0");
925
+ foreignObject.setAttribute("externalResourcesRequired", "true");
926
+ svg.appendChild(foreignObject);
927
+ foreignObject.appendChild(node);
928
+ return svgToDataURL(svg);
929
+ }
930
+ var isInstanceOfElement = (node, instance) => {
931
+ if (node instanceof instance)
932
+ return true;
933
+ const nodePrototype = Object.getPrototypeOf(node);
934
+ if (nodePrototype === null)
935
+ return false;
936
+ return nodePrototype.constructor.name === instance.name || isInstanceOfElement(nodePrototype, instance);
937
+ };
938
+
939
+ // node_modules/html-to-image/es/clone-pseudos.js
940
+ function formatCSSText(style) {
941
+ const content = style.getPropertyValue("content");
942
+ return `${style.cssText} content: '${content.replace(/'|"/g, "")}';`;
943
+ }
944
+ function formatCSSProperties(style, options) {
945
+ return getStyleProperties(options).map((name) => {
946
+ const value = style.getPropertyValue(name);
947
+ const priority = style.getPropertyPriority(name);
948
+ return `${name}: ${value}${priority ? " !important" : ""};`;
949
+ }).join(" ");
950
+ }
951
+ function getPseudoElementStyle(className, pseudo, style, options) {
952
+ const selector = `.${className}:${pseudo}`;
953
+ const cssText = style.cssText ? formatCSSText(style) : formatCSSProperties(style, options);
954
+ return document.createTextNode(`${selector}{${cssText}}`);
955
+ }
956
+ function clonePseudoElement(nativeNode, clonedNode, pseudo, options) {
957
+ const style = window.getComputedStyle(nativeNode, pseudo);
958
+ const content = style.getPropertyValue("content");
959
+ if (content === "" || content === "none") {
960
+ return;
961
+ }
962
+ const className = uuid();
963
+ try {
964
+ clonedNode.className = `${clonedNode.className} ${className}`;
965
+ } catch (err) {
966
+ return;
967
+ }
968
+ const styleElement = document.createElement("style");
969
+ styleElement.appendChild(getPseudoElementStyle(className, pseudo, style, options));
970
+ clonedNode.appendChild(styleElement);
971
+ }
972
+ function clonePseudoElements(nativeNode, clonedNode, options) {
973
+ clonePseudoElement(nativeNode, clonedNode, ":before", options);
974
+ clonePseudoElement(nativeNode, clonedNode, ":after", options);
975
+ }
976
+
977
+ // node_modules/html-to-image/es/mimes.js
978
+ var WOFF = "application/font-woff";
979
+ var JPEG = "image/jpeg";
980
+ var mimes = {
981
+ woff: WOFF,
982
+ woff2: WOFF,
983
+ ttf: "application/font-truetype",
984
+ eot: "application/vnd.ms-fontobject",
985
+ png: "image/png",
986
+ jpg: JPEG,
987
+ jpeg: JPEG,
988
+ gif: "image/gif",
989
+ tiff: "image/tiff",
990
+ svg: "image/svg+xml",
991
+ webp: "image/webp"
992
+ };
993
+ function getExtension(url) {
994
+ const match = /\.([^./]*?)$/g.exec(url);
995
+ return match ? match[1] : "";
996
+ }
997
+ function getMimeType(url) {
998
+ const extension = getExtension(url).toLowerCase();
999
+ return mimes[extension] || "";
1000
+ }
1001
+
1002
+ // node_modules/html-to-image/es/dataurl.js
1003
+ function getContentFromDataUrl(dataURL) {
1004
+ return dataURL.split(/,/)[1];
1005
+ }
1006
+ function isDataUrl(url) {
1007
+ return url.search(/^(data:)/) !== -1;
1008
+ }
1009
+ function makeDataUrl(content, mimeType) {
1010
+ return `data:${mimeType};base64,${content}`;
1011
+ }
1012
+ async function fetchAsDataURL(url, init2, process2) {
1013
+ const res = await fetch(url, init2);
1014
+ if (res.status === 404) {
1015
+ throw new Error(`Resource "${res.url}" not found`);
1016
+ }
1017
+ const blob = await res.blob();
1018
+ return new Promise((resolve, reject) => {
1019
+ const reader = new FileReader();
1020
+ reader.onerror = reject;
1021
+ reader.onloadend = () => {
1022
+ try {
1023
+ resolve(process2({ res, result: reader.result }));
1024
+ } catch (error) {
1025
+ reject(error);
1026
+ }
1027
+ };
1028
+ reader.readAsDataURL(blob);
1029
+ });
1030
+ }
1031
+ var cache = {};
1032
+ function getCacheKey(url, contentType, includeQueryParams) {
1033
+ let key = url.replace(/\?.*/, "");
1034
+ if (includeQueryParams) {
1035
+ key = url;
1036
+ }
1037
+ if (/ttf|otf|eot|woff2?/i.test(key)) {
1038
+ key = key.replace(/.*\//, "");
1039
+ }
1040
+ return contentType ? `[${contentType}]${key}` : key;
1041
+ }
1042
+ async function resourceToDataURL(resourceUrl, contentType, options) {
1043
+ const cacheKey = getCacheKey(resourceUrl, contentType, options.includeQueryParams);
1044
+ if (cache[cacheKey] != null) {
1045
+ return cache[cacheKey];
1046
+ }
1047
+ if (options.cacheBust) {
1048
+ resourceUrl += (/\?/.test(resourceUrl) ? "&" : "?") + (/* @__PURE__ */ new Date()).getTime();
1049
+ }
1050
+ let dataURL;
1051
+ try {
1052
+ const content = await fetchAsDataURL(resourceUrl, options.fetchRequestInit, ({ res, result }) => {
1053
+ if (!contentType) {
1054
+ contentType = res.headers.get("Content-Type") || "";
1055
+ }
1056
+ return getContentFromDataUrl(result);
1057
+ });
1058
+ dataURL = makeDataUrl(content, contentType);
1059
+ } catch (error) {
1060
+ dataURL = options.imagePlaceholder || "";
1061
+ let msg = `Failed to fetch resource: ${resourceUrl}`;
1062
+ if (error) {
1063
+ msg = typeof error === "string" ? error : error.message;
1064
+ }
1065
+ if (msg) {
1066
+ console.warn(msg);
1067
+ }
1068
+ }
1069
+ cache[cacheKey] = dataURL;
1070
+ return dataURL;
1071
+ }
1072
+
1073
+ // node_modules/html-to-image/es/clone-node.js
1074
+ async function cloneCanvasElement(canvas) {
1075
+ const dataURL = canvas.toDataURL();
1076
+ if (dataURL === "data:,") {
1077
+ return canvas.cloneNode(false);
1078
+ }
1079
+ return createImage(dataURL);
1080
+ }
1081
+ async function cloneVideoElement(video, options) {
1082
+ if (video.currentSrc) {
1083
+ const canvas = document.createElement("canvas");
1084
+ const ctx = canvas.getContext("2d");
1085
+ canvas.width = video.clientWidth;
1086
+ canvas.height = video.clientHeight;
1087
+ ctx === null || ctx === void 0 ? void 0 : ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
1088
+ const dataURL2 = canvas.toDataURL();
1089
+ return createImage(dataURL2);
1090
+ }
1091
+ const poster = video.poster;
1092
+ const contentType = getMimeType(poster);
1093
+ const dataURL = await resourceToDataURL(poster, contentType, options);
1094
+ return createImage(dataURL);
1095
+ }
1096
+ async function cloneIFrameElement(iframe, options) {
1097
+ var _a;
1098
+ try {
1099
+ if ((_a = iframe === null || iframe === void 0 ? void 0 : iframe.contentDocument) === null || _a === void 0 ? void 0 : _a.body) {
1100
+ return await cloneNode(iframe.contentDocument.body, options, true);
1101
+ }
1102
+ } catch (_b) {
1103
+ }
1104
+ return iframe.cloneNode(false);
1105
+ }
1106
+ async function cloneSingleNode(node, options) {
1107
+ if (isInstanceOfElement(node, HTMLCanvasElement)) {
1108
+ return cloneCanvasElement(node);
1109
+ }
1110
+ if (isInstanceOfElement(node, HTMLVideoElement)) {
1111
+ return cloneVideoElement(node, options);
1112
+ }
1113
+ if (isInstanceOfElement(node, HTMLIFrameElement)) {
1114
+ return cloneIFrameElement(node, options);
1115
+ }
1116
+ return node.cloneNode(isSVGElement(node));
1117
+ }
1118
+ var isSlotElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SLOT";
1119
+ var isSVGElement = (node) => node.tagName != null && node.tagName.toUpperCase() === "SVG";
1120
+ async function cloneChildren(nativeNode, clonedNode, options) {
1121
+ var _a, _b;
1122
+ if (isSVGElement(clonedNode)) {
1123
+ return clonedNode;
1124
+ }
1125
+ let children = [];
1126
+ if (isSlotElement(nativeNode) && nativeNode.assignedNodes) {
1127
+ children = toArray(nativeNode.assignedNodes());
1128
+ } else if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && ((_a = nativeNode.contentDocument) === null || _a === void 0 ? void 0 : _a.body)) {
1129
+ children = toArray(nativeNode.contentDocument.body.childNodes);
1130
+ } else {
1131
+ children = toArray(((_b = nativeNode.shadowRoot) !== null && _b !== void 0 ? _b : nativeNode).childNodes);
1132
+ }
1133
+ if (children.length === 0 || isInstanceOfElement(nativeNode, HTMLVideoElement)) {
1134
+ return clonedNode;
1135
+ }
1136
+ await children.reduce((deferred, child) => deferred.then(() => cloneNode(child, options)).then((clonedChild) => {
1137
+ if (clonedChild) {
1138
+ clonedNode.appendChild(clonedChild);
1139
+ }
1140
+ }), Promise.resolve());
1141
+ return clonedNode;
1142
+ }
1143
+ function cloneCSSStyle(nativeNode, clonedNode, options) {
1144
+ const targetStyle = clonedNode.style;
1145
+ if (!targetStyle) {
1146
+ return;
1147
+ }
1148
+ const sourceStyle = window.getComputedStyle(nativeNode);
1149
+ if (sourceStyle.cssText) {
1150
+ targetStyle.cssText = sourceStyle.cssText;
1151
+ targetStyle.transformOrigin = sourceStyle.transformOrigin;
1152
+ } else {
1153
+ getStyleProperties(options).forEach((name) => {
1154
+ let value = sourceStyle.getPropertyValue(name);
1155
+ if (name === "font-size" && value.endsWith("px")) {
1156
+ const reducedFont = Math.floor(parseFloat(value.substring(0, value.length - 2))) - 0.1;
1157
+ value = `${reducedFont}px`;
1158
+ }
1159
+ if (isInstanceOfElement(nativeNode, HTMLIFrameElement) && name === "display" && value === "inline") {
1160
+ value = "block";
1161
+ }
1162
+ if (name === "d" && clonedNode.getAttribute("d")) {
1163
+ value = `path(${clonedNode.getAttribute("d")})`;
1164
+ }
1165
+ targetStyle.setProperty(name, value, sourceStyle.getPropertyPriority(name));
1166
+ });
1167
+ }
1168
+ }
1169
+ function cloneInputValue(nativeNode, clonedNode) {
1170
+ if (isInstanceOfElement(nativeNode, HTMLTextAreaElement)) {
1171
+ clonedNode.innerHTML = nativeNode.value;
1172
+ }
1173
+ if (isInstanceOfElement(nativeNode, HTMLInputElement)) {
1174
+ clonedNode.setAttribute("value", nativeNode.value);
1175
+ }
1176
+ }
1177
+ function cloneSelectValue(nativeNode, clonedNode) {
1178
+ if (isInstanceOfElement(nativeNode, HTMLSelectElement)) {
1179
+ const clonedSelect = clonedNode;
1180
+ const selectedOption = Array.from(clonedSelect.children).find((child) => nativeNode.value === child.getAttribute("value"));
1181
+ if (selectedOption) {
1182
+ selectedOption.setAttribute("selected", "");
1183
+ }
1184
+ }
1185
+ }
1186
+ function decorate(nativeNode, clonedNode, options) {
1187
+ if (isInstanceOfElement(clonedNode, Element)) {
1188
+ cloneCSSStyle(nativeNode, clonedNode, options);
1189
+ clonePseudoElements(nativeNode, clonedNode, options);
1190
+ cloneInputValue(nativeNode, clonedNode);
1191
+ cloneSelectValue(nativeNode, clonedNode);
1192
+ }
1193
+ return clonedNode;
1194
+ }
1195
+ async function ensureSVGSymbols(clone, options) {
1196
+ const uses = clone.querySelectorAll ? clone.querySelectorAll("use") : [];
1197
+ if (uses.length === 0) {
1198
+ return clone;
1199
+ }
1200
+ const processedDefs = {};
1201
+ for (let i = 0; i < uses.length; i++) {
1202
+ const use = uses[i];
1203
+ const id = use.getAttribute("xlink:href");
1204
+ if (id) {
1205
+ const exist = clone.querySelector(id);
1206
+ const definition = document.querySelector(id);
1207
+ if (!exist && definition && !processedDefs[id]) {
1208
+ processedDefs[id] = await cloneNode(definition, options, true);
1209
+ }
1210
+ }
1211
+ }
1212
+ const nodes = Object.values(processedDefs);
1213
+ if (nodes.length) {
1214
+ const ns = "http://www.w3.org/1999/xhtml";
1215
+ const svg = document.createElementNS(ns, "svg");
1216
+ svg.setAttribute("xmlns", ns);
1217
+ svg.style.position = "absolute";
1218
+ svg.style.width = "0";
1219
+ svg.style.height = "0";
1220
+ svg.style.overflow = "hidden";
1221
+ svg.style.display = "none";
1222
+ const defs = document.createElementNS(ns, "defs");
1223
+ svg.appendChild(defs);
1224
+ for (let i = 0; i < nodes.length; i++) {
1225
+ defs.appendChild(nodes[i]);
1226
+ }
1227
+ clone.appendChild(svg);
1228
+ }
1229
+ return clone;
1230
+ }
1231
+ async function cloneNode(node, options, isRoot) {
1232
+ if (!isRoot && options.filter && !options.filter(node)) {
1233
+ return null;
1234
+ }
1235
+ 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));
1236
+ }
1237
+
1238
+ // node_modules/html-to-image/es/embed-resources.js
1239
+ var URL_REGEX = /url\((['"]?)([^'"]+?)\1\)/g;
1240
+ var URL_WITH_FORMAT_REGEX = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g;
1241
+ var FONT_SRC_REGEX = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g;
1242
+ function toRegex(url) {
1243
+ const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1");
1244
+ return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, "g");
1245
+ }
1246
+ function parseURLs(cssText) {
1247
+ const urls = [];
1248
+ cssText.replace(URL_REGEX, (raw, quotation, url) => {
1249
+ urls.push(url);
1250
+ return raw;
1251
+ });
1252
+ return urls.filter((url) => !isDataUrl(url));
1253
+ }
1254
+ async function embed(cssText, resourceURL, baseURL, options, getContentFromUrl) {
1255
+ try {
1256
+ const resolvedURL = baseURL ? resolveUrl(resourceURL, baseURL) : resourceURL;
1257
+ const contentType = getMimeType(resourceURL);
1258
+ let dataURL;
1259
+ if (getContentFromUrl) {
1260
+ const content = await getContentFromUrl(resolvedURL);
1261
+ dataURL = makeDataUrl(content, contentType);
1262
+ } else {
1263
+ dataURL = await resourceToDataURL(resolvedURL, contentType, options);
1264
+ }
1265
+ return cssText.replace(toRegex(resourceURL), `$1${dataURL}$3`);
1266
+ } catch (error) {
1267
+ }
1268
+ return cssText;
1269
+ }
1270
+ function filterPreferredFontFormat(str, { preferredFontFormat }) {
1271
+ return !preferredFontFormat ? str : str.replace(FONT_SRC_REGEX, (match) => {
1272
+ while (true) {
1273
+ const [src, , format] = URL_WITH_FORMAT_REGEX.exec(match) || [];
1274
+ if (!format) {
1275
+ return "";
1276
+ }
1277
+ if (format === preferredFontFormat) {
1278
+ return `src: ${src};`;
1279
+ }
1280
+ }
1281
+ });
1282
+ }
1283
+ function shouldEmbed(url) {
1284
+ return url.search(URL_REGEX) !== -1;
1285
+ }
1286
+ async function embedResources(cssText, baseUrl, options) {
1287
+ if (!shouldEmbed(cssText)) {
1288
+ return cssText;
1289
+ }
1290
+ const filteredCSSText = filterPreferredFontFormat(cssText, options);
1291
+ const urls = parseURLs(filteredCSSText);
1292
+ return urls.reduce((deferred, url) => deferred.then((css) => embed(css, url, baseUrl, options)), Promise.resolve(filteredCSSText));
1293
+ }
1294
+
1295
+ // node_modules/html-to-image/es/embed-images.js
1296
+ async function embedProp(propName, node, options) {
1297
+ var _a;
1298
+ const propValue = (_a = node.style) === null || _a === void 0 ? void 0 : _a.getPropertyValue(propName);
1299
+ if (propValue) {
1300
+ const cssString = await embedResources(propValue, null, options);
1301
+ node.style.setProperty(propName, cssString, node.style.getPropertyPriority(propName));
1302
+ return true;
1303
+ }
1304
+ return false;
1305
+ }
1306
+ async function embedBackground(clonedNode, options) {
1307
+ ;
1308
+ await embedProp("background", clonedNode, options) || await embedProp("background-image", clonedNode, options);
1309
+ await embedProp("mask", clonedNode, options) || await embedProp("-webkit-mask", clonedNode, options) || await embedProp("mask-image", clonedNode, options) || await embedProp("-webkit-mask-image", clonedNode, options);
1310
+ }
1311
+ async function embedImageNode(clonedNode, options) {
1312
+ const isImageElement = isInstanceOfElement(clonedNode, HTMLImageElement);
1313
+ if (!(isImageElement && !isDataUrl(clonedNode.src)) && !(isInstanceOfElement(clonedNode, SVGImageElement) && !isDataUrl(clonedNode.href.baseVal))) {
1314
+ return;
1315
+ }
1316
+ const url = isImageElement ? clonedNode.src : clonedNode.href.baseVal;
1317
+ const dataURL = await resourceToDataURL(url, getMimeType(url), options);
1318
+ await new Promise((resolve, reject) => {
1319
+ clonedNode.onload = resolve;
1320
+ clonedNode.onerror = options.onImageErrorHandler ? (...attributes) => {
1321
+ try {
1322
+ resolve(options.onImageErrorHandler(...attributes));
1323
+ } catch (error) {
1324
+ reject(error);
1325
+ }
1326
+ } : reject;
1327
+ const image = clonedNode;
1328
+ if (image.decode) {
1329
+ image.decode = resolve;
1330
+ }
1331
+ if (image.loading === "lazy") {
1332
+ image.loading = "eager";
1333
+ }
1334
+ if (isImageElement) {
1335
+ clonedNode.srcset = "";
1336
+ clonedNode.src = dataURL;
1337
+ } else {
1338
+ clonedNode.href.baseVal = dataURL;
1339
+ }
1340
+ });
1341
+ }
1342
+ async function embedChildren(clonedNode, options) {
1343
+ const children = toArray(clonedNode.childNodes);
1344
+ const deferreds = children.map((child) => embedImages(child, options));
1345
+ await Promise.all(deferreds).then(() => clonedNode);
1346
+ }
1347
+ async function embedImages(clonedNode, options) {
1348
+ if (isInstanceOfElement(clonedNode, Element)) {
1349
+ await embedBackground(clonedNode, options);
1350
+ await embedImageNode(clonedNode, options);
1351
+ await embedChildren(clonedNode, options);
1352
+ }
1353
+ }
1354
+
1355
+ // node_modules/html-to-image/es/apply-style.js
1356
+ function applyStyle(node, options) {
1357
+ const { style } = node;
1358
+ if (options.backgroundColor) {
1359
+ style.backgroundColor = options.backgroundColor;
1360
+ }
1361
+ if (options.width) {
1362
+ style.width = `${options.width}px`;
1363
+ }
1364
+ if (options.height) {
1365
+ style.height = `${options.height}px`;
1366
+ }
1367
+ const manual = options.style;
1368
+ if (manual != null) {
1369
+ Object.keys(manual).forEach((key) => {
1370
+ style[key] = manual[key];
1371
+ });
1372
+ }
1373
+ return node;
1374
+ }
1375
+
1376
+ // node_modules/html-to-image/es/embed-webfonts.js
1377
+ var cssFetchCache = {};
1378
+ async function fetchCSS(url) {
1379
+ let cache2 = cssFetchCache[url];
1380
+ if (cache2 != null) {
1381
+ return cache2;
1382
+ }
1383
+ const res = await fetch(url);
1384
+ const cssText = await res.text();
1385
+ cache2 = { url, cssText };
1386
+ cssFetchCache[url] = cache2;
1387
+ return cache2;
1388
+ }
1389
+ async function embedFonts(data, options) {
1390
+ let cssText = data.cssText;
1391
+ const regexUrl = /url\(["']?([^"')]+)["']?\)/g;
1392
+ const fontLocs = cssText.match(/url\([^)]+\)/g) || [];
1393
+ const loadFonts = fontLocs.map(async (loc) => {
1394
+ let url = loc.replace(regexUrl, "$1");
1395
+ if (!url.startsWith("https://")) {
1396
+ url = new URL(url, data.url).href;
1397
+ }
1398
+ return fetchAsDataURL(url, options.fetchRequestInit, ({ result }) => {
1399
+ cssText = cssText.replace(loc, `url(${result})`);
1400
+ return [loc, result];
1401
+ });
1402
+ });
1403
+ return Promise.all(loadFonts).then(() => cssText);
1404
+ }
1405
+ function parseCSS(source) {
1406
+ if (source == null) {
1407
+ return [];
1408
+ }
1409
+ const result = [];
1410
+ const commentsRegex = /(\/\*[\s\S]*?\*\/)/gi;
1411
+ let cssText = source.replace(commentsRegex, "");
1412
+ const keyframesRegex = new RegExp("((@.*?keyframes [\\s\\S]*?){([\\s\\S]*?}\\s*?)})", "gi");
1413
+ while (true) {
1414
+ const matches = keyframesRegex.exec(cssText);
1415
+ if (matches === null) {
1416
+ break;
1417
+ }
1418
+ result.push(matches[0]);
1419
+ }
1420
+ cssText = cssText.replace(keyframesRegex, "");
1421
+ const importRegex = /@import[\s\S]*?url\([^)]*\)[\s\S]*?;/gi;
1422
+ const combinedCSSRegex = "((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})";
1423
+ const unifiedRegex = new RegExp(combinedCSSRegex, "gi");
1424
+ while (true) {
1425
+ let matches = importRegex.exec(cssText);
1426
+ if (matches === null) {
1427
+ matches = unifiedRegex.exec(cssText);
1428
+ if (matches === null) {
1429
+ break;
1430
+ } else {
1431
+ importRegex.lastIndex = unifiedRegex.lastIndex;
1432
+ }
1433
+ } else {
1434
+ unifiedRegex.lastIndex = importRegex.lastIndex;
1435
+ }
1436
+ result.push(matches[0]);
1437
+ }
1438
+ return result;
1439
+ }
1440
+ async function getCSSRules(styleSheets, options) {
1441
+ const ret = [];
1442
+ const deferreds = [];
1443
+ styleSheets.forEach((sheet) => {
1444
+ if ("cssRules" in sheet) {
1445
+ try {
1446
+ toArray(sheet.cssRules || []).forEach((item, index) => {
1447
+ if (item.type === CSSRule.IMPORT_RULE) {
1448
+ let importIndex = index + 1;
1449
+ const url = item.href;
1450
+ const deferred = fetchCSS(url).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => {
1451
+ try {
1452
+ sheet.insertRule(rule, rule.startsWith("@import") ? importIndex += 1 : sheet.cssRules.length);
1453
+ } catch (error) {
1454
+ console.error("Error inserting rule from remote css", {
1455
+ rule,
1456
+ error
1457
+ });
1458
+ }
1459
+ })).catch((e) => {
1460
+ console.error("Error loading remote css", e.toString());
1461
+ });
1462
+ deferreds.push(deferred);
1463
+ }
1464
+ });
1465
+ } catch (e) {
1466
+ const inline = styleSheets.find((a) => a.href == null) || document.styleSheets[0];
1467
+ if (sheet.href != null) {
1468
+ deferreds.push(fetchCSS(sheet.href).then((metadata) => embedFonts(metadata, options)).then((cssText) => parseCSS(cssText).forEach((rule) => {
1469
+ inline.insertRule(rule, inline.cssRules.length);
1470
+ })).catch((err) => {
1471
+ console.error("Error loading remote stylesheet", err);
1472
+ }));
1473
+ }
1474
+ console.error("Error inlining remote css file", e);
1475
+ }
1476
+ }
1477
+ });
1478
+ return Promise.all(deferreds).then(() => {
1479
+ styleSheets.forEach((sheet) => {
1480
+ if ("cssRules" in sheet) {
1481
+ try {
1482
+ toArray(sheet.cssRules || []).forEach((item) => {
1483
+ ret.push(item);
1484
+ });
1485
+ } catch (e) {
1486
+ console.error(`Error while reading CSS rules from ${sheet.href}`, e);
1487
+ }
1488
+ }
1489
+ });
1490
+ return ret;
1491
+ });
1492
+ }
1493
+ function getWebFontRules(cssRules) {
1494
+ return cssRules.filter((rule) => rule.type === CSSRule.FONT_FACE_RULE).filter((rule) => shouldEmbed(rule.style.getPropertyValue("src")));
1495
+ }
1496
+ async function parseWebFontRules(node, options) {
1497
+ if (node.ownerDocument == null) {
1498
+ throw new Error("Provided element is not within a Document");
1499
+ }
1500
+ const styleSheets = toArray(node.ownerDocument.styleSheets);
1501
+ const cssRules = await getCSSRules(styleSheets, options);
1502
+ return getWebFontRules(cssRules);
1503
+ }
1504
+ function normalizeFontFamily(font) {
1505
+ return font.trim().replace(/["']/g, "");
1506
+ }
1507
+ function getUsedFonts(node) {
1508
+ const fonts = /* @__PURE__ */ new Set();
1509
+ function traverse(node2) {
1510
+ const fontFamily = node2.style.fontFamily || getComputedStyle(node2).fontFamily;
1511
+ fontFamily.split(",").forEach((font) => {
1512
+ fonts.add(normalizeFontFamily(font));
1513
+ });
1514
+ Array.from(node2.children).forEach((child) => {
1515
+ if (child instanceof HTMLElement) {
1516
+ traverse(child);
1517
+ }
1518
+ });
1519
+ }
1520
+ traverse(node);
1521
+ return fonts;
1522
+ }
1523
+ async function getWebFontCSS(node, options) {
1524
+ const rules = await parseWebFontRules(node, options);
1525
+ const usedFonts = getUsedFonts(node);
1526
+ const cssTexts = await Promise.all(rules.filter((rule) => usedFonts.has(normalizeFontFamily(rule.style.fontFamily))).map((rule) => {
1527
+ const baseUrl = rule.parentStyleSheet ? rule.parentStyleSheet.href : null;
1528
+ return embedResources(rule.cssText, baseUrl, options);
1529
+ }));
1530
+ return cssTexts.join("\n");
1531
+ }
1532
+ async function embedWebFonts(clonedNode, options) {
1533
+ const cssText = options.fontEmbedCSS != null ? options.fontEmbedCSS : options.skipFonts ? null : await getWebFontCSS(clonedNode, options);
1534
+ if (cssText) {
1535
+ const styleNode = document.createElement("style");
1536
+ const sytleContent = document.createTextNode(cssText);
1537
+ styleNode.appendChild(sytleContent);
1538
+ if (clonedNode.firstChild) {
1539
+ clonedNode.insertBefore(styleNode, clonedNode.firstChild);
1540
+ } else {
1541
+ clonedNode.appendChild(styleNode);
1542
+ }
1543
+ }
1544
+ }
1545
+
1546
+ // node_modules/html-to-image/es/index.js
1547
+ async function toSvg(node, options = {}) {
1548
+ const { width, height } = getImageSize(node, options);
1549
+ const clonedNode = await cloneNode(node, options, true);
1550
+ await embedWebFonts(clonedNode, options);
1551
+ await embedImages(clonedNode, options);
1552
+ applyStyle(clonedNode, options);
1553
+ const datauri = await nodeToDataURL(clonedNode, width, height);
1554
+ return datauri;
1555
+ }
1556
+ async function toCanvas(node, options = {}) {
1557
+ const { width, height } = getImageSize(node, options);
1558
+ const svg = await toSvg(node, options);
1559
+ const img = await createImage(svg);
1560
+ const canvas = document.createElement("canvas");
1561
+ const context = canvas.getContext("2d");
1562
+ const ratio = options.pixelRatio || getPixelRatio();
1563
+ const canvasWidth = options.canvasWidth || width;
1564
+ const canvasHeight = options.canvasHeight || height;
1565
+ canvas.width = canvasWidth * ratio;
1566
+ canvas.height = canvasHeight * ratio;
1567
+ if (!options.skipAutoScale) {
1568
+ checkCanvasDimensions(canvas);
1569
+ }
1570
+ canvas.style.width = `${canvasWidth}`;
1571
+ canvas.style.height = `${canvasHeight}`;
1572
+ if (options.backgroundColor) {
1573
+ context.fillStyle = options.backgroundColor;
1574
+ context.fillRect(0, 0, canvas.width, canvas.height);
1575
+ }
1576
+ context.drawImage(img, 0, 0, canvas.width, canvas.height);
1577
+ return canvas;
1578
+ }
1579
+ async function toPng(node, options = {}) {
1580
+ const canvas = await toCanvas(node, options);
1581
+ return canvas.toDataURL();
1582
+ }
1583
+
1584
+ // src/ui/screenshot.ts
1585
+ async function captureElement(el) {
1586
+ try {
1587
+ return await toPng(el, {
1588
+ cacheBust: true,
1589
+ pixelRatio: 2,
1590
+ skipFonts: true,
1591
+ filter: (node) => {
1592
+ var _a, _b;
1593
+ if ((_a = node.getAttribute) == null ? void 0 : _a.call(node, "data-instruckt")) return false;
1594
+ if (node.tagName === "LINK" && node.getAttribute("rel") === "stylesheet") {
1595
+ const href = (_b = node.getAttribute("href")) != null ? _b : "";
1596
+ if (href.startsWith("http") && !href.startsWith(window.location.origin)) return false;
1597
+ }
1598
+ return true;
1599
+ }
1600
+ });
1601
+ } catch (e) {
1602
+ return null;
1603
+ }
1604
+ }
1605
+ async function captureRegion(rect) {
1606
+ try {
1607
+ const full = await toPng(document.body, {
1608
+ cacheBust: true,
1609
+ pixelRatio: 2,
1610
+ skipFonts: true,
1611
+ filter: (node) => {
1612
+ var _a, _b;
1613
+ if ((_a = node.getAttribute) == null ? void 0 : _a.call(node, "data-instruckt")) return false;
1614
+ if (node.tagName === "LINK" && node.getAttribute("rel") === "stylesheet") {
1615
+ const href = (_b = node.getAttribute("href")) != null ? _b : "";
1616
+ if (href.startsWith("http") && !href.startsWith(window.location.origin)) return false;
1617
+ }
1618
+ return true;
1619
+ }
1620
+ });
1621
+ return await cropImage(full, rect);
1622
+ } catch (e) {
1623
+ return null;
1624
+ }
1625
+ }
1626
+ function cropImage(dataUrl, rect) {
1627
+ return new Promise((resolve, reject) => {
1628
+ const img = new Image();
1629
+ img.onload = () => {
1630
+ const ratio = 2;
1631
+ const canvas = document.createElement("canvas");
1632
+ canvas.width = rect.width * ratio;
1633
+ canvas.height = rect.height * ratio;
1634
+ const ctx = canvas.getContext("2d");
1635
+ ctx.drawImage(
1636
+ img,
1637
+ rect.x * ratio,
1638
+ rect.y * ratio,
1639
+ rect.width * ratio,
1640
+ rect.height * ratio,
1641
+ 0,
1642
+ 0,
1643
+ rect.width * ratio,
1644
+ rect.height * ratio
1645
+ );
1646
+ resolve(canvas.toDataURL("image/png"));
1647
+ };
1648
+ img.onerror = reject;
1649
+ img.src = dataUrl;
1650
+ });
1651
+ }
1652
+ function selectRegion() {
1653
+ return new Promise((resolve) => {
1654
+ const overlay = document.createElement("div");
1655
+ Object.assign(overlay.style, {
1656
+ position: "fixed",
1657
+ inset: "0",
1658
+ zIndex: "2147483647",
1659
+ cursor: "crosshair",
1660
+ background: "rgba(0,0,0,0.1)"
1661
+ });
1662
+ overlay.setAttribute("data-instruckt", "region-select");
1663
+ const box = document.createElement("div");
1664
+ Object.assign(box.style, {
1665
+ position: "fixed",
1666
+ border: "2px dashed #6366f1",
1667
+ background: "rgba(99,102,241,0.08)",
1668
+ borderRadius: "4px",
1669
+ display: "none",
1670
+ pointerEvents: "none"
1671
+ });
1672
+ overlay.appendChild(box);
1673
+ let startX = 0, startY = 0, dragging = false;
1674
+ const onMouseDown = (e) => {
1675
+ startX = e.clientX;
1676
+ startY = e.clientY;
1677
+ dragging = true;
1678
+ box.style.display = "block";
1679
+ updateBox(e);
1680
+ };
1681
+ const onMouseMove = (e) => {
1682
+ if (!dragging) return;
1683
+ updateBox(e);
1684
+ };
1685
+ const updateBox = (e) => {
1686
+ const x = Math.min(startX, e.clientX);
1687
+ const y = Math.min(startY, e.clientY);
1688
+ const w = Math.abs(e.clientX - startX);
1689
+ const h = Math.abs(e.clientY - startY);
1690
+ Object.assign(box.style, {
1691
+ left: `${x}px`,
1692
+ top: `${y}px`,
1693
+ width: `${w}px`,
1694
+ height: `${h}px`
1695
+ });
1696
+ };
1697
+ const onMouseUp = (e) => {
1698
+ if (!dragging) return;
1699
+ dragging = false;
1700
+ const x = Math.min(startX, e.clientX);
1701
+ const y = Math.min(startY, e.clientY);
1702
+ const w = Math.abs(e.clientX - startX);
1703
+ const h = Math.abs(e.clientY - startY);
1704
+ cleanup();
1705
+ if (w < 10 || h < 10) {
1706
+ resolve(null);
1707
+ } else {
1708
+ resolve(new DOMRect(x, y, w, h));
1709
+ }
1710
+ };
1711
+ const onKeyDown = (e) => {
1712
+ if (e.key === "Escape") {
1713
+ cleanup();
1714
+ resolve(null);
1715
+ }
1716
+ };
1717
+ const cleanup = () => {
1718
+ overlay.remove();
1719
+ document.removeEventListener("keydown", onKeyDown, true);
1720
+ };
1721
+ overlay.addEventListener("mousedown", onMouseDown);
1722
+ overlay.addEventListener("mousemove", onMouseMove);
1723
+ overlay.addEventListener("mouseup", onMouseUp);
1724
+ document.addEventListener("keydown", onKeyDown, true);
1725
+ document.body.appendChild(overlay);
1726
+ });
1727
+ }
1728
+
737
1729
  // src/ui/popup.ts
1730
+ function screenshotUrl(screenshot, endpoint) {
1731
+ if (!screenshot) return null;
1732
+ if (screenshot.startsWith("data:")) return screenshot;
1733
+ const base = endpoint != null ? endpoint : "/instruckt";
1734
+ return `${base}/${screenshot}`;
1735
+ }
738
1736
  function esc(s) {
739
1737
  return String(s != null ? s : "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
740
1738
  }
@@ -750,7 +1748,7 @@ var AnnotationPopup = class {
750
1748
  }
751
1749
  // ── New annotation popup ──────────────────────────────────────
752
1750
  showNew(pending, callbacks) {
753
- var _a;
1751
+ var _a, _b;
754
1752
  this.destroy();
755
1753
  this.host = document.createElement("div");
756
1754
  this.host.setAttribute("data-instruckt", "popup");
@@ -763,23 +1761,56 @@ var AnnotationPopup = class {
763
1761
  popup.className = "popup";
764
1762
  const fwBadge = pending.framework ? `<div class="fw-badge">${esc(pending.framework.component)}</div>` : "";
765
1763
  const selText = pending.selectedText ? `<div class="selected-text">"${esc(pending.selectedText.slice(0, 80))}"</div>` : "";
1764
+ const hasScreenshot = !!pending.screenshot;
766
1765
  popup.innerHTML = `
767
1766
  <div class="header">
768
1767
  <span class="element-tag" title="${esc(pending.elementPath)}">${esc(pending.elementLabel)}</span>
769
1768
  <button class="close-btn" title="Cancel (Esc)">\u2715</button>
770
1769
  </div>
771
1770
  ${fwBadge}${selText}
772
- <textarea placeholder="What needs to change here?" rows="3"></textarea>
1771
+ <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>
1772
+ <textarea placeholder="${hasScreenshot ? "Add a note (optional)" : "What needs to change here?"}" rows="3"></textarea>
773
1773
  <div class="actions">
774
1774
  <button class="btn-secondary" data-action="cancel">Cancel</button>
775
- <button class="btn-primary" data-action="submit" disabled>Add note</button>
1775
+ <button class="btn-primary" data-action="submit" ${hasScreenshot ? "" : "disabled"}>Add note</button>
776
1776
  </div>
777
1777
  `;
1778
+ let currentScreenshot = (_a = pending.screenshot) != null ? _a : null;
778
1779
  const textarea = popup.querySelector("textarea");
779
1780
  const submitBtn = popup.querySelector('[data-action="submit"]');
780
- textarea.addEventListener("input", () => {
781
- submitBtn.disabled = textarea.value.trim().length === 0;
782
- });
1781
+ const screenshotSlot = popup.querySelector(".screenshot-slot");
1782
+ const updateSubmitState = () => {
1783
+ submitBtn.disabled = !currentScreenshot && textarea.value.trim().length === 0;
1784
+ };
1785
+ const attachScreenshotEvents = () => {
1786
+ const captureBtn = screenshotSlot.querySelector('[data-action="capture"]');
1787
+ captureBtn == null ? void 0 : captureBtn.addEventListener("click", async () => {
1788
+ captureBtn.textContent = "Capturing...";
1789
+ const dataUrl = await captureElement(pending.element);
1790
+ if (dataUrl) {
1791
+ currentScreenshot = dataUrl;
1792
+ screenshotSlot.innerHTML = `<div class="screenshot-preview"><img src="${dataUrl}" alt="Screenshot" /><button class="screenshot-remove" title="Remove screenshot">\u2715</button></div>`;
1793
+ textarea.placeholder = "Add a note (optional)";
1794
+ attachScreenshotEvents();
1795
+ updateSubmitState();
1796
+ } else {
1797
+ captureBtn.textContent = "Capture failed";
1798
+ setTimeout(() => {
1799
+ 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`;
1800
+ }, 1500);
1801
+ }
1802
+ });
1803
+ const removeBtn = screenshotSlot.querySelector(".screenshot-remove");
1804
+ removeBtn == null ? void 0 : removeBtn.addEventListener("click", () => {
1805
+ currentScreenshot = null;
1806
+ 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>`;
1807
+ textarea.placeholder = "What needs to change here?";
1808
+ attachScreenshotEvents();
1809
+ updateSubmitState();
1810
+ });
1811
+ };
1812
+ attachScreenshotEvents();
1813
+ textarea.addEventListener("input", updateSubmitState);
783
1814
  textarea.addEventListener("keydown", (e) => {
784
1815
  e.stopPropagation();
785
1816
  if (e.key === "Enter" && !e.shiftKey) {
@@ -801,18 +1832,18 @@ var AnnotationPopup = class {
801
1832
  });
802
1833
  submitBtn.addEventListener("click", () => {
803
1834
  const comment = textarea.value.trim();
804
- if (!comment) return;
805
- callbacks.onSubmit({ comment });
1835
+ if (!comment && !currentScreenshot) return;
1836
+ callbacks.onSubmit({ comment: comment || "(screenshot)", screenshot: currentScreenshot != null ? currentScreenshot : void 0 });
806
1837
  this.destroy();
807
1838
  });
808
1839
  this.shadow.appendChild(popup);
809
- ((_a = document.getElementById("instruckt-root")) != null ? _a : document.body).appendChild(this.host);
1840
+ ((_b = document.getElementById("instruckt-root")) != null ? _b : document.body).appendChild(this.host);
810
1841
  this.positionHost(pending.x, pending.y);
811
1842
  this.setupOutsideClick();
812
1843
  textarea.focus();
813
1844
  }
814
1845
  // ── Edit existing annotation ──────────────────────────────────
815
- showEdit(annotation, callbacks) {
1846
+ showEdit(annotation, callbacks, endpoint) {
816
1847
  var _a;
817
1848
  this.destroy();
818
1849
  this.host = document.createElement("div");
@@ -825,19 +1856,28 @@ var AnnotationPopup = class {
825
1856
  const popup = document.createElement("div");
826
1857
  popup.className = "popup";
827
1858
  const fwBadge = annotation.framework ? `<div class="fw-badge">${esc(annotation.framework.component)}</div>` : "";
1859
+ const ssUrl = screenshotUrl(annotation.screenshot, endpoint);
1860
+ const screenshotPreview = ssUrl ? `<div class="screenshot-preview screenshot-slot"><img src="${ssUrl}" alt="Screenshot" /><button class="screenshot-remove" title="Remove screenshot">\u2715</button></div>` : "";
1861
+ const commentText = annotation.comment === "(screenshot)" ? "" : annotation.comment;
828
1862
  popup.innerHTML = `
829
1863
  <div class="header">
830
1864
  <span class="element-tag" title="${esc(annotation.elementPath)}">${esc(annotation.element)}</span>
831
1865
  <button class="close-btn">\u2715</button>
832
1866
  </div>
833
- ${fwBadge}
834
- <textarea rows="3">${esc(annotation.comment)}</textarea>
1867
+ ${fwBadge}${screenshotPreview}
1868
+ <textarea rows="3">${esc(commentText)}</textarea>
835
1869
  <div class="actions">
836
1870
  <button class="btn-danger" data-action="delete">Remove</button>
837
1871
  <button class="btn-primary" data-action="save">Save</button>
838
1872
  </div>
839
1873
  `;
840
1874
  popup.querySelector(".close-btn").addEventListener("click", () => this.destroy());
1875
+ const ssRemoveBtn = popup.querySelector(".screenshot-remove");
1876
+ ssRemoveBtn == null ? void 0 : ssRemoveBtn.addEventListener("click", () => {
1877
+ callbacks.onSave(annotation, annotation.comment);
1878
+ const slot = popup.querySelector(".screenshot-slot");
1879
+ if (slot) slot.remove();
1880
+ });
841
1881
  const textarea = popup.querySelector("textarea");
842
1882
  const saveBtn = popup.querySelector('[data-action="save"]');
843
1883
  const deleteBtn = popup.querySelector('[data-action="delete"]');
@@ -932,9 +1972,10 @@ var AnnotationMarkers = class {
932
1972
  return;
933
1973
  }
934
1974
  const el = document.createElement("div");
935
- el.className = `ik-marker ${this.statusClass(annotation.status)}`;
1975
+ const ssClass = annotation.screenshot ? " has-screenshot" : "";
1976
+ el.className = `ik-marker ${this.statusClass(annotation.status)}${ssClass}`;
936
1977
  el.textContent = String(index);
937
- el.title = annotation.comment.slice(0, 60);
1978
+ el.title = annotation.comment === "(screenshot)" ? "Screenshot" : annotation.comment.slice(0, 60);
938
1979
  el.style.pointerEvents = "all";
939
1980
  el.style.left = `${annotation.x / 100 * window.innerWidth}px`;
940
1981
  el.style.top = `${annotation.y - window.scrollY}px`;
@@ -952,13 +1993,13 @@ var AnnotationMarkers = class {
952
1993
  this.updateStyle(marker.el, annotation);
953
1994
  }
954
1995
  updateStyle(el, annotation) {
955
- el.className = `ik-marker ${this.statusClass(annotation.status)}`;
956
- el.title = annotation.comment.slice(0, 60);
1996
+ const ssClass = annotation.screenshot ? " has-screenshot" : "";
1997
+ el.className = `ik-marker ${this.statusClass(annotation.status)}${ssClass}`;
1998
+ el.title = annotation.comment === "(screenshot)" ? "Screenshot" : annotation.comment.slice(0, 60);
957
1999
  }
958
2000
  statusClass(status) {
959
2001
  if (status === "resolved") return "resolved";
960
2002
  if (status === "dismissed") return "dismissed";
961
- if (status === "acknowledged") return "acknowledged";
962
2003
  return "";
963
2004
  }
964
2005
  /** Reposition all markers (e.g. after scroll or resize) */
@@ -1293,7 +2334,7 @@ var _Instruckt = class _Instruckt {
1293
2334
  e.stopImmediatePropagation();
1294
2335
  };
1295
2336
  this.boundClick = (e) => {
1296
- var _a, _b, _c, _d;
2337
+ var _a, _b, _c;
1297
2338
  const target = e.target;
1298
2339
  if (this.isInstruckt(target)) return;
1299
2340
  e.preventDefault();
@@ -1307,6 +2348,8 @@ var _Instruckt = class _Instruckt {
1307
2348
  const nearbyText = getNearbyText(target) || void 0;
1308
2349
  const boundingBox = getPageBoundingBox(target);
1309
2350
  const framework = (_b = this.detectFramework(target)) != null ? _b : void 0;
2351
+ (_c = this.highlight) == null ? void 0 : _c.show(target);
2352
+ this.highlightLocked = true;
1310
2353
  const pending = {
1311
2354
  element: target,
1312
2355
  elementPath,
@@ -1320,21 +2363,7 @@ var _Instruckt = class _Instruckt {
1320
2363
  nearbyText,
1321
2364
  framework
1322
2365
  };
1323
- (_c = this.highlight) == null ? void 0 : _c.show(target);
1324
- this.highlightLocked = true;
1325
- (_d = this.popup) == null ? void 0 : _d.showNew(pending, {
1326
- onSubmit: (result) => {
1327
- var _a2;
1328
- this.highlightLocked = false;
1329
- (_a2 = this.highlight) == null ? void 0 : _a2.hide();
1330
- this.submitAnnotation(pending, result.comment);
1331
- },
1332
- onCancel: () => {
1333
- var _a2;
1334
- this.highlightLocked = false;
1335
- (_a2 = this.highlight) == null ? void 0 : _a2.hide();
1336
- }
1337
- });
2366
+ this.showAnnotationPopup(pending);
1338
2367
  };
1339
2368
  this.config = __spreadValues({
1340
2369
  adapters: ["livewire", "vue", "svelte", "react"],
@@ -1346,7 +2375,7 @@ var _Instruckt = class _Instruckt {
1346
2375
  this.init();
1347
2376
  }
1348
2377
  init() {
1349
- injectGlobalStyles();
2378
+ injectGlobalStyles(this.config.colors);
1350
2379
  if (this.config.theme !== "auto") {
1351
2380
  document.documentElement.setAttribute("data-instruckt-theme", this.config.theme);
1352
2381
  }
@@ -1357,11 +2386,12 @@ var _Instruckt = class _Instruckt {
1357
2386
  onFreezeAnimations: (frozen) => {
1358
2387
  this.setFrozen(frozen);
1359
2388
  },
2389
+ onScreenshot: () => this.startRegionCapture(),
1360
2390
  onCopy: () => this.copyToClipboard(true),
1361
2391
  onClearPage: () => this.clearPage(),
1362
2392
  onClearAll: () => this.clearEverything(),
1363
2393
  onMinimize: (min) => this.onMinimize(min)
1364
- });
2394
+ }, this.config.keys);
1365
2395
  this.highlight = new ElementHighlight();
1366
2396
  this.popup = new AnnotationPopup();
1367
2397
  this.markers = new AnnotationMarkers((annotation) => this.onMarkerClick(annotation));
@@ -1374,7 +2404,6 @@ var _Instruckt = class _Instruckt {
1374
2404
  setTimeout(() => this.reattach(), 0);
1375
2405
  });
1376
2406
  this.loadAnnotations();
1377
- this.pollTimer = setInterval(() => this.pollForChanges(), 3e3);
1378
2407
  this.syncMarkers();
1379
2408
  }
1380
2409
  makeToolbarCallbacks() {
@@ -1385,6 +2414,7 @@ var _Instruckt = class _Instruckt {
1385
2414
  onFreezeAnimations: (frozen) => {
1386
2415
  this.setFrozen(frozen);
1387
2416
  },
2417
+ onScreenshot: () => this.startRegionCapture(),
1388
2418
  onCopy: () => this.copyToClipboard(true),
1389
2419
  onClearPage: () => this.clearPage(),
1390
2420
  onClearAll: () => this.clearEverything(),
@@ -1408,7 +2438,7 @@ var _Instruckt = class _Instruckt {
1408
2438
  if (wasMinimized) this.markers.setVisible(false);
1409
2439
  const existing = document.getElementById("instruckt-global");
1410
2440
  if (existing) existing.remove();
1411
- injectGlobalStyles();
2441
+ injectGlobalStyles(this.config.colors);
1412
2442
  this.syncMarkers();
1413
2443
  if (wasAnnotating && !wasMinimized) this.setAnnotating(true);
1414
2444
  }
@@ -1453,6 +2483,16 @@ var _Instruckt = class _Instruckt {
1453
2483
  } catch (e) {
1454
2484
  }
1455
2485
  }
2486
+ /** Start or stop polling based on whether there are active annotations */
2487
+ updatePolling() {
2488
+ const hasActive = this.totalActiveCount() > 0;
2489
+ if (hasActive && !this.pollTimer) {
2490
+ this.pollTimer = setInterval(() => this.pollForChanges(), 3e3);
2491
+ } else if (!hasActive && this.pollTimer) {
2492
+ clearInterval(this.pollTimer);
2493
+ this.pollTimer = null;
2494
+ }
2495
+ }
1456
2496
  /** Poll API for status changes (e.g. agent resolved via MCP) */
1457
2497
  async pollForChanges() {
1458
2498
  try {
@@ -1489,6 +2529,7 @@ var _Instruckt = class _Instruckt {
1489
2529
  }
1490
2530
  (_c = this.toolbar) == null ? void 0 : _c.setAnnotationCount(this.pageAnnotations().length);
1491
2531
  (_d = this.toolbar) == null ? void 0 : _d.setTotalCount(this.totalActiveCount());
2532
+ this.updatePolling();
1492
2533
  }
1493
2534
  annotationPageKey(a) {
1494
2535
  try {
@@ -1618,6 +2659,22 @@ var _Instruckt = class _Instruckt {
1618
2659
  `;
1619
2660
  document.head.appendChild(this.frozenStyleEl);
1620
2661
  }
2662
+ showAnnotationPopup(pending) {
2663
+ var _a;
2664
+ (_a = this.popup) == null ? void 0 : _a.showNew(pending, {
2665
+ onSubmit: (result) => {
2666
+ var _a2;
2667
+ this.highlightLocked = false;
2668
+ (_a2 = this.highlight) == null ? void 0 : _a2.hide();
2669
+ this.submitAnnotation(pending, result.comment, result.screenshot);
2670
+ },
2671
+ onCancel: () => {
2672
+ var _a2;
2673
+ this.highlightLocked = false;
2674
+ (_a2 = this.highlight) == null ? void 0 : _a2.hide();
2675
+ }
2676
+ });
2677
+ }
1621
2678
  attachAnnotateListeners() {
1622
2679
  document.addEventListener("mousemove", this.boundMouseMove);
1623
2680
  document.addEventListener("mouseleave", this.boundMouseLeave);
@@ -1638,6 +2695,39 @@ var _Instruckt = class _Instruckt {
1638
2695
  if (!el || !(el instanceof Element)) return false;
1639
2696
  return el.closest("[data-instruckt]") !== null;
1640
2697
  }
2698
+ // ── Region screenshot ────────────────────────────────────────
2699
+ async startRegionCapture() {
2700
+ var _a, _b;
2701
+ const wasAnnotating = this.isAnnotating;
2702
+ if (wasAnnotating) this.setAnnotating(false);
2703
+ const rect = await selectRegion();
2704
+ if (!rect) {
2705
+ if (wasAnnotating) this.setAnnotating(true);
2706
+ return;
2707
+ }
2708
+ const screenshot = await captureRegion(rect);
2709
+ if (!screenshot) {
2710
+ if (wasAnnotating) this.setAnnotating(true);
2711
+ return;
2712
+ }
2713
+ const centerX = rect.x + rect.width / 2;
2714
+ const centerY = rect.y + rect.height / 2;
2715
+ const target = (_a = document.elementFromPoint(centerX, centerY)) != null ? _a : document.body;
2716
+ const pending = {
2717
+ element: target,
2718
+ elementPath: getElementSelector(target),
2719
+ elementName: getElementName(target),
2720
+ elementLabel: getElementLabel(target),
2721
+ cssClasses: getCssClasses(target),
2722
+ boundingBox: getPageBoundingBox(target),
2723
+ x: centerX,
2724
+ y: centerY,
2725
+ nearbyText: getNearbyText(target) || void 0,
2726
+ screenshot,
2727
+ framework: (_b = this.detectFramework(target)) != null ? _b : void 0
2728
+ };
2729
+ this.showAnnotationPopup(pending);
2730
+ }
1641
2731
  // ── Framework detection ───────────────────────────────────────
1642
2732
  detectFramework(el) {
1643
2733
  var _a;
@@ -1661,7 +2751,7 @@ var _Instruckt = class _Instruckt {
1661
2751
  return null;
1662
2752
  }
1663
2753
  // ── Submit ────────────────────────────────────────────────────
1664
- async submitAnnotation(pending, comment) {
2754
+ async submitAnnotation(pending, comment, screenshot) {
1665
2755
  var _a, _b;
1666
2756
  const payload = {
1667
2757
  x: pending.x / window.innerWidth * 100,
@@ -1673,6 +2763,7 @@ var _Instruckt = class _Instruckt {
1673
2763
  boundingBox: pending.boundingBox,
1674
2764
  selectedText: pending.selectedText,
1675
2765
  nearbyText: pending.nearbyText,
2766
+ screenshot,
1676
2767
  intent: "fix",
1677
2768
  severity: "important",
1678
2769
  framework: pending.framework,
@@ -1685,7 +2776,6 @@ var _Instruckt = class _Instruckt {
1685
2776
  annotation = __spreadProps(__spreadValues({}, payload), {
1686
2777
  id: crypto.randomUUID(),
1687
2778
  status: "pending",
1688
- thread: [],
1689
2779
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
1690
2780
  });
1691
2781
  }
@@ -1714,7 +2804,7 @@ var _Instruckt = class _Instruckt {
1714
2804
  }
1715
2805
  this.removeAnnotation(a.id);
1716
2806
  }
1717
- });
2807
+ }, this.config.endpoint);
1718
2808
  }
1719
2809
  onAnnotationUpdated(updated) {
1720
2810
  const idx = this.annotations.findIndex((a) => a.id === updated.id);
@@ -1756,19 +2846,24 @@ var _Instruckt = class _Instruckt {
1756
2846
  }
1757
2847
  // ── Keyboard ──────────────────────────────────────────────────
1758
2848
  onKeydown(e) {
1759
- var _a;
2849
+ var _a, _b, _c, _d, _e, _f;
1760
2850
  if ((_a = this.toolbar) == null ? void 0 : _a.isMinimized()) return;
1761
2851
  const target = e.target;
1762
2852
  if (["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName)) return;
1763
2853
  if (target.closest('[contenteditable="true"]')) return;
1764
2854
  if (this.isInstruckt(target)) return;
1765
- if (e.key === "a" && !e.metaKey && !e.ctrlKey && !e.altKey) {
2855
+ const keys = (_b = this.config.keys) != null ? _b : {};
2856
+ const noMod = !e.metaKey && !e.ctrlKey && !e.altKey;
2857
+ if (e.key === ((_c = keys.annotate) != null ? _c : "a") && noMod) {
1766
2858
  this.setAnnotating(!this.isAnnotating);
1767
2859
  }
1768
- if (e.key === "f" && !e.metaKey && !e.ctrlKey && !e.altKey) {
2860
+ if (e.key === ((_d = keys.freeze) != null ? _d : "f") && noMod) {
1769
2861
  this.setFrozen(!this.isFrozen);
1770
2862
  }
1771
- if (e.key === "x" && !e.metaKey && !e.ctrlKey && !e.altKey) {
2863
+ if (e.key === ((_e = keys.screenshot) != null ? _e : "c") && noMod) {
2864
+ this.startRegionCapture();
2865
+ }
2866
+ if (e.key === ((_f = keys.clearPage) != null ? _f : "x") && noMod) {
1772
2867
  this.clearPage();
1773
2868
  }
1774
2869
  if (e.key === "Escape") {
@@ -1843,6 +2938,13 @@ No open annotations.`;
1843
2938
  } else if (a.nearbyText) {
1844
2939
  lines.push(`- Text: "${a.nearbyText.slice(0, 100)}"`);
1845
2940
  }
2941
+ if (a.screenshot) {
2942
+ if (!a.screenshot.startsWith("data:")) {
2943
+ lines.push(`- Screenshot: \`storage/app/_instruckt/${a.screenshot}\``);
2944
+ } else {
2945
+ lines.push(`- Screenshot: attached`);
2946
+ }
2947
+ }
1846
2948
  lines.push("");
1847
2949
  });
1848
2950
  }