@triagly/sdk 1.3.0 → 1.4.0-beta.2

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){this.container=null,this.isOpen=!1,this.previouslyFocusedElement=null,this.focusableElements=[],this.config=t}init(){this.createButton(),this.injectStyles(),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.config.buttonShape||"rounded";t.classList.add(`triagly-shape-${e}`);const n=this.config.orientation||"horizontal";t.classList.add(`triagly-orientation-${n}`);const i='<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>',r=this.config.buttonText||"Feedback";"circular"===e?(t.innerHTML=i,t.setAttribute("aria-label",r)):"expandable"===e?(t.innerHTML=`<span class="triagly-btn-icon">${i}</span><span class="triagly-btn-text"> ${this.config.buttonText||"Feedback"}</span>`,t.setAttribute("aria-label",r)):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">${r}</span>`,t.onclick=()=>this.toggle();const o=this.config.position||"bottom-right";t.classList.add(`triagly-${o}`),"expandable"===e&&(o.includes("right")?t.classList.add("triagly-expand-left"):o.includes("left")&&t.classList.add("triagly-expand-right")),this.config.offsetX&&(o.includes("right")?t.style.right=this.config.offsetX:o.includes("left")&&(t.style.left=this.config.offsetX)),this.config.offsetY&&(o.includes("top")?t.style.top=this.config.offsetY:o.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.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.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)};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>')}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},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 .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, #27272a);\n box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(0, 0, 0, 0.2));\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 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 ",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.close(),document.getElementById("triagly-button")?.remove(),document.getElementById("triagly-styles")?.remove()}}const n={production:"https://iipkklhhafrjesryscjh.supabase.co/functions/v1",staging:"https://bssghvinezdawvupcyci.supabase.co/functions/v1"};class i{constructor(t,e="production",r,o,a){this.apiUrl=(r||n[e]).replace(/\/$/,""),this.publishableKey=t,this.getToken=o,this.turnstileSiteKey=a||i.DEFAULT_TURNSTILE_SITE_KEY}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,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 r(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}}i.DEFAULT_TURNSTILE_SITE_KEY="0x4AAAAAAB8Dc-Fl964Vp1Nn";class o{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 a{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 s{constructor(t){this.consoleLogger=null;let n=t.apiKey||t.publishableKey;if(!n&&t.projectId&&(console.warn("Triagly: projectId is deprecated. Please use apiKey instead. See migration guide: https://docs.triagly.com/sdk/migration"),n=t.projectId),!n)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:n,publishableKey:n},this.api=new i(n,this.config.environment||"production",this.config.apiUrl,this.config.getToken,this.config.turnstileSiteKey),this.config.turnstileSiteKey=this.api.getTurnstileSiteKey(),this.widget=new e(this.config),this.rateLimiter=new o(n,3,3e5),!1!==this.config.captureConsole&&(this.consoleLogger=new a(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=r(this.config.metadata),n={title:t.title,description:t.description,reporterEmail:t.reporterEmail,consoleLogs:this.consoleLogger?.getLogs()},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=r(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 s(t)),window.Triagly=s}t.Triagly=s,t.default=s,Object.defineProperty(t,"__esModule",{value:!0})});
1
+ !function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).Triagly={})}(this,function(t){"use strict";const n=["https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js","https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js","https://unpkg.com/html2canvas@1.4.1/dist/html2canvas.min.js"];class e{constructor(t={}){this.html2canvasLoaded=!1,this.loadPromise=null,this.quality=t.quality??.8,this.maxWidth=t.maxWidth??1920,this.options=t.html2canvasOptions??{},this.customCdnUrl=t.html2canvasCdnUrl}async loadHtml2Canvas(){if(this.html2canvasLoaded&&window.html2canvas)return!0;if(this.loadPromise)return this.loadPromise;if(window.html2canvas)return this.html2canvasLoaded=!0,!0;const t=this.customCdnUrl?[this.customCdnUrl,...n]:n;return this.loadPromise=this.tryLoadFromCdns(t),this.loadPromise}async tryLoadFromCdns(t){for(const n of t){if(await this.loadScriptFromUrl(n)&&window.html2canvas)return this.html2canvasLoaded=!0,!0}return console.error("[Triagly] Failed to load html2canvas from all CDN sources"),!1}loadScriptFromUrl(t){return new Promise(n=>{const e=document.createElement("script");e.src=t,e.async=!0;const i=()=>{e.onload=null,e.onerror=null};e.onload=()=>{i(),n(!0)},e.onerror=()=>{i(),e.remove(),n(!1)},document.head.appendChild(e)})}async capture(){if(!await this.loadHtml2Canvas()||!window.html2canvas)throw new Error('Screenshot capture requires html2canvas. Include <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"><\/script> or ensure network access to CDN.');const t=document.querySelectorAll(".triagly-overlay, .triagly-button, #triagly-button"),n=new Map;t.forEach(t=>{const e=t;n.set(t,e.style.visibility),e.style.visibility="hidden"});try{const t=await window.html2canvas(document.body,{useCORS:!0,allowTaint:!1,logging:!1,backgroundColor:null,scale:Math.min(window.devicePixelRatio||1,2),...this.options});return this.resizeCanvas(t).toDataURL("image/jpeg",this.quality)}finally{t.forEach(t=>{t.style.visibility=n.get(t)||""})}}resizeCanvas(t){if(t.width<=this.maxWidth)return t;const n=this.maxWidth/t.width,e=document.createElement("canvas");e.width=this.maxWidth,e.height=Math.round(t.height*n);const i=e.getContext("2d");return i&&(i.imageSmoothingEnabled=!0,i.imageSmoothingQuality="high",i.drawImage(t,0,0,e.width,e.height)),e}static getDataUrlSize(t){const n=t.split(",")[1]||"";return Math.round(3*n.length/4)}}class i{constructor(t,n,e){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=n,this.onCancel=e}async open(){this.image=await this.loadImage(this.imageData),this.createOverlay(),this.setupCanvas(),this.render()}loadImage(t){return new Promise((n,e)=>{const i=new Image;i.onload=()=>n(i),i.onerror=()=>e(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 n=t.getBoundingClientRect(),e=n.width-40,i=n.height-40,r=this.image.width/this.image.height;let o,a;r>e/i?(o=Math.min(this.image.width,e),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=(n.width-o)/2,this.offsetY=(n.height-a)/2}setupEventListeners(){if(!this.overlay||!this.canvas)return;const t=this.overlay.querySelectorAll(".triagly-tool-btn");t.forEach(n=>{n.addEventListener("click",n=>{const e=n.currentTarget.dataset.tool;e&&(this.currentTool=e,t.forEach(t=>t.classList.remove("active")),n.currentTarget.classList.add("active"))})});const n=this.overlay.querySelector(".triagly-color-picker");n&&n.addEventListener("change",t=>{this.currentColor=t.target.value});const e=this.overlay.querySelector(".triagly-btn-clear");e&&e.addEventListener("click",()=>{this.annotations=[],this.render()});const i=this.overlay.querySelector(".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)),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 n=t.touches[0];this.handleMouseDown(n)}),this.canvas.addEventListener("touchmove",t=>{t.preventDefault();const n=t.touches[0];this.handleMouseMove(n)}),this.canvas.addEventListener("touchend",()=>this.handleMouseUp()),this.canvas.addEventListener("touchcancel",()=>this.handleMouseUp());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)}getCanvasPoint(t){if(!this.canvas)return{x:0,y:0};const n=this.canvas.getBoundingClientRect();return{x:t.clientX-n.left,y:t.clientY-n.top}}handleMouseDown(t){const n=this.getCanvasPoint(t);if(this.isDrawing=!0,"text"===this.currentTool)return this.pendingTextPoint=n,this.showTextModal(),void(this.isDrawing=!1);this.currentAnnotation={type:this.currentTool,color:this.currentColor,strokeWidth:this.strokeWidth,points:[n]}}showTextModal(){if(!this.overlay)return;const t=this.overlay.querySelector("#triagly-text-modal"),n=this.overlay.querySelector("#triagly-text-input");t&&n&&(t.style.display="flex",n.value="",n.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"),n=t?.value.trim();n&&(this.annotations.push({type:"text",color:this.currentColor,strokeWidth:this.strokeWidth,points:[this.pendingTextPoint],text:n}),this.render()),this.hideTextModal()}handleMouseMove(t){if(!this.isDrawing||!this.currentAnnotation)return;const n=this.getCanvasPoint(t);"freehand"===this.currentTool||1===this.currentAnnotation.points.length?this.currentAnnotation.points.push(n):this.currentAnnotation.points[1]=n,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[n,e]=t.points,i=e.x-n.x,r=e.y-n.y;this.ctx.globalAlpha=.3,this.ctx.fillRect(n.x,n.y,i,r),this.ctx.globalAlpha=1,this.ctx.strokeRect(n.x,n.y,i,r)}drawArrow(t){if(!this.ctx||t.points.length<2)return;const[n,e]=t.points,i=Math.atan2(e.y-n.y,e.x-n.x);this.ctx.beginPath(),this.ctx.moveTo(n.x,n.y),this.ctx.lineTo(e.x,e.y),this.ctx.stroke(),this.ctx.beginPath(),this.ctx.moveTo(e.x,e.y),this.ctx.lineTo(e.x-15*Math.cos(i-Math.PI/6),e.y-15*Math.sin(i-Math.PI/6)),this.ctx.moveTo(e.x,e.y),this.ctx.lineTo(e.x-15*Math.cos(i+Math.PI/6),e.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 n=1;n<t.points.length;n++)this.ctx.lineTo(t.points[n].x,t.points[n].y);this.ctx.stroke()}}drawText(t){if(!this.ctx||!t.text||t.points.length<1)return;const n=t.points[0];this.ctx.font="bold 16px sans-serif",this.ctx.textBaseline="top";const e=this.ctx.measureText(t.text);this.ctx.fillStyle="rgba(0, 0, 0, 0.7)",this.ctx.fillRect(n.x-4,n.y-4,e.width+8,28),this.ctx.fillStyle="#ffffff",this.ctx.fillText(t.text,n.x,n.y)}close(t){if(document.removeEventListener("keydown",this.handleKeyDown),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 n=t.getContext("2d");if(!n)return this.imageData;n.drawImage(this.image,0,0);const e=1/this.scale;for(const t of this.annotations){n.strokeStyle=t.color,n.fillStyle=t.color,n.lineWidth=t.strokeWidth*e,n.lineCap="round",n.lineJoin="round";const i=t.points.map(t=>({x:t.x*e,y:t.y*e})),r={...t,points:i};switch(t.type){case"highlight":this.drawHighlightOnCtx(n,r);break;case"arrow":this.drawArrowOnCtx(n,r,e);break;case"freehand":this.drawFreehandOnCtx(n,r);break;case"text":this.drawTextOnCtx(n,r,e)}}return t.toDataURL("image/jpeg",.9)}drawHighlightOnCtx(t,n){if(n.points.length<2)return;const[e,i]=n.points,r=i.x-e.x,o=i.y-e.y;t.globalAlpha=.3,t.fillRect(e.x,e.y,r,o),t.globalAlpha=1,t.strokeRect(e.x,e.y,r,o)}drawArrowOnCtx(t,n,e){if(n.points.length<2)return;const[i,r]=n.points,o=15*e,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,n){if(!(n.points.length<2)){t.beginPath(),t.moveTo(n.points[0].x,n.points[0].y);for(let e=1;e<n.points.length;e++)t.lineTo(n.points[e].x,n.points[e].y);t.stroke()}}drawTextOnCtx(t,n,e){if(!n.text||n.points.length<1)return;const i=n.points[0],r=16*e;t.font=`bold ${r}px sans-serif`,t.textBaseline="top";const o=t.measureText(n.text),a=4*e;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(n.text,i.x,i.y)}injectStyles(){const t="triagly-annotation-styles";if(document.getElementById(t))return;const n=document.createElement("style");n.id=t,n.textContent="\n .triagly-annotation-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 999999;\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(n)}}class r{constructor(t){this.container=null,this.isOpen=!1,this.previouslyFocusedElement=null,this.focusableElements=[],this.screenshotDataUrl=null,this.screenshotCapture=null,this.config=t}init(){this.createButton(),this.injectStyles(),this.config.turnstileSiteKey&&this.loadTurnstileScript(),this.config.enableScreenshot&&(this.screenshotCapture=new e({quality:this.config.screenshotQuality,maxWidth:this.config.screenshotMaxWidth}))}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 n=this.config.buttonShape||"rounded";t.classList.add(`triagly-shape-${n}`);const e=this.config.orientation||"horizontal";t.classList.add(`triagly-orientation-${e}`);const i='<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>',r=this.config.buttonText||"Feedback";"circular"===n?(t.innerHTML=i,t.setAttribute("aria-label",r)):"expandable"===n?(t.innerHTML=`<span class="triagly-btn-icon">${i}</span><span class="triagly-btn-text"> ${this.config.buttonText||"Feedback"}</span>`,t.setAttribute("aria-label",r)):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">${r}</span>`,t.onclick=()=>this.toggle();const o=this.config.position||"bottom-right";t.classList.add(`triagly-${o}`),"expandable"===n&&(o.includes("right")?t.classList.add("triagly-expand-left"):o.includes("left")&&t.classList.add("triagly-expand-right")),this.config.offsetX&&(o.includes("right")?t.style.right=this.config.offsetX:o.includes("left")&&(t.style.left=this.config.offsetX)),this.config.offsetY&&(o.includes("top")?t.style.top=this.config.offsetY:o.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 n=this.container._tabHandler;n&&document.removeEventListener("keydown",n,!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=n=>{n.target===t&&this.close("overlay")};const n=document.createElement("div");n.className="triagly-modal",n.setAttribute("role","document");const e=document.createElement("div");e.className="triagly-header",e.innerHTML='\n <button type="button" class="triagly-close" aria-label="Close feedback form">×</button>\n ';const i=e.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 <div class="triagly-screenshot-controls" id="triagly-screenshot-controls">\n <button type="button" class="triagly-btn-capture" id="triagly-capture-btn">\n <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">\n <rect x="3" y="3" width="18" height="18" rx="2"/>\n <circle cx="8.5" cy="8.5" r="1.5"/>\n <path d="M21 15l-5-5L5 21"/>\n </svg>\n ${this.config.screenshotButtonText||"Capture Screenshot"}\n </button>\n </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 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 ',n.appendChild(e),n.appendChild(r),n.appendChild(a),t.appendChild(n),this.config.turnstileSiteKey&&setTimeout(()=>{this.renderTurnstileWidget(r)},100),t}renderTurnstileWidget(t){const n=t.querySelector(".cf-turnstile");if(n)if(window.turnstile)try{const t=window.turnstile.render(n,{sitekey:this.config.turnstileSiteKey,theme:"dark"===this.config.theme?"dark":"light",callback:e=>{n.setAttribute("data-turnstile-response",e),n.setAttribute("data-widget-id",t)},"error-callback":()=>{console.error("Triagly: Turnstile widget error")},"expired-callback":()=>{n.removeAttribute("data-turnstile-response")}});n.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-capture-btn"),e=t.querySelector("#triagly-annotate-btn"),r=t.querySelector("#triagly-remove-screenshot-btn"),o=t.querySelector("#triagly-screenshot-preview"),a=t.querySelector("#triagly-screenshot-controls"),s=t.querySelector("#triagly-screenshot-img");n?.addEventListener("click",async()=>{if(!this.screenshotCapture)return;const t=n,e=t.innerHTML;t.disabled=!0,t.innerHTML='\n <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" class="triagly-spin">\n <path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" opacity="0.3"/>\n <path d="M12 2v4"/>\n </svg>\n Capturing...\n ';try{const t=await this.screenshotCapture.capture();this.screenshotDataUrl=t,s&&(s.src=t),o&&(o.style.display="block"),a&&(a.style.display="none"),this.config.onScreenshotCapture&&this.config.onScreenshotCapture(t)}catch(t){console.error("Triagly: Screenshot capture failed:",t),this.config.onScreenshotError&&this.config.onScreenshotError(t instanceof Error?t:new Error(String(t)))}finally{t.disabled=!1,t.innerHTML=e}}),e?.addEventListener("click",()=>{if(!this.screenshotDataUrl)return;new i(this.screenshotDataUrl,t=>{this.screenshotDataUrl=t,s&&(s.src=t)},()=>{}).open()}),r?.addEventListener("click",()=>{this.screenshotDataUrl=null,o&&(o.style.display="none"),a&&(a.style.display="block"),s&&(s.src="")})}async handleSubmit(t){const n=t.querySelector("#triagly-description"),e=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:n.value.trim(),reporterName:e.value.trim()||void 0,reporterEmail:i.value.trim()||void 0,turnstileToken:t,screenshot:this.screenshotDataUrl||void 0},s=new Promise((t,n)=>{const e=()=>{document.removeEventListener("triagly:success",e),document.removeEventListener("triagly:error",i),t()},i=t=>{document.removeEventListener("triagly:success",e),document.removeEventListener("triagly:error",i),n(t.detail)};document.addEventListener("triagly:success",e,{once:!0}),document.addEventListener("triagly:error",i,{once:!0}),setTimeout(()=>{document.removeEventListener("triagly:success",e),document.removeEventListener("triagly:error",i),n(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 n=t instanceof Error?t.message:this.config.errorMessage||"Failed to send feedback. Please try again.";r.textContent=n,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 .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, #27272a);\n box-shadow: var(--triagly-button-shadow-hover, 0 6px 16px rgba(0, 0, 0, 0.2));\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 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-capture {\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-capture:hover:not(:disabled) {\n background: var(--triagly-btn-secondary-bg-hover, #e5e7eb);\n border-style: solid;\n }\n\n .triagly-btn-capture:disabled {\n opacity: 0.7;\n cursor: wait;\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 n=t=>{const n=t;if("Tab"!==n.key)return;if(!this.container?.contains(document.activeElement))return;const e=this.focusableElements[0],i=this.focusableElements[this.focusableElements.length-1];n.shiftKey?document.activeElement===e&&(n.preventDefault(),i?.focus()):document.activeElement===i&&(n.preventDefault(),e?.focus())};document.addEventListener("keydown",n,!0),this.container._tabHandler=n}destroy(){if(this.container){const t=this.container._keydownHandler;t&&document.removeEventListener("keydown",t);const n=this.container._tabHandler;n&&document.removeEventListener("keydown",n,!0)}this.close(),document.getElementById("triagly-button")?.remove(),document.getElementById("triagly-styles")?.remove()}}const o={production:"https://iipkklhhafrjesryscjh.supabase.co/functions/v1",staging:"https://bssghvinezdawvupcyci.supabase.co/functions/v1"};class a{constructor(t,n="production",e,i,r){this.apiUrl=(e||o[n]).replace(/\/$/,""),this.publishableKey=t,this.getToken=i,this.turnstileSiteKey=r||a.DEFAULT_TURNSTILE_SITE_KEY}getTurnstileSiteKey(){return this.turnstileSiteKey}async getTurnstileToken(){const t=document.querySelector("[data-turnstile-response]");if(t){const n=t.getAttribute("data-turnstile-response");if(n)return n}if(window.turnstile)try{const t=document.querySelectorAll(".cf-turnstile");if(t.length>0){const n=t[0].getAttribute("data-widget-id");if(n){const t=window.turnstile.getResponse(n);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,n,e){if(e||(e=await this.getTurnstileToken()||void 0),this.turnstileSiteKey&&!e)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:{...n,consoleLogs:t.consoleLogs},tags:t.tags,reporterEmail:t.reporterEmail,reporterName:t.reporterName,screenshot:t.screenshot,turnstileToken:e,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 s(t){const n=`${window.innerWidth}x${window.innerHeight}`,e=function(){const t=navigator.userAgent;let n="Unknown";if(t.includes("Firefox/")){const e=t.match(/Firefox\/(\d+)/)?.[1];n=`Firefox ${e}`}else if(t.includes("Chrome/")&&!t.includes("Edg")){const e=t.match(/Chrome\/(\d+)/)?.[1];n=`Chrome ${e}`}else if(t.includes("Safari/")&&!t.includes("Chrome")){const e=t.match(/Version\/(\d+)/)?.[1];n=`Safari ${e}`}else if(t.includes("Edg/")){const e=t.match(/Edg\/(\d+)/)?.[1];n=`Edge ${e}`}return n}();return{url:window.location.href,browser:e,viewport:n,userAgent:navigator.userAgent,timestamp:(new Date).toISOString(),...t}}a.DEFAULT_TURNSTILE_SITE_KEY="0x4AAAAAAB8Dc-Fl964Vp1Nn";class l{constructor(t,n=3,e=3e5){this.key=`triagly_ratelimit_${t}`,this.maxAttempts=n,this.windowMs=e}canProceed(){const t=Date.now();return!(this.getData().attempts.filter(n=>t-n<this.windowMs).length>=this.maxAttempts)}recordAttempt(){const t=Date.now(),n=this.getData();n.attempts.push(t),n.attempts=n.attempts.filter(n=>t-n<this.windowMs),this.setData(n)}getTimeUntilReset(){const t=Date.now(),n=this.getData();if(0===n.attempts.length)return 0;const e=Math.min(...n.attempts)+this.windowMs;return Math.max(0,e-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 c{constructor(t=50,n=["error","warn"]){this.buffer=[],this.isActive=!1,this.maxLogs=t,this.levels=new Set(n),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,n){try{const e=n.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=n.find(t=>t instanceof Error);i=t?t.stack:(new Error).stack?.split("\n").slice(2).join("\n")}const r=this.sanitize(e),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 d{constructor(t){this.consoleLogger=null;let n=t.apiKey||t.publishableKey;if(!n&&t.projectId&&(console.warn("Triagly: projectId is deprecated. Please use apiKey instead. See migration guide: https://docs.triagly.com/sdk/migration"),n=t.projectId),!n)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:n,publishableKey:n},this.api=new a(n,this.config.environment||"production",this.config.apiUrl,this.config.getToken,this.config.turnstileSiteKey),this.config.turnstileSiteKey=this.api.getTurnstileSiteKey(),this.widget=new r(this.config),this.rateLimiter=new l(n,3,3e5),!1!==this.config.captureConsole&&(this.consoleLogger=new c(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 n=t;this.handleSubmit(n.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 n=s(this.config.metadata),e={title:t.title,description:t.description,reporterEmail:t.reporterEmail,consoleLogs:this.consoleLogger?.getLogs(),screenshot:t.screenshot},i=await this.api.submitFeedback(e,n,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,n){if(this.config.turnstileSiteKey&&!n)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 e=s(this.config.metadata);!t.consoleLogs&&this.consoleLogger&&(t.consoleLogs=this.consoleLogger.getLogs()),await this.api.submitFeedback(t,e,n),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 d(t)),window.Triagly=d}t.Triagly=d,t.default=d,Object.defineProperty(t,"__esModule",{value:!0})});
2
2
  //# sourceMappingURL=index.min.js.map