@cap.js/widget 0.1.38 → 0.1.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/cap.min.js +1 -1
  2. package/package.json +1 -1
  3. package/src/cap.js +17 -19
package/cap.min.js CHANGED
@@ -1 +1 @@
1
- (()=>{const e="0.0.6";if("undefined"==typeof window)return;const t=(t,r={})=>{const n={...r,headers:{...r.headers||{},"Cap-Stamp":btoa(String.fromCharCode(...[[Date.now()]].map(e=>[e>>24,e>>16,e>>8,e[0]].map(e=>255&e))[0])).replace(/=/g,""),"Cap-Solver":`0,${e}`}};return window?.CAP_CUSTOM_FETCH?window.CAP_CUSTOM_FETCH(t,n):fetch(t,n)};function r(e,t){let r=function(e){let t=2166136261;for(let r=0;r<e.length;r++)t^=e.charCodeAt(r),t+=(t<<1)+(t<<4)+(t<<7)+(t<<8)+(t<<24);return t>>>0}(e),n="";function s(){return r^=r<<13,r^=r>>>17,r^=r<<5,r>>>0}for(;n.length<t;){n+=s().toString(16).padStart(8,"0")}return n.substring(0,t)}async function n(e){var t=(e=>{const t=atob(e),r=new Uint8Array(t.length);for(let e=0;e<t.length;e++)r[e]=t.charCodeAt(e);return r})(e);const r=await new Promise((e,r)=>{try{var n=new DecompressionStream("deflate-raw"),s=n.writable.getWriter(),i=n.readable.getReader(),o=[];i.read().then(function t(n){if(n.done){for(var s=0,a=0,l=0;l<o.length;l++)s+=o[l].length;var c=new Uint8Array(s);for(l=0;l<o.length;l++)c.set(o[l],a),a+=o[l].length;e((new TextDecoder).decode(c))}else o.push(n.value),i.read().then(t).catch(r)}).catch(r),s.write(t).then(()=>{s.close()}).catch(r)}catch(e){r(e)}});return new Promise(e=>{var t=setTimeout(()=>{i(),e({__timeout:!0})},2e4),n=document.createElement("iframe");n.setAttribute("sandbox","allow-scripts"),n.setAttribute("aria-hidden","true"),n.style.cssText="position:absolute;width:1px;height:1px;top:-9999px;left:-9999px;border:none;opacity:0;pointer-events:none;";var s=!1;function i(){s||(s=!0,clearTimeout(t),window.removeEventListener("message",o),n.parentNode&&n.parentNode.removeChild(n))}function o(t){var r=t.data;r&&"object"==typeof r&&("cap:instr"===r.type?(i(),r.blocked?e({__blocked:!0,blockReason:r.blockReason||"automated_browser"}):r.result?e(r.result):e({__timeout:!0})):"cap:error"===r.type&&(i(),e({__timeout:!0})))}window.addEventListener("message",o),n.srcdoc='<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><script>'+r+"\n<\/script></body></html>",document.body.appendChild(n)})}let s=null;const i=()=>{if(s)return s;const t=window.CAP_CUSTOM_WASM_URL||`https://cdn.jsdelivr.net/npm/@cap.js/wasm@${e}/browser/cap_wasm_bg.wasm`;return s=fetch(t).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 s=null,e}),s};"object"==typeof WebAssembly&&"function"==typeof WebAssembly.compile&&i().catch(()=>{});const o={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)}};function a(){o.state="idle",o.challengeResp=null,o.challenges=null,o.results=[],o.completedCount=0,o.solvePromise=null,o.promoteFn=null,o.pendingPromotion=null,o._listeners=[],o.token=null,o.tokenExpires=null,h()}let l=null;let c=null;function d(){c&&(window.removeEventListener("mousemove",c),window.removeEventListener("touchstart",c),window.removeEventListener("keydown",c),c=null)}function h(){d();const e=()=>{d(),"idle"===o.state&&(o.state="waiting",l=setTimeout(()=>{p()},2500))};c=e,window.addEventListener("mousemove",e,{passive:!0}),window.addEventListener("touchstart",e,{passive:!0}),window.addEventListener("keydown",e,{passive:!0})}async function p(){if("waiting"!==o.state)return;o.state="fetching";const e=document.querySelector("cap-widget");if(!e)return void(o.state="idle");let s=e.getAttribute("data-cap-api-endpoint");if(!s&&window?.CAP_CUSTOM_FETCH&&(s="/"),s){s.endsWith("/")||(s+="/");try{const e=await t(`${s}challenge`,{method:"POST"});let a;try{a=await e.json()}catch{throw new Error("Failed to parse speculative challenge response")}if(a.error)throw new Error(a.error);a._apiEndpoint=s,o.challengeResp=a;const{challenge:l,token:c}=a;let d=l;if(!Array.isArray(d)){let e=0;d=Array.from({length:l.c},()=>(e++,[r(`${c}${e}`,l.s),r(`${c}${e}d`,l.d)]))}o.challenges=d,o.state="solving",o.solvePromise=async function(e){g();let r=null;try{r=await i()}catch{}v.setWasm(r);const s=e.length,a=new Array(s);let l=1,c=!1;o.promoteFn=e=>{c||(c=!0,l=e,v._size=e,v._ensureSize(e))},null!==o.pendingPromotion&&(o.promoteFn(o.pendingPromotion),o.pendingPromotion=null);let d=0;for(;d<s;){const t=l,r=[],n=[];for(let i=0;i<t&&d<s;i++)n.push(d),r.push(e[d]),d++;v._ensureSize(Math.max(l,t));const i=await Promise.all(r.map(e=>v.run(e[0],e[1]).then(e=>(o.completedCount++,e))));for(let e=0;e<n.length;e++)a[n[e]]=i[e];!c&&d<s&&await new Promise(e=>setTimeout(e,120))}return o.results=a,o.state="redeeming",async function(e){try{const r=o.challengeResp,s=r._apiEndpoint;if(!s)throw new Error("[cap] speculative redeem: missing apiEndpoint");let i=null;if(r.instrumentation&&(i=await n(r.instrumentation),i?.__timeout||i?.__blocked))return o.state="done",void o.notify();const a=await t(`${s}redeem`,{method:"POST",body:JSON.stringify({token:r.token,solutions:e,...i&&{instr:i}}),headers:{"Content-Type":"application/json"}});let l;try{l=await a.json()}catch{throw new Error("Failed to parse speculative redeem response")}if(!l.success)throw new Error(l.error||"Speculative redeem failed");o.token=l.token,o.tokenExpires=new Date(l.expires).getTime(),o.state="done",o.notify()}catch(e){console.warn("[cap] speculative redeem failed (will redo on click):",e),o.state="done",o.notify()}}(a),a}(d)}catch(e){console.warn("[cap] speculative challenge fetch failed:",e),o.state="error",o.notify()}}else o.state="idle"}h();let u=null;function g(){return u||(u=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"})),u)}class m{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=g(),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 r=this._idle.indexOf(e);-1!==r&&this._idle.splice(r,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((r,n)=>{this._queue.push({salt:e,target:t,resolve:r,reject:n}),this._dispatch()})}_dispatch(){for(;this._idle.length>0&&this._queue.length>0;){const e=this._idle.shift(),{salt:t,target:r,resolve:n,reject:s}=this._queue.shift();let i=!1;const o=({data:t})=>{i||(i=!0,e.removeEventListener("message",o),e.removeEventListener("error",a),this._spawnFailures=0,this._idle.push(e),t.found?n(t.nonce):s(new Error(t.error||"worker failed")),this._dispatch())},a=t=>{if(i)return;i=!0,e.removeEventListener("message",o),e.removeEventListener("error",a);const r=this._replaceWorker(e);s(t),r&&this._dispatch()};e.addEventListener("message",o),e.addEventListener("error",a),this._wasmModule?e.postMessage({salt:t,target:r,wasmModule:this._wasmModule},[]):e.postMessage({salt:t,target:r})}}terminate(){for(const e of this._workers)try{e.terminate()}catch{}this._workers=[],this._idle=[],this._queue=[]}}const v=new m(1);v._spawn();class b extends HTMLElement{#e=null;#t=navigator.hardwareConcurrency||8;token=null;#r;#n;#s;#i=!1;#o;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"]}constructor(){super(),this.#o&&this.#o.forEach((e,t)=>{this.removeEventListener(t.slice(2),e)}),this.#o=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)}initialize(){g()}attributeChangedCallback(e,t,r){if(e.startsWith("on")){const t=e.slice(2),n=this.#o.get(e);if(n&&this.removeEventListener(t,n),r){const r=t=>{const r=this.getAttribute(e);"function"==typeof window[r]&&window[r].call(this,t)};this.#o.set(e,r),this.addEventListener(t,r)}}"data-cap-worker-count"===e&&this.setWorkersCount(parseInt(r,10)),"data-cap-i18n-initial-state"===e&&this.#n&&this.#n?.querySelector(".label.active")&&this.animateLabel(this.getI18nText("initial-state","Verify you're human"))}async connectedCallback(){this.#s=this,this.#r=this.attachShadow({mode:"open"}),this.#n=document.createElement("div"),this.createUI(),this.addEventListeners(),this.initialize(),this.#n.removeAttribute("disabled");const e=this.getAttribute("data-cap-worker-count"),t=e?parseInt(e,10):null;this.setWorkersCount(t||navigator.hardwareConcurrency||8);const r=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.#s.innerHTML=`<input type="hidden" name="${r}">`,"idle"!==o.state&&"waiting"!==o.state||(l&&(clearTimeout(l),l=null),o.state="waiting",l=setTimeout(()=>p(),2500))}async solve(){if(!this.#i)try{this.#i=!0,this.updateUI("verifying",this.getI18nText("verifying-label","Verifying..."),!0),this.#n.setAttribute("aria-label",this.getI18nText("verifying-aria-label","Verifying you're a human, please wait")),this.dispatchEvent("progress",{progress:0});try{let e,s,i=this.getAttribute("data-cap-api-endpoint");if(!i&&window?.CAP_CUSTOM_FETCH)i="/";else if(!i)throw new Error("Missing API endpoint. Either custom fetch or an API endpoint must be provided.");if(i.endsWith("/")||(i+="/"),"done"===o.state&&o.token&&o.tokenExpires&&Date.now()<o.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=o.token),this.dispatchEvent("solve",{token:o.token}),this.token=o.token;const t=o.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),t),this.#n.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),a(),void(this.#i=!1)}if("done"===o.state)e=o.results,s=o.challengeResp,this.dispatchEvent("progress",{progress:100});else if("solving"===o.state||"redeeming"===o.state||"fetching"===o.state||"waiting"===o.state){"waiting"===o.state&&(l&&(clearTimeout(l),l=null),o.state="waiting",p()),o.pendingPromotion=this.#t,o.promoteFn&&o.promoteFn(this.#t);const t=setInterval(()=>{if("solving"!==o.state&&"redeeming"!==o.state)return void clearInterval(t);const e=o.challenges?o.challenges.length:1,r=o.completedCount,n="redeeming"===o.state?99:Math.min(98,Math.round(r/e*100));this.dispatchEvent("progress",{progress:n})},150);if(await new Promise(e=>o.onSettled(e)),clearInterval(t),"done"!==o.state)throw new Error("Speculative solve failed – please try again");if(o.token&&o.tokenExpires&&Date.now()<o.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=o.token),this.dispatchEvent("solve",{token:o.token}),this.token=o.token;const t=o.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),t),this.#n.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),a(),void(this.#i=!1)}e=o.results,s=o.challengeResp,this.dispatchEvent("progress",{progress:100})}else{const n=await t(`${i}challenge`,{method:"POST"});try{s=await n.json()}catch{throw new Error("Failed to parse challenge response from server")}if(s.error)throw new Error(s.error);const{challenge:o,token:a}=s;let l=o;if(!Array.isArray(l)){let e=0;l=Array.from({length:o.c},()=>(e++,[r(`${a}${e}`,o.s),r(`${a}${e}d`,o.d)]))}e=await this.solveChallenges(l)}const c=s.instrumentation?n(s.instrumentation):Promise.resolve(null),d=await c;if(d?.__timeout||d?.__blocked){this.updateUIBlocked(this.getI18nText("error-label","Error"),d?.__blocked),this.#n.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.#i=!1)}const{token:h}=s,u=await t(`${i}redeem`,{method:"POST",body:JSON.stringify({token:h,solutions:e,...d&&{instr:d}}),headers:{"Content-Type":"application/json"}});let g;try{g=await u.json()}catch{throw new Error("Failed to parse server response")}if(this.dispatchEvent("progress",{progress:100}),!g.success)throw new Error(g.error||"Invalid solution");const m=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${m}']`)&&(this.querySelector(`input[name='${m}']`).value=g.token),this.dispatchEvent("solve",{token:g.token}),this.token=g.token,a(),this.#e&&clearTimeout(this.#e);const v=new Date(g.expires).getTime()-Date.now();return v>0&&v<864e5?this.#e=setTimeout(()=>this.reset(),v):this.error("Invalid expiration time"),this.#n.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),{success:!0,token:this.token}}catch(e){throw this.#n.setAttribute("aria-label",this.getI18nText("error-aria-label","An error occurred, please try again")),this.error(e.message),e}}finally{this.#i=!1}}async solveChallenges(e){const t=e.length;let r=0;let n=null;const s="object"==typeof WebAssembly&&"function"==typeof WebAssembly.instantiate;if(s)try{n=await i()}catch(e){console.warn("[cap] wasm unavailable, falling back to JS solver:",e)}if(!s&&!this.#r.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.#r.insertBefore(e,this.#r.firstChild),setTimeout(()=>{e.style.marginBottom="calc(-1 * var(--cap-border-radius, 14px))",e.style.opacity=1},10)}const o=new m(this.#t);o.setWasm(n),o._ensureSize(this.#t);const a=[];try{for(let n=0;n<e.length;n+=this.#t){const s=e.slice(n,Math.min(n+this.#t,e.length)),i=await Promise.all(s.map(([e,n])=>o.run(e,n).then(e=>{r++;const n=Math.min(99,Math.round((0+r)/t*100));return this.dispatchEvent("progress",{progress:n}),e})));a.push(...i)}}finally{o.terminate()}return a}setWorkersCount(e){const t=parseInt(e,10),r=Math.min(navigator.hardwareConcurrency||8,16);this.#t=!Number.isNaN(t)&&t>0&&t<=r?t:navigator.hardwareConcurrency||8}createUI(){this.#n.classList.add("captcha"),this.#n.setAttribute("role","button"),this.#n.setAttribute("tabindex","0"),this.#n.setAttribute("aria-label",this.getI18nText("verify-aria-label","Click to verify you're a human")),this.#n.setAttribute("aria-live","polite"),this.#n.setAttribute("disabled","true"),this.#n.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://capjs.js.org/" class="credits" target="_blank" rel="follow noopener" title="Secured by Cap: Self-hosted CAPTCHA for the modern web.">Cap</a>`,this.#r.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[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);opacity:.8;text-underline-offset:.18em;z-index:6;font-size:12px;text-decoration-thickness:.08em;position:absolute;bottom:10px;right:10px}.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.#r.appendChild(this.#n)}addEventListeners(){this.#n&&(this.#n.querySelector("a").addEventListener("click",e=>{e.stopPropagation(),e.preventDefault(),window.open("https://capjs.js.org","_blank")}),this.#n.addEventListener("click",()=>{this.#n.hasAttribute("disabled")||this.solve()}),this.#n.addEventListener("keydown",e=>{"Enter"!==e.key&&" "!==e.key||this.#n.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.#n)return;const t=this.#n.querySelector(".label-wrapper");if(!t)return;if(window.matchMedia?.("(prefers-reduced-motion: reduce)").matches){const r=t.querySelector(".label.active");if(r)r.textContent=e;else{const r=document.createElement("span");r.className="label active",r.textContent=e,t.appendChild(r)}return}const r=t.querySelector(".label.active"),n=document.createElement("span");n.className="label",n.textContent=e,t.appendChild(n),n.offsetWidth,n.classList.add("active"),r&&(r.classList.remove("active"),r.classList.add("exit"),r.addEventListener("transitionend",()=>r.remove(),{once:!0}))}updateUI(e,t,r=!1){this.#n&&(this.#n.setAttribute("data-state",e),this.animateLabel(t),r?this.#n.setAttribute("disabled","true"):this.#n.removeAttribute("disabled"))}updateUIBlocked(e,t=!1){if(!this.#n)return;this.#n.setAttribute("data-state","error"),this.#n.removeAttribute("disabled");const r=this.#n.querySelector(".label-wrapper");if(!r)return;const n=this.getAttribute("data-cap-troubleshooting-url")||"https://capjs.js.org/guide/troubleshooting/instrumentation.html",s=r.querySelector(".label.active"),i=document.createElement("span");i.className="label",i.innerHTML=t?`${e} · <a class="cap-troubleshoot-link" href="${n}" target="_blank" rel="noopener">${this.getI18nText("troubleshooting-label","Troubleshoot")}</a>`:e,r.appendChild(i),i.offsetWidth,i.classList.add("active"),s&&(s.classList.remove("active"),s.classList.add("exit"),s.addEventListener("transitionend",()=>s.remove(),{once:!0}));const o=i.querySelector(".cap-troubleshoot-link");o&&(console.log("linkblud"),o.addEventListener("click",e=>{e.stopPropagation()}))}handleProgress(e){if(!this.#n)return;const t=this.#n.querySelector(".progress-ring-circle");if(t){const r=2*Math.PI*14,n=r-e.detail.progress/100*r;t.style.strokeDashoffset=n}const r=this.#n.querySelector(".label-wrapper");if(r){const t=r.querySelector(".label.active");t&&(t.textContent=`${this.getI18nText("verifying-label","Verifying...")} ${e.detail.progress}%`)}this.executeAttributeCode("onprogress",e)}handleSolve(e){this.updateUI("done",this.getI18nText("solved-label","You're a human"),!0),this.executeAttributeCode("onsolve",e)}handleError(e){this.updateUI("error",this.getI18nText("error-label","Error. Try again.")),this.executeAttributeCode("onerror",e)}handleReset(e){this.updateUI("",this.getI18nText("initial-state","I'm a human")),this.executeAttributeCode("onreset",e)}executeAttributeCode(e,t){const r=this.getAttribute(e);r&&(console.error("[cap] using `onxxx='…'` is strongly discouraged and will be deprecated soon. please use `addEventListener` callbacks instead."),new Function("event",r).call(this,t))}error(e="Unknown error"){console.error("[cap]",e),this.dispatchEvent("error",{isCap:!0,message:e})}dispatchEvent(e,t={}){const r=new CustomEvent(e,{bubbles:!0,composed:!0,detail:t});super.dispatchEvent(r)}reset(){this.#e&&(clearTimeout(this.#e),this.#e=null),this.dispatchEvent("reset"),this.token=null;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.#o.forEach((e,t)=>{this.removeEventListener(t.slice(2),e)}),this.#o.clear(),this.#r&&(this.#r.innerHTML=""),this.reset(),this.cleanup()}cleanup(){this.#e&&(clearTimeout(this.#e),this.#e=null)}}class f{constructor(e={},t){const r=t||document.createElement("cap-widget");if(Object.entries(e).forEach(([e,t])=>{r.setAttribute(e,t)}),!e.apiEndpoint&&!window?.CAP_CUSTOM_FETCH)throw r.remove(),new Error("Missing API endpoint. Either custom fetch or an API endpoint must be provided.");e.apiEndpoint&&r.setAttribute("data-cap-api-endpoint",e.apiEndpoint),this.widget=r,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:()=>r.token,configurable:!0,enumerable:!0}),t||(r.style.display="none",document.documentElement.appendChild(r))}}window.Cap=f,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",b),"object"==typeof exports&&"undefined"!=typeof module?module.exports=f:"function"==typeof define&&define.amd&&define([],()=>f),"undefined"!=typeof exports&&(exports.default=f)})();
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 r(e,t){let r=function(e){let t=2166136261;for(let r=0;r<e.length;r++)t^=e.charCodeAt(r),t+=(t<<1)+(t<<4)+(t<<7)+(t<<8)+(t<<24);return t>>>0}(e),n="";function s(){return r^=r<<13,r^=r>>>17,r^=r<<5,r>>>0}for(;n.length<t;){n+=s().toString(16).padStart(8,"0")}return n.substring(0,t)}async function n(e){var t=(e=>{const t=atob(e),r=new Uint8Array(t.length);for(let e=0;e<t.length;e++)r[e]=t.charCodeAt(e);return r})(e);const r=await new Promise((e,r)=>{try{var n=new DecompressionStream("deflate-raw"),s=n.writable.getWriter(),i=n.readable.getReader(),o=[];i.read().then(function t(n){if(n.done){for(var s=0,a=0,l=0;l<o.length;l++)s+=o[l].length;var c=new Uint8Array(s);for(l=0;l<o.length;l++)c.set(o[l],a),a+=o[l].length;e((new TextDecoder).decode(c))}else o.push(n.value),i.read().then(t).catch(r)}).catch(r),s.write(t).then(()=>{s.close()}).catch(r)}catch(e){r(e)}});return new Promise(e=>{var t=setTimeout(()=>{i(),e({__timeout:!0})},2e4),n=document.createElement("iframe");n.setAttribute("sandbox","allow-scripts"),n.setAttribute("aria-hidden","true"),n.style.cssText="position:absolute;width:1px;height:1px;top:-9999px;left:-9999px;border:none;opacity:0;pointer-events:none;";var s=!1;function i(){s||(s=!0,clearTimeout(t),window.removeEventListener("message",o),n.parentNode&&n.parentNode.removeChild(n))}function o(t){var r=t.data;r&&"object"==typeof r&&("cap:instr"===r.type?(i(),r.blocked?e({__blocked:!0,blockReason:r.blockReason||"automated_browser"}):r.result?e(r.result):e({__timeout:!0})):"cap:error"===r.type&&(i(),e({__timeout:!0})))}window.addEventListener("message",o),n.srcdoc='<!DOCTYPE html><html><head><meta charset="UTF-8"></head><body><script>'+r+"\n<\/script></body></html>",document.body.appendChild(n)})}let s=null;const i=()=>{if(s)return s;const e=window.CAP_CUSTOM_WASM_URL||"https://cdn.jsdelivr.net/npm/@cap.js/wasm@0.0.6/browser/cap_wasm_bg.wasm";return s=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 s=null,e}),s};"object"==typeof WebAssembly&&"function"==typeof WebAssembly.compile&&i().catch(()=>{});const o={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)}};function a(){o.state="idle",o.challengeResp=null,o.challenges=null,o.results=[],o.completedCount=0,o.solvePromise=null,o.promoteFn=null,o.pendingPromotion=null,o._listeners=[],o.token=null,o.tokenExpires=null,h()}let l=null;let c=null;function d(){c&&(window.removeEventListener("mousemove",c),window.removeEventListener("touchstart",c),window.removeEventListener("keydown",c),c=null)}function h(){d();const e=()=>{d(),"idle"===o.state&&(o.state="waiting",l=setTimeout(()=>{p()},2500))};c=e,window.addEventListener("mousemove",e,{passive:!0}),window.addEventListener("touchstart",e,{passive:!0}),window.addEventListener("keydown",e,{passive:!0})}async function p(){if("waiting"!==o.state)return;o.state="fetching";const e=document.querySelector("cap-widget");if(!e)return void(o.state="idle");let s=e.getAttribute("data-cap-api-endpoint");if(!s&&window?.CAP_CUSTOM_FETCH&&(s="/"),s){s.endsWith("/")||(s+="/");try{const e=await t(`${s}challenge`,{method:"POST"});let a;try{a=await e.json()}catch{throw new Error("Failed to parse speculative challenge response")}if(a.error)throw new Error(a.error);a._apiEndpoint=s,o.challengeResp=a;const{challenge:l,token:c}=a;let d=l;if(!Array.isArray(d)){let e=0;d=Array.from({length:l.c},()=>(e++,[r(`${c}${e}`,l.s),r(`${c}${e}d`,l.d)]))}o.challenges=d,o.state="solving",o.solvePromise=async function(e){g();let r=null;try{r=await i()}catch{}v.setWasm(r);const s=e.length,a=new Array(s);let l=1,c=!1;o.promoteFn=e=>{c||(c=!0,l=e,v._size=e,v._ensureSize(e))},null!==o.pendingPromotion&&(o.promoteFn(o.pendingPromotion),o.pendingPromotion=null);let d=0;for(;d<s;){const t=l,r=[],n=[];for(let i=0;i<t&&d<s;i++)n.push(d),r.push(e[d]),d++;v._ensureSize(Math.max(l,t));const i=await Promise.all(r.map(e=>v.run(e[0],e[1]).then(e=>(o.completedCount++,e))));for(let e=0;e<n.length;e++)a[n[e]]=i[e];!c&&d<s&&await new Promise(e=>setTimeout(e,120))}return o.results=a,o.state="redeeming",async function(e){try{const r=o.challengeResp,s=r._apiEndpoint;if(!s)throw new Error("[cap] speculative redeem: missing apiEndpoint");let i=null;if(r.instrumentation&&(i=await n(r.instrumentation),i?.__timeout||i?.__blocked))return o.state="done",void o.notify();const a=await t(`${s}redeem`,{method:"POST",body:JSON.stringify({token:r.token,solutions:e,...i&&{instr:i}}),headers:{"Content-Type":"application/json"}});let l;try{l=await a.json()}catch{throw new Error("Failed to parse speculative redeem response")}if(!l.success)throw new Error(l.error||"Speculative redeem failed");o.token=l.token,o.tokenExpires=new Date(l.expires).getTime(),o.state="done",o.notify()}catch(e){console.warn("[cap] speculative redeem failed (will redo on click):",e),o.state="done",o.notify()}}(a),a}(d)}catch(e){console.warn("[cap] speculative challenge fetch failed:",e),o.state="error",o.notify()}}else o.state="idle"}h();let u=null;function g(){return u||(u=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"})),u)}class m{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=g(),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 r=this._idle.indexOf(e);-1!==r&&this._idle.splice(r,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((r,n)=>{this._queue.push({salt:e,target:t,resolve:r,reject:n}),this._dispatch()})}_dispatch(){for(;this._idle.length>0&&this._queue.length>0;){const e=this._idle.shift(),{salt:t,target:r,resolve:n,reject:s}=this._queue.shift();let i=!1;const o=({data:t})=>{i||(i=!0,e.removeEventListener("message",o),e.removeEventListener("error",a),this._spawnFailures=0,this._idle.push(e),t.found?n(t.nonce):s(new Error(t.error||"worker failed")),this._dispatch())},a=t=>{if(i)return;i=!0,e.removeEventListener("message",o),e.removeEventListener("error",a);const r=this._replaceWorker(e);s(t),r&&this._dispatch()};e.addEventListener("message",o),e.addEventListener("error",a),this._wasmModule?e.postMessage({salt:t,target:r,wasmModule:this._wasmModule},[]):e.postMessage({salt:t,target:r})}}terminate(){for(const e of this._workers)try{e.terminate()}catch{}this._workers=[],this._idle=[],this._queue=[]}}const v=new m(1);v._spawn();class b extends HTMLElement{#e=null;#t=navigator.hardwareConcurrency||8;token=null;#r;#n;#s;#i=!1;#o;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"]}constructor(){super(),this.#o&&this.#o.forEach((e,t)=>{this.removeEventListener(t.slice(2),e)}),this.#o=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)}initialize(){g()}attributeChangedCallback(e,t,r){if(e.startsWith("on")){const t=e.slice(2),n=this.#o.get(e);if(n&&this.removeEventListener(t,n),r){const r=t=>{const r=this.getAttribute(e);"function"==typeof window[r]&&window[r].call(this,t)};this.#o.set(e,r),this.addEventListener(t,r)}}"data-cap-worker-count"===e&&this.setWorkersCount(parseInt(r,10)),"data-cap-i18n-initial-state"===e&&this.#n&&this.#n?.querySelector(".label.active")&&this.animateLabel(this.getI18nText("initial-state","Verify you're human"))}async connectedCallback(){this.#s=this,this.#r=this.attachShadow({mode:"open"}),this.#n=document.createElement("div"),this.createUI(),this.addEventListeners(),this.initialize(),this.#n.removeAttribute("disabled");const e=this.getAttribute("data-cap-worker-count"),t=e?parseInt(e,10):null;this.setWorkersCount(t||navigator.hardwareConcurrency||8);const r=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.#s.innerHTML=`<input type="hidden" name="${r}">`,"idle"!==o.state&&"waiting"!==o.state||(l&&(clearTimeout(l),l=null),o.state="waiting",l=setTimeout(()=>p(),2500))}async solve(){if(!this.#i)try{this.#i=!0,this.updateUI("verifying",this.getI18nText("verifying-label","Verifying..."),!0),this.#n.setAttribute("aria-label",this.getI18nText("verifying-aria-label","Verifying you're a human, please wait")),this.dispatchEvent("progress",{progress:0});try{let s,i,c=this.getAttribute("data-cap-api-endpoint");if(!c&&window?.CAP_CUSTOM_FETCH)c="/";else if(!c)throw new Error("Missing API endpoint. Either custom fetch or an API endpoint must be provided.");if(c.endsWith("/")||(c+="/"),"done"===o.state&&o.token&&o.tokenExpires&&Date.now()<o.tokenExpires){this.dispatchEvent("progress",{progress:100});const t=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${t}']`)&&(this.querySelector(`input[name='${t}']`).value=o.token),this.dispatchEvent("solve",{token:o.token}),this.token=o.token;const r=o.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),r),this.#n.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),e&&navigator.vibrate([10,50,20,30,40]),a(),this.#i=!1,{success:!0,token:this.token}}if("done"===o.state)s=o.results,i=o.challengeResp,this.dispatchEvent("progress",{progress:100});else if("solving"===o.state||"redeeming"===o.state||"fetching"===o.state||"waiting"===o.state){"waiting"===o.state&&(l&&(clearTimeout(l),l=null),o.state="waiting",p()),o.pendingPromotion=this.#t,o.promoteFn&&o.promoteFn(this.#t);const t=setInterval(()=>{if("solving"!==o.state&&"redeeming"!==o.state)return void clearInterval(t);const e=o.challenges?o.challenges.length:1,r=o.completedCount,n="redeeming"===o.state?99:Math.min(98,Math.round(r/e*100));this.dispatchEvent("progress",{progress:n})},150);if(await new Promise(e=>o.onSettled(e)),clearInterval(t),"done"!==o.state)throw new Error("Speculative solve failed – please try again");if(o.token&&o.tokenExpires&&Date.now()<o.tokenExpires){this.dispatchEvent("progress",{progress:100});const t=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${t}']`)&&(this.querySelector(`input[name='${t}']`).value=o.token),this.dispatchEvent("solve",{token:o.token}),this.token=o.token;const r=o.tokenExpires-Date.now();return this.#e&&clearTimeout(this.#e),this.#e=setTimeout(()=>this.reset(),r),this.#n.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),e&&navigator.vibrate([10,50,20,30,40]),a(),this.#i=!1,{success:!0,token:this.token}}s=o.results,i=o.challengeResp,this.dispatchEvent("progress",{progress:100})}else{const e=await t(`${c}challenge`,{method:"POST"});try{i=await e.json()}catch{throw new Error("Failed to parse challenge response from server")}if(i.error)throw new Error(i.error);const{challenge:n,token:o}=i;let a=n;if(!Array.isArray(a)){let e=0;a=Array.from({length:n.c},()=>(e++,[r(`${o}${e}`,n.s),r(`${o}${e}d`,n.d)]))}s=await this.solveChallenges(a)}const d=i.instrumentation?n(i.instrumentation):Promise.resolve(null),h=await d;if(h?.__timeout||h?.__blocked){this.updateUIBlocked(this.getI18nText("error-label","Error"),h?.__blocked),this.#n.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.#i=!1)}const{token:u}=i,g=await t(`${c}redeem`,{method:"POST",body:JSON.stringify({token:u,solutions:s,...h&&{instr:h}}),headers:{"Content-Type":"application/json"}});let m;try{m=await g.json()}catch{throw new Error("Failed to parse server response")}if(this.dispatchEvent("progress",{progress:100}),!m.success)throw new Error(m.error||"Invalid solution");const v=this.getAttribute("data-cap-hidden-field-name")||"cap-token";this.querySelector(`input[name='${v}']`)&&(this.querySelector(`input[name='${v}']`).value=m.token),this.dispatchEvent("solve",{token:m.token}),this.token=m.token,a(),this.#e&&clearTimeout(this.#e);const b=new Date(m.expires).getTime()-Date.now();return b>0&&b<864e5?this.#e=setTimeout(()=>this.reset(),b):this.error("Invalid expiration time"),this.#n.setAttribute("aria-label",this.getI18nText("verified-aria-label","We have verified you're a human, you may now continue")),e&&navigator.vibrate([10,50,20,30,40]),{success:!0,token:this.token}}catch(e){throw this.#n.setAttribute("aria-label",this.getI18nText("error-aria-label","An error occurred, please try again")),this.error(e.message),e}}finally{this.#i=!1}}async solveChallenges(e){const t=e.length;let r=0;let n=null;const s="object"==typeof WebAssembly&&"function"==typeof WebAssembly.instantiate;if(s)try{n=await i()}catch(e){console.warn("[cap] wasm unavailable, falling back to JS solver:",e)}if(!s&&!this.#r.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.#r.insertBefore(e,this.#r.firstChild),setTimeout(()=>{e.style.marginBottom="calc(-1 * var(--cap-border-radius, 14px))",e.style.opacity=1},10)}const o=new m(this.#t);o.setWasm(n),o._ensureSize(this.#t);const a=[];try{for(let n=0;n<e.length;n+=this.#t){const s=e.slice(n,Math.min(n+this.#t,e.length)),i=await Promise.all(s.map(([e,n])=>o.run(e,n).then(e=>{r++;const n=Math.min(99,Math.round((0+r)/t*100));return this.dispatchEvent("progress",{progress:n}),e})));a.push(...i)}}finally{o.terminate()}return a}setWorkersCount(e){const t=parseInt(e,10),r=Math.min(navigator.hardwareConcurrency||8,16);this.#t=!Number.isNaN(t)&&t>0&&t<=r?t:navigator.hardwareConcurrency||8}createUI(){this.#n.classList.add("captcha"),this.#n.setAttribute("role","button"),this.#n.setAttribute("tabindex","0"),this.#n.setAttribute("aria-label",this.getI18nText("verify-aria-label","Click to verify you're a human")),this.#n.setAttribute("aria-live","polite"),this.#n.setAttribute("disabled","true"),this.#n.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://capjs.js.org/" class="credits" target="_blank" rel="follow noopener" title="Secured by Cap: Self-hosted CAPTCHA for the modern web.">Cap</a>`,this.#r.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[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);opacity:.8;text-underline-offset:.18em;z-index:6;font-size:12px;text-decoration-thickness:.08em;position:absolute;bottom:10px;right:10px}.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.#r.appendChild(this.#n)}addEventListeners(){this.#n&&(this.#n.querySelector("a").addEventListener("click",e=>{e.stopPropagation(),e.preventDefault(),window.open("https://capjs.js.org","_blank")}),this.#n.addEventListener("click",()=>{this.#n.hasAttribute("disabled")||this.solve()}),this.#n.addEventListener("mousedown",()=>{!this.#n.hasAttribute("disabled")&&e&&navigator.vibrate(5)}),this.#n.addEventListener("keydown",e=>{"Enter"!==e.key&&" "!==e.key||this.#n.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.#n)return;const t=this.#n.querySelector(".label-wrapper");if(!t)return;if(window.matchMedia?.("(prefers-reduced-motion: reduce)").matches){const r=t.querySelector(".label.active");if(r)r.textContent=e;else{const r=document.createElement("span");r.className="label active",r.textContent=e,t.appendChild(r)}return}const r=t.querySelector(".label.active"),n=document.createElement("span");n.className="label",n.textContent=e,t.appendChild(n),n.offsetWidth,n.classList.add("active"),r&&(r.classList.remove("active"),r.classList.add("exit"),r.addEventListener("transitionend",()=>r.remove(),{once:!0}))}updateUI(e,t,r=!1){this.#n&&(this.#n.setAttribute("data-state",e),this.animateLabel(t),r?this.#n.setAttribute("disabled","true"):this.#n.removeAttribute("disabled"))}updateUIBlocked(e,t=!1){if(!this.#n)return;this.#n.setAttribute("data-state","error"),this.#n.removeAttribute("disabled");const r=this.#n.querySelector(".label-wrapper");if(!r)return;const n=this.getAttribute("data-cap-troubleshooting-url")||"https://capjs.js.org/guide/troubleshooting/instrumentation.html",s=r.querySelector(".label.active"),i=document.createElement("span");i.className="label",i.innerHTML=t?`${e} · <a class="cap-troubleshoot-link" href="${n}" target="_blank" rel="noopener">${this.getI18nText("troubleshooting-label","Troubleshoot")}</a>`:e,r.appendChild(i),i.offsetWidth,i.classList.add("active"),s&&(s.classList.remove("active"),s.classList.add("exit"),s.addEventListener("transitionend",()=>s.remove(),{once:!0}));const o=i.querySelector(".cap-troubleshoot-link");o&&(console.log("linkblud"),o.addEventListener("click",e=>{e.stopPropagation()}))}handleProgress(e){if(!this.#n)return;const t=this.#n.querySelector(".progress-ring-circle");if(t){const r=2*Math.PI*14,n=r-e.detail.progress/100*r;t.style.strokeDashoffset=n}const r=this.#n.querySelector(".label-wrapper");if(r){const e=r.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)}handleError(t){this.updateUI("error",this.getI18nText("error-label","Error. Try again.")),this.executeAttributeCode("onerror",t),e&&navigator.vibrate([10,40,10])}handleReset(e){this.updateUI("",this.getI18nText("initial-state","I'm a human")),this.executeAttributeCode("onreset",e)}executeAttributeCode(e,t){const r=this.getAttribute(e);r&&(console.error("[cap] using `onxxx='…'` is strongly discouraged and will be deprecated soon. please use `addEventListener` callbacks instead."),new Function("event",r).call(this,t))}error(e="Unknown error"){console.error("[cap]",e),this.dispatchEvent("error",{isCap:!0,message:e})}dispatchEvent(e,t={}){const r=new CustomEvent(e,{bubbles:!0,composed:!0,detail:t});super.dispatchEvent(r)}reset(){this.#e&&(clearTimeout(this.#e),this.#e=null),this.dispatchEvent("reset"),this.token=null;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.#o.forEach((e,t)=>{this.removeEventListener(t.slice(2),e)}),this.#o.clear(),this.#r&&(this.#r.innerHTML=""),this.reset(),this.cleanup()}cleanup(){this.#e&&(clearTimeout(this.#e),this.#e=null)}}class f{constructor(e={},t){const r=t||document.createElement("cap-widget");if(Object.entries(e).forEach(([e,t])=>{r.setAttribute(e,t)}),!e.apiEndpoint&&!window?.CAP_CUSTOM_FETCH)throw r.remove(),new Error("Missing API endpoint. Either custom fetch or an API endpoint must be provided.");e.apiEndpoint&&r.setAttribute("data-cap-api-endpoint",e.apiEndpoint),this.widget=r,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:()=>r.token,configurable:!0,enumerable:!0}),t||(r.style.display="none",document.documentElement.appendChild(r))}}window.Cap=f,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",b),"object"==typeof exports&&"undefined"!=typeof module?module.exports=f:"function"==typeof define&&define.amd&&define([],()=>f),"undefined"!=typeof exports&&(exports.default=f)})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap.js/widget",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "description": "Client-side widget for Cap, a lightweight, modern open-source CAPTCHA alternative designed using SHA-256 PoW.",
5
5
  "keywords": [
6
6
  "algorithm",
package/src/cap.js CHANGED
@@ -1,24 +1,13 @@
1
1
  (() => {
2
2
  const WASM_VERSION = "0.0.6";
3
+ const hasHaptics =
4
+ "vibrate" in navigator && !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
3
5
 
4
6
  if (typeof window === "undefined") {
5
7
  return;
6
8
  }
7
9
 
8
- const capFetch = (u, _conf = {}) => {
9
- const conf = {
10
- ..._conf,
11
- headers: {
12
- ...(_conf.headers || {}),
13
- "Cap-Stamp": btoa(
14
- String.fromCharCode(
15
- ...[[Date.now()]].map((n) => [n >> 24, n >> 16, n >> 8, n[0]].map((x) => x & 255))[0],
16
- ),
17
- ).replace(/=/g, ""),
18
- "Cap-Solver": `0,${WASM_VERSION}`,
19
- },
20
- };
21
-
10
+ const capFetch = (u, conf = {}) => {
22
11
  if (window?.CAP_CUSTOM_FETCH) {
23
12
  return window.CAP_CUSTOM_FETCH(u, conf);
24
13
  }
@@ -713,10 +702,11 @@
713
702
  "We have verified you're a human, you may now continue",
714
703
  ),
715
704
  );
705
+ if (hasHaptics) navigator.vibrate([10, 50, 20, 30, 40]);
716
706
 
717
707
  _resetSpeculativeState();
718
708
  this.#solving = false;
719
- return;
709
+ return { success: true, token: this.token };
720
710
  }
721
711
 
722
712
  if (speculative.state === "done") {
@@ -789,10 +779,11 @@
789
779
  "We have verified you're a human, you may now continue",
790
780
  ),
791
781
  );
782
+ if (hasHaptics) navigator.vibrate([10, 50, 20, 30, 40]);
792
783
 
793
784
  _resetSpeculativeState();
794
785
  this.#solving = false;
795
- return;
786
+ return { success: true, token: this.token };
796
787
  }
797
788
 
798
789
  solutions = speculative.results;
@@ -895,6 +886,7 @@
895
886
  "We have verified you're a human, you may now continue",
896
887
  ),
