@qaiddev/thumbs-embed 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/auto.d.ts +24 -0
  2. package/dist/auto.js +2 -0
  3. package/dist/auto.js.map +1 -0
  4. package/dist/bootstrap.d.ts +15 -0
  5. package/dist/chunks/annotate-BNibaDg3.js +367 -0
  6. package/dist/chunks/annotate-BNibaDg3.js.map +1 -0
  7. package/dist/chunks/bootstrap-U4yiPm5-.js +1099 -0
  8. package/dist/chunks/bootstrap-U4yiPm5-.js.map +1 -0
  9. package/dist/chunks/modal-BX5wqy4s.js +119 -0
  10. package/dist/chunks/modal-BX5wqy4s.js.map +1 -0
  11. package/dist/chunks/network-capture-DMbMwwwR.js +63 -0
  12. package/dist/chunks/network-capture-DMbMwwwR.js.map +1 -0
  13. package/dist/chunks/recording--IwOtRL2.js +256 -0
  14. package/dist/chunks/recording--IwOtRL2.js.map +1 -0
  15. package/dist/chunks/screenshot-BkOCLuS2.js +33 -0
  16. package/dist/chunks/screenshot-BkOCLuS2.js.map +1 -0
  17. package/dist/chunks/screenshot-dom-CME7tYRE.js +63 -0
  18. package/dist/chunks/screenshot-dom-CME7tYRE.js.map +1 -0
  19. package/dist/chunks/targeting-DwdDr9Cm.js +295 -0
  20. package/dist/chunks/targeting-DwdDr9Cm.js.map +1 -0
  21. package/dist/chunks/video-BOYqf2Im.js +209 -0
  22. package/dist/chunks/video-BOYqf2Im.js.map +1 -0
  23. package/dist/dom.d.ts +36 -0
  24. package/dist/embed.d.ts +53 -68
  25. package/dist/index.d.ts +10 -12
  26. package/dist/index.js +22 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/loader.d.ts +18 -0
  29. package/dist/loader.js +7 -0
  30. package/dist/loader.js.map +1 -0
  31. package/dist/modal.d.ts +49 -0
  32. package/dist/qaid.umd.cjs +23 -23
  33. package/dist/qaid.umd.cjs.map +1 -1
  34. package/dist/recording.d.ts +74 -0
  35. package/dist/targeting.d.ts +72 -0
  36. package/dist/types.d.ts +7 -1
  37. package/dist/video-capture.d.ts +7 -0
  38. package/dist/video-orientation.d.ts +2 -0
  39. package/dist/video-redaction.d.ts +58 -0
  40. package/package.json +12 -5
  41. package/dist/qaid.js +0 -2013
  42. package/dist/qaid.js.map +0 -1
