cap-widget 0.1.47 → 0.1.49

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/cap.d.ts CHANGED
@@ -3,6 +3,7 @@ declare global {
3
3
  CAP_CUSTOM_FETCH?: typeof fetch;
4
4
  CAP_CUSTOM_WASM_URL?: string;
5
5
  CAP_CSS_NONCE?: string;
6
+ CAP_SCRIPT_NONCE?: string;
6
7
  CAP_DONT_SKIP_REDEFINE?: boolean;
7
8
  Cap: typeof Cap;
8
9
  }
package/cap.min.js CHANGED
@@ -1 +1 @@
1
- (()=>{const e="vibrate"in navigator&&!window.matchMedia("(prefers-reduced-motion: reduce)").matches;if("undefined"==typeof window)return;const t=(e,t={})=>window?.CAP_CUSTOM_FETCH?window.CAP_CUSTOM_FETCH(e,t):fetch(e,t);function i(e,t){let i=function(e){let t=2166136261;for(let i=0;i<e.length;i++)t^=e.charCodeAt(i),t+=(t<<1)+(t<<4)+(t<<7)+(t<<8)+(t<<24);return t>>>0}(e),s="";function r(){return i^=i<<13,i^=i>>>17,i^=i<<5,i>>>0}for(;s.length<t;){s+=r().toString(16).padStart(8,"0")}return s.substring(0,t)}async function s(e){var t=(e=>{const t=atob(e),i=new Uint8Array(t.length);for(let e=0;e<t.length;e++)i[e]=t.charCodeAt(e);return i})(e);const i=await new Promise((e,i)=>{try{var s=new DecompressionStream("deflate-raw"),r=s.writable.getWriter(),a=s.readable.getReader(),n=[];a.read().then(function t(s){if(s.done){for(var r=0,o=0,l=0;l<n.length;l++)r+=n[l].length;var c=new Uint8Array(r);for(l=0;l<n.length;l++)c.set(n[l],o),o+=n[l].length;e((new TextDecoder).decode(c))}else n.push(s.value),a.read().then(t).catch(i)}).catch(i),r.write(t).then(()=>{r.close()}).catch(i)}catch(e){i(e)}});return new Promise(e=>{var t=setTimeout(()=>{a(),e({__timeout:!0})},2e4),s=document.createElement("iframe");s.setAttribute("sandbox","allow-scripts"),s.setAttribute("aria-hidden","true"),s.style.cssText="position:absolute;width:1px;height:1px;top:-9999px;left:-9999px;border:none;opacity:0;pointer-events:none;";var r=!1;function a(){r||(r=!0,clearTimeout(t),window.removeEventListener("message",n),s.parentNode&&s.parentNode.removeChild(s))}function n(t){var i=t.data;i&&"object"==typeof i&&("cap:instr"===i.type?(a(),i.blocked?e({__blocked:!0,blockReason:i.blockReason||"automated_browser"}):i.result?e(i.result):e({__timeout:!0})):"cap:error"===i.type&&(a(),e({__timeout:!0})))}window.addEventListener("message",n),s.srcdoc='<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><script>'+i+"\n<\/script></body></html>",document.body.appendChild(s)})}let r=null;const a=()=>{if(r)return r;const e=window.CAP_CUSTOM_WASM_URL||"https://cdn.jsdelivr.net/npm/@cap.js/wasm@0.0.6/browser/cap_wasm_bg.wasm";return r=fetch(e).then(e=>{if(!e.ok)throw new Error(`Failed to fetch wasm: ${e.status}`);return e.arrayBuffer()}).then(e=>WebAssembly.compile(e)).catch(e=>{throw r=null,e}),r};"object"==typeof WebAssembly&&"function"==typeof WebAssembly.compile&&a().catch(()=>{});let n=null;function o(){return n||(n=URL.createObjectURL(new Blob(['(()=>{const e=async({salt:e,target:t})=>{let n=0;const r=new TextEncoder,o=4*t.length,s=Math.floor(o/8),l=o%8,a=t.length%2==0?t:t+"0",c=a.length/2,i=new Uint8Array(c);for(let e=0;e<c;e++)i[e]=parseInt(a.substring(2*e,2*e+2),16);const f=l>0?255<<8-l&255:0;for(;;)try{for(let t=0;t<5e4;t++){const t=e+n,o=r.encode(t),a=await crypto.subtle.digest("SHA-256",o),c=new Uint8Array(a);let g=!0;for(let e=0;e<s;e++)if(c[e]!==i[e]){g=!1;break}if(g&&l>0&&(c[s]&f)!==(i[s]&f)&&(g=!1),g)return void self.postMessage({nonce:n,found:!0});n++}}catch(e){return console.error("[cap worker]",e),void self.postMessage({found:!1,error:e.message})}};if("object"!=typeof WebAssembly||"function"!=typeof WebAssembly?.instantiate)return console.warn("[cap worker] wasm not supported, falling back to alternative solver. this will be significantly slower."),void(self.onmessage=async({data:{salt:t,target:n}})=>e({salt:t,target:n}));let t=null;self.onmessage=async({data:{salt:n,target:r,wasmModule:o}})=>{if(o instanceof WebAssembly.Module&&null===t){const s=(e=>{try{let n,r=0,o=null;const s=()=>(null!==o&&0!==o.byteLength||(o=new Uint8Array(n.memory.buffer)),o),l=new TextEncoder,a=(e,t,n)=>{if(void 0===n){const n=l.encode(e),o=t(n.length,1)>>>0;return s().subarray(o,o+n.length).set(n),r=n.length,o}let o=e.length,a=t(o,1)>>>0;const c=s();let i=0;for(;i<o;i++){const t=e.charCodeAt(i);if(t>127)break;c[a+i]=t}if(i!==o){0!==i&&(e=e.slice(i)),a=n(a,o,o=i+3*e.length,1)>>>0;const t=s().subarray(a+i,a+o),{written:r}=l.encodeInto(e,t);i+=r,a=n(a,o,i,1)>>>0}return r=i,a},c={wbg:{}};c.wbg.__wbindgen_init_externref_table=()=>{const e=n.__wbindgen_export_0,t=e.grow(4);e.set(0,void 0),e.set(t+0,void 0),e.set(t+1,null),e.set(t+2,!0),e.set(t+3,!1)};const i=new WebAssembly.Instance(e,c);return n=i.exports,n.__wbindgen_start&&n.__wbindgen_start(),t=(e,t)=>{const o=a(e,n.__wbindgen_malloc,n.__wbindgen_realloc),s=r,l=a(t,n.__wbindgen_malloc,n.__wbindgen_realloc),c=r;return BigInt.asUintN(64,n.solve_pow(o,s,l,c))},!0}catch(e){return console.error("[cap worker] failed to init wasm from module:",e),!1}})(o);if(!s)return console.warn("[cap worker] wasm init failed, falling back to JS solver."),e({salt:n,target:r})}if(null===t)return console.warn("[cap worker] no wasm module provided, falling back to JS solver."),e({salt:n,target:r});try{const e=performance.now(),o=t(n,r),s=performance.now();self.postMessage({nonce:Number(o),found:!0,durationMs:(s-e).toFixed(2)})}catch(e){console.error("[cap worker]",e),self.postMessage({found:!1,error:e.message||String(e)})}},self.onerror=e=>{self.postMessage({found:!1,error:e})}})();'],{type:"application/javascript"})),n)}class l{constructor(e){this._size=e,this._workers=[],this._idle=[],this._queue=[],this._wasmModule=null,this._spawnFailures=0}setWasm(e){this._wasmModule=e}_spawn(){const e=o(),t=new Worker(e);return t._busy=!1,this._workers.push(t),this._idle.push(t),t}_replaceWorker(e){const t=this._workers.indexOf(e);-1!==t&&this._workers.splice(t,1);const i=this._idle.indexOf(e);-1!==i&&this._idle.splice(i,1);try{e.terminate()}catch{}return this._spawnFailures++,this._spawnFailures>3?(console.error("[cap] worker spawn failed repeatedly, not retrying"),null):this._spawn()}_ensureSize(e){for(;this._workers.length<e;)this._spawn()}run(e,t){return new Promise((i,s)=>{this._queue.push({salt:e,target:t,resolve:i,reject:s}),this._dispatch()})}_dispatch(){for(;this._idle.length>0&&this._queue.length>0;){const e=this._idle.shift(),{salt:t,target:i,resolve:s,reject:r}=this._queue.shift();let a=!1;const n=({data:t})=>{a||(a=!0,e.removeEventListener("message",n),e.removeEventListener("error",o),this._spawnFailures=0,this._idle.push(e),t.found?s(t.nonce):r(new Error(t.error||"worker failed")),this._dispatch())},o=t=>{if(a)return;a=!0,e.removeEventListener("message",n),e.removeEventListener("error",o);const i=this._replaceWorker(e);r(t),i&&this._dispatch()};e.addEventListener("message",n),e.addEventListener("error",o),this._wasmModule?e.postMessage({salt:t,target:i,wasmModule:this._wasmModule},[]):e.postMessage({salt:t,target:i})}}terminate(){for(const e of this._workers)try{e.terminate()}catch{}this._workers=[],this._idle=[],this._queue=[]}}class c extends HTMLElement{static formAssociated=!0;#e=null;#t=navigator.hardwareConcurrency||8;token=null;#i;#s;#r;#a=!1;#n;#o;#l=null;#c=null;#h=null;#d=null;get#p(){return e&&!window.CAP_DISABLE_HAPTICS&&!this.hasAttribute("data-cap-disable-haptics")}#u(){return{state:"idle",challengeResp:null,challenges:null,results:[],completedCount:0,solvePromise:null,promoteFn:null,_listeners:[],pendingPromotion:null,token:null,tokenExpires:null,notify(){for(const e of this._listeners)e();this._listeners=[]},onSettled(e){"done"===this.state||"error"===this.state?e():this._listeners.push(e)}}}#v(){this.#l=this.#u(),this.#m()}#g(){this.#d&&(window.removeEventListener("mousemove",this.#d),window.removeEventListener("touchstart",this.#d),window.removeEventListener("keydown",this.#d),this.#d=null)}#m(){this.#g();const e=()=>{this.#g(),this.#b()};this.#d=e,window.addEventListener("mousemove",e,{passive:!0}),window.addEventListener("touchstart",e,{passive:!0}),window.addEventListener("keydown",e,{passive:!0})}#f(){return"function"==typeof this.checkVisibility?this.checkVisibility({checkOpacity:!0,checkVisibilityCSS:!0}):!!(this.offsetParent||this.getClientRects().length>0)}#b(){"idle"===this.#l.state&&this.#f()&&(this.#l.state="waiting",this.#c=setTimeout(()=>{this.#w()},2500))}async#w(){if("waiting"!==this.#l.state)return;this.#l.state="fetching";let e=this.getAttribute("data-cap-api-endpoint");if(!e&&window?.CAP_CUSTOM_FETCH&&(e="/"),e){e.endsWith("/")||(e+="/");try{const s=await t(`${e}challenge`,{method:"POST"});let r;try{r=await s.json()}catch{throw new Error("Failed to parse speculative challenge response")}if(r.error)throw new Error(r.error);r._apiEndpoint=e,this.#l.challengeResp=r;const{challenge:a,token:n}=r;let o=a;if(!Array.isArray(o)){let e=0;o=Array.from({length:a.c},()=>(e++,[i(`${n}${e}`,a.s),i(`${n}${e}d`,a.d)]))}this.#l.challenges=o,this.#l.state="solving",this.#l.solvePromise=this.#y(o)}catch(e){console.warn("[cap] speculative challenge fetch failed:",e),this.#l.state="error",this.#l.notify()}}else this.#l.state="idle"}async#y(e){o();let t=null;try{t=await a()}catch{}this.#h||(this.#h=new l(1),this.#h._spawn()),this.#h.setWasm(t);const i=e.length,s=new Array(i);let r=1,n=!1;this.#l.promoteFn=e=>{n||(n=!0,r=e,this.#h._size=e,this.#h._ensureSize(e))},null!==this.#l.pendingPromotion&&(this.#l.promoteFn(this.#l.pendingPromotion),this.#l.pendingPromotion=null);let c=0;for(;c<i;){const t=r,a=[],o=[];for(let s=0;s<t&&c<i;s++)o.push(c),a.push(e[c]),c++;this.#h._ensureSize(Math.max(r,t));const l=await Promise.all(a.map(e=>this.#h.run(e[0],e[1]).then(e=>(this.#l.completedCount++,e))));for(let e=0;e<o.length;e++)s[o[e]]=l[e];!n&&c<i&&await new Promise(e=>setTimeout(e,120))}return this.#l.results=s,this.#l.state="redeeming",this.#k(s),s}async#k(e){try{const i=this.#l.challengeResp,r=i._apiEndpoint;if(!r)throw new Error("[cap] speculative redeem: missing apiEndpoint");let a=null;if(i.instrumentation&&(a=await s(i.instrumentation),a?.__timeout||a?.__blocked))return this.#l.state="done",void this.#l.notify();const n=await t(`${r}redeem`,{method:"POST",body:JSON.stringify({token:i.token,solutions:e,...a&&{instr:a}}),headers:{"Content-Type":"application/json"}});let o;try{o=await n.json()}catch{throw new Error("Failed to parse speculative redeem response")}if(!o.success)throw new Error(o.error||"Speculative redeem failed");this.#l.token=o.token,this.#l.tokenExpires=new Date(o.expires).getTime(),this.#l.state="done",this.#l.notify()}catch(e){console.warn("[cap] speculative redeem failed (will redo on click):",e),this.#l.state="done",this.#l.notify()}}getI18nText(e,t){return this.getAttribute(`data-cap-i18n-${e}`)||t}static get observedAttributes(){return["onsolve","onprogress","onreset","onerror","data-cap-worker-count","data-cap-i18n-initial-state","required"]}constructor(){super(),this.#n&&this.#n.forEach((e,t)=>{this.removeEventListener(t.slice(2),e)}),this.#n=new Map,this.boundHandleProgress=this.handleProgress.bind(this),this.boundHandleSolve=this.handleSolve.bind(this),this.boundHandleError=this.handleError.bind(this),this.boundHandleReset=this.handleReset.bind(this);try{this.#o=this.attachInternals()}catch{}}#x(){this.#o?.setValidity&&(this.hasAttribute("required")&&!this.token?this.#o.setValidity({valueMissing:!0},this.getI18nText("required-label","Please verify you're human"),this.#s||this):this.#o.setValidity({}))}initialize(){o(),this.#l||(this.#l=this.#u()),this.#h||(this.#h=new l(1),this.#h._spawn())}attributeChangedCallback(e,t,i){if(e.startsWith("on")){const t=e.slice(2),s=this.#n.get(e);if(s&&this.removeEventListener(t,s),i){const i=t=>{const i=this.getAttribute(e);"function"==typeof window[i]&&window[i].call(this,t)};this.#n.set(e,i),this.addEventListener(t,i)}}"data-cap-worker-count"===e&&this.setWorkersCount(parseInt(i,10)),"data-cap-i18n-initial-state"===e&&this.#s&&this.#s?.querySelector(".label.active")&&this.animateLabel(this.getI18nText("initial-state","Verify you're human")),"required"===e&&this.#x()}async connectedCallback(){this.#r=this,this.shadowRoot?this.#i=this.shadowRoot:this.#i=this.attachShadow({mode:"open"}),this.#s||(this.#s=document.createElement("div")),this.createUI(),this.addEventListeners(),this.initialize(),this.#s.removeAttribute("disabled");const e=this.getAttribute("data-cap-worker-count"),t=e?parseInt(e,10):null;this.setWorkersCount(t||navigator.hardwareConcurrency||8);const i=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.#r.innerHTML=`<input type="hidden" name="${i}">`,this.#m(),this.#x(),this.addEventListener("invalid",this.#E)}#E=()=>{if(this.#s){try{this.scrollIntoView({behavior:"smooth",block:"center"})}catch{this.scrollIntoView()}this.#s.classList.remove("invalid"),this.#s.offsetWidth,this.#s.classList.add("invalid"),setTimeout(()=>this.#s?.classList.remove("invalid"),1500)}};async solve(){if(!this.#a)try{this.#a=!0,this.updateUI("verifying",this.getI18nText("verifying-label","Verifying..."),!0),this.#s.setAttribute("aria-label",this.getI18nText("verifying-aria-label","Verifying you're a human, please wait")),this.dispatchEvent("progress",{progress:0});try{let e,r,a=this.getAttribute("data-cap-api-endpoint");if(!a&&window?.CAP_CUSTOM_FETCH)a="/";else if(!a)throw new Error("Missing API endpoint. Either custom fetch or an API endpoint must be provided.");if(a.endsWith("/")||(a+="/"),"done"===this.#l.state&&this.#l.token&&this.#l.tokenExpires&&Date.now()<this.#l.tokenExpires){this.dispatchEvent("progress",{progress:100});const e=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${e}']`)&&(this.querySelector(`input[name='${e}']`).value=this.#l.token),this.dispatchEvent("solve",{token:this.#l.token}),this.token=this.#l.token;const t=this.#l.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),t),this.#s.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),this.#p&&navigator.vibrate([10,50,20,30,40]),this.#v(),this.#a=!1,{success:!0,token:this.token}}if("done"===this.#l.state)e=this.#l.results,r=this.#l.challengeResp,this.dispatchEvent("progress",{progress:100});else if("solving"===this.#l.state||"redeeming"===this.#l.state||"fetching"===this.#l.state||"waiting"===this.#l.state){"waiting"===this.#l.state&&(this.#c&&(clearTimeout(this.#c),this.#c=null),this.#l.state="waiting",this.#w()),this.#l.pendingPromotion=this.#t,this.#l.promoteFn&&this.#l.promoteFn(this.#t);const t=setInterval(()=>{const e=this.#l.state;if("done"===e||"error"===e)return void clearInterval(t);const i=this.#l.challenges?this.#l.challenges.length:1,s=this.#l.completedCount,r="redeeming"===e?99:"fetching"===e||"waiting"===e?0:Math.min(98,Math.round(s/i*100));this.dispatchEvent("progress",{progress:r})},150);if(await new Promise(e=>this.#l.onSettled(e)),clearInterval(t),"done"!==this.#l.state)throw new Error("Speculative solve failed – please try again");if(this.#l.token&&this.#l.tokenExpires&&Date.now()<this.#l.tokenExpires){this.dispatchEvent("progress",{progress:100});const e=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${e}']`)&&(this.querySelector(`input[name='${e}']`).value=this.#l.token),this.dispatchEvent("solve",{token:this.#l.token}),this.token=this.#l.token;const t=this.#l.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),t),this.#s.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),this.#p&&navigator.vibrate([10,50,20,30,40]),this.#v(),this.#a=!1,{success:!0,token:this.token}}e=this.#l.results,r=this.#l.challengeResp,this.dispatchEvent("progress",{progress:100})}else{const s=await t(`${a}challenge`,{method:"POST"});try{r=await s.json()}catch{throw new Error("Failed to parse challenge response from server")}if(r.error)throw new Error(r.error);const{challenge:n,token:o}=r;let l=n;if(!Array.isArray(l)){let e=0;l=Array.from({length:n.c},()=>(e++,[i(`${o}${e}`,n.s),i(`${o}${e}d`,n.d)]))}e=await this.solveChallenges(l)}const n=r.instrumentation?s(r.instrumentation):Promise.resolve(null),o=await n;if(o?.__timeout||o?.__blocked){this.updateUIBlocked(this.getI18nText("error-label","Error"),o?.__blocked),this.#s.setAttribute("aria-label",this.getI18nText("error-aria-label","An error occurred, please try again")),this.removeEventListener("error",this.boundHandleError);const e=new CustomEvent("error",{bubbles:!0,composed:!0,detail:{isCap:!0,message:"Instrumentation failed"}});return super.dispatchEvent(e),this.addEventListener("error",this.boundHandleError),this.executeAttributeCode("onerror",e),console.error("[cap]","Instrumentation failed"),void(this.#a=!1)}const{token:l}=r,c=await t(`${a}redeem`,{method:"POST",body:JSON.stringify({token:l,solutions:e,...o&&{instr:o}}),headers:{"Content-Type":"application/json"}});let h;try{h=await c.json()}catch{throw new Error("Failed to parse server response")}if(this.dispatchEvent("progress",{progress:100}),!h.success)throw new Error(h.error||"Invalid solution");const d=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${d}']`)&&(this.querySelector(`input[name='${d}']`).value=h.token),this.dispatchEvent("solve",{token:h.token}),this.token=h.token,this.#v(),this.#e&&clearTimeout(this.#e);const p=new Date(h.expires).getTime()-Date.now();return p>0&&p<864e5?this.#e=setTimeout(()=>this.reset(),p):this.error("Invalid expiration time"),this.#s.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),this.#p&&navigator.vibrate([10,50,20,30,40]),{success:!0,token:this.token}}catch(e){throw this.#s.setAttribute("aria-label",this.getI18nText("error-aria-label","An error occurred, please try again")),this.error(e.message),e}}finally{this.#a=!1}}async solveChallenges(e){const t=e.length;let i=0;let s=null;const r="object"==typeof WebAssembly&&"function"==typeof WebAssembly.instantiate;if(r)try{s=await a()}catch(e){console.warn("[cap] wasm unavailable, falling back to JS solver:",e)}if(!r&&!this.#i.querySelector(".warning")){const e=document.createElement("div");e.className="warning",e.style.cssText="width:var(--cap-widget-width,230px);background:rgb(237,56,46);color:white;padding:4px 6px;padding-bottom:calc(var(--cap-border-radius,14px) + 5px);font-size:10px;box-sizing:border-box;font-family:system-ui;border-top-left-radius:8px;border-top-right-radius:8px;text-align:center;user-select:none;margin-bottom:-35.5px;opacity:0;transition:margin-bottom .3s,opacity .3s;",e.innerText=this.getI18nText("wasm-disabled","Enable WASM for significantly faster solving"),this.#i.insertBefore(e,this.#i.firstChild),setTimeout(()=>{e.style.marginBottom="calc(-1 * var(--cap-border-radius, 14px))",e.style.opacity=1},10)}const n=new l(this.#t);n.setWasm(s),n._ensureSize(this.#t);const o=[];try{for(let s=0;s<e.length;s+=this.#t){const r=e.slice(s,Math.min(s+this.#t,e.length)),a=await Promise.all(r.map(([e,s])=>n.run(e,s).then(e=>{i++;const s=Math.min(99,Math.round((0+i)/t*100));return this.dispatchEvent("progress",{progress:s}),e})));o.push(...a)}}finally{n.terminate()}return o}setWorkersCount(e){const t=parseInt(e,10),i=Math.min(navigator.hardwareConcurrency||8,16);this.#t=!Number.isNaN(t)&&t>0&&t<=i?t:navigator.hardwareConcurrency||8}createUI(){this.#s.classList.add("captcha"),this.#s.setAttribute("role","button"),this.#s.setAttribute("tabindex","0"),this.#s.setAttribute("aria-label",this.getI18nText("verify-aria-label","Click to verify you're a human")),this.#s.setAttribute("aria-live","polite"),this.#s.setAttribute("disabled","true"),this.#s.innerHTML=`<div class="checkbox" part="checkbox"><svg class="progress-ring" viewBox="0 0 32 32"><circle class="progress-ring-bg" cx="16" cy="16" r="14"></circle><circle class="progress-ring-circle" cx="16" cy="16" r="14"></circle></svg></div><p part="label" class="label-wrapper"><span class="label active">${this.getI18nText("initial-state","Verify you're human")}</span></p><a part="attribution" aria-label="Secured by Cap" href="https://trycap.dev" class="credits" target="_blank" title="Secured by Cap: The self-hosted CAPTCHA for the modern web.">Cap</a>`,this.#i.innerHTML=`<style${window.CAP_CSS_NONCE?` nonce=${window.CAP_CSS_NONCE}`:""}>@media (prefers-reduced-motion:reduce){.captcha,.captcha *{transition:none!important;animation:none!important}.label{filter:none!important;opacity:1!important;transition:none!important;transform:none!important}.label:not(.active),.label.exit{display:none!important}}.captcha,.captcha *{box-sizing:border-box}.captcha{background-color:var(--cap-background,#fdfdfd);border:1px solid var(--cap-border-color,#dddddd8f);border-radius:var(--cap-border-radius,14px);-webkit-user-select:none;user-select:none;height:var(--cap-widget-height,58px);width:var(--cap-widget-width,260px);padding:var(--cap-widget-padding,14px);align-items:center;gap:var(--cap-gap,15px);cursor:pointer;-webkit-tap-highlight-color:#fff0;color:var(--cap-color,#212121);transition:filter .2s,transform .2s;display:flex;position:relative;overflow:hidden}.captcha:not([data-state]):active{transform:scale(.97)}.captcha:hover{filter:brightness(98%)}.captcha *{font-family:var(--cap-font,system, -apple-system, "BlinkMacSystemFont", ".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif)}.checkbox{width:var(--cap-checkbox-size,25px);height:var(--cap-checkbox-size,25px);border:var(--cap-checkbox-border,1px solid #aaaaaad1);border-radius:var(--cap-checkbox-border-radius,6px);background-color:var(--cap-checkbox-background,#fafafa91);margin-top:var(--cap-checkbox-margin,2px);margin-bottom:var(--cap-checkbox-margin,2px);transition:opacity .2s}.captcha p{-webkit-user-select:none;user-select:none;margin:0;font-size:15px;font-weight:500}.label-wrapper{flex:1;align-items:center;min-width:0;height:2em;display:flex;position:relative;overflow:hidden}.label-wrapper:before,.label-wrapper:after{content:"";pointer-events:none;z-index:1;width:100%;height:.6em;position:absolute;left:0}.label-wrapper:before{background:linear-gradient(to bottom, var(--cap-background,#fdfdfd), transparent);top:0}.label-wrapper:after{background:linear-gradient(to top, var(--cap-background,#fdfdfd), transparent);bottom:0}.label{white-space:nowrap;opacity:0;filter:blur(2px);transition:transform .5s cubic-bezier(.25,1,.5,1),opacity .5s,filter .5s;position:absolute;transform:translateY(100%)}.label.active{opacity:1;filter:none;transform:translateY(0)}.label.exit{opacity:0;filter:blur(2px);transform:translateY(-100%)}.checkbox .progress-ring{width:100%;height:100%;display:none;transform:rotate(-90deg)}.checkbox .progress-ring-bg{fill:none;stroke:var(--cap-spinner-background-color,#eee);stroke-width:var(--cap-spinner-thickness,3)}.checkbox .progress-ring-circle{fill:none;stroke:var(--cap-spinner-color,#000);stroke-width:var(--cap-spinner-thickness,3);stroke-linecap:round;stroke-dasharray:87.96;stroke-dashoffset:87.96px;transition:stroke-dashoffset .3s}.captcha[data-state=verifying] .checkbox{background:0 0;border:none;border-radius:50%;justify-content:center;align-items:center;display:flex;transform:scale(1.1)}.captcha[data-state=verifying] .checkbox .progress-ring{display:block}.captcha[data-state=done] .checkbox{background-image:var(--cap-checkmark,url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cstyle%3E%40keyframes%20anim%7B0%25%7Bstroke-dashoffset%3A23.21320343017578px%7Dto%7Bstroke-dashoffset%3A0%7D%7D%3C%2Fstyle%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%2300a67d%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m5%2012%205%205L20%207%22%20style%3D%22stroke-dashoffset%3A0%3Bstroke-dasharray%3A23.21320343017578px%3Banimation%3Aanim%20.5s%20ease%22%2F%3E%3C%2Fsvg%3E));background-size:cover;border:1px solid #0000}.captcha[data-state=done] .checkbox .progress-ring{display:none}.captcha[data-state=error] .checkbox{background-image:var(--cap-error-cross,url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='96' viewBox='0 0 24 24'%3E%3Cpath fill='%23f55b50' d='M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8'/%3E%3C%2Fsvg%3E"));background-size:cover;border:1px solid #0000}.captcha[data-state=error] .checkbox .progress-ring{display:none}.captcha.invalid{border-color:var(--cap-invalid-border-color,#f55b50);box-shadow:0 0 0 3px var(--cap-invalid-ring-color,#f55b5033);animation:.5s cubic-bezier(.36,.07,.19,.97) cap-shake}@keyframes cap-shake{10%,90%{transform:translate(-1px)}20%,80%{transform:translate(2px)}30%,50%,70%{transform:translate(-4px)}40%,60%{transform:translate(4px)}}@media (prefers-reduced-motion:reduce){.captcha.invalid{animation:none!important}}.captcha[disabled]{cursor:not-allowed}.captcha[disabled][data-state=verifying]{cursor:progress}.captcha[disabled][data-state=done]{cursor:default}.captcha .credits{color:var(--cap-color,#212121);text-underline-offset:.18em;z-index:6;text-decoration-thickness:.08em;position:absolute;bottom:10px;right:10px;opacity:.8!important;font-size:12px!important;display:block!important}.cap-troubleshoot-link{color:#06c;text-underline-offset:.15em;cursor:pointer;font-weight:500;font-size:inherit;text-decoration:underline;text-decoration-thickness:.08em}.cap-troubleshoot-link:hover{opacity:.8}</style>`,this.#i.appendChild(this.#s)}addEventListeners(){this.#s&&(this.#s.querySelector("a").addEventListener("click",e=>{e.stopPropagation(),e.preventDefault(),window.open(`https://trycap.dev/?${new URLSearchParams({utm_source:"cap_widget",utm_medium:"referral",utm_campaign:"widget",utm_content:window.CAP_DISABLE_WIDGET_REF?"":location.hostname,ref:window.CAP_DISABLE_WIDGET_REF?"":location.href||"",sub:window.CAP_DISABLE_WIDGET_REF?"":document.referrer||""}).toString()}`,"_blank")}),this.#s.addEventListener("click",()=>{this.#s.hasAttribute("disabled")||this.solve()}),this.#s.addEventListener("mousedown",()=>{!this.#s.hasAttribute("disabled")&&this.#p&&navigator.vibrate(5)}),this.#s.addEventListener("keydown",e=>{"Enter"!==e.key&&" "!==e.key||this.#s.hasAttribute("disabled")||(e.preventDefault(),e.stopPropagation(),this.solve())}),this.addEventListener("progress",this.boundHandleProgress),this.addEventListener("solve",this.boundHandleSolve),this.addEventListener("error",this.boundHandleError),this.addEventListener("reset",this.boundHandleReset))}animateLabel(e){if(!this.#s)return;const t=this.#s.querySelector(".label-wrapper");if(!t)return;if(window.matchMedia?.("(prefers-reduced-motion: reduce)").matches){const i=t.querySelector(".label.active");if(i)i.textContent=e;else{const i=document.createElement("span");i.className="label active",i.textContent=e,t.appendChild(i)}return}const i=t.querySelector(".label.active"),s=document.createElement("span");s.className="label",s.textContent=e,t.appendChild(s),s.offsetWidth,s.classList.add("active"),i&&(i.classList.remove("active"),i.classList.add("exit"),i.addEventListener("transitionend",()=>i.remove(),{once:!0}))}updateUI(e,t,i=!1){this.#s&&(this.#s.setAttribute("data-state",e),this.animateLabel(t),i?this.#s.setAttribute("disabled","true"):this.#s.removeAttribute("disabled"))}updateUIBlocked(e,t=!1){if(!this.#s)return;this.#s.setAttribute("data-state","error"),this.#s.removeAttribute("disabled");const i=this.#s.querySelector(".label-wrapper");if(!i)return;const s=this.getAttribute("data-cap-troubleshooting-url")||"https://trycap.dev/guide/troubleshooting/instrumentation.html",r=i.querySelector(".label.active"),a=document.createElement("span");a.className="label",a.innerHTML=t?`${e} · <a class="cap-troubleshoot-link" href="${s}" target="_blank" rel="noopener">${this.getI18nText("troubleshooting-label","Troubleshoot")}</a>`:e,i.appendChild(a),a.offsetWidth,a.classList.add("active"),r&&(r.classList.remove("active"),r.classList.add("exit"),r.addEventListener("transitionend",()=>r.remove(),{once:!0}));const n=a.querySelector(".cap-troubleshoot-link");n&&n.addEventListener("click",e=>{e.stopPropagation()})}handleProgress(e){if(!this.#s)return;const t=this.#s.querySelector(".progress-ring-circle");if(t){const i=2*Math.PI*14,s=i-e.detail.progress/100*i;t.style.strokeDashoffset=s}const i=this.#s.querySelector(".label-wrapper");if(i){const e=i.querySelector(".label.active");e&&(e.textContent=`${this.getI18nText("verifying-label","Verifying...")}`)}this.executeAttributeCode("onprogress",e)}handleSolve(e){this.updateUI("done",this.getI18nText("solved-label","You're a human"),!0),this.executeAttributeCode("onsolve",e),this.#o?.setValidity?.({}),this.#s?.classList.remove("invalid")}handleError(e){this.updateUI("error",this.getI18nText("error-label","Error. Try again.")),this.executeAttributeCode("onerror",e),this.#p&&navigator.vibrate([10,40,10])}handleReset(e){this.updateUI("",this.getI18nText("initial-state","I'm a human")),this.executeAttributeCode("onreset",e),this.#x()}executeAttributeCode(e,t){const i=this.getAttribute(e);i&&(console.error("[cap] using `onxxx='…'` is strongly discouraged and will be deprecated soon. please use `addEventListener` callbacks instead."),new Function("event",i).call(this,t))}error(e="Unknown error"){console.error("[cap]",e),this.dispatchEvent("error",{isCap:!0,message:e})}dispatchEvent(e,t={}){const i=new CustomEvent(e,{bubbles:!0,composed:!0,detail:t});super.dispatchEvent(i)}reset(){this.#e&&(clearTimeout(this.#e),this.#e=null),this.token=null,this.dispatchEvent("reset");const e=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${e}']`)&&(this.querySelector(`input[name='${e}']`).value="")}get tokenValue(){return this.token}disconnectedCallback(){this.removeEventListener("progress",this.boundHandleProgress),this.removeEventListener("solve",this.boundHandleSolve),this.removeEventListener("error",this.boundHandleError),this.removeEventListener("reset",this.boundHandleReset),this.#n.forEach((e,t)=>{this.removeEventListener(t.slice(2),e)}),this.#n.clear(),this.#i&&(this.#i.innerHTML=""),this.reset(),this.cleanup()}cleanup(){this.#e&&(clearTimeout(this.#e),this.#e=null),this.#g(),this.#c&&(clearTimeout(this.#c),this.#c=null),this.#h&&(this.#h.terminate(),this.#h=null),this.#l&&(this.#l.state="error",this.#l.notify(),this.#l=null)}}class h{constructor(e={},t){const i=t||document.createElement("cap-widget");if(Object.entries(e).forEach(([e,t])=>{i.setAttribute(e,t)}),!e.apiEndpoint&&!window?.CAP_CUSTOM_FETCH)throw i.remove(),new Error("Missing API endpoint. Either custom fetch or an API endpoint must be provided.");e.apiEndpoint&&i.setAttribute("data-cap-api-endpoint",e.apiEndpoint),t||i.hasAttribute("data-cap-disable-haptics")||i.setAttribute("data-cap-disable-haptics",""),this.widget=i,this.solve=this.widget.solve.bind(this.widget),this.reset=this.widget.reset.bind(this.widget),this.addEventListener=this.widget.addEventListener.bind(this.widget),Object.defineProperty(this,"token",{get:()=>i.token,configurable:!0,enumerable:!0}),t||(i.style.display="none",document.documentElement.appendChild(i))}}window.Cap=h,customElements.get("cap-widget")||window?.CAP_DONT_SKIP_REDEFINE?customElements.get("cap-widget")&&console.warn("[cap] the cap-widget element has already been defined, skipping re-defining it.\nto prevent this, set window.CAP_DONT_SKIP_REDEFINE to true"):customElements.define("cap-widget",c),"object"==typeof exports&&"undefined"!=typeof module?module.exports=h:"function"==typeof define&&define.amd&&define([],()=>h),"undefined"!=typeof exports&&(exports.default=h)})();
1
+ (()=>{const e="vibrate"in navigator&&!window.matchMedia("(prefers-reduced-motion: reduce)").matches;if("undefined"==typeof window)return;const t=(e,t={})=>window?.CAP_CUSTOM_FETCH?window.CAP_CUSTOM_FETCH(e,t):fetch(e,t);function i(e,t){let i=function(e){let t=2166136261;for(let i=0;i<e.length;i++)t^=e.charCodeAt(i),t+=(t<<1)+(t<<4)+(t<<7)+(t<<8)+(t<<24);return t>>>0}(e),s="";function r(){return i^=i<<13,i^=i>>>17,i^=i<<5,i>>>0}for(;s.length<t;){s+=r().toString(16).padStart(8,"0")}return s.substring(0,t)}let s=null;async function r(e){const t=(e=>{const t=atob(e),i=new Uint8Array(t.length);for(let e=0;e<t.length;e++)i[e]=t.charCodeAt(e);return i})(e),i=(new TextDecoder).decode(await async function(e){if("undefined"!=typeof DecompressionStream)try{const t=new DecompressionStream("deflate-raw"),i=t.writable.getWriter(),s=t.readable.getReader();i.write(e).then(()=>i.close()).catch(()=>{});const r=[];for(;;){const{done:e,value:t}=await s.read();if(e)break;r.push(t)}let a=0;for(const e of r)a+=e.length;const n=new Uint8Array(a);let o=0;for(const e of r)n.set(e,o),o+=e.length;return n}catch{}return s||(s=new Promise((e,t)=>{const i=window.CAP_PAKO_URL||"https://cdn.jsdelivr.net/npm/pako@2.1.0/dist/pako_inflate.min.js",r=document.createElement("script");r.src=i;const a=window.CAP_SCRIPT_NONCE||window.CAP_CSS_NONCE;a&&r.setAttribute("nonce",a),r.onload=()=>{window.pako?.inflateRaw?e(window.pako):t(new Error("[cap] pako loaded but inflateRaw is missing"))},r.onerror=()=>{s=null,t(new Error(`[cap] failed to load pako fallback from ${i}`))},document.head.appendChild(r)})),(await s).inflateRaw(e)}(t));return new Promise(e=>{var t=setTimeout(()=>{a(),e({__timeout:!0})},2e4),s=document.createElement("iframe");s.setAttribute("sandbox","allow-scripts"),s.setAttribute("aria-hidden","true"),s.style.cssText="position:absolute;width:1px;height:1px;top:-9999px;left:-9999px;border:none;opacity:0;pointer-events:none;";var r=!1;function a(){r||(r=!0,clearTimeout(t),window.removeEventListener("message",n),s.parentNode&&s.parentNode.removeChild(s))}function n(t){var i=t.data;i&&"object"==typeof i&&("cap:instr"===i.type?(a(),i.blocked?e({__blocked:!0,blockReason:i.blockReason||"automated_browser"}):i.result?e(i.result):e({__timeout:!0})):"cap:error"===i.type&&(a(),e({__timeout:!0})))}window.addEventListener("message",n);const o=window.CAP_SCRIPT_NONCE||window.CAP_CSS_NONCE,l=o?' nonce="'+String(o).replace(/"/g,"&quot;")+'"':"";s.srcdoc='<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><script'+l+">"+i+"\n<\/script></body></html>",document.body.appendChild(s)})}let a=null;const n=()=>{if(a)return a;const e=window.CAP_CUSTOM_WASM_URL||"https://cdn.jsdelivr.net/npm/@cap.js/wasm@0.0.6/browser/cap_wasm_bg.wasm";return a=fetch(e).then(e=>{if(!e.ok)throw new Error(`Failed to fetch wasm: ${e.status}`);return e.arrayBuffer()}).then(e=>WebAssembly.compile(e)).catch(e=>{throw a=null,e}),a};"object"==typeof WebAssembly&&"function"==typeof WebAssembly.compile&&n().catch(()=>{});let o=null;function l(){return o||(o=URL.createObjectURL(new Blob(['(()=>{const e=async({salt:e,target:t})=>{let n=0;const r=new TextEncoder,o=4*t.length,s=Math.floor(o/8),l=o%8,a=t.length%2==0?t:t+"0",c=a.length/2,i=new Uint8Array(c);for(let e=0;e<c;e++)i[e]=parseInt(a.substring(2*e,2*e+2),16);const f=l>0?255<<8-l&255:0;for(;;)try{for(let t=0;t<5e4;t++){const t=e+n,o=r.encode(t),a=await crypto.subtle.digest("SHA-256",o),c=new Uint8Array(a);let g=!0;for(let e=0;e<s;e++)if(c[e]!==i[e]){g=!1;break}if(g&&l>0&&(c[s]&f)!==(i[s]&f)&&(g=!1),g)return void self.postMessage({nonce:n,found:!0});n++}}catch(e){return console.error("[cap worker]",e),void self.postMessage({found:!1,error:e.message})}};if("object"!=typeof WebAssembly||"function"!=typeof WebAssembly?.instantiate)return console.warn("[cap worker] wasm not supported, falling back to alternative solver. this will be significantly slower."),void(self.onmessage=async({data:{salt:t,target:n}})=>e({salt:t,target:n}));let t=null;self.onmessage=async({data:{salt:n,target:r,wasmModule:o}})=>{if(o instanceof WebAssembly.Module&&null===t){const s=(e=>{try{let n,r=0,o=null;const s=()=>(null!==o&&0!==o.byteLength||(o=new Uint8Array(n.memory.buffer)),o),l=new TextEncoder,a=(e,t,n)=>{if(void 0===n){const n=l.encode(e),o=t(n.length,1)>>>0;return s().subarray(o,o+n.length).set(n),r=n.length,o}let o=e.length,a=t(o,1)>>>0;const c=s();let i=0;for(;i<o;i++){const t=e.charCodeAt(i);if(t>127)break;c[a+i]=t}if(i!==o){0!==i&&(e=e.slice(i)),a=n(a,o,o=i+3*e.length,1)>>>0;const t=s().subarray(a+i,a+o),{written:r}=l.encodeInto(e,t);i+=r,a=n(a,o,i,1)>>>0}return r=i,a},c={wbg:{}};c.wbg.__wbindgen_init_externref_table=()=>{const e=n.__wbindgen_export_0,t=e.grow(4);e.set(0,void 0),e.set(t+0,void 0),e.set(t+1,null),e.set(t+2,!0),e.set(t+3,!1)};const i=new WebAssembly.Instance(e,c);return n=i.exports,n.__wbindgen_start&&n.__wbindgen_start(),t=(e,t)=>{const o=a(e,n.__wbindgen_malloc,n.__wbindgen_realloc),s=r,l=a(t,n.__wbindgen_malloc,n.__wbindgen_realloc),c=r;return BigInt.asUintN(64,n.solve_pow(o,s,l,c))},!0}catch(e){return console.error("[cap worker] failed to init wasm from module:",e),!1}})(o);if(!s)return console.warn("[cap worker] wasm init failed, falling back to JS solver."),e({salt:n,target:r})}if(null===t)return console.warn("[cap worker] no wasm module provided, falling back to JS solver."),e({salt:n,target:r});try{const e=performance.now(),o=t(n,r),s=performance.now();self.postMessage({nonce:Number(o),found:!0,durationMs:(s-e).toFixed(2)})}catch(e){console.error("[cap worker]",e),self.postMessage({found:!1,error:e.message||String(e)})}},self.onerror=e=>{self.postMessage({found:!1,error:e})}})();'],{type:"application/javascript"})),o)}class c{constructor(e){this._size=e,this._workers=[],this._idle=[],this._queue=[],this._wasmModule=null,this._spawnFailures=0}setWasm(e){this._wasmModule=e}_spawn(){const e=l(),t=new Worker(e);return t._busy=!1,this._workers.push(t),this._idle.push(t),t}_replaceWorker(e){const t=this._workers.indexOf(e);-1!==t&&this._workers.splice(t,1);const i=this._idle.indexOf(e);-1!==i&&this._idle.splice(i,1);try{e.terminate()}catch{}return this._spawnFailures++,this._spawnFailures>3?(console.error("[cap] worker spawn failed repeatedly, not retrying"),null):this._spawn()}_ensureSize(e){for(;this._workers.length<e;)this._spawn()}run(e,t){return new Promise((i,s)=>{this._queue.push({salt:e,target:t,resolve:i,reject:s}),this._dispatch()})}_dispatch(){for(;this._idle.length>0&&this._queue.length>0;){const e=this._idle.shift(),{salt:t,target:i,resolve:s,reject:r}=this._queue.shift();let a=!1;const n=({data:t})=>{a||(a=!0,e.removeEventListener("message",n),e.removeEventListener("error",o),this._spawnFailures=0,this._idle.push(e),t.found?s(t.nonce):r(new Error(t.error||"worker failed")),this._dispatch())},o=t=>{if(a)return;a=!0,e.removeEventListener("message",n),e.removeEventListener("error",o);const i=this._replaceWorker(e);r(t),i&&this._dispatch()};e.addEventListener("message",n),e.addEventListener("error",o),this._wasmModule?e.postMessage({salt:t,target:i,wasmModule:this._wasmModule},[]):e.postMessage({salt:t,target:i})}}terminate(){for(const e of this._workers)try{e.terminate()}catch{}this._workers=[],this._idle=[],this._queue=[]}}class h extends HTMLElement{static formAssociated=!0;#e=null;#t=navigator.hardwareConcurrency||8;token=null;#i;#s;#r;#a;#n;#o;#l=!1;#c;#h;#d=null;#p=null;#u=null;#g=null;get#v(){return e&&!window.CAP_DISABLE_HAPTICS&&!this.hasAttribute("data-cap-disable-haptics")}#b(){return{state:"idle",challengeResp:null,challenges:null,results:[],completedCount:0,solvePromise:null,promoteFn:null,_listeners:[],pendingPromotion:null,token:null,tokenExpires:null,notify(){for(const e of this._listeners)e();this._listeners=[]},onSettled(e){"done"===this.state||"error"===this.state?e():this._listeners.push(e)}}}#m(){this.#d=this.#b(),this.#f()}#w(){this.#g&&(window.removeEventListener("mousemove",this.#g),window.removeEventListener("touchstart",this.#g),window.removeEventListener("keydown",this.#g),this.#g=null)}#f(){this.#w();const e=()=>{this.#w(),this.#k()};this.#g=e,window.addEventListener("mousemove",e,{passive:!0}),window.addEventListener("touchstart",e,{passive:!0}),window.addEventListener("keydown",e,{passive:!0})}#y(){return"function"==typeof this.checkVisibility?this.checkVisibility({checkOpacity:!0,checkVisibilityCSS:!0}):!!(this.offsetParent||this.getClientRects().length>0)}#k(){"idle"===this.#d.state&&this.#y()&&(this.#d.state="waiting",this.#p=setTimeout(()=>{this.#x()},2500))}async#x(){if("waiting"!==this.#d.state)return;this.#d.state="fetching";let e=this.getAttribute("data-cap-api-endpoint");if(!e&&window?.CAP_CUSTOM_FETCH&&(e="/"),e){e.endsWith("/")||(e+="/");try{const s=await t(`${e}challenge`,{method:"POST"});let r;try{r=await s.json()}catch{throw new Error("Failed to parse speculative challenge response")}if(r.error)throw new Error(r.error);r._apiEndpoint=e,this.#d.challengeResp=r;const{challenge:a,token:n}=r;let o=a;if(!Array.isArray(o)){let e=0;o=Array.from({length:a.c},()=>(e++,[i(`${n}${e}`,a.s),i(`${n}${e}d`,a.d)]))}this.#d.challenges=o,this.#d.state="solving",this.#d.solvePromise=this.#E(o)}catch(e){console.warn("[cap] speculative challenge fetch failed:",e),this.#d.state="error",this.#d.notify()}}else this.#d.state="idle"}async#E(e){l();let t=null;try{t=await n()}catch{}this.#u||(this.#u=new c(1),this.#u._spawn()),this.#u.setWasm(t);const i=e.length,s=new Array(i);let r=1,a=!1;this.#d.promoteFn=e=>{a||(a=!0,r=e,this.#u._size=e,this.#u._ensureSize(e))},null!==this.#d.pendingPromotion&&(this.#d.promoteFn(this.#d.pendingPromotion),this.#d.pendingPromotion=null);let o=0;for(;o<i;){const t=r,n=[],l=[];for(let s=0;s<t&&o<i;s++)l.push(o),n.push(e[o]),o++;this.#u._ensureSize(Math.max(r,t));const c=await Promise.all(n.map(e=>this.#u.run(e[0],e[1]).then(e=>(this.#d.completedCount++,e))));for(let e=0;e<l.length;e++)s[l[e]]=c[e];!a&&o<i&&await new Promise(e=>setTimeout(e,120))}return this.#d.results=s,this.#d.state="redeeming",this.#_(s),s}async#_(e){try{const i=this.#d.challengeResp,s=i._apiEndpoint;if(!s)throw new Error("[cap] speculative redeem: missing apiEndpoint");let a=null;if(i.instrumentation&&(a=await r(i.instrumentation),a?.__timeout||a?.__blocked))return this.#d.state="done",void this.#d.notify();const n=await t(`${s}redeem`,{method:"POST",body:JSON.stringify({token:i.token,solutions:e,...a&&{instr:a}}),headers:{"Content-Type":"application/json"}});let o;try{o=await n.json()}catch{throw new Error("Failed to parse speculative redeem response")}if(!o.success)throw new Error(o.error||"Speculative redeem failed");this.#d.token=o.token,this.#d.tokenExpires=new Date(o.expires).getTime(),this.#d.state="done",this.#d.notify()}catch(e){console.warn("[cap] speculative redeem failed (will redo on click):",e),this.#d.state="done",this.#d.notify()}}getI18nText(e,t){return this.getAttribute(`data-cap-i18n-${e}`)||t}static get observedAttributes(){return["onsolve","onprogress","onreset","onerror","data-cap-worker-count","data-cap-i18n-initial-state","required"]}constructor(){super(),this.#c&&this.#c.forEach((e,t)=>{this.removeEventListener(t.slice(2),e)}),this.#c=new Map,this.boundHandleProgress=this.handleProgress.bind(this),this.boundHandleSolve=this.handleSolve.bind(this),this.boundHandleError=this.handleError.bind(this),this.boundHandleReset=this.handleReset.bind(this);try{this.#h=this.attachInternals()}catch{}}#A(){this.#h?.setValidity&&(this.hasAttribute("required")&&!this.token?this.#h.setValidity({valueMissing:!0},this.getI18nText("required-label","Please verify you're human"),this.#s||this):this.#h.setValidity({}))}initialize(){l(),this.#d||(this.#d=this.#b()),this.#u||(this.#u=new c(1),this.#u._spawn())}attributeChangedCallback(e,t,i){if(e.startsWith("on")){const t=e.slice(2),s=this.#c.get(e);if(s&&this.removeEventListener(t,s),i){const i=t=>{const i=this.getAttribute(e);"function"==typeof window[i]&&window[i].call(this,t)};this.#c.set(e,i),this.addEventListener(t,i)}}"data-cap-worker-count"===e&&this.setWorkersCount(parseInt(i,10)),"data-cap-i18n-initial-state"===e&&this.#s&&this.#s?.querySelector(".label.active")&&this.animateLabel(this.getI18nText("initial-state","Verify you're human")),"required"===e&&this.#A()}async connectedCallback(){this.#o=this,this.shadowRoot?this.#i=this.shadowRoot:this.#i=this.attachShadow({mode:"open"}),this.#s||(this.#s=document.createElement("div")),this.createUI(),this.addEventListeners(),this.initialize(),this.#r.removeAttribute("disabled");const e=this.getAttribute("data-cap-worker-count"),t=e?parseInt(e,10):null;this.setWorkersCount(t||navigator.hardwareConcurrency||8);const i=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.#o.innerHTML=`<input type="hidden" name="${i}">`,this.#f(),this.#A(),this.addEventListener("invalid",this.#C)}#C=()=>{if(this.#s){try{this.scrollIntoView({behavior:"smooth",block:"center"})}catch{this.scrollIntoView()}this.#s.classList.remove("invalid"),this.#s.offsetWidth,this.#s.classList.add("invalid"),setTimeout(()=>this.#s?.classList.remove("invalid"),1500)}};async solve(){if(!this.#l)try{this.#l=!0,this.updateUI("verifying",this.getI18nText("verifying-label","Verifying..."),!0),this.#r.setAttribute("aria-label",this.getI18nText("verifying-aria-label","Verifying you're a human, please wait")),this.dispatchEvent("progress",{progress:0});try{let e,s,a=this.getAttribute("data-cap-api-endpoint");if(!a&&window?.CAP_CUSTOM_FETCH)a="/";else if(!a)throw new Error("Missing API endpoint. Either custom fetch or an API endpoint must be provided.");if(a.endsWith("/")||(a+="/"),"done"===this.#d.state&&this.#d.token&&this.#d.tokenExpires&&Date.now()<this.#d.tokenExpires){this.dispatchEvent("progress",{progress:100});const e=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${e}']`)&&(this.querySelector(`input[name='${e}']`).value=this.#d.token),this.dispatchEvent("solve",{token:this.#d.token}),this.token=this.#d.token;const t=this.#d.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),t),this.#r.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),this.#v&&navigator.vibrate([10,50,20,30,40]),this.#m(),this.#l=!1,{success:!0,token:this.token}}if("done"===this.#d.state)e=this.#d.results,s=this.#d.challengeResp,this.dispatchEvent("progress",{progress:100});else if("solving"===this.#d.state||"redeeming"===this.#d.state||"fetching"===this.#d.state||"waiting"===this.#d.state){"waiting"===this.#d.state&&(this.#p&&(clearTimeout(this.#p),this.#p=null),this.#d.state="waiting",this.#x()),this.#d.pendingPromotion=this.#t,this.#d.promoteFn&&this.#d.promoteFn(this.#t);const t=setInterval(()=>{const e=this.#d.state;if("done"===e||"error"===e)return void clearInterval(t);const i=this.#d.challenges?this.#d.challenges.length:1,s=this.#d.completedCount,r="redeeming"===e?99:"fetching"===e||"waiting"===e?0:Math.min(98,Math.round(s/i*100));this.dispatchEvent("progress",{progress:r})},150);if(await new Promise(e=>this.#d.onSettled(e)),clearInterval(t),"done"!==this.#d.state)throw new Error("Speculative solve failed – please try again");if(this.#d.token&&this.#d.tokenExpires&&Date.now()<this.#d.tokenExpires){this.dispatchEvent("progress",{progress:100});const e=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${e}']`)&&(this.querySelector(`input[name='${e}']`).value=this.#d.token),this.dispatchEvent("solve",{token:this.#d.token}),this.token=this.#d.token;const t=this.#d.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),t),this.#r.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),this.#v&&navigator.vibrate([10,50,20,30,40]),this.#m(),this.#l=!1,{success:!0,token:this.token}}e=this.#d.results,s=this.#d.challengeResp,this.dispatchEvent("progress",{progress:100})}else{const r=await t(`${a}challenge`,{method:"POST"});try{s=await r.json()}catch{throw new Error("Failed to parse challenge response from server")}if(s.error)throw new Error(s.error);const{challenge:n,token:o}=s;let l=n;if(!Array.isArray(l)){let e=0;l=Array.from({length:n.c},()=>(e++,[i(`${o}${e}`,n.s),i(`${o}${e}d`,n.d)]))}e=await this.solveChallenges(l)}const n=s.instrumentation?r(s.instrumentation):Promise.resolve(null),o=await n;if(o?.__timeout||o?.__blocked){this.updateUIBlocked(this.getI18nText("error-label","Error"),o?.__blocked),this.#r.setAttribute("aria-label",this.getI18nText("error-aria-label","An error occurred, please try again")),this.removeEventListener("error",this.boundHandleError);const e=new CustomEvent("error",{bubbles:!0,composed:!0,detail:{isCap:!0,message:"Instrumentation failed"}});return super.dispatchEvent(e),this.addEventListener("error",this.boundHandleError),this.executeAttributeCode("onerror",e),console.error("[cap]","Instrumentation failed"),void(this.#l=!1)}const{token:l}=s,c=await t(`${a}redeem`,{method:"POST",body:JSON.stringify({token:l,solutions:e,...o&&{instr:o}}),headers:{"Content-Type":"application/json"}});let h;try{h=await c.json()}catch{throw new Error("Failed to parse server response")}if(this.dispatchEvent("progress",{progress:100}),!h.success)throw new Error(h.error||"Invalid solution");const d=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${d}']`)&&(this.querySelector(`input[name='${d}']`).value=h.token),this.dispatchEvent("solve",{token:h.token}),this.token=h.token,this.#m(),this.#e&&clearTimeout(this.#e);const p=new Date(h.expires).getTime()-Date.now();return p>0&&p<864e5?this.#e=setTimeout(()=>this.reset(),p):this.error("Invalid expiration time"),this.#r.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),this.#v&&navigator.vibrate([10,50,20,30,40]),{success:!0,token:this.token}}catch(e){throw this.#r.setAttribute("aria-label",this.getI18nText("error-aria-label","An error occurred, please try again")),this.error(e.message),e}}finally{this.#l=!1}}async solveChallenges(e){const t=e.length;let i=0;let s=null;const r="object"==typeof WebAssembly&&"function"==typeof WebAssembly.instantiate;if(r)try{s=await n()}catch(e){console.warn("[cap] wasm unavailable, falling back to JS solver:",e)}if(!r&&!this.#i.querySelector(".warning")){const e=document.createElement("div");e.className="warning",e.style.cssText="width:var(--cap-widget-width,230px);background:rgb(237,56,46);color:white;padding:4px 6px;padding-bottom:calc(var(--cap-border-radius,14px) + 5px);font-size:10px;box-sizing:border-box;font-family:system-ui;border-top-left-radius:8px;border-top-right-radius:8px;text-align:center;user-select:none;margin-bottom:-35.5px;opacity:0;transition:margin-bottom .3s,opacity .3s;",e.innerText=this.getI18nText("wasm-disabled","Enable WASM for significantly faster solving"),this.#i.insertBefore(e,this.#i.firstChild),setTimeout(()=>{e.style.marginBottom="calc(-1 * var(--cap-border-radius, 14px))",e.style.opacity=1},10)}const a=new c(this.#t);a.setWasm(s),a._ensureSize(this.#t);const o=[];try{for(let s=0;s<e.length;s+=this.#t){const r=e.slice(s,Math.min(s+this.#t,e.length)),n=await Promise.all(r.map(([e,s])=>a.run(e,s).then(e=>{i++;const s=Math.min(99,Math.round((0+i)/t*100));return this.dispatchEvent("progress",{progress:s}),e})));o.push(...n)}}finally{a.terminate()}return o}setWorkersCount(e){const t=parseInt(e,10),i=Math.min(navigator.hardwareConcurrency||8,16);this.#t=!Number.isNaN(t)&&t>0&&t<=i?t:navigator.hardwareConcurrency||8}createUI(){this.#s.classList.add("captcha"),this.#s.setAttribute("role","group"),this.#s.setAttribute("aria-label",this.getI18nText("group-aria-label","Cap verification")),this.#r=document.createElement("div"),this.#r.className="captcha-trigger",this.#r.setAttribute("part","trigger"),this.#r.setAttribute("role","button"),this.#r.setAttribute("tabindex","0"),this.#r.setAttribute("aria-label",this.getI18nText("verify-aria-label","Click to verify you're a human")),this.#r.setAttribute("aria-live","polite"),this.#r.setAttribute("disabled","true"),this.#r.innerHTML=`<div class="checkbox" part="checkbox" aria-hidden="true"><svg class="progress-ring" viewBox="0 0 32 32" aria-hidden="true"><circle class="progress-ring-bg" cx="16" cy="16" r="14"></circle><circle class="progress-ring-circle" cx="16" cy="16" r="14"></circle></svg></div><p part="label" class="label-wrapper"><span class="label active">${this.getI18nText("initial-state","Verify you're human")}</span></p>`,this.#s.appendChild(this.#r),this.#n=document.createElement("a"),this.#n.className="cap-troubleshoot-link",this.#n.setAttribute("part","troubleshoot"),this.#n.setAttribute("target","_blank"),this.#n.setAttribute("rel","noopener"),this.#n.hidden=!0,this.#s.appendChild(this.#n),this.#a=document.createElement("a"),this.#a.className="credits",this.#a.setAttribute("part","attribution"),this.#a.setAttribute("aria-label","Secured by Cap"),this.#a.setAttribute("href","https://trycap.dev"),this.#a.setAttribute("target","_blank"),this.#a.setAttribute("title","Secured by Cap: The self-hosted CAPTCHA for the modern web."),this.#a.textContent="Cap",this.#s.appendChild(this.#a),this.#i.innerHTML=`<style${window.CAP_CSS_NONCE?` nonce=${window.CAP_CSS_NONCE}`:""}>:host{display:inline-block}@media (prefers-reduced-motion:reduce){.captcha,.captcha *{transition:none!important;animation:none!important}.label{filter:none!important;opacity:1!important;transition:none!important;transform:none!important}.label:not(.active),.label.exit{display:none!important}}.captcha,.captcha *{box-sizing:border-box}.captcha{width:var(--cap-widget-width,260px);height:var(--cap-widget-height,58px);background-color:var(--cap-background,#fdfdfd);border:1px solid var(--cap-border-color,#dddddd8f);border-radius:var(--cap-border-radius,14px);-webkit-user-select:none;user-select:none;cursor:pointer;-webkit-tap-highlight-color:#fff0;color:var(--cap-color,#212121);font-family:var(--cap-font,system, -apple-system, "BlinkMacSystemFont", ".SFNSText-Regular", "San Francisco", "Roboto", "Segoe UI", "Helvetica Neue", "Lucida Grande", "Ubuntu", "arial", sans-serif);transition:filter .2s,transform .2s,height .2s;display:inline-block;position:relative;overflow:hidden}.captcha.has-troubleshoot{height:calc(var(--cap-widget-height,58px) + 26px)}.captcha.has-troubleshoot .captcha-trigger{inset-block-end:26px}.captcha:not([data-state]):active{transform:scale(.97)}.captcha:hover{filter:brightness(98%)}.captcha-trigger{padding:var(--cap-widget-padding,14px);align-items:center;gap:var(--cap-gap,15px);border-radius:inherit;transition:inset .2s;display:flex;position:absolute;inset:0}.captcha-trigger:focus-visible{outline:2px solid var(--cap-focus-ring,#06c);outline-offset:-3px}.checkbox{width:var(--cap-checkbox-size,25px);height:var(--cap-checkbox-size,25px);border:var(--cap-checkbox-border,1px solid #aaaaaad1);border-radius:var(--cap-checkbox-border-radius,6px);background-color:var(--cap-checkbox-background,#fafafa91);margin-top:var(--cap-checkbox-margin,2px);margin-bottom:var(--cap-checkbox-margin,2px);flex-shrink:0;transition:opacity .2s}.captcha p{-webkit-user-select:none;user-select:none;margin:0;font-size:15px;font-weight:500}.label-wrapper{flex:1;align-items:center;min-width:0;height:2em;display:flex;position:relative;overflow:hidden}.label-wrapper:before,.label-wrapper:after{content:"";pointer-events:none;z-index:1;width:100%;height:.6em;position:absolute;inset-inline-start:0}.label-wrapper:before{background:linear-gradient(to bottom, var(--cap-background,#fdfdfd), transparent);top:0}.label-wrapper:after{background:linear-gradient(to top, var(--cap-background,#fdfdfd), transparent);bottom:0}.label{white-space:nowrap;opacity:0;filter:blur(2px);transition:transform .5s cubic-bezier(.25,1,.5,1),opacity .5s,filter .5s;position:absolute;transform:translateY(100%)}.label.active{opacity:1;filter:none;transform:translateY(0)}.label.exit{opacity:0;filter:blur(2px);transform:translateY(-100%)}.checkbox .progress-ring{width:100%;height:100%;display:none;transform:rotate(-90deg)}.checkbox .progress-ring-bg{fill:none;stroke:var(--cap-spinner-background-color,#eee);stroke-width:var(--cap-spinner-thickness,3)}.checkbox .progress-ring-circle{fill:none;stroke:var(--cap-spinner-color,#000);stroke-width:var(--cap-spinner-thickness,3);stroke-linecap:round;stroke-dasharray:87.96;stroke-dashoffset:87.96px;transition:stroke-dashoffset .3s}.captcha[data-state=verifying] .checkbox{background:0 0;border:none;border-radius:50%;justify-content:center;align-items:center;display:flex;transform:scale(1.1)}.captcha[data-state=verifying] .checkbox .progress-ring{display:block}.captcha[data-state=done] .checkbox{background-image:var(--cap-checkmark,url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2224%22%20height%3D%2224%22%20viewBox%3D%220%200%2024%2024%22%3E%3Cstyle%3E%40keyframes%20anim%7B0%25%7Bstroke-dashoffset%3A23.21320343017578px%7Dto%7Bstroke-dashoffset%3A0%7D%7D%3C%2Fstyle%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%2300a67d%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m5%2012%205%205L20%207%22%20style%3D%22stroke-dashoffset%3A0%3Bstroke-dasharray%3A23.21320343017578px%3Banimation%3Aanim%20.5s%20ease%22%2F%3E%3C%2Fsvg%3E));background-size:cover;border:1px solid #0000}.captcha[data-state=done] .checkbox .progress-ring{display:none}.captcha[data-state=error] .checkbox{background-image:var(--cap-error-cross,url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='96' height='96' viewBox='0 0 24 24'%3E%3Cpath fill='%23f55b50' d='M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8'/%3E%3C%2Fsvg%3E"));background-size:cover;border:1px solid #0000}.captcha[data-state=error] .checkbox .progress-ring{display:none}.captcha.invalid{border-color:var(--cap-invalid-border-color,#f55b50);box-shadow:0 0 0 3px var(--cap-invalid-ring-color,#f55b5033);animation:.5s cubic-bezier(.36,.07,.19,.97) cap-shake}@keyframes cap-shake{10%,90%{transform:translate(-1px)}20%,80%{transform:translate(2px)}30%,50%,70%{transform:translate(-4px)}40%,60%{transform:translate(4px)}}@media (prefers-reduced-motion:reduce){.captcha.invalid{animation:none!important}}.captcha-trigger[disabled]{cursor:not-allowed}.captcha[data-state=verifying] .captcha-trigger{cursor:progress}.captcha[data-state=done] .captcha-trigger{cursor:default}.credits{color:var(--cap-color,#212121);text-underline-offset:.18em;z-index:2;border-radius:4px;justify-content:flex-end;align-items:center;min-width:24px;min-height:24px;padding:4px 6px;text-decoration:underline;position:absolute;inset-block-end:4px;inset-inline-end:4px;opacity:.8!important;font-size:12px!important;display:inline-flex!important}.credits:focus-visible{outline:2px solid var(--cap-focus-ring,#06c);outline-offset:2px;opacity:1!important}.cap-troubleshoot-link{min-width:24px;min-height:24px;color:var(--cap-troubleshoot-color,#0050a0);text-underline-offset:.15em;cursor:pointer;z-index:2;border-radius:4px;align-items:center;padding:4px 6px;font-size:13px;font-weight:500;text-decoration:underline;text-decoration-thickness:.08em;display:inline-flex;position:absolute;inset-block-end:4px;inset-inline-start:14px}.cap-troubleshoot-link[hidden]{display:none}.cap-troubleshoot-link:hover{opacity:.85}.cap-troubleshoot-link:focus-visible{outline:2px solid var(--cap-focus-ring,#06c);outline-offset:2px}@media (forced-colors:active){.captcha,.checkbox{border-color:canvastext}:is(.captcha-trigger:focus-visible,.credits:focus-visible,.cap-troubleshoot-link:focus-visible){outline-color:highlight}.progress-ring-bg{stroke:graytext}.progress-ring-circle{stroke:canvastext}.captcha.invalid{box-shadow:none;border-color:mark}.credits{opacity:1!important}}</style>`,this.#i.appendChild(this.#s)}addEventListeners(){this.#r&&(this.#a.addEventListener("click",e=>{e.preventDefault(),window.open(`https://trycap.dev/?${new URLSearchParams({utm_source:"cap_widget",utm_medium:"referral",utm_campaign:"widget",utm_content:window.CAP_DISABLE_WIDGET_REF?"":location.hostname,ref:window.CAP_DISABLE_WIDGET_REF?"":location.href||"",sub:window.CAP_DISABLE_WIDGET_REF?"":document.referrer||""}).toString()}`,"_blank")}),this.#r.addEventListener("click",()=>{this.#r.hasAttribute("disabled")||this.solve()}),this.#r.addEventListener("mousedown",()=>{!this.#r.hasAttribute("disabled")&&this.#v&&navigator.vibrate(5)}),this.#r.addEventListener("keydown",e=>{e.target===this.#r&&("Enter"!==e.key&&" "!==e.key||this.#r.hasAttribute("disabled")||(e.preventDefault(),e.stopPropagation(),this.solve()))}),this.addEventListener("progress",this.boundHandleProgress),this.addEventListener("solve",this.boundHandleSolve),this.addEventListener("error",this.boundHandleError),this.addEventListener("reset",this.boundHandleReset))}animateLabel(e){if(!this.#r)return;const t=this.#r.querySelector(".label-wrapper");if(!t)return;if(window.matchMedia?.("(prefers-reduced-motion: reduce)").matches){const i=t.querySelector(".label.active");if(i)i.textContent=e;else{const i=document.createElement("span");i.className="label active",i.textContent=e,t.appendChild(i)}return}const i=t.querySelector(".label.active"),s=document.createElement("span");s.className="label",s.textContent=e,t.appendChild(s),s.offsetWidth,s.classList.add("active"),i&&(i.classList.remove("active"),i.classList.add("exit"),i.addEventListener("transitionend",()=>i.remove(),{once:!0}))}updateUI(e,t,i=!1){this.#s&&this.#r&&(this.#s.setAttribute("data-state",e),this.#s.classList.remove("has-troubleshoot"),this.animateLabel(t),this.#n&&(this.#n.hidden=!0),i?this.#r.setAttribute("disabled","true"):this.#r.removeAttribute("disabled"))}updateUIBlocked(e,t=!1){if(this.#s&&this.#r&&(this.#s.setAttribute("data-state","error"),this.#r.removeAttribute("disabled"),this.animateLabel(e),this.#n))if(t){const e=this.getAttribute("data-cap-troubleshooting-url")||"https://trycap.dev/guide/troubleshooting/instrumentation.html";this.#n.setAttribute("href",e),this.#n.textContent=this.getI18nText("troubleshooting-label","Troubleshoot"),this.#n.hidden=!1,this.#s.classList.add("has-troubleshoot")}else this.#n.hidden=!0,this.#s.classList.remove("has-troubleshoot")}handleProgress(e){if(!this.#r)return;const t=this.#r.querySelector(".progress-ring-circle");if(t){const i=2*Math.PI*14,s=i-e.detail.progress/100*i;t.style.strokeDashoffset=s}const i=this.#r.querySelector(".label-wrapper");if(i){const e=i.querySelector(".label.active");e&&(e.textContent=`${this.getI18nText("verifying-label","Verifying...")}`)}this.executeAttributeCode("onprogress",e)}handleSolve(e){this.updateUI("done",this.getI18nText("solved-label","You're a human"),!0),this.executeAttributeCode("onsolve",e),this.#h?.setValidity?.({}),this.#s?.classList.remove("invalid")}handleError(e){this.updateUI("error",this.getI18nText("error-label","Error. Try again.")),this.executeAttributeCode("onerror",e),this.#v&&navigator.vibrate([10,40,10])}handleReset(e){this.updateUI("",this.getI18nText("initial-state","I'm a human")),this.executeAttributeCode("onreset",e),this.#A()}executeAttributeCode(e,t){const i=this.getAttribute(e);i&&(console.error("[cap] using `onxxx='…'` is strongly discouraged and will be deprecated soon. please use `addEventListener` callbacks instead."),new Function("event",i).call(this,t))}error(e="Unknown error"){console.error("[cap]",e),this.dispatchEvent("error",{isCap:!0,message:e})}dispatchEvent(e,t={}){const i=new CustomEvent(e,{bubbles:!0,composed:!0,detail:t});super.dispatchEvent(i)}reset(){this.#e&&(clearTimeout(this.#e),this.#e=null),this.token=null,this.dispatchEvent("reset");const e=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${e}']`)&&(this.querySelector(`input[name='${e}']`).value="")}get tokenValue(){return this.token}disconnectedCallback(){this.removeEventListener("progress",this.boundHandleProgress),this.removeEventListener("solve",this.boundHandleSolve),this.removeEventListener("error",this.boundHandleError),this.removeEventListener("reset",this.boundHandleReset),this.#c.forEach((e,t)=>{this.removeEventListener(t.slice(2),e)}),this.#c.clear(),this.#i&&(this.#i.innerHTML=""),this.reset(),this.cleanup()}cleanup(){this.#e&&(clearTimeout(this.#e),this.#e=null),this.#w(),this.#p&&(clearTimeout(this.#p),this.#p=null),this.#u&&(this.#u.terminate(),this.#u=null),this.#d&&(this.#d.state="error",this.#d.notify(),this.#d=null)}}class d{constructor(e={},t){const i=t||document.createElement("cap-widget");if(Object.entries(e).forEach(([e,t])=>{i.setAttribute(e,t)}),!e.apiEndpoint&&!window?.CAP_CUSTOM_FETCH)throw i.remove(),new Error("Missing API endpoint. Either custom fetch or an API endpoint must be provided.");e.apiEndpoint&&i.setAttribute("data-cap-api-endpoint",e.apiEndpoint),t||i.hasAttribute("data-cap-disable-haptics")||i.setAttribute("data-cap-disable-haptics",""),this.widget=i,this.solve=this.widget.solve.bind(this.widget),this.reset=this.widget.reset.bind(this.widget),this.addEventListener=this.widget.addEventListener.bind(this.widget),Object.defineProperty(this,"token",{get:()=>i.token,configurable:!0,enumerable:!0}),t||(i.style.display="none",document.documentElement.appendChild(i))}}window.Cap=d,customElements.get("cap-widget")||window?.CAP_DONT_SKIP_REDEFINE?customElements.get("cap-widget")&&console.warn("[cap] the cap-widget element has already been defined, skipping re-defining it.\nto prevent this, set window.CAP_DONT_SKIP_REDEFINE to true"):customElements.define("cap-widget",h),"object"==typeof exports&&"undefined"!=typeof module?module.exports=d:"function"==typeof define&&define.amd&&define([],()=>d),"undefined"!=typeof exports&&(exports.default=d)})();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "cap-widget",
3
- "version": "0.1.47",
4
- "description": "Client-side widget for Cap, a lightweight, modern open-source CAPTCHA alternative designed using SHA-256 PoW.",
3
+ "version": "0.1.49",
4
+ "description": "The self-hosted CAPTCHA for the modern web.",
5
5
  "keywords": [
6
6
  "algorithm",
7
7
  "anti-abuse",
package/src/cap.css CHANGED
@@ -1,3 +1,7 @@
1
+ :host {
2
+ display: inline-block;
3
+ }
4
+
1
5
  @media (prefers-reduced-motion: reduce) {
2
6
  .captcha,
3
7
  .captcha * {
@@ -27,35 +31,22 @@
27
31
  }
28
32
 
29
33
  .captcha {
34
+ position: relative;
35
+ display: inline-block;
36
+ width: var(--cap-widget-width, 260px);
37
+ height: var(--cap-widget-height, 58px);
30
38
  background-color: var(--cap-background, #fdfdfd);
31
39
  border: 1px solid var(--cap-border-color, #dddddd8f);
32
40
  border-radius: var(--cap-border-radius, 14px);
33
41
  user-select: none;
34
- height: var(--cap-widget-height, 58px);
35
- width: var(--cap-widget-width, 260px);
36
- display: flex;
37
- align-items: center;
38
- padding: var(--cap-widget-padding, 14px);
39
- gap: var(--cap-gap, 15px);
40
42
  cursor: pointer;
41
43
  transition:
42
44
  filter 0.2s,
43
- transform 0.2s;
44
- position: relative;
45
+ transform 0.2s,
46
+ height 0.2s;
45
47
  -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
46
48
  overflow: hidden;
47
49
  color: var(--cap-color, #212121);
48
- }
49
-
50
- .captcha:not([data-state]):active {
51
- transform: scale(0.97);
52
- }
53
-
54
- .captcha:hover {
55
- filter: brightness(98%);
56
- }
57
-
58
- .captcha * {
59
50
  font-family: var(
60
51
  --cap-font,
61
52
  system,
@@ -73,6 +64,38 @@
73
64
  );
74
65
  }
75
66
 
67
+ .captcha.has-troubleshoot {
68
+ height: calc(var(--cap-widget-height, 58px) + 26px);
69
+ }
70
+
71
+ .captcha.has-troubleshoot .captcha-trigger {
72
+ inset-block-end: 26px;
73
+ }
74
+
75
+ .captcha:not([data-state]):active {
76
+ transform: scale(0.97);
77
+ }
78
+
79
+ .captcha:hover {
80
+ filter: brightness(98%);
81
+ }
82
+
83
+ .captcha-trigger {
84
+ position: absolute;
85
+ inset: 0;
86
+ display: flex;
87
+ align-items: center;
88
+ padding: var(--cap-widget-padding, 14px);
89
+ gap: var(--cap-gap, 15px);
90
+ border-radius: inherit;
91
+ transition: inset 0.2s;
92
+ }
93
+
94
+ .captcha-trigger:focus-visible {
95
+ outline: 2px solid var(--cap-focus-ring, #0066cc);
96
+ outline-offset: -3px;
97
+ }
98
+
76
99
  .checkbox {
77
100
  width: var(--cap-checkbox-size, 25px);
78
101
  height: var(--cap-checkbox-size, 25px);
@@ -82,6 +105,7 @@
82
105
  transition: opacity 0.2s;
83
106
  margin-top: var(--cap-checkbox-margin, 2px);
84
107
  margin-bottom: var(--cap-checkbox-margin, 2px);
108
+ flex-shrink: 0;
85
109
  }
86
110
 
87
111
  .captcha p {
@@ -106,7 +130,7 @@
106
130
  content: "";
107
131
  pointer-events: none;
108
132
  position: absolute;
109
- left: 0;
133
+ inset-inline-start: 0;
110
134
  width: 100%;
111
135
  height: 0.6em;
112
136
  z-index: 1;
@@ -229,40 +253,100 @@
229
253
  }
230
254
  }
231
255
 
232
- .captcha[disabled] {
256
+ .captcha-trigger[disabled] {
233
257
  cursor: not-allowed;
234
258
  }
235
259
 
236
- .captcha[disabled][data-state="verifying"] {
260
+ .captcha[data-state="verifying"] .captcha-trigger {
237
261
  cursor: progress;
238
262
  }
239
263
 
240
- .captcha[disabled][data-state="done"] {
264
+ .captcha[data-state="done"] .captcha-trigger {
241
265
  cursor: default;
242
266
  }
243
267
 
244
- .captcha .credits {
268
+ .credits {
245
269
  position: absolute;
246
- bottom: 10px;
247
- right: 10px;
270
+ inset-block-end: 4px;
271
+ inset-inline-end: 4px;
248
272
  font-size: 12px !important;
249
273
  color: var(--cap-color, #212121);
250
274
  opacity: 0.8 !important;
251
275
  text-underline-offset: 0.18em;
252
276
  text-decoration-thickness: 0.08em;
253
- z-index: 6;
254
- display: block !important;
277
+ z-index: 2;
278
+ min-width: 24px;
279
+ min-height: 24px;
280
+ padding: 4px 6px;
281
+ display: inline-flex !important;
282
+ align-items: center;
283
+ justify-content: flex-end;
284
+ border-radius: 4px;
285
+ text-decoration: underline;
286
+ }
287
+
288
+ .credits:focus-visible {
289
+ outline: 2px solid var(--cap-focus-ring, #0066cc);
290
+ outline-offset: 2px;
291
+ opacity: 1 !important;
255
292
  }
256
293
 
257
294
  .cap-troubleshoot-link {
258
- color: #0066cc;
295
+ position: absolute;
296
+ inset-block-end: 4px;
297
+ inset-inline-start: 14px;
298
+ display: inline-flex;
299
+ align-items: center;
300
+ padding: 4px 6px;
301
+ min-width: 24px;
302
+ min-height: 24px;
303
+ color: var(--cap-troubleshoot-color, #0050a0);
259
304
  text-decoration: underline;
260
305
  text-underline-offset: 0.15em;
261
306
  text-decoration-thickness: 0.08em;
262
307
  cursor: pointer;
263
308
  font-weight: 500;
264
- font-size: inherit;
309
+ font-size: 13px;
310
+ border-radius: 4px;
311
+ z-index: 2;
265
312
  }
313
+
314
+ .cap-troubleshoot-link[hidden] {
315
+ display: none;
316
+ }
317
+
266
318
  .cap-troubleshoot-link:hover {
267
- opacity: 0.8;
319
+ opacity: 0.85;
320
+ }
321
+
322
+ .cap-troubleshoot-link:focus-visible {
323
+ outline: 2px solid var(--cap-focus-ring, #0066cc);
324
+ outline-offset: 2px;
325
+ }
326
+
327
+ @media (forced-colors: active) {
328
+ .captcha {
329
+ border-color: CanvasText;
330
+ }
331
+ .checkbox {
332
+ border-color: CanvasText;
333
+ }
334
+ .captcha-trigger:focus-visible,
335
+ .credits:focus-visible,
336
+ .cap-troubleshoot-link:focus-visible {
337
+ outline-color: Highlight;
338
+ }
339
+ .progress-ring-bg {
340
+ stroke: GrayText;
341
+ }
342
+ .progress-ring-circle {
343
+ stroke: CanvasText;
344
+ }
345
+ .captcha.invalid {
346
+ border-color: Mark;
347
+ box-shadow: none;
348
+ }
349
+ .credits {
350
+ opacity: 1 !important;
351
+ }
268
352
  }
package/src/cap.js CHANGED
@@ -43,6 +43,58 @@
43
43
  return result.substring(0, length);
44
44
  }
45
45
 
46
+ let _pakoPromise = null;
47
+ async function _inflateRaw(compressed) {
48
+ if (typeof DecompressionStream !== "undefined") {
49
+ try {
50
+ const ds = new DecompressionStream("deflate-raw");
51
+ const writer = ds.writable.getWriter();
52
+ const reader = ds.readable.getReader();
53
+ writer
54
+ .write(compressed)
55
+ .then(() => writer.close())
56
+ .catch(() => {});
57
+ const chunks = [];
58
+ while (true) {
59
+ const { done, value } = await reader.read();
60
+ if (done) break;
61
+ chunks.push(value);
62
+ }
63
+ let len = 0;
64
+ for (const c of chunks) len += c.length;
65
+ const out = new Uint8Array(len);
66
+ let off = 0;
67
+ for (const c of chunks) {
68
+ out.set(c, off);
69
+ off += c.length;
70
+ }
71
+ return out;
72
+ } catch {}
73
+ }
74
+ if (!_pakoPromise) {
75
+ _pakoPromise = new Promise((resolve, reject) => {
76
+ const url =
77
+ window.CAP_PAKO_URL ||
78
+ "https://cdn.jsdelivr.net/npm/pako@2.1.0/dist/pako_inflate.min.js";
79
+ const script = document.createElement("script");
80
+ script.src = url;
81
+ const pakoNonce = window.CAP_SCRIPT_NONCE || window.CAP_CSS_NONCE;
82
+ if (pakoNonce) script.setAttribute("nonce", pakoNonce);
83
+ script.onload = () => {
84
+ if (window.pako?.inflateRaw) resolve(window.pako);
85
+ else reject(new Error("[cap] pako loaded but inflateRaw is missing"));
86
+ };
87
+ script.onerror = () => {
88
+ _pakoPromise = null;
89
+ reject(new Error(`[cap] failed to load pako fallback from ${url}`));
90
+ };
91
+ document.head.appendChild(script);
92
+ });
93
+ }
94
+ const pako = await _pakoPromise;
95
+ return pako.inflateRaw(compressed);
96
+ }
97
+
46
98
  async function runInstrumentationChallenge(instrBytes) {
47
99
  const b64ToUint8 = (b64) => {
48
100
  const bin = atob(b64);
@@ -51,41 +103,8 @@
51
103
  return arr;
52
104
  };
53
105
 
54
- var compressed = b64ToUint8(instrBytes);
55
-
56
- const scriptText = await new Promise((resolve, reject) => {
57
- try {
58
- var ds = new DecompressionStream("deflate-raw");
59
- var writer = ds.writable.getWriter();
60
- var reader = ds.readable.getReader();
61
- var chunks = [];
62
- function pump(res) {
63
- if (res.done) {
64
- var len = 0,
65
- off = 0;
66
- for (var i = 0; i < chunks.length; i++) len += chunks[i].length;
67
- var out = new Uint8Array(len);
68
- for (var i = 0; i < chunks.length; i++) {
69
- out.set(chunks[i], off);
70
- off += chunks[i].length;
71
- }
72
- resolve(new TextDecoder().decode(out));
73
- } else {
74
- chunks.push(res.value);
75
- reader.read().then(pump).catch(reject);
76
- }
77
- }
78
- reader.read().then(pump).catch(reject);
79
- writer
80
- .write(compressed)
81
- .then(() => {
82
- writer.close();
83
- })
84
- .catch(reject);
85
- } catch (e) {
86
- reject(e);
87
- }
88
- });
106
+ const compressed = b64ToUint8(instrBytes);
107
+ const scriptText = new TextDecoder().decode(await _inflateRaw(compressed));
89
108
 
90
109
  return new Promise((resolve) => {
91
110
  var timeout = setTimeout(() => {
@@ -131,8 +150,14 @@
131
150
 
132
151
  window.addEventListener("message", handler);
133
152
 
153
+ const scriptNonce = window.CAP_SCRIPT_NONCE || window.CAP_CSS_NONCE;
154
+ const nonceAttr = scriptNonce
155
+ ? ' nonce="' + String(scriptNonce).replace(/"/g, "&quot;") + '"'
156
+ : "";
134
157
  iframe.srcdoc =
135
- '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><script>' +
158
+ '<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><script' +
159
+ nonceAttr +
160
+ ">" +
136
161
  scriptText +
137
162
  "\n</scr" +
138
163
  "ipt></body></html>";
@@ -303,6 +328,9 @@
303
328
  token = null;
304
329
  #shadow;
305
330
  #div;
331
+ #trigger;
332
+ #credits;
333
+ #troubleshootLink;
306
334
  #host;
307
335
  #solving = false;
308
336
  #eventHandlers;
@@ -667,7 +695,7 @@
667
695
  this.createUI();
668
696
  this.addEventListeners();
669
697
  this.initialize();
670
- this.#div.removeAttribute("disabled");
698
+ this.#trigger.removeAttribute("disabled");
671
699
 
672
700
  const workers = this.getAttribute("data-cap-worker-count");
673
701
  const parsedWorkers = workers ? parseInt(workers, 10) : null;
@@ -702,7 +730,7 @@
702
730
  try {
703
731
  this.#solving = true;
704
732
  this.updateUI("verifying", this.getI18nText("verifying-label", "Verifying..."), true);
705
- this.#div.setAttribute(
733
+ this.#trigger.setAttribute(
706
734
  "aria-label",
707
735
  this.getI18nText("verifying-aria-label", "Verifying you're a human, please wait"),
708
736
  );
@@ -741,7 +769,7 @@
741
769
  if (this.#resetTimer) clearTimeout(this.#resetTimer);
742
770
  this.#resetTimer = setTimeout(() => this.reset(), expiresIn);
743
771
 
744
- this.#div.setAttribute(
772
+ this.#trigger.setAttribute(
745
773
  "aria-label",
746
774
  this.getI18nText(
747
775
  "verified-aria-label",
@@ -821,7 +849,7 @@
821
849
  if (this.#resetTimer) clearTimeout(this.#resetTimer);
822
850
  this.#resetTimer = setTimeout(() => this.reset(), expiresIn);
823
851
 
824
- this.#div.setAttribute(
852
+ this.#trigger.setAttribute(
825
853
  "aria-label",
826
854
  this.getI18nText(
827
855
  "verified-aria-label",
@@ -870,7 +898,7 @@
870
898
 
871
899
  if (instrOut?.__timeout || instrOut?.__blocked) {
872
900
  this.updateUIBlocked(this.getI18nText("error-label", "Error"), instrOut?.__blocked);
873
- this.#div.setAttribute(
901
+ this.#trigger.setAttribute(
874
902
  "aria-label",
875
903
  this.getI18nText("error-aria-label", "An error occurred, please try again"),
876
904
  );
@@ -928,7 +956,7 @@
928
956
  this.error("Invalid expiration time");
929
957
  }
930
958
 
931
- this.#div.setAttribute(
959
+ this.#trigger.setAttribute(
932
960
  "aria-label",
933
961
  this.getI18nText(
934
962
  "verified-aria-label",
@@ -939,7 +967,7 @@
939
967
 
940
968
  return { success: true, token: this.token };
941
969
  } catch (err) {
942
- this.#div.setAttribute(
970
+ this.#trigger.setAttribute(
943
971
  "aria-label",
944
972
  this.getI18nText("error-aria-label", "An error occurred, please try again"),
945
973
  );
@@ -1027,18 +1055,49 @@
1027
1055
 
1028
1056
  createUI() {
1029
1057
  this.#div.classList.add("captcha");
1030
- this.#div.setAttribute("role", "button");
1031
- this.#div.setAttribute("tabindex", "0");
1058
+ this.#div.setAttribute("role", "group");
1032
1059
  this.#div.setAttribute(
1060
+ "aria-label",
1061
+ this.getI18nText("group-aria-label", "Cap verification"),
1062
+ );
1063
+
1064
+ this.#trigger = document.createElement("div");
1065
+ this.#trigger.className = "captcha-trigger";
1066
+ this.#trigger.setAttribute("part", "trigger");
1067
+ this.#trigger.setAttribute("role", "button");
1068
+ this.#trigger.setAttribute("tabindex", "0");
1069
+ this.#trigger.setAttribute(
1033
1070
  "aria-label",
1034
1071
  this.getI18nText("verify-aria-label", "Click to verify you're a human"),
1035
1072
  );
1036
- this.#div.setAttribute("aria-live", "polite");
1037
- this.#div.setAttribute("disabled", "true");
1038
- this.#div.innerHTML = `<div class="checkbox" part="checkbox"><svg class="progress-ring" viewBox="0 0 32 32"><circle class="progress-ring-bg" cx="16" cy="16" r="14"></circle><circle class="progress-ring-circle" cx="16" cy="16" r="14"></circle></svg></div><p part="label" class="label-wrapper"><span class="label active">${this.getI18nText(
1073
+ this.#trigger.setAttribute("aria-live", "polite");
1074
+ this.#trigger.setAttribute("disabled", "true");
1075
+ this.#trigger.innerHTML = `<div class="checkbox" part="checkbox" aria-hidden="true"><svg class="progress-ring" viewBox="0 0 32 32" aria-hidden="true"><circle class="progress-ring-bg" cx="16" cy="16" r="14"></circle><circle class="progress-ring-circle" cx="16" cy="16" r="14"></circle></svg></div><p part="label" class="label-wrapper"><span class="label active">${this.getI18nText(
1039
1076
  "initial-state",
1040
1077
  "Verify you're human",
1041
- )}</span></p><a part="attribution" aria-label="Secured by Cap" href="https://trycap.dev" class="credits" target="_blank" title="Secured by Cap: The self-hosted CAPTCHA for the modern web.">Cap</a>`;
1078
+ )}</span></p>`;
1079
+ this.#div.appendChild(this.#trigger);
1080
+
1081
+ this.#troubleshootLink = document.createElement("a");
1082
+ this.#troubleshootLink.className = "cap-troubleshoot-link";
1083
+ this.#troubleshootLink.setAttribute("part", "troubleshoot");
1084
+ this.#troubleshootLink.setAttribute("target", "_blank");
1085
+ this.#troubleshootLink.setAttribute("rel", "noopener");
1086
+ this.#troubleshootLink.hidden = true;
1087
+ this.#div.appendChild(this.#troubleshootLink);
1088
+
1089
+ this.#credits = document.createElement("a");
1090
+ this.#credits.className = "credits";
1091
+ this.#credits.setAttribute("part", "attribution");
1092
+ this.#credits.setAttribute("aria-label", "Secured by Cap");
1093
+ this.#credits.setAttribute("href", "https://trycap.dev");
1094
+ this.#credits.setAttribute("target", "_blank");
1095
+ this.#credits.setAttribute(
1096
+ "title",
1097
+ "Secured by Cap: The self-hosted CAPTCHA for the modern web.",
1098
+ );
1099
+ this.#credits.textContent = "Cap";
1100
+ this.#div.appendChild(this.#credits);
1042
1101
 
1043
1102
  this.#shadow.innerHTML = `<style${window.CAP_CSS_NONCE ? ` nonce=${window.CAP_CSS_NONCE}` : ""}>%%capCSS%%</style>`;
1044
1103
 
@@ -1046,10 +1105,9 @@
1046
1105
  }
1047
1106
 
1048
1107
  addEventListeners() {
1049
- if (!this.#div) return;
1108
+ if (!this.#trigger) return;
1050
1109
 
1051
- this.#div.querySelector("a").addEventListener("click", (e) => {
1052
- e.stopPropagation();
1110
+ this.#credits.addEventListener("click", (e) => {
1053
1111
  e.preventDefault();
1054
1112
  window.open(
1055
1113
  `https://trycap.dev/?${new URLSearchParams(
@@ -1068,17 +1126,18 @@
1068
1126
  );
1069
1127
  });
1070
1128
 
1071
- this.#div.addEventListener("click", () => {
1072
- if (!this.#div.hasAttribute("disabled")) this.solve();
1129
+ this.#trigger.addEventListener("click", () => {
1130
+ if (!this.#trigger.hasAttribute("disabled")) this.solve();
1073
1131
  });
1074
- this.#div.addEventListener("mousedown", () => {
1075
- if (!this.#div.hasAttribute("disabled") && this.#hasHaptics) {
1132
+ this.#trigger.addEventListener("mousedown", () => {
1133
+ if (!this.#trigger.hasAttribute("disabled") && this.#hasHaptics) {
1076
1134
  navigator.vibrate(5);
1077
1135
  }
1078
1136
  });
1079
1137
 
1080
- this.#div.addEventListener("keydown", (e) => {
1081
- if ((e.key === "Enter" || e.key === " ") && !this.#div.hasAttribute("disabled")) {
1138
+ this.#trigger.addEventListener("keydown", (e) => {
1139
+ if (e.target !== this.#trigger) return;
1140
+ if ((e.key === "Enter" || e.key === " ") && !this.#trigger.hasAttribute("disabled")) {
1082
1141
  e.preventDefault();
1083
1142
  e.stopPropagation();
1084
1143
  this.solve();
@@ -1092,8 +1151,8 @@
1092
1151
  }
1093
1152
 
1094
1153
  animateLabel(text) {
1095
- if (!this.#div) return;
1096
- const wrapper = this.#div.querySelector(".label-wrapper");
1154
+ if (!this.#trigger) return;
1155
+ const wrapper = this.#trigger.querySelector(".label-wrapper");
1097
1156
  if (!wrapper) return;
1098
1157
 
1099
1158
  if (prefersReducedMotion()) {
@@ -1129,62 +1188,53 @@
1129
1188
  }
1130
1189
 
1131
1190
  updateUI(state, text, disabled = false) {
1132
- if (!this.#div) return;
1191
+ if (!this.#div || !this.#trigger) return;
1133
1192
 
1134
1193
  this.#div.setAttribute("data-state", state);
1194
+ this.#div.classList.remove("has-troubleshoot");
1135
1195
 
1136
1196
  this.animateLabel(text);
1137
1197
 
1198
+ if (this.#troubleshootLink) this.#troubleshootLink.hidden = true;
1199
+
1138
1200
  if (disabled) {
1139
- this.#div.setAttribute("disabled", "true");
1201
+ this.#trigger.setAttribute("disabled", "true");
1140
1202
  } else {
1141
- this.#div.removeAttribute("disabled");
1203
+ this.#trigger.removeAttribute("disabled");
1142
1204
  }
1143
1205
  }
1144
1206
 
1145
1207
  updateUIBlocked(label, showTroubleshooting = false) {
1146
- if (!this.#div) return;
1208
+ if (!this.#div || !this.#trigger) return;
1147
1209
 
1148
1210
  this.#div.setAttribute("data-state", "error");
1149
- this.#div.removeAttribute("disabled");
1150
-
1151
- const wrapper = this.#div.querySelector(".label-wrapper");
1152
- if (!wrapper) return;
1153
-
1154
- const troubleshootingUrl =
1155
- this.getAttribute("data-cap-troubleshooting-url") ||
1156
- "https://trycap.dev/guide/troubleshooting/instrumentation.html";
1157
-
1158
- const current = wrapper.querySelector(".label.active");
1159
- const next = document.createElement("span");
1160
- next.className = "label";
1161
- next.innerHTML = showTroubleshooting
1162
- ? `${label} · <a class="cap-troubleshoot-link" href="${troubleshootingUrl}" target="_blank" rel="noopener">${this.getI18nText("troubleshooting-label", "Troubleshoot")}</a>`
1163
- : label;
1164
- wrapper.appendChild(next);
1165
-
1166
- void next.offsetWidth;
1167
- next.classList.add("active");
1168
- if (current) {
1169
- current.classList.remove("active");
1170
- current.classList.add("exit");
1171
- current.addEventListener("transitionend", () => current.remove(), {
1172
- once: true,
1173
- });
1174
- }
1175
-
1176
- const link = next.querySelector(".cap-troubleshoot-link");
1177
- if (link) {
1178
- link.addEventListener("click", (e) => {
1179
- e.stopPropagation();
1180
- });
1211
+ this.#trigger.removeAttribute("disabled");
1212
+
1213
+ this.animateLabel(label);
1214
+
1215
+ if (this.#troubleshootLink) {
1216
+ if (showTroubleshooting) {
1217
+ const troubleshootingUrl =
1218
+ this.getAttribute("data-cap-troubleshooting-url") ||
1219
+ "https://trycap.dev/guide/troubleshooting/instrumentation.html";
1220
+ this.#troubleshootLink.setAttribute("href", troubleshootingUrl);
1221
+ this.#troubleshootLink.textContent = this.getI18nText(
1222
+ "troubleshooting-label",
1223
+ "Troubleshoot",
1224
+ );
1225
+ this.#troubleshootLink.hidden = false;
1226
+ this.#div.classList.add("has-troubleshoot");
1227
+ } else {
1228
+ this.#troubleshootLink.hidden = true;
1229
+ this.#div.classList.remove("has-troubleshoot");
1230
+ }
1181
1231
  }
1182
1232
  }
1183
1233
 
1184
1234
  handleProgress(event) {
1185
- if (!this.#div) return;
1235
+ if (!this.#trigger) return;
1186
1236
 
1187
- const progressCircle = this.#div.querySelector(".progress-ring-circle");
1237
+ const progressCircle = this.#trigger.querySelector(".progress-ring-circle");
1188
1238
 
1189
1239
  if (progressCircle) {
1190
1240
  const circumference = 2 * Math.PI * 14;
@@ -1192,7 +1242,7 @@
1192
1242
  progressCircle.style.strokeDashoffset = offset;
1193
1243
  }
1194
1244
 
1195
- const wrapper = this.#div.querySelector(".label-wrapper");
1245
+ const wrapper = this.#trigger.querySelector(".label-wrapper");
1196
1246
  if (wrapper) {
1197
1247
  const activeLabel = wrapper.querySelector(".label.active");
1198
1248
  if (activeLabel) {