897
888
  );
889
+ if (hasHaptics) navigator.vibrate([10, 50, 20, 30, 40]);
898
890
 
899
891
  return { success: true, token: this.token };
900
892
  } catch (err) {
@@ -915,7 +907,6 @@
915
907
  let completed = 0;
916
908
 
917
909
  const speculativeHead = 0;
918
- const remaining = total - speculativeHead;
919
910
 
920
911
  let wasmModule = null;
921
912
  const wasmSupported =
@@ -1017,6 +1008,11 @@
1017
1008
  this.#div.addEventListener("click", () => {
1018
1009
  if (!this.#div.hasAttribute("disabled")) this.solve();
1019
1010
  });
1011
+ this.#div.addEventListener("mousedown", () => {
1012
+ if (!this.#div.hasAttribute("disabled") && hasHaptics) {
1013
+ navigator.vibrate(5);
1014
+ }
1015
+ });
1020
1016
 
1021
1017
  this.#div.addEventListener("keydown", (e) => {
1022
1018
  if ((e.key === "Enter" || e.key === " ") && !this.#div.hasAttribute("disabled")) {
@@ -1116,7 +1112,7 @@
1116
1112
 
1117
1113
  const link = next.querySelector(".cap-troubleshoot-link");
1118
1114
  if (link) {
1119
- console.log("linkblud")
1115
+ console.log("linkblud");
1120
1116
  link.addEventListener("click", (e) => {
1121
1117
  e.stopPropagation();
1122
1118
  });
@@ -1138,7 +1134,7 @@
1138
1134
  if (wrapper) {
1139
1135
  const activeLabel = wrapper.querySelector(".label.active");
1140
1136
  if (activeLabel) {
1141
- activeLabel.textContent = `${this.getI18nText("verifying-label", "Verifying...")} ${event.detail.progress}%`;
1137
+ activeLabel.textContent = `${this.getI18nText("verifying-label", "Verifying...")}`;
1142
1138
  }
1143
1139
  }
1144
1140
 
@@ -1153,6 +1149,8 @@
1153
1149
  handleError(event) {
1154
1150
  this.updateUI("error", this.getI18nText("error-label", "Error. Try again."));
1155
1151
  this.executeAttributeCode("onerror", event);
1152
+
1153
+ if (hasHaptics) navigator.vibrate([10, 40, 10]);
1156
1154
  }
1157
1155
 
1158
1156
  handleReset(event) {