package/dist/qaid.js DELETED
@@ -1,2013 +0,0 @@
1
- const D = `<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>`, M = `<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>`, xt = `<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>`, kt = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
8
- <rect x="4" y="6" width="16" height="12" rx="1" stroke-linejoin="round"/>
9
- </svg>`, Ct = `<svg xmlns="http://www.w3.org/2000/svg" class="qaid-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
10
- <path stroke-linecap="round" stroke-linejoin="round" d="M5 19L19 5m0 0h-8m8 0v8"/>
11
- </svg>`, Et = `<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="M12 20h9M16.5 3.5a2.121 2.121 0 013 3L7 19l-4 1 1-4L16.5 3.5z"/>
13
- </svg>`, St = `<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 3s6 6.4 6 10a6 6 0 01-12 0c0-3.6 6-10 6-10z"/>
15
- </svg>`, Tt = `<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="M9 14L4 9l5-5M4 9h11a5 5 0 010 10h-4"/>
17
- </svg>`, Lt = `<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="M20 6L9 17l-5-5"/>
19
- </svg>`, It = `<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="M6 6l12 12M18 6L6 18"/>
21
- </svg>`, At = "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-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{padding:0}button.qaid-annotate-tool svg,button.qaid-annotate-action svg{width:20px;height:20px}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}}", Dt = "body.qaid-targeting,body.qaid-targeting *{cursor:none!important}";
22
- let E = 0;
23
- const Mt = "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 svg{width:var(--qaid-icon-size);height:var(--qaid-icon-size)}", Rt = {
24
- small: 36,
25
- medium: 48,
26
- large: 64
27
- }, Bt = {
28
- small: 18,
29
- medium: 24,
30
- large: 32
31
- };
32
- function Ht(e, t, i) {
33
- const [o, n, s] = [e, t, i].map((a) => (a = a / 255, a <= 0.03928 ? a / 12.92 : Math.pow((a + 0.055) / 1.055, 2.4)));
34
- return 0.2126 * o + 0.7152 * n + 0.0722 * s;
35
- }
36
- function Pt(e) {
37
- if (e.startsWith("#")) {
38
- const i = e.slice(1), o = i.length === 3 ? i.split("").map((s) => s + s).join("") : i, n = parseInt(o, 16);
39
- return {
40
- r: n >> 16 & 255,
41
- g: n >> 8 & 255,
42
- b: n & 255
43
- };
44
- }
45
- const t = e.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
46
- return t ? {
47
- r: parseInt(t[1]),
48
- g: parseInt(t[2]),
49
- b: parseInt(t[3])
50
- } : null;
51
- }
52
- function Ot(e) {
53
- const t = Pt(e);
54
- return t && Ht(t.r, t.g, t.b) > 0.4 ? "black" : "white";
55
- }
56
- function zt(e = {}) {
57
- const {
58
- positiveColor: t = "rgb(0, 200, 83)",
59
- negativeColor: i = "rgb(255, 0, 0)",
60
- markerColor: o = "#6366f1",
61
- buttonSize: n = "medium",
62
- modalWidth: s = 400,
63
- backdropOpacity: a = 0.3,
64
- fontFamily: d = "system-ui, -apple-system, sans-serif",
65
- fontSize: c = 16
66
- } = e, r = Rt[n], l = Bt[n], p = Ot(o);
67
- return {
68
- "--qaid-positive": t,
69
- "--qaid-negative": i,
70
- "--qaid-marker": o,
71
- "--qaid-marker-text": p,
72
- "--qaid-btn-size": `${r}px`,
73
- "--qaid-icon-size": `${l}px`,
74
- "--qaid-modal-width": `${s}px`,
75
- "--qaid-backdrop-opacity": String(a),
76
- "--qaid-font-family": d,
77
- "--qaid-font-size": `${c}px`
78
- };
79
- }
80
- function $t(e, t) {
81
- for (const [i, o] of Object.entries(t))
82
- e.style.setProperty(i, o);
83
- }
84
- function _() {
85
- return At + Mt;
86
- }
87
- function Nt() {
88
- if (E++, E > 1) return;
89
- const e = document.createElement("style");
90
- e.id = "qaid-styles", e.textContent = Dt, document.head.appendChild(e);
91
- }
92
- function Ut() {
93
- E <= 0 || (E--, E === 0 && document.getElementById("qaid-styles").remove());
94
- }
95
- const et = ["data-comp", "data-qa", "data-testid", "data-id"];
96
- function it(e, t = document.body) {
97
- let i = e;
98
- for (; i && i !== t; ) {
99
- for (const o of et) {
100
- const n = i.getAttribute(o);
101
- if (n)
102
- return `${o}="${n}"`;
103
- }
104
- i = i.parentElement;
105
- }
106
- return null;
107
- }
108
- function Ft(e, t = 100) {
109
- const i = e.textContent?.trim().slice(0, t) || "";
110
- return i.length === t ? i + "..." : i;
111
- }
112
- function Vt(e) {
113
- let t = 1, i = e.previousElementSibling;
114
- for (; i; )
115
- t++, i = i.previousElementSibling;
116
- return t;
117
- }
118
- function _t(e) {
119
- const t = [];
120
- let i = e;
121
- for (; i && i !== document.body && i !== document.documentElement; ) {
122
- const o = i.tagName.toLowerCase(), n = Vt(i);
123
- t.unshift(`${o}:nth-child(${n})`), i = i.parentElement;
124
- }
125
- return t.length > 0 ? `body > ${t.join(" > ")}` : "body";
126
- }
127
- function Kt(e) {
128
- return e.replace(/([!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, "\\$1");
129
- }
130
- function jt(e) {
131
- const t = it(e);
132
- return t ? `[${t}]` : e.id ? `#${Kt(e.id)}` : _t(e);
133
- }
134
- function R(e) {
135
- const t = Ft(e), i = it(e);
136
- return { selector: jt(e), text: t, dataAttr: i };
137
- }
138
- const Wt = [
139
- "a[href]",
140
- "button",
141
- "input:not([type=hidden])",
142
- "select",
143
- "textarea",
144
- "summary",
145
- "[tabindex]",
146
- "[role]",
147
- "img",
148
- "h1",
149
- "h2",
150
- "h3",
151
- "h4",
152
- "h5",
153
- "h6",
154
- "p",
155
- "li",
156
- "label",
157
- ...et.map((e) => `[${e}]`)
158
- ].join(","), Xt = /^(?:A|BUTTON|INPUT|SELECT|TEXTAREA|SUMMARY)$/;
159
- function Yt(e) {
160
- if (e.hasAttribute("hidden") || e.getAttribute("aria-hidden") === "true") return !1;
161
- const t = e.ownerDocument?.defaultView;
162
- if (t && typeof t.getComputedStyle == "function") {
163
- const i = t.getComputedStyle(e);
164
- if (i.display === "none" || i.visibility === "hidden") return !1;
165
- }
166
- return !0;
167
- }
168
- function Qt(e = {}) {
169
- const t = e.root ?? document.body, i = e.selector ?? Wt, o = e.isExcluded ?? (() => !1), n = e.isVisible ?? Yt;
170
- return Array.from(t.querySelectorAll(i)).filter(
171
- (s) => !o(s) && n(s)
172
- );
173
- }
174
- function Gt(e) {
175
- const t = e.candidates ?? Qt({
176
- root: e.root,
177
- selector: e.selector,
178
- isExcluded: e.isExcluded,
179
- isVisible: e.isVisible
180
- }), i = e.moveFocus ?? !0, o = e.eventTarget ?? document, n = /* @__PURE__ */ new Set();
181
- let s = -1, a = !1;
182
- const d = e.initial !== void 0 ? e.initial : typeof document < "u" ? document.activeElement : null;
183
- if (d) {
184
- const h = t.indexOf(d);
185
- h >= 0 && (s = h);
186
- }
187
- s < 0 && t.length > 0 && (s = 0);
188
- function c(h) {
189
- if (!i) return;
190
- const w = h;
191
- if (typeof w.focus == "function") {
192
- h.getAttribute("tabindex") === null && !Xt.test(h.tagName) && (h.setAttribute("tabindex", "-1"), n.add(h));
193
- try {
194
- w.focus();
195
- } catch {
196
- }
197
- }
198
- }
199
- function r() {
200
- const h = t[s];
201
- h && (c(h), e.onHighlight?.(h, s));
202
- }
203
- function l(h) {
204
- if (a || t.length === 0) return;
205
- const w = t.length;
206
- s = (h % w + w) % w, r();
207
- }
208
- function p() {
209
- l(s + 1);
210
- }
211
- function u() {
212
- l(s - 1);
213
- }
214
- function f() {
215
- return t[s] ?? null;
216
- }
217
- function b() {
218
- a || (a = !0, o.removeEventListener("keydown", m, !0), n.forEach((h) => h.removeAttribute("tabindex")), n.clear());
219
- }
220
- function v() {
221
- if (a) return;
222
- const h = f();
223
- b(), h && e.onSelect(h);
224
- }
225
- function y() {
226
- a || (b(), e.onCancel?.());
227
- }
228
- function m(h) {
229
- if (!a)
230
- switch (h.key) {
231
- case "Tab":
232
- h.preventDefault(), h.stopPropagation(), h.shiftKey ? u() : p();
233
- break;
234
- case "ArrowDown":
235
- case "ArrowRight":
236
- h.preventDefault(), h.stopPropagation(), p();
237
- break;
238
- case "ArrowUp":
239
- case "ArrowLeft":
240
- h.preventDefault(), h.stopPropagation(), u();
241
- break;
242
- case "Enter":
243
- case " ":
244
- case "Spacebar":
245
- h.preventDefault(), h.stopPropagation(), v();
246
- break;
247
- case "Escape":
248
- case "Esc":
249
- h.preventDefault(), h.stopPropagation(), y();
250
- break;
251
- }
252
- }
253
- return o.addEventListener("keydown", m, !0), s >= 0 && r(), {
254
- candidates: t,
255
- getIndex: () => s,
256
- getCurrent: f,
257
- next: p,
258
- prev: u,
259
- moveTo: l,
260
- select: v,
261
- cancel: y,
262
- handleKey: m,
263
- stop: b
264
- };
265
- }
266
- const K = "data-qaid-a11y-live", Jt = "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;", Zt = [
267
- "a[href]",
268
- "button",
269
- "input",
270
- "textarea",
271
- "select",
272
- "[tabindex]"
273
- ].join(",");
274
- function te(e) {
275
- return e.ownerDocument || document;
276
- }
277
- function ee(e, t) {
278
- const i = t ? "assertive" : "polite", o = e.querySelector(
279
- `[${K}="${i}"]`
280
- );
281
- if (o) return o;
282
- const s = te(e).createElement("div");
283
- return s.setAttribute(K, i), s.setAttribute("role", t ? "alert" : "status"), s.setAttribute("aria-live", t ? "assertive" : "polite"), s.setAttribute("aria-atomic", "true"), s.style.cssText = Jt, e.appendChild(s), s;
284
- }
285
- function ie(e, t, i = {}) {
286
- const o = ee(e, !!i.assertive);
287
- o.textContent = "", o.textContent = t;
288
- }
289
- function oe(e) {
290
- return e.hasAttribute("disabled") ? !0 : e.disabled === !0;
291
- }
292
- function ne(e) {
293
- let t = e;
294
- for (; t; ) {
295
- if (t.hasAttribute("hidden")) return !0;
296
- const i = t.style;
297
- if (i && (i.display === "none" || i.visibility === "hidden" || i.visibility === "collapse"))
298
- return !0;
299
- t = t.parentElement;
300
- }
301
- return !1;
302
- }
303
- function j(e) {
304
- return Array.from(
305
- e.querySelectorAll(Zt)
306
- ).filter((i) => !(i.getAttribute("tabindex") === "-1" || i instanceof HTMLInputElement && i.type === "hidden" || i instanceof HTMLAnchorElement && !i.getAttribute("href") || oe(i) || ne(i)));
307
- }
308
- function ot(e) {
309
- let t = e.activeElement;
310
- for (; t && t.shadowRoot && t.shadowRoot.activeElement; )
311
- t = t.shadowRoot.activeElement;
312
- return t instanceof HTMLElement ? t : null;
313
- }
314
- function se(e) {
315
- const t = e.ownerDocument || document;
316
- let i = !1;
317
- const o = (s) => {
318
- if (s.key !== "Tab") return;
319
- const a = j(e);
320
- if (a.length === 0) {
321
- s.preventDefault(), e.focus();
322
- return;
323
- }
324
- const d = a[0], c = a[a.length - 1], r = ot(t), l = r ? a.indexOf(r) !== -1 : !1;
325
- s.shiftKey ? (!l || r === d) && (s.preventDefault(), c.focus()) : (!l || r === c) && (s.preventDefault(), d.focus());
326
- };
327
- e.addEventListener("keydown", o);
328
- const n = j(e);
329
- return n.length > 0 ? n[0].focus() : (e.hasAttribute("tabindex") || (e.setAttribute("tabindex", "-1"), i = !0), e.focus()), {
330
- release() {
331
- e.removeEventListener("keydown", o), i && (e.removeAttribute("tabindex"), i = !1);
332
- }
333
- };
334
- }
335
- function ae() {
336
- return ot(document);
337
- }
338
- function re(e) {
339
- if (!(!e || typeof e.focus != "function"))
340
- try {
341
- e.focus();
342
- } catch {
343
- }
344
- }
345
- function de(e, t = {}) {
346
- e.setAttribute("role", "dialog"), e.setAttribute("aria-modal", "true"), t.labelledbyId && e.setAttribute("aria-labelledby", t.labelledbyId), t.describedbyId && e.setAttribute("aria-describedby", t.describedbyId), t.label && e.setAttribute("aria-label", t.label);
347
- }
348
- function le(e, t) {
349
- let i = e;
350
- for (; i && i !== t; ) {
351
- const o = i.parentNode;
352
- if (o === t) return i;
353
- if (o && o.host) {
354
- i = o.host;
355
- continue;
356
- }
357
- if (!o) return null;
358
- i = o;
359
- }
360
- return null;
361
- }
362
- function ce(e) {
363
- const i = (e.ownerDocument || document).body;
364
- if (!i) return () => {
365
- };
366
- const o = le(e, i), n = [];
367
- return Array.from(i.children).forEach((s) => {
368
- if (!(s instanceof HTMLElement) || o && s === o) return;
369
- const a = s.inert === !0, d = s.getAttribute("aria-hidden");
370
- a && d === "true" || (s.inert = !0, s.setAttribute("aria-hidden", "true"), n.push({ el: s, prevInert: a, prevAriaHidden: d }));
371
- }), function() {
372
- for (; n.length; ) {
373
- const a = n.pop();
374
- a.el.inert = a.prevInert, a.prevAriaHidden === null ? a.el.removeAttribute("aria-hidden") : a.el.setAttribute("aria-hidden", a.prevAriaHidden);
375
- }
376
- };
377
- }
378
- const nt = {
379
- width: 400,
380
- height: 280,
381
- arrowHeight: 12,
382
- gap: 8,
383
- viewportPadding: 16
384
- };
385
- function he(e, t, i, o, n) {
386
- const s = e.y, a = t - (e.y + e.height);
387
- if (a >= i + o)
388
- return {
389
- top: e.y + e.height + o,
390
- position: "below"
391
- };
392
- if (s >= i + o)
393
- return {
394
- top: e.y - i - o,
395
- position: "above"
396
- };
397
- const d = a > s ? "below" : "above";
398
- let c;
399
- return d === "below" ? c = Math.min(
400
- e.y + e.height + o,
401
- t - i - n
402
- ) : c = Math.max(n, e.y - i - o), { top: c, position: d };
403
- }
404
- function ue(e, t, i, o) {
405
- let s = e.x + e.width / 2 - i / 2;
406
- return s = Math.max(o, Math.min(s, t - i - o)), s;
407
- }
408
- function pe(e, t, i, o = 24, n = 24) {
409
- const a = e.clickX - t - n / 2;
410
- return Math.max(o, Math.min(a, i - o - n / 2));
411
- }
412
- function fe(e, t, i, o = nt) {
413
- const n = o.height + o.arrowHeight, s = he(
414
- e,
415
- i,
416
- n,
417
- o.gap,
418
- o.viewportPadding
419
- ), a = ue(
420
- e,
421
- t,
422
- o.width,
423
- o.viewportPadding
424
- );
425
- return {
426
- top: s.top,
427
- left: a,
428
- position: s.position
429
- };
430
- }
431
- function me(e, t, i, o = {}) {
432
- const n = o.gap ?? 8;
433
- let s = e.bottom + n;
434
- s + t.height > i.height - n && (s = e.top - t.height - n);
435
- let a = e.left;
436
- return a < n ? a = n : a + t.width > i.width - n && (a = i.width - t.width - n), s < n ? s = n : s + t.height > i.height - n && (s = i.height - t.height - n), { top: s, left: a };
437
- }
438
- function be(e, t, i, o = nt) {
439
- const n = fe(e, t, i, o), s = pe(
440
- e,
441
- n.left,
442
- o.width
443
- );
444
- return {
445
- modal: n,
446
- arrow: { left: s }
447
- };
448
- }
449
- const ge = 20;
450
- function ve(e) {
451
- const t = [], i = console.error, o = console.warn, n = console.log, s = (a, d) => {
452
- const c = {
453
- message: d.map((r) => String(r)).join(" "),
454
- timestamp: Date.now(),
455
- level: a
456
- };
457
- t.length >= ge && t.shift(), t.push(c), e && e(c);
458
- };
459
- return console.error = function(...a) {
460
- s("error", a), i.apply(console, a);
461
- }, console.warn = function(...a) {
462
- s("warn", a), o.apply(console, a);
463
- }, console.log = function(...a) {
464
- s("log", a), n.apply(console, a);
465
- }, {
466
- errors: t,
467
- restore: () => {
468
- console.error = i, console.warn = o, console.log = n;
469
- }
470
- };
471
- }
472
- const ye = 20, W = 4096;
473
- function T(e) {
474
- const t = typeof e == "string" ? e : JSON.stringify(e);
475
- return t.length > W ? t.slice(0, W) + "…[truncated]" : t;
476
- }
477
- function X(e, t) {
478
- e.length >= ye && e.shift(), e.push(t);
479
- }
480
- async function we(e) {
481
- try {
482
- const t = await e.clone().text();
483
- return T(t);
484
- } catch {
485
- return;
486
- }
487
- }
488
- function qe() {
489
- const e = [], t = window.fetch;
490
- window.fetch = async function(n, s) {
491
- const a = typeof n == "string" ? n : n instanceof URL ? n.toString() : n.url, d = s?.method ?? (typeof n == "object" && "method" in n ? n.method : "GET");
492
- let c;
493
- s?.body && (c = T(s.body));
494
- const r = await t.apply(window, [n, s]);
495
- if (r.status >= 400) {
496
- const l = await we(r);
497
- X(e, {
498
- url: a,
499
- method: d.toUpperCase(),
500
- status: r.status,
501
- statusText: r.statusText,
502
- requestBody: c,
503
- responseBody: l,
504
- timestamp: Date.now()
505
- });
506
- }
507
- return r;
508
- };
509
- const i = XMLHttpRequest.prototype.open, o = XMLHttpRequest.prototype.send;
510
- return XMLHttpRequest.prototype.open = function(n, s, ...a) {
511
- return this._qaid_method = n, this._qaid_url = typeof s == "string" ? s : s.toString(), i.apply(this, [n, s, ...a]);
512
- }, XMLHttpRequest.prototype.send = function(n) {
513
- const s = this, a = n ? T(n) : void 0;
514
- return s.addEventListener("load", function() {
515
- s.status >= 400 && X(e, {
516
- url: s._qaid_url,
517
- method: s._qaid_method.toUpperCase(),
518
- status: s.status,
519
- statusText: s.statusText,
520
- requestBody: a,
521
- responseBody: T(s.responseText),
522
- timestamp: Date.now()
523
- });
524
- }), o.apply(this, [n]);
525
- }, {
526
- errors: e,
527
- restore: () => {
528
- window.fetch = t, XMLHttpRequest.prototype.open = i, XMLHttpRequest.prototype.send = o;
529
- }
530
- };
531
- }
532
- function xe() {
533
- const e = navigator.userAgent || "";
534
- return /iP(hone|ad|od)/.test(e) || // iPadOS 13+ masquerades as "MacIntel" but is a multi-touch device.
535
- navigator.platform === "MacIntel" && (navigator.maxTouchPoints || 0) > 1;
536
- }
537
- function ke(e, t, i, o, n) {
538
- const s = e >= t, a = i >= o;
539
- return s === a ? { width: e, height: t, rotate: 0 } : { width: t, height: e, rotate: n === 270 ? -90 : 90 };
540
- }
541
- function Ce(e, t, i) {
542
- if (i.rotate === 0) {
543
- e.drawImage(t, 0, 0, i.width, i.height);
544
- return;
545
- }
546
- e.save(), i.rotate === 90 ? (e.translate(i.width, 0), e.rotate(Math.PI / 2)) : (e.translate(0, i.height), e.rotate(-Math.PI / 2)), e.drawImage(t, 0, 0, i.height, i.width), e.restore();
547
- }
548
- function Ee() {
549
- const e = typeof screen < "u" ? screen.orientation : void 0;
550
- return e && typeof e.angle == "number" ? e.angle : 0;
551
- }
552
- function Se(e) {
553
- return new Promise((t) => {
554
- if (e.videoWidth > 0) {
555
- t();
556
- return;
557
- }
558
- e.addEventListener("loadedmetadata", () => t(), { once: !0 });
559
- });
560
- }
561
- async function Te(e) {
562
- try {
563
- await e.play();
564
- } catch {
565
- }
566
- }
567
- async function Le(e, t = 15) {
568
- const i = document.createElement("canvas");
569
- if (typeof i.captureStream != "function") return null;
570
- const o = i.getContext("2d");
571
- if (!o) return null;
572
- const n = document.createElement("video");
573
- n.muted = !0, n.playsInline = !0, n.srcObject = e, await Se(n);
574
- const s = ke(
575
- n.videoWidth,
576
- n.videoHeight,
577
- window.innerWidth,
578
- window.innerHeight,
579
- Ee()
580
- );
581
- i.width = s.width, i.height = s.height, await Te(n);
582
- let a = 0;
583
- const d = () => {
584
- Ce(o, n, s), a = requestAnimationFrame(d);
585
- };
586
- d();
587
- const c = i.captureStream(t);
588
- return {
589
- stream: c,
590
- stop: () => {
591
- a && cancelAnimationFrame(a), a = 0, c.getTracks().forEach((r) => r.stop()), n.pause(), n.srcObject = null;
592
- }
593
- };
594
- }
595
- function Ie() {
596
- return typeof navigator < "u" && !!navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia == "function" && typeof MediaRecorder < "u";
597
- }
598
- function Ae() {
599
- if (typeof MediaRecorder > "u") return "";
600
- const e = [
601
- "video/webm;codecs=vp9",
602
- "video/webm;codecs=vp8",
603
- "video/webm",
604
- "video/mp4"
605
- ];
606
- for (const t of e)
607
- if (MediaRecorder.isTypeSupported(t))
608
- return t;
609
- return "";
610
- }
611
- function De(e = {}) {
612
- const t = e.maxDuration ?? 15, i = e.videoBitsPerSecond ?? 8e5;
613
- let o = null, n = null, s = null, a = [], d = null, c = null, r = null, l = 0, p = null, u = null, f = !1, b = null;
614
- function v() {
615
- r !== null && (clearInterval(r), r = null), b !== null && (clearTimeout(b), b = null), n && (n.stop(), n = null), o && (o.getTracks().forEach((m) => m.stop()), o = null), s = null, a = [], d = null, c = null, p = null, u = null;
616
- }
617
- function y() {
618
- f || (f = !0, s && s.state !== "inactive" && s.stop());
619
- }
620
- return {
621
- async start() {
622
- f = !1, a = [];
623
- const m = Ae();
624
- if (!m)
625
- throw new Error("No supported video MIME type found");
626
- o = await navigator.mediaDevices.getDisplayMedia({
627
- video: {
628
- frameRate: 15
629
- },
630
- audio: !1,
631
- // @ts-expect-error preferCurrentTab is not in the TS types yet
632
- preferCurrentTab: !0
633
- });
634
- const h = o.getVideoTracks()[0];
635
- h && h.addEventListener("ended", () => {
636
- y();
637
- });
638
- let w = o;
639
- if (xe()) {
640
- const g = await Le(o);
641
- g && (n = g, w = g.stream);
642
- }
643
- s = new MediaRecorder(w, {
644
- mimeType: m,
645
- videoBitsPerSecond: i
646
- }), s.ondataavailable = (g) => {
647
- g.data.size > 0 && a.push(g.data);
648
- }, s.onstop = () => {
649
- const g = new Blob(a, { type: m });
650
- p && p(g), c && c(g), n && (n.stop(), n = null), o && o.getTracks().forEach((S) => S.stop());
651
- }, s.onerror = () => {
652
- u && u(new Error("MediaRecorder error"));
653
- }, s.start(1e3), l = Date.now(), r = setInterval(() => {
654
- const g = Math.floor((Date.now() - l) / 1e3);
655
- d && d(g);
656
- }, 1e3), b = setTimeout(() => {
657
- y();
658
- }, t * 1e3);
659
- },
660
- stop() {
661
- return new Promise((m, h) => {
662
- p = m, u = h, y();
663
- });
664
- },
665
- onTick(m) {
666
- d = m;
667
- },
668
- onStop(m) {
669
- c = m;
670
- },
671
- destroy() {
672
- y(), v();
673
- }
674
- };
675
- }
676
- function q(e, t, i) {
677
- const o = document.createElement(e);
678
- if (t)
679
- for (const [n, s] of Object.entries(t))
680
- n === "className" ? o.className = s : o.setAttribute(n, s);
681
- return o;
682
- }
683
- function Me(e = 640) {
684
- return typeof window < "u" && window.innerWidth < e;
685
- }
686
- function Y(e, t, i) {
687
- const o = i.map((s) => s.style.visibility);
688
- i.forEach((s) => s.style.visibility = "hidden");
689
- const n = document.elementFromPoint(e, t);
690
- return i.forEach((s, a) => s.style.visibility = o[a]), n;
691
- }
692
- function B(e) {
693
- return e ? e.hasAttribute("data-qaid-embed") || e.hasAttribute("data-qaid-embed-overlay") ? !0 : !!e.closest("[data-qaid-embed], [data-qaid-embed-overlay]") : !1;
694
- }
695
- function Q(e, t = 0) {
696
- const i = e.getBoundingClientRect();
697
- return {
698
- x: i.left - t,
699
- y: i.top - t,
700
- width: i.width + t * 2,
701
- height: i.height + t * 2
702
- };
703
- }
704
- async function Re(e = {}) {
705
- const { quality: t = 1, maxWidth: i = 1280, maxHeight: o = 800 } = e;
706
- try {
707
- if (!navigator.mediaDevices?.getDisplayMedia)
708
- return console.warn("Screen Capture API not available"), null;
709
- const n = await navigator.mediaDevices.getDisplayMedia({
710
- preferCurrentTab: !0,
711
- video: {
712
- displaySurface: "browser"
713
- }
714
- }), s = n.getVideoTracks()[0], a = s.getSettings(), d = document.createElement("video");
715
- d.srcObject = n, d.muted = !0, await new Promise((v) => {
716
- d.onloadedmetadata = () => {
717
- d.play(), v();
718
- };
719
- }), await new Promise((v) => {
720
- const y = () => {
721
- d.readyState >= 2 ? v() : requestAnimationFrame(y);
722
- };
723
- y();
724
- }), await new Promise((v) => setTimeout(v, 100));
725
- const c = a.width || d.videoWidth, r = a.height || d.videoHeight, l = Math.min(i / c, o / r, 1), p = Math.round(c * l), u = Math.round(r * l), f = document.createElement("canvas");
726
- f.width = p, f.height = u;
727
- const b = f.getContext("2d");
728
- return b ? (b.drawImage(d, 0, 0, p, u), s.stop(), f.toDataURL("image/webp", t)) : (s.stop(), null);
729
- } catch (n) {
730
- return console.warn("Screenshot capture failed:", n), null;
731
- }
732
- }
733
- const G = "https://qaid.dev/lib/html2canvas.min.js", Be = 1e4, He = 50;
734
- let k = null;
735
- function li() {
736
- if (typeof document > "u" || typeof window > "u")
737
- return !1;
738
- const e = document.createElement("canvas");
739
- return typeof e.getContext == "function" && !!e.getContext("2d");
740
- }
741
- function Pe() {
742
- return window.html2canvas ? Promise.resolve(!0) : k || (k = new Promise((e) => {
743
- if (!document.querySelector(
744
- `script[src="${G}"]`
745
- )) {
746
- const n = document.createElement("script");
747
- n.src = G, n.async = !0, document.head.appendChild(n);
748
- }
749
- const i = Date.now(), o = () => {
750
- if (window.html2canvas) {
751
- e(!0);
752
- return;
753
- }
754
- if (Date.now() - i > Be) {
755
- k = null, e(!1);
756
- return;
757
- }
758
- setTimeout(o, He);
759
- };
760
- o();
761
- }), k);
762
- }
763
- async function Oe(e = {}) {
764
- const { quality: t = 0.8, maxWidth: i = 1280, maxHeight: o = 800 } = e;
765
- try {
766
- if (!await Pe() || !window.html2canvas)
767
- return console.warn("html2canvas failed to load"), null;
768
- const s = await window.html2canvas(document.body, {
769
- useCORS: !0,
770
- allowTaint: !1,
771
- logging: !1,
772
- windowWidth: document.documentElement.clientWidth,
773
- windowHeight: document.documentElement.clientHeight,
774
- width: document.documentElement.clientWidth,
775
- height: document.documentElement.clientHeight,
776
- x: window.scrollX,
777
- y: window.scrollY,
778
- ignoreElements: (r) => r instanceof HTMLElement ? r.classList.contains("qaid-buttons") || r.classList.contains("qaid-targeting-overlay") || r.classList.contains("qaid-modal-container") || r.classList.contains("qaid-bottom-sheet") || r.classList.contains("qaid-backdrop") || r.classList.contains("qaid-selected-marker") || r.classList.contains("qaid-tooltip-text") || r.classList.contains("qaid-recording-indicator") || r.classList.contains("qaid-video-preview") || r.className?.toString().startsWith?.("qaid-") : !1
779
- }), a = s.width, d = s.height, c = Math.min(i / a, o / d, 1);
780
- if (c < 1) {
781
- const r = Math.round(a * c), l = Math.round(d * c), p = document.createElement("canvas");
782
- p.width = r, p.height = l;
783
- const u = p.getContext("2d");
784
- return u ? (u.drawImage(s, 0, 0, r, l), p.toDataURL("image/webp", t)) : null;
785
- }
786
- return s.toDataURL("image/webp", t);
787
- } catch (n) {
788
- return console.warn("DOM screenshot capture failed:", n), null;
789
- }
790
- }
791
- const x = 12, ze = "#ef4444", $e = 4, Ne = 0.8, Ue = {
792
- title: "Annotate screenshot",
793
- instructions: "Draw on the screenshot to highlight or hide details, then choose Done to attach it or Skip to send the original.",
794
- rectangle: "Rectangle",
795
- arrow: "Arrow",
796
- pen: "Pen",
797
- blur: "Blur or redact",
798
- undo: "Undo last",
799
- skip: "Skip annotation",
800
- done: "Done"
801
- };
802
- function P(e, t) {
803
- return {
804
- x: Math.min(e.x, t.x),
805
- y: Math.min(e.y, t.y),
806
- w: Math.abs(e.x - t.x),
807
- h: Math.abs(e.y - t.y)
808
- };
809
- }
810
- function Fe(e) {
811
- if (e.type === "pen") return e.points.length < 2;
812
- const t = P(e.points[0], e.points[1]);
813
- return t.w < 3 && t.h < 3;
814
- }
815
- function Ve(e, t, i, o) {
816
- e.beginPath(), e.moveTo(t.x, t.y), e.lineTo(i.x, i.y), e.stroke();
817
- const n = Math.atan2(i.y - t.y, i.x - t.x), s = Math.max(10, o * 3);
818
- e.beginPath(), e.moveTo(i.x, i.y), e.lineTo(
819
- i.x - s * Math.cos(n - Math.PI / 6),
820
- i.y - s * Math.sin(n - Math.PI / 6)
821
- ), e.moveTo(i.x, i.y), e.lineTo(
822
- i.x - s * Math.cos(n + Math.PI / 6),
823
- i.y - s * Math.sin(n + Math.PI / 6)
824
- ), e.stroke();
825
- }
826
- function _e(e, t) {
827
- if (t.length !== 0) {
828
- e.beginPath(), e.moveTo(t[0].x, t[0].y);
829
- for (let i = 1; i < t.length; i++)
830
- e.lineTo(t[i].x, t[i].y);
831
- e.stroke();
832
- }
833
- }
834
- function Ke(e, t, i, o, n) {
835
- e.fillStyle = "rgb(15, 23, 42)", e.fillRect(t, i, o, n), e.fillStyle = "rgb(37, 47, 63)";
836
- for (let s = i; s < i + n; s += x)
837
- for (let a = t; a < t + o; a += x)
838
- (Math.floor((a - t) / x) + Math.floor((s - i) / x)) % 2 === 0 && e.fillRect(
839
- a,
840
- s,
841
- Math.min(x, t + o - a),
842
- Math.min(x, i + n - s)
843
- );
844
- }
845
- function O(e, t) {
846
- switch (e.save(), e.strokeStyle = t.color, e.fillStyle = t.color, e.lineWidth = t.strokeWidth, e.lineJoin = "round", e.lineCap = "round", t.type) {
847
- case "rect": {
848
- const i = P(t.points[0], t.points[1]);
849
- e.strokeRect(i.x, i.y, i.w, i.h);
850
- break;
851
- }
852
- case "arrow":
853
- Ve(e, t.points[0], t.points[1], t.strokeWidth);
854
- break;
855
- case "pen":
856
- _e(e, t.points);
857
- break;
858
- case "blur": {
859
- const i = P(t.points[0], t.points[1]);
860
- Ke(e, i.x, i.y, i.w, i.h);
861
- break;
862
- }
863
- }
864
- e.restore();
865
- }
866
- function st(e) {
867
- return new Promise((t, i) => {
868
- const o = new Image();
869
- o.onload = () => t(o), o.onerror = () => i(new Error("Failed to load screenshot")), o.src = e;
870
- });
871
- }
872
- async function je(e, t, i = {}) {
873
- const o = i.quality ?? Ne;
874
- let n;
875
- try {
876
- n = await st(e);
877
- } catch {
878
- return e;
879
- }
880
- const s = document.createElement("canvas");
881
- s.width = n.naturalWidth || n.width || 1, s.height = n.naturalHeight || n.height || 1;
882
- const a = s.getContext("2d");
883
- if (!a) return e;
884
- a.drawImage(n, 0, 0, s.width, s.height);
885
- for (const d of t)
886
- O(a, d);
887
- return s.toDataURL("image/webp", o);
888
- }
889
- let We = 0;
890
- class Xe {
891
- shapes = [];
892
- tool = "rect";
893
- opts;
894
- labels;
895
- color;
896
- strokeWidth;
897
- uid = `qaid-annotate-${++We}`;
898
- container;
899
- canvas;
900
- ctx = null;
901
- toolbar;
902
- undoBtn;
903
- img = null;
904
- drawing = !1;
905
- current = null;
906
- settled = !1;
907
- // Assigned in open() before any settle() is reachable (done/skip only fire
908
- // after the editor is mounted).
909
- resolveResult;
910
- constructor(t) {
911
- this.opts = t, this.labels = { ...Ue, ...t.labels ?? {} }, this.color = t.color ?? ze, this.strokeWidth = t.strokeWidth ?? $e;
912
- }
913
- /** Mount the editor and resolve when the user commits (Done) or skips. */
914
- open() {
915
- const t = new Promise((i) => {
916
- this.resolveResult = i;
917
- });
918
- return this.mount(), t;
919
- }
920
- toolButton(t, i, o) {
921
- const n = q("button", {
922
- type: "button",
923
- class: "qaid-annotate-tool",
924
- "data-qaid-tool": t,
925
- "aria-pressed": t === this.tool ? "true" : "false",
926
- "aria-label": `${o} tool`,
927
- title: o
928
- });
929
- return n.innerHTML = i, n;
930
- }
931
- actionButton(t, i, o, n) {
932
- const s = q("button", {
933
- type: "button",
934
- class: `qaid-annotate-action ${n}`,
935
- "data-qaid-action": t,
936
- "aria-label": o,
937
- title: o
938
- });
939
- return s.innerHTML = `${i}<span class="qaid-annotate-btn-text">${o}</span>`, s;
940
- }
941
- mount() {
942
- const t = `${this.uid}-title`, i = `${this.uid}-desc`;
943
- this.container = q("div", { class: "qaid-annotate" }), this.opts.applyVars?.(this.container);
944
- const o = q("h2", {
945
- id: t,
946
- class: "qaid-annotate-title"
947
- });
948
- o.textContent = this.labels.title;
949
- const n = q("p", {
950
- id: i,
951
- class: "qaid-annotate-desc"
952
- });
953
- n.textContent = this.labels.instructions;
954
- const s = q("div", { class: "qaid-annotate-stage" });
955
- 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", {
956
- class: "qaid-annotate-toolbar",
957
- role: "toolbar",
958
- "aria-label": this.labels.title
959
- });
960
- const a = q("div", { class: "qaid-annotate-tools" });
961
- a.appendChild(this.toolButton("rect", kt, this.labels.rectangle)), a.appendChild(this.toolButton("arrow", Ct, this.labels.arrow)), a.appendChild(this.toolButton("pen", Et, this.labels.pen)), a.appendChild(this.toolButton("blur", St, this.labels.blur)), this.undoBtn = this.actionButton("undo", Tt, this.labels.undo, "qaid-annotate-undo"), this.undoBtn.disabled = !0;
962
- const d = this.actionButton("skip", It, this.labels.skip, "qaid-annotate-skip"), c = this.actionButton("done", Lt, this.labels.done, "qaid-annotate-done"), r = q("div", { class: "qaid-annotate-actions" });
963
- r.appendChild(this.undoBtn), r.appendChild(d), r.appendChild(c), this.toolbar.appendChild(a), 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, {
964
- labelledbyId: t,
965
- describedbyId: i
966
- }), this.opts.announce?.(this.labels.instructions), st(this.opts.dataUrl).then((l) => {
967
- this.img = l, this.canvas.width = l.naturalWidth || l.width || 1, this.canvas.height = l.naturalHeight || l.height || 1, this.redraw();
968
- }).catch(() => {
969
- this.img = null, this.redraw();
970
- });
971
- }
972
- /** Convert a pointer event's client coordinates into natural image pixels. */
973
- toImageCoords(t) {
974
- const i = this.canvas.getBoundingClientRect(), o = this.canvas.width / (i.width || this.canvas.width), n = this.canvas.height / (i.height || this.canvas.height);
975
- return {
976
- x: (t.clientX - i.left) * o,
977
- y: (t.clientY - i.top) * n
978
- };
979
- }
980
- /** Repaint the display canvas: base image (if loaded) then every shape. */
981
- redraw() {
982
- const t = this.ctx;
983
- if (t) {
984
- t.clearRect(0, 0, this.canvas.width, this.canvas.height), this.img && t.drawImage(this.img, 0, 0, this.canvas.width, this.canvas.height);
985
- for (const i of this.shapes)
986
- O(t, i);
987
- this.current && O(t, this.current);
988
- }
989
- }
990
- /** Select the active drawing tool and reflect it on the toolbar. */
991
- selectTool(t) {
992
- this.tool = t, this.toolbar.querySelectorAll("[data-qaid-tool]").forEach((n) => {
993
- n.setAttribute(
994
- "aria-pressed",
995
- n.getAttribute("data-qaid-tool") === t ? "true" : "false"
996
- );
997
- });
998
- const o = this.labels[Ye[t]];
999
- this.opts.announce?.(`${o} tool selected`);
1000
- }
1001
- onToolbarClick = (t) => {
1002
- const o = t.target?.closest("[data-qaid-tool],[data-qaid-action]");
1003
- if (!o) return;
1004
- const n = o.getAttribute("data-qaid-tool");
1005
- if (n) {
1006
- this.selectTool(n);
1007
- return;
1008
- }
1009
- const s = o.getAttribute("data-qaid-action");
1010
- s === "undo" ? this.undo() : s === "skip" ? this.skip() : s === "done" && this.done();
1011
- };
1012
- onPointerDown = (t) => {
1013
- t.preventDefault?.(), this.drawing = !0;
1014
- const i = this.toImageCoords(t);
1015
- this.current = {
1016
- type: this.tool,
1017
- color: this.color,
1018
- strokeWidth: this.strokeWidth,
1019
- points: this.tool === "pen" ? [i] : [i, { ...i }]
1020
- }, this.redraw();
1021
- };
1022
- onPointerMove = (t) => {
1023
- if (!this.drawing || !this.current) return;
1024
- const i = this.toImageCoords(t);
1025
- this.current.type === "pen" ? this.current.points.push(i) : this.current.points[1] = i, this.redraw();
1026
- };
1027
- onPointerUp = () => {
1028
- if (!this.drawing || !this.current) return;
1029
- this.drawing = !1;
1030
- const t = this.current;
1031
- if (this.current = null, Fe(t)) {
1032
- this.redraw();
1033
- return;
1034
- }
1035
- this.shapes.push(t), this.undoBtn.disabled = !1, this.opts.announce?.("Annotation added"), this.redraw();
1036
- };
1037
- onKeyDown = (t) => {
1038
- if (t.key === "Escape") {
1039
- t.preventDefault(), this.skip();
1040
- return;
1041
- }
1042
- if (t.key === "Enter") {
1043
- const i = t.target;
1044
- if (i?.getAttribute("data-qaid-tool") || i?.getAttribute("data-qaid-action"))
1045
- return;
1046
- t.preventDefault(), this.done();
1047
- }
1048
- };
1049
- /** Remove the most recently added shape. */
1050
- undo() {
1051
- if (this.shapes.length === 0) {
1052
- this.opts.announce?.("Nothing to undo");
1053
- return;
1054
- }
1055
- this.shapes.pop(), this.undoBtn.disabled = this.shapes.length === 0, this.opts.announce?.("Removed last annotation"), this.redraw();
1056
- }
1057
- /** Composite the annotations and resolve with the new WebP data URL. */
1058
- async done() {
1059
- if (this.settled) return;
1060
- const t = await je(this.opts.dataUrl, this.shapes, {
1061
- quality: this.opts.quality
1062
- });
1063
- this.opts.announce?.("Annotated screenshot attached"), this.settle(t);
1064
- }
1065
- /** Discard annotations and resolve with `null` (caller keeps the original). */
1066
- skip() {
1067
- this.settled || (this.opts.announce?.("Annotation skipped"), this.settle(null));
1068
- }
1069
- settle(t) {
1070
- this.settled = !0, this.teardown(), this.resolveResult(t);
1071
- }
1072
- teardown() {
1073
- 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();
1074
- }
1075
- }
1076
- const Ye = {
1077
- rect: "rectangle",
1078
- arrow: "arrow",
1079
- pen: "pen",
1080
- blur: "blur"
1081
- };
1082
- function Qe(e) {
1083
- return new Xe(e).open();
1084
- }
1085
- const Ge = "https://unpkg.com/@qaiddev/quests-embed@1/dist/qaid-quests.js", Je = (e) => (
1086
- // The URL is a runtime value, not a static specifier — keep Vite from
1087
- // trying to analyze/bundle it.
1088
- import(
1089
- /* @vite-ignore */
1090
- e
1091
- )
1092
- );
1093
- let Ze = Je, C = null, H = null;
1094
- function ti(e) {
1095
- return C && H === e || (H = e, C = Promise.resolve(Ze(e)).then((t) => {
1096
- const i = t;
1097
- if (!i || typeof i.QaidQuests != "function")
1098
- throw new Error("quests module has no QaidQuests export");
1099
- return i;
1100
- }).catch((t) => {
1101
- throw C = null, H = null, t;
1102
- })), C;
1103
- }
1104
- async function ei(e) {
1105
- const t = await ti(e.moduleUrl), i = e.base.replace(/\/+$/, ""), o = e.feedbackId != null ? { feedbackId: e.feedbackId } : void 0;
1106
- return new t.QaidQuests({
1107
- endpoint: `${i}/responses`,
1108
- configUrl: `${i}/${encodeURIComponent(e.questId)}/definition`,
1109
- apiKey: e.apiKey || void 0,
1110
- metadata: o,
1111
- onClose: e.onClose
1112
- });
1113
- }
1114
- const J = "qaid_visitor_id", Z = "qaid_hide_feedback", ii = 12;
1115
- function oi() {
1116
- return typeof window.matchMedia == "function" && window.matchMedia("(pointer: coarse)").matches;
1117
- }
1118
- function z(e) {
1119
- return e ? `${Z}_${e}` : Z;
1120
- }
1121
- function ni(e) {
1122
- try {
1123
- return localStorage.getItem(z(e)) === "1";
1124
- } catch {
1125
- return !1;
1126
- }
1127
- }
1128
- function tt(e, t = !0) {
1129
- try {
1130
- t ? localStorage.setItem(z(e), "1") : localStorage.removeItem(z(e));
1131
- } catch {
1132
- }
1133
- }
1134
- function si() {
1135
- try {
1136
- let e = localStorage.getItem(J);
1137
- return e || (e = crypto.randomUUID(), localStorage.setItem(J, e)), e;
1138
- } catch {
1139
- return crypto.randomUUID();
1140
- }
1141
- }
1142
- class ai {
1143
- config;
1144
- state = "IDLE";
1145
- feedbackData = {
1146
- feedbackType: null,
1147
- elementSelector: null,
1148
- elementText: null,
1149
- consoleErrors: []
1150
- };
1151
- selectedBounds = {
1152
- x: 0,
1153
- y: 0,
1154
- width: 0,
1155
- height: 0,
1156
- clickX: 0,
1157
- clickY: 0,
1158
- visible: !1
1159
- };
1160
- feedbackId = null;
1161
- // A quest launched from a button (in place of the message box), if any.
1162
- activeQuest = null;
1163
- mousePos = { x: 0, y: 0 };
1164
- isMobile = !1;
1165
- visitorId;
1166
- // Console capture
1167
- consoleCapture = null;
1168
- // Video recording
1169
- videoRecorder = null;
1170
- networkCapture = null;
1171
- recordedBlob = null;
1172
- recordingIndicator = null;
1173
- videoPreview = null;
1174
- isRecording = !1;
1175
- isSendingVideo = !1;
1176
- // Shadow DOM
1177
- shadowHost = null;
1178
- shadowRoot = null;
1179
- // Overlay shadow DOM (always on document.body for full-page coverage)
1180
- overlayShadowHost = null;
1181
- overlayShadowRoot = null;
1182
- // DOM elements (inside shadow root)
1183
- buttonsContainer = null;
1184
- isUserProvidedContainer = !1;
1185
- overlayContainer = null;
1186
- captureLayer = null;
1187
- crosshairH = null;
1188
- crosshairV = null;
1189
- scope = null;
1190
- highlightBox = null;
1191
- marker = null;
1192
- modalContainer = null;
1193
- backdrop = null;
1194
- dismissBtn = null;
1195
- // Per-instance CSS variables
1196
- cssVars = {};
1197
- // Keyboard element-targeting
1198
- keyboardController = null;
1199
- activeThumbBtn = null;
1200
- // Tracks whether the pending activation came from the keyboard (Enter/Space)
1201
- // rather than a pointer, so targeting can avoid elementFromPoint(0,0).
1202
- keyboardActivation = !1;
1203
- // Accessibility: dialog focus management
1204
- dialogTrigger = null;
1205
- dialogTrap = null;
1206
- dialogRestoreInert = null;
1207
- // Unique id suffix for aria-labelledby/describedby references
1208
- uid = Math.random().toString(36).slice(2, 9);
1209
- // Bound event handlers
1210
- boundKeyDown;
1211
- boundMouseMove;
1212
- boundClick;
1213
- // Touch equivalents for targeting. iOS/iPadOS does not synthesise `click`
1214
- // (or `mousemove`) for taps on non-interactive page elements, so targeting
1215
- // must be driven by touch events there.
1216
- boundTouchStart;
1217
- boundTouchEnd;
1218
- // Where the current targeting touch began, to tell a tap from a scroll.
1219
- touchStartPos = null;
1220
- boundResize;
1221
- // Start with buttons slid off-screen (localStorage dismiss, no animation)
1222
- _startDismissed = !1;
1223
- // DOM persistence (survives framework client-side navigation)
1224
- destroyed = !1;
1225
- domObserver = null;
1226
- boundBeforeSwap = null;
1227
- constructor(t) {
1228
- this.config = {
1229
- endpoint: t.endpoint,
1230
- apiKey: t.apiKey ?? "",
1231
- container: t.container ?? "",
1232
- buttonClass: t.buttonClass ?? "",
1233
- direction: t.direction ?? "horizontal",
1234
- position: t.position ?? "bottom-right",
1235
- offset: {
1236
- x: t.offset?.x ?? 16,
1237
- y: t.offset?.y ?? 16
1238
- },
1239
- zIndex: t.zIndex ?? 50,
1240
- skipTargeting: t.skipTargeting ?? !1,
1241
- colors: {
1242
- positive: t.colors?.positive ?? "rgb(0, 200, 83)",
1243
- negative: t.colors?.negative ?? "rgb(255, 0, 0)",
1244
- marker: t.colors?.marker ?? "#6366f1"
1245
- },
1246
- buttonSize: t.buttonSize ?? "medium",
1247
- text: {
1248
- tooltip: t.text?.tooltip ?? "",
1249
- modalTitle: t.text?.modalTitle ?? "Thank you for your feedback!",
1250
- modalSubtitle: t.text?.modalSubtitle ?? "Would you like to add a message to help us understand your feedback better?",
1251
- placeholder: t.text?.placeholder ?? "Optional: Tell us more about your experience...",
1252
- submitButton: t.text?.submitButton ?? "Submit",
1253
- skipButton: t.text?.skipButton ?? "Skip",
1254
- positiveLabel: t.text?.positiveLabel ?? "Send positive feedback",
1255
- negativeLabel: t.text?.negativeLabel ?? "Send negative feedback",
1256
- recordLabel: t.text?.recordLabel ?? "Record a screen recording",
1257
- dismissLabel: t.text?.dismissLabel ?? "Hide Feedback"
1258
- },
1259
- modalWidth: t.modalWidth ?? 400,
1260
- backdropOpacity: t.backdropOpacity ?? 0.3,
1261
- fontFamily: t.fontFamily ?? "system-ui, -apple-system, sans-serif",
1262
- fontSize: t.fontSize ?? 16,
1263
- captureScreenshot: t.captureScreenshot ?? !1,
1264
- annotate: t.annotate ?? !0,
1265
- screenshotMethod: t.screenshotMethod ?? "permission",
1266
- screenshotOptions: {
1267
- quality: t.screenshotOptions?.quality ?? 0.8,
1268
- maxWidth: t.screenshotOptions?.maxWidth ?? 1280,
1269
- maxHeight: t.screenshotOptions?.maxHeight ?? 800
1270
- },
1271
- incognito: t.incognito ?? !1,
1272
- hideDismiss: t.hideDismiss ?? !1,
1273
- positiveIcon: t.positiveIcon ?? "",
1274
- negativeIcon: t.negativeIcon ?? "",
1275
- hideThumbs: t.hideThumbs ?? !1,
1276
- css: t.css ?? "",
1277
- captureVideo: t.captureVideo ?? !1,
1278
- videoOptions: {
1279
- maxDuration: t.videoOptions?.maxDuration ?? 15
1280
- },
1281
- recordIcon: t.recordIcon ?? "",
1282
- quests: {
1283
- base: t.quests?.base ?? "",
1284
- up: t.quests?.up ?? "",
1285
- down: t.quests?.down ?? "",
1286
- video: t.quests?.video ?? "",
1287
- // Quest service reuses the feedback API key unless overridden.
1288
- apiKey: t.quests?.apiKey ?? t.apiKey ?? "",
1289
- moduleUrl: t.quests?.moduleUrl ?? Ge
1290
- }
1291
- }, this.boundKeyDown = this.handleKeyDown.bind(this), this.boundMouseMove = this.handleMouseMove.bind(this), this.boundClick = this.handleClick.bind(this), this.boundTouchStart = this.handleTouchStart.bind(this), this.boundTouchEnd = this.handleTouchEnd.bind(this), this.boundResize = this.handleResize.bind(this), this.visitorId = si(), this.init();
1292
- }
1293
- applyVars(t) {
1294
- $t(t, this.cssVars);
1295
- }
1296
- /**
1297
- * Announce a message via the shared visually-hidden live regions.
1298
- * Prefer the overlay shadow root (which hosts every transient surface and
1299
- * is never inerted by its own dialogs) so announcements are not suppressed
1300
- * while a dialog aria-hides the main button host.
1301
- */
1302
- announceMsg(t, i = !1) {
1303
- const o = this.overlayShadowRoot ?? this.shadowRoot;
1304
- o && ie(o, t, { assertive: i });
1305
- }
1306
- /**
1307
- * Turn a transient surface into an accessible modal dialog: save the
1308
- * invoking control, apply dialog semantics, trap focus, and inert the
1309
- * background. Paired with closeDialogA11y() on every close path.
1310
- */
1311
- openDialogA11y(t, i) {
1312
- this.dialogTrigger = ae(), de(t, i), this.dialogTrap = se(t), this.dialogRestoreInert = ce(t);
1313
- }
1314
- closeDialogA11y() {
1315
- this.dialogTrap?.release(), this.dialogTrap = null, this.dialogRestoreInert && (this.dialogRestoreInert(), this.dialogRestoreInert = null), re(this.dialogTrigger), this.dialogTrigger = null;
1316
- }
1317
- clearActiveThumb() {
1318
- this.activeThumbBtn && (this.activeThumbBtn.setAttribute("aria-pressed", "false"), this.activeThumbBtn = null);
1319
- }
1320
- init() {
1321
- Nt(), this.cssVars = zt({
1322
- positiveColor: this.config.colors.positive,
1323
- negativeColor: this.config.colors.negative,
1324
- markerColor: this.config.colors.marker,
1325
- buttonSize: this.config.buttonSize,
1326
- modalWidth: this.config.modalWidth,
1327
- backdropOpacity: this.config.backdropOpacity,
1328
- fontFamily: this.config.fontFamily,
1329
- fontSize: this.config.fontSize
1330
- }), !this.config.hideDismiss && ni(this.config.apiKey) && (this._startDismissed = !0), this.checkMobile(), window.addEventListener("resize", this.boundResize), this.createEmbed(), this.consoleCapture = ve((t) => {
1331
- this.feedbackData.consoleErrors = this.consoleCapture?.errors ?? [];
1332
- }), this.feedbackData.consoleErrors = this.consoleCapture.errors, this.observeDom();
1333
- }
1334
- /**
1335
- * Watch for the shadow hosts being removed from the DOM by framework
1336
- * client-side navigation (e.g. Astro View Transitions swapping <body>
1337
- * contents, or any SPA router that replaces DOM subtrees). If the host
1338
- * is disconnected and destroy() wasn't called, re-append it.
1339
- *
1340
- * Also hooks into Astro's `astro:before-swap` when available, which
1341
- * lets us carry elements into the new document before the swap happens
1342
- * (avoids a flash of the widget disappearing and reappearing).
1343
- */
1344
- observeDom() {
1345
- this.boundBeforeSwap = (t) => {
1346
- const i = t.newDocument;
1347
- !i || this.destroyed || (this.shadowHost && i.body.appendChild(this.shadowHost), this.overlayShadowHost && i.body.appendChild(this.overlayShadowHost));
1348
- }, document.addEventListener("astro:before-swap", this.boundBeforeSwap), this.domObserver = new MutationObserver(() => {
1349
- this.destroyed || (this.shadowHost && !this.shadowHost.isConnected && document.body.appendChild(this.shadowHost), this.overlayShadowHost && !this.overlayShadowHost.isConnected && document.body.appendChild(this.overlayShadowHost));
1350
- }), this.domObserver.observe(document.body, { childList: !0 });
1351
- }
1352
- checkMobile() {
1353
- this.isMobile = Me();
1354
- }
1355
- handleResize() {
1356
- this.checkMobile();
1357
- }
1358
- createEmbed() {
1359
- this.shadowHost = document.createElement("div"), this.shadowHost.setAttribute("data-qaid-embed", ""), this.shadowHost.style.position = "static", this.shadowHost.style.display = "contents";
1360
- let t = null;
1361
- 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" });
1362
- const i = document.createElement("style");
1363
- if (i.textContent = _(), this.shadowRoot.appendChild(i), this.config.css) {
1364
- const r = document.createElement("style");
1365
- r.textContent = this.config.css, this.shadowRoot.appendChild(r);
1366
- }
1367
- const o = this.config.direction === "vertical" ? "qaid-vertical" : "";
1368
- if (this.isUserProvidedContainer)
1369
- 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");
1370
- else {
1371
- 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";
1372
- const { x: r, y: l } = this.config.offset;
1373
- this.config.position.includes("right") ? this.buttonsContainer.style.right = `${r}px` : this.buttonsContainer.style.left = `${r}px`, this.config.position.includes("bottom") ? this.buttonsContainer.style.bottom = `${l}px` : this.buttonsContainer.style.top = `${l}px`;
1374
- }
1375
- this.applyVars(this.buttonsContainer), this.shadowRoot.appendChild(this.buttonsContainer), this.buttonsContainer.addEventListener("keydown", (r) => {
1376
- (r.key === "Enter" || r.key === " " || r.key === "Spacebar") && (this.keyboardActivation = !0);
1377
- }), this.buttonsContainer.addEventListener("mousedown", () => {
1378
- this.keyboardActivation = !1;
1379
- }), this.buttonsContainer.addEventListener("pointerdown", () => {
1380
- this.keyboardActivation = !1;
1381
- });
1382
- const n = !!this.config.buttonClass, s = n ? `qaid-btn-structural ${this.config.buttonClass}` : "qaid-btn", d = this.config.text.tooltip || "Feedback for us?", c = document.createElement("div");
1383
- if (c.className = "qaid-tooltip-text", c.textContent = d, this.applyVars(c), this.shadowRoot.appendChild(c), this.tooltipElement = c, !this.config.hideThumbs) {
1384
- const r = document.createElement("div");
1385
- r.className = "qaid-tooltip-wrapper";
1386
- const l = document.createElement("button");
1387
- l.type = "button", l.className = n ? `${s} qaid-btn-up` : "qaid-btn qaid-btn-up", l.setAttribute("aria-label", this.config.text.positiveLabel), l.innerHTML = this.config.positiveIcon || D, l.addEventListener("click", (f) => this.handleThumbClick("up", f.currentTarget, f)), l.addEventListener("mouseenter", () => this.showTooltip(l)), l.addEventListener("mouseleave", () => this.hideTooltip()), r.appendChild(l);
1388
- const p = document.createElement("div");
1389
- p.className = "qaid-tooltip-wrapper";
1390
- const u = document.createElement("button");
1391
- u.type = "button", u.className = n ? `${s} qaid-btn-down` : "qaid-btn qaid-btn-down", u.setAttribute("aria-label", this.config.text.negativeLabel), u.innerHTML = this.config.negativeIcon || M, u.addEventListener("click", (f) => this.handleThumbClick("down", f.currentTarget, f)), u.addEventListener("mouseenter", () => this.showTooltip(u)), u.addEventListener("mouseleave", () => this.hideTooltip()), p.appendChild(u), this.buttonsContainer.appendChild(r), this.buttonsContainer.appendChild(p);
1392
- }
1393
- if (this.config.captureVideo && Ie()) {
1394
- const r = document.createElement("div");
1395
- r.className = "qaid-tooltip-wrapper";
1396
- const l = document.createElement("button");
1397
- l.type = "button", l.className = n ? `${s} qaid-btn-record` : "qaid-btn qaid-btn-record", l.setAttribute("aria-label", this.config.text.recordLabel), l.innerHTML = this.config.recordIcon || xt, l.addEventListener("click", () => this.startRecording()), l.addEventListener("mouseenter", () => this.showTooltip(l)), l.addEventListener("mouseleave", () => this.hideTooltip()), r.appendChild(l), this.buttonsContainer.appendChild(r);
1398
- }
1399
- 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", (r) => {
1400
- r.stopPropagation(), this.handleDismiss();
1401
- }), this.buttonsContainer.insertBefore(this.dismissBtn, this.buttonsContainer.firstChild), this._startDismissed && this.buttonsContainer.classList.add("qaid-dismissed"));
1402
- }
1403
- /**
1404
- * Lazily create a separate overlay shadow host on document.body.
1405
- * This host contains all full-page elements (targeting overlay, marker,
1406
- * backdrop, modal, recording indicator, video preview) so they escape
1407
- * clip-path / transform containing blocks in user containers.
1408
- */
1409
- ensureOverlayHost() {
1410
- if (this.overlayShadowRoot) return this.overlayShadowRoot;
1411
- 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" });
1412
- const t = document.createElement("style");
1413
- if (t.textContent = _(), this.overlayShadowRoot.appendChild(t), this.config.css) {
1414
- const i = document.createElement("style");
1415
- i.textContent = this.config.css, this.overlayShadowRoot.appendChild(i);
1416
- }
1417
- return this.overlayShadowRoot;
1418
- }
1419
- tooltipElement = null;
1420
- showTooltip(t) {
1421
- const i = this.tooltipElement;
1422
- i.style.visibility = "hidden", i.classList.add("qaid-tooltip-visible");
1423
- const o = t.getBoundingClientRect(), n = i.getBoundingClientRect(), { top: s, left: a } = me(
1424
- o,
1425
- n,
1426
- { width: window.innerWidth, height: window.innerHeight }
1427
- );
1428
- i.style.top = `${s}px`, i.style.left = `${a}px`, i.style.visibility = "visible";
1429
- }
1430
- hideTooltip() {
1431
- this.tooltipElement.classList.remove("qaid-tooltip-visible");
1432
- }
1433
- handleDismiss() {
1434
- this.buttonsContainer.classList.add("qaid-dismissed"), tt(this.config.apiKey, !0);
1435
- }
1436
- handleThumbClick(t, i, o) {
1437
- if (this.buttonsContainer?.classList.contains("qaid-incognito") && (this.buttonsContainer.classList.remove("qaid-incognito"), tt(this.config.apiKey, !1)), this.config.skipTargeting)
1438
- this.submitDirectFeedback(t, i);
1439
- else {
1440
- this.activeThumbBtn = i, i.setAttribute("aria-pressed", "true");
1441
- const n = this.keyboardActivation;
1442
- this.keyboardActivation = !1, n ? this.startKeyboardTargetingFlow(t) : this.startTargeting(t, o);
1443
- }
1444
- }
1445
- submitDirectFeedback(t, i) {
1446
- this.feedbackData.feedbackType = t, this.feedbackData.elementSelector = null, this.feedbackData.elementText = null;
1447
- const o = i.getBoundingClientRect();
1448
- this.selectedBounds = {
1449
- x: o.left,
1450
- y: o.top,
1451
- width: o.width,
1452
- height: o.height,
1453
- clickX: o.left + o.width / 2,
1454
- clickY: o.top + o.height / 2,
1455
- visible: !1
1456
- }, this.submitFeedback();
1457
- }
1458
- startTargeting(t, i) {
1459
- this.state = "TARGETING", this.feedbackData.feedbackType = t, this.feedbackData.elementSelector = null, this.feedbackData.elementText = null, this.selectedBounds.visible = !1, this.mousePos.x = i.clientX, this.mousePos.y = i.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.cssVars["--qaid-positive"]), document.body.style.setProperty("--qaid-negative", this.cssVars["--qaid-negative"]), this.createTargetingOverlay(), document.addEventListener("keydown", this.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 });
1460
- }
1461
- /**
1462
- * Keyboard-driven targeting. Mirrors startTargeting minus the mouse
1463
- * plumbing: no `qaid-targeting` body class (keeps the cursor visible for
1464
- * keyboard users), no mouse reticle, and no document mouse/click listeners.
1465
- * The KeyboardTargetingController owns Tab/Arrow/Enter/Space/Escape.
1466
- */
1467
- startKeyboardTargetingFlow(t) {
1468
- this.state = "TARGETING", this.feedbackData.feedbackType = t, this.feedbackData.elementSelector = null, this.feedbackData.elementText = null, this.selectedBounds.visible = !1, document.body.style.setProperty("--qaid-positive", this.cssVars["--qaid-positive"]), document.body.style.setProperty("--qaid-negative", this.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 = Gt({
1469
- isExcluded: (i) => B(i),
1470
- onHighlight: (i) => {
1471
- const o = i.getBoundingClientRect(), n = this.highlightBox;
1472
- 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");
1473
- const { text: s } = R(i);
1474
- this.announceMsg(`Targeting ${s || i.tagName.toLowerCase()}`);
1475
- },
1476
- onSelect: (i) => this.selectKeyboardTarget(i),
1477
- onCancel: () => this.cancelTargeting()
1478
- });
1479
- }
1480
- selectKeyboardTarget(t) {
1481
- const i = Q(t, 8);
1482
- this.selectedBounds = {
1483
- ...i,
1484
- clickX: i.x + i.width / 2,
1485
- clickY: i.y + i.height / 2,
1486
- visible: !0
1487
- };
1488
- const { selector: o, text: n } = R(t);
1489
- this.feedbackData.elementSelector = o, this.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.clearActiveThumb(), this.state = "SELECTED", this.showSelectedMarker(), this.submitFeedback();
1490
- }
1491
- createTargetingOverlay() {
1492
- const t = this.ensureOverlayHost();
1493
- this.overlayContainer = document.createElement("div"), this.overlayContainer.className = `qaid-targeting-overlay qaid-type-${this.feedbackData.feedbackType}`, this.captureLayer = document.createElement("div"), this.captureLayer.className = "qaid-capture-layer";
1494
- const i = document.createElement("div");
1495
- i.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 = `
1496
- <div class="qaid-scope-ring"></div>
1497
- <div class="qaid-scope-ring-inner"></div>
1498
- <div class="qaid-scope-dot"></div>
1499
- `, this.highlightBox = document.createElement("div"), this.highlightBox.className = "qaid-highlight-box", this.overlayContainer.appendChild(this.captureLayer), this.overlayContainer.appendChild(i), 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.applyVars(this.overlayContainer), t.appendChild(this.overlayContainer);
1500
- }
1501
- handleKeyDown(t) {
1502
- t.key === "Escape" && (this.isRecording ? this.stopRecording() : this.videoPreview ? this.cancelRecordingPreview() : this.state === "TARGETING" ? this.cancelTargeting() : this.state === "MODAL_OPEN" && this.closeModal());
1503
- }
1504
- handleMouseMove(t) {
1505
- this.updateReticleAt(t.clientX, t.clientY);
1506
- }
1507
- /** Move the crosshair/scope reticle and highlight the element under (x, y).
1508
- * Shared by the mouse (hover) and touch (drag) targeting paths. */
1509
- updateReticleAt(t, i) {
1510
- if (this.mousePos.x = t, this.mousePos.y = i, this.crosshairH && (this.crosshairH.style.top = `${i}px`), this.crosshairV && (this.crosshairV.style.left = `${t}px`), this.scope && (this.scope.style.left = `${t}px`, this.scope.style.top = `${i}px`), this.captureLayer && this.shadowHost) {
1511
- const o = [this.shadowHost, this.overlayShadowHost].filter(Boolean), n = Y(t, i, o);
1512
- if (n && !B(n)) {
1513
- if (this.highlightBox) {
1514
- const s = n.getBoundingClientRect();
1515
- 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";
1516
- }
1517
- } else
1518
- this.highlightBox && (this.highlightBox.style.display = "none");
1519
- }
1520
- }
1521
- handleClick(t) {
1522
- t.preventDefault(), t.stopPropagation(), this.selectAt(t.clientX, t.clientY);
1523
- }
1524
- // ---- Touch targeting (iOS/iPadOS) ----
1525
- // iOS/iPadOS taps on non-interactive elements don't fire click, so touch
1526
- // drives targeting here. Scrolling stays enabled (the page may need to scroll
1527
- // to bring the target into view); a low-movement touch is treated as a tap
1528
- // that selects, distinguishing it from a scroll/drag.
1529
- handleTouchStart(t) {
1530
- const i = t.touches[0];
1531
- i && (this.touchStartPos = { x: i.clientX, y: i.clientY }, this.updateReticleAt(i.clientX, i.clientY));
1532
- }
1533
- handleTouchEnd(t) {
1534
- const i = t.changedTouches[0], o = this.touchStartPos;
1535
- this.touchStartPos = null, !(!i || !o || Math.hypot(i.clientX - o.x, i.clientY - o.y) > ii) && (t.preventDefault(), this.selectAt(i.clientX, i.clientY));
1536
- }
1537
- /** Select the element under (x, y) and tear down targeting.
1538
- * Shared by the mouse (click) and touch (touchend) targeting paths. */
1539
- selectAt(t, i) {
1540
- const o = [this.shadowHost, this.overlayShadowHost].filter(Boolean), n = Y(t, i, o);
1541
- if (!n || B(n))
1542
- return;
1543
- const s = Q(n, 8);
1544
- this.selectedBounds = {
1545
- ...s,
1546
- clickX: t,
1547
- clickY: i,
1548
- visible: !0
1549
- };
1550
- const { selector: a, text: d } = R(n);
1551
- this.feedbackData.elementSelector = a, this.feedbackData.elementText = d, this.stopTargetingListeners(), document.body.classList.remove("qaid-targeting", "qaid-type-up"), document.body.style.removeProperty("--qaid-positive"), document.body.style.removeProperty("--qaid-negative"), this.state = "SELECTED", this.clearActiveThumb(), this.showSelectedMarker(), this.submitFeedback();
1552
- }
1553
- /** Remove the targeting overlay and every mouse/touch/keyboard listener the
1554
- * pointer-targeting flow attaches to the document. */
1555
- stopTargetingListeners() {
1556
- 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.boundKeyDown);
1557
- }
1558
- cancelTargeting() {
1559
- 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.clearActiveThumb(), this.state = "IDLE", this.feedbackData.feedbackType = null, this.feedbackData.elementSelector = null, this.feedbackData.elementText = null, this.selectedBounds.visible = !1;
1560
- }
1561
- removeTargetingOverlay() {
1562
- this.overlayContainer && (this.overlayContainer.remove(), this.overlayContainer = null), this.captureLayer = null, this.crosshairH = null, this.crosshairV = null, this.scope = null;
1563
- }
1564
- showSelectedMarker() {
1565
- const t = this.ensureOverlayHost();
1566
- this.marker = document.createElement("div"), this.marker.className = "qaid-selected-marker", this.marker.style.left = `${this.selectedBounds.x}px`, this.marker.style.top = `${this.selectedBounds.y}px`, this.marker.style.width = `${this.selectedBounds.width}px`, this.marker.style.height = `${this.selectedBounds.height}px`, this.marker.style.zIndex = String(this.config.zIndex + 1), this.applyVars(this.marker), t.appendChild(this.marker);
1567
- }
1568
- hideSelectedMarker() {
1569
- this.marker && (this.marker.remove(), this.marker = null);
1570
- }
1571
- /** Whether to capture the screenshot with the DOM/canvas method (html2canvas)
1572
- * instead of the permission-based Screen Capture API. Explicit "dom" wins;
1573
- * otherwise DOM is used on touch devices to avoid the getDisplayMedia prompt. */
1574
- shouldCaptureViaDom() {
1575
- return this.config.screenshotMethod === "dom" || oi();
1576
- }
1577
- /**
1578
- * Open the full-screen annotation editor over the captured screenshot,
1579
- * reusing the overlay shadow host and the shared dialog a11y helpers.
1580
- * Resolves with the composited WebP data URL, or null when the user skips
1581
- * (caller keeps the original). Pointer events on the overlay host are
1582
- * enabled while the editor is open and restored on close.
1583
- */
1584
- async openAnnotationEditor(t) {
1585
- const i = this.ensureOverlayHost(), o = this.overlayShadowHost, n = o.style.pointerEvents;
1586
- o.style.pointerEvents = "auto";
1587
- try {
1588
- return await Qe({
1589
- dataUrl: t,
1590
- root: i,
1591
- quality: this.config.screenshotOptions.quality,
1592
- color: this.config.colors.marker,
1593
- applyVars: (s) => this.applyVars(s),
1594
- announce: (s, a) => this.announceMsg(s, a),
1595
- openDialog: (s, a) => this.openDialogA11y(s, a),
1596
- closeDialog: () => this.closeDialogA11y()
1597
- });
1598
- } finally {
1599
- o.style.pointerEvents = n;
1600
- }
1601
- }
1602
- async submitFeedback() {
1603
- let t = null;
1604
- this.config.captureScreenshot && (t = this.shouldCaptureViaDom() ? await Oe(this.config.screenshotOptions) : await Re(this.config.screenshotOptions), t && this.announceMsg("Screenshot captured")), t && this.config.annotate && (t = await this.openAnnotationEditor(t) ?? t);
1605
- const i = this.feedbackData.elementSelector ? {
1606
- x: this.selectedBounds.x,
1607
- y: this.selectedBounds.y,
1608
- width: this.selectedBounds.width,
1609
- height: this.selectedBounds.height
1610
- } : null, o = {
1611
- feedbackType: this.feedbackData.feedbackType,
1612
- pageUrl: window.location.href,
1613
- apiKey: this.config.apiKey || void 0,
1614
- elementSelector: this.feedbackData.elementSelector,
1615
- elementText: this.feedbackData.elementText,
1616
- consoleErrors: [...this.feedbackData.consoleErrors],
1617
- screenWidth: window.innerWidth,
1618
- screenHeight: window.innerHeight,
1619
- clickX: this.selectedBounds.clickX,
1620
- clickY: this.selectedBounds.clickY,
1621
- scrollX: window.scrollX,
1622
- scrollY: window.scrollY,
1623
- screenshot: t,
1624
- visitorId: this.visitorId,
1625
- // Include element bounds and user agent for server-side screenshot fallback
1626
- elementBounds: i,
1627
- userAgent: navigator.userAgent
1628
- };
1629
- try {
1630
- const s = await fetch(this.config.endpoint, {
1631
- method: "POST",
1632
- headers: { "Content-Type": "application/json" },
1633
- body: JSON.stringify(o)
1634
- });
1635
- if (s.ok) {
1636
- const a = await s.json();
1637
- this.feedbackId = a.id, this.announceMsg("Feedback sent");
1638
- }
1639
- } catch (s) {
1640
- console.error("Failed to submit feedback:", s), this.announceMsg("Failed to send feedback", !0);
1641
- }
1642
- const n = this.feedbackData.feedbackType;
1643
- if (n && await this.tryLaunchQuest(n, this.feedbackId)) {
1644
- this.resetFeedbackUi();
1645
- return;
1646
- }
1647
- this.state = "MODAL_OPEN", this.showModal(), this.overlayShadowHost && (this.overlayShadowHost.style.pointerEvents = "auto");
1648
- }
1649
- /**
1650
- * Quest id linked to `type`, or "" when quest launching is disabled
1651
- * (no `base`) or this button has no quest configured.
1652
- */
1653
- questIdFor(t) {
1654
- const i = this.config.quests;
1655
- return i.base && i[t] || "";
1656
- }
1657
- /**
1658
- * Launch the quest linked to `type`, if any. Resolves `true` when a quest
1659
- * was configured and the widget launched; `false` when no quest is
1660
- * configured or the widget failed to load (caller falls back to its
1661
- * normal UI). The created feedback record id is passed through so the
1662
- * quest response can be joined back to it server-side.
1663
- */
1664
- async tryLaunchQuest(t, i) {
1665
- const o = this.questIdFor(t);
1666
- if (!o) return !1;
1667
- try {
1668
- return this.activeQuest?.destroy(), this.activeQuest = await ei({
1669
- questId: o,
1670
- base: this.config.quests.base,
1671
- apiKey: this.config.quests.apiKey || void 0,
1672
- moduleUrl: this.config.quests.moduleUrl,
1673
- feedbackId: i,
1674
- onClose: () => {
1675
- this.activeQuest = null;
1676
- }
1677
- }), !0;
1678
- } catch (n) {
1679
- return console.error("Failed to launch quest:", n), this.activeQuest = null, !1;
1680
- }
1681
- }
1682
- /**
1683
- * Reset the thumbs targeting/marker UI back to idle without opening or
1684
- * closing the message modal. Shared by closeModal() and the quest-launch
1685
- * path (which bypasses the modal entirely).
1686
- */
1687
- resetFeedbackUi() {
1688
- this.hideSelectedMarker(), 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;
1689
- }
1690
- showModal() {
1691
- const t = this.ensureOverlayHost();
1692
- this.backdrop = document.createElement("div"), this.backdrop.className = "qaid-backdrop", this.backdrop.style.zIndex = String(this.config.zIndex + 2), this.backdrop.style.background = `rgba(0, 0, 0, ${this.config.backdropOpacity})`, this.backdrop.addEventListener("click", () => this.closeModal()), this.applyVars(this.backdrop), this.isMobile ? this.showBottomSheet() : this.showPositionedModal(), t.appendChild(this.backdrop), document.addEventListener("keydown", this.boundKeyDown);
1693
- }
1694
- showBottomSheet() {
1695
- const t = this.ensureOverlayHost(), i = document.createElement("div");
1696
- i.className = "qaid-bottom-sheet", i.style.zIndex = String(this.config.zIndex + 3), i.innerHTML = `
1697
- <div class="qaid-bottom-sheet-content">
1698
- <div class="qaid-bottom-sheet-handle"></div>
1699
- ${this.getModalContent()}
1700
- </div>
1701
- `, this.applyVars(i), t.appendChild(i), this.modalContainer = i, this.setupModalInteractions();
1702
- }
1703
- showPositionedModal() {
1704
- const t = this.ensureOverlayHost(), { modal: i, arrow: o } = be(
1705
- this.selectedBounds,
1706
- window.innerWidth,
1707
- window.innerHeight,
1708
- {
1709
- width: this.config.modalWidth,
1710
- height: 280,
1711
- arrowHeight: 12,
1712
- gap: 8,
1713
- viewportPadding: 16
1714
- }
1715
- );
1716
- this.modalContainer = document.createElement("div"), this.modalContainer.className = `qaid-modal-container qaid-${i.position}`, this.modalContainer.style.top = `${i.top}px`, this.modalContainer.style.left = `${i.left}px`, this.modalContainer.style.zIndex = String(this.config.zIndex + 3);
1717
- const n = document.createElement("div");
1718
- n.className = "qaid-modal-arrow", n.style.left = `${o.left}px`;
1719
- const s = document.createElement("div");
1720
- s.className = "qaid-modal-box", s.innerHTML = this.getModalContent(), this.modalContainer.appendChild(n), this.modalContainer.appendChild(s), this.applyVars(this.modalContainer), t.appendChild(this.modalContainer), this.setupModalInteractions();
1721
- }
1722
- getModalContent() {
1723
- const t = this.feedbackData.feedbackType === "up", i = this.config.positiveIcon || D, o = this.config.negativeIcon || M;
1724
- return `
1725
- <div class="qaid-modal-header">
1726
- <button type="button" class="${this.config.buttonClass ? `qaid-type-toggle qaid-type-toggle-custom ${this.config.buttonClass} ${t ? "qaid-btn-up" : "qaid-btn-down"}` : `qaid-type-toggle ${t ? "qaid-type-up" : "qaid-type-down"}`}" title="Click to switch" aria-pressed="${t}" aria-label="${t ? "Feedback type: positive" : "Feedback type: negative"}">
1727
- ${t ? i : o}
1728
- </button>
1729
- <div class="qaid-modal-header-text">
1730
- <h3 class="qaid-modal-title" id="qaid-modal-title-${this.uid}">${this.config.text.modalTitle}</h3>
1731
- <p class="qaid-modal-subtitle" id="qaid-modal-subtitle-${this.uid}">${this.config.text.modalSubtitle}</p>
1732
- </div>
1733
- </div>
1734
- <textarea class="qaid-textarea" aria-label="${this.config.text.modalSubtitle}" placeholder="${this.config.text.placeholder}"></textarea>
1735
- <div class="qaid-btn-row">
1736
- <button type="button" class="qaid-btn-submit">${this.config.text.skipButton}</button>
1737
- </div>
1738
- `;
1739
- }
1740
- setupModalInteractions() {
1741
- this.openDialogA11y(this.modalContainer, {
1742
- labelledbyId: `qaid-modal-title-${this.uid}`,
1743
- describedbyId: `qaid-modal-subtitle-${this.uid}`
1744
- });
1745
- const t = this.modalContainer.querySelector(".qaid-textarea"), i = this.modalContainer.querySelector(".qaid-btn-submit");
1746
- t && (setTimeout(() => t.focus(), 100), t.addEventListener("input", () => {
1747
- i && (i.textContent = t.value.trim() ? this.config.text.submitButton : this.config.text.skipButton);
1748
- })), i && i.addEventListener("click", () => {
1749
- const n = t?.value.trim() || null;
1750
- this.submitMessage(n);
1751
- });
1752
- const o = this.modalContainer.querySelector(".qaid-type-toggle");
1753
- o && o.addEventListener("click", () => {
1754
- const n = this.feedbackData.feedbackType === "up" ? "down" : "up";
1755
- this.feedbackData.feedbackType = n, this.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"));
1756
- const s = this.config.positiveIcon || D, a = this.config.negativeIcon || M;
1757
- o.innerHTML = n === "up" ? s : a;
1758
- const d = n === "up" ? "Feedback type: positive" : "Feedback type: negative";
1759
- o.setAttribute("aria-pressed", String(n === "up")), o.setAttribute("aria-label", d), this.announceMsg(d), this.feedbackId && fetch(`${this.config.endpoint}/${this.feedbackId}`, {
1760
- method: "PATCH",
1761
- headers: { "Content-Type": "application/json" },
1762
- body: JSON.stringify({ feedbackType: n })
1763
- }).catch((c) => console.error("Failed to update feedback type:", c));
1764
- });
1765
- }
1766
- async submitMessage(t) {
1767
- if (this.feedbackId) {
1768
- try {
1769
- await fetch(`${this.config.endpoint}/${this.feedbackId}`, {
1770
- method: "PATCH",
1771
- headers: { "Content-Type": "application/json" },
1772
- body: JSON.stringify({ message: t })
1773
- });
1774
- } catch (i) {
1775
- console.error("Failed to submit feedback message:", i);
1776
- }
1777
- this.feedbackId = null;
1778
- }
1779
- this.closeModal();
1780
- }
1781
- closeModal() {
1782
- this.state === "MODAL_OPEN" && this.feedbackId && fetch(`${this.config.endpoint}/${this.feedbackId}`, {
1783
- method: "PATCH",
1784
- headers: { "Content-Type": "application/json" },
1785
- body: JSON.stringify({ message: null })
1786
- }).catch((t) => console.error("Failed to finalize feedback:", t)), this.closeDialogA11y(), this.modalContainer && (this.modalContainer.remove(), this.modalContainer = null), this.backdrop && (this.backdrop.remove(), this.backdrop = null), document.removeEventListener("keydown", this.boundKeyDown), this.resetFeedbackUi();
1787
- }
1788
- // ==================== Video Recording ====================
1789
- async startRecording() {
1790
- if (!(this.isRecording || this.state !== "IDLE"))
1791
- try {
1792
- this.networkCapture = qe(), this.videoRecorder = De({
1793
- maxDuration: this.config.videoOptions.maxDuration
1794
- }), this.videoRecorder.onTick((t) => {
1795
- this.updateRecordingTimer(t);
1796
- }), this.videoRecorder.onStop((t) => {
1797
- this.isRecording && (this.recordedBlob = t, this.isRecording = !1, this.announceMsg("Recording stopped"), this.removeRecordingIndicator(), document.removeEventListener("keydown", this.boundKeyDown), this.setButtonsDisabled(!1), t && t.size > 0 ? this.showRecordingPreview() : this.cleanupRecording());
1798
- }), await this.videoRecorder.start(), this.isRecording = !0, this.announceMsg("Recording started"), this.setButtonsDisabled(!0), this.showRecordingIndicator(), document.addEventListener("keydown", this.boundKeyDown);
1799
- } catch {
1800
- this.cleanupRecording();
1801
- }
1802
- }
1803
- async stopRecording() {
1804
- try {
1805
- this.recordedBlob = await this.videoRecorder.stop();
1806
- } catch {
1807
- this.recordedBlob = null;
1808
- }
1809
- this.isRecording = !1, this.announceMsg("Recording stopped"), this.removeRecordingIndicator(), document.removeEventListener("keydown", this.boundKeyDown), this.recordedBlob && this.recordedBlob.size > 0 ? this.showRecordingPreview() : this.cleanupRecording();
1810
- }
1811
- showRecordingIndicator() {
1812
- const t = this.ensureOverlayHost();
1813
- this.recordingIndicator = document.createElement("div"), this.recordingIndicator.className = "qaid-recording-indicator", this.recordingIndicator.style.zIndex = String(this.config.zIndex + 100);
1814
- const i = document.createElement("div");
1815
- i.className = "qaid-recording-dot";
1816
- const o = document.createElement("span");
1817
- o.className = "qaid-recording-time", o.textContent = this.formatTime(this.config.videoOptions.maxDuration);
1818
- const n = document.createElement("button");
1819
- n.type = "button", n.className = "qaid-recording-stop", n.textContent = "Stop", n.addEventListener("click", () => this.stopRecording()), this.recordingIndicator.appendChild(i), this.recordingIndicator.appendChild(o), this.recordingIndicator.appendChild(n), this.applyVars(this.recordingIndicator), this.overlayShadowHost && (this.overlayShadowHost.style.pointerEvents = "auto"), t.appendChild(this.recordingIndicator);
1820
- }
1821
- updateRecordingTimer(t) {
1822
- if (!this.recordingIndicator) return;
1823
- const i = this.recordingIndicator.querySelector(".qaid-recording-time");
1824
- if (i) {
1825
- const o = Math.max(0, this.config.videoOptions.maxDuration - t);
1826
- i.textContent = this.formatTime(o), o > 0 && o <= 5 && this.announceMsg(`${o} second${o === 1 ? "" : "s"} remaining`);
1827
- }
1828
- }
1829
- formatTime(t) {
1830
- const i = Math.floor(t / 60), o = t % 60;
1831
- return `${i}:${o.toString().padStart(2, "0")}`;
1832
- }
1833
- removeRecordingIndicator() {
1834
- this.recordingIndicator && (this.recordingIndicator.remove(), this.recordingIndicator = null);
1835
- }
1836
- showRecordingPreview() {
1837
- const t = this.ensureOverlayHost(), i = URL.createObjectURL(this.recordedBlob);
1838
- this.videoPreview = document.createElement("div"), this.videoPreview.className = "qaid-video-preview", this.videoPreview.style.zIndex = String(this.config.zIndex + 100);
1839
- const o = document.createElement("div");
1840
- o.className = "qaid-video-preview-box";
1841
- const n = document.createElement("h3");
1842
- n.textContent = "Review your recording", n.id = `qaid-video-title-${this.uid}`;
1843
- const s = document.createElement("video");
1844
- s.src = i, s.controls = !0, s.autoplay = !0, s.muted = !0;
1845
- const a = document.createElement("textarea");
1846
- a.placeholder = "Optional: Describe the issue you recorded...", a.setAttribute("aria-label", "Describe the issue you recorded");
1847
- const d = document.createElement("div");
1848
- d.className = "qaid-video-preview-actions";
1849
- const c = document.createElement("button");
1850
- c.type = "button", c.className = "qaid-video-btn qaid-video-btn-cancel", c.textContent = "Cancel", c.addEventListener("click", () => this.cancelRecordingPreview());
1851
- const r = document.createElement("button");
1852
- r.type = "button", r.className = "qaid-video-btn qaid-video-btn-rerecord", r.textContent = "Re-record", r.addEventListener("click", () => {
1853
- this.cancelRecordingPreview(), this.startRecording();
1854
- });
1855
- const l = document.createElement("button");
1856
- l.type = "button", l.className = "qaid-video-btn qaid-video-btn-send", l.textContent = "Send", l.addEventListener("click", () => {
1857
- const p = a.value.trim() || null;
1858
- this.submitVideoFeedback(p, l);
1859
- }), d.appendChild(c), d.appendChild(r), d.appendChild(l), o.appendChild(n), o.appendChild(s), o.appendChild(a), o.appendChild(d), this.videoPreview.appendChild(o), this.applyVars(this.videoPreview), this.overlayShadowHost && (this.overlayShadowHost.style.pointerEvents = "auto"), t.appendChild(this.videoPreview), this.openDialogA11y(o, { labelledbyId: n.id }), document.addEventListener("keydown", this.boundKeyDown);
1860
- }
1861
- cancelRecordingPreview() {
1862
- this.removeVideoPreview(), this.cleanupRecording();
1863
- }
1864
- removeVideoPreview() {
1865
- if (this.closeDialogA11y(), this.videoPreview) {
1866
- const t = this.videoPreview.querySelector("video");
1867
- t?.src && URL.revokeObjectURL(t.src), this.videoPreview.remove(), this.videoPreview = null;
1868
- }
1869
- document.removeEventListener("keydown", this.boundKeyDown);
1870
- }
1871
- async submitVideoFeedback(t, i) {
1872
- if (!this.recordedBlob || this.isSendingVideo) return;
1873
- this.isSendingVideo = !0, i.disabled = !0, i.textContent = "Sending...";
1874
- const o = new FormData();
1875
- o.append("video", this.recordedBlob, `recording.${this.recordedBlob.type.includes("mp4") ? "mp4" : "webm"}`), o.append("pageUrl", window.location.href), o.append("visitorId", this.visitorId), this.config.apiKey && o.append("apiKey", this.config.apiKey), t && o.append("message", t), this.consoleCapture && o.append("consoleErrors", JSON.stringify(this.consoleCapture.errors)), this.networkCapture && o.append("networkErrors", JSON.stringify(this.networkCapture.errors));
1876
- let n = null;
1877
- try {
1878
- const s = await fetch(`${this.config.endpoint}/video`, {
1879
- method: "POST",
1880
- body: o
1881
- });
1882
- if (s.ok) {
1883
- this.announceMsg("Recording sent");
1884
- try {
1885
- n = (await s.json())?.id ?? null;
1886
- } catch {
1887
- }
1888
- } else
1889
- console.error("Failed to submit video feedback:", await s.text()), this.announceMsg("Failed to send recording", !0);
1890
- } catch (s) {
1891
- console.error("Failed to submit video feedback:", s), this.announceMsg("Failed to send recording", !0);
1892
- }
1893
- this.isSendingVideo = !1, this.removeVideoPreview(), this.cleanupRecording(), await this.tryLaunchQuest("video", n);
1894
- }
1895
- cleanupRecording() {
1896
- 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.setButtonsDisabled(!1), this.overlayShadowHost && this.state === "IDLE" && (this.overlayShadowHost.style.pointerEvents = "none");
1897
- }
1898
- setButtonsDisabled(t) {
1899
- if (!this.buttonsContainer) return;
1900
- this.buttonsContainer.querySelectorAll("button.qaid-btn, button.qaid-btn-structural").forEach((o) => {
1901
- t ? o.classList.contains("qaid-btn-record") || (o.disabled = !0, o.style.opacity = "0.5") : (o.disabled = !1, o.style.opacity = "");
1902
- });
1903
- }
1904
- /**
1905
- * Destroy the embed and clean up all resources
1906
- */
1907
- destroy() {
1908
- this.destroyed = !0, this.domObserver && (this.domObserver.disconnect(), this.domObserver = null), this.boundBeforeSwap && (document.removeEventListener("astro:before-swap", this.boundBeforeSwap), this.boundBeforeSwap = null), this.keyboardController?.stop(), this.keyboardController = null, this.clearActiveThumb(), this.closeDialogA11y(), this.cleanupRecording(), this.removeVideoPreview(), 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.removeEventListener("mousemove", this.boundMouseMove), document.removeEventListener("click", this.boundClick, !0), document.removeEventListener("touchstart", this.boundTouchStart), document.removeEventListener("touchend", this.boundTouchEnd), 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.overlayContainer = null, this.captureLayer = null, this.crosshairH = null, this.crosshairV = null, this.scope = null, this.marker = null, this.modalContainer = null, this.backdrop = null, this.dismissBtn = null, this.tooltipElement = null, Ut();
1909
- }
1910
- }
1911
- function at(e) {
1912
- return document.querySelector(e)?.textContent?.trim() ?? "";
1913
- }
1914
- function ri() {
1915
- const e = document.querySelector(
1916
- 'script[type="application/json"][data-feedback-config]'
1917
- );
1918
- if (!e) return null;
1919
- const t = e.textContent?.trim();
1920
- if (!t) return null;
1921
- try {
1922
- const i = JSON.parse(t);
1923
- return i.cssSelector && !i.css && (i.css = at(i.cssSelector), delete i.cssSelector), i;
1924
- } catch {
1925
- return null;
1926
- }
1927
- }
1928
- function di(e) {
1929
- const t = e.getAttribute("data-endpoint");
1930
- if (!t) return null;
1931
- const i = e.getAttribute("data-position"), o = e.getAttribute("data-zindex"), n = e.getAttribute("data-positive-color"), s = e.getAttribute("data-negative-color"), a = e.getAttribute("data-marker-color"), d = e.getAttribute("data-container"), c = e.getAttribute("data-button-class"), r = e.getAttribute("data-skip-targeting"), l = e.getAttribute("data-incognito"), p = e.getAttribute("data-button-size"), u = e.getAttribute("data-offset-x"), f = e.getAttribute("data-offset-y"), b = e.getAttribute("data-modal-width"), v = e.getAttribute("data-backdrop-opacity"), y = e.getAttribute("data-font-family"), m = e.getAttribute("data-font-size"), h = e.getAttribute("data-tooltip"), w = e.getAttribute("data-modal-title"), g = e.getAttribute("data-modal-subtitle"), S = e.getAttribute("data-placeholder"), $ = e.getAttribute("data-submit-button"), N = e.getAttribute("data-skip-button"), rt = e.getAttribute("data-positive-icon"), dt = e.getAttribute("data-negative-icon"), lt = e.getAttribute("data-api-key"), ct = e.getAttribute("data-capture-screenshot"), ht = e.getAttribute("data-annotate"), L = e.getAttribute("data-screenshot-quality"), I = e.getAttribute("data-screenshot-max-width"), A = e.getAttribute("data-screenshot-max-height"), ut = e.getAttribute("data-capture-video"), pt = e.getAttribute("data-hide-thumbs"), ft = e.getAttribute("data-hide-dismiss"), U = e.getAttribute("data-video-max-duration"), mt = e.getAttribute("data-screenshot-method"), bt = e.getAttribute("data-direction"), F = e.getAttribute("data-css-selector"), V = e.getAttribute("data-quest-base"), gt = e.getAttribute("data-quest-up"), vt = e.getAttribute("data-quest-down"), yt = e.getAttribute("data-quest-video"), wt = e.getAttribute("data-quest-api-key"), qt = e.getAttribute("data-quest-module-url");
1932
- return {
1933
- endpoint: t,
1934
- css: F ? at(F) : void 0,
1935
- apiKey: lt ?? void 0,
1936
- captureScreenshot: ct === "true" ? !0 : void 0,
1937
- // Annotation is on by default; only an explicit "false" disables it.
1938
- annotate: ht === "false" ? !1 : void 0,
1939
- screenshotOptions: L || I || A ? {
1940
- quality: L ? parseFloat(L) : void 0,
1941
- maxWidth: I ? parseInt(I, 10) : void 0,
1942
- maxHeight: A ? parseInt(A, 10) : void 0
1943
- } : void 0,
1944
- container: d ?? void 0,
1945
- buttonClass: c ?? void 0,
1946
- direction: bt ?? void 0,
1947
- position: i ?? void 0,
1948
- zIndex: o ? parseInt(o, 10) : void 0,
1949
- skipTargeting: r === "true" ? !0 : void 0,
1950
- incognito: l === "true" ? !0 : void 0,
1951
- buttonSize: p ?? void 0,
1952
- offset: u || f ? {
1953
- x: u ? parseInt(u, 10) : void 0,
1954
- y: f ? parseInt(f, 10) : void 0
1955
- } : void 0,
1956
- modalWidth: b ? parseInt(b, 10) : void 0,
1957
- backdropOpacity: v ? parseFloat(v) : void 0,
1958
- fontFamily: y ?? void 0,
1959
- fontSize: m ? parseInt(m, 10) : void 0,
1960
- colors: {
1961
- positive: n ?? void 0,
1962
- negative: s ?? void 0,
1963
- marker: a ?? void 0
1964
- },
1965
- text: h || w || g || S || $ || N ? {
1966
- tooltip: h ?? void 0,
1967
- modalTitle: w ?? void 0,
1968
- modalSubtitle: g ?? void 0,
1969
- placeholder: S ?? void 0,
1970
- submitButton: $ ?? void 0,
1971
- skipButton: N ?? void 0
1972
- } : void 0,
1973
- positiveIcon: rt ?? void 0,
1974
- negativeIcon: dt ?? void 0,
1975
- screenshotMethod: mt ?? void 0,
1976
- captureVideo: ut === "true" ? !0 : void 0,
1977
- hideThumbs: pt === "true" ? !0 : void 0,
1978
- hideDismiss: ft === "true" ? !0 : void 0,
1979
- videoOptions: U ? {
1980
- maxDuration: parseInt(U, 10)
1981
- } : void 0,
1982
- quests: V ? {
1983
- base: V,
1984
- up: gt ?? void 0,
1985
- down: vt ?? void 0,
1986
- video: yt ?? void 0,
1987
- apiKey: wt ?? void 0,
1988
- moduleUrl: qt ?? void 0
1989
- } : void 0
1990
- };
1991
- }
1992
- if (typeof document < "u") {
1993
- const e = () => {
1994
- const t = document.currentScript, i = ri(), o = t ? di(t) : null, n = i ?? o;
1995
- n?.endpoint && new ai(n);
1996
- };
1997
- document.readyState === "loading" ? document.addEventListener("DOMContentLoaded", e) : e();
1998
- }
1999
- export {
2000
- Xe as AnnotationEditor,
2001
- ai as QaidFeedback,
2002
- Oe as captureDomScreenshot,
2003
- qe as captureNetworkErrors,
2004
- je as compositeAnnotations,
2005
- De as createVideoRecorder,
2006
- O as drawShape,
2007
- Ae as getSupportedMimeType,
2008
- li as isDomScreenshotSupported,
2009
- Ie as isVideoRecordingSupported,
2010
- Qe as openAnnotationEditor,
2011
- P as rectFromPoints
2012
- };
2013
- //# sourceMappingURL=qaid.js.map