@triagly/sdk 1.4.0-beta.5 → 1.4.0-beta.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.min.js CHANGED
@@ -1,2 +1,2 @@
1
- !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Triagly={})}(this,function(t){"use strict";class e{constructor(t,e,n){this.overlay=null,this.canvas=null,this.ctx=null,this.image=null,this.annotations=[],this.currentTool="highlight",this.currentColor="#ff0000",this.strokeWidth=3,this.isDrawing=!1,this.currentAnnotation=null,this.scale=1,this.offsetX=0,this.offsetY=0,this.pendingTextPoint=null,this.handleKeyDown=t=>{"Escape"===t.key&&this.close(!0)},this.imageData=t,this.onComplete=e,this.onCancel=n}async open(){this.image=await this.loadImage(this.imageData),this.createOverlay(),this.setupCanvas(),this.setupCanvasEventListeners(),this.render()}loadImage(t){return new Promise((e,n)=>{const i=new Image;i.onload=()=>e(i),i.onerror=()=>n(new Error("Failed to load image")),i.src=t})}createOverlay(){this.overlay=document.createElement("div"),this.overlay.className="triagly-annotation-overlay",this.overlay.innerHTML='\n <div class="triagly-annotation-header">\n <div class="triagly-annotation-tools">\n <button type="button" class="triagly-tool-btn active" data-tool="highlight" title="Highlight">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <rect x="3" y="3" width="18" height="18" rx="2" opacity="0.3" fill="currentColor"/>\n <rect x="3" y="3" width="18" height="18" rx="2"/>\n </svg>\n </button>\n <button type="button" class="triagly-tool-btn" data-tool="arrow" title="Arrow">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <line x1="5" y1="19" x2="19" y2="5"/>\n <polyline points="10,5 19,5 19,14"/>\n </svg>\n </button>\n <button type="button" class="triagly-tool-btn" data-tool="freehand" title="Draw">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <path d="M12 19l7-7 3 3-7 7-3-3z"/>\n <path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/>\n <path d="M2 2l7.586 7.586"/>\n </svg>\n </button>\n <button type="button" class="triagly-tool-btn" data-tool="text" title="Text">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <polyline points="4 7 4 4 20 4 20 7"/>\n <line x1="9" y1="20" x2="15" y2="20"/>\n <line x1="12" y1="4" x2="12" y2="20"/>\n </svg>\n </button>\n <div class="triagly-tool-divider"></div>\n <input type="color" class="triagly-color-picker" value="#ff0000" title="Color">\n </div>\n <div class="triagly-annotation-actions">\n <button type="button" class="triagly-annotation-btn triagly-btn-clear" title="Clear All">\n <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <polyline points="3 6 5 6 21 6"/>\n <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>\n </svg>\n Clear\n </button>\n <button type="button" class="triagly-annotation-btn triagly-btn-cancel">Cancel</button>\n <button type="button" class="triagly-annotation-btn triagly-btn-done">Done</button>\n </div>\n </div>\n <div class="triagly-annotation-canvas-container">\n <canvas class="triagly-annotation-canvas"></canvas>\n </div>\n <div class="triagly-text-modal" id="triagly-text-modal" style="display: none;">\n <div class="triagly-text-modal-content">\n <h3>Add Text Annotation</h3>\n <input type="text" id="triagly-text-input" placeholder="Enter text..." maxlength="200" autofocus />\n <div class="triagly-text-modal-actions">\n <button type="button" class="triagly-annotation-btn triagly-btn-cancel" id="triagly-text-cancel">Cancel</button>\n <button type="button" class="triagly-annotation-btn triagly-btn-done" id="triagly-text-confirm">Add Text</button>\n </div>\n </div>\n </div>\n ',this.injectStyles(),this.setupEventListeners(),document.body.appendChild(this.overlay)}setupCanvas(){if(!this.overlay||!this.image)return;this.canvas=this.overlay.querySelector(".triagly-annotation-canvas");const t=this.overlay.querySelector(".triagly-annotation-canvas-container");if(!this.canvas||!t)return;if(this.ctx=this.canvas.getContext("2d"),!this.ctx)return;const e=t.getBoundingClientRect(),n=e.width-40,i=e.height-40,r=this.image.width/this.image.height;let o,a;r>n/i?(o=Math.min(this.image.width,n),a=o/r):(a=Math.min(this.image.height,i),o=a*r),this.scale=o/this.image.width,this.canvas.width=o,this.canvas.height=a,this.canvas.style.width=`${o}px`,this.canvas.style.height=`${a}px`,this.offsetX=(e.width-o)/2,this.offsetY=(e.height-a)/2}setupEventListeners(){if(!this.overlay)return;const t=this.overlay.querySelectorAll(".triagly-tool-btn");t.forEach(e=>{e.addEventListener("click",e=>{const n=e.currentTarget.dataset.tool;n&&(this.currentTool=n,t.forEach(t=>t.classList.remove("active")),e.currentTarget.classList.add("active"))})});const e=this.overlay.querySelector(".triagly-color-picker");e&&e.addEventListener("change",t=>{this.currentColor=t.target.value});const n=this.overlay.querySelector(".triagly-btn-clear");n&&n.addEventListener("click",()=>{this.annotations=[],this.render()});const i=this.overlay.querySelector(".triagly-annotation-actions .triagly-btn-cancel");i&&i.addEventListener("click",()=>this.close(!0));const r=this.overlay.querySelector(".triagly-btn-done");r&&r.addEventListener("click",()=>this.close(!1));const o=this.overlay.querySelector("#triagly-text-confirm"),a=this.overlay.querySelector("#triagly-text-cancel"),s=this.overlay.querySelector("#triagly-text-input");o?.addEventListener("click",()=>this.confirmTextAnnotation()),a?.addEventListener("click",()=>this.hideTextModal()),s?.addEventListener("keydown",t=>{"Enter"===t.key?(t.preventDefault(),this.confirmTextAnnotation()):"Escape"===t.key&&(t.preventDefault(),this.hideTextModal())}),document.addEventListener("keydown",this.handleKeyDown)}setupCanvasEventListeners(){this.canvas&&(this.canvas.addEventListener("mousedown",t=>this.handleMouseDown(t)),this.canvas.addEventListener("mousemove",t=>this.handleMouseMove(t)),this.canvas.addEventListener("mouseup",()=>this.handleMouseUp()),this.canvas.addEventListener("mouseleave",()=>this.handleMouseUp()),this.canvas.addEventListener("touchstart",t=>{t.preventDefault();const e=t.touches[0];this.handleMouseDown(e)},{passive:!1}),this.canvas.addEventListener("touchmove",t=>{t.preventDefault();const e=t.touches[0];this.handleMouseMove(e)},{passive:!1}),this.canvas.addEventListener("touchend",()=>this.handleMouseUp()),this.canvas.addEventListener("touchcancel",()=>this.handleMouseUp()))}removeCanvasEventListeners(){this.canvas&&(this.canvas=null,this.ctx=null)}getCanvasPoint(t){if(!this.canvas)return{x:0,y:0};const e=this.canvas.getBoundingClientRect();return{x:t.clientX-e.left,y:t.clientY-e.top}}handleMouseDown(t){const e=this.getCanvasPoint(t);if(this.isDrawing=!0,"text"===this.currentTool)return this.pendingTextPoint=e,this.showTextModal(),void(this.isDrawing=!1);this.currentAnnotation={type:this.currentTool,color:this.currentColor,strokeWidth:this.strokeWidth,points:[e]}}showTextModal(){if(!this.overlay)return;const t=this.overlay.querySelector("#triagly-text-modal"),e=this.overlay.querySelector("#triagly-text-input");t&&e&&(t.style.display="flex",e.value="",e.focus())}hideTextModal(){if(!this.overlay)return;const t=this.overlay.querySelector("#triagly-text-modal");t&&(t.style.display="none"),this.pendingTextPoint=null}confirmTextAnnotation(){if(!this.overlay||!this.pendingTextPoint)return;const t=this.overlay.querySelector("#triagly-text-input"),e=t?.value.trim();e&&(this.annotations.push({type:"text",color:this.currentColor,strokeWidth:this.strokeWidth,points:[this.pendingTextPoint],text:e}),this.render()),this.hideTextModal()}handleMouseMove(t){if(!this.isDrawing||!this.currentAnnotation)return;const e=this.getCanvasPoint(t);"freehand"===this.currentTool||1===this.currentAnnotation.points.length?this.currentAnnotation.points.push(e):this.currentAnnotation.points[1]=e,this.render()}handleMouseUp(){this.isDrawing&&this.currentAnnotation&&this.currentAnnotation.points.length>1&&this.annotations.push(this.currentAnnotation),this.isDrawing=!1,this.currentAnnotation=null,this.render()}render(){if(this.ctx&&this.canvas&&this.image){this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.ctx.drawImage(this.image,0,0,this.canvas.width,this.canvas.height);for(const t of this.annotations)this.drawAnnotation(t);this.currentAnnotation&&this.drawAnnotation(this.currentAnnotation)}}drawAnnotation(t){if(this.ctx)switch(this.ctx.strokeStyle=t.color,this.ctx.fillStyle=t.color,this.ctx.lineWidth=t.strokeWidth,this.ctx.lineCap="round",this.ctx.lineJoin="round",t.type){case"highlight":this.drawHighlight(t);break;case"arrow":this.drawArrow(t);break;case"freehand":this.drawFreehand(t);break;case"text":this.drawText(t)}}drawHighlight(t){if(!this.ctx||t.points.length<2)return;const[e,n]=t.points,i=n.x-e.x,r=n.y-e.y;this.ctx.globalAlpha=.3,this.ctx.fillRect(e.x,e.y,i,r),this.ctx.globalAlpha=1,this.ctx.strokeRect(e.x,e.y,i,r)}drawArrow(t){if(!this.ctx||t.points.length<2)return;const[e,n]=t.points,i=Math.atan2(n.y-e.y,n.x-e.x);this.ctx.beginPath(),this.ctx.moveTo(e.x,e.y),this.ctx.lineTo(n.x,n.y),this.ctx.stroke(),this.ctx.beginPath(),this.ctx.moveTo(n.x,n.y),this.ctx.lineTo(n.x-15*Math.cos(i-Math.PI/6),n.y-15*Math.sin(i-Math.PI/6)),this.ctx.moveTo(n.x,n.y),this.ctx.lineTo(n.x-15*Math.cos(i+Math.PI/6),n.y-15*Math.sin(i+Math.PI/6)),this.ctx.stroke()}drawFreehand(t){if(this.ctx&&!(t.points.length<2)){this.ctx.beginPath(),this.ctx.moveTo(t.points[0].x,t.points[0].y);for(let e=1;e<t.points.length;e++)this.ctx.lineTo(t.points[e].x,t.points[e].y);this.ctx.stroke()}}drawText(t){if(!this.ctx||!t.text||t.points.length<1)return;const e=t.points[0];this.ctx.font="bold 16px sans-serif",this.ctx.textBaseline="top";const n=this.ctx.measureText(t.text);this.ctx.fillStyle="rgba(0, 0, 0, 0.7)",this.ctx.fillRect(e.x-4,e.y-4,n.width+8,28),this.ctx.fillStyle="#ffffff",this.ctx.fillText(t.text,e.x,e.y)}close(t){if(document.removeEventListener("keydown",this.handleKeyDown),this.removeCanvasEventListeners(),t)this.onCancel();else{const t=this.renderFinalImage();this.onComplete(t)}this.overlay&&(this.overlay.remove(),this.overlay=null)}renderFinalImage(){if(!this.image)return this.imageData;const t=document.createElement("canvas");t.width=this.image.width,t.height=this.image.height;const e=t.getContext("2d");if(!e)return this.imageData;e.drawImage(this.image,0,0);const n=1/this.scale;for(const t of this.annotations){e.strokeStyle=t.color,e.fillStyle=t.color,e.lineWidth=t.strokeWidth*n,e.lineCap="round",e.lineJoin="round";const i=t.points.map(t=>({x:t.x*n,y:t.y*n})),r={...t,points:i};switch(t.type){case"highlight":this.drawHighlightOnCtx(e,r);break;case"arrow":this.drawArrowOnCtx(e,r,n);break;case"freehand":this.drawFreehandOnCtx(e,r);break;case"text":this.drawTextOnCtx(e,r,n)}}return t.toDataURL("image/jpeg",.9)}drawHighlightOnCtx(t,e){if(e.points.length<2)return;const[n,i]=e.points,r=i.x-n.x,o=i.y-n.y;t.globalAlpha=.3,t.fillRect(n.x,n.y,r,o),t.globalAlpha=1,t.strokeRect(n.x,n.y,r,o)}drawArrowOnCtx(t,e,n){if(e.points.length<2)return;const[i,r]=e.points,o=15*n,a=Math.atan2(r.y-i.y,r.x-i.x);t.beginPath(),t.moveTo(i.x,i.y),t.lineTo(r.x,r.y),t.stroke(),t.beginPath(),t.moveTo(r.x,r.y),t.lineTo(r.x-o*Math.cos(a-Math.PI/6),r.y-o*Math.sin(a-Math.PI/6)),t.moveTo(r.x,r.y),t.lineTo(r.x-o*Math.cos(a+Math.PI/6),r.y-o*Math.sin(a+Math.PI/6)),t.stroke()}drawFreehandOnCtx(t,e){if(!(e.points.length<2)){t.beginPath(),t.moveTo(e.points[0].x,e.points[0].y);for(let n=1;n<e.points.length;n++)t.lineTo(e.points[n].x,e.points[n].y);t.stroke()}}drawTextOnCtx(t,e,n){if(!e.text||e.points.length<1)return;const i=e.points[0],r=16*n;t.font=`bold ${r}px sans-serif`,t.textBaseline="top";const o=t.measureText(e.text),a=4*n;t.fillStyle="rgba(0, 0, 0, 0.7)",t.fillRect(i.x-a,i.y-a,o.width+2*a,1.2*r+2*a),t.fillStyle="#ffffff",t.fillText(e.text,i.x,i.y)}injectStyles(){const t="triagly-annotation-styles";if(document.getElementById(t))return;const e=document.createElement("style");e.id=t,e.textContent="\n .triagly-annotation-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 2000000;\n background: rgba(0, 0, 0, 0.9);\n display: flex;\n flex-direction: column;\n }\n\n .triagly-annotation-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 20px;\n background: #1a1a1a;\n border-bottom: 1px solid #333;\n }\n\n .triagly-annotation-tools {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .triagly-tool-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border: none;\n border-radius: 8px;\n background: #333;\n color: #fff;\n cursor: pointer;\n transition: all 0.2s;\n }\n\n .triagly-tool-btn:hover {\n background: #444;\n }\n\n .triagly-tool-btn.active {\n background: #0066cc;\n }\n\n .triagly-tool-divider {\n width: 1px;\n height: 24px;\n background: #444;\n margin: 0 8px;\n }\n\n .triagly-color-picker {\n width: 40px;\n height: 40px;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n padding: 0;\n background: none;\n }\n\n .triagly-color-picker::-webkit-color-swatch-wrapper {\n padding: 4px;\n }\n\n .triagly-color-picker::-webkit-color-swatch {\n border: none;\n border-radius: 4px;\n }\n\n .triagly-annotation-actions {\n display: flex;\n gap: 8px;\n }\n\n .triagly-annotation-btn {\n padding: 8px 16px;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .triagly-btn-clear {\n background: transparent;\n color: #999;\n border: 1px solid #444;\n }\n\n .triagly-btn-clear:hover {\n background: #333;\n color: #fff;\n }\n\n .triagly-btn-cancel {\n background: #333;\n color: #fff;\n }\n\n .triagly-btn-cancel:hover {\n background: #444;\n }\n\n .triagly-btn-done {\n background: #0066cc;\n color: #fff;\n }\n\n .triagly-btn-done:hover {\n background: #0055aa;\n }\n\n .triagly-annotation-canvas-container {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n padding: 20px;\n }\n\n .triagly-annotation-canvas {\n cursor: crosshair;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);\n border-radius: 4px;\n }\n\n .triagly-text-modal {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.7);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000000;\n }\n\n .triagly-text-modal-content {\n background: #1a1a1a;\n border-radius: 12px;\n padding: 24px;\n min-width: 320px;\n max-width: 400px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);\n }\n\n .triagly-text-modal-content h3 {\n color: #fff;\n font-size: 16px;\n font-weight: 600;\n margin: 0 0 16px 0;\n }\n\n .triagly-text-modal-content input {\n width: 100%;\n padding: 12px;\n border: 1px solid #444;\n border-radius: 8px;\n background: #2a2a2a;\n color: #fff;\n font-size: 14px;\n outline: none;\n box-sizing: border-box;\n }\n\n .triagly-text-modal-content input:focus {\n border-color: #0066cc;\n }\n\n .triagly-text-modal-content input::placeholder {\n color: #666;\n }\n\n .triagly-text-modal-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n margin-top: 16px;\n }\n ",document.head.appendChild(e)}}class n{constructor(t){this.container=null,this.isOpen=!1,this.previouslyFocusedElement=null,this.focusableElements=[],this.screenshotDataUrl=null,this.themeMediaQuery=null,this.updateButtonTheme=()=>{const t=document.getElementById("triagly-button");if(!t)return;const e=this.getResolvedTheme();t.classList.remove("triagly-theme-light","triagly-theme-dark"),t.classList.add(`triagly-theme-${e}`)},this.config=t}getResolvedTheme(){const t=this.config.theme||"auto";return"auto"===t?"undefined"!=typeof window&&window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":t}setupThemeListener(){"auto"===(this.config.theme||"auto")&&"undefined"!=typeof window&&window.matchMedia&&(this.themeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)"),this.themeMediaQuery.addEventListener?this.themeMediaQuery.addEventListener("change",this.updateButtonTheme):this.themeMediaQuery.addListener&&this.themeMediaQuery.addListener(this.updateButtonTheme))}init(){this.createButton(),this.injectStyles(),this.setupThemeListener(),this.config.turnstileSiteKey&&this.loadTurnstileScript()}loadTurnstileScript(){if(window.turnstile||document.querySelector('script[src*="turnstile"]'))return;const t=document.createElement("script");t.src="https://challenges.cloudflare.com/turnstile/v0/api.js",t.async=!0,t.defer=!0,document.head.appendChild(t)}createButton(){const t=document.createElement("button");t.id="triagly-button",t.className="triagly-button";const e=this.getResolvedTheme();t.classList.add(`triagly-theme-${e}`);const n=this.config.buttonShape||"rounded";t.classList.add(`triagly-shape-${n}`);const i=this.config.orientation||"horizontal";t.classList.add(`triagly-orientation-${i}`);const r='<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="currentColor" class="triagly-icon"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>',o=this.config.buttonText||"Feedback";"circular"===n?(t.innerHTML=r,t.setAttribute("aria-label",o)):"expandable"===n?(t.innerHTML=`<span class="triagly-btn-icon">${r}</span><span class="triagly-btn-text"> ${this.config.buttonText||"Feedback"}</span>`,t.setAttribute("aria-label",o)):t.innerHTML=`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" class="triagly-icon"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg><span class="triagly-btn-label">${o}</span>`,t.onclick=()=>this.toggle();const a=this.config.position||"bottom-right";t.classList.add(`triagly-${a}`),"expandable"===n&&(a.includes("right")?t.classList.add("triagly-expand-left"):a.includes("left")&&t.classList.add("triagly-expand-right")),this.config.offsetX&&(a.includes("right")?t.style.right=this.config.offsetX:a.includes("left")&&(t.style.left=this.config.offsetX)),this.config.offsetY&&(a.includes("top")?t.style.top=this.config.offsetY:a.includes("bottom")&&(t.style.bottom=this.config.offsetY)),document.body.appendChild(t)}toggle(){this.isOpen?this.close():this.open()}open(){this.isOpen||(this.previouslyFocusedElement=document.activeElement,this.container=this.createContainer(),document.body.appendChild(this.container),this.isOpen=!0,this.config.onOpen&&this.config.onOpen(),setTimeout(()=>{this.setupKeyboardEvents(),this.setupFocusTrap();const t=this.container?.querySelector("#triagly-description");t?.focus()},0))}close(t){if(!this.isOpen||!this.container)return;const e=this.container._tabHandler;e&&document.removeEventListener("keydown",e,!0),this.container.remove(),this.container=null,this.isOpen=!1,this.screenshotDataUrl=null,this.previouslyFocusedElement&&(this.previouslyFocusedElement.focus(),this.previouslyFocusedElement=null),"cancel"===t&&this.config.onCancel?this.config.onCancel():"dismiss"===t&&this.config.onDismiss?this.config.onDismiss():"overlay"===t&&this.config.onOverlayClick&&this.config.onOverlayClick(),this.config.onClose&&this.config.onClose()}createContainer(){const t=document.createElement("div");t.className="triagly-overlay",t.setAttribute("role","dialog"),t.setAttribute("aria-modal","true"),t.setAttribute("aria-label","Send feedback"),t.onclick=e=>{e.target===t&&this.close("overlay")};const e=document.createElement("div");e.className="triagly-modal",e.setAttribute("role","document");const n=document.createElement("div");n.className="triagly-header",n.innerHTML='\n <button type="button" class="triagly-close" aria-label="Close feedback form">×</button>\n ';const i=n.querySelector(".triagly-close");i?.addEventListener("click",()=>this.close("dismiss"));const r=document.createElement("form");r.className="triagly-form",r.innerHTML=`\n <div class="triagly-field">\n <label for="triagly-description">What's on your mind?</label>\n <textarea\n id="triagly-description"\n required\n rows="5"\n placeholder="${this.config.placeholderText||"Describe what happened..."}"\n ></textarea>\n </div>\n\n <div class="triagly-field">\n <label for="triagly-name">Name (optional)</label>\n <input\n type="text"\n id="triagly-name"\n placeholder="Your name"\n />\n </div>\n\n <div class="triagly-field">\n <label for="triagly-email">Email (optional)</label>\n <input\n type="email"\n id="triagly-email"\n placeholder="your@email.com"\n />\n </div>\n\n ${this.config.enableScreenshot?`\n <div class="triagly-field triagly-screenshot-field">\n <label>Screenshot (optional)</label>\n <input type="file" id="triagly-screenshot-input" accept="image/*" style="display: none;" />\n <div class="triagly-screenshot-controls" id="triagly-screenshot-controls">\n <button type="button" class="triagly-btn-upload" id="triagly-upload-btn">\n <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>\n <polyline points="17 8 12 3 7 8"/>\n <line x1="12" y1="3" x2="12" y2="15"/>\n </svg>\n ${this.config.screenshotButtonText||"Upload Screenshot"}\n </button>\n </div>\n <div class="triagly-screenshot-error" id="triagly-screenshot-error" style="display: none;" role="alert"></div>\n <div class="triagly-screenshot-preview" id="triagly-screenshot-preview" style="display: none;">\n <img id="triagly-screenshot-img" alt="Screenshot preview" />\n <div class="triagly-screenshot-actions">\n ${!1!==this.config.enableAnnotation?'\n <button type="button" class="triagly-btn-icon" id="triagly-annotate-btn" title="Add annotations">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <path d="M12 19l7-7 3 3-7 7-3-3z"/>\n <path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/>\n </svg>\n </button>\n ':""}\n <button type="button" class="triagly-btn-icon" id="triagly-change-screenshot-btn" title="Upload different screenshot">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>\n <polyline points="17 8 12 3 7 8"/>\n <line x1="12" y1="3" x2="12" y2="15"/>\n </svg>\n </button>\n <button type="button" class="triagly-btn-icon triagly-btn-danger" id="triagly-remove-screenshot-btn" title="Remove screenshot">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <line x1="18" y1="6" x2="6" y2="18"/>\n <line x1="6" y1="6" x2="18" y2="18"/>\n </svg>\n </button>\n </div>\n </div>\n </div>\n `:""}\n\n ${this.config.turnstileSiteKey?`\n <div class="triagly-field triagly-turnstile">\n <div class="cf-turnstile" data-sitekey="${this.config.turnstileSiteKey}" data-theme="light"></div>\n </div>\n `:""}\n\n <div class="triagly-actions">\n <button type="button" class="triagly-btn-secondary" id="triagly-cancel" aria-label="Cancel and close feedback form">\n Cancel\n </button>\n <button type="submit" class="triagly-btn-primary" aria-label="Submit feedback">\n Send Feedback\n </button>\n </div>\n\n <div class="triagly-status" id="triagly-status" role="status" aria-live="polite"></div>\n `;const o=r.querySelector("#triagly-cancel");o?.addEventListener("click",()=>this.close("cancel")),r.onsubmit=t=>{t.preventDefault(),this.handleSubmit(r)},this.config.enableScreenshot&&this.setupScreenshotHandlers(r);const a=document.createElement("div");return a.className="triagly-footer",a.innerHTML='\n <a href="https://triagly.com" target="_blank" rel="noopener noreferrer" class="triagly-branding">\n Powered by <strong>Triagly</strong>\n </a>\n ',e.appendChild(n),e.appendChild(r),e.appendChild(a),t.appendChild(e),this.config.turnstileSiteKey&&setTimeout(()=>{this.renderTurnstileWidget(r)},100),t}renderTurnstileWidget(t){const e=t.querySelector(".cf-turnstile");if(e)if(window.turnstile)try{const t=window.turnstile.render(e,{sitekey:this.config.turnstileSiteKey,theme:"dark"===this.config.theme?"dark":"light",callback:n=>{e.setAttribute("data-turnstile-response",n),e.setAttribute("data-widget-id",t)},"error-callback":()=>{console.error("Triagly: Turnstile widget error")},"expired-callback":()=>{e.removeAttribute("data-turnstile-response")}});e.setAttribute("data-widget-id",t)}catch(t){console.error("Triagly: Failed to render Turnstile widget:",t instanceof Error?t.message:"Unknown error")}else console.warn('Triagly: Turnstile script not loaded. Please include: <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer><\/script>')}setupScreenshotHandlers(t){const n=t.querySelector("#triagly-screenshot-input"),i=t.querySelector("#triagly-upload-btn"),r=t.querySelector("#triagly-annotate-btn"),o=t.querySelector("#triagly-change-screenshot-btn"),a=t.querySelector("#triagly-remove-screenshot-btn"),s=t.querySelector("#triagly-screenshot-preview"),l=t.querySelector("#triagly-screenshot-controls"),c=t.querySelector("#triagly-screenshot-img"),d=t.querySelector("#triagly-screenshot-error"),h=t=>{d&&(d.textContent=t,d.style.display="block")},g=()=>{d&&(d.textContent="",d.style.display="none")},u=t=>{if(g(),!t.type.startsWith("image/")){const t="Invalid file type. Please select an image.";return h(t),void(this.config.onScreenshotError&&this.config.onScreenshotError(new Error(t)))}const e=this.config.screenshotMaxFileSize??10485760;if(t.size>e){const t=`File too large. Maximum size is ${Math.round(e/1048576)}MB.`;return h(t),void(this.config.onScreenshotError&&this.config.onScreenshotError(new Error(t)))}const n=new FileReader;n.onload=t=>{const e=t.target?.result;this.screenshotDataUrl=e,c&&(c.src=e),s&&(s.style.display="block"),l&&(l.style.display="none"),this.config.onScreenshotUpload&&this.config.onScreenshotUpload(e)},n.onerror=()=>{const t="Failed to read file. Please try again.";h(t),this.config.onScreenshotError&&this.config.onScreenshotError(new Error(t))},n.readAsDataURL(t)};n?.addEventListener("change",()=>{const t=n.files?.[0];t&&u(t),n.value=""}),i?.addEventListener("click",()=>{g(),n?.click()}),o?.addEventListener("click",()=>{g(),n?.click()}),r?.addEventListener("click",()=>{if(!this.screenshotDataUrl)return;new e(this.screenshotDataUrl,t=>{this.screenshotDataUrl=t,c&&(c.src=t)},()=>{}).open()}),a?.addEventListener("click",()=>{this.screenshotDataUrl=null,s&&(s.style.display="none"),l&&(l.style.display="block"),c&&(c.src="")})}async handleSubmit(t){const e=t.querySelector("#triagly-description"),n=t.querySelector("#triagly-name"),i=t.querySelector("#triagly-email"),r=t.querySelector("#triagly-status"),o=t.querySelector('button[type="submit"]'),a=t.querySelector(".cf-turnstile");o.disabled=!0,o.textContent="Sending...";try{let t;a&&(t=a.getAttribute("data-turnstile-response")||void 0);const o={description:e.value.trim(),reporterName:n.value.trim()||void 0,reporterEmail:i.value.trim()||void 0,turnstileToken:t,screenshot:this.screenshotDataUrl||void 0},s=new Promise((t,e)=>{const n=()=>{document.removeEventListener("triagly:success",n),document.removeEventListener("triagly:error",i),t()},i=t=>{document.removeEventListener("triagly:success",n),document.removeEventListener("triagly:error",i),e(t.detail)};document.addEventListener("triagly:success",n,{once:!0}),document.addEventListener("triagly:error",i,{once:!0}),setTimeout(()=>{document.removeEventListener("triagly:success",n),document.removeEventListener("triagly:error",i),e(new Error("Submission timeout"))},3e4)}),l=new CustomEvent("triagly:submit",{detail:o,bubbles:!0});document.dispatchEvent(l),await s,r.className="triagly-status triagly-success",r.textContent=this.config.successMessage||"Feedback sent successfully!",setTimeout(()=>{this.close()},2e3)}catch(t){r.className="triagly-status triagly-error";const e=t instanceof Error?t.message:this.config.errorMessage||"Failed to send feedback. Please try again.";r.textContent=e,o.disabled=!1,o.textContent="Send Feedback"}}injectStyles(){if(document.getElementById("triagly-styles"))return;const t=document.createElement("style");t.id="triagly-styles",t.textContent="\n .triagly-button {\n position: fixed;\n z-index: 999999;\n padding: 12px 20px;\n background: var(--triagly-button-bg, #18181b);\n color: var(--triagly-button-text, #ffffff);\n border: none;\n border-radius: var(--triagly-button-radius, 8px);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n box-shadow: var(--triagly-button-shadow, 0 4px 12px rgba(0, 0, 0, 0.15));\n transition: all 0.2s;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n }\n\n .triagly-button:hover {\n background: var(--triagly-button-bg-hover, #27272a);\n transform: translateY(-2px);\n box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(0, 0, 0, 0.2));\n }\n\n /* Dark theme: white button with black text */\n .triagly-button.triagly-theme-dark {\n --triagly-button-bg: #ffffff;\n --triagly-button-text: #18181b;\n --triagly-button-bg-hover: #f4f4f5;\n --triagly-button-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);\n --triagly-button-shadow-hover: 0 6px 16px rgba(0, 0, 0, 0.35);\n }\n\n /* Light theme: dark button with white text (default) */\n .triagly-button.triagly-theme-light {\n --triagly-button-bg: #18181b;\n --triagly-button-text: #ffffff;\n --triagly-button-bg-hover: #27272a;\n --triagly-button-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n --triagly-button-shadow-hover: 0 6px 16px rgba(0, 0, 0, 0.2);\n }\n\n .triagly-button .triagly-icon {\n flex-shrink: 0;\n }\n\n .triagly-button .triagly-btn-label {\n line-height: 1;\n }\n\n /* Prevent expandable buttons from shifting on hover */\n .triagly-button.triagly-shape-expandable:hover {\n transform: translateY(0) !important;\n }\n\n .triagly-bottom-right { bottom: 20px; right: 20px; }\n .triagly-bottom-left { bottom: 20px; left: 20px; }\n .triagly-top-right { top: 20px; right: 20px; }\n .triagly-top-left { top: 20px; left: 20px; }\n\n /* Edge-aligned positions (0 offset from edges) */\n .triagly-edge-bottom-right { bottom: 0; right: 0; }\n .triagly-edge-bottom-left { bottom: 0; left: 0; }\n .triagly-edge-top-right { top: 0; right: 0; }\n .triagly-edge-top-left { top: 0; left: 0; }\n .triagly-edge-right { top: 50%; right: 0; transform: translateY(-50%); }\n .triagly-edge-left { top: 50%; left: 0; transform: translateY(-50%); }\n .triagly-edge-top { top: 0; left: 50%; transform: translateX(-50%); }\n .triagly-edge-bottom { bottom: 0; left: 50%; transform: translateX(-50%); }\n\n /* Button shapes */\n .triagly-shape-rounded {\n border-radius: var(--triagly-button-radius, 8px);\n }\n .triagly-shape-circular {\n border-radius: 50%;\n width: 60px;\n height: 60px;\n padding: 0;\n font-size: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .triagly-shape-square {\n border-radius: 0;\n }\n .triagly-shape-pill {\n border-radius: 30px;\n }\n .triagly-shape-expandable {\n border-radius: 50%;\n width: 60px;\n height: 60px;\n min-width: 60px;\n padding: 0;\n font-size: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n border-radius 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n padding 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n background 0.2s,\n box-shadow 0.2s;\n white-space: nowrap;\n }\n \n /* Expansion direction - expands left for right-positioned buttons */\n .triagly-shape-expandable.triagly-expand-left {\n flex-direction: row-reverse;\n }\n \n /* Expansion direction - expands right for left-positioned buttons */\n .triagly-shape-expandable.triagly-expand-right {\n flex-direction: row;\n }\n \n .triagly-shape-expandable .triagly-btn-icon {\n display: inline-block;\n flex-shrink: 0;\n transition: margin 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n }\n .triagly-shape-expandable .triagly-btn-text {\n display: inline-block;\n width: 0;\n opacity: 0;\n overflow: hidden;\n font-size: 14px;\n font-weight: 500;\n transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n }\n \n /* Hover state */\n .triagly-shape-expandable:hover {\n width: auto;\n min-width: auto;\n padding: 12px 20px;\n border-radius: 30px;\n background: var(--triagly-button-bg-hover);\n box-shadow: var(--triagly-button-shadow-hover);\n }\n .triagly-shape-expandable:hover .triagly-btn-text {\n width: auto;\n opacity: 1;\n }\n\n /* Button orientations */\n .triagly-orientation-horizontal {\n writing-mode: horizontal-tb;\n }\n .triagly-orientation-vertical {\n writing-mode: vertical-rl;\n text-orientation: mixed;\n }\n\n .triagly-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--triagly-overlay-bg, rgba(0, 0, 0, 0.5));\n z-index: 1000000;\n display: flex;\n align-items: center;\n justify-content: center;\n animation: triagly-fadeIn 0.2s;\n }\n\n @keyframes triagly-fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n .triagly-modal {\n background: var(--triagly-modal-bg, #ffffff);\n border-radius: var(--triagly-modal-radius, 12px);\n width: 90%;\n max-width: var(--triagly-modal-max-width, 500px);\n max-height: 90vh;\n overflow-y: auto;\n box-shadow: var(--triagly-modal-shadow, 0 20px 60px rgba(0, 0, 0, 0.3));\n animation: triagly-slideUp 0.3s;\n }\n\n @keyframes triagly-slideUp {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n .triagly-header {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n padding: 8px 12px 0;\n background: var(--triagly-header-bg, #ffffff);\n }\n\n .triagly-close {\n background: none;\n border: none;\n font-size: 28px;\n color: #6b7280;\n cursor: pointer;\n padding: 0;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n transition: all 0.2s;\n }\n\n .triagly-close:hover {\n background: #f3f4f6;\n color: #111827;\n }\n\n .triagly-form {\n padding: 24px;\n background: var(--triagly-form-bg, #ffffff);\n }\n\n .triagly-field {\n margin-bottom: 16px;\n }\n\n .triagly-field label {\n display: block;\n margin-bottom: 6px;\n font-size: 14px;\n font-weight: 500;\n color: var(--triagly-label-text, #374151);\n }\n\n .triagly-field input,\n .triagly-field textarea {\n width: 100%;\n padding: 10px 12px;\n background: var(--triagly-input-bg, #ffffff);\n border: 1px solid var(--triagly-input-border, #d1d5db);\n border-radius: var(--triagly-input-radius, 6px);\n color: var(--triagly-input-text, #111827);\n font-size: 14px;\n font-family: inherit;\n transition: border-color 0.2s;\n box-sizing: border-box;\n }\n\n .triagly-field input:focus,\n .triagly-field textarea:focus {\n outline: none;\n background: var(--triagly-input-bg, #ffffff);\n border-color: var(--triagly-input-border-focus, #a1a1aa);\n box-shadow: 0 0 0 2px rgba(161, 161, 170, 0.15);\n }\n\n /* Focus visible styles for accessibility */\n .triagly-button:focus-visible,\n .triagly-field input:focus-visible,\n .triagly-field textarea:focus-visible,\n .triagly-btn-primary:focus-visible,\n .triagly-btn-secondary:focus-visible,\n .triagly-close:focus-visible {\n outline: 2px solid #a1a1aa;\n outline-offset: 2px;\n }\n\n .triagly-turnstile {\n display: flex;\n justify-content: center;\n margin: 8px 0;\n }\n\n .triagly-actions {\n display: flex;\n gap: 12px;\n margin-top: 24px;\n }\n\n .triagly-btn-primary,\n .triagly-btn-secondary {\n flex: 1;\n padding: 10px 16px;\n border-radius: var(--triagly-btn-radius, 6px);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n border: none;\n }\n\n .triagly-btn-primary {\n background: var(--triagly-btn-primary-bg, #18181b);\n color: var(--triagly-btn-primary-text, #ffffff);\n }\n\n .triagly-btn-primary:hover:not(:disabled) {\n background: var(--triagly-btn-primary-bg-hover, #27272a);\n }\n\n .triagly-btn-primary:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .triagly-btn-secondary {\n background: var(--triagly-btn-secondary-bg, #f3f4f6);\n color: var(--triagly-btn-secondary-text, #374151);\n }\n\n .triagly-btn-secondary:hover {\n background: var(--triagly-btn-secondary-bg-hover, #e5e7eb);\n }\n\n .triagly-status {\n margin-top: 16px;\n padding: 12px;\n border-radius: 6px;\n font-size: 14px;\n display: none;\n }\n\n .triagly-status.triagly-success {\n display: block;\n background: var(--triagly-success-bg, #d1fae5);\n color: var(--triagly-success-text, #065f46);\n }\n\n .triagly-status.triagly-error {\n display: block;\n background: var(--triagly-error-bg, #fee2e2);\n color: var(--triagly-error-text, #991b1b);\n }\n\n .triagly-footer {\n padding: 12px 24px 16px;\n text-align: right;\n border-top: 1px solid var(--triagly-footer-border, #e5e7eb);\n background: var(--triagly-footer-bg, #f9fafb);\n border-radius: 0 0 var(--triagly-modal-radius, 12px) var(--triagly-modal-radius, 12px);\n }\n\n .triagly-branding {\n font-size: 12px;\n color: var(--triagly-footer-text, #6b7280);\n text-decoration: none;\n transition: color 0.2s;\n }\n\n .triagly-branding:hover {\n color: var(--triagly-footer-text-hover, #18181b);\n }\n\n .triagly-branding strong {\n font-weight: 600;\n }\n\n /* Screenshot styles */\n .triagly-screenshot-field {\n margin-bottom: 16px;\n }\n\n .triagly-screenshot-controls {\n display: flex;\n gap: 8px;\n }\n\n .triagly-btn-upload {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 10px 16px;\n background: var(--triagly-btn-secondary-bg, #f3f4f6);\n color: var(--triagly-btn-secondary-text, #374151);\n border: 1px dashed var(--triagly-input-border, #d1d5db);\n border-radius: var(--triagly-input-radius, 6px);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n width: 100%;\n justify-content: center;\n }\n\n .triagly-btn-upload:hover:not(:disabled) {\n background: var(--triagly-btn-secondary-bg-hover, #e5e7eb);\n border-style: solid;\n }\n\n .triagly-btn-upload:disabled {\n opacity: 0.7;\n cursor: not-allowed;\n }\n\n .triagly-screenshot-error {\n margin-top: 8px;\n padding: 8px 12px;\n background: var(--triagly-error-bg, #fee2e2);\n color: var(--triagly-error-text, #991b1b);\n border-radius: var(--triagly-input-radius, 6px);\n font-size: 13px;\n }\n\n .triagly-screenshot-preview {\n position: relative;\n border-radius: var(--triagly-input-radius, 6px);\n overflow: hidden;\n border: 1px solid var(--triagly-input-border, #d1d5db);\n background: var(--triagly-input-bg, #ffffff);\n }\n\n .triagly-screenshot-preview img {\n display: block;\n width: 100%;\n max-height: 200px;\n object-fit: contain;\n background: #f9fafb;\n }\n\n .triagly-screenshot-actions {\n position: absolute;\n top: 8px;\n right: 8px;\n display: flex;\n gap: 4px;\n }\n\n .triagly-btn-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n background: rgba(255, 255, 255, 0.9);\n border: 1px solid var(--triagly-input-border, #d1d5db);\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n color: #374151;\n }\n\n .triagly-btn-icon:hover {\n background: #ffffff;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n }\n\n .triagly-btn-icon.triagly-btn-danger:hover {\n background: #fee2e2;\n border-color: #fca5a5;\n color: #dc2626;\n }\n\n @keyframes triagly-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n\n .triagly-spin {\n animation: triagly-spin 1s linear infinite;\n }\n ",document.head.appendChild(t)}setupKeyboardEvents(){const t=t=>{"Escape"===t.key&&this.isOpen&&(t.preventDefault(),this.close("dismiss"))};document.addEventListener("keydown",t),this.container&&(this.container._keydownHandler=t)}setupFocusTrap(){if(!this.container)return;const t=this.container.querySelector(".triagly-modal");if(!t)return;if(this.focusableElements=Array.from(t.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')),0===this.focusableElements.length)return void console.warn("Triagly: No focusable elements found in modal");const e=t=>{const e=t;if("Tab"!==e.key)return;if(!this.container?.contains(document.activeElement))return;const n=this.focusableElements[0],i=this.focusableElements[this.focusableElements.length-1];e.shiftKey?document.activeElement===n&&(e.preventDefault(),i?.focus()):document.activeElement===i&&(e.preventDefault(),n?.focus())};document.addEventListener("keydown",e,!0),this.container._tabHandler=e}destroy(){if(this.container){const t=this.container._keydownHandler;t&&document.removeEventListener("keydown",t);const e=this.container._tabHandler;e&&document.removeEventListener("keydown",e,!0)}this.themeMediaQuery&&(this.themeMediaQuery.removeEventListener?this.themeMediaQuery.removeEventListener("change",this.updateButtonTheme):this.themeMediaQuery.removeListener&&this.themeMediaQuery.removeListener(this.updateButtonTheme),this.themeMediaQuery=null),this.close(),document.getElementById("triagly-button")?.remove(),document.getElementById("triagly-styles")?.remove()}}const i={production:"https://iipkklhhafrjesryscjh.supabase.co/functions/v1",staging:"https://bssghvinezdawvupcyci.supabase.co/functions/v1"};class r{constructor(t,e="production",n,r,o){this.apiUrl=(n||i[e]).replace(/\/$/,""),this.publishableKey=t,this.getToken=r,this.turnstileSiteKey=o}getTurnstileSiteKey(){return this.turnstileSiteKey}async getTurnstileToken(){const t=document.querySelector("[data-turnstile-response]");if(t){const e=t.getAttribute("data-turnstile-response");if(e)return e}if(window.turnstile)try{const t=document.querySelectorAll(".cf-turnstile");if(t.length>0){const e=t[0].getAttribute("data-widget-id");if(e){const t=window.turnstile.getResponse(e);if(t)return t}}}catch(t){console.warn("Failed to get Turnstile token:",t instanceof Error?t.message:"Unknown error")}return null}async submitFeedback(t,e,n){if(n||(n=await this.getTurnstileToken()||void 0),this.turnstileSiteKey&&!n)throw new Error("Turnstile verification required. Please complete the captcha.");let i;if(this.getToken)try{i=await this.getToken()}catch(t){throw console.error("Failed to get hardened token:",t instanceof Error?t.message:"Unknown error"),new Error("Failed to authenticate. Please try again.")}const r={publishableKey:this.publishableKey,title:t.title,description:t.description,metadata:{...e,consoleLogs:t.consoleLogs},tags:t.tags,reporterEmail:t.reporterEmail,reporterName:t.reporterName,screenshot:t.screenshot,turnstileToken:n,hardenedToken:i},o=await fetch(`${this.apiUrl}/feedback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!o.ok){const t=await o.json().catch(()=>({error:"Unknown error",message:"Unknown error"}));if(401===o.status){if("invalid_publishable_key"===t.error)throw new Error("Invalid API key. Please contact support.");if("token_required"===t.error)throw new Error("Authentication required. Please refresh and try again.");throw new Error(t.message||"Authentication failed")}if(403===o.status){if("origin_not_allowed"===t.error)throw new Error("This website is not authorized to submit feedback.");throw new Error(t.message||"Access denied")}if(429===o.status){const t=o.headers.get("Retry-After");throw new Error(`Too many requests. Please try again ${t?`in ${t} seconds`:"later"}.`)}if(400===o.status&&"captcha_failed"===t.error)throw new Error("Captcha verification failed. Please try again.");throw new Error(t.message||t.error||`Failed to submit feedback (HTTP ${o.status})`)}return await o.json()}}function o(t){const e=`${window.innerWidth}x${window.innerHeight}`,n=function(){const t=navigator.userAgent;let e="Unknown";if(t.includes("Firefox/")){const n=t.match(/Firefox\/(\d+)/)?.[1];e=`Firefox ${n}`}else if(t.includes("Chrome/")&&!t.includes("Edg")){const n=t.match(/Chrome\/(\d+)/)?.[1];e=`Chrome ${n}`}else if(t.includes("Safari/")&&!t.includes("Chrome")){const n=t.match(/Version\/(\d+)/)?.[1];e=`Safari ${n}`}else if(t.includes("Edg/")){const n=t.match(/Edg\/(\d+)/)?.[1];e=`Edge ${n}`}return e}();return{url:window.location.href,browser:n,viewport:e,userAgent:navigator.userAgent,timestamp:(new Date).toISOString(),...t}}class a{constructor(t,e=3,n=3e5){this.key=`triagly_ratelimit_${t}`,this.maxAttempts=e,this.windowMs=n}canProceed(){const t=Date.now();return!(this.getData().attempts.filter(e=>t-e<this.windowMs).length>=this.maxAttempts)}recordAttempt(){const t=Date.now(),e=this.getData();e.attempts.push(t),e.attempts=e.attempts.filter(e=>t-e<this.windowMs),this.setData(e)}getTimeUntilReset(){const t=Date.now(),e=this.getData();if(0===e.attempts.length)return 0;const n=Math.min(...e.attempts)+this.windowMs;return Math.max(0,n-t)}getData(){try{const t=localStorage.getItem(this.key);return t?JSON.parse(t):{attempts:[]}}catch{return{attempts:[]}}}setData(t){try{localStorage.setItem(this.key,JSON.stringify(t))}catch(t){console.error("Failed to store rate limit data:",t instanceof Error?t.message:"Unknown error")}}}class s{constructor(t=50,e=["error","warn"]){this.buffer=[],this.isActive=!1,this.maxLogs=t,this.levels=new Set(e),this.originalConsole={error:console.error,warn:console.warn,log:console.log}}start(){this.isActive||(this.isActive=!0,this.levels.has("error")&&(console.error=(...t)=>{this.captureLog("error",t),this.originalConsole.error.apply(console,t)}),this.levels.has("warn")&&(console.warn=(...t)=>{this.captureLog("warn",t),this.originalConsole.warn.apply(console,t)}),this.levels.has("log")&&(console.log=(...t)=>{this.captureLog("log",t),this.originalConsole.log.apply(console,t)}))}stop(){this.isActive&&(this.isActive=!1,console.error=this.originalConsole.error,console.warn=this.originalConsole.warn,console.log=this.originalConsole.log)}captureLog(t,e){try{const n=e.map(t=>{if("string"==typeof t)return t;if(t instanceof Error)return t.message;try{return JSON.stringify(t)}catch{return String(t)}}).join(" ");let i;if("error"===t){const t=e.find(t=>t instanceof Error);i=t?t.stack:(new Error).stack?.split("\n").slice(2).join("\n")}const r=this.sanitize(n),o=i?this.sanitize(i):void 0,a={level:t,message:r,timestamp:(new Date).toISOString(),stack:o};this.buffer.push(a),this.buffer.length>this.maxLogs&&this.buffer.shift()}catch(t){this.originalConsole.error("Failed to capture log:",t instanceof Error?t.message:"Unknown error")}}sanitize(t){return t.replace(/[a-zA-Z0-9_-]*token[a-zA-Z0-9_-]*\s*[:=]\s*["']?[\w-]{20,}["']?/gi,"token=***").replace(/[a-zA-Z0-9_-]*key[a-zA-Z0-9_-]*\s*[:=]\s*["']?[\w-]{20,}["']?/gi,"key=***").replace(/[a-zA-Z0-9_-]*secret[a-zA-Z0-9_-]*\s*[:=]\s*["']?[\w-]{20,}["']?/gi,"secret=***").replace(/gh[ps]_[a-zA-Z0-9]{36,}/g,"gh*_***").replace(/eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,"jwt.***").replace(/password\s*[:=]\s*["']?[^"'\s]+["']?/gi,"password=***").replace(/\b[\w._%+-]+@[\w.-]+\.[a-zA-Z]{2,}\b/g,"***@***.com").replace(/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g,"****-****-****-****").replace(/([?&])(token|key|secret|auth)=[^&\s]+/gi,"$1$2=***")}getLogs(){return[...this.buffer]}clear(){this.buffer=[]}getCount(){return this.buffer.length}}class l{constructor(t){this.consoleLogger=null;let e=t.apiKey||t.publishableKey;if(!e&&t.projectId&&(console.warn("Triagly: projectId is deprecated. Please use apiKey instead. See migration guide: https://docs.triagly.com/sdk/migration"),e=t.projectId),!e)throw new Error("Triagly: apiKey is required. Get yours at https://triagly.com/dashboard");this.config={theme:"auto",position:"bottom-right",buttonShape:"rounded",buttonText:"Feedback",placeholderText:"Describe what happened...",successMessage:"Feedback sent successfully!",errorMessage:"Failed to send feedback. Please try again.",captureConsole:!0,consoleLogLimit:50,consoleLogLevels:["error","warn"],...t,apiKey:e,publishableKey:e},this.api=new r(e,this.config.environment||"production",this.config.apiUrl,this.config.getToken,this.config.turnstileSiteKey),this.widget=new n(this.config),this.rateLimiter=new a(e,3,3e5),!1!==this.config.captureConsole&&(this.consoleLogger=new s(this.config.consoleLogLimit,this.config.consoleLogLevels),this.consoleLogger.start()),this.init()}init(){"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{this.widget.init()}):this.widget.init(),document.addEventListener("triagly:submit",t=>{const e=t;this.handleSubmit(e.detail)})}async handleSubmit(t){try{if(!this.rateLimiter.canProceed()){const t=Math.ceil(this.rateLimiter.getTimeUntilReset()/1e3/60);throw new Error(`Rate limit exceeded. Please try again in ${t} minute(s).`)}const e=o(this.config.metadata),n={title:t.title,description:t.description,reporterEmail:t.reporterEmail,consoleLogs:this.consoleLogger?.getLogs(),screenshot:t.screenshot},i=await this.api.submitFeedback(n,e,t.turnstileToken);this.rateLimiter.recordAttempt(),document.dispatchEvent(new CustomEvent("triagly:success",{detail:{feedbackId:i.id}})),this.config.onSuccess&&this.config.onSuccess(i.id)}catch(t){throw console.error("Failed to submit feedback:",t instanceof Error?t.message:"Unknown error"),document.dispatchEvent(new CustomEvent("triagly:error",{detail:t})),this.config.onError&&t instanceof Error&&this.config.onError(t),t}}open(){this.widget.open()}close(){this.widget.close()}async submit(t,e){if(this.config.turnstileSiteKey&&!e)throw new Error("Turnstile verification required. When Turnstile is enabled, you must:\n1. Use the widget UI (triagly.open()), or\n2. Implement Turnstile in your form and pass the token: triagly.submit(data, token)");const n=o(this.config.metadata);!t.consoleLogs&&this.consoleLogger&&(t.consoleLogs=this.consoleLogger.getLogs()),await this.api.submitFeedback(t,n,e),this.rateLimiter.recordAttempt()}destroy(){this.widget.destroy(),this.consoleLogger?.stop(),document.removeEventListener("triagly:submit",()=>{})}}if("undefined"!=typeof window){const t=window.TriaglyConfig;t&&(window.triagly=new l(t)),window.Triagly=l}t.Triagly=l,t.default=l,Object.defineProperty(t,"__esModule",{value:!0})});
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).Triagly={})}(this,function(t){"use strict";class e{constructor(t,e,n){this.overlay=null,this.canvas=null,this.ctx=null,this.image=null,this.annotations=[],this.currentTool="highlight",this.currentColor="#ff0000",this.strokeWidth=3,this.isDrawing=!1,this.currentAnnotation=null,this.scale=1,this.offsetX=0,this.offsetY=0,this.pendingTextPoint=null,this.handleKeyDown=t=>{"Escape"===t.key&&this.close(!0)},this.imageData=t,this.onComplete=e,this.onCancel=n}async open(){this.image=await this.loadImage(this.imageData),this.createOverlay(),this.setupCanvas(),this.setupCanvasEventListeners(),this.render()}loadImage(t){return new Promise((e,n)=>{const i=new Image;i.onload=()=>e(i),i.onerror=()=>n(new Error("Failed to load image")),i.src=t})}createOverlay(){this.overlay=document.createElement("div"),this.overlay.className="triagly-annotation-overlay",this.overlay.innerHTML='\n <div class="triagly-annotation-header">\n <div class="triagly-annotation-tools">\n <button type="button" class="triagly-tool-btn active" data-tool="highlight" title="Highlight">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <rect x="3" y="3" width="18" height="18" rx="2" opacity="0.3" fill="currentColor"/>\n <rect x="3" y="3" width="18" height="18" rx="2"/>\n </svg>\n </button>\n <button type="button" class="triagly-tool-btn" data-tool="arrow" title="Arrow">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <line x1="5" y1="19" x2="19" y2="5"/>\n <polyline points="10,5 19,5 19,14"/>\n </svg>\n </button>\n <button type="button" class="triagly-tool-btn" data-tool="freehand" title="Draw">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <path d="M12 19l7-7 3 3-7 7-3-3z"/>\n <path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/>\n <path d="M2 2l7.586 7.586"/>\n </svg>\n </button>\n <button type="button" class="triagly-tool-btn" data-tool="text" title="Text">\n <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <polyline points="4 7 4 4 20 4 20 7"/>\n <line x1="9" y1="20" x2="15" y2="20"/>\n <line x1="12" y1="4" x2="12" y2="20"/>\n </svg>\n </button>\n <div class="triagly-tool-divider"></div>\n <input type="color" class="triagly-color-picker" value="#ff0000" title="Color">\n </div>\n <div class="triagly-annotation-actions">\n <button type="button" class="triagly-annotation-btn triagly-btn-clear" title="Clear All">\n <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <polyline points="3 6 5 6 21 6"/>\n <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>\n </svg>\n Clear\n </button>\n <button type="button" class="triagly-annotation-btn triagly-btn-cancel">Cancel</button>\n <button type="button" class="triagly-annotation-btn triagly-btn-done">Done</button>\n </div>\n </div>\n <div class="triagly-annotation-canvas-container">\n <canvas class="triagly-annotation-canvas"></canvas>\n </div>\n <div class="triagly-text-modal" id="triagly-text-modal" style="display: none;">\n <div class="triagly-text-modal-content">\n <h3>Add Text Annotation</h3>\n <input type="text" id="triagly-text-input" placeholder="Enter text..." maxlength="200" autofocus />\n <div class="triagly-text-modal-actions">\n <button type="button" class="triagly-annotation-btn triagly-btn-cancel" id="triagly-text-cancel">Cancel</button>\n <button type="button" class="triagly-annotation-btn triagly-btn-done" id="triagly-text-confirm">Add Text</button>\n </div>\n </div>\n </div>\n ',this.injectStyles(),this.setupEventListeners(),document.body.appendChild(this.overlay)}setupCanvas(){if(!this.overlay||!this.image)return;this.canvas=this.overlay.querySelector(".triagly-annotation-canvas");const t=this.overlay.querySelector(".triagly-annotation-canvas-container");if(!this.canvas||!t)return;if(this.ctx=this.canvas.getContext("2d"),!this.ctx)return;const e=t.getBoundingClientRect(),n=e.width-40,i=e.height-40,r=this.image.width/this.image.height;let o,a;r>n/i?(o=Math.min(this.image.width,n),a=o/r):(a=Math.min(this.image.height,i),o=a*r),this.scale=o/this.image.width,this.canvas.width=o,this.canvas.height=a,this.canvas.style.width=`${o}px`,this.canvas.style.height=`${a}px`,this.offsetX=(e.width-o)/2,this.offsetY=(e.height-a)/2}setupEventListeners(){if(!this.overlay)return;const t=this.overlay.querySelectorAll(".triagly-tool-btn");t.forEach(e=>{e.addEventListener("click",e=>{const n=e.currentTarget.dataset.tool;n&&(this.currentTool=n,t.forEach(t=>t.classList.remove("active")),e.currentTarget.classList.add("active"))})});const e=this.overlay.querySelector(".triagly-color-picker");e&&e.addEventListener("change",t=>{this.currentColor=t.target.value});const n=this.overlay.querySelector(".triagly-btn-clear");n&&n.addEventListener("click",()=>{this.annotations=[],this.render()});const i=this.overlay.querySelector(".triagly-annotation-actions .triagly-btn-cancel");i&&i.addEventListener("click",()=>this.close(!0));const r=this.overlay.querySelector(".triagly-btn-done");r&&r.addEventListener("click",()=>this.close(!1));const o=this.overlay.querySelector("#triagly-text-confirm"),a=this.overlay.querySelector("#triagly-text-cancel"),s=this.overlay.querySelector("#triagly-text-input");o?.addEventListener("click",()=>this.confirmTextAnnotation()),a?.addEventListener("click",()=>this.hideTextModal()),s?.addEventListener("keydown",t=>{"Enter"===t.key?(t.preventDefault(),this.confirmTextAnnotation()):"Escape"===t.key&&(t.preventDefault(),this.hideTextModal())}),document.addEventListener("keydown",this.handleKeyDown)}setupCanvasEventListeners(){this.canvas&&(this.canvas.addEventListener("mousedown",t=>this.handleMouseDown(t)),this.canvas.addEventListener("mousemove",t=>this.handleMouseMove(t)),this.canvas.addEventListener("mouseup",()=>this.handleMouseUp()),this.canvas.addEventListener("mouseleave",()=>this.handleMouseUp()),this.canvas.addEventListener("touchstart",t=>{t.preventDefault();const e=t.touches[0];this.handleMouseDown(e)},{passive:!1}),this.canvas.addEventListener("touchmove",t=>{t.preventDefault();const e=t.touches[0];this.handleMouseMove(e)},{passive:!1}),this.canvas.addEventListener("touchend",()=>this.handleMouseUp()),this.canvas.addEventListener("touchcancel",()=>this.handleMouseUp()))}removeCanvasEventListeners(){this.canvas&&(this.canvas=null,this.ctx=null)}getCanvasPoint(t){if(!this.canvas)return{x:0,y:0};const e=this.canvas.getBoundingClientRect();return{x:t.clientX-e.left,y:t.clientY-e.top}}handleMouseDown(t){const e=this.getCanvasPoint(t);if(this.isDrawing=!0,"text"===this.currentTool)return this.pendingTextPoint=e,this.showTextModal(),void(this.isDrawing=!1);this.currentAnnotation={type:this.currentTool,color:this.currentColor,strokeWidth:this.strokeWidth,points:[e]}}showTextModal(){if(!this.overlay)return;const t=this.overlay.querySelector("#triagly-text-modal"),e=this.overlay.querySelector("#triagly-text-input");t&&e&&(t.style.display="flex",e.value="",e.focus())}hideTextModal(){if(!this.overlay)return;const t=this.overlay.querySelector("#triagly-text-modal");t&&(t.style.display="none"),this.pendingTextPoint=null}confirmTextAnnotation(){if(!this.overlay||!this.pendingTextPoint)return;const t=this.overlay.querySelector("#triagly-text-input"),e=t?.value.trim();e&&(this.annotations.push({type:"text",color:this.currentColor,strokeWidth:this.strokeWidth,points:[this.pendingTextPoint],text:e}),this.render()),this.hideTextModal()}handleMouseMove(t){if(!this.isDrawing||!this.currentAnnotation)return;const e=this.getCanvasPoint(t);"freehand"===this.currentTool||1===this.currentAnnotation.points.length?this.currentAnnotation.points.push(e):this.currentAnnotation.points[1]=e,this.render()}handleMouseUp(){this.isDrawing&&this.currentAnnotation&&this.currentAnnotation.points.length>1&&this.annotations.push(this.currentAnnotation),this.isDrawing=!1,this.currentAnnotation=null,this.render()}render(){if(this.ctx&&this.canvas&&this.image){this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height),this.ctx.drawImage(this.image,0,0,this.canvas.width,this.canvas.height);for(const t of this.annotations)this.drawAnnotation(t);this.currentAnnotation&&this.drawAnnotation(this.currentAnnotation)}}drawAnnotation(t){if(this.ctx)switch(this.ctx.strokeStyle=t.color,this.ctx.fillStyle=t.color,this.ctx.lineWidth=t.strokeWidth,this.ctx.lineCap="round",this.ctx.lineJoin="round",t.type){case"highlight":this.drawHighlight(t);break;case"arrow":this.drawArrow(t);break;case"freehand":this.drawFreehand(t);break;case"text":this.drawText(t)}}drawHighlight(t){if(!this.ctx||t.points.length<2)return;const[e,n]=t.points,i=n.x-e.x,r=n.y-e.y;this.ctx.globalAlpha=.3,this.ctx.fillRect(e.x,e.y,i,r),this.ctx.globalAlpha=1,this.ctx.strokeRect(e.x,e.y,i,r)}drawArrow(t){if(!this.ctx||t.points.length<2)return;const[e,n]=t.points,i=Math.atan2(n.y-e.y,n.x-e.x);this.ctx.beginPath(),this.ctx.moveTo(e.x,e.y),this.ctx.lineTo(n.x,n.y),this.ctx.stroke(),this.ctx.beginPath(),this.ctx.moveTo(n.x,n.y),this.ctx.lineTo(n.x-15*Math.cos(i-Math.PI/6),n.y-15*Math.sin(i-Math.PI/6)),this.ctx.moveTo(n.x,n.y),this.ctx.lineTo(n.x-15*Math.cos(i+Math.PI/6),n.y-15*Math.sin(i+Math.PI/6)),this.ctx.stroke()}drawFreehand(t){if(this.ctx&&!(t.points.length<2)){this.ctx.beginPath(),this.ctx.moveTo(t.points[0].x,t.points[0].y);for(let e=1;e<t.points.length;e++)this.ctx.lineTo(t.points[e].x,t.points[e].y);this.ctx.stroke()}}drawText(t){if(!this.ctx||!t.text||t.points.length<1)return;const e=t.points[0];this.ctx.font="bold 16px sans-serif",this.ctx.textBaseline="top";const n=this.ctx.measureText(t.text);this.ctx.fillStyle="rgba(0, 0, 0, 0.7)",this.ctx.fillRect(e.x-4,e.y-4,n.width+8,28),this.ctx.fillStyle="#ffffff",this.ctx.fillText(t.text,e.x,e.y)}close(t){if(document.removeEventListener("keydown",this.handleKeyDown),this.removeCanvasEventListeners(),t)this.onCancel();else{const t=this.renderFinalImage();this.onComplete(t)}this.overlay&&(this.overlay.remove(),this.overlay=null)}renderFinalImage(){if(!this.image)return this.imageData;const t=document.createElement("canvas");t.width=this.image.width,t.height=this.image.height;const e=t.getContext("2d");if(!e)return this.imageData;e.drawImage(this.image,0,0);const n=1/this.scale;for(const t of this.annotations){e.strokeStyle=t.color,e.fillStyle=t.color,e.lineWidth=t.strokeWidth*n,e.lineCap="round",e.lineJoin="round";const i=t.points.map(t=>({x:t.x*n,y:t.y*n})),r={...t,points:i};switch(t.type){case"highlight":this.drawHighlightOnCtx(e,r);break;case"arrow":this.drawArrowOnCtx(e,r,n);break;case"freehand":this.drawFreehandOnCtx(e,r);break;case"text":this.drawTextOnCtx(e,r,n)}}return t.toDataURL("image/jpeg",.9)}drawHighlightOnCtx(t,e){if(e.points.length<2)return;const[n,i]=e.points,r=i.x-n.x,o=i.y-n.y;t.globalAlpha=.3,t.fillRect(n.x,n.y,r,o),t.globalAlpha=1,t.strokeRect(n.x,n.y,r,o)}drawArrowOnCtx(t,e,n){if(e.points.length<2)return;const[i,r]=e.points,o=15*n,a=Math.atan2(r.y-i.y,r.x-i.x);t.beginPath(),t.moveTo(i.x,i.y),t.lineTo(r.x,r.y),t.stroke(),t.beginPath(),t.moveTo(r.x,r.y),t.lineTo(r.x-o*Math.cos(a-Math.PI/6),r.y-o*Math.sin(a-Math.PI/6)),t.moveTo(r.x,r.y),t.lineTo(r.x-o*Math.cos(a+Math.PI/6),r.y-o*Math.sin(a+Math.PI/6)),t.stroke()}drawFreehandOnCtx(t,e){if(!(e.points.length<2)){t.beginPath(),t.moveTo(e.points[0].x,e.points[0].y);for(let n=1;n<e.points.length;n++)t.lineTo(e.points[n].x,e.points[n].y);t.stroke()}}drawTextOnCtx(t,e,n){if(!e.text||e.points.length<1)return;const i=e.points[0],r=16*n;t.font=`bold ${r}px sans-serif`,t.textBaseline="top";const o=t.measureText(e.text),a=4*n;t.fillStyle="rgba(0, 0, 0, 0.7)",t.fillRect(i.x-a,i.y-a,o.width+2*a,1.2*r+2*a),t.fillStyle="#ffffff",t.fillText(e.text,i.x,i.y)}injectStyles(){const t="triagly-annotation-styles";if(document.getElementById(t))return;const e=document.createElement("style");e.id=t,e.textContent="\n .triagly-annotation-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 2000000;\n background: rgba(0, 0, 0, 0.9);\n display: flex;\n flex-direction: column;\n }\n\n .triagly-annotation-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 20px;\n background: #1a1a1a;\n border-bottom: 1px solid #333;\n }\n\n .triagly-annotation-tools {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .triagly-tool-btn {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border: none;\n border-radius: 8px;\n background: #333;\n color: #fff;\n cursor: pointer;\n transition: all 0.2s;\n }\n\n .triagly-tool-btn:hover {\n background: #444;\n }\n\n .triagly-tool-btn.active {\n background: #0066cc;\n }\n\n .triagly-tool-divider {\n width: 1px;\n height: 24px;\n background: #444;\n margin: 0 8px;\n }\n\n .triagly-color-picker {\n width: 40px;\n height: 40px;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n padding: 0;\n background: none;\n }\n\n .triagly-color-picker::-webkit-color-swatch-wrapper {\n padding: 4px;\n }\n\n .triagly-color-picker::-webkit-color-swatch {\n border: none;\n border-radius: 4px;\n }\n\n .triagly-annotation-actions {\n display: flex;\n gap: 8px;\n }\n\n .triagly-annotation-btn {\n padding: 8px 16px;\n border: none;\n border-radius: 8px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n display: flex;\n align-items: center;\n gap: 6px;\n }\n\n .triagly-btn-clear {\n background: transparent;\n color: #999;\n border: 1px solid #444;\n }\n\n .triagly-btn-clear:hover {\n background: #333;\n color: #fff;\n }\n\n .triagly-btn-cancel {\n background: #333;\n color: #fff;\n }\n\n .triagly-btn-cancel:hover {\n background: #444;\n }\n\n .triagly-btn-done {\n background: #0066cc;\n color: #fff;\n }\n\n .triagly-btn-done:hover {\n background: #0055aa;\n }\n\n .triagly-annotation-canvas-container {\n flex: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n padding: 20px;\n }\n\n .triagly-annotation-canvas {\n cursor: crosshair;\n box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);\n border-radius: 4px;\n }\n\n .triagly-text-modal {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.7);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000000;\n }\n\n .triagly-text-modal-content {\n background: #1a1a1a;\n border-radius: 12px;\n padding: 24px;\n min-width: 320px;\n max-width: 400px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);\n }\n\n .triagly-text-modal-content h3 {\n color: #fff;\n font-size: 16px;\n font-weight: 600;\n margin: 0 0 16px 0;\n }\n\n .triagly-text-modal-content input {\n width: 100%;\n padding: 12px;\n border: 1px solid #444;\n border-radius: 8px;\n background: #2a2a2a;\n color: #fff;\n font-size: 14px;\n outline: none;\n box-sizing: border-box;\n }\n\n .triagly-text-modal-content input:focus {\n border-color: #0066cc;\n }\n\n .triagly-text-modal-content input::placeholder {\n color: #666;\n }\n\n .triagly-text-modal-actions {\n display: flex;\n gap: 8px;\n justify-content: flex-end;\n margin-top: 16px;\n }\n ",document.head.appendChild(e)}}class n{constructor(t){this.container=null,this.isOpen=!1,this.previouslyFocusedElement=null,this.focusableElements=[],this.screenshotDataUrl=null,this.themeMediaQuery=null,this.updateButtonTheme=()=>{const t=document.getElementById("triagly-button");if(!t)return;const e=this.getResolvedTheme();t.classList.remove("triagly-theme-light","triagly-theme-dark"),t.classList.add(`triagly-theme-${e}`)},this.config=t}getResolvedTheme(){const t=this.config.theme||"auto";return"auto"===t?"undefined"!=typeof window&&window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":t}setupThemeListener(){"auto"===(this.config.theme||"auto")&&"undefined"!=typeof window&&window.matchMedia&&(this.themeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)"),this.themeMediaQuery.addEventListener?this.themeMediaQuery.addEventListener("change",this.updateButtonTheme):this.themeMediaQuery.addListener&&this.themeMediaQuery.addListener(this.updateButtonTheme))}init(){this.createButton(),this.injectStyles(),this.setupThemeListener(),this.config.turnstileSiteKey&&this.loadTurnstileScript()}loadTurnstileScript(){if(window.turnstile||document.querySelector('script[src*="turnstile"]'))return;const t=document.createElement("script");t.src="https://challenges.cloudflare.com/turnstile/v0/api.js",t.async=!0,t.defer=!0,document.head.appendChild(t)}createButton(){const t=document.createElement("button");t.id="triagly-button",t.className="triagly-button";const e=this.getResolvedTheme();t.classList.add(`triagly-theme-${e}`);const n=this.config.buttonShape||"rounded";t.classList.add(`triagly-shape-${n}`);const i=this.config.orientation||"horizontal";t.classList.add(`triagly-orientation-${i}`);const r='<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 24 24" fill="currentColor" class="triagly-icon"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>',o=this.config.buttonText||"Feedback";"circular"===n?(t.innerHTML=r,t.setAttribute("aria-label",o)):"expandable"===n?(t.innerHTML=`<span class="triagly-btn-icon">${r}</span><span class="triagly-btn-text"> ${this.config.buttonText||"Feedback"}</span>`,t.setAttribute("aria-label",o)):t.innerHTML=`<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="currentColor" class="triagly-icon"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg><span class="triagly-btn-label">${o}</span>`,t.onclick=()=>this.toggle();const a=this.config.position||"bottom-right";t.classList.add(`triagly-${a}`),"expandable"===n&&(a.includes("right")?t.classList.add("triagly-expand-left"):a.includes("left")&&t.classList.add("triagly-expand-right")),this.config.offsetX&&(a.includes("right")?t.style.right=this.config.offsetX:a.includes("left")&&(t.style.left=this.config.offsetX)),this.config.offsetY&&(a.includes("top")?t.style.top=this.config.offsetY:a.includes("bottom")&&(t.style.bottom=this.config.offsetY)),document.body.appendChild(t)}toggle(){this.isOpen?this.close():this.open()}open(){this.isOpen||(this.previouslyFocusedElement=document.activeElement,this.container=this.createContainer(),document.body.appendChild(this.container),this.isOpen=!0,this.config.onOpen&&this.config.onOpen(),setTimeout(()=>{this.setupKeyboardEvents(),this.setupFocusTrap();const t=this.container?.querySelector("#triagly-description");t?.focus()},0))}close(t){if(!this.isOpen||!this.container)return;const e=this.container._tabHandler;e&&document.removeEventListener("keydown",e,!0),this.container.remove(),this.container=null,this.isOpen=!1,this.screenshotDataUrl=null,this.previouslyFocusedElement&&(this.previouslyFocusedElement.focus(),this.previouslyFocusedElement=null),"cancel"===t&&this.config.onCancel?this.config.onCancel():"dismiss"===t&&this.config.onDismiss?this.config.onDismiss():"overlay"===t&&this.config.onOverlayClick&&this.config.onOverlayClick(),this.config.onClose&&this.config.onClose()}createContainer(){const t=document.createElement("div");t.className="triagly-overlay",t.setAttribute("role","dialog"),t.setAttribute("aria-modal","true"),t.setAttribute("aria-label","Send feedback"),t.onclick=e=>{e.target===t&&this.close("overlay")};const e=document.createElement("div");e.className="triagly-modal",e.setAttribute("role","document");const n=document.createElement("div");n.className="triagly-header",n.innerHTML='\n <button type="button" class="triagly-close" aria-label="Close feedback form">×</button>\n ';const i=n.querySelector(".triagly-close");i?.addEventListener("click",()=>this.close("dismiss"));const r=document.createElement("form");r.className="triagly-form",r.innerHTML=`\n <div class="triagly-field">\n <label for="triagly-description">What's on your mind?</label>\n <textarea\n id="triagly-description"\n required\n rows="5"\n placeholder="${this.config.placeholderText||"Describe what happened..."}"\n ></textarea>\n </div>\n\n <div class="triagly-field">\n <label for="triagly-name">Name (optional)</label>\n <input\n type="text"\n id="triagly-name"\n placeholder="Your name"\n ${this.config.user?.name?`value="${this.escapeHtml(this.config.user.name)}"`:""}\n />\n </div>\n\n <div class="triagly-field">\n <label for="triagly-email">Email (optional)</label>\n <input\n type="email"\n id="triagly-email"\n placeholder="your@email.com"\n ${this.config.user?.email?`value="${this.escapeHtml(this.config.user.email)}"`:""}\n />\n </div>\n\n ${this.config.user?.email||this.config.user?.name?`\n <div class="triagly-identified-user" id="triagly-identified-user">\n Submitting as ${this.escapeHtml(this.config.user.name||this.config.user.email||"")}\n </div>\n `:""}\n\n ${this.config.enableScreenshot?`\n <div class="triagly-field triagly-screenshot-field">\n <label>Screenshot (optional)</label>\n <input type="file" id="triagly-screenshot-input" accept="image/*" style="display: none;" />\n <div class="triagly-screenshot-controls" id="triagly-screenshot-controls">\n <button type="button" class="triagly-btn-upload" id="triagly-upload-btn">\n <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>\n <polyline points="17 8 12 3 7 8"/>\n <line x1="12" y1="3" x2="12" y2="15"/>\n </svg>\n ${this.config.screenshotButtonText||"Upload Screenshot"}\n </button>\n </div>\n <div class="triagly-screenshot-error" id="triagly-screenshot-error" style="display: none;" role="alert"></div>\n <div class="triagly-screenshot-preview" id="triagly-screenshot-preview" style="display: none;">\n <img id="triagly-screenshot-img" alt="Screenshot preview" />\n <div class="triagly-screenshot-actions">\n ${!1!==this.config.enableAnnotation?'\n <button type="button" class="triagly-btn-icon" id="triagly-annotate-btn" title="Add annotations">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <path d="M12 19l7-7 3 3-7 7-3-3z"/>\n <path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/>\n </svg>\n </button>\n ':""}\n <button type="button" class="triagly-btn-icon" id="triagly-change-screenshot-btn" title="Upload different screenshot">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>\n <polyline points="17 8 12 3 7 8"/>\n <line x1="12" y1="3" x2="12" y2="15"/>\n </svg>\n </button>\n <button type="button" class="triagly-btn-icon triagly-btn-danger" id="triagly-remove-screenshot-btn" title="Remove screenshot">\n <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <line x1="18" y1="6" x2="6" y2="18"/>\n <line x1="6" y1="6" x2="18" y2="18"/>\n </svg>\n </button>\n </div>\n </div>\n </div>\n `:""}\n\n ${this.config.turnstileSiteKey?`\n <div class="triagly-field triagly-turnstile">\n <div class="cf-turnstile" data-sitekey="${this.config.turnstileSiteKey}" data-theme="light"></div>\n </div>\n `:""}\n\n <div class="triagly-actions">\n <button type="button" class="triagly-btn-secondary" id="triagly-cancel" aria-label="Cancel and close feedback form">\n Cancel\n </button>\n <button type="submit" class="triagly-btn-primary" aria-label="Submit feedback">\n Send Feedback\n </button>\n </div>\n\n <div class="triagly-status" id="triagly-status" role="status" aria-live="polite"></div>\n `;const o=r.querySelector("#triagly-cancel");o?.addEventListener("click",()=>this.close("cancel")),r.onsubmit=t=>{t.preventDefault(),this.handleSubmit(r)},this.config.enableScreenshot&&this.setupScreenshotHandlers(r);const a=document.createElement("div");return a.className="triagly-footer",a.innerHTML='\n <a href="https://triagly.com" target="_blank" rel="noopener noreferrer" class="triagly-branding">\n Powered by <strong>Triagly</strong>\n </a>\n ',e.appendChild(n),e.appendChild(r),e.appendChild(a),t.appendChild(e),this.config.turnstileSiteKey&&setTimeout(()=>{this.renderTurnstileWidget(r)},100),t}renderTurnstileWidget(t){const e=t.querySelector(".cf-turnstile");if(e)if(window.turnstile)try{const t=window.turnstile.render(e,{sitekey:this.config.turnstileSiteKey,theme:"dark"===this.config.theme?"dark":"light",callback:n=>{e.setAttribute("data-turnstile-response",n),e.setAttribute("data-widget-id",t)},"error-callback":()=>{console.error("Triagly: Turnstile widget error")},"expired-callback":()=>{e.removeAttribute("data-turnstile-response")}});e.setAttribute("data-widget-id",t)}catch(t){console.error("Triagly: Failed to render Turnstile widget:",t instanceof Error?t.message:"Unknown error")}else console.warn('Triagly: Turnstile script not loaded. Please include: <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer><\/script>')}setupScreenshotHandlers(t){const n=t.querySelector("#triagly-screenshot-input"),i=t.querySelector("#triagly-upload-btn"),r=t.querySelector("#triagly-annotate-btn"),o=t.querySelector("#triagly-change-screenshot-btn"),a=t.querySelector("#triagly-remove-screenshot-btn"),s=t.querySelector("#triagly-screenshot-preview"),l=t.querySelector("#triagly-screenshot-controls"),c=t.querySelector("#triagly-screenshot-img"),d=t.querySelector("#triagly-screenshot-error"),h=t=>{d&&(d.textContent=t,d.style.display="block")},g=()=>{d&&(d.textContent="",d.style.display="none")},u=t=>{if(g(),!t.type.startsWith("image/")){const t="Invalid file type. Please select an image.";return h(t),void(this.config.onScreenshotError&&this.config.onScreenshotError(new Error(t)))}const e=this.config.screenshotMaxFileSize??10485760;if(t.size>e){const t=`File too large. Maximum size is ${Math.round(e/1048576)}MB.`;return h(t),void(this.config.onScreenshotError&&this.config.onScreenshotError(new Error(t)))}const n=new FileReader;n.onload=t=>{const e=t.target?.result;this.screenshotDataUrl=e,c&&(c.src=e),s&&(s.style.display="block"),l&&(l.style.display="none"),this.config.onScreenshotUpload&&this.config.onScreenshotUpload(e)},n.onerror=()=>{const t="Failed to read file. Please try again.";h(t),this.config.onScreenshotError&&this.config.onScreenshotError(new Error(t))},n.readAsDataURL(t)};n?.addEventListener("change",()=>{const t=n.files?.[0];t&&u(t),n.value=""}),i?.addEventListener("click",()=>{g(),n?.click()}),o?.addEventListener("click",()=>{g(),n?.click()}),r?.addEventListener("click",()=>{if(!this.screenshotDataUrl)return;new e(this.screenshotDataUrl,t=>{this.screenshotDataUrl=t,c&&(c.src=t)},()=>{}).open()}),a?.addEventListener("click",()=>{this.screenshotDataUrl=null,s&&(s.style.display="none"),l&&(l.style.display="block"),c&&(c.src="")})}async handleSubmit(t){const e=t.querySelector("#triagly-description"),n=t.querySelector("#triagly-name"),i=t.querySelector("#triagly-email"),r=t.querySelector("#triagly-status"),o=t.querySelector('button[type="submit"]'),a=t.querySelector(".cf-turnstile");o.disabled=!0,o.textContent="Sending...";try{let t;a&&(t=a.getAttribute("data-turnstile-response")||void 0);const o={description:e.value.trim(),reporterName:n.value.trim()||void 0,reporterEmail:i.value.trim()||void 0,turnstileToken:t,screenshot:this.screenshotDataUrl||void 0},s=new Promise((t,e)=>{const n=()=>{document.removeEventListener("triagly:success",n),document.removeEventListener("triagly:error",i),t()},i=t=>{document.removeEventListener("triagly:success",n),document.removeEventListener("triagly:error",i),e(t.detail)};document.addEventListener("triagly:success",n,{once:!0}),document.addEventListener("triagly:error",i,{once:!0}),setTimeout(()=>{document.removeEventListener("triagly:success",n),document.removeEventListener("triagly:error",i),e(new Error("Submission timeout"))},3e4)}),l=new CustomEvent("triagly:submit",{detail:o,bubbles:!0});document.dispatchEvent(l),await s,r.className="triagly-status triagly-success";const c=this.config.user?.name,d=c?c.split(" ")[0]:null;r.textContent=d?`Thanks, ${d}! Your feedback was received.`:this.config.successMessage||"Feedback sent successfully!",setTimeout(()=>{this.close()},2e3)}catch(t){r.className="triagly-status triagly-error";const e=t instanceof Error?t.message:this.config.errorMessage||"Failed to send feedback. Please try again.";r.textContent=e,o.disabled=!1,o.textContent="Send Feedback"}}injectStyles(){if(document.getElementById("triagly-styles"))return;const t=document.createElement("style");t.id="triagly-styles",t.textContent="\n .triagly-button {\n position: fixed;\n z-index: 999999;\n padding: 12px 20px;\n background: var(--triagly-button-bg, #18181b);\n color: var(--triagly-button-text, #ffffff);\n border: none;\n border-radius: var(--triagly-button-radius, 8px);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n box-shadow: var(--triagly-button-shadow, 0 4px 12px rgba(0, 0, 0, 0.15));\n transition: all 0.2s;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n }\n\n .triagly-button:hover {\n background: var(--triagly-button-bg-hover, #27272a);\n transform: translateY(-2px);\n box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(0, 0, 0, 0.2));\n }\n\n /* Dark theme: white button with black text */\n .triagly-button.triagly-theme-dark {\n --triagly-button-bg: #ffffff;\n --triagly-button-text: #18181b;\n --triagly-button-bg-hover: #f4f4f5;\n --triagly-button-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);\n --triagly-button-shadow-hover: 0 6px 16px rgba(0, 0, 0, 0.35);\n }\n\n /* Light theme: dark button with white text (default) */\n .triagly-button.triagly-theme-light {\n --triagly-button-bg: #18181b;\n --triagly-button-text: #ffffff;\n --triagly-button-bg-hover: #27272a;\n --triagly-button-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n --triagly-button-shadow-hover: 0 6px 16px rgba(0, 0, 0, 0.2);\n }\n\n .triagly-button .triagly-icon {\n flex-shrink: 0;\n }\n\n .triagly-button .triagly-btn-label {\n line-height: 1;\n }\n\n /* Prevent expandable buttons from shifting on hover */\n .triagly-button.triagly-shape-expandable:hover {\n transform: translateY(0) !important;\n }\n\n .triagly-bottom-right { bottom: 20px; right: 20px; }\n .triagly-bottom-left { bottom: 20px; left: 20px; }\n .triagly-top-right { top: 20px; right: 20px; }\n .triagly-top-left { top: 20px; left: 20px; }\n\n /* Edge-aligned positions (0 offset from edges) */\n .triagly-edge-bottom-right { bottom: 0; right: 0; }\n .triagly-edge-bottom-left { bottom: 0; left: 0; }\n .triagly-edge-top-right { top: 0; right: 0; }\n .triagly-edge-top-left { top: 0; left: 0; }\n .triagly-edge-right { top: 50%; right: 0; transform: translateY(-50%); }\n .triagly-edge-left { top: 50%; left: 0; transform: translateY(-50%); }\n .triagly-edge-top { top: 0; left: 50%; transform: translateX(-50%); }\n .triagly-edge-bottom { bottom: 0; left: 50%; transform: translateX(-50%); }\n\n /* Button shapes */\n .triagly-shape-rounded {\n border-radius: var(--triagly-button-radius, 8px);\n }\n .triagly-shape-circular {\n border-radius: 50%;\n width: 60px;\n height: 60px;\n padding: 0;\n font-size: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .triagly-shape-square {\n border-radius: 0;\n }\n .triagly-shape-pill {\n border-radius: 30px;\n }\n .triagly-shape-expandable {\n border-radius: 50%;\n width: 60px;\n height: 60px;\n min-width: 60px;\n padding: 0;\n font-size: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow: hidden;\n transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n border-radius 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n padding 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n background 0.2s,\n box-shadow 0.2s;\n white-space: nowrap;\n }\n \n /* Expansion direction - expands left for right-positioned buttons */\n .triagly-shape-expandable.triagly-expand-left {\n flex-direction: row-reverse;\n }\n \n /* Expansion direction - expands right for left-positioned buttons */\n .triagly-shape-expandable.triagly-expand-right {\n flex-direction: row;\n }\n \n .triagly-shape-expandable .triagly-btn-icon {\n display: inline-block;\n flex-shrink: 0;\n transition: margin 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n }\n .triagly-shape-expandable .triagly-btn-text {\n display: inline-block;\n width: 0;\n opacity: 0;\n overflow: hidden;\n font-size: 14px;\n font-weight: 500;\n transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1),\n opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n }\n \n /* Hover state */\n .triagly-shape-expandable:hover {\n width: auto;\n min-width: auto;\n padding: 12px 20px;\n border-radius: 30px;\n background: var(--triagly-button-bg-hover);\n box-shadow: var(--triagly-button-shadow-hover);\n }\n .triagly-shape-expandable:hover .triagly-btn-text {\n width: auto;\n opacity: 1;\n }\n\n /* Button orientations */\n .triagly-orientation-horizontal {\n writing-mode: horizontal-tb;\n }\n .triagly-orientation-vertical {\n writing-mode: vertical-rl;\n text-orientation: mixed;\n }\n\n .triagly-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: var(--triagly-overlay-bg, rgba(0, 0, 0, 0.5));\n z-index: 1000000;\n display: flex;\n align-items: center;\n justify-content: center;\n animation: triagly-fadeIn 0.2s;\n }\n\n @keyframes triagly-fadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n }\n\n .triagly-modal {\n background: var(--triagly-modal-bg, #ffffff);\n border-radius: var(--triagly-modal-radius, 12px);\n width: 90%;\n max-width: var(--triagly-modal-max-width, 500px);\n max-height: 90vh;\n overflow-y: auto;\n box-shadow: var(--triagly-modal-shadow, 0 20px 60px rgba(0, 0, 0, 0.3));\n animation: triagly-slideUp 0.3s;\n }\n\n @keyframes triagly-slideUp {\n from {\n opacity: 0;\n transform: translateY(20px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n .triagly-header {\n display: flex;\n justify-content: flex-end;\n align-items: center;\n padding: 8px 12px 0;\n background: var(--triagly-header-bg, #ffffff);\n }\n\n .triagly-close {\n background: none;\n border: none;\n font-size: 28px;\n color: #6b7280;\n cursor: pointer;\n padding: 0;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 6px;\n transition: all 0.2s;\n }\n\n .triagly-close:hover {\n background: #f3f4f6;\n color: #111827;\n }\n\n .triagly-form {\n padding: 24px;\n background: var(--triagly-form-bg, #ffffff);\n }\n\n .triagly-field {\n margin-bottom: 16px;\n }\n\n .triagly-field label {\n display: block;\n margin-bottom: 6px;\n font-size: 14px;\n font-weight: 500;\n color: var(--triagly-label-text, #374151);\n }\n\n .triagly-field input,\n .triagly-field textarea {\n width: 100%;\n padding: 10px 12px;\n background: var(--triagly-input-bg, #ffffff);\n border: 1px solid var(--triagly-input-border, #d1d5db);\n border-radius: var(--triagly-input-radius, 6px);\n color: var(--triagly-input-text, #111827);\n font-size: 14px;\n font-family: inherit;\n transition: border-color 0.2s;\n box-sizing: border-box;\n }\n\n .triagly-field input:focus,\n .triagly-field textarea:focus {\n outline: none;\n background: var(--triagly-input-bg, #ffffff);\n border-color: var(--triagly-input-border-focus, #a1a1aa);\n box-shadow: 0 0 0 2px rgba(161, 161, 170, 0.15);\n }\n\n /* Focus visible styles for accessibility */\n .triagly-button:focus-visible,\n .triagly-field input:focus-visible,\n .triagly-field textarea:focus-visible,\n .triagly-btn-primary:focus-visible,\n .triagly-btn-secondary:focus-visible,\n .triagly-close:focus-visible {\n outline: 2px solid #a1a1aa;\n outline-offset: 2px;\n }\n\n .triagly-turnstile {\n display: flex;\n justify-content: center;\n margin: 8px 0;\n }\n\n .triagly-actions {\n display: flex;\n gap: 12px;\n margin-top: 24px;\n }\n\n .triagly-btn-primary,\n .triagly-btn-secondary {\n flex: 1;\n padding: 10px 16px;\n border-radius: var(--triagly-btn-radius, 6px);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n border: none;\n }\n\n .triagly-btn-primary {\n background: var(--triagly-btn-primary-bg, #18181b);\n color: var(--triagly-btn-primary-text, #ffffff);\n }\n\n .triagly-btn-primary:hover:not(:disabled) {\n background: var(--triagly-btn-primary-bg-hover, #27272a);\n }\n\n .triagly-btn-primary:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n .triagly-btn-secondary {\n background: var(--triagly-btn-secondary-bg, #f3f4f6);\n color: var(--triagly-btn-secondary-text, #374151);\n }\n\n .triagly-btn-secondary:hover {\n background: var(--triagly-btn-secondary-bg-hover, #e5e7eb);\n }\n\n .triagly-status {\n margin-top: 16px;\n padding: 12px;\n border-radius: 6px;\n font-size: 14px;\n display: none;\n }\n\n .triagly-status.triagly-success {\n display: block;\n background: var(--triagly-success-bg, #d1fae5);\n color: var(--triagly-success-text, #065f46);\n }\n\n .triagly-status.triagly-error {\n display: block;\n background: var(--triagly-error-bg, #fee2e2);\n color: var(--triagly-error-text, #991b1b);\n }\n\n .triagly-footer {\n padding: 12px 24px 16px;\n text-align: right;\n border-top: 1px solid var(--triagly-footer-border, #e5e7eb);\n background: var(--triagly-footer-bg, #f9fafb);\n border-radius: 0 0 var(--triagly-modal-radius, 12px) var(--triagly-modal-radius, 12px);\n }\n\n .triagly-branding {\n font-size: 12px;\n color: var(--triagly-footer-text, #6b7280);\n text-decoration: none;\n transition: color 0.2s;\n }\n\n .triagly-branding:hover {\n color: var(--triagly-footer-text-hover, #18181b);\n }\n\n .triagly-branding strong {\n font-weight: 600;\n }\n\n /* Screenshot styles */\n .triagly-screenshot-field {\n margin-bottom: 16px;\n }\n\n .triagly-screenshot-controls {\n display: flex;\n gap: 8px;\n }\n\n .triagly-btn-upload {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 10px 16px;\n background: var(--triagly-btn-secondary-bg, #f3f4f6);\n color: var(--triagly-btn-secondary-text, #374151);\n border: 1px dashed var(--triagly-input-border, #d1d5db);\n border-radius: var(--triagly-input-radius, 6px);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: all 0.2s;\n width: 100%;\n justify-content: center;\n }\n\n .triagly-btn-upload:hover:not(:disabled) {\n background: var(--triagly-btn-secondary-bg-hover, #e5e7eb);\n border-style: solid;\n }\n\n .triagly-btn-upload:disabled {\n opacity: 0.7;\n cursor: not-allowed;\n }\n\n .triagly-screenshot-error {\n margin-top: 8px;\n padding: 8px 12px;\n background: var(--triagly-error-bg, #fee2e2);\n color: var(--triagly-error-text, #991b1b);\n border-radius: var(--triagly-input-radius, 6px);\n font-size: 13px;\n }\n\n .triagly-screenshot-preview {\n position: relative;\n border-radius: var(--triagly-input-radius, 6px);\n overflow: hidden;\n border: 1px solid var(--triagly-input-border, #d1d5db);\n background: var(--triagly-input-bg, #ffffff);\n }\n\n .triagly-screenshot-preview img {\n display: block;\n width: 100%;\n max-height: 200px;\n object-fit: contain;\n background: #f9fafb;\n }\n\n .triagly-screenshot-actions {\n position: absolute;\n top: 8px;\n right: 8px;\n display: flex;\n gap: 4px;\n }\n\n .triagly-btn-icon {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n background: rgba(255, 255, 255, 0.9);\n border: 1px solid var(--triagly-input-border, #d1d5db);\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n color: #374151;\n }\n\n .triagly-btn-icon:hover {\n background: #ffffff;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);\n }\n\n .triagly-btn-icon.triagly-btn-danger:hover {\n background: #fee2e2;\n border-color: #fca5a5;\n color: #dc2626;\n }\n\n @keyframes triagly-spin {\n from { transform: rotate(0deg); }\n to { transform: rotate(360deg); }\n }\n\n .triagly-spin {\n animation: triagly-spin 1s linear infinite;\n }\n\n .triagly-identified-user {\n font-size: 12px;\n color: var(--triagly-label-text, #6b7280);\n margin-bottom: 16px;\n padding: 6px 10px;\n background: var(--triagly-btn-secondary-bg, #f3f4f6);\n border-radius: 4px;\n }\n ",document.head.appendChild(t)}setupKeyboardEvents(){const t=t=>{"Escape"===t.key&&this.isOpen&&(t.preventDefault(),this.close("dismiss"))};document.addEventListener("keydown",t),this.container&&(this.container._keydownHandler=t)}setupFocusTrap(){if(!this.container)return;const t=this.container.querySelector(".triagly-modal");if(!t)return;if(this.focusableElements=Array.from(t.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])')),0===this.focusableElements.length)return void console.warn("Triagly: No focusable elements found in modal");const e=t=>{const e=t;if("Tab"!==e.key)return;if(!this.container?.contains(document.activeElement))return;const n=this.focusableElements[0],i=this.focusableElements[this.focusableElements.length-1];e.shiftKey?document.activeElement===n&&(e.preventDefault(),i?.focus()):document.activeElement===i&&(e.preventDefault(),n?.focus())};document.addEventListener("keydown",e,!0),this.container._tabHandler=e}updateUser(t){if(this.config.user={...this.config.user,...t},!this.isOpen||!this.container)return;const e=this.container.querySelector("#triagly-name"),n=this.container.querySelector("#triagly-email"),i=this.container.querySelector("#triagly-identified-user");e&&void 0!==t.name&&(e.value=t.name),n&&void 0!==t.email&&(n.value=t.email);const r=this.config.user?.name||this.config.user?.email;i&&(r?(i.textContent=`Submitting as ${r}`,i.style.display=""):i.style.display="none")}escapeHtml(t){return t.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}destroy(){if(this.container){const t=this.container._keydownHandler;t&&document.removeEventListener("keydown",t);const e=this.container._tabHandler;e&&document.removeEventListener("keydown",e,!0)}this.themeMediaQuery&&(this.themeMediaQuery.removeEventListener?this.themeMediaQuery.removeEventListener("change",this.updateButtonTheme):this.themeMediaQuery.removeListener&&this.themeMediaQuery.removeListener(this.updateButtonTheme),this.themeMediaQuery=null),this.close(),document.getElementById("triagly-button")?.remove(),document.getElementById("triagly-styles")?.remove()}}const i={production:"https://iipkklhhafrjesryscjh.supabase.co/functions/v1",staging:"https://bssghvinezdawvupcyci.supabase.co/functions/v1"};class r{constructor(t,e="production",n,r,o){this.apiUrl=(n||i[e]).replace(/\/$/,""),this.publishableKey=t,this.getToken=r,this.turnstileSiteKey=o}getTurnstileSiteKey(){return this.turnstileSiteKey}async getTurnstileToken(){const t=document.querySelector("[data-turnstile-response]");if(t){const e=t.getAttribute("data-turnstile-response");if(e)return e}if(window.turnstile)try{const t=document.querySelectorAll(".cf-turnstile");if(t.length>0){const e=t[0].getAttribute("data-widget-id");if(e){const t=window.turnstile.getResponse(e);if(t)return t}}}catch(t){console.warn("Failed to get Turnstile token:",t instanceof Error?t.message:"Unknown error")}return null}async submitFeedback(t,e,n){if(n||(n=await this.getTurnstileToken()||void 0),this.turnstileSiteKey&&!n)throw new Error("Turnstile verification required. Please complete the captcha.");let i;if(this.getToken)try{i=await this.getToken()}catch(t){throw console.error("Failed to get hardened token:",t instanceof Error?t.message:"Unknown error"),new Error("Failed to authenticate. Please try again.")}const r={publishableKey:this.publishableKey,title:t.title,description:t.description,metadata:{...e,consoleLogs:t.consoleLogs},tags:t.tags,reporterEmail:t.reporterEmail,reporterName:t.reporterName,reporterUserId:t.reporterUserId,screenshot:t.screenshot,turnstileToken:n,hardenedToken:i},o=await fetch(`${this.apiUrl}/feedback`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(r)});if(!o.ok){const t=await o.json().catch(()=>({error:"Unknown error",message:"Unknown error"}));if(401===o.status){if("invalid_publishable_key"===t.error)throw new Error("Invalid API key. Please contact support.");if("token_required"===t.error)throw new Error("Authentication required. Please refresh and try again.");throw new Error(t.message||"Authentication failed")}if(403===o.status){if("origin_not_allowed"===t.error)throw new Error("This website is not authorized to submit feedback.");throw new Error(t.message||"Access denied")}if(429===o.status){const t=o.headers.get("Retry-After");throw new Error(`Too many requests. Please try again ${t?`in ${t} seconds`:"later"}.`)}if(400===o.status&&"captcha_failed"===t.error)throw new Error("Captcha verification failed. Please try again.");throw new Error(t.message||t.error||`Failed to submit feedback (HTTP ${o.status})`)}return await o.json()}}function o(t){const e=`${window.innerWidth}x${window.innerHeight}`,n=function(){const t=navigator.userAgent;let e="Unknown";if(t.includes("Firefox/")){const n=t.match(/Firefox\/(\d+)/)?.[1];e=`Firefox ${n}`}else if(t.includes("Chrome/")&&!t.includes("Edg")){const n=t.match(/Chrome\/(\d+)/)?.[1];e=`Chrome ${n}`}else if(t.includes("Safari/")&&!t.includes("Chrome")){const n=t.match(/Version\/(\d+)/)?.[1];e=`Safari ${n}`}else if(t.includes("Edg/")){const n=t.match(/Edg\/(\d+)/)?.[1];e=`Edge ${n}`}return e}();return{url:window.location.href,browser:n,viewport:e,userAgent:navigator.userAgent,timestamp:(new Date).toISOString(),...t}}class a{constructor(t,e=3,n=3e5){this.key=`triagly_ratelimit_${t}`,this.maxAttempts=e,this.windowMs=n}canProceed(){const t=Date.now();return!(this.getData().attempts.filter(e=>t-e<this.windowMs).length>=this.maxAttempts)}recordAttempt(){const t=Date.now(),e=this.getData();e.attempts.push(t),e.attempts=e.attempts.filter(e=>t-e<this.windowMs),this.setData(e)}getTimeUntilReset(){const t=Date.now(),e=this.getData();if(0===e.attempts.length)return 0;const n=Math.min(...e.attempts)+this.windowMs;return Math.max(0,n-t)}getData(){try{const t=localStorage.getItem(this.key);return t?JSON.parse(t):{attempts:[]}}catch{return{attempts:[]}}}setData(t){try{localStorage.setItem(this.key,JSON.stringify(t))}catch(t){console.error("Failed to store rate limit data:",t instanceof Error?t.message:"Unknown error")}}}class s{constructor(t=50,e=["error","warn"]){this.buffer=[],this.isActive=!1,this.maxLogs=t,this.levels=new Set(e),this.originalConsole={error:console.error,warn:console.warn,log:console.log}}start(){this.isActive||(this.isActive=!0,this.levels.has("error")&&(console.error=(...t)=>{this.captureLog("error",t),this.originalConsole.error.apply(console,t)}),this.levels.has("warn")&&(console.warn=(...t)=>{this.captureLog("warn",t),this.originalConsole.warn.apply(console,t)}),this.levels.has("log")&&(console.log=(...t)=>{this.captureLog("log",t),this.originalConsole.log.apply(console,t)}))}stop(){this.isActive&&(this.isActive=!1,console.error=this.originalConsole.error,console.warn=this.originalConsole.warn,console.log=this.originalConsole.log)}captureLog(t,e){try{const n=e.map(t=>{if("string"==typeof t)return t;if(t instanceof Error)return t.message;try{return JSON.stringify(t)}catch{return String(t)}}).join(" ");let i;if("error"===t){const t=e.find(t=>t instanceof Error);i=t?t.stack:(new Error).stack?.split("\n").slice(2).join("\n")}const r=this.sanitize(n),o=i?this.sanitize(i):void 0,a={level:t,message:r,timestamp:(new Date).toISOString(),stack:o};this.buffer.push(a),this.buffer.length>this.maxLogs&&this.buffer.shift()}catch(t){this.originalConsole.error("Failed to capture log:",t instanceof Error?t.message:"Unknown error")}}sanitize(t){return t.replace(/[a-zA-Z0-9_-]*token[a-zA-Z0-9_-]*\s*[:=]\s*["']?[\w-]{20,}["']?/gi,"token=***").replace(/[a-zA-Z0-9_-]*key[a-zA-Z0-9_-]*\s*[:=]\s*["']?[\w-]{20,}["']?/gi,"key=***").replace(/[a-zA-Z0-9_-]*secret[a-zA-Z0-9_-]*\s*[:=]\s*["']?[\w-]{20,}["']?/gi,"secret=***").replace(/gh[ps]_[a-zA-Z0-9]{36,}/g,"gh*_***").replace(/eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,"jwt.***").replace(/password\s*[:=]\s*["']?[^"'\s]+["']?/gi,"password=***").replace(/\b[\w._%+-]+@[\w.-]+\.[a-zA-Z]{2,}\b/g,"***@***.com").replace(/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g,"****-****-****-****").replace(/([?&])(token|key|secret|auth)=[^&\s]+/gi,"$1$2=***")}getLogs(){return[...this.buffer]}clear(){this.buffer=[]}getCount(){return this.buffer.length}}class l{constructor(t){this.consoleLogger=null;let e=t.apiKey||t.publishableKey;if(!e&&t.projectId&&(console.warn("Triagly: projectId is deprecated. Please use apiKey instead. See migration guide: https://docs.triagly.com/sdk/migration"),e=t.projectId),!e)throw new Error("Triagly: apiKey is required. Get yours at https://triagly.com/dashboard");this.config={theme:"auto",position:"bottom-right",buttonShape:"rounded",buttonText:"Feedback",placeholderText:"Describe what happened...",successMessage:"Feedback sent successfully!",errorMessage:"Failed to send feedback. Please try again.",captureConsole:!0,consoleLogLimit:50,consoleLogLevels:["error","warn"],...t,apiKey:e,publishableKey:e},this.api=new r(e,this.config.environment||"production",this.config.apiUrl,this.config.getToken,this.config.turnstileSiteKey),this.widget=new n(this.config),this.rateLimiter=new a(e,3,3e5),!1!==this.config.captureConsole&&(this.consoleLogger=new s(this.config.consoleLogLimit,this.config.consoleLogLevels),this.consoleLogger.start()),this.init()}init(){"loading"===document.readyState?document.addEventListener("DOMContentLoaded",()=>{this.widget.init()}):this.widget.init(),document.addEventListener("triagly:submit",t=>{const e=t;this.handleSubmit(e.detail)})}async handleSubmit(t){try{if(!this.rateLimiter.canProceed()){const t=Math.ceil(this.rateLimiter.getTimeUntilReset()/1e3/60);throw new Error(`Rate limit exceeded. Please try again in ${t} minute(s).`)}const e=o(this.config.metadata),n={title:t.title,description:t.description,reporterEmail:t.reporterEmail||this.config.user?.email,reporterName:t.reporterName||this.config.user?.name,reporterUserId:this.config.user?.id,consoleLogs:this.consoleLogger?.getLogs(),screenshot:t.screenshot},i=await this.api.submitFeedback(n,e,t.turnstileToken);this.rateLimiter.recordAttempt(),document.dispatchEvent(new CustomEvent("triagly:success",{detail:{feedbackId:i.id}})),this.config.onSuccess&&this.config.onSuccess(i.id)}catch(t){throw console.error("Failed to submit feedback:",t instanceof Error?t.message:"Unknown error"),document.dispatchEvent(new CustomEvent("triagly:error",{detail:t})),this.config.onError&&t instanceof Error&&this.config.onError(t),t}}open(){this.widget.open()}close(){this.widget.close()}identify(t){this.config.user={...this.config.user,...t},this.widget.updateUser(this.config.user)}async submit(t,e){if(this.config.turnstileSiteKey&&!e)throw new Error("Turnstile verification required. When Turnstile is enabled, you must:\n1. Use the widget UI (triagly.open()), or\n2. Implement Turnstile in your form and pass the token: triagly.submit(data, token)");const n=o(this.config.metadata);!t.consoleLogs&&this.consoleLogger&&(t.consoleLogs=this.consoleLogger.getLogs()),await this.api.submitFeedback(t,n,e),this.rateLimiter.recordAttempt()}destroy(){this.widget.destroy(),this.consoleLogger?.stop(),document.removeEventListener("triagly:submit",()=>{})}}if("undefined"!=typeof window){const t=window.TriaglyConfig;t&&(window.triagly=new l(t)),window.Triagly=l}t.Triagly=l,t.default=l,Object.defineProperty(t,"__esModule",{value:!0})});
2
2
  //# sourceMappingURL=index.min.js.map