@qaiddev/thumbs-embed 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,22 +1,2572 @@
1
- import { a as o } from "./chunks/bootstrap-rINL2pCt.js";
2
- import { Q as a } from "./chunks/bootstrap-rINL2pCt.js";
3
- import { c as i } from "./chunks/network-capture-DMbMwwwR.js";
4
- import { createVideoRecorder as n, getSupportedMimeType as d, isVideoRecordingSupported as c } from "./chunks/video-BOYqf2Im.js";
5
- import { captureDomScreenshot as f, isDomScreenshotSupported as u } from "./chunks/screenshot-dom-CME7tYRE.js";
6
- import { A as x, c as A, d as h, o as E, r as g } from "./chunks/annotate-pSxJNPPL.js";
7
- o();
1
+ const V = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
2
+ <path stroke-linecap="round" stroke-linejoin="round" d="M14 10h4.764a2 2 0 011.789 2.894l-3.5 7A2 2 0 0115.263 21h-4.017c-.163 0-.326-.02-.485-.06L7 20m7-10V5a2 2 0 00-2-2h-.095c-.5 0-.905.405-.905.905 0 .714-.211 1.412-.608 2.006L7 11v9m7-10h-2M7 20H5a2 2 0 01-2-2v-6a2 2 0 012-2h2.5"/>
3
+ </svg>`, N = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
4
+ <path stroke-linecap="round" stroke-linejoin="round" d="M10 14H5.236a2 2 0 01-1.789-2.894l3.5-7A2 2 0 018.737 3h4.018a2 2 0 01.485.06l3.76.94m-7 10v5a2 2 0 002 2h.095c.5 0 .905-.405.905-.905 0-.714.211-1.412.608-2.006L17 13V4m-7 10h2m5-10h2a2 2 0 012 2v6a2 2 0 01-2 2h-2.5"/>
5
+ </svg>`, Nt = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
6
+ <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 10.5l4.72-4.72a.75.75 0 011.28.53v11.38a.75.75 0 01-1.28.53l-4.72-4.72M4.5 18.75h9a2.25 2.25 0 002.25-2.25v-9a2.25 2.25 0 00-2.25-2.25h-9A2.25 2.25 0 002.25 7.5v9a2.25 2.25 0 002.25 2.25z"/>
7
+ </svg>`, pt = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
8
+ <path stroke-linecap="round" stroke-linejoin="round" d="M8 10h8M8 14h5m-9 6l3.5-3.5H18a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v14z"/>
9
+ </svg>`, Ut = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
10
+ <rect x="4" y="6" width="16" height="12" rx="1" stroke-linejoin="round"/>
11
+ </svg>`, jt = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
12
+ <path stroke-linecap="round" stroke-linejoin="round" d="M5 19L19 5m0 0h-8m8 0v8"/>
13
+ </svg>`, Kt = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
14
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 20h9M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/>
15
+ </svg>`, Wt = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
16
+ <path stroke-linecap="round" stroke-linejoin="round" d="M12 3s6 6.4 6 10a6 6 0 01-12 0c0-3.6 6-10 6-10z"/>
17
+ </svg>`, Xt = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
18
+ <path stroke-linecap="round" stroke-linejoin="round" d="M9 14L4 9l5-5M4 9h11a5 5 0 010 10h-4"/>
19
+ </svg>`, Yt = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
20
+ <path stroke-linecap="round" stroke-linejoin="round" d="M20 6L9 17l-5-5"/>
21
+ </svg>`, Qt = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
22
+ <path stroke-linecap="round" stroke-linejoin="round" d="M6 6l12 12M18 6L6 18"/>
23
+ </svg>`, Gt = "button{cursor:pointer}.qaid-buttons{display:flex;gap:.5rem}.qaid-buttons.qaid-vertical{flex-direction:column}.qaid-buttons.qaid-auto-container{position:fixed;z-index:50}.qaid-buttons.qaid-auto-container.qaid-bottom-right{bottom:1rem;right:1rem}.qaid-buttons.qaid-auto-container.qaid-bottom-left{bottom:1rem;left:1rem}.qaid-buttons.qaid-auto-container.qaid-top-right{top:1rem;right:1rem}.qaid-buttons.qaid-auto-container.qaid-top-left{top:1rem;left:1rem}.qaid-buttons.qaid-incognito{opacity:0;transition:opacity .2s ease-in-out}.qaid-buttons.qaid-incognito:hover{opacity:1}.qaid-buttons.qaid-dismissed{display:none!important}.qaid-dismiss-btn{width:20px;height:20px;padding:0;border:none;border-radius:50%;background:#0006;color:#fff;display:flex;align-items:center;justify-content:center;align-self:center;opacity:0;transition:opacity .15s,background .15s;cursor:pointer;pointer-events:auto;-webkit-appearance:none;appearance:none}.qaid-dismiss-btn:hover{background:#0009}.qaid-buttons:hover .qaid-dismiss-btn{opacity:1}button.qaid-btn-structural{display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-appearance:none;appearance:none}.qaid-icon{width:24px;height:24px}.qaid-btn-structural:not(:has(svg)),.qaid-btn:not(:has(svg)){font-size:var(--qaid-icon-size, 24px);line-height:1}.qaid-emoji-icon{font-size:var(--qaid-icon-size, 24px);line-height:1}.qaid-buttons,.qaid-buttons *,.qaid-modal-container,.qaid-modal-container *{cursor:pointer!important}.qaid-tooltip-wrapper{position:relative}.qaid-tooltip-text{position:fixed;padding:.5rem .75rem;background:#1f2937;color:#fff;font-size:1rem;font-weight:600;border-radius:.5rem;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .15s;z-index:99999}.qaid-tooltip-text.qaid-tooltip-visible{opacity:1}.qaid-targeting-overlay{position:fixed;inset:0;z-index:40;pointer-events:none}.qaid-capture-layer{position:fixed;inset:0;pointer-events:none;z-index:9999}@keyframes qaid-slideDown{0%{transform:translateY(-100%)}to{transform:translateY(0)}}.qaid-vignette{position:fixed;inset:0;pointer-events:none;background:radial-gradient(ellipse at center,transparent 30%,rgba(0,0,0,.3) 70%,rgba(0,0,0,.6) 100%);z-index:41}.qaid-crosshair-h,.qaid-crosshair-v{position:fixed;pointer-events:none;z-index:42}.qaid-crosshair-h{left:0;right:0;height:1px;background:color-mix(in srgb,var(--qaid-negative) 60%,transparent)}.qaid-crosshair-v{top:0;bottom:0;width:1px;background:color-mix(in srgb,var(--qaid-negative) 60%,transparent)}.qaid-type-up .qaid-crosshair-h,.qaid-type-up .qaid-crosshair-v{background:color-mix(in srgb,var(--qaid-positive) 60%,transparent)}.qaid-highlight-box{position:fixed;top:0;left:0;pointer-events:none;z-index:41;display:none;border:3px solid color-mix(in srgb,var(--qaid-negative) 80%,transparent);border-radius:2px;background:color-mix(in srgb,var(--qaid-negative) 8%,transparent);will-change:transform,width,height}.qaid-type-up .qaid-highlight-box{border-color:color-mix(in srgb,var(--qaid-positive) 80%,transparent);background:color-mix(in srgb,var(--qaid-positive) 8%,transparent)}.qaid-scope{position:fixed;width:80px;height:80px;pointer-events:none;z-index:43;transform:translate(-50%,-50%)}.qaid-scope-ring{position:absolute;inset:10px;border:2px solid color-mix(in srgb,var(--qaid-negative) 80%,transparent);border-radius:50%}.qaid-type-up .qaid-scope-ring{border-color:color-mix(in srgb,var(--qaid-positive) 80%,transparent)}.qaid-scope-ring-inner{position:absolute;inset:20px;border:1px solid color-mix(in srgb,var(--qaid-negative) 50%,transparent);border-radius:50%}.qaid-type-up .qaid-scope-ring-inner{border-color:color-mix(in srgb,var(--qaid-positive) 50%,transparent)}.qaid-scope-dot{position:absolute;top:50%;left:50%;width:4px;height:4px;background:var(--qaid-negative);border-radius:50%;transform:translate(-50%,-50%)}.qaid-type-up .qaid-scope-dot{background:var(--qaid-positive)}.qaid-selected-marker{position:fixed;border:3px solid var(--qaid-marker, #6366f1);border-radius:50%;pointer-events:none;z-index:44;animation:qaid-markerPulse 1.5s ease-in-out infinite}@keyframes qaid-markerPulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.7;transform:scale(1.05)}}.qaid-backdrop{position:fixed;inset:0;z-index:45;background:#0000004d}.qaid-modal-container{position:fixed;z-index:50;display:flex;flex-direction:column;align-items:flex-start;max-height:calc(100vh - 32px);font-family:var(--qaid-font-family, system-ui, -apple-system, sans-serif);font-size:var(--qaid-font-size, 16px)}.qaid-modal-container.qaid-above{flex-direction:column-reverse}.qaid-modal-arrow{width:0;height:0;border-left:12px solid transparent;border-right:12px solid transparent;position:relative;align-self:flex-start}.qaid-modal-container.qaid-below .qaid-modal-arrow{border-bottom:12px solid light-dark(#ffffff,#1f2937)}.qaid-modal-container.qaid-above .qaid-modal-arrow{border-top:12px solid light-dark(#ffffff,#1f2937)}.qaid-modal-box{color-scheme:inherit;background:light-dark(#ffffff,#1f2937);border-radius:1rem;padding:1.5rem;box-shadow:0 25px 50px -12px light-dark(rgba(0,0,0,.25),rgba(0,0,0,.5));width:var(--qaid-modal-width, 400px);max-width:calc(100vw - 32px);max-height:calc(100vh - 60px);overflow-y:auto}.qaid-modal-header{display:flex;align-items:flex-start;gap:.75rem;margin-bottom:1rem}button.qaid-type-toggle{border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .2s;flex-shrink:0;-webkit-appearance:none;appearance:none}button.qaid-type-toggle:hover{transform:scale(1.1)}button.qaid-type-toggle:not(.qaid-type-toggle-custom){width:2.5rem;height:2.5rem;border-radius:50%}button.qaid-type-toggle:not(.qaid-type-toggle-custom) svg{width:1.25rem;height:1.25rem}button.qaid-type-toggle.qaid-type-up{background:var(--qaid-positive);color:#fff}button.qaid-type-toggle.qaid-type-down{background:var(--qaid-negative);color:#fff}.qaid-type-static{width:2.5rem;height:2.5rem;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0;background:var(--qaid-marker, #6366f1);color:var(--qaid-marker-text, white)}.qaid-type-static svg{width:1.25rem;height:1.25rem}.qaid-modal-header-text{flex:1;min-width:0}.qaid-modal-title{font-size:1.125rem;font-weight:700;margin:0 0 .25rem;color:light-dark(#1f2937,#f9fafb)}.qaid-modal-subtitle{color:light-dark(#6b7280,#9ca3af);margin:0;font-size:.875rem}.qaid-textarea{width:100%;height:6rem;padding:.75rem;border:1px solid light-dark(#d1d5db,#374151);border-radius:.5rem;font-family:inherit;font-size:1rem;resize:vertical;margin-bottom:1rem;box-sizing:border-box;background:light-dark(#ffffff,#111827);color:light-dark(#1f2937,#f9fafb)}.qaid-textarea:focus{outline:none;border-color:var(--qaid-marker);box-shadow:0 0 0 3px color-mix(in srgb,var(--qaid-marker) 20%,transparent)}.qaid-btn-row{display:flex;gap:.5rem;justify-content:flex-end}button.qaid-btn-submit{padding:.5rem 1rem;background:var(--qaid-marker);color:var(--qaid-marker-text, white);border:none;border-radius:.5rem;font-size:.875rem;font-weight:500;cursor:pointer;transition:background-color .2s,filter .2s;-webkit-appearance:none;appearance:none}button.qaid-btn-submit:hover{filter:brightness(.85)}button.qaid-btn-submit:focus{outline:none;box-shadow:0 0 0 3px color-mix(in srgb,var(--qaid-marker) 30%,transparent)}.qaid-bottom-sheet{position:fixed;bottom:0;left:0;right:0;z-index:50;animation:qaid-slideUpSheet .3s ease-out;font-family:var(--qaid-font-family, system-ui, -apple-system, sans-serif);font-size:var(--qaid-font-size, 16px)}.qaid-bottom-sheet-content{background:light-dark(#ffffff,#1f2937);border-radius:1rem 1rem 0 0;padding:1.5rem;padding-bottom:max(1.5rem,env(safe-area-inset-bottom))}.qaid-bottom-sheet-handle{width:36px;height:4px;background:light-dark(rgba(0,0,0,.2),rgba(255,255,255,.2));border-radius:2px;margin:0 auto 1rem}@keyframes qaid-slideUpSheet{0%{transform:translateY(100%)}to{transform:translateY(0)}}button.qaid-btn-record:hover{background:#dc2626;color:#fff}.qaid-recording-indicator{position:fixed;top:12px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:.5rem;padding:.5rem 1rem;background:light-dark(#1f2937,#374151);color:#fff;border-radius:9999px;font-family:var(--qaid-font-family, system-ui, -apple-system, sans-serif);font-size:.875rem;font-weight:500;box-shadow:0 4px 12px #0000004d;z-index:99999;animation:qaid-slideDown .2s ease-out}.qaid-recording-dot{width:10px;height:10px;background:#dc2626;border-radius:50%;animation:qaid-dotPulse 1.5s ease-in-out infinite}@keyframes qaid-dotPulse{0%,to{opacity:1}50%{opacity:.3}}.qaid-recording-time{font-variant-numeric:tabular-nums;min-width:2.5rem;text-align:center}button.qaid-recording-stop{padding:.25rem .75rem;background:#dc2626;color:#fff;border:none;border-radius:9999px;font-size:.75rem;font-weight:600;cursor:pointer;transition:background-color .2s;-webkit-appearance:none;appearance:none}button.qaid-recording-stop:hover{background:#b91c1c}.qaid-video-preview{position:fixed;inset:0 0 auto;height:100vh;height:100dvh;display:flex;align-items:center;justify-content:center;padding:16px;padding-top:max(16px,env(safe-area-inset-top));padding-bottom:max(16px,env(safe-area-inset-bottom));box-sizing:border-box;background:#0009;z-index:99998;animation:qaid-fadeIn .2s ease-out}@keyframes qaid-fadeIn{0%{opacity:0}to{opacity:1}}.qaid-video-preview-box{color-scheme:inherit;background:light-dark(#ffffff,#1f2937);border-radius:1rem;padding:1.5rem;box-shadow:0 25px 50px -12px #00000080;width:560px;max-width:100%;max-height:100%;overflow-y:auto;font-family:var(--qaid-font-family, system-ui, -apple-system, sans-serif);font-size:var(--qaid-font-size, 16px)}.qaid-video-preview-box h3{font-size:1.125rem;font-weight:700;margin:0 0 1rem;color:light-dark(#1f2937,#f9fafb)}.qaid-video-preview-box video{width:100%;max-height:50vh;max-height:50dvh;object-fit:contain;border-radius:.5rem;background:#000;margin-bottom:1rem}.qaid-video-preview-box textarea{width:100%;height:4rem;padding:.75rem;border:1px solid light-dark(#d1d5db,#374151);border-radius:.5rem;font-family:inherit;font-size:.875rem;resize:vertical;margin-bottom:1rem;box-sizing:border-box;background:light-dark(#ffffff,#111827);color:light-dark(#1f2937,#f9fafb)}.qaid-video-preview-box textarea:focus{outline:none;border-color:var(--qaid-marker);box-shadow:0 0 0 3px color-mix(in srgb,var(--qaid-marker) 20%,transparent)}.qaid-video-preview-actions{display:flex;gap:.5rem;justify-content:flex-end}button.qaid-video-btn{padding:.5rem 1rem;border:none;border-radius:.5rem;font-size:.875rem;font-weight:500;cursor:pointer;transition:background-color .2s,filter .2s;-webkit-appearance:none;appearance:none}button.qaid-video-btn-cancel{background:light-dark(#f3f4f6,#374151);color:light-dark(#374151,#d1d5db)}button.qaid-video-btn-cancel:hover{background:light-dark(#e5e7eb,#4b5563)}button.qaid-video-btn-rerecord{background:light-dark(#fef3c7,#78350f);color:light-dark(#92400e,#fde68a)}button.qaid-video-btn-rerecord:hover{filter:brightness(.9)}button.qaid-video-btn-send{background:var(--qaid-marker);color:var(--qaid-marker-text, white)}button.qaid-video-btn-send:hover{filter:brightness(.85)}button.qaid-video-btn-send:disabled{opacity:.5;cursor:not-allowed}.qaid-video-sending{display:flex;align-items:center;gap:.5rem;font-size:.875rem;color:light-dark(#6b7280,#9ca3af)}.qaid-annotate{position:fixed;inset:0;z-index:2147483000;display:flex;flex-direction:column;gap:.75rem;padding:16px;padding-top:max(16px,env(safe-area-inset-top));padding-bottom:max(16px,env(safe-area-inset-bottom));box-sizing:border-box;background:#000000d9;font-family:var(--qaid-font-family, system-ui, -apple-system, sans-serif);font-size:var(--qaid-font-size, 16px);animation:qaid-fadeIn .2s ease-out}.qaid-annotate-title{margin:0;font-size:1rem;font-weight:700;text-align:center;color:#f9fafb}.qaid-annotate-desc{margin:0;font-size:.8125rem;line-height:1.4;text-align:center;color:#f9fafbbf}.qaid-annotate-stage{flex:1 1 auto;min-height:0;display:flex;align-items:center;justify-content:center;overflow:auto}.qaid-annotate-canvas{max-width:100%;max-height:100%;object-fit:contain;background:light-dark(#ffffff,#111827);border-radius:.5rem;box-shadow:0 10px 30px #00000080;touch-action:none;cursor:crosshair}.qaid-annotate-toolbar{display:flex;flex-wrap:wrap;gap:.75rem;align-items:center;justify-content:center;padding:.5rem .75rem;background:light-dark(#ffffff,#1f2937);border-radius:.75rem;box-shadow:0 10px 30px light-dark(rgba(0,0,0,.25),rgba(0,0,0,.5))}.qaid-annotate-tools,.qaid-annotate-actions{display:flex;gap:.375rem;align-items:center}button.qaid-annotate-tool,button.qaid-annotate-action{display:inline-flex;align-items:center;justify-content:center;gap:.375rem;min-width:44px;min-height:44px;padding:0 .75rem;border:1px solid light-dark(#d1d5db,#374151);border-radius:.5rem;background:light-dark(#f9fafb,#111827);color:light-dark(#1f2937,#f9fafb);font-family:inherit;font-size:.8125rem;font-weight:600;cursor:pointer;-webkit-appearance:none;appearance:none;transition:background-color .15s,color .15s,border-color .15s,filter .15s}button.qaid-annotate-tool svg,button.qaid-annotate-action svg{width:20px;height:20px}.qaid-annotate-colors{display:flex;gap:.25rem;align-items:center}button.qaid-annotate-swatch{width:28px;height:28px;min-width:28px;padding:0;border-radius:9999px;border:2px solid light-dark(#d1d5db,#4b5563);background:var(--qaid-swatch, #000);cursor:pointer;-webkit-appearance:none;appearance:none;transition:transform .1s,box-shadow .15s,border-color .15s}button.qaid-annotate-swatch:hover{transform:scale(1.12)}button.qaid-annotate-swatch[aria-pressed=true]{border-color:light-dark(#111827,#f9fafb);box-shadow:0 0 0 2px var(--qaid-swatch)}@media(forced-colors:active){button.qaid-annotate-swatch[aria-pressed=true]{outline:2px solid Highlight;outline-offset:1px}}button.qaid-annotate-tool:hover,button.qaid-annotate-action:hover{background:light-dark(#eef2ff,#312e81)}button.qaid-annotate-tool[aria-pressed=true],button.qaid-annotate-done{background:var(--qaid-marker, #6366f1);color:var(--qaid-marker-text, #fff);border-color:var(--qaid-marker, #6366f1)}button.qaid-annotate-done:hover{filter:brightness(.9);background:var(--qaid-marker, #6366f1)}button.qaid-annotate-action:disabled{opacity:.45;cursor:not-allowed}@media(forced-colors:active){button.qaid-annotate-tool[aria-pressed=true],button.qaid-annotate-done{border:2px solid Highlight}.qaid-annotate-canvas{border:1px solid CanvasText}}button:focus-visible,textarea:focus-visible,a:focus-visible,[tabindex]:focus-visible,[role=button]:focus-visible{outline:2px solid var(--qaid-marker, #6366f1);outline-offset:2px}.qaid-textarea:focus-visible,button.qaid-btn-submit:focus-visible,.qaid-video-preview-box textarea:focus-visible{outline:2px solid var(--qaid-marker, #6366f1);outline-offset:2px}.qaid-buttons.qaid-incognito:focus-within{opacity:1}.qaid-buttons:focus-within .qaid-dismiss-btn,.qaid-dismiss-btn:focus-visible{opacity:1}.qaid-dismiss-btn{min-width:24px;min-height:24px}button.qaid-recording-stop{min-height:24px}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation:none!important;transition:none!important;scroll-behavior:auto!important}}@media(forced-colors:active){button:focus-visible,textarea:focus-visible,a:focus-visible,[tabindex]:focus-visible,[role=button]:focus-visible,.qaid-textarea:focus,button.qaid-btn-submit:focus,.qaid-video-preview-box textarea:focus{outline:2px solid CanvasText;outline-offset:2px}.qaid-selected-marker,.qaid-highlight-box{border-color:Highlight}}", Jt = "body.qaid-targeting,body.qaid-targeting *{cursor:none!important}";
24
+ let I = 0;
25
+ const Zt = "button.qaid-btn{width:var(--qaid-btn-size);height:var(--qaid-btn-size);border-radius:50%;border:none;background:#f3f4f6;color:#374151;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -1px rgba(0,0,0,.06);transition:background-color .2s,color .2s,transform .2s;-webkit-appearance:none;appearance:none;--qaid-hover-up-bg:var(--qaid-positive);--qaid-hover-up-color:#fff;--qaid-hover-down-bg:var(--qaid-negative);--qaid-hover-down-color:#fff}button.qaid-btn:hover{transform:scale(1.05)}button.qaid-btn-up:hover{background:var(--qaid-hover-up-bg);color:var(--qaid-hover-up-color)}button.qaid-btn-down:hover{background:var(--qaid-hover-down-bg);color:var(--qaid-hover-down-color)}button.qaid-btn-feedback:hover{background:var(--qaid-marker,#6366f1);color:var(--qaid-marker-text,#fff)}button.qaid-btn svg{width:var(--qaid-icon-size);height:var(--qaid-icon-size)}", te = {
26
+ small: 36,
27
+ medium: 48,
28
+ large: 64
29
+ }, ee = {
30
+ small: 18,
31
+ medium: 24,
32
+ large: 32
33
+ };
34
+ function ie(i, t, e) {
35
+ const [o, n, s] = [i, t, e].map((a) => (a = a / 255, a <= 0.03928 ? a / 12.92 : Math.pow((a + 0.055) / 1.055, 2.4)));
36
+ return 0.2126 * o + 0.7152 * n + 0.0722 * s;
37
+ }
38
+ function oe(i) {
39
+ if (i.startsWith("#")) {
40
+ const e = i.slice(1), o = e.length === 3 ? e.split("").map((s) => s + s).join("") : e, n = parseInt(o, 16);
41
+ return {
42
+ r: n >> 16 & 255,
43
+ g: n >> 8 & 255,
44
+ b: n & 255
45
+ };
46
+ }
47
+ const t = i.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
48
+ return t ? {
49
+ r: parseInt(t[1]),
50
+ g: parseInt(t[2]),
51
+ b: parseInt(t[3])
52
+ } : null;
53
+ }
54
+ function ne(i) {
55
+ const t = oe(i);
56
+ return t && ie(t.r, t.g, t.b) > 0.4 ? "black" : "white";
57
+ }
58
+ function se(i = {}) {
59
+ const {
60
+ positiveColor: t = "rgb(0, 200, 83)",
61
+ negativeColor: e = "rgb(255, 0, 0)",
62
+ markerColor: o = "#6366f1",
63
+ buttonSize: n = "medium",
64
+ modalWidth: s = 400,
65
+ backdropOpacity: a = 0.3,
66
+ fontFamily: c = "system-ui, -apple-system, sans-serif",
67
+ fontSize: l = 16
68
+ } = i, d = te[n], r = ee[n], h = ne(o);
69
+ return {
70
+ "--qaid-positive": t,
71
+ "--qaid-negative": e,
72
+ "--qaid-marker": o,
73
+ "--qaid-marker-text": h,
74
+ "--qaid-btn-size": `${d}px`,
75
+ "--qaid-icon-size": `${r}px`,
76
+ "--qaid-modal-width": `${s}px`,
77
+ "--qaid-backdrop-opacity": String(a),
78
+ "--qaid-font-family": c,
79
+ "--qaid-font-size": `${l}px`
80
+ };
81
+ }
82
+ function ae(i, t) {
83
+ for (const [e, o] of Object.entries(t))
84
+ i.style.setProperty(e, o);
85
+ }
86
+ function Y() {
87
+ return Gt + Zt;
88
+ }
89
+ function re() {
90
+ if (I++, I > 1) return;
91
+ const i = document.createElement("style");
92
+ i.id = "qaid-styles", i.textContent = Jt, document.head.appendChild(i);
93
+ }
94
+ function de() {
95
+ I <= 0 || (I--, I === 0 && document.getElementById("qaid-styles").remove());
96
+ }
97
+ const Q = "data-qaid-a11y-live", ce = "position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;", le = [
98
+ "a[href]",
99
+ "button",
100
+ "input",
101
+ "textarea",
102
+ "select",
103
+ "[tabindex]"
104
+ ].join(",");
105
+ function he(i) {
106
+ return i.ownerDocument || document;
107
+ }
108
+ function ue(i, t) {
109
+ const e = t ? "assertive" : "polite", o = i.querySelector(
110
+ `[${Q}="${e}"]`
111
+ );
112
+ if (o) return o;
113
+ const s = he(i).createElement("div");
114
+ return s.setAttribute(Q, e), s.setAttribute("role", t ? "alert" : "status"), s.setAttribute("aria-live", t ? "assertive" : "polite"), s.setAttribute("aria-atomic", "true"), s.style.cssText = ce, i.appendChild(s), s;
115
+ }
116
+ function pe(i, t, e = {}) {
117
+ const o = ue(i, !!e.assertive);
118
+ o.textContent = "", o.textContent = t;
119
+ }
120
+ function fe(i) {
121
+ return i.hasAttribute("disabled") ? !0 : i.disabled === !0;
122
+ }
123
+ function be(i) {
124
+ let t = i;
125
+ for (; t; ) {
126
+ if (t.hasAttribute("hidden")) return !0;
127
+ const e = t.style;
128
+ if (e && (e.display === "none" || e.visibility === "hidden" || e.visibility === "collapse"))
129
+ return !0;
130
+ t = t.parentElement;
131
+ }
132
+ return !1;
133
+ }
134
+ function G(i) {
135
+ return Array.from(
136
+ i.querySelectorAll(le)
137
+ ).filter((e) => !(e.getAttribute("tabindex") === "-1" || e instanceof HTMLInputElement && e.type === "hidden" || e instanceof HTMLAnchorElement && !e.getAttribute("href") || fe(e) || be(e)));
138
+ }
139
+ function ft(i) {
140
+ let t = i.activeElement;
141
+ for (; t && t.shadowRoot && t.shadowRoot.activeElement; )
142
+ t = t.shadowRoot.activeElement;
143
+ return t instanceof HTMLElement ? t : null;
144
+ }
145
+ function ge(i) {
146
+ const t = i.ownerDocument || document;
147
+ let e = !1;
148
+ const o = (s) => {
149
+ if (s.key !== "Tab") return;
150
+ const a = G(i);
151
+ if (a.length === 0) {
152
+ s.preventDefault(), i.focus();
153
+ return;
154
+ }
155
+ const c = a[0], l = a[a.length - 1], d = ft(t), r = d ? a.indexOf(d) !== -1 : !1;
156
+ s.shiftKey ? (!r || d === c) && (s.preventDefault(), l.focus()) : (!r || d === l) && (s.preventDefault(), c.focus());
157
+ };
158
+ i.addEventListener("keydown", o);
159
+ const n = G(i);
160
+ return n.length > 0 ? n[0].focus() : (i.hasAttribute("tabindex") || (i.setAttribute("tabindex", "-1"), e = !0), i.focus()), {
161
+ release() {
162
+ i.removeEventListener("keydown", o), e && (i.removeAttribute("tabindex"), e = !1);
163
+ }
164
+ };
165
+ }
166
+ function me() {
167
+ return ft(document);
168
+ }
169
+ function ve(i) {
170
+ if (!(!i || typeof i.focus != "function"))
171
+ try {
172
+ i.focus();
173
+ } catch {
174
+ }
175
+ }
176
+ function ye(i, t = {}) {
177
+ i.setAttribute("role", "dialog"), i.setAttribute("aria-modal", "true"), t.labelledbyId && i.setAttribute("aria-labelledby", t.labelledbyId), t.describedbyId && i.setAttribute("aria-describedby", t.describedbyId), t.label && i.setAttribute("aria-label", t.label);
178
+ }
179
+ function we(i, t) {
180
+ let e = i;
181
+ for (; e && e !== t; ) {
182
+ const o = e.parentNode;
183
+ if (o === t) return e;
184
+ if (o && o.host) {
185
+ e = o.host;
186
+ continue;
187
+ }
188
+ if (!o) return null;
189
+ e = o;
190
+ }
191
+ return null;
192
+ }
193
+ function ke(i) {
194
+ const e = (i.ownerDocument || document).body;
195
+ if (!e) return () => {
196
+ };
197
+ const o = we(i, e), n = [];
198
+ return Array.from(e.children).forEach((s) => {
199
+ if (!(s instanceof HTMLElement) || o && s === o) return;
200
+ const a = s.inert === !0, c = s.getAttribute("aria-hidden");
201
+ a && c === "true" || (s.inert = !0, s.setAttribute("aria-hidden", "true"), n.push({ el: s, prevInert: a, prevAriaHidden: c }));
202
+ }), function() {
203
+ for (; n.length; ) {
204
+ const a = n.pop();
205
+ a.el.inert = a.prevInert, a.prevAriaHidden === null ? a.el.removeAttribute("aria-hidden") : a.el.setAttribute("aria-hidden", a.prevAriaHidden);
206
+ }
207
+ };
208
+ }
209
+ const bt = {
210
+ width: 400,
211
+ height: 280,
212
+ arrowHeight: 12,
213
+ gap: 8,
214
+ viewportPadding: 16
215
+ };
216
+ function xe(i, t, e, o, n) {
217
+ const s = i.y, a = t - (i.y + i.height);
218
+ if (a >= e + o)
219
+ return {
220
+ top: i.y + i.height + o,
221
+ position: "below"
222
+ };
223
+ if (s >= e + o)
224
+ return {
225
+ top: i.y - e - o,
226
+ position: "above"
227
+ };
228
+ const c = a > s ? "below" : "above";
229
+ let l;
230
+ return c === "below" ? l = Math.min(
231
+ i.y + i.height + o,
232
+ t - e - n
233
+ ) : l = Math.max(n, i.y - e - o), { top: l, position: c };
234
+ }
235
+ function qe(i, t, e, o) {
236
+ let s = i.x + i.width / 2 - e / 2;
237
+ return s = Math.max(o, Math.min(s, t - e - o)), s;
238
+ }
239
+ function Ce(i, t, e, o = 24, n = 24) {
240
+ const a = i.clickX - t - n / 2;
241
+ return Math.max(o, Math.min(a, e - o - n / 2));
242
+ }
243
+ function Se(i, t, e, o = bt) {
244
+ const n = o.height + o.arrowHeight, s = xe(
245
+ i,
246
+ e,
247
+ n,
248
+ o.gap,
249
+ o.viewportPadding
250
+ ), a = qe(
251
+ i,
252
+ t,
253
+ o.width,
254
+ o.viewportPadding
255
+ );
256
+ return {
257
+ top: s.top,
258
+ left: a,
259
+ position: s.position
260
+ };
261
+ }
262
+ function Ee(i, t, e, o = {}) {
263
+ const n = o.gap ?? 8;
264
+ let s = i.bottom + n;
265
+ s + t.height > e.height - n && (s = i.top - t.height - n);
266
+ let a = i.left;
267
+ return a < n ? a = n : a + t.width > e.width - n && (a = e.width - t.width - n), s < n ? s = n : s + t.height > e.height - n && (s = e.height - t.height - n), { top: s, left: a };
268
+ }
269
+ function Te(i, t, e, o = bt) {
270
+ const n = Se(i, t, e, o), s = Ce(
271
+ i,
272
+ n.left,
273
+ o.width
274
+ );
275
+ return {
276
+ modal: n,
277
+ arrow: { left: s }
278
+ };
279
+ }
280
+ const Ae = 20;
281
+ function Le(i) {
282
+ const t = [], e = console.error, o = console.warn, n = console.log, s = (a, c) => {
283
+ const l = {
284
+ message: c.map((d) => String(d)).join(" "),
285
+ timestamp: Date.now(),
286
+ level: a
287
+ };
288
+ t.length >= Ae && t.shift(), t.push(l), i && i(l);
289
+ };
290
+ return console.error = function(...a) {
291
+ s("error", a), e.apply(console, a);
292
+ }, console.warn = function(...a) {
293
+ s("warn", a), o.apply(console, a);
294
+ }, console.log = function(...a) {
295
+ s("log", a), n.apply(console, a);
296
+ }, {
297
+ errors: t,
298
+ restore: () => {
299
+ console.error = e, console.warn = o, console.log = n;
300
+ }
301
+ };
302
+ }
303
+ function q(i, t, e) {
304
+ const o = document.createElement(i);
305
+ if (t)
306
+ for (const [n, s] of Object.entries(t))
307
+ n === "className" ? o.className = s : o.setAttribute(n, s);
308
+ return o;
309
+ }
310
+ function Ie(i = 640) {
311
+ return typeof window < "u" && window.innerWidth < i;
312
+ }
313
+ function J(i, t, e) {
314
+ const o = e.map((s) => s.style.visibility);
315
+ e.forEach((s) => s.style.visibility = "hidden");
316
+ const n = document.elementFromPoint(i, t);
317
+ return e.forEach((s, a) => s.style.visibility = o[a]), n;
318
+ }
319
+ function M(i) {
320
+ return i ? i.hasAttribute("data-qaid-embed") || i.hasAttribute("data-qaid-embed-overlay") ? !0 : !!i.closest("[data-qaid-embed], [data-qaid-embed-overlay]") : !1;
321
+ }
322
+ function Z(i, t = 0) {
323
+ const e = i.getBoundingClientRect();
324
+ return {
325
+ x: e.left - t,
326
+ y: e.top - t,
327
+ width: e.width + t * 2,
328
+ height: e.height + t * 2
329
+ };
330
+ }
331
+ const De = "https://unpkg.com/@qaiddev/quests-embed@1/dist/qaid-quests.js", Me = (i) => (
332
+ // The URL is a runtime value, not a static specifier — keep Vite from
333
+ // trying to analyze/bundle it.
334
+ import(
335
+ /* @vite-ignore */
336
+ i
337
+ )
338
+ );
339
+ let Re = Me, A = null, _ = null;
340
+ function Pe(i) {
341
+ return A && _ === i || (_ = i, A = Promise.resolve(Re(i)).then((t) => {
342
+ const e = t;
343
+ if (!e || typeof e.QaidQuests != "function")
344
+ throw new Error("quests module has no QaidQuests export");
345
+ return e;
346
+ }).catch((t) => {
347
+ throw A = null, _ = null, t;
348
+ })), A;
349
+ }
350
+ async function Be(i) {
351
+ const t = await Pe(i.moduleUrl), e = i.base.replace(/\/+$/, ""), o = i.feedbackId != null ? { feedbackId: i.feedbackId } : void 0;
352
+ return new t.QaidQuests({
353
+ endpoint: `${e}/responses`,
354
+ configUrl: `${e}/${encodeURIComponent(i.questId)}/definition`,
355
+ apiKey: i.apiKey || void 0,
356
+ metadata: o,
357
+ onClose: i.onClose
358
+ });
359
+ }
360
+ const tt = "qaid_visitor_id", et = "qaid_hide_feedback", He = [
361
+ "#ef4444",
362
+ "#f59e0b",
363
+ "#22c55e",
364
+ "#3b82f6",
365
+ "#111827",
366
+ "#ffffff"
367
+ ];
368
+ function Oe() {
369
+ return typeof window.matchMedia == "function" && window.matchMedia("(pointer: coarse)").matches;
370
+ }
371
+ function U(i) {
372
+ return i ? `${et}_${i}` : et;
373
+ }
374
+ function $e(i) {
375
+ try {
376
+ return localStorage.getItem(U(i)) === "1";
377
+ } catch {
378
+ return !1;
379
+ }
380
+ }
381
+ function it(i, t = !0) {
382
+ try {
383
+ t ? localStorage.setItem(U(i), "1") : localStorage.removeItem(U(i));
384
+ } catch {
385
+ }
386
+ }
387
+ function ze() {
388
+ try {
389
+ let i = localStorage.getItem(tt);
390
+ return i || (i = crypto.randomUUID(), localStorage.setItem(tt, i)), i;
391
+ } catch {
392
+ return crypto.randomUUID();
393
+ }
394
+ }
395
+ class _e {
396
+ config;
397
+ state = "IDLE";
398
+ feedbackData = {
399
+ feedbackType: null,
400
+ elementSelector: null,
401
+ elementText: null,
402
+ consoleErrors: []
403
+ };
404
+ selectedBounds = {
405
+ x: 0,
406
+ y: 0,
407
+ width: 0,
408
+ height: 0,
409
+ clickX: 0,
410
+ clickY: 0,
411
+ visible: !1
412
+ };
413
+ feedbackId = null;
414
+ // A quest launched from a button (in place of the message box), if any.
415
+ activeQuest = null;
416
+ isMobile = !1;
417
+ visitorId;
418
+ // Console capture
419
+ consoleCapture = null;
420
+ // Video recording — the whole subsystem lives in a lazily-loaded chunk
421
+ // (recording.ts); a thumbs-only visitor never downloads it.
422
+ recording = null;
423
+ // Element targeting — also a lazily-loaded chunk (targeting.ts).
424
+ targeting = null;
425
+ targetingPrewarmed = !1;
426
+ // Preload-on-intent: warm the lazily-split feature chunks before first use.
427
+ prewarmHandle = null;
428
+ prewarmIsTimeout = !1;
429
+ videoPrewarmed = !1;
430
+ screenshotPrewarmed = !1;
431
+ // Shadow DOM
432
+ shadowHost = null;
433
+ shadowRoot = null;
434
+ // Overlay shadow DOM (always on document.body for full-page coverage)
435
+ overlayShadowHost = null;
436
+ overlayShadowRoot = null;
437
+ // DOM elements (inside shadow root)
438
+ buttonsContainer = null;
439
+ isUserProvidedContainer = !1;
440
+ // The message modal lives in a lazily-loaded chunk (modal.ts).
441
+ modal = null;
442
+ modalPrewarmed = !1;
443
+ dismissBtn = null;
444
+ // Per-instance CSS variables
445
+ cssVars = {};
446
+ activeThumbBtn = null;
447
+ // Tracks whether the pending activation came from the keyboard (Enter/Space)
448
+ // rather than a pointer, so targeting can avoid elementFromPoint(0,0).
449
+ keyboardActivation = !1;
450
+ // Accessibility: dialog focus management
451
+ dialogTrigger = null;
452
+ dialogTrap = null;
453
+ dialogRestoreInert = null;
454
+ // Unique id suffix for aria-labelledby/describedby references
455
+ uid = Math.random().toString(36).slice(2, 9);
456
+ // Bound event handlers
457
+ boundKeyDown;
458
+ boundResize;
459
+ // Start with buttons slid off-screen (localStorage dismiss, no animation)
460
+ _startDismissed = !1;
461
+ // DOM persistence (survives framework client-side navigation)
462
+ destroyed = !1;
463
+ domObserver = null;
464
+ boundBeforeSwap = null;
465
+ constructor(t) {
466
+ this.config = {
467
+ endpoint: t.endpoint,
468
+ apiKey: t.apiKey ?? "",
469
+ container: t.container ?? "",
470
+ buttonClass: t.buttonClass ?? "",
471
+ direction: t.direction ?? "horizontal",
472
+ position: t.position ?? "bottom-right",
473
+ offset: {
474
+ x: t.offset?.x ?? 16,
475
+ y: t.offset?.y ?? 16
476
+ },
477
+ zIndex: t.zIndex ?? 50,
478
+ skipTargeting: t.skipTargeting ?? !1,
479
+ singleButton: t.singleButton ?? !1,
480
+ feedbackMode: t.feedbackMode ?? "target",
481
+ colors: {
482
+ positive: t.colors?.positive ?? "rgb(0, 200, 83)",
483
+ negative: t.colors?.negative ?? "rgb(255, 0, 0)",
484
+ marker: t.colors?.marker ?? "#6366f1"
485
+ },
486
+ buttonSize: t.buttonSize ?? "medium",
487
+ text: {
488
+ tooltip: t.text?.tooltip ?? "",
489
+ modalTitle: t.text?.modalTitle ?? "Thank you for your feedback!",
490
+ modalSubtitle: t.text?.modalSubtitle ?? "Would you like to add a message to help us understand your feedback better?",
491
+ placeholder: t.text?.placeholder ?? "Optional: Tell us more about your experience...",
492
+ submitButton: t.text?.submitButton ?? "Submit",
493
+ skipButton: t.text?.skipButton ?? "Skip",
494
+ positiveLabel: t.text?.positiveLabel ?? "Send positive feedback",
495
+ negativeLabel: t.text?.negativeLabel ?? "Send negative feedback",
496
+ recordLabel: t.text?.recordLabel ?? "Record a screen recording",
497
+ dismissLabel: t.text?.dismissLabel ?? "Hide Feedback",
498
+ feedbackLabel: t.text?.feedbackLabel ?? "Send feedback"
499
+ },
500
+ modalWidth: t.modalWidth ?? 400,
501
+ backdropOpacity: t.backdropOpacity ?? 0.3,
502
+ fontFamily: t.fontFamily ?? "system-ui, -apple-system, sans-serif",
503
+ fontSize: t.fontSize ?? 16,
504
+ captureScreenshot: t.captureScreenshot ?? !1,
505
+ annotate: t.annotate ?? !0,
506
+ annotationColor: t.annotationColor ?? t.colors?.marker ?? "#6366f1",
507
+ annotationPalette: t.annotationPalette ?? He,
508
+ screenshotMethod: t.screenshotMethod ?? "permission",
509
+ screenshotOptions: {
510
+ quality: t.screenshotOptions?.quality ?? 0.8,
511
+ maxWidth: t.screenshotOptions?.maxWidth ?? 1280,
512
+ maxHeight: t.screenshotOptions?.maxHeight ?? 800
513
+ },
514
+ incognito: t.incognito ?? !1,
515
+ hideDismiss: t.hideDismiss ?? !1,
516
+ positiveIcon: t.positiveIcon ?? "",
517
+ negativeIcon: t.negativeIcon ?? "",
518
+ feedbackIcon: t.feedbackIcon ?? "",
519
+ hideThumbs: t.hideThumbs ?? !1,
520
+ css: t.css ?? "",
521
+ captureVideo: t.captureVideo ?? !1,
522
+ videoOptions: {
523
+ maxDuration: t.videoOptions?.maxDuration ?? 15,
524
+ redaction: t.videoOptions?.redaction ?? !1
525
+ },
526
+ recordIcon: t.recordIcon ?? "",
527
+ quests: {
528
+ base: t.quests?.base ?? "",
529
+ up: t.quests?.up ?? "",
530
+ down: t.quests?.down ?? "",
531
+ video: t.quests?.video ?? "",
532
+ // Quest service reuses the feedback API key unless overridden.
533
+ apiKey: t.quests?.apiKey ?? t.apiKey ?? "",
534
+ moduleUrl: t.quests?.moduleUrl ?? De
535
+ }
536
+ }, this.boundKeyDown = this.handleKeyDown.bind(this), this.boundResize = this.handleResize.bind(this), this.visitorId = ze(), this.init();
537
+ }
538
+ applyVars(t) {
539
+ ae(t, this.cssVars);
540
+ }
541
+ /**
542
+ * Announce a message via the shared visually-hidden live regions.
543
+ * Prefer the overlay shadow root (which hosts every transient surface and
544
+ * is never inerted by its own dialogs) so announcements are not suppressed
545
+ * while a dialog aria-hides the main button host.
546
+ */
547
+ announceMsg(t, e = !1) {
548
+ const o = this.overlayShadowRoot ?? this.shadowRoot;
549
+ o && pe(o, t, { assertive: e });
550
+ }
551
+ /**
552
+ * Turn a transient surface into an accessible modal dialog: save the
553
+ * invoking control, apply dialog semantics, trap focus, and inert the
554
+ * background. Paired with closeDialogA11y() on every close path.
555
+ */
556
+ openDialogA11y(t, e) {
557
+ this.dialogTrigger = me(), ye(t, e), this.dialogTrap = ge(t), this.dialogRestoreInert = ke(t);
558
+ }
559
+ closeDialogA11y() {
560
+ this.dialogTrap?.release(), this.dialogTrap = null, this.dialogRestoreInert && (this.dialogRestoreInert(), this.dialogRestoreInert = null), ve(this.dialogTrigger), this.dialogTrigger = null;
561
+ }
562
+ clearActiveThumb() {
563
+ this.activeThumbBtn && (this.activeThumbBtn.setAttribute("aria-pressed", "false"), this.activeThumbBtn = null);
564
+ }
565
+ init() {
566
+ re(), this.cssVars = se({
567
+ positiveColor: this.config.colors.positive,
568
+ negativeColor: this.config.colors.negative,
569
+ markerColor: this.config.colors.marker,
570
+ buttonSize: this.config.buttonSize,
571
+ modalWidth: this.config.modalWidth,
572
+ backdropOpacity: this.config.backdropOpacity,
573
+ fontFamily: this.config.fontFamily,
574
+ fontSize: this.config.fontSize
575
+ }), !this.config.hideDismiss && $e(this.config.apiKey) && (this._startDismissed = !0), this.checkMobile(), window.addEventListener("resize", this.boundResize), this.createEmbed(), this.consoleCapture = Le((t) => {
576
+ this.feedbackData.consoleErrors = this.consoleCapture?.errors ?? [];
577
+ }), this.feedbackData.consoleErrors = this.consoleCapture.errors, this.observeDom(), (this.config.captureVideo && this.videoSupported() || this.config.captureScreenshot) && this.schedulePrewarm(() => {
578
+ this.config.captureVideo && this.videoSupported() && this.prewarmVideo(), this.config.captureScreenshot && this.prewarmScreenshot();
579
+ });
580
+ }
581
+ /**
582
+ * Best-effort preload of a lazily-split feature chunk so its first use is
583
+ * instant. Warming fetches + compiles (and defines) the module; the feature
584
+ * modules have no load-time side effects, so this is safe. Errors are
585
+ * swallowed — a failed preload just falls back to an on-demand load.
586
+ */
587
+ prewarmVideo() {
588
+ this.videoPrewarmed || (this.videoPrewarmed = !0, Promise.resolve().then(() => ct).catch(() => {
589
+ }), Promise.resolve().then(() => wt).catch(() => {
590
+ }));
591
+ }
592
+ prewarmScreenshot() {
593
+ this.screenshotPrewarmed || (this.screenshotPrewarmed = !0, (this.shouldCaptureViaDom() ? Promise.resolve().then(() => at) : Promise.resolve().then(() => lt)).catch(() => {
594
+ }), this.config.annotate && Promise.resolve().then(() => dt).catch(() => {
595
+ }));
596
+ }
597
+ /** Run fn when the main thread is idle; cancelled by destroy(). */
598
+ schedulePrewarm(t) {
599
+ const e = window, o = () => {
600
+ this.destroyed || t();
601
+ };
602
+ typeof e.requestIdleCallback == "function" ? (this.prewarmIsTimeout = !1, this.prewarmHandle = e.requestIdleCallback(o, { timeout: 2e3 })) : (this.prewarmIsTimeout = !0, this.prewarmHandle = window.setTimeout(o, 1200));
603
+ }
604
+ /**
605
+ * Watch for the shadow hosts being removed from the DOM by framework
606
+ * client-side navigation (e.g. Astro View Transitions swapping <body>
607
+ * contents, or any SPA router that replaces DOM subtrees). If the host
608
+ * is disconnected and destroy() wasn't called, re-append it.
609
+ *
610
+ * Also hooks into Astro's `astro:before-swap` when available, which
611
+ * lets us carry elements into the new document before the swap happens
612
+ * (avoids a flash of the widget disappearing and reappearing).
613
+ */
614
+ observeDom() {
615
+ this.boundBeforeSwap = (t) => {
616
+ const e = t.newDocument;
617
+ !e || this.destroyed || (this.shadowHost && e.body.appendChild(this.shadowHost), this.overlayShadowHost && e.body.appendChild(this.overlayShadowHost));
618
+ }, document.addEventListener("astro:before-swap", this.boundBeforeSwap), this.domObserver = new MutationObserver(() => {
619
+ this.destroyed || (this.shadowHost && !this.shadowHost.isConnected && document.body.appendChild(this.shadowHost), this.overlayShadowHost && !this.overlayShadowHost.isConnected && document.body.appendChild(this.overlayShadowHost));
620
+ }), this.domObserver.observe(document.body, { childList: !0 });
621
+ }
622
+ checkMobile() {
623
+ this.isMobile = Ie();
624
+ }
625
+ handleResize() {
626
+ this.checkMobile();
627
+ }
628
+ createEmbed() {
629
+ this.shadowHost = document.createElement("div"), this.shadowHost.setAttribute("data-qaid-embed", ""), this.shadowHost.style.position = "static", this.shadowHost.style.display = "contents";
630
+ let t = null;
631
+ this.config.container && (t = document.querySelector(this.config.container)), t ? (t.appendChild(this.shadowHost), this.isUserProvidedContainer = !0, this.config.hideDismiss = !0) : (this.shadowHost.style.position = "fixed", this.shadowHost.style.display = "block", this.shadowHost.style.inset = "0", this.shadowHost.style.pointerEvents = "none", this.shadowHost.style.zIndex = String(this.config.zIndex), document.body.appendChild(this.shadowHost)), this.shadowRoot = this.shadowHost.attachShadow({ mode: "open" });
632
+ const e = document.createElement("style");
633
+ if (e.textContent = Y(), this.shadowRoot.appendChild(e), this.config.css) {
634
+ const d = document.createElement("style");
635
+ d.textContent = this.config.css, this.shadowRoot.appendChild(d);
636
+ }
637
+ const o = this.config.direction === "vertical" ? "qaid-vertical" : "";
638
+ if (this.isUserProvidedContainer)
639
+ this.buttonsContainer = document.createElement("div"), this.buttonsContainer.className = `qaid-buttons qaid-${this.config.position} ${o}`.trim(), this.config.incognito && this.buttonsContainer.classList.add("qaid-incognito");
640
+ else {
641
+ this.buttonsContainer = document.createElement("div"), this.buttonsContainer.className = `qaid-buttons qaid-auto-container qaid-${this.config.position} ${o}${this.config.incognito ? " qaid-incognito" : ""}`.trim(), this.buttonsContainer.style.pointerEvents = "auto";
642
+ const { x: d, y: r } = this.config.offset;
643
+ this.config.position.includes("right") ? this.buttonsContainer.style.right = `${d}px` : this.buttonsContainer.style.left = `${d}px`, this.config.position.includes("bottom") ? this.buttonsContainer.style.bottom = `${r}px` : this.buttonsContainer.style.top = `${r}px`;
644
+ }
645
+ this.applyVars(this.buttonsContainer), this.shadowRoot.appendChild(this.buttonsContainer), this.buttonsContainer.addEventListener("keydown", (d) => {
646
+ (d.key === "Enter" || d.key === " " || d.key === "Spacebar") && (this.keyboardActivation = !0);
647
+ }), this.buttonsContainer.addEventListener("mousedown", () => {
648
+ this.keyboardActivation = !1;
649
+ }), this.buttonsContainer.addEventListener("pointerdown", () => {
650
+ this.keyboardActivation = !1;
651
+ });
652
+ const n = !!this.config.buttonClass, s = n ? `qaid-btn-structural ${this.config.buttonClass}` : "qaid-btn", c = this.config.text.tooltip || "Feedback for us?", l = document.createElement("div");
653
+ if (l.className = "qaid-tooltip-text", l.textContent = c, this.applyVars(l), this.shadowRoot.appendChild(l), this.tooltipElement = l, this.config.singleButton) {
654
+ const d = document.createElement("div");
655
+ d.className = "qaid-tooltip-wrapper";
656
+ const r = document.createElement("button");
657
+ r.type = "button", r.className = n ? `${s} qaid-btn-feedback` : "qaid-btn qaid-btn-feedback", r.setAttribute("aria-label", this.config.text.feedbackLabel), r.innerHTML = this.config.feedbackIcon || pt, r.addEventListener("click", (h) => this.handleThumbClick("neutral", h.currentTarget, h)), r.addEventListener("mouseenter", () => {
658
+ this.prewarmTargeting(), this.showTooltip(r);
659
+ }), r.addEventListener("mouseleave", () => this.hideTooltip()), d.appendChild(r), this.buttonsContainer.appendChild(d);
660
+ } else if (!this.config.hideThumbs) {
661
+ const d = document.createElement("div");
662
+ d.className = "qaid-tooltip-wrapper";
663
+ const r = document.createElement("button");
664
+ r.type = "button", r.className = n ? `${s} qaid-btn-up` : "qaid-btn qaid-btn-up", r.setAttribute("aria-label", this.config.text.positiveLabel), r.innerHTML = this.config.positiveIcon || V, r.addEventListener("click", (g) => this.handleThumbClick("up", g.currentTarget, g)), r.addEventListener("mouseenter", () => {
665
+ this.prewarmTargeting(), this.showTooltip(r);
666
+ }), r.addEventListener("mouseleave", () => this.hideTooltip()), d.appendChild(r);
667
+ const h = document.createElement("div");
668
+ h.className = "qaid-tooltip-wrapper";
669
+ const p = document.createElement("button");
670
+ p.type = "button", p.className = n ? `${s} qaid-btn-down` : "qaid-btn qaid-btn-down", p.setAttribute("aria-label", this.config.text.negativeLabel), p.innerHTML = this.config.negativeIcon || N, p.addEventListener("click", (g) => this.handleThumbClick("down", g.currentTarget, g)), p.addEventListener("mouseenter", () => {
671
+ this.prewarmTargeting(), this.showTooltip(p);
672
+ }), p.addEventListener("mouseleave", () => this.hideTooltip()), h.appendChild(p), this.buttonsContainer.appendChild(d), this.buttonsContainer.appendChild(h);
673
+ }
674
+ if (this.config.captureVideo && this.videoSupported()) {
675
+ const d = document.createElement("div");
676
+ d.className = "qaid-tooltip-wrapper";
677
+ const r = document.createElement("button");
678
+ r.type = "button", r.className = n ? `${s} qaid-btn-record` : "qaid-btn qaid-btn-record", r.setAttribute("aria-label", this.config.text.recordLabel), r.innerHTML = this.config.recordIcon || Nt, r.addEventListener("click", () => {
679
+ this.ensureRecording().then(
680
+ (p) => this.config.videoOptions.redaction ? p.startPicking() : p.startRecording()
681
+ );
682
+ });
683
+ const h = () => this.prewarmVideo();
684
+ r.addEventListener("mouseenter", () => {
685
+ h(), this.showTooltip(r);
686
+ }), r.addEventListener("focus", h), r.addEventListener("touchstart", h, { passive: !0 }), r.addEventListener("mouseleave", () => this.hideTooltip()), d.appendChild(r), this.buttonsContainer.appendChild(d);
687
+ }
688
+ this.config.hideDismiss || (this.dismissBtn = document.createElement("button"), this.dismissBtn.type = "button", this.dismissBtn.className = "qaid-dismiss-btn", this.dismissBtn.setAttribute("aria-label", this.config.text.dismissLabel), this.dismissBtn.title = this.config.text.dismissLabel, this.dismissBtn.innerHTML = '<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2 2l8 8M10 2l-8 8" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>', this.dismissBtn.addEventListener("click", (d) => {
689
+ d.stopPropagation(), this.handleDismiss();
690
+ }), this.buttonsContainer.insertBefore(this.dismissBtn, this.buttonsContainer.firstChild), this._startDismissed && this.buttonsContainer.classList.add("qaid-dismissed"));
691
+ }
692
+ /**
693
+ * Lazily create a separate overlay shadow host on document.body.
694
+ * This host contains all full-page elements (targeting overlay, marker,
695
+ * backdrop, modal, recording indicator, video preview) so they escape
696
+ * clip-path / transform containing blocks in user containers.
697
+ */
698
+ ensureOverlayHost() {
699
+ if (this.overlayShadowRoot) return this.overlayShadowRoot;
700
+ this.overlayShadowHost = document.createElement("div"), this.overlayShadowHost.setAttribute("data-qaid-embed-overlay", ""), this.overlayShadowHost.style.position = "fixed", this.overlayShadowHost.style.inset = "0", this.overlayShadowHost.style.pointerEvents = "none", this.overlayShadowHost.style.zIndex = String(this.config.zIndex), document.body.appendChild(this.overlayShadowHost), this.overlayShadowRoot = this.overlayShadowHost.attachShadow({ mode: "open" });
701
+ const t = document.createElement("style");
702
+ if (t.textContent = Y(), this.overlayShadowRoot.appendChild(t), this.config.css) {
703
+ const e = document.createElement("style");
704
+ e.textContent = this.config.css, this.overlayShadowRoot.appendChild(e);
705
+ }
706
+ return this.overlayShadowRoot;
707
+ }
708
+ tooltipElement = null;
709
+ showTooltip(t) {
710
+ const e = this.tooltipElement;
711
+ e.style.visibility = "hidden", e.classList.add("qaid-tooltip-visible");
712
+ const o = t.getBoundingClientRect(), n = e.getBoundingClientRect(), { top: s, left: a } = Ee(
713
+ o,
714
+ n,
715
+ { width: window.innerWidth, height: window.innerHeight }
716
+ );
717
+ e.style.top = `${s}px`, e.style.left = `${a}px`, e.style.visibility = "visible";
718
+ }
719
+ hideTooltip() {
720
+ this.tooltipElement.classList.remove("qaid-tooltip-visible");
721
+ }
722
+ handleDismiss() {
723
+ this.buttonsContainer.classList.add("qaid-dismissed"), it(this.config.apiKey, !0);
724
+ }
725
+ handleThumbClick(t, e, o) {
726
+ if (this.buttonsContainer?.classList.contains("qaid-incognito") && (this.buttonsContainer.classList.remove("qaid-incognito"), it(this.config.apiKey, !1)), this.prewarmModal(), this.config.skipTargeting || this.config.feedbackMode === "annotate")
727
+ this.submitDirectFeedback(t, e);
728
+ else {
729
+ this.activeThumbBtn = e, e.setAttribute("aria-pressed", "true");
730
+ const n = this.keyboardActivation;
731
+ this.keyboardActivation = !1, this.ensureTargeting().then((s) => {
732
+ this.destroyed || (n ? s.startKeyboard(t) : s.startPointer(t, o));
733
+ });
734
+ }
735
+ }
736
+ submitDirectFeedback(t, e) {
737
+ this.feedbackData.feedbackType = t, this.feedbackData.elementSelector = null, this.feedbackData.elementText = null;
738
+ const o = e.getBoundingClientRect();
739
+ this.selectedBounds = {
740
+ x: o.left,
741
+ y: o.top,
742
+ width: o.width,
743
+ height: o.height,
744
+ clickX: o.left + o.width / 2,
745
+ clickY: o.top + o.height / 2,
746
+ visible: !1
747
+ }, this.submitFeedback();
748
+ }
749
+ handleKeyDown(t) {
750
+ if (t.key === "Escape") {
751
+ if (this.recording?.handleEscape()) return;
752
+ this.state === "TARGETING" ? this.targeting?.cancel() : this.state === "MODAL_OPEN" && this.modal?.close();
753
+ }
754
+ }
755
+ /** Whether to capture the screenshot with the DOM/canvas method (html2canvas)
756
+ * instead of the permission-based Screen Capture API. Explicit "dom" wins;
757
+ * otherwise DOM is used on touch devices to avoid the getDisplayMedia prompt. */
758
+ shouldCaptureViaDom() {
759
+ return this.config.screenshotMethod === "dom" || Oe();
760
+ }
761
+ /**
762
+ * Open the full-screen annotation editor over the captured screenshot,
763
+ * reusing the overlay shadow host and the shared dialog a11y helpers.
764
+ * Resolves with the composited WebP data URL, or null when the user skips
765
+ * (caller keeps the original). Pointer events on the overlay host are
766
+ * enabled while the editor is open and restored on close.
767
+ */
768
+ async openAnnotationEditor(t) {
769
+ const e = this.ensureOverlayHost(), o = this.overlayShadowHost, n = o.style.pointerEvents;
770
+ o.style.pointerEvents = "auto";
771
+ try {
772
+ const { openAnnotationEditor: s } = await Promise.resolve().then(() => dt);
773
+ return await s({
774
+ dataUrl: t,
775
+ root: e,
776
+ quality: this.config.screenshotOptions.quality,
777
+ color: this.config.annotationColor,
778
+ palette: this.config.annotationPalette,
779
+ applyVars: (a) => this.applyVars(a),
780
+ announce: (a, c) => this.announceMsg(a, c),
781
+ openDialog: (a, c) => this.openDialogA11y(a, c),
782
+ closeDialog: () => this.closeDialogA11y()
783
+ });
784
+ } catch (s) {
785
+ return console.error("Annotation editor failed to open:", s), null;
786
+ } finally {
787
+ o.style.pointerEvents = n;
788
+ }
789
+ }
790
+ async submitFeedback() {
791
+ let t = null;
792
+ if (this.config.captureScreenshot || this.config.feedbackMode === "annotate")
793
+ try {
794
+ if (this.shouldCaptureViaDom()) {
795
+ const { captureDomScreenshot: a } = await Promise.resolve().then(() => at);
796
+ t = await a(this.config.screenshotOptions);
797
+ } else {
798
+ const { captureScreenshot: a } = await Promise.resolve().then(() => lt);
799
+ t = await a(this.config.screenshotOptions);
800
+ }
801
+ t && this.announceMsg("Screenshot captured");
802
+ } catch (a) {
803
+ console.error("Screenshot capture failed:", a), t = null;
804
+ }
805
+ t && this.config.annotate && (t = await this.openAnnotationEditor(t) ?? t);
806
+ const o = this.feedbackData.elementSelector ? {
807
+ x: this.selectedBounds.x,
808
+ y: this.selectedBounds.y,
809
+ width: this.selectedBounds.width,
810
+ height: this.selectedBounds.height
811
+ } : null, n = {
812
+ feedbackType: this.feedbackData.feedbackType,
813
+ pageUrl: window.location.href,
814
+ apiKey: this.config.apiKey || void 0,
815
+ elementSelector: this.feedbackData.elementSelector,
816
+ elementText: this.feedbackData.elementText,
817
+ consoleErrors: [...this.feedbackData.consoleErrors],
818
+ screenWidth: window.innerWidth,
819
+ screenHeight: window.innerHeight,
820
+ clickX: this.selectedBounds.clickX,
821
+ clickY: this.selectedBounds.clickY,
822
+ scrollX: window.scrollX,
823
+ scrollY: window.scrollY,
824
+ screenshot: t,
825
+ visitorId: this.visitorId,
826
+ // Include element bounds and user agent for server-side screenshot fallback
827
+ elementBounds: o,
828
+ userAgent: navigator.userAgent
829
+ };
830
+ try {
831
+ const a = await fetch(this.config.endpoint, {
832
+ method: "POST",
833
+ headers: { "Content-Type": "application/json" },
834
+ body: JSON.stringify(n)
835
+ });
836
+ if (a.ok) {
837
+ const c = await a.json();
838
+ this.feedbackId = c.id, this.announceMsg("Feedback sent");
839
+ }
840
+ } catch (a) {
841
+ console.error("Failed to submit feedback:", a), this.announceMsg("Failed to send feedback", !0);
842
+ }
843
+ const s = this.feedbackData.feedbackType;
844
+ if (s && s !== "neutral" && await this.tryLaunchQuest(s, this.feedbackId)) {
845
+ this.resetFeedbackUi();
846
+ return;
847
+ }
848
+ this.state = "MODAL_OPEN";
849
+ try {
850
+ const a = await this.ensureModal();
851
+ if (this.destroyed) return;
852
+ a.open(), this.overlayShadowHost && (this.overlayShadowHost.style.pointerEvents = "auto");
853
+ } catch (a) {
854
+ console.error("Failed to open the feedback modal:", a), this.announceMsg("Thank you for your feedback!", !0), this.resetFeedbackUi();
855
+ }
856
+ }
857
+ /**
858
+ * Quest id linked to `type`, or "" when quest launching is disabled
859
+ * (no `base`) or this button has no quest configured.
860
+ */
861
+ questIdFor(t) {
862
+ const e = this.config.quests;
863
+ return e.base && e[t] || "";
864
+ }
865
+ /**
866
+ * Launch the quest linked to `type`, if any. Resolves `true` when a quest
867
+ * was configured and the widget launched; `false` when no quest is
868
+ * configured or the widget failed to load (caller falls back to its
869
+ * normal UI). The created feedback record id is passed through so the
870
+ * quest response can be joined back to it server-side.
871
+ */
872
+ async tryLaunchQuest(t, e) {
873
+ const o = this.questIdFor(t);
874
+ if (!o) return !1;
875
+ try {
876
+ return this.activeQuest?.destroy(), this.activeQuest = await Be({
877
+ questId: o,
878
+ base: this.config.quests.base,
879
+ apiKey: this.config.quests.apiKey || void 0,
880
+ moduleUrl: this.config.quests.moduleUrl,
881
+ feedbackId: e,
882
+ onClose: () => {
883
+ this.activeQuest = null;
884
+ }
885
+ }), !0;
886
+ } catch (n) {
887
+ return console.error("Failed to launch quest:", n), this.activeQuest = null, !1;
888
+ }
889
+ }
890
+ /**
891
+ * Reset the thumbs targeting/marker UI back to idle without opening or
892
+ * closing the message modal. Shared by closeModal() and the quest-launch
893
+ * path (which bypasses the modal entirely).
894
+ */
895
+ resetFeedbackUi() {
896
+ this.targeting?.hideMarker(), this.overlayShadowHost && (this.overlayShadowHost.style.pointerEvents = "none"), this.state = "IDLE", this.feedbackId = null, this.feedbackData.feedbackType = null, this.feedbackData.elementSelector = null, this.feedbackData.elementText = null, this.selectedBounds.visible = !1;
897
+ }
898
+ /**
899
+ * Lazily load and instantiate the modal controller. The message modal lives
900
+ * in a separate chunk, fetched the first time it opens (and pre-warmed while
901
+ * the user targets an element — see prewarmModal).
902
+ */
903
+ async ensureModal() {
904
+ if (!this.modal) {
905
+ const { ModalController: t } = await Promise.resolve().then(() => ht);
906
+ this.modal = new t(this.makeModalHost());
907
+ }
908
+ return this.modal;
909
+ }
910
+ /** Narrow view of the embed the modal controller talks back through. */
911
+ makeModalHost() {
912
+ const t = this;
913
+ return {
914
+ get config() {
915
+ return t.config;
916
+ },
917
+ get uid() {
918
+ return t.uid;
919
+ },
920
+ get isMobile() {
921
+ return t.isMobile;
922
+ },
923
+ get state() {
924
+ return t.state;
925
+ },
926
+ get boundKeyDown() {
927
+ return t.boundKeyDown;
928
+ },
929
+ get feedbackData() {
930
+ return t.feedbackData;
931
+ },
932
+ get selectedBounds() {
933
+ return t.selectedBounds;
934
+ },
935
+ get feedbackId() {
936
+ return t.feedbackId;
937
+ },
938
+ setFeedbackId: (e) => {
939
+ t.feedbackId = e;
940
+ },
941
+ ensureOverlayHost: () => t.ensureOverlayHost(),
942
+ applyVars: (e) => t.applyVars(e),
943
+ announceMsg: (e, o) => t.announceMsg(e, o),
944
+ openDialogA11y: (e, o) => t.openDialogA11y(e, o),
945
+ closeDialogA11y: () => t.closeDialogA11y(),
946
+ resetFeedbackUi: () => t.resetFeedbackUi()
947
+ };
948
+ }
949
+ /** Warm the modal chunk while the user is targeting, so it opens instantly. */
950
+ prewarmModal() {
951
+ this.modalPrewarmed || (this.modalPrewarmed = !0, Promise.resolve().then(() => ht).catch(() => {
952
+ }));
953
+ }
954
+ /**
955
+ * Lazily load and instantiate the targeting controller. The subsystem (with
956
+ * element-selector) lives in a separate chunk, fetched the first time the
957
+ * user targets — pre-warmed on thumb-button hover (see prewarmTargeting).
958
+ */
959
+ async ensureTargeting() {
960
+ if (!this.targeting) {
961
+ const { TargetingController: t } = await Promise.resolve().then(() => ut);
962
+ this.targeting = new t(this.makeTargetingHost());
963
+ }
964
+ return this.targeting;
965
+ }
966
+ /** Narrow view of the embed the targeting controller talks back through. */
967
+ makeTargetingHost() {
968
+ const t = this;
969
+ return {
970
+ get config() {
971
+ return t.config;
972
+ },
973
+ get cssVars() {
974
+ return t.cssVars;
975
+ },
976
+ get state() {
977
+ return t.state;
978
+ },
979
+ get shadowHost() {
980
+ return t.shadowHost;
981
+ },
982
+ get overlayShadowHost() {
983
+ return t.overlayShadowHost;
984
+ },
985
+ get boundKeyDown() {
986
+ return t.boundKeyDown;
987
+ },
988
+ get feedbackData() {
989
+ return t.feedbackData;
990
+ },
991
+ get selectedBounds() {
992
+ return t.selectedBounds;
993
+ },
994
+ setState: (e) => {
995
+ t.state = e;
996
+ },
997
+ ensureOverlayHost: () => t.ensureOverlayHost(),
998
+ applyVars: (e) => t.applyVars(e),
999
+ announceMsg: (e, o) => t.announceMsg(e, o),
1000
+ clearActiveThumb: () => t.clearActiveThumb(),
1001
+ submitFeedback: () => t.submitFeedback()
1002
+ };
1003
+ }
1004
+ /** Warm the targeting chunk on thumb-button hover, before the click. */
1005
+ prewarmTargeting() {
1006
+ this.targetingPrewarmed || this.config.skipTargeting || (this.targetingPrewarmed = !0, Promise.resolve().then(() => ut).catch(() => {
1007
+ }));
1008
+ }
1009
+ // ==================== Video Recording ====================
1010
+ /**
1011
+ * Cheap synchronous capability check so the record button can render without
1012
+ * pulling in the (lazily-loaded) video subsystem. Mirrors
1013
+ * isVideoRecordingSupported() in video-capture.ts.
1014
+ */
1015
+ videoSupported() {
1016
+ return typeof navigator < "u" && !!navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia == "function" && typeof MediaRecorder < "u";
1017
+ }
1018
+ /**
1019
+ * Lazily load and instantiate the recording controller. The whole recording
1020
+ * subsystem lives in a separate chunk, fetched only the first time the record
1021
+ * button is used (and pre-warmed on hover — see prewarmVideo).
1022
+ */
1023
+ async ensureRecording() {
1024
+ if (!this.recording) {
1025
+ const { RecordingController: t } = await Promise.resolve().then(() => ct);
1026
+ this.recording = new t(this.makeRecordingHost());
1027
+ }
1028
+ return this.recording;
1029
+ }
1030
+ /** Narrow view of the embed the recording controller talks back through. */
1031
+ makeRecordingHost() {
1032
+ const t = this;
1033
+ return {
1034
+ get config() {
1035
+ return t.config;
1036
+ },
1037
+ get visitorId() {
1038
+ return t.visitorId;
1039
+ },
1040
+ get uid() {
1041
+ return t.uid;
1042
+ },
1043
+ get overlayShadowHost() {
1044
+ return t.overlayShadowHost;
1045
+ },
1046
+ get consoleCapture() {
1047
+ return t.consoleCapture;
1048
+ },
1049
+ get boundKeyDown() {
1050
+ return t.boundKeyDown;
1051
+ },
1052
+ get state() {
1053
+ return t.state;
1054
+ },
1055
+ setState: (e) => {
1056
+ t.state = e;
1057
+ },
1058
+ ensureOverlayHost: () => t.ensureOverlayHost(),
1059
+ applyVars: (e) => t.applyVars(e),
1060
+ announceMsg: (e, o) => t.announceMsg(e, o),
1061
+ openDialogA11y: (e, o) => t.openDialogA11y(e, o),
1062
+ closeDialogA11y: () => t.closeDialogA11y(),
1063
+ setButtonsDisabled: (e) => t.setButtonsDisabled(e),
1064
+ tryLaunchQuest: (e, o) => t.tryLaunchQuest(e, o)
1065
+ };
1066
+ }
1067
+ setButtonsDisabled(t) {
1068
+ if (!this.buttonsContainer) return;
1069
+ this.buttonsContainer.querySelectorAll("button.qaid-btn, button.qaid-btn-structural").forEach((o) => {
1070
+ t ? o.classList.contains("qaid-btn-record") || (o.disabled = !0, o.style.opacity = "0.5") : (o.disabled = !1, o.style.opacity = "");
1071
+ });
1072
+ }
1073
+ /**
1074
+ * Destroy the embed and clean up all resources
1075
+ */
1076
+ destroy() {
1077
+ if (this.destroyed = !0, this.domObserver && (this.domObserver.disconnect(), this.domObserver = null), this.boundBeforeSwap && (document.removeEventListener("astro:before-swap", this.boundBeforeSwap), this.boundBeforeSwap = null), this.targeting?.destroy(), this.targeting = null, this.clearActiveThumb(), this.closeDialogA11y(), this.prewarmHandle !== null) {
1078
+ const t = window;
1079
+ this.prewarmIsTimeout ? clearTimeout(this.prewarmHandle) : t.cancelIdleCallback?.(this.prewarmHandle), this.prewarmHandle = null;
1080
+ }
1081
+ this.recording?.destroy(), this.recording = null, this.modal?.destroy(), this.modal = null, this.activeQuest?.destroy(), this.activeQuest = null, this.consoleCapture && (this.consoleCapture.restore(), this.consoleCapture = null), window.removeEventListener("resize", this.boundResize), document.removeEventListener("keydown", this.boundKeyDown), document.body.classList.remove("qaid-targeting", "qaid-type-up"), document.body.style.removeProperty("--qaid-positive"), document.body.style.removeProperty("--qaid-negative"), this.shadowHost && (this.shadowHost.remove(), this.shadowHost = null, this.shadowRoot = null), this.overlayShadowHost && (this.overlayShadowHost.remove(), this.overlayShadowHost = null, this.overlayShadowRoot = null), this.buttonsContainer = null, this.dismissBtn = null, this.tooltipElement = null, de();
1082
+ }
1083
+ }
1084
+ function gt(i) {
1085
+ return document.querySelector(i)?.textContent?.trim() ?? "";
1086
+ }
1087
+ function Fe() {
1088
+ const i = document.querySelector(
1089
+ 'script[type="application/json"][data-feedback-config]'
1090
+ );
1091
+ if (!i) return null;
1092
+ const t = i.textContent?.trim();
1093
+ if (!t) return null;
1094
+ try {
1095
+ const e = JSON.parse(t);
1096
+ return e.cssSelector && !e.css && (e.css = gt(e.cssSelector), delete e.cssSelector), e;
1097
+ } catch {
1098
+ return null;
1099
+ }
1100
+ }
1101
+ function Ve(i) {
1102
+ const t = i.getAttribute("data-endpoint");
1103
+ if (!t) return null;
1104
+ const e = i.getAttribute("data-position"), o = i.getAttribute("data-zindex"), n = i.getAttribute("data-positive-color"), s = i.getAttribute("data-negative-color"), a = i.getAttribute("data-marker-color"), c = i.getAttribute("data-container"), l = i.getAttribute("data-button-class"), d = i.getAttribute("data-skip-targeting"), r = i.getAttribute("data-single-button"), h = i.getAttribute("data-feedback-mode"), p = i.getAttribute("data-incognito"), g = i.getAttribute("data-button-size"), v = i.getAttribute("data-offset-x"), m = i.getAttribute("data-offset-y"), k = i.getAttribute("data-modal-width"), x = i.getAttribute("data-backdrop-opacity"), u = i.getAttribute("data-font-family"), y = i.getAttribute("data-font-size"), w = i.getAttribute("data-tooltip"), C = i.getAttribute("data-modal-title"), T = i.getAttribute("data-modal-subtitle"), S = i.getAttribute("data-placeholder"), f = i.getAttribute("data-submit-button"), D = i.getAttribute("data-skip-button"), Et = i.getAttribute("data-positive-icon"), Tt = i.getAttribute("data-negative-icon"), At = i.getAttribute("data-feedback-icon"), j = i.getAttribute("data-feedback-label"), Lt = i.getAttribute("data-api-key"), It = i.getAttribute("data-capture-screenshot"), Dt = i.getAttribute("data-annotate"), Mt = i.getAttribute("data-annotation-color"), H = i.getAttribute("data-screenshot-quality"), O = i.getAttribute("data-screenshot-max-width"), $ = i.getAttribute("data-screenshot-max-height"), Rt = i.getAttribute("data-capture-video"), K = i.getAttribute("data-video-redaction"), Pt = i.getAttribute("data-hide-thumbs"), Bt = i.getAttribute("data-hide-dismiss"), z = i.getAttribute("data-video-max-duration"), Ht = i.getAttribute("data-screenshot-method"), Ot = i.getAttribute("data-direction"), W = i.getAttribute("data-css-selector"), X = i.getAttribute("data-quest-base"), $t = i.getAttribute("data-quest-up"), zt = i.getAttribute("data-quest-down"), _t = i.getAttribute("data-quest-video"), Ft = i.getAttribute("data-quest-api-key"), Vt = i.getAttribute("data-quest-module-url");
1105
+ return {
1106
+ endpoint: t,
1107
+ css: W ? gt(W) : void 0,
1108
+ apiKey: Lt ?? void 0,
1109
+ captureScreenshot: It === "true" ? !0 : void 0,
1110
+ // Annotation is on by default; only an explicit "false" disables it.
1111
+ annotate: Dt === "false" ? !1 : void 0,
1112
+ annotationColor: Mt ?? void 0,
1113
+ screenshotOptions: H || O || $ ? {
1114
+ quality: H ? parseFloat(H) : void 0,
1115
+ maxWidth: O ? parseInt(O, 10) : void 0,
1116
+ maxHeight: $ ? parseInt($, 10) : void 0
1117
+ } : void 0,
1118
+ container: c ?? void 0,
1119
+ buttonClass: l ?? void 0,
1120
+ direction: Ot ?? void 0,
1121
+ position: e ?? void 0,
1122
+ zIndex: o ? parseInt(o, 10) : void 0,
1123
+ skipTargeting: d === "true" ? !0 : void 0,
1124
+ singleButton: r === "true" ? !0 : void 0,
1125
+ feedbackMode: h ?? void 0,
1126
+ incognito: p === "true" ? !0 : void 0,
1127
+ buttonSize: g ?? void 0,
1128
+ offset: v || m ? {
1129
+ x: v ? parseInt(v, 10) : void 0,
1130
+ y: m ? parseInt(m, 10) : void 0
1131
+ } : void 0,
1132
+ modalWidth: k ? parseInt(k, 10) : void 0,
1133
+ backdropOpacity: x ? parseFloat(x) : void 0,
1134
+ fontFamily: u ?? void 0,
1135
+ fontSize: y ? parseInt(y, 10) : void 0,
1136
+ colors: {
1137
+ positive: n ?? void 0,
1138
+ negative: s ?? void 0,
1139
+ marker: a ?? void 0
1140
+ },
1141
+ text: w || C || T || S || f || D || j ? {
1142
+ tooltip: w ?? void 0,
1143
+ modalTitle: C ?? void 0,
1144
+ modalSubtitle: T ?? void 0,
1145
+ placeholder: S ?? void 0,
1146
+ submitButton: f ?? void 0,
1147
+ skipButton: D ?? void 0,
1148
+ feedbackLabel: j ?? void 0
1149
+ } : void 0,
1150
+ positiveIcon: Et ?? void 0,
1151
+ negativeIcon: Tt ?? void 0,
1152
+ feedbackIcon: At ?? void 0,
1153
+ screenshotMethod: Ht ?? void 0,
1154
+ captureVideo: Rt === "true" ? !0 : void 0,
1155
+ hideThumbs: Pt === "true" ? !0 : void 0,
1156
+ hideDismiss: Bt === "true" ? !0 : void 0,
1157
+ videoOptions: z || K ? {
1158
+ maxDuration: z ? parseInt(z, 10) : void 0,
1159
+ redaction: K === "true" ? !0 : void 0
1160
+ } : void 0,
1161
+ quests: X ? {
1162
+ base: X,
1163
+ up: $t ?? void 0,
1164
+ down: zt ?? void 0,
1165
+ video: _t ?? void 0,
1166
+ apiKey: Ft ?? void 0,
1167
+ moduleUrl: Vt ?? void 0
1168
+ } : void 0
1169
+ };
1170
+ }
1171
+ function Ne() {
1172
+ if (typeof document > "u") return;
1173
+ const i = () => {
1174
+ const t = document.currentScript ?? document.querySelector("script[data-endpoint]"), e = Fe(), o = t ? Ve(t) : null, n = e ?? o;
1175
+ n?.endpoint && new _e(n);
1176
+ };
1177
+ document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", i) : i();
1178
+ }
1179
+ const Ue = 20, ot = 4096;
1180
+ function R(i) {
1181
+ const t = typeof i == "string" ? i : JSON.stringify(i);
1182
+ return t.length > ot ? t.slice(0, ot) + "…[truncated]" : t;
1183
+ }
1184
+ function nt(i, t) {
1185
+ i.length >= Ue && i.shift(), i.push(t);
1186
+ }
1187
+ async function je(i) {
1188
+ try {
1189
+ const t = await i.clone().text();
1190
+ return R(t);
1191
+ } catch {
1192
+ return;
1193
+ }
1194
+ }
1195
+ function Ke() {
1196
+ const i = [], t = window.fetch;
1197
+ window.fetch = async function(n, s) {
1198
+ const a = typeof n == "string" ? n : n instanceof URL ? n.toString() : n.url, c = s?.method ?? (typeof n == "object" && "method" in n ? n.method : "GET");
1199
+ let l;
1200
+ s?.body && (l = R(s.body));
1201
+ const d = await t.apply(window, [n, s]);
1202
+ if (d.status >= 400) {
1203
+ const r = await je(d);
1204
+ nt(i, {
1205
+ url: a,
1206
+ method: c.toUpperCase(),
1207
+ status: d.status,
1208
+ statusText: d.statusText,
1209
+ requestBody: l,
1210
+ responseBody: r,
1211
+ timestamp: Date.now()
1212
+ });
1213
+ }
1214
+ return d;
1215
+ };
1216
+ const e = XMLHttpRequest.prototype.open, o = XMLHttpRequest.prototype.send;
1217
+ return XMLHttpRequest.prototype.open = function(n, s, ...a) {
1218
+ return this._qaid_method = n, this._qaid_url = typeof s == "string" ? s : s.toString(), e.apply(this, [n, s, ...a]);
1219
+ }, XMLHttpRequest.prototype.send = function(n) {
1220
+ const s = this, a = n ? R(n) : void 0;
1221
+ return s.addEventListener("load", function() {
1222
+ s.status >= 400 && nt(i, {
1223
+ url: s._qaid_url,
1224
+ method: s._qaid_method.toUpperCase(),
1225
+ status: s.status,
1226
+ statusText: s.statusText,
1227
+ requestBody: a,
1228
+ responseBody: R(s.responseText),
1229
+ timestamp: Date.now()
1230
+ });
1231
+ }), o.apply(this, [n]);
1232
+ }, {
1233
+ errors: i,
1234
+ restore: () => {
1235
+ window.fetch = t, XMLHttpRequest.prototype.open = e, XMLHttpRequest.prototype.send = o;
1236
+ }
1237
+ };
1238
+ }
1239
+ function We() {
1240
+ const i = navigator.userAgent || "";
1241
+ return /iP(hone|ad|od)/.test(i) || // iPadOS 13+ masquerades as "MacIntel" but is a multi-touch device.
1242
+ navigator.platform === "MacIntel" && (navigator.maxTouchPoints || 0) > 1;
1243
+ }
1244
+ function Xe(i, t, e, o, n) {
1245
+ const s = i >= t, a = e >= o;
1246
+ return s === a ? { width: i, height: t, rotate: 0 } : { width: t, height: i, rotate: n === 270 ? -90 : 90 };
1247
+ }
1248
+ function Ye(i, t, e) {
1249
+ if (e.rotate === 0) {
1250
+ i.drawImage(t, 0, 0, e.width, e.height);
1251
+ return;
1252
+ }
1253
+ i.save(), e.rotate === 90 ? (i.translate(e.width, 0), i.rotate(Math.PI / 2)) : (i.translate(0, e.height), i.rotate(-Math.PI / 2)), i.drawImage(t, 0, 0, e.height, e.width), i.restore();
1254
+ }
1255
+ function Qe() {
1256
+ const i = typeof screen < "u" ? screen.orientation : void 0;
1257
+ return i && typeof i.angle == "number" ? i.angle : 0;
1258
+ }
1259
+ function mt(i) {
1260
+ return new Promise((t) => {
1261
+ if (i.videoWidth > 0) {
1262
+ t();
1263
+ return;
1264
+ }
1265
+ i.addEventListener("loadedmetadata", () => t(), { once: !0 });
1266
+ });
1267
+ }
1268
+ async function vt(i) {
1269
+ try {
1270
+ await i.play();
1271
+ } catch {
1272
+ }
1273
+ }
1274
+ async function Ge(i, t = 15) {
1275
+ const e = document.createElement("canvas");
1276
+ if (typeof e.captureStream != "function") return null;
1277
+ const o = e.getContext("2d");
1278
+ if (!o) return null;
1279
+ const n = document.createElement("video");
1280
+ n.muted = !0, n.playsInline = !0, n.srcObject = i, await mt(n);
1281
+ const s = Xe(
1282
+ n.videoWidth,
1283
+ n.videoHeight,
1284
+ window.innerWidth,
1285
+ window.innerHeight,
1286
+ Qe()
1287
+ );
1288
+ e.width = s.width, e.height = s.height, await vt(n);
1289
+ let a = 0;
1290
+ const c = () => {
1291
+ Ye(o, n, s), a = requestAnimationFrame(c);
1292
+ };
1293
+ c();
1294
+ const l = e.captureStream(t);
1295
+ return {
1296
+ stream: l,
1297
+ stop: () => {
1298
+ a && cancelAnimationFrame(a), a = 0, l.getTracks().forEach((d) => d.stop()), n.pause(), n.srcObject = null;
1299
+ }
1300
+ };
1301
+ }
1302
+ function Je(i, t, e, o, n) {
1303
+ const s = Math.max(0, Math.min(o, i.left * t)), a = Math.max(0, Math.min(n, i.top * e)), c = Math.max(0, Math.min(o, (i.left + i.width) * t)), l = Math.max(0, Math.min(n, (i.top + i.height) * e)), d = c - s, r = l - a;
1304
+ return d <= 0 || r <= 0 ? null : { x: s, y: a, w: d, h: r };
1305
+ }
1306
+ function Ze(i, t, e, o, n, s, a) {
1307
+ i.drawImage(t, 0, 0, o, n);
1308
+ const c = o / Math.max(1, window.innerWidth), l = n / Math.max(1, window.innerHeight);
1309
+ for (const d of e) {
1310
+ const r = Je(
1311
+ d.getBoundingClientRect(),
1312
+ c,
1313
+ l,
1314
+ o,
1315
+ n
1316
+ );
1317
+ r && (i.save(), a ? (i.filter = `blur(${s}px)`, i.drawImage(t, r.x, r.y, r.w, r.h, r.x, r.y, r.w, r.h)) : (i.fillStyle = "#0b0b0b", i.fillRect(r.x, r.y, r.w, r.h)), i.restore());
1318
+ }
1319
+ }
1320
+ async function ti(i, t, e = {}) {
1321
+ const o = e.frameRate ?? 15, n = e.blurRadius ?? 12, s = document.createElement("canvas");
1322
+ if (typeof s.captureStream != "function") return null;
1323
+ const a = s.getContext("2d");
1324
+ if (!a) return null;
1325
+ const c = "filter" in a, l = document.createElement("video");
1326
+ l.muted = !0, l.playsInline = !0, l.srcObject = i, await mt(l), s.width = l.videoWidth, s.height = l.videoHeight, await vt(l);
1327
+ let d = 0;
1328
+ const r = () => {
1329
+ Ze(
1330
+ a,
1331
+ l,
1332
+ t,
1333
+ s.width,
1334
+ s.height,
1335
+ n,
1336
+ c
1337
+ ), d = requestAnimationFrame(r);
1338
+ };
1339
+ r();
1340
+ const h = s.captureStream(o);
1341
+ return {
1342
+ stream: h,
1343
+ stop: () => {
1344
+ d && cancelAnimationFrame(d), d = 0, h.getTracks().forEach((p) => p.stop()), l.pause(), l.srcObject = null;
1345
+ }
1346
+ };
1347
+ }
1348
+ function ei() {
1349
+ return typeof navigator < "u" && !!navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia == "function" && typeof MediaRecorder < "u";
1350
+ }
1351
+ function yt() {
1352
+ if (typeof MediaRecorder > "u") return "";
1353
+ const i = [
1354
+ "video/webm;codecs=vp9",
1355
+ "video/webm;codecs=vp8",
1356
+ "video/webm",
1357
+ "video/mp4"
1358
+ ];
1359
+ for (const t of i)
1360
+ if (MediaRecorder.isTypeSupported(t))
1361
+ return t;
1362
+ return "";
1363
+ }
1364
+ function ii(i = {}) {
1365
+ const t = i.maxDuration ?? 15, e = i.videoBitsPerSecond ?? 8e5, o = i.redactionElements ?? [], n = i.redactionBlurRadius;
1366
+ let s = null, a = null, c = null, l = null, d = [], r = null, h = null, p = null, g = 0, v = null, m = null, k = !1, x = null;
1367
+ function u() {
1368
+ p !== null && (clearInterval(p), p = null), x !== null && (clearTimeout(x), x = null), a && (a.stop(), a = null), c && (c.stop(), c = null), s && (s.getTracks().forEach((w) => w.stop()), s = null), l = null, d = [], r = null, h = null, v = null, m = null;
1369
+ }
1370
+ function y() {
1371
+ k || (k = !0, l && l.state !== "inactive" && l.stop());
1372
+ }
1373
+ return {
1374
+ async start() {
1375
+ k = !1, d = [];
1376
+ const w = yt();
1377
+ if (!w)
1378
+ throw new Error("No supported video MIME type found");
1379
+ s = await navigator.mediaDevices.getDisplayMedia({
1380
+ video: {
1381
+ frameRate: 15,
1382
+ displaySurface: "browser"
1383
+ },
1384
+ audio: !1,
1385
+ // @ts-expect-error preferCurrentTab is not in the TS types yet
1386
+ preferCurrentTab: !0,
1387
+ selfBrowserSurface: "include",
1388
+ monitorTypeSurfaces: "exclude",
1389
+ surfaceSwitching: "exclude"
1390
+ });
1391
+ const C = s.getVideoTracks()[0], T = C?.getSettings?.()?.displaySurface;
1392
+ if (T === "monitor" || T === "window")
1393
+ throw s.getTracks().forEach((f) => f.stop()), s = null, new Error(
1394
+ "qaid records only the current tab — please share this tab, not a window or your whole screen."
1395
+ );
1396
+ C && C.addEventListener("ended", () => {
1397
+ y();
1398
+ });
1399
+ let S = s;
1400
+ if (We()) {
1401
+ const f = await Ge(s);
1402
+ f && (a = f, S = f.stream);
1403
+ } else if (o.length > 0) {
1404
+ const f = await ti(s, o, {
1405
+ blurRadius: n
1406
+ });
1407
+ f && (c = f, S = f.stream);
1408
+ }
1409
+ l = new MediaRecorder(S, {
1410
+ mimeType: w,
1411
+ videoBitsPerSecond: e
1412
+ }), l.ondataavailable = (f) => {
1413
+ f.data.size > 0 && d.push(f.data);
1414
+ }, l.onstop = () => {
1415
+ const f = new Blob(d, { type: w });
1416
+ v && v(f), h && h(f), a && (a.stop(), a = null), c && (c.stop(), c = null), s && s.getTracks().forEach((D) => D.stop());
1417
+ }, l.onerror = () => {
1418
+ m && m(new Error("MediaRecorder error"));
1419
+ }, l.start(1e3), g = Date.now(), p = setInterval(() => {
1420
+ const f = Math.floor((Date.now() - g) / 1e3);
1421
+ r && r(f);
1422
+ }, 1e3), x = setTimeout(() => {
1423
+ y();
1424
+ }, t * 1e3);
1425
+ },
1426
+ stop() {
1427
+ return new Promise((w, C) => {
1428
+ v = w, m = C, y();
1429
+ });
1430
+ },
1431
+ onTick(w) {
1432
+ r = w;
1433
+ },
1434
+ onStop(w) {
1435
+ h = w;
1436
+ },
1437
+ destroy() {
1438
+ y(), u();
1439
+ }
1440
+ };
1441
+ }
1442
+ const wt = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1443
+ __proto__: null,
1444
+ createVideoRecorder: ii,
1445
+ getSupportedMimeType: yt,
1446
+ isVideoRecordingSupported: ei
1447
+ }, Symbol.toStringTag, { value: "Module" })), st = "https://qaid.dev/lib/html2canvas.min.js", oi = 1e4, ni = 50;
1448
+ let L = null;
1449
+ function si() {
1450
+ if (typeof document > "u" || typeof window > "u")
1451
+ return !1;
1452
+ const i = document.createElement("canvas");
1453
+ return typeof i.getContext == "function" && !!i.getContext("2d");
1454
+ }
1455
+ function ai() {
1456
+ return window.html2canvas ? Promise.resolve(!0) : L || (L = new Promise((i) => {
1457
+ if (!document.querySelector(
1458
+ `script[src="${st}"]`
1459
+ )) {
1460
+ const n = document.createElement("script");
1461
+ n.src = st, n.async = !0, document.head.appendChild(n);
1462
+ }
1463
+ const e = Date.now(), o = () => {
1464
+ if (window.html2canvas) {
1465
+ i(!0);
1466
+ return;
1467
+ }
1468
+ if (Date.now() - e > oi) {
1469
+ L = null, i(!1);
1470
+ return;
1471
+ }
1472
+ setTimeout(o, ni);
1473
+ };
1474
+ o();
1475
+ }), L);
1476
+ }
1477
+ async function ri(i = {}) {
1478
+ const { quality: t = 0.8, maxWidth: e = 1280, maxHeight: o = 800 } = i;
1479
+ try {
1480
+ if (!await ai() || !window.html2canvas)
1481
+ return console.warn("html2canvas failed to load"), null;
1482
+ const s = await window.html2canvas(document.body, {
1483
+ useCORS: !0,
1484
+ allowTaint: !1,
1485
+ logging: !1,
1486
+ windowWidth: document.documentElement.clientWidth,
1487
+ windowHeight: document.documentElement.clientHeight,
1488
+ width: document.documentElement.clientWidth,
1489
+ height: document.documentElement.clientHeight,
1490
+ x: window.scrollX,
1491
+ y: window.scrollY,
1492
+ ignoreElements: (d) => d instanceof HTMLElement ? d.classList.contains("qaid-buttons") || d.classList.contains("qaid-targeting-overlay") || d.classList.contains("qaid-modal-container") || d.classList.contains("qaid-bottom-sheet") || d.classList.contains("qaid-backdrop") || d.classList.contains("qaid-selected-marker") || d.classList.contains("qaid-tooltip-text") || d.classList.contains("qaid-recording-indicator") || d.classList.contains("qaid-video-preview") || d.className?.toString().startsWith?.("qaid-") : !1
1493
+ }), a = s.width, c = s.height, l = Math.min(e / a, o / c, 1);
1494
+ if (l < 1) {
1495
+ const d = Math.round(a * l), r = Math.round(c * l), h = document.createElement("canvas");
1496
+ h.width = d, h.height = r;
1497
+ const p = h.getContext("2d");
1498
+ return p ? (p.drawImage(s, 0, 0, d, r), h.toDataURL("image/webp", t)) : null;
1499
+ }
1500
+ return s.toDataURL("image/webp", t);
1501
+ } catch (n) {
1502
+ return console.warn("DOM screenshot capture failed:", n), null;
1503
+ }
1504
+ }
1505
+ const at = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1506
+ __proto__: null,
1507
+ captureDomScreenshot: ri,
1508
+ isDomScreenshotSupported: si
1509
+ }, Symbol.toStringTag, { value: "Module" })), E = 12, di = "#ef4444", ci = ["#ef4444", "#f59e0b", "#22c55e", "#3b82f6", "#111827", "#ffffff"], li = {
1510
+ "#ef4444": "red",
1511
+ "#f59e0b": "amber",
1512
+ "#22c55e": "green",
1513
+ "#3b82f6": "blue",
1514
+ "#111827": "black",
1515
+ "#ffffff": "white",
1516
+ "#6366f1": "indigo"
1517
+ };
1518
+ function rt(i) {
1519
+ return li[i.toLowerCase()] ?? i;
1520
+ }
1521
+ const hi = 4, ui = 0.8, pi = {
1522
+ title: "Annotate screenshot",
1523
+ instructions: "Draw on the screenshot to highlight or hide details, then choose Done to attach it or Skip to send the original.",
1524
+ rectangle: "Rectangle",
1525
+ arrow: "Arrow",
1526
+ pen: "Pen",
1527
+ blur: "Blur or redact",
1528
+ undo: "Undo last",
1529
+ skip: "Skip annotation",
1530
+ done: "Done",
1531
+ colors: "Drawing colour"
1532
+ };
1533
+ function P(i, t) {
1534
+ return {
1535
+ x: Math.min(i.x, t.x),
1536
+ y: Math.min(i.y, t.y),
1537
+ w: Math.abs(i.x - t.x),
1538
+ h: Math.abs(i.y - t.y)
1539
+ };
1540
+ }
1541
+ function fi(i) {
1542
+ if (i.type === "pen") return i.points.length < 2;
1543
+ const t = P(i.points[0], i.points[1]);
1544
+ return t.w < 3 && t.h < 3;
1545
+ }
1546
+ function bi(i, t, e, o) {
1547
+ i.beginPath(), i.moveTo(t.x, t.y), i.lineTo(e.x, e.y), i.stroke();
1548
+ const n = Math.atan2(e.y - t.y, e.x - t.x), s = Math.max(10, o * 3);
1549
+ i.beginPath(), i.moveTo(e.x, e.y), i.lineTo(
1550
+ e.x - s * Math.cos(n - Math.PI / 6),
1551
+ e.y - s * Math.sin(n - Math.PI / 6)
1552
+ ), i.moveTo(e.x, e.y), i.lineTo(
1553
+ e.x - s * Math.cos(n + Math.PI / 6),
1554
+ e.y - s * Math.sin(n + Math.PI / 6)
1555
+ ), i.stroke();
1556
+ }
1557
+ function gi(i, t) {
1558
+ if (t.length !== 0) {
1559
+ i.beginPath(), i.moveTo(t[0].x, t[0].y);
1560
+ for (let e = 1; e < t.length; e++)
1561
+ i.lineTo(t[e].x, t[e].y);
1562
+ i.stroke();
1563
+ }
1564
+ }
1565
+ function mi(i, t, e, o, n) {
1566
+ i.fillStyle = "rgb(15, 23, 42)", i.fillRect(t, e, o, n), i.fillStyle = "rgb(37, 47, 63)";
1567
+ for (let s = e; s < e + n; s += E)
1568
+ for (let a = t; a < t + o; a += E)
1569
+ (Math.floor((a - t) / E) + Math.floor((s - e) / E)) % 2 === 0 && i.fillRect(
1570
+ a,
1571
+ s,
1572
+ Math.min(E, t + o - a),
1573
+ Math.min(E, e + n - s)
1574
+ );
1575
+ }
1576
+ function B(i, t) {
1577
+ switch (i.save(), i.strokeStyle = t.color, i.fillStyle = t.color, i.lineWidth = t.strokeWidth, i.lineJoin = "round", i.lineCap = "round", t.type) {
1578
+ case "rect": {
1579
+ const e = P(t.points[0], t.points[1]);
1580
+ i.strokeRect(e.x, e.y, e.w, e.h);
1581
+ break;
1582
+ }
1583
+ case "arrow":
1584
+ bi(i, t.points[0], t.points[1], t.strokeWidth);
1585
+ break;
1586
+ case "pen":
1587
+ gi(i, t.points);
1588
+ break;
1589
+ case "blur": {
1590
+ const e = P(t.points[0], t.points[1]);
1591
+ mi(i, e.x, e.y, e.w, e.h);
1592
+ break;
1593
+ }
1594
+ }
1595
+ i.restore();
1596
+ }
1597
+ function kt(i) {
1598
+ return new Promise((t, e) => {
1599
+ const o = new Image();
1600
+ o.onload = () => t(o), o.onerror = () => e(new Error("Failed to load screenshot")), o.src = i;
1601
+ });
1602
+ }
1603
+ async function xt(i, t, e = {}) {
1604
+ const o = e.quality ?? ui;
1605
+ let n;
1606
+ try {
1607
+ n = await kt(i);
1608
+ } catch {
1609
+ return i;
1610
+ }
1611
+ const s = document.createElement("canvas");
1612
+ s.width = n.naturalWidth || n.width || 1, s.height = n.naturalHeight || n.height || 1;
1613
+ const a = s.getContext("2d");
1614
+ if (!a) return i;
1615
+ a.drawImage(n, 0, 0, s.width, s.height);
1616
+ for (const c of t)
1617
+ B(a, c);
1618
+ return s.toDataURL("image/webp", o);
1619
+ }
1620
+ let vi = 0;
1621
+ class qt {
1622
+ shapes = [];
1623
+ tool = "rect";
1624
+ opts;
1625
+ labels;
1626
+ color;
1627
+ palette;
1628
+ strokeWidth;
1629
+ uid = `qaid-annotate-${++vi}`;
1630
+ container;
1631
+ canvas;
1632
+ ctx = null;
1633
+ toolbar;
1634
+ undoBtn;
1635
+ img = null;
1636
+ drawing = !1;
1637
+ current = null;
1638
+ settled = !1;
1639
+ // Assigned in open() before any settle() is reachable (done/skip only fire
1640
+ // after the editor is mounted).
1641
+ resolveResult;
1642
+ constructor(t) {
1643
+ this.opts = t, this.labels = { ...pi, ...t.labels ?? {} }, this.color = t.color ?? di, this.palette = (t.palette && t.palette.length ? t.palette : ci).slice(), this.palette.some((e) => e.toLowerCase() === this.color.toLowerCase()) || this.palette.unshift(this.color), this.strokeWidth = t.strokeWidth ?? hi;
1644
+ }
1645
+ /** Mount the editor and resolve when the user commits (Done) or skips. */
1646
+ open() {
1647
+ const t = new Promise((e) => {
1648
+ this.resolveResult = e;
1649
+ });
1650
+ return this.mount(), t;
1651
+ }
1652
+ toolButton(t, e, o) {
1653
+ const n = q("button", {
1654
+ type: "button",
1655
+ class: "qaid-annotate-tool",
1656
+ "data-qaid-tool": t,
1657
+ "aria-pressed": t === this.tool ? "true" : "false",
1658
+ "aria-label": `${o} tool`,
1659
+ title: o
1660
+ });
1661
+ return n.innerHTML = `${e}<span class="qaid-annotate-btn-text">${o}</span>`, n;
1662
+ }
1663
+ /** A round colour swatch button for the toolbar's colour group. */
1664
+ swatchButton(t) {
1665
+ const e = rt(t), o = q("button", {
1666
+ type: "button",
1667
+ class: "qaid-annotate-swatch",
1668
+ "data-qaid-color": t,
1669
+ "aria-pressed": t.toLowerCase() === this.color.toLowerCase() ? "true" : "false",
1670
+ "aria-label": `Draw in ${e}`,
1671
+ title: e
1672
+ });
1673
+ return o.style.setProperty("--qaid-swatch", t), o;
1674
+ }
1675
+ actionButton(t, e, o, n) {
1676
+ const s = q("button", {
1677
+ type: "button",
1678
+ class: `qaid-annotate-action ${n}`,
1679
+ "data-qaid-action": t,
1680
+ "aria-label": o,
1681
+ title: o
1682
+ });
1683
+ return s.innerHTML = `${e}<span class="qaid-annotate-btn-text">${o}</span>`, s;
1684
+ }
1685
+ mount() {
1686
+ const t = `${this.uid}-title`, e = `${this.uid}-desc`;
1687
+ this.container = q("div", { class: "qaid-annotate" }), this.opts.applyVars?.(this.container);
1688
+ const o = q("h2", {
1689
+ id: t,
1690
+ class: "qaid-annotate-title"
1691
+ });
1692
+ o.textContent = this.labels.title;
1693
+ const n = q("p", {
1694
+ id: e,
1695
+ class: "qaid-annotate-desc"
1696
+ });
1697
+ n.textContent = this.labels.instructions;
1698
+ const s = q("div", { class: "qaid-annotate-stage" });
1699
+ this.canvas = document.createElement("canvas"), this.canvas.className = "qaid-annotate-canvas", this.canvas.width = 1, this.canvas.height = 1, this.canvas.setAttribute("role", "img"), this.canvas.setAttribute("aria-label", this.labels.title), this.ctx = this.canvas.getContext("2d"), s.appendChild(this.canvas), this.toolbar = q("div", {
1700
+ class: "qaid-annotate-toolbar",
1701
+ role: "toolbar",
1702
+ "aria-label": this.labels.title
1703
+ });
1704
+ const a = q("div", { class: "qaid-annotate-tools" });
1705
+ a.appendChild(this.toolButton("rect", Ut, this.labels.rectangle)), a.appendChild(this.toolButton("arrow", jt, this.labels.arrow)), a.appendChild(this.toolButton("pen", Kt, this.labels.pen)), a.appendChild(this.toolButton("blur", Wt, this.labels.blur));
1706
+ const c = q("div", {
1707
+ class: "qaid-annotate-colors",
1708
+ role: "group",
1709
+ "aria-label": this.labels.colors
1710
+ });
1711
+ for (const h of this.palette) c.appendChild(this.swatchButton(h));
1712
+ this.undoBtn = this.actionButton("undo", Xt, this.labels.undo, "qaid-annotate-undo"), this.undoBtn.disabled = !0;
1713
+ const l = this.actionButton("skip", Qt, this.labels.skip, "qaid-annotate-skip"), d = this.actionButton("done", Yt, this.labels.done, "qaid-annotate-done"), r = q("div", { class: "qaid-annotate-actions" });
1714
+ r.appendChild(this.undoBtn), r.appendChild(l), r.appendChild(d), this.toolbar.appendChild(a), this.toolbar.appendChild(c), this.toolbar.appendChild(r), this.container.appendChild(o), this.container.appendChild(n), this.container.appendChild(s), this.container.appendChild(this.toolbar), this.opts.root.appendChild(this.container), this.toolbar.addEventListener("click", this.onToolbarClick), this.canvas.addEventListener("pointerdown", this.onPointerDown), this.container.addEventListener("pointermove", this.onPointerMove), this.container.addEventListener("pointerup", this.onPointerUp), this.container.addEventListener("keydown", this.onKeyDown), this.opts.openDialog?.(this.container, {
1715
+ labelledbyId: t,
1716
+ describedbyId: e
1717
+ }), this.opts.announce?.(this.labels.instructions), kt(this.opts.dataUrl).then((h) => {
1718
+ this.img = h, this.canvas.width = h.naturalWidth || h.width || 1, this.canvas.height = h.naturalHeight || h.height || 1, this.redraw();
1719
+ }).catch(() => {
1720
+ this.img = null, this.redraw();
1721
+ });
1722
+ }
1723
+ /** Convert a pointer event's client coordinates into natural image pixels. */
1724
+ toImageCoords(t) {
1725
+ const e = this.canvas.getBoundingClientRect(), o = this.canvas.width / (e.width || this.canvas.width), n = this.canvas.height / (e.height || this.canvas.height);
1726
+ return {
1727
+ x: (t.clientX - e.left) * o,
1728
+ y: (t.clientY - e.top) * n
1729
+ };
1730
+ }
1731
+ /** Repaint the display canvas: base image (if loaded) then every shape. */
1732
+ redraw() {
1733
+ const t = this.ctx;
1734
+ if (t) {
1735
+ t.clearRect(0, 0, this.canvas.width, this.canvas.height), this.img && t.drawImage(this.img, 0, 0, this.canvas.width, this.canvas.height);
1736
+ for (const e of this.shapes)
1737
+ B(t, e);
1738
+ this.current && B(t, this.current);
1739
+ }
1740
+ }
1741
+ /** Select the active drawing tool and reflect it on the toolbar. */
1742
+ selectTool(t) {
1743
+ this.tool = t, this.toolbar.querySelectorAll("[data-qaid-tool]").forEach((n) => {
1744
+ n.setAttribute(
1745
+ "aria-pressed",
1746
+ n.getAttribute("data-qaid-tool") === t ? "true" : "false"
1747
+ );
1748
+ });
1749
+ const o = this.labels[yi[t]];
1750
+ this.opts.announce?.(`${o} tool selected`);
1751
+ }
1752
+ /** Set the active drawing colour; subsequent shapes use it. */
1753
+ selectColor(t) {
1754
+ this.color = t, this.toolbar.querySelectorAll("[data-qaid-color]").forEach((o) => {
1755
+ o.setAttribute(
1756
+ "aria-pressed",
1757
+ (o.getAttribute("data-qaid-color") ?? "").toLowerCase() === t.toLowerCase() ? "true" : "false"
1758
+ );
1759
+ }), this.opts.announce?.(`${rt(t)} colour selected`);
1760
+ }
1761
+ onToolbarClick = (t) => {
1762
+ const o = t.target?.closest(
1763
+ "[data-qaid-tool],[data-qaid-action],[data-qaid-color]"
1764
+ );
1765
+ if (!o) return;
1766
+ const n = o.getAttribute("data-qaid-color");
1767
+ if (n) {
1768
+ this.selectColor(n);
1769
+ return;
1770
+ }
1771
+ const s = o.getAttribute("data-qaid-tool");
1772
+ if (s) {
1773
+ this.selectTool(s);
1774
+ return;
1775
+ }
1776
+ const a = o.getAttribute("data-qaid-action");
1777
+ a === "undo" ? this.undo() : a === "skip" ? this.skip() : a === "done" && this.done();
1778
+ };
1779
+ onPointerDown = (t) => {
1780
+ t.preventDefault?.(), this.drawing = !0;
1781
+ const e = this.toImageCoords(t);
1782
+ this.current = {
1783
+ type: this.tool,
1784
+ color: this.color,
1785
+ strokeWidth: this.strokeWidth,
1786
+ points: this.tool === "pen" ? [e] : [e, { ...e }]
1787
+ }, this.redraw();
1788
+ };
1789
+ onPointerMove = (t) => {
1790
+ if (!this.drawing || !this.current) return;
1791
+ const e = this.toImageCoords(t);
1792
+ this.current.type === "pen" ? this.current.points.push(e) : this.current.points[1] = e, this.redraw();
1793
+ };
1794
+ onPointerUp = () => {
1795
+ if (!this.drawing || !this.current) return;
1796
+ this.drawing = !1;
1797
+ const t = this.current;
1798
+ if (this.current = null, fi(t)) {
1799
+ this.redraw();
1800
+ return;
1801
+ }
1802
+ this.shapes.push(t), this.undoBtn.disabled = !1, this.opts.announce?.("Annotation added"), this.redraw();
1803
+ };
1804
+ onKeyDown = (t) => {
1805
+ if (t.key === "Escape") {
1806
+ t.preventDefault(), this.skip();
1807
+ return;
1808
+ }
1809
+ if (t.key === "Enter") {
1810
+ const e = t.target;
1811
+ if (e?.getAttribute("data-qaid-tool") || e?.getAttribute("data-qaid-action"))
1812
+ return;
1813
+ t.preventDefault(), this.done();
1814
+ }
1815
+ };
1816
+ /** Remove the most recently added shape. */
1817
+ undo() {
1818
+ if (this.shapes.length === 0) {
1819
+ this.opts.announce?.("Nothing to undo");
1820
+ return;
1821
+ }
1822
+ this.shapes.pop(), this.undoBtn.disabled = this.shapes.length === 0, this.opts.announce?.("Removed last annotation"), this.redraw();
1823
+ }
1824
+ /** Composite the annotations and resolve with the new WebP data URL. */
1825
+ async done() {
1826
+ if (this.settled) return;
1827
+ const t = await xt(this.opts.dataUrl, this.shapes, {
1828
+ quality: this.opts.quality
1829
+ });
1830
+ this.opts.announce?.("Annotated screenshot attached"), this.settle(t);
1831
+ }
1832
+ /** Discard annotations and resolve with `null` (caller keeps the original). */
1833
+ skip() {
1834
+ this.settled || (this.opts.announce?.("Annotation skipped"), this.settle(null));
1835
+ }
1836
+ settle(t) {
1837
+ this.settled = !0, this.teardown(), this.resolveResult(t);
1838
+ }
1839
+ teardown() {
1840
+ this.toolbar.removeEventListener("click", this.onToolbarClick), this.canvas.removeEventListener("pointerdown", this.onPointerDown), this.container.removeEventListener("pointermove", this.onPointerMove), this.container.removeEventListener("pointerup", this.onPointerUp), this.container.removeEventListener("keydown", this.onKeyDown), this.opts.closeDialog?.(), this.container.remove();
1841
+ }
1842
+ }
1843
+ const yi = {
1844
+ rect: "rectangle",
1845
+ arrow: "arrow",
1846
+ pen: "pen",
1847
+ blur: "blur"
1848
+ };
1849
+ function wi(i) {
1850
+ return new qt(i).open();
1851
+ }
1852
+ const dt = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
1853
+ __proto__: null,
1854
+ AnnotationEditor: qt,
1855
+ compositeAnnotations: xt,
1856
+ drawShape: B,
1857
+ openAnnotationEditor: wi,
1858
+ rectFromPoints: P
1859
+ }, Symbol.toStringTag, { value: "Module" }));
1860
+ Ne();
1861
+ const ki = document;
1862
+ function b(i, t, ...e) {
1863
+ const o = ki.createElement(i);
1864
+ if (t) {
1865
+ if (t.class && (o.className = t.class), t.style && (o.style.cssText = t.style), t.html != null && (o.innerHTML = t.html), t.text != null && (o.textContent = t.text), t.attrs)
1866
+ for (const n in t.attrs) o.setAttribute(n, t.attrs[n]);
1867
+ if (t.on)
1868
+ for (const n in t.on) o.addEventListener(n, t.on[n]);
1869
+ }
1870
+ for (const n of e)
1871
+ n != null && n !== !1 && o.append(n);
1872
+ return o;
1873
+ }
1874
+ class xi {
1875
+ constructor(t) {
1876
+ this.host = t;
1877
+ }
1878
+ videoRecorder = null;
1879
+ networkCapture = null;
1880
+ recordedBlob = null;
1881
+ recordingIndicator = null;
1882
+ videoPreview = null;
1883
+ isRecording = !1;
1884
+ isSendingVideo = !1;
1885
+ // Pre-recording redaction picking.
1886
+ redactPicks = [];
1887
+ redactPickerRoot = null;
1888
+ redactRafId = 0;
1889
+ boundRedactClick = (t) => this.handleRedactPickClick(t);
1890
+ /** Escape-key handler; returns true when it consumed the key. */
1891
+ handleEscape() {
1892
+ return this.isRecording ? (this.stopRecording(), !0) : this.videoPreview ? (this.cancelRecordingPreview(), !0) : this.redactPickerRoot ? (this.finishRedactionPicking(!1), !0) : !1;
1893
+ }
1894
+ destroy() {
1895
+ this.redactRafId && cancelAnimationFrame(this.redactRafId), this.redactRafId = 0, document.removeEventListener("click", this.boundRedactClick, !0), this.redactPickerRoot && (this.redactPickerRoot.remove(), this.redactPickerRoot = null), this.redactPicks = [], this.cleanupRecording(), this.removeVideoPreview();
1896
+ }
1897
+ async startRecording(t = []) {
1898
+ if (!(this.isRecording || this.host.state !== "IDLE"))
1899
+ try {
1900
+ this.networkCapture = Ke();
1901
+ const { createVideoRecorder: e } = await Promise.resolve().then(() => wt);
1902
+ this.videoRecorder = e({
1903
+ maxDuration: this.host.config.videoOptions.maxDuration,
1904
+ redactionElements: t
1905
+ }), this.videoRecorder.onTick((o) => {
1906
+ this.updateRecordingTimer(o);
1907
+ }), this.videoRecorder.onStop((o) => {
1908
+ this.isRecording && (this.recordedBlob = o, this.isRecording = !1, this.host.announceMsg("Recording stopped"), this.removeRecordingIndicator(), document.removeEventListener("keydown", this.host.boundKeyDown), this.host.setButtonsDisabled(!1), o && o.size > 0 ? this.showRecordingPreview() : this.cleanupRecording());
1909
+ }), await this.videoRecorder.start(), this.isRecording = !0, this.host.announceMsg("Recording started"), this.host.setButtonsDisabled(!0), this.showRecordingIndicator(), document.addEventListener("keydown", this.host.boundKeyDown);
1910
+ } catch (e) {
1911
+ e instanceof Error && /current tab/i.test(e.message) && this.host.announceMsg(e.message), this.cleanupRecording();
1912
+ }
1913
+ }
1914
+ async stopRecording() {
1915
+ try {
1916
+ this.recordedBlob = await this.videoRecorder.stop();
1917
+ } catch {
1918
+ this.recordedBlob = null;
1919
+ }
1920
+ this.isRecording = !1, this.host.announceMsg("Recording stopped"), this.removeRecordingIndicator(), document.removeEventListener("keydown", this.host.boundKeyDown), this.recordedBlob && this.recordedBlob.size > 0 ? this.showRecordingPreview() : this.cleanupRecording();
1921
+ }
1922
+ /**
1923
+ * Pre-recording redaction picker. The user clicks page areas to blur; each
1924
+ * gets an outline that tracks its position, then "Start recording" hands the
1925
+ * picks to the recorder, which blurs their live bounding boxes so the
1926
+ * redaction follows the content as the page scrolls. "Cancel"/Escape aborts.
1927
+ */
1928
+ startPicking() {
1929
+ if (this.isRecording || this.host.state !== "IDLE") return;
1930
+ this.host.setState("REDACT_PICKING"), this.redactPicks = [];
1931
+ const t = this.host.ensureOverlayHost(), e = b("div", { style: "position:absolute;inset:0;" }), o = b(
1932
+ "div",
1933
+ { class: "qaid-redact-picker", style: "position:fixed;inset:0;pointer-events:none;" },
1934
+ e,
1935
+ // Control bar — the only pointer-interactive part of the overlay.
1936
+ b(
1937
+ "div",
1938
+ {
1939
+ style: "position:fixed;left:50%;bottom:24px;transform:translateX(-50%);display:flex;gap:12px;align-items:center;pointer-events:auto;background:#0b1220;color:#fff;border:1px solid #ff6b6b;border-radius:9999px;padding:10px 16px;font:600 13px system-ui,sans-serif;box-shadow:0 8px 30px rgba(0,0,0,.5);"
1940
+ },
1941
+ b("span", {
1942
+ text: "Click sensitive areas to blur",
1943
+ attrs: { "data-qaid-redact-label": "" }
1944
+ }),
1945
+ b("button", {
1946
+ text: "Start recording",
1947
+ attrs: { type: "button" },
1948
+ style: "cursor:pointer;border:0;border-radius:9999px;padding:8px 14px;background:#ff6b6b;color:#0b1220;font:inherit;",
1949
+ on: { click: () => this.finishRedactionPicking(!0) }
1950
+ }),
1951
+ b("button", {
1952
+ text: "Cancel",
1953
+ attrs: { type: "button" },
1954
+ style: "cursor:pointer;border:0;background:transparent;color:#9fb3c8;font:inherit;",
1955
+ on: { click: () => this.finishRedactionPicking(!1) }
1956
+ })
1957
+ )
1958
+ );
1959
+ t.appendChild(o), this.redactPickerRoot = o;
1960
+ const n = () => {
1961
+ this.renderRedactPicks(e), this.redactRafId = requestAnimationFrame(n);
1962
+ };
1963
+ n(), document.addEventListener("click", this.boundRedactClick, !0), document.addEventListener("keydown", this.host.boundKeyDown), this.host.announceMsg("Click sensitive areas to blur, then start recording");
1964
+ }
1965
+ handleRedactPickClick(t) {
1966
+ const e = t.target instanceof Element ? t.target : null;
1967
+ if (!e || M(e) || this.redactPickerRoot?.contains(e)) return;
1968
+ t.preventDefault(), t.stopPropagation();
1969
+ const o = this.redactPicks.indexOf(e);
1970
+ o >= 0 ? this.redactPicks.splice(o, 1) : this.redactPicks.push(e);
1971
+ const n = this.redactPickerRoot?.querySelector("[data-qaid-redact-label]");
1972
+ if (n) {
1973
+ const s = this.redactPicks.length;
1974
+ n.textContent = s === 0 ? "Click sensitive areas to blur" : `${s} area${s === 1 ? "" : "s"} will be blurred`;
1975
+ }
1976
+ }
1977
+ renderRedactPicks(t) {
1978
+ t.textContent = "";
1979
+ for (const e of this.redactPicks) {
1980
+ const o = e.getBoundingClientRect();
1981
+ o.width <= 0 || o.height <= 0 || t.appendChild(
1982
+ b("div", {
1983
+ style: `position:fixed;left:${o.left}px;top:${o.top}px;width:${o.width}px;height:${o.height}px;border:2px solid #ff6b6b;border-radius:4px;background:rgba(255,107,107,.18);pointer-events:none;`
1984
+ })
1985
+ );
1986
+ }
1987
+ }
1988
+ finishRedactionPicking(t) {
1989
+ this.redactRafId && cancelAnimationFrame(this.redactRafId), this.redactRafId = 0, document.removeEventListener("click", this.boundRedactClick, !0), document.removeEventListener("keydown", this.host.boundKeyDown), this.redactPickerRoot && (this.redactPickerRoot.remove(), this.redactPickerRoot = null);
1990
+ const e = this.redactPicks;
1991
+ this.redactPicks = [], this.host.setState("IDLE"), t && this.startRecording(e);
1992
+ }
1993
+ showRecordingIndicator() {
1994
+ const t = this.host.ensureOverlayHost();
1995
+ this.recordingIndicator = b(
1996
+ "div",
1997
+ { class: "qaid-recording-indicator", style: `z-index:${this.host.config.zIndex + 100}` },
1998
+ b("div", { class: "qaid-recording-dot" }),
1999
+ b("span", {
2000
+ class: "qaid-recording-time",
2001
+ text: this.formatTime(this.host.config.videoOptions.maxDuration)
2002
+ }),
2003
+ b("button", {
2004
+ class: "qaid-recording-stop",
2005
+ text: "Stop",
2006
+ attrs: { type: "button" },
2007
+ on: { click: () => this.stopRecording() }
2008
+ })
2009
+ ), this.host.applyVars(this.recordingIndicator), this.host.overlayShadowHost && (this.host.overlayShadowHost.style.pointerEvents = "auto"), t.appendChild(this.recordingIndicator);
2010
+ }
2011
+ updateRecordingTimer(t) {
2012
+ if (!this.recordingIndicator) return;
2013
+ const e = this.recordingIndicator.querySelector(".qaid-recording-time");
2014
+ if (e) {
2015
+ const o = Math.max(0, this.host.config.videoOptions.maxDuration - t);
2016
+ e.textContent = this.formatTime(o), o > 0 && o <= 5 && this.host.announceMsg(`${o} second${o === 1 ? "" : "s"} remaining`);
2017
+ }
2018
+ }
2019
+ formatTime(t) {
2020
+ const e = Math.floor(t / 60), o = t % 60;
2021
+ return `${e}:${o.toString().padStart(2, "0")}`;
2022
+ }
2023
+ removeRecordingIndicator() {
2024
+ this.recordingIndicator && (this.recordingIndicator.remove(), this.recordingIndicator = null);
2025
+ }
2026
+ showRecordingPreview() {
2027
+ const t = this.host.ensureOverlayHost(), e = URL.createObjectURL(this.recordedBlob), o = `qaid-video-title-${this.host.uid}`, n = b("h3", { text: "Review your recording", attrs: { id: o } }), s = b("video");
2028
+ s.src = e, s.controls = !0, s.autoplay = !0, s.muted = !0;
2029
+ const a = b("textarea", {
2030
+ attrs: {
2031
+ placeholder: "Optional: Describe the issue you recorded...",
2032
+ "aria-label": "Describe the issue you recorded"
2033
+ }
2034
+ }), c = b("button", {
2035
+ class: "qaid-video-btn qaid-video-btn-send",
2036
+ text: "Send",
2037
+ attrs: { type: "button" },
2038
+ on: { click: () => this.submitVideoFeedback(a.value.trim() || null, c) }
2039
+ }), l = b(
2040
+ "div",
2041
+ { class: "qaid-video-preview-box" },
2042
+ n,
2043
+ s,
2044
+ a,
2045
+ b(
2046
+ "div",
2047
+ { class: "qaid-video-preview-actions" },
2048
+ b("button", {
2049
+ class: "qaid-video-btn qaid-video-btn-cancel",
2050
+ text: "Cancel",
2051
+ attrs: { type: "button" },
2052
+ on: { click: () => this.cancelRecordingPreview() }
2053
+ }),
2054
+ b("button", {
2055
+ class: "qaid-video-btn qaid-video-btn-rerecord",
2056
+ text: "Re-record",
2057
+ attrs: { type: "button" },
2058
+ on: {
2059
+ click: () => {
2060
+ this.cancelRecordingPreview(), this.startRecording();
2061
+ }
2062
+ }
2063
+ }),
2064
+ c
2065
+ )
2066
+ );
2067
+ this.videoPreview = b(
2068
+ "div",
2069
+ { class: "qaid-video-preview", style: `z-index:${this.host.config.zIndex + 100}` },
2070
+ l
2071
+ ), this.host.applyVars(this.videoPreview), this.host.overlayShadowHost && (this.host.overlayShadowHost.style.pointerEvents = "auto"), t.appendChild(this.videoPreview), this.host.openDialogA11y(l, { labelledbyId: n.id }), document.addEventListener("keydown", this.host.boundKeyDown);
2072
+ }
2073
+ cancelRecordingPreview() {
2074
+ this.removeVideoPreview(), this.cleanupRecording();
2075
+ }
2076
+ removeVideoPreview() {
2077
+ if (this.host.closeDialogA11y(), this.videoPreview) {
2078
+ const t = this.videoPreview.querySelector("video");
2079
+ t?.src && URL.revokeObjectURL(t.src), this.videoPreview.remove(), this.videoPreview = null;
2080
+ }
2081
+ document.removeEventListener("keydown", this.host.boundKeyDown);
2082
+ }
2083
+ async submitVideoFeedback(t, e) {
2084
+ if (!this.recordedBlob || this.isSendingVideo) return;
2085
+ this.isSendingVideo = !0, e.disabled = !0, e.textContent = "Sending...";
2086
+ const o = new FormData();
2087
+ o.append("video", this.recordedBlob, `recording.${this.recordedBlob.type.includes("mp4") ? "mp4" : "webm"}`), o.append("pageUrl", window.location.href), o.append("visitorId", this.host.visitorId), this.host.config.apiKey && o.append("apiKey", this.host.config.apiKey), t && o.append("message", t), this.host.consoleCapture && o.append("consoleErrors", JSON.stringify(this.host.consoleCapture.errors)), this.networkCapture && o.append("networkErrors", JSON.stringify(this.networkCapture.errors));
2088
+ let n = null;
2089
+ try {
2090
+ const s = await fetch(`${this.host.config.endpoint}/video`, {
2091
+ method: "POST",
2092
+ body: o
2093
+ });
2094
+ if (s.ok) {
2095
+ this.host.announceMsg("Recording sent");
2096
+ try {
2097
+ n = (await s.json())?.id ?? null;
2098
+ } catch {
2099
+ }
2100
+ } else
2101
+ console.error("Failed to submit video feedback:", await s.text()), this.host.announceMsg("Failed to send recording", !0);
2102
+ } catch (s) {
2103
+ console.error("Failed to submit video feedback:", s), this.host.announceMsg("Failed to send recording", !0);
2104
+ }
2105
+ this.isSendingVideo = !1, this.removeVideoPreview(), this.cleanupRecording(), await this.host.tryLaunchQuest("video", n);
2106
+ }
2107
+ cleanupRecording() {
2108
+ this.isRecording = !1, this.removeRecordingIndicator(), this.videoRecorder && (this.videoRecorder.destroy(), this.videoRecorder = null), this.networkCapture && (this.networkCapture.restore(), this.networkCapture = null), this.recordedBlob && (this.recordedBlob = null), this.host.setButtonsDisabled(!1), this.host.overlayShadowHost && this.host.state === "IDLE" && (this.host.overlayShadowHost.style.pointerEvents = "none");
2109
+ }
2110
+ }
2111
+ const ct = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2112
+ __proto__: null,
2113
+ RecordingController: xi
2114
+ }, Symbol.toStringTag, { value: "Module" }));
2115
+ async function qi(i = {}) {
2116
+ const { quality: t = 1, maxWidth: e = 1280, maxHeight: o = 800 } = i;
2117
+ try {
2118
+ if (!navigator.mediaDevices?.getDisplayMedia)
2119
+ return console.warn("Screen Capture API not available"), null;
2120
+ const n = await navigator.mediaDevices.getDisplayMedia({
2121
+ preferCurrentTab: !0,
2122
+ video: {
2123
+ displaySurface: "browser"
2124
+ }
2125
+ }), s = n.getVideoTracks()[0], a = s.getSettings(), c = document.createElement("video");
2126
+ c.srcObject = n, c.muted = !0, await new Promise((m) => {
2127
+ c.onloadedmetadata = () => {
2128
+ c.play(), m();
2129
+ };
2130
+ }), await new Promise((m) => {
2131
+ const k = () => {
2132
+ c.readyState >= 2 ? m() : requestAnimationFrame(k);
2133
+ };
2134
+ k();
2135
+ }), await new Promise((m) => setTimeout(m, 100));
2136
+ const l = a.width || c.videoWidth, d = a.height || c.videoHeight, r = Math.min(e / l, o / d, 1), h = Math.round(l * r), p = Math.round(d * r), g = document.createElement("canvas");
2137
+ g.width = h, g.height = p;
2138
+ const v = g.getContext("2d");
2139
+ return v ? (v.drawImage(c, 0, 0, h, p), s.stop(), g.toDataURL("image/webp", t)) : (s.stop(), null);
2140
+ } catch (n) {
2141
+ return console.warn("Screenshot capture failed:", n), null;
2142
+ }
2143
+ }
2144
+ const lt = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2145
+ __proto__: null,
2146
+ captureScreenshot: qi
2147
+ }, Symbol.toStringTag, { value: "Module" }));
2148
+ class Ci {
2149
+ constructor(t) {
2150
+ this.host = t;
2151
+ }
2152
+ modalContainer = null;
2153
+ backdrop = null;
2154
+ open() {
2155
+ const t = this.host.ensureOverlayHost();
2156
+ this.backdrop = document.createElement("div"), this.backdrop.className = "qaid-backdrop", this.backdrop.style.zIndex = String(this.host.config.zIndex + 2), this.backdrop.style.background = `rgba(0, 0, 0, ${this.host.config.backdropOpacity})`, this.backdrop.addEventListener("click", () => this.close()), this.host.applyVars(this.backdrop), this.host.isMobile ? this.showBottomSheet() : this.showPositionedModal(), t.appendChild(this.backdrop), document.addEventListener("keydown", this.host.boundKeyDown);
2157
+ }
2158
+ /** Escape / external close. Runs the finalize-PATCH + teardown. */
2159
+ close() {
2160
+ this.host.state === "MODAL_OPEN" && this.host.feedbackId && fetch(`${this.host.config.endpoint}/${this.host.feedbackId}`, {
2161
+ method: "PATCH",
2162
+ headers: { "Content-Type": "application/json" },
2163
+ body: JSON.stringify({ message: null })
2164
+ }).catch((t) => console.error("Failed to finalize feedback:", t)), this.teardown(), this.host.resetFeedbackUi();
2165
+ }
2166
+ /** DOM/listener teardown only — no PATCH, no state reset (for embed destroy). */
2167
+ destroy() {
2168
+ this.teardown();
2169
+ }
2170
+ teardown() {
2171
+ this.host.closeDialogA11y(), this.modalContainer && (this.modalContainer.remove(), this.modalContainer = null), this.backdrop && (this.backdrop.remove(), this.backdrop = null), document.removeEventListener("keydown", this.host.boundKeyDown);
2172
+ }
2173
+ showBottomSheet() {
2174
+ const t = this.host.ensureOverlayHost(), e = document.createElement("div");
2175
+ e.className = "qaid-bottom-sheet", e.style.zIndex = String(this.host.config.zIndex + 3), e.innerHTML = `
2176
+ <div class="qaid-bottom-sheet-content">
2177
+ <div class="qaid-bottom-sheet-handle"></div>
2178
+ ${this.getModalContent()}
2179
+ </div>
2180
+ `, this.host.applyVars(e), t.appendChild(e), this.modalContainer = e, this.setupModalInteractions();
2181
+ }
2182
+ showPositionedModal() {
2183
+ const t = this.host.ensureOverlayHost(), { modal: e, arrow: o } = Te(
2184
+ this.host.selectedBounds,
2185
+ window.innerWidth,
2186
+ window.innerHeight,
2187
+ {
2188
+ width: this.host.config.modalWidth,
2189
+ height: 280,
2190
+ arrowHeight: 12,
2191
+ gap: 8,
2192
+ viewportPadding: 16
2193
+ }
2194
+ );
2195
+ this.modalContainer = document.createElement("div"), this.modalContainer.className = `qaid-modal-container qaid-${e.position}`, this.modalContainer.style.top = `${e.top}px`, this.modalContainer.style.left = `${e.left}px`, this.modalContainer.style.zIndex = String(this.host.config.zIndex + 3);
2196
+ const n = document.createElement("div");
2197
+ n.className = "qaid-modal-arrow", n.style.left = `${o.left}px`;
2198
+ const s = document.createElement("div");
2199
+ s.className = "qaid-modal-box", s.innerHTML = this.getModalContent(), this.modalContainer.appendChild(n), this.modalContainer.appendChild(s), this.host.applyVars(this.modalContainer), t.appendChild(this.modalContainer), this.setupModalInteractions();
2200
+ }
2201
+ getModalContent() {
2202
+ const t = this.host.feedbackData.feedbackType, e = t === "up", o = this.host.config.positiveIcon || V, n = this.host.config.negativeIcon || N, s = this.host.config.buttonClass ? `qaid-type-toggle qaid-type-toggle-custom ${this.host.config.buttonClass} ${e ? "qaid-btn-up" : "qaid-btn-down"}` : `qaid-type-toggle ${e ? "qaid-type-up" : "qaid-type-down"}`;
2203
+ return `
2204
+ <div class="qaid-modal-header">
2205
+ ${t === "neutral" ? `<span class="qaid-type-static" aria-hidden="true">${this.host.config.feedbackIcon || pt}</span>` : `<button type="button" class="${s}" title="Click to switch" aria-pressed="${e}" aria-label="${e ? "Feedback type: positive" : "Feedback type: negative"}">
2206
+ ${e ? o : n}
2207
+ </button>`}
2208
+ <div class="qaid-modal-header-text">
2209
+ <h3 class="qaid-modal-title" id="qaid-modal-title-${this.host.uid}">${this.host.config.text.modalTitle}</h3>
2210
+ <p class="qaid-modal-subtitle" id="qaid-modal-subtitle-${this.host.uid}">${this.host.config.text.modalSubtitle}</p>
2211
+ </div>
2212
+ </div>
2213
+ <textarea class="qaid-textarea" aria-label="${this.host.config.text.modalSubtitle}" placeholder="${this.host.config.text.placeholder}"></textarea>
2214
+ <div class="qaid-btn-row">
2215
+ <button type="button" class="qaid-btn-submit">${this.host.config.text.skipButton}</button>
2216
+ </div>
2217
+ `;
2218
+ }
2219
+ setupModalInteractions() {
2220
+ this.host.openDialogA11y(this.modalContainer, {
2221
+ labelledbyId: `qaid-modal-title-${this.host.uid}`,
2222
+ describedbyId: `qaid-modal-subtitle-${this.host.uid}`
2223
+ });
2224
+ const t = this.modalContainer.querySelector(".qaid-textarea"), e = this.modalContainer.querySelector(".qaid-btn-submit");
2225
+ t && (setTimeout(() => t.focus(), 100), t.addEventListener("input", () => {
2226
+ e && (e.textContent = t.value.trim() ? this.host.config.text.submitButton : this.host.config.text.skipButton);
2227
+ })), e && e.addEventListener("click", () => {
2228
+ const n = t?.value.trim() || null;
2229
+ this.submitMessage(n);
2230
+ });
2231
+ const o = this.modalContainer.querySelector(".qaid-type-toggle");
2232
+ o && o.addEventListener("click", () => {
2233
+ const n = this.host.feedbackData.feedbackType === "up" ? "down" : "up";
2234
+ this.host.feedbackData.feedbackType = n, this.host.config.buttonClass ? (o.classList.toggle("qaid-btn-up", n === "up"), o.classList.toggle("qaid-btn-down", n === "down")) : (o.classList.toggle("qaid-type-up", n === "up"), o.classList.toggle("qaid-type-down", n === "down"));
2235
+ const s = this.host.config.positiveIcon || V, a = this.host.config.negativeIcon || N;
2236
+ o.innerHTML = n === "up" ? s : a;
2237
+ const c = n === "up" ? "Feedback type: positive" : "Feedback type: negative";
2238
+ o.setAttribute("aria-pressed", String(n === "up")), o.setAttribute("aria-label", c), this.host.announceMsg(c), this.host.feedbackId && fetch(`${this.host.config.endpoint}/${this.host.feedbackId}`, {
2239
+ method: "PATCH",
2240
+ headers: { "Content-Type": "application/json" },
2241
+ body: JSON.stringify({ feedbackType: n })
2242
+ }).catch((l) => console.error("Failed to update feedback type:", l));
2243
+ });
2244
+ }
2245
+ async submitMessage(t) {
2246
+ if (this.host.feedbackId) {
2247
+ try {
2248
+ await fetch(`${this.host.config.endpoint}/${this.host.feedbackId}`, {
2249
+ method: "PATCH",
2250
+ headers: { "Content-Type": "application/json" },
2251
+ body: JSON.stringify({ message: t })
2252
+ });
2253
+ } catch (e) {
2254
+ console.error("Failed to submit feedback message:", e);
2255
+ }
2256
+ this.host.setFeedbackId(null);
2257
+ }
2258
+ this.close();
2259
+ }
2260
+ }
2261
+ const ht = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2262
+ __proto__: null,
2263
+ ModalController: Ci
2264
+ }, Symbol.toStringTag, { value: "Module" })), Ct = ["data-comp", "data-qa", "data-testid", "data-id"];
2265
+ function St(i, t = document.body) {
2266
+ let e = i;
2267
+ for (; e && e !== t; ) {
2268
+ for (const o of Ct) {
2269
+ const n = e.getAttribute(o);
2270
+ if (n)
2271
+ return `${o}="${n}"`;
2272
+ }
2273
+ e = e.parentElement;
2274
+ }
2275
+ return null;
2276
+ }
2277
+ function Si(i, t = 100) {
2278
+ const e = i.textContent?.trim().slice(0, t) || "";
2279
+ return e.length === t ? e + "..." : e;
2280
+ }
2281
+ function Ei(i) {
2282
+ let t = 1, e = i.previousElementSibling;
2283
+ for (; e; )
2284
+ t++, e = e.previousElementSibling;
2285
+ return t;
2286
+ }
2287
+ function Ti(i) {
2288
+ const t = [];
2289
+ let e = i;
2290
+ for (; e && e !== document.body && e !== document.documentElement; ) {
2291
+ const o = e.tagName.toLowerCase(), n = Ei(e);
2292
+ t.unshift(`${o}:nth-child(${n})`), e = e.parentElement;
2293
+ }
2294
+ return t.length > 0 ? `body > ${t.join(" > ")}` : "body";
2295
+ }
2296
+ function Ai(i) {
2297
+ return i.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, "\\$1");
2298
+ }
2299
+ function Li(i) {
2300
+ const t = St(i);
2301
+ return t ? `[${t}]` : i.id ? `#${Ai(i.id)}` : Ti(i);
2302
+ }
2303
+ function F(i) {
2304
+ const t = Si(i), e = St(i);
2305
+ return { selector: Li(i), text: t, dataAttr: e };
2306
+ }
2307
+ const Ii = [
2308
+ "a[href]",
2309
+ "button",
2310
+ "input:not([type=hidden])",
2311
+ "select",
2312
+ "textarea",
2313
+ "summary",
2314
+ "[tabindex]",
2315
+ "[role]",
2316
+ "img",
2317
+ "h1",
2318
+ "h2",
2319
+ "h3",
2320
+ "h4",
2321
+ "h5",
2322
+ "h6",
2323
+ "p",
2324
+ "li",
2325
+ "label",
2326
+ ...Ct.map((i) => `[${i}]`)
2327
+ ].join(","), Di = /^(?:A|BUTTON|INPUT|SELECT|TEXTAREA|SUMMARY)$/;
2328
+ function Mi(i) {
2329
+ if (i.hasAttribute("hidden") || i.getAttribute("aria-hidden") === "true") return !1;
2330
+ const t = i.ownerDocument?.defaultView;
2331
+ if (t && typeof t.getComputedStyle == "function") {
2332
+ const e = t.getComputedStyle(i);
2333
+ if (e.display === "none" || e.visibility === "hidden") return !1;
2334
+ }
2335
+ return !0;
2336
+ }
2337
+ function Ri(i = {}) {
2338
+ const t = i.root ?? document.body, e = i.selector ?? Ii, o = i.isExcluded ?? (() => !1), n = i.isVisible ?? Mi;
2339
+ return Array.from(t.querySelectorAll(e)).filter(
2340
+ (s) => !o(s) && n(s)
2341
+ );
2342
+ }
2343
+ function Pi(i) {
2344
+ const t = i.candidates ?? Ri({
2345
+ root: i.root,
2346
+ selector: i.selector,
2347
+ isExcluded: i.isExcluded,
2348
+ isVisible: i.isVisible
2349
+ }), e = i.moveFocus ?? !0, o = i.eventTarget ?? document, n = /* @__PURE__ */ new Set();
2350
+ let s = -1, a = !1;
2351
+ const c = i.initial !== void 0 ? i.initial : typeof document < "u" ? document.activeElement : null;
2352
+ if (c) {
2353
+ const u = t.indexOf(c);
2354
+ u >= 0 && (s = u);
2355
+ }
2356
+ s < 0 && t.length > 0 && (s = 0);
2357
+ function l(u) {
2358
+ if (!e) return;
2359
+ const y = u;
2360
+ if (typeof y.focus == "function") {
2361
+ u.getAttribute("tabindex") === null && !Di.test(u.tagName) && (u.setAttribute("tabindex", "-1"), n.add(u));
2362
+ try {
2363
+ y.focus();
2364
+ } catch {
2365
+ }
2366
+ }
2367
+ }
2368
+ function d() {
2369
+ const u = t[s];
2370
+ u && (l(u), i.onHighlight?.(u, s));
2371
+ }
2372
+ function r(u) {
2373
+ if (a || t.length === 0) return;
2374
+ const y = t.length;
2375
+ s = (u % y + y) % y, d();
2376
+ }
2377
+ function h() {
2378
+ r(s + 1);
2379
+ }
2380
+ function p() {
2381
+ r(s - 1);
2382
+ }
2383
+ function g() {
2384
+ return t[s] ?? null;
2385
+ }
2386
+ function v() {
2387
+ a || (a = !0, o.removeEventListener("keydown", x, !0), n.forEach((u) => u.removeAttribute("tabindex")), n.clear());
2388
+ }
2389
+ function m() {
2390
+ if (a) return;
2391
+ const u = g();
2392
+ v(), u && i.onSelect(u);
2393
+ }
2394
+ function k() {
2395
+ a || (v(), i.onCancel?.());
2396
+ }
2397
+ function x(u) {
2398
+ if (!a)
2399
+ switch (u.key) {
2400
+ case "Tab":
2401
+ u.preventDefault(), u.stopPropagation(), u.shiftKey ? p() : h();
2402
+ break;
2403
+ case "ArrowDown":
2404
+ case "ArrowRight":
2405
+ u.preventDefault(), u.stopPropagation(), h();
2406
+ break;
2407
+ case "ArrowUp":
2408
+ case "ArrowLeft":
2409
+ u.preventDefault(), u.stopPropagation(), p();
2410
+ break;
2411
+ case "Enter":
2412
+ case " ":
2413
+ case "Spacebar":
2414
+ u.preventDefault(), u.stopPropagation(), m();
2415
+ break;
2416
+ case "Escape":
2417
+ case "Esc":
2418
+ u.preventDefault(), u.stopPropagation(), k();
2419
+ break;
2420
+ }
2421
+ }
2422
+ return o.addEventListener("keydown", x, !0), s >= 0 && d(), {
2423
+ candidates: t,
2424
+ getIndex: () => s,
2425
+ getCurrent: g,
2426
+ next: h,
2427
+ prev: p,
2428
+ moveTo: r,
2429
+ select: m,
2430
+ cancel: k,
2431
+ handleKey: x,
2432
+ stop: v
2433
+ };
2434
+ }
2435
+ const Bi = 12;
2436
+ class Hi {
2437
+ constructor(t) {
2438
+ this.host = t;
2439
+ }
2440
+ mousePos = { x: 0, y: 0 };
2441
+ touchStartPos = null;
2442
+ overlayContainer = null;
2443
+ captureLayer = null;
2444
+ crosshairH = null;
2445
+ crosshairV = null;
2446
+ scope = null;
2447
+ highlightBox = null;
2448
+ marker = null;
2449
+ keyboardController = null;
2450
+ boundMouseMove = (t) => this.handleMouseMove(t);
2451
+ boundClick = (t) => this.handleClick(t);
2452
+ boundTouchStart = (t) => this.handleTouchStart(t);
2453
+ boundTouchEnd = (t) => this.handleTouchEnd(t);
2454
+ startPointer(t, e) {
2455
+ this.host.setState("TARGETING"), this.host.feedbackData.feedbackType = t, this.host.feedbackData.elementSelector = null, this.host.feedbackData.elementText = null, this.host.selectedBounds.visible = !1, this.mousePos.x = e.clientX, this.mousePos.y = e.clientY, document.body.classList.add("qaid-targeting"), t === "up" ? document.body.classList.add("qaid-type-up") : document.body.classList.remove("qaid-type-up"), document.body.style.setProperty("--qaid-positive", this.host.cssVars["--qaid-positive"]), document.body.style.setProperty("--qaid-negative", this.host.cssVars["--qaid-negative"]), this.createTargetingOverlay(), document.addEventListener("keydown", this.host.boundKeyDown), document.addEventListener("mousemove", this.boundMouseMove), document.addEventListener("click", this.boundClick, !0), document.addEventListener("touchstart", this.boundTouchStart, { passive: !0 }), document.addEventListener("touchend", this.boundTouchEnd, { passive: !1 });
2456
+ }
2457
+ /**
2458
+ * Keyboard-driven targeting. Mirrors startPointer minus the mouse plumbing:
2459
+ * no `qaid-targeting` body class (keeps the cursor visible for keyboard
2460
+ * users), no mouse reticle, and no document mouse/click listeners. The
2461
+ * KeyboardTargetingController owns Tab/Arrow/Enter/Space/Escape.
2462
+ */
2463
+ startKeyboard(t) {
2464
+ this.host.setState("TARGETING"), this.host.feedbackData.feedbackType = t, this.host.feedbackData.elementSelector = null, this.host.feedbackData.elementText = null, this.host.selectedBounds.visible = !1, document.body.style.setProperty("--qaid-positive", this.host.cssVars["--qaid-positive"]), document.body.style.setProperty("--qaid-negative", this.host.cssVars["--qaid-negative"]), this.createTargetingOverlay(), this.crosshairH && (this.crosshairH.style.display = "none"), this.crosshairV && (this.crosshairV.style.display = "none"), this.scope && (this.scope.style.display = "none"), this.keyboardController = Pi({
2465
+ isExcluded: (e) => M(e),
2466
+ onHighlight: (e) => {
2467
+ const o = e.getBoundingClientRect(), n = this.highlightBox;
2468
+ n && (n.style.transform = `translate(${o.left}px, ${o.top}px)`, n.style.width = `${o.width}px`, n.style.height = `${o.height}px`, n.style.display = "block");
2469
+ const { text: s } = F(e);
2470
+ this.host.announceMsg(`Targeting ${s || e.tagName.toLowerCase()}`);
2471
+ },
2472
+ onSelect: (e) => this.selectKeyboardTarget(e),
2473
+ onCancel: () => this.cancel()
2474
+ });
2475
+ }
2476
+ selectKeyboardTarget(t) {
2477
+ const e = Z(t, 8);
2478
+ Object.assign(this.host.selectedBounds, e, {
2479
+ clickX: e.x + e.width / 2,
2480
+ clickY: e.y + e.height / 2,
2481
+ visible: !0
2482
+ });
2483
+ const { selector: o, text: n } = F(t);
2484
+ this.host.feedbackData.elementSelector = o, this.host.feedbackData.elementText = n, this.removeTargetingOverlay(), document.body.classList.remove("qaid-targeting", "qaid-type-up"), document.body.style.removeProperty("--qaid-positive"), document.body.style.removeProperty("--qaid-negative"), this.keyboardController = null, this.host.clearActiveThumb(), this.host.setState("SELECTED"), this.showSelectedMarker(), this.host.submitFeedback();
2485
+ }
2486
+ createTargetingOverlay() {
2487
+ const t = this.host.ensureOverlayHost();
2488
+ this.overlayContainer = document.createElement("div"), this.overlayContainer.className = `qaid-targeting-overlay qaid-type-${this.host.feedbackData.feedbackType}`, this.captureLayer = document.createElement("div"), this.captureLayer.className = "qaid-capture-layer";
2489
+ const e = document.createElement("div");
2490
+ e.className = "qaid-vignette", this.crosshairH = document.createElement("div"), this.crosshairH.className = "qaid-crosshair-h", this.crosshairV = document.createElement("div"), this.crosshairV.className = "qaid-crosshair-v", this.scope = document.createElement("div"), this.scope.className = "qaid-scope", this.scope.innerHTML = `
2491
+ <div class="qaid-scope-ring"></div>
2492
+ <div class="qaid-scope-ring-inner"></div>
2493
+ <div class="qaid-scope-dot"></div>
2494
+ `, this.highlightBox = document.createElement("div"), this.highlightBox.className = "qaid-highlight-box", this.overlayContainer.appendChild(this.captureLayer), this.overlayContainer.appendChild(e), this.overlayContainer.appendChild(this.highlightBox), this.overlayContainer.appendChild(this.crosshairH), this.overlayContainer.appendChild(this.crosshairV), this.overlayContainer.appendChild(this.scope), this.crosshairH.style.top = `${this.mousePos.y}px`, this.crosshairV.style.left = `${this.mousePos.x}px`, this.scope.style.left = `${this.mousePos.x}px`, this.scope.style.top = `${this.mousePos.y}px`, this.host.applyVars(this.overlayContainer), t.appendChild(this.overlayContainer);
2495
+ }
2496
+ handleMouseMove(t) {
2497
+ this.updateReticleAt(t.clientX, t.clientY);
2498
+ }
2499
+ /** Move the crosshair/scope reticle and highlight the element under (x, y). */
2500
+ updateReticleAt(t, e) {
2501
+ if (this.mousePos.x = t, this.mousePos.y = e, this.crosshairH && (this.crosshairH.style.top = `${e}px`), this.crosshairV && (this.crosshairV.style.left = `${t}px`), this.scope && (this.scope.style.left = `${t}px`, this.scope.style.top = `${e}px`), this.captureLayer && this.host.shadowHost) {
2502
+ const o = [this.host.shadowHost, this.host.overlayShadowHost].filter(Boolean), n = J(t, e, o);
2503
+ if (n && !M(n)) {
2504
+ if (this.highlightBox) {
2505
+ const s = n.getBoundingClientRect();
2506
+ this.highlightBox.style.transform = `translate(${s.left}px, ${s.top}px)`, this.highlightBox.style.width = `${s.width}px`, this.highlightBox.style.height = `${s.height}px`, this.highlightBox.style.display = "block";
2507
+ }
2508
+ } else this.highlightBox && (this.highlightBox.style.display = "none");
2509
+ }
2510
+ }
2511
+ handleClick(t) {
2512
+ t.preventDefault(), t.stopPropagation(), this.selectAt(t.clientX, t.clientY);
2513
+ }
2514
+ // ---- Touch targeting (iOS/iPadOS) ----
2515
+ handleTouchStart(t) {
2516
+ const e = t.touches[0];
2517
+ e && (this.touchStartPos = { x: e.clientX, y: e.clientY }, this.updateReticleAt(e.clientX, e.clientY));
2518
+ }
2519
+ handleTouchEnd(t) {
2520
+ const e = t.changedTouches[0], o = this.touchStartPos;
2521
+ this.touchStartPos = null, !(!e || !o || Math.hypot(e.clientX - o.x, e.clientY - o.y) > Bi) && (t.preventDefault(), this.selectAt(e.clientX, e.clientY));
2522
+ }
2523
+ /** Select the element under (x, y) and tear down targeting. */
2524
+ selectAt(t, e) {
2525
+ const o = [this.host.shadowHost, this.host.overlayShadowHost].filter(Boolean), n = J(t, e, o);
2526
+ if (!n || M(n))
2527
+ return;
2528
+ const s = Z(n, 8);
2529
+ Object.assign(this.host.selectedBounds, s, { clickX: t, clickY: e, visible: !0 });
2530
+ const { selector: a, text: c } = F(n);
2531
+ this.host.feedbackData.elementSelector = a, this.host.feedbackData.elementText = c, this.stopTargetingListeners(), document.body.classList.remove("qaid-targeting", "qaid-type-up"), document.body.style.removeProperty("--qaid-positive"), document.body.style.removeProperty("--qaid-negative"), this.host.setState("SELECTED"), this.host.clearActiveThumb(), this.showSelectedMarker(), this.host.submitFeedback();
2532
+ }
2533
+ stopTargetingListeners() {
2534
+ this.removeTargetingOverlay(), this.touchStartPos = null, document.removeEventListener("mousemove", this.boundMouseMove), document.removeEventListener("click", this.boundClick, !0), document.removeEventListener("touchstart", this.boundTouchStart), document.removeEventListener("touchend", this.boundTouchEnd), document.removeEventListener("keydown", this.host.boundKeyDown);
2535
+ }
2536
+ cancel() {
2537
+ this.keyboardController?.stop(), this.keyboardController = null, this.stopTargetingListeners(), document.body.classList.remove("qaid-targeting", "qaid-type-up"), document.body.style.removeProperty("--qaid-positive"), document.body.style.removeProperty("--qaid-negative"), this.host.clearActiveThumb(), this.host.setState("IDLE"), this.host.feedbackData.feedbackType = null, this.host.feedbackData.elementSelector = null, this.host.feedbackData.elementText = null, this.host.selectedBounds.visible = !1;
2538
+ }
2539
+ removeTargetingOverlay() {
2540
+ this.overlayContainer && (this.overlayContainer.remove(), this.overlayContainer = null), this.captureLayer = null, this.crosshairH = null, this.crosshairV = null, this.scope = null;
2541
+ }
2542
+ showSelectedMarker() {
2543
+ const t = this.host.ensureOverlayHost();
2544
+ this.marker = document.createElement("div"), this.marker.className = "qaid-selected-marker", this.marker.style.left = `${this.host.selectedBounds.x}px`, this.marker.style.top = `${this.host.selectedBounds.y}px`, this.marker.style.width = `${this.host.selectedBounds.width}px`, this.marker.style.height = `${this.host.selectedBounds.height}px`, this.marker.style.zIndex = String(this.host.config.zIndex + 1), this.host.applyVars(this.marker), t.appendChild(this.marker);
2545
+ }
2546
+ hideMarker() {
2547
+ this.marker && (this.marker.remove(), this.marker = null);
2548
+ }
2549
+ /** Full teardown for embed.destroy(). */
2550
+ destroy() {
2551
+ this.keyboardController?.stop(), this.keyboardController = null, this.stopTargetingListeners(), this.hideMarker();
2552
+ }
2553
+ }
2554
+ const ut = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2555
+ __proto__: null,
2556
+ TargetingController: Hi
2557
+ }, Symbol.toStringTag, { value: "Module" }));
8
2558
  export {
9
- x as AnnotationEditor,
10
- a as QaidFeedback,
11
- f as captureDomScreenshot,
12
- i as captureNetworkErrors,
13
- A as compositeAnnotations,
14
- n as createVideoRecorder,
15
- h as drawShape,
16
- d as getSupportedMimeType,
17
- u as isDomScreenshotSupported,
18
- c as isVideoRecordingSupported,
19
- E as openAnnotationEditor,
20
- g as rectFromPoints
2559
+ qt as AnnotationEditor,
2560
+ _e as QaidFeedback,
2561
+ ri as captureDomScreenshot,
2562
+ Ke as captureNetworkErrors,
2563
+ xt as compositeAnnotations,
2564
+ ii as createVideoRecorder,
2565
+ B as drawShape,
2566
+ yt as getSupportedMimeType,
2567
+ si as isDomScreenshotSupported,
2568
+ ei as isVideoRecordingSupported,
2569
+ wi as openAnnotationEditor,
2570
+ P as rectFromPoints
21
2571
  };
22
2572
  //# sourceMappingURL=index.js.map