1"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[56904],{56904:(e,t,r)=>{r.r(t),r.d(t,{default:()=>P});var n=r(95155),s=r(12115),l=r(97003),a=r(33534),o=r(25016);let i=[{id:"simple",name:"Simple visual",short:"Visual",cue:"The panel goes dark, then jumps bright. React the moment it does."},{id:"audio",name:"Sound",short:"Sound",cue:"The panel stays dark and a short tone plays. React to the tone."},{id:"choice",name:"Choice",short:"Choice",cue:"One of the panels lights up. Press the letter on that panel, not any other."},{id:"gonogo",name:"Go / no-go",short:"Go / no-go",cue:"A circle means react. A cross means hold still. Both are bright."}];function u(e){return i.find(t=>t.id===e)??i[0]}function d(e,t){return"valid"===e.outcome&&null!==e.ms&&e.ms>t}function c(e,t){return d(e,t)?`${e.reason}, and slower than your ${t} ms flag`:e.reason}function m(){return{anticipation:0,falseStart:0,voided:0,wrong:0,falseAlarm:0,noResponse:0}}let h={valid:"counted",anticipation:"too fast to be a reaction",falseStart:"moved before the change",voided:"thrown out",wrong:"wrong panel",falseAlarm:"reacted to a hold signal",noResponse:"no response",correctHold:"held correctly"};var f=r(66567),p=r(91134);let x="rtt-history-v1",g="rtt-settings-v1",b=["simple","audio","choice","gonogo"];function y(e){return"number"==typeof e&&Number.isFinite(e)?e:null}let v={mode:"simple",trialsPerSession:5,choiceCount:2,slowThresholdMs:600,scanoutCorrected:!1},w=[3,5,10,20];async function j(){let e,t=function(){let e=window;return e.AudioContext??e.webkitAudioContext??null}();if(null===t)return null;try{e=new t}catch{return null}try{"suspended"===e.state&&await e.resume()}catch{}let r="function"==typeof e.getOutputTimestamp?e.getOutputTimestamp():null,n=null!==r&&"number"==typeof r.contextTime&&"number"==typeof r.performanceTime,s="number"==typeof e.baseLatency?1e3*e.baseLatency:null,l="number"==typeof e.outputLatency?1e3*e.outputLatency:null;return{ctx:e,canTime:n,baseLatencyMs:null!==s&&Number.isFinite(s)?s:null,outputLatencyMs:null!==l&&Number.isFinite(l)?l:null}}var M=r(52863),N=r(70134),k=r(84296),C=r(52849),S=r(18974);let T={idle:"#111827",wait:"#0b1220",go:"#f1f5f9",nogo:"#f1f5f9",early:"#3b1220",scored:"#1f2937"},$=["F","J"],R=["D","F","J","K"],H=S.IH;function A(e,t){let r=m(),n=[],s=0,l=0,a=0;for(let o of e)switch(o.outcome){case"valid":null!==o.ms&&n.push(o.ms),d(o,t)&&(s+=1),a+=1;break;case"correctHold":l+=1,a+=1;break;case"falseStart":r.falseStart+=1;break;case"voided":r.voided+=1;break;case"anticipation":r.anticipation+=1,a+=1;break;case"wrong":r.wrong+=1,a+=1;break;case"falseAlarm":r.falseAlarm+=1,a+=1;break;case"noResponse":r.noResponse+=1,a+=1}return{valid:n,rejected:r,slow:s,holds:l,consumed:a}}function P(){let[e,t]=(0,s.useState)(v),[r,S]=(0,s.useState)("idle"),[P,E]=(0,s.useState)([]),[I,L]=(0,s.useState)("Press start, then react the moment the panel changes."),[O,V]=(0,s.useState)(null),[_,z]=(0,s.useState)(null),[D,Y]=(0,s.useState)(!1),[U,W]=(0,s.useState)({}),[Z,G]=(0,s.useState)(null),[B,J]=(0,s.useState)(!1),[X,Q]=(0,s.use
1State)(null),[ee,et]=(0,s.useState)(!1),[er,en]=(0,s.useState)(!1),[es,el]=(0,s.useState)(["Player 1","Player 2"]),[ea,eo]=(0,s.useState)(5),[ei,eu]=(0,s.useState)(0),[ed,ec]=(0,s.useState)([]),[em,eh]=(0,s.useState)(!1),[ef,ep]=(0,s.useState)(null),[ex,eg]=(0,s.useState)(null),[eb,ey]=(0,s.useState)(!1),[ev,ew]=(0,s.useState)(!1),ej=(0,s.useRef)("idle"),eM=(0,s.useRef)([]),eN=(0,s.useRef)(v),ek=(0,s.useRef)(null),eC=(0,s.useRef)({kind:"go",target:0}),eS=(0,s.useRef)(null),eT=(0,s.useRef)(null),e$=(0,s.useRef)(null),eR=(0,s.useRef)(null),eH=(0,s.useRef)(0),eA=(0,s.useRef)(null),eP=(0,s.useRef)(new Set),eq=(0,s.useRef)(null),eK=(0,s.useRef)(null),eF=(0,s.useRef)(!1),eE=(0,s.useRef)([]),eI=(0,s.useRef)([]),eL=(0,s.useRef)({running:!1,index:0,trials:5}),eO=(0,s.useRef)({}),eV=(0,s.useRef)(0),e_=e.mode,ez=u(e_),eD="choice"===e_?e.choiceCount:1,eY=4===e.choiceCount?R:$,eU=er?ea:e.trialsPerSession;(0,s.useEffect)(()=>{eN.current=e},[e]),(0,s.useEffect)(()=>{eO.current=U},[U]),(0,s.useEffect)(()=>{eL.current={running:em,index:ei,trials:ea}},[em,ei,ea]),(0,s.useEffect)(()=>{eK.current=X},[X]);let eW=(0,s.useCallback)(()=>{let e=[];for(let t of eE.current)t&&e.push({root:t,ring:t.querySelector('[data-shape="ring"]'),dot:t.querySelector('[data-shape="dot"]'),cross:t.querySelector('[data-shape="cross"]'),bang:t.querySelector('[data-shape="bang"]'),label:t.querySelector('[data-part="label"]')});eI.current=e},[]),eZ=(0,s.useCallback)((e,t,r)=>{let n=eI.current[e];n&&(n.root.style.backgroundColor=T[t],n.ring&&(n.ring.style.visibility="idle"===t||"wait"===t?"visible":"hidden"),n.dot&&(n.dot.
1style.visibility="go"===t?"visible":"hidden"),n.cross&&(n.cross.style.visibility="nogo"===t?"visible":"hidden"),n.bang&&(n.bang.style.visibility="early"===t?"visible":"hidden"),n.label&&(n.label.textContent=r,n.label.style.color="go"===t||"nogo"===t?"#0f172a":"#94a3b8"))},[]),eG=(0,s.useCallback)((e,t)=>{for(let r=0;r<eI.current.length;r+=1)eZ(r,e,t)},[eZ]),eB=(0,s.useCallback)((e,t)=>"done"!==e?"Press to start":t?"Set finished":"Press to run another set",[]),eJ=(0,s.useCallback)(()=>{null!==eS.current&&window.clearTimeout(eS.current),null!==eT.current&&window.clearTimeout(eT.current),null!==e$.current&&window.clearTimeout(e$.current),null!==eR.current&&cancelAnimationFrame(eR.current),eS.current=null,eT.current=null,e$.current=null,eR.current=null,null!==eq.current&&(eq.current.stop(),eq.current=null)},[]),eX=(0,s.useCallback)(e=>{ej.current=e,S(e)},[]),eQ=(0,s.useCallback)(e=>{let t=[...eM.current,e];eM.current=t,E(t)},[]),e0=(0,s.useCallback)(()=>{eJ();let e=eM.current,t=A(e,eN.current.slowThresholdMs),r=(0,f.eh)(t.valid);if(eL.current.running){let t=es[eL.current.index]??`Player ${eL.current.index+1}`;ec(n=>[...n,{name:t,trials:e,summary:r}]),eX("done"),eG("idle",eB("done",!0)),L(`${t} is done. Pass the device to the next player.`);return}let n={startedAt:eV.current,trials:t.valid,rejected:t.rejected,slowCount:t.slow,medianMs:r.medianMs,iqrMs:r.iqrMs,refreshIntervalMs:_?.intervalMs??null,refreshStable:_?.stable??!1,inputTypes:[...eP.current],scanoutCorrected:!1},s=eN.current.mode,l=eO.current[s]??[],a=null;for(let e of l)for(let t of e.trials)(null===a||t<a)&&(a=t);ew(null!==r.bestMs&&(null===a||r.bestMs<a));let o={...eO.current,[s]:[...l,n]};eO.current=o,W(o),!function(e){let t={};for(let r of b){let n=e[r];n&&0!==n.length&&(t[r]=n.slice(Math.max(0,n.length-50)))}try{return window.localStorage.setItem(x,JSON.stringify(t)),!0}catch{return!1}}(o)&&J(!0),eX("done"),eG("idle",eB("done",!1));let i=eN.current.scanoutCorrected?(0,p.IH)(_):0;L(null===r.medianMs?"That set recorded no valid trials.":`Set finished. Median ${(0,f.HK)(Math.max(0,r.medianMs-i))} ms across ${r.validCount} counted trials.`)},[eJ,es,eB,eG,_,eX]),e1=(0,s.useCallback)(e=>{eJ(),eQ({outcome:"voided",ms:null,reason:e,inputType:"none"}),eX("voided"),eG("idle","Trial thrown out"),L(`${e} That trial was thrown out. Press to run it again.`)},[eJ,eG,eQ,eX]),e2=(0,s.useCallback)(e=>{let t=eN.current,r="gonogo"===t.mode&&3>H(10),n="choice"===t.mode?H(t.choiceCount):0;eC.current={kind:r?"nogo":"go",target:n};for(let e=0;e<eI.current.length;e+=1)"choice"===t.mode&&e!==n?eZ(e,"wait",""):eZ(e,r?"nogo":"go","");ek.current=e,eX("armed"),r?eT.current=window.setTimeout(()=>{eQ({outcome:"correctHold",ms:null,reason:"held still on a cross, which is the correct answer",inputType:"none"}),eX("scored"),eG("scored","Good hold"),L("Cross, and you held still. Press to continue."),eH.current=performance.now()+p.SV,A(eM.current,eN.current.slowThresholdMs).consumed>=e3.current&&e0()},p.q7):eT.current=window.setTimeout(()=>{eQ({outcome:"noResponse",ms:null,reason:`nothing pressed within ${p.$I/1e3} seconds`,inputType:"none"}),eX("scored"),eG("scored","No response"),L("No response that time. Press to continue."),eH.current=performance.now()+p.SV,A(eM.current,eN.current.slowThresholdMs).consumed>=e3.current&&e0()},p.$I)},[e0,eG,eZ,eQ,eX]),e3=(0,s.useRef)(v.trialsPerSession);(0,s.useEffect)(()=>{e3.current=eU},[eU]);let e4=(0,s.useCallback)(()=>{eJ();let e=eN.current;eX("waiting"),V(null),eG("wait",""),L("Wait for it.");let t=(0,p.jI)(),r=performance.now();if("audio"===e.mode){let e=eK.current;if(null===e||!e.canTime){eX("idle"),eG("idle","Sound unavailable"),L("The sound test cannot time you accurately in this browser.");return}let r=function(e,t){try{let r=e.currentTime+Math.max(0,t),n=e.createOscillator(),s=e.createGain();n.type="sine",n.frequency.value=880,s.gain.setValueAtTime(1e-4,r),s.gain.setValueAtTime(.28,r+.001),s.gain.setValueAtTime(.28,r+.11),s.gain.setValueAtTime(1e-4,r+.12),n.connect(s),s.connect(e.destination),n.start(r),n.stop(r+.16);let l=!1;return{contextTime:r,stop:()=>
1{if(!l){l=!0;try{n.stop(),n.disconnect(),s.disconnect()}catch{}}}}}catch{return null}}(e.ctx,t/1e3);if(null===r)return void e1("The tone could not be scheduled.");eq.current=r;let n=e.ctx.getOutputTimestamp?.bind(e.ctx),s=n?n():null,l=s?.contextTime,a=s?.performanceTime;if("number"!=typeof l||"number"!=typeof a)return void e1("This browser stopped reporting when sound leaves the speaker.");let o=a+(r.contextTime-l)*1e3;return void(eS.current=window.setTimeout(()=>{if(performance.now()-o>p.pG)return void e1("The browser held this trial back while it was busy.");ek.current=o,eC.current={kind:"go",target:0},eX("armed"),eT.current=window.setTimeout(()=>{eQ({outcome:"noResponse",ms:null,reason:`nothing pressed within ${p.$I/1e3} seconds`,inputType:"none"}),eX("scored"),eG("scored","No response"),L("No response that time. Press to continue."),eH.current=performance.now()+p.SV,A(eM.current,eN.current.slowThresholdMs).consumed>=e3.current&&e0()},p.$I)},Math.max(0,o-performance.now())))}eS.current=window.setTimeout(()=>{if(performance.now()-r-t>p.pG)return void e1("The browser held this trial back while it was busy or in the background.");eR.current=requestAnimationFrame(e=>e2(e))},t)},[e2,eJ,e0,eG,eQ,eX,e1]),e5=(0,s.useCallback)(()=>{eM.current=[],E([]),eP.current=new Set,eV.current=Date.now(),ew(!1),e4()},[e4]),e6=(0,s.useCallback)((e,t,r)=>{let n=ek.current;if(null===n)return;eJ(),eP.current.add(t);let s=eN.current,l=e-n,a=eC.current,o="valid",i="counted",u=!1;l<p._b?(o="anticipation",i=`${(0,f.HK)(l)} ms is faster than a person can react, so it was a guess that happened to land`):"gonogo"===s.mode&&"nogo"===a.kind?(o="falseAlarm",i="that was a cross, which means hold still"):"choice"===s.mode&&null!==r&&r!==a.target?(o="wrong",i=`pressed ${eY[r]??"?"} when ${eY[a.target]??"?"} lit up`):(u=l>s.slowThresholdMs,i="counted"),eQ({outcome:o,ms:l,reason:i,inputType:t}),V("valid"===o?l:null),eX("scored"),eH.current=performance.now()+p.SV,"valid"===o?(eG("scored",`${(0,f.HK)(l)} ms`),L(u?`${(0,f.HK)(l)} ms, flagged as slow. Press to continue.`:`${(0,f.HK)(l)} ms. Press to continue.`)):"anticipation"===o?(eG("early","Too fast to count"),L(`${(0,f.HK)(l)} ms is under our ${p._b} ms floor, so it is not counted.`)):"falseAlarm"===o?(eG("early","That was a hold"),L("That was a cross. You were supposed to hold still.")):(eG("early","Wrong panel"),L(i.charAt(0).toUpperCase()+i.slice(1)+".")),A(eM.current,eN.current.slowThresholdMs).consumed>=e3.current&&e0()},[eJ,e0,eY,eG,eQ,eX]),e7=(0,s.useCallback)(e=>{eJ(),eP.current.add(e),eQ({outcome:"falseStart",ms:null,reason:"you moved before the change, so this one does not count",inputType:e}),eX("falseStart"),eG("early","Too early"),L("You moved before the change. Not counted, going again with a fresh delay."),eH.current=performance.now()+p.SV,e$.current=window.setTimeout(()=>e4(),1100)},[eJ,eG,eQ,eX,e4]),e8=(0,s.useCallback)((e,t,r)=>{if(!(performance.now()<eH.current))switch(ej.current){case"idle":e5();return;case"waiting":e7(t);return;case"armed":e6(e,t,r);return;case"scored":case"falseStart":case"voided":eJ(),e4();return;case"done":if(eL.current.running)return;e5();return}},[eJ,e6,e7,e5,e4]),e9=(0,s.useCallback)(e=>{let t=performance.now();return null===eA.current&&(eA.current=(0,p.jC)(e,t)),eA.current?e:t},[]),te=(0,s.useCallback)((e,t)=>{let r=e.pointerType||"mouse";e8(e9(e.timeStamp),r,"choice"===eN.current.mode?t:null)},[e8,e9]);(0,s.useEffect)(()=>{function e(e){if(e.repeat||e.metaKey||e.ctrlKey||e.altKey)return;let t=document.activeElement;if(t instanceof HTMLElement){let e=t.tagName;if("INPUT"===e||"TEXTAREA"===e||"SELECT"===e||t.isContentEditable)return}
1let r=eN.current,n=4===r.choiceCount?R:$,s=1===e.key.length?e.key.toUpperCase():e.key,l=n.indexOf(s),a=" "===e.key||"Enter"===e.key;if("choice"===r.mode&&"armed"===ej.current){if(l<0)return;e.preventDefault(),e8(e9(e.timeStamp),"keyboard",l);return}(a||!(l<0))&&(e.preventDefault(),e8(e9(e.timeStamp),"keyboard",null))}return window.addEventListener("keydown",e),()=>window.removeEventListener("keydown",e)},[e8,e9]),(0,s.useEffect)(()=>{function e(){"visible"!==document.visibilityState&&("waiting"===ej.current||"armed"===ej.current)&&e1("You left the tab mid-trial.")}return document.addEventListener("visibilitychange",e),()=>document.removeEventListener("visibilitychange",e)},[e1]);let tt=(0,s.useCallback)(async()=>{if(null!==eK.current||eF.current)return;eF.current=!0,et(!0);let e=await j();eK.current=e,Q(e)},[]);(0,s.useEffect)(()=>{let e=function(){try{let e=window.localStorage.getItem(g);if(null===e)return{...v};let t=JSON.parse(e);if("object"!=typeof t||null===t)return{...v};let r=t.mode,n=t.trialsPerSession,s=t.choiceCount,l=t.slowThresholdMs;return{mode:b.includes(r)?r:v.mode,trialsPerSession:"number"==typeof n&&w.includes(n)?n:v.trialsPerSession,choiceCount:4===s?4:2,slowThresholdMs:"number"==typeof l&&l>=300&&l<=2e3?l:v.slowThresholdMs,scanoutCorrected:!0===t.scanoutCorrected}}catch{return{...v}}}();t(e),eN.current=e;let r=function(){let e,t={history:{},problem:null,found:0,kept:0},r=null;try{r=window.localStorage.getItem(x)}catch{return{...t,problem:"This browser is blocking local storage, so nothing can be saved here."}}if(null===r)return t;try{e=JSON.parse(r)}catch{return{...t,problem:"Your saved history could not be read, so this page is starting from an empty one."}}if("object"!=typeof e||null===e)return{...t,problem:"Your saved history could not be read, so this page is starting from an empty one."};let n=e,s={},l=0,a=0;for(let e of b){let t=n[e];if(!Array.isArray(t))continue;let r=[];for(let e of t){l+=1;let t=function(e){var t,r;if("object"!=typeof e||null===e)return null;let n=y(e.startedAt);return null===n?null:{startedAt:n,trials:Array.isArray(t=e.trials)?t.filter(e=>"number"==typeof e&&Number.isFinite(e)):[],rejected:function(e){let t=m();if("object"!=typeof e||null===e)return t;for(let r of Object.keys(t)){let n=e[r];"number"==typeof n&&Number.isFinite(n)&&n>=0&&(t[r]=n)}return t}(e.rejected),slowCount:y(e.slowCount)??0,medianMs:y(e.medianMs),iqrMs:y(e.iqrMs),refreshIntervalMs:y(e.refreshIntervalMs),refreshStable:!0===e.refreshStable,inputTypes:Array.isArray(r=e.inputTypes)?r.filter(e=>"string"==typeof e):[],scanoutCorrected:!1}}(e);null!==t&&r.push(t)}r.sort((e,t)=>e.startedAt-t.startedAt);let o=r.slice(Math.max(0,r.length-50));a+=o.length,s[e]=o}return{history:s,problem:null,found:l,kept:a}}();W(r.history),eO.current=r.history,null!==r.problem?G(r.problem):r.found>r.kept&&G(`Kept your most recent ${r.kept} sets out of ${r.found} found on this device. Older ones were dropped.`);let n=new URLSearchParams(window.location.search),s=n.get("beat"),l=n.get("mode");if(null!==s){let e=Number.parseFloat(s),t=i.find(e=>e.id===l)?.id??"simple";Number.isFinite(e)&&e>=p._b&&e<=3e3&&ep({ms:e,mode:t})}let a=i.find(e=>e.id===n.get("mode"))?.id,o=n.get("panels");"audio"===a&&tt(),(a||"2"===o||"4"===o)&&t(e=>({...e,...a?{mode:a}:{},..."4"===o?{choiceCount:4}:"2"===o?{choiceCount:2}:{}}))},[tt]),(0,s.useEffect)(()=>(0,p.fK)(p.CQ,e=>{z(e),Y(!0)}),[]),(0,s.useEffect)(()=>{eW(),("idle"===ej.current||"done"===ej.current)&&eG("idle",eB(ej.current,eL.current.running))},[eW,eB,eG,eD,e_]),(0,s.useEffect)(()=>()=>eJ(),[eJ]);let tr=(0,s.useMemo)(()=>A(P,e.slowThresholdMs),[P,e.slowThresholdMs]),tn=(0,s.useMemo)(()=>(0,f.eh)(tr.valid),[tr.valid]),ts=e.scanoutCorrected?(0,p.IH)(_):0,tl=null===tn.medianMs?null:Math.max(0,tn.medianMs-ts),ta=(0,s.useMemo)(()=>U[e_]??[],[U,e_]),to=(0,s.useMemo)(()=>{let e=null;for(let t of ta)for(let r of t.trials)(null===e||r<e)&&(e=r);return e},[ta]),ti=tr.consumed+tr.rejected.falseStart>0?tr.rejected.falseStart/(tr.consumed+tr.rejected.falseStart)*100:0,tu=eP.current.size>1,td=(0,s.useMemo)(()=>{if("audio"===e_){if(null===X)return"Sound could not be started in this browser.";if(!X.canTime)return"This browser will not tell us when the tone actually left the speaker, so the sound test cannot time you accurately here.";let e=[];null!==X.outputLatencyMs&&e.push(`about ${X.outputLatencyMs.toFixed(1)} ms of reported output delay`),null!==X.baseLatencyMs&&e.push(`${X.baseLatencyMs.toFixed(1)} ms of processing delay`);let t=e.length>0?`Your browser reports ${e.join(" and ")}. `:"";return`${t}The tone reaches your ears later than we time it from, so your sound score is an overestimate by roughly that much. Wireless headphones add a delay on top that no browser can see, and it is often large enough to swamp the test.`}if(!D)return"Measuring your display timing.";if(null===_||!_.stable)return`${(0,p.ZP)(_)} Without a steady figure we cannot state the floor under your score, so treat small differences as noise.`;let e=(0,f.Om)(_.intervalMs);return`${(0,p.ZP)(_)} The change is on screen somewhere inside that ${e} ms, so anything smaller than it is below what this test can see.`},[X,e_,_,D]),tc=(0,s.useCallback)(e=>{t(t=>{let r={...t,...e};eN.current=r;
1try{window.localStorage.setItem(g,JSON.stringify(r))}catch{}return r})},[]),tm=(0,s.useCallback)(()=>{eJ(),eM.current=[],E([]),V(null),eX("idle"),eG("idle",eB("idle",eL.current.running)),L("Press start, then react the moment the panel changes.")},[eJ,eB,eG,eX]),th=(0,s.useCallback)(async e=>{tm(),tc({mode:e}),"audio"===e&&await tt()},[tt,tm,tc]),tf=(0,s.useCallback)((e,t=!1)=>{eg({text:e,bad:t}),t||window.setTimeout(()=>eg(t=>t?.text===e?null:t),1800)},[]),tp=(0,s.useCallback)(async()=>{let t=function(e,t){let r=[];r.push(["trial","milliseconds","outcome","note","slower_than_threshold","input","mode","display_frame_ms"].map(e=>(0,k.r)(e)).join(","));let n=null===t.refreshIntervalMs?"not measured":`${(Math.round(10*t.refreshIntervalMs)/10).toFixed(1)}${t.refreshStable?"":" (uneven)"}`;return e.forEach((e,s)=>{r.push([String(s+1),null===e.ms?"":String(Math.round(10*e.ms)/10),e.outcome,c(e,t.slowThresholdMs)||h[e.outcome],d(e,t.slowThresholdMs)?"yes":"no",e.inputType,t.modeName,n].map(e=>(0,k.r)(e)).join(","))}),r.join("\r\n")}(P,{modeName:ez.name,refreshIntervalMs:_?.intervalMs??null,refreshStable:_?.stable??!1,slowThresholdMs:e.slowThresholdMs}),{ok:r,note:n}=await (0,M.copyText)(t);tf(n??"Results copied as CSV",!r)},[tf,ez.name,_,e.slowThresholdMs,P]),tx=null===tn.medianMs||tn.medianMs<p._b||0===tn.validCount,tg=(0,s.useCallback)(async()=>{var e,t;if(null===tn.medianMs||null===tl)return;let r=[`${tn.validCount} counted trials, best ${null===tn.bestMs?"n/a":(0,f.HK)(tn.bestMs)} ms`,null===tn.iqrMs?`Spread needs ${f.vp} counted trials`:`Middle half spans ${(0,f.HK)(tn.iqrMs)} ms`,`${(e=tr.rejected).anticipation+e.falseStart+e.voided+e.wrong+e.falseAlarm+e.noResponse} trials thrown out, ${tr.rejected.falseStart} of them false starts`,null!==_&&_.stable?`Display frame ${(0,f.Om)(_.intervalMs)} ms, the floor under this score`:"Display timing could not be measured on this device"];ts>0&&r.push(`Half a frame (${(0,f.Om)(ts)} ms) taken off for display scanout. Raw median ${(0,f.HK)(tn.medianMs)} ms`);let n=await (t={headline:`${(0,f.HK)(tl)} ms`,modeName:`${ez.name} median`,lines:r,footnote:"Measured in the browser. Scores are not comparable across devices."},new Promise(e=>{let r=(0,N.$t)(1200,630);if(null===r)return void e(null);let{canvas:n,ctx:s}=r,l="system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif";s.fillStyle="#0f172a",s.fillRect(0,0,1200,630),s.fillStyle="#1e293b",s.fillRect(0,0,1200,8),s.fillStyle="#94a3b8",s.font=`500 30px ${l}`,s.fillText(t.modeName.toUpperCase(),72,110),s.fillStyle="#f8fafc",s.font=`700 168px ${l}`,s.fillText(t.headline,72,270),s.font=`400 30px ${l}`,s.fillStyle="#cbd5e1";let a=340;for(let e of t.lines.slice(0,5))s.fillText(e,72,a),a+=48;s.fillStyle="#64748b",s.font=`400 24px ${l}`,s.fillText(t.footnote,72,560),s.fillStyle="#e2e8f0",s.font=`600 28px ${l}`,s.fillText("toolbreezy.com",72,600),n.toBlob(t=>e(t),"image/png")}));if(null===n)return void tf("This browser would not draw the card");(0,C.WN)(n,`reaction-time-${e_}.png`),tf("Card saved")},[ts,tr.rejected,tf,e_,ez.name,_,tl,tn]),tb=(0,s.useCallback)(async()=>{if(null===tl)return;let e=`${window.location.origin}${window.location.pathname}?mode=${e_}&beat=${Math.round(tl)}`,{ok:t,note:r}=await (0,M.copyText)(e);tf(r??"Challenge link copied",!t)},[tf,e_,tl]),ty=(0,s.useCallback)(()=>{let t=new URLSearchParams;return t.set("mode",e_),"choice"===e_&&t.set("panels",String(e.choiceCount)),t},[e_,e.choiceCount]),tv=(0,s.useCallback)(()=>{try{window.localStorage.removeItem(x)}catch{}W({}),eO.current={},ey(!1),G(null),tf("History cleared")},[tf]),tw=(0,s.useCallback)(()=>{ec([]),eu(0),eh(!0),eL.current={running:!0,index:0,trials:ea},tm(),L(`${es[0]??"Player 1"} first. Press the panel when you are ready.`)},[es,ea,tm]),tj=(0,s.useCallback)(()=>{let e=ei+1;eu(e),eL.current={running:!0,index:e,trials:ea},eM.current=[],E([]),eP.current=new Set,eX("idle"),eG("idle",eB("idle",!0)),L(`${es[e]??`Player ${e+1}`} is up. Press the panel to begin.`)},[ei,es,ea,eB,eG,eX]),tM=(0,s.useMemo)(()=>{let e=[...ed].filter(e=>null!==e.summary.medianMs).sort((e,t)=>
1(e.summary.medianMs??0)-(t.summary.medianMs??0)),t=e[0],r=e[1];return t&&r?{ranked:e,verdict:(0,f.ZX)(t.summary,r.summary)?`Statistical tie, we cannot call it. ${t.name} and ${r.name} have overlapping middle halves, which is our stated rule for refusing to name a winner.`:`${t.name} takes it. Their middle half sits clear of ${r.name}'s, with no overlap.`}:{ranked:e,verdict:null}},[ed]),tN=em&&ed.length>=es.length,tk=eB(r,em);return(0,n.jsxs)("div",{className:"space-y-5",children:[null!==ef&&(0,n.jsxs)("div",{className:"rounded-md border border-input bg-muted/40 px-3 py-2 text-sm text-foreground",children:["Someone shared a score of ",(0,n.jsxs)("strong",{className:"tabular-nums",children:[(0,f.HK)(ef.ms)," ms"]})," in"," ",u(ef.mode).name,". That number is self-reported, we have no way to check it, so treat it as a target and not a record."]}),(0,n.jsxs)("div",{className:"flex flex-wrap items-center gap-x-5 gap-y-3",children:[(0,n.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,n.jsx)("span",{className:"text-sm font-medium text-foreground",children:"Test"}),(0,n.jsx)("div",{className:"inline-flex flex-wrap rounded-md border border-input p-0.5",children:i.map(e=>(0,n.jsx)("button",{type:"button",onClick:()=>{th(e.id)},"aria-pressed":e_===e.id,className:(0,o.cn)("rounded px-3 py-1 text-sm transition-colors",e_===e.id?"bg-primary text-primary-foreground":"text-muted-foreground hover:text-foreground"),children:e.short},e.id))})]}),(0,n.jsxs)("div",{className:"flex items-center gap-2",children:[(0,n.jsx)("label",{htmlFor:"rtt-trials",className:"text-sm font-medium text-foreground",children:"Trials per set"}),(0,n.jsx)("select",{id:"rtt-trials",value:e.trialsPerSession,onChange:e=>{tm(),tc({trialsPerSession:Number(e.target.value)})},className:"h-9 rounded-md border border-input bg-background px-2 text-sm text-foreground",children:w.map(e=>(0,n.jsx)("option",{value:e,children:e},e))})]}),"choice"===e_&&(0,n.jsxs)("div",{className:"flex items-center gap-2",children:[(0,n.jsx)("label",{htmlFor:"rtt-panels",className:"text-sm font-medium text-foreground",children:"Panels"}),(0,n.jsxs)("select",{id:"rtt-panels",value:e.choiceCount,onChange:e=>{tm(),tc({choiceCount:4===Number(e.target.value)?4:2})},className:"h-9 rounded-md border border-input bg-background px-2 text-sm text-foreground",children:[(0,n.jsx)("option",{value:2,children:"2"}),(0,n.jsx)("option",{value:4,children:"4"})]})]})]}),(0,n.jsx)("p",{className:"text-sm text-muted-foreground",children:ez.cue}),(0,n.jsx)("div",{className:(0,o.cn)("grid gap-2",4===eD?"grid-cols-2 sm:grid-cols-4":2===eD?"grid-cols-2":"grid-cols-1"),children:Array.from({length:eD}).map((e,t)=>(0,n.jsxs)("div",{ref:e=>{eE.current[t]=e},role:"button",tabIndex:0,"aria-label":eD>1?`Reaction panel ${eY[t]??t+1}`:"Reaction panel. Press space or click to start.",onPointerDown:e=>te(e,t),onKeyDown:e=>{(" "===e.key||"Enter"===e.key)&&e.preventDefault()},style:{backgroundColor:T.idle,transition:"none",animation:"none",touchAction:"manipulation",userSelect:"none",WebkitUserSelect:"none"},className:"relative flex h-56 select-none items-center justify-center rounded-lg border border-input outline-none focus-visible:ring-2 focus-visible:ring-ring sm:h-64",children:[(0,n.jsx)("span",{"data-shape":"ring","aria-hidden":"true",style:{visibility:"visible",color:"#475569",fontSize:"92px",lineHeight:1},className:"pointer-events-none absolute",children:"â"}),(0,n.jsx)("span",{"data-shape":"dot","aria-hidden":"true",style:{visibility:"hidden",color:"#0f172a",fontSize:"92px",lineHeight:1},className:"pointer-events-none absolute",children:"â"}),(0,n.jsx)("span",{"data-shape":"cross","aria-hidden":"true",style:{visibility:"hidden",color:"#0f172a",fontSize:"84px",lineHeight:1},className:"pointer-events-none absolute",children:"â"}),(0,n.jsx)("span",{"data-shape":"bang","aria-hidden":"true",style:{visibility:"hidden",color:"#fca5a5",fontSize:"84px",lineHeight:1},className:"pointer-events-none absolute",children:"â"}),(0,n.jsx)("span",{"data-part":"label",className:"pointer-events-none absolute bottom-3 left-0 right-0 px-2 text-center text-sm font-medium",style:{color:"#94a3b8"},children:tk}),eD>1&&(0,n.jsx)("span",{className:"pointer-events-none absolute left-3 top-2 text-sm font-semibold",style:{color:"#64748b"},children:eY[t]??t+1})]},t))}),(0,n.jsx)("div",{"aria-live":"polite",className:"min-h-[1.5rem] text-sm text-foreground",children:I}),(0,n.jsxs)("div",{className:"grid grid-cols-2 gap-2 sm:grid-cols-4",children:[(0,n.jsx)(q,{label:"Last trial",value:null===O?"-":`${(0,f.HK)(O)} ms`}),(0,n.jsx)(q,{label:e.scanoutCorrected?"Median, adjusted":"Median",value:null===tl?"-":`${(0,f.HK)(tl)} ms`,emphasize:!0}),(0,n.jsx)(q,{label:"Counted trials",value:`${tn.validCount} of ${eU}`}),(0,n.jsx)(q,{label:"Middle half",value:null===tn.iqrMs?`needs ${f.vp}`:`${(0,f.HK)(tn.iqrMs)} ms`})]}),P.length>0&&(0,n.jsxs)("div",{className:"space-y-1",children:[(0,n.jsx)("div",{className:"flex flex-wrap gap-1",children:P.map((t,r)=>(0,n.jsxs)("span",{title:`Trial ${r+1}: ${c(t,e.slowThresholdMs)}`,className:(0,o.cn)("inline-flex h-7 min-w-[3rem] items-center justify-center rounded border px-1 text-xs tabular-nums","valid"===t.outcome&&!d(t,e.slowThresholdMs)&&"border-input bg-muted text-foreground","valid"===t.outcome&&d(t,e.slowThresholdMs)&&"border-dashed border-input bg-muted text-muted-foreground","correctHold"===t.outcome&&"border-input bg-background text-muted-foreground","valid"!==t.outcome&&"correctHold"!==t.outcome&&"border-destructive/60 bg-background text-destructive"),children:["valid"===t.outcome&&null!==t.ms?(0,f.HK)(t.ms):null,"correctHold"===t.outcome?"hold":null,"anticipation"===t.outcome?"fast":null,"falseStart"===t.outcome?"early":null,"voided"===t.outcome?"void":null,"wrong"===t.outcome?"wrong":null,"falseAlarm"===t.outcome?"go?":null,"noResponse"===t.outcome?"none":null]},r))}),(0,n.jsx)("p",{className:"text-xs text-muted-foreground",children:"Every attempt is on the strip, thrown-out ones included. Hover any tile for the reason it was kept or dropped."})]}),"done"===r&&!em&&(0,n.jsx)("div",{className:"space-y-3 rounded-lg border border-input bg-muted/30 p-4",children:null===tn.medianMs?(0,n.jsxs)("p",{className:"text-sm text-foreground",children:["No valid trials were recorded in that set, so there is no median to show. Everything you pressed came in under the ",p._b," ms floor or landed on the wrong panel. Run it again and wait for the change rather than trying to time it."]}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"flex flex-wrap items-baseline gap-x-3 gap-y-1",children:[(0,n.jsxs)("span",{className:"text-3xl font-semibold tabular-nums text-foreground",children:[(0,f.HK)(tl??tn.medianMs)," ms"]}),(0,n.jsxs)("span",{className:"text-sm text-muted-foreground",children:["median of ",tn.validCount," counted ",1===tn.val
1idCount?"trial":"trials",e.scanoutCorrected?", with the display adjustment applied":""]}),ev&&(0,n.jsx)("span",{className:"rounded bg-primary px-2 py-0.5 text-xs font-medium text-primary-foreground",children:"Best single trial yet"})]}),(0,n.jsxs)("dl",{className:"grid grid-cols-2 gap-3 sm:grid-cols-4",children:[(0,n.jsx)(K,{label:"Best",value:null===tn.bestMs?"-":`${(0,f.HK)(tn.bestMs)} ms`}),(0,n.jsx)(K,{label:"Slowest",value:null===tn.worstMs?"-":`${(0,f.HK)(tn.worstMs)} ms`}),(0,n.jsx)(K,{label:"Middle half",value:null===tn.iqrMs?`needs ${f.vp} trials`:`${(0,f.HK)(tn.q1Ms??0)} to ${(0,f.HK)(tn.q3Ms??0)} ms`}),(0,n.jsx)(K,{label:"False starts",value:`${tr.rejected.falseStart} (${ti.toFixed(0)}%)`})]}),null===tn.iqrMs&&tn.validCount>0&&(0,n.jsxs)("p",{className:"text-sm text-muted-foreground",children:["The spread needs at least ",f.vp," counted trials. Three points would give a tight-looking range that means nothing, so we leave it blank."]}),(0,n.jsx)("p",{className:"text-sm text-muted-foreground",children:td}),null!==_&&_.stable&&"audio"!==e_&&(0,n.jsxs)("label",{className:"flex items-start gap-2 text-sm text-foreground",children:[(0,n.jsx)("input",{type:"checkbox",checked:e.scanoutCorrected,onChange:e=>tc({scanoutCorrected:e.target.checked}),className:"mt-1"}),(0,n.jsxs)("span",{children:["Subtract half a frame (",(0,f.Om)((0,p.IH)(_))," ms) as an estimate of when the pixels actually lit up. This is a model of your display, not a measurement, so it is off by default and it never changes what gets saved."]})]}),tu&&(0,n.jsxs)("p",{className:"text-sm text-foreground",children:["You used more than one input device in that set (",[...eP.current].join(", "),"). A mouse and a touchscreen have different delays, so that median is really two measurements stacked together."]}),(tr.rejected.anticipation>0||tr.rejected.wrong>0||tr.rejected.falseAlarm>0||tr.rejected.voided>0||tr.rejected.noResponse>0)&&(0,n.jsxs)("p",{className:"text-sm text-muted-foreground",children:["Thrown out: ",tr.rejected.anticipation," under the floor, ",tr.rejected.wrong," ","wrong panel, ",tr.rejected.falseAlarm," reacted to a hold signal,"," ",tr.rejected.voided," voided, ",tr.rejected.noResponse," with no response."]}),(0,n.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,n.jsx)(l.$,{type:"button",onClick:e5,children:"Run another set"}),(0,n.jsx)(l.$,{type:"button",variant:"outline",onClick:tp,children:"Copy results as CSV"}),(0,n.jsx)(l.$,{type:"button",variant:"outline",onClick:tg,disabled:tx,children:"Save result card"}),(0,n.jsx)(l.$,{type:"button",variant:"outline",onClick:tb,children:"Copy challenge link"})]}),tx&&(0,n.jsx)("p",{className:"text-xs text-muted-foreground",children:"A set with no valid median cannot be turned into a card."})]})}),(0,n.jsxs)("div",{className:"space-y-3 rounded-lg border border-input p-4",children:[(0,n.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,n.jsx)("h3",{className:"text-sm font-semibold text-foreground",children:"Pass the device"}),(0,n.jsx)(l.$,{type:"button",variant:er?"default":"outline",size:"sm",onClick:()=>{en(!er),eh(!1),ec([]),eu(0),eL.current={running:!1,index:0,trials:ea},tm()},children:er?"Leave duel mode":"Set up a duel"})]}),!er&&(0,n.jsx)("p",{className:"text-sm text-muted-foreground",children:"Two to four people, same device, same display, same mouse. That is the only setup where comparing two reaction times is fair, and it is the reason this runs on one device instead of over a link."}),er&&!em&&(0,n.jsxs)("div",{className:"space-y-3",children:[(0,n.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,n.jsx)("label",{htmlFor:"rtt-players",className:"text-sm text-foreground",children:"Players"}),(0,n.jsx)("select",{id:"rtt-players",value:es.length,onChange:e=>{let t=Number(e.target.value);el(e=>{let r=[...e];for(;r.length<t;)r.push(`Player ${r.length+1}`);return r.slice(0,t)})},className:"h-9 rounded-md border border-input bg-background px-2 text-sm text-foreground",children:[2,3,4].map(e=>(0,n.jsx)("option",{value:e,children:e},e))}),(0,n.jsx)("label",{htmlFor:"rtt-duel-trials",className:"text-sm text-foreground",children:"Trials each"}),(0,n.jsx)("select",{id:"rtt-duel-trials",value:ea,onChange:e=>eo(Number(e.target.value)),className:"h-9 rounded-md border border-input bg-background px-2 text-sm text-foreground",children:w.filter(e=>e>=f.vp).map(e=>(0,n.jsx)("option",{value:e,children:e},e))})]}),(0,n.jsx)("div",{className:"grid gap-2 sm:grid-cols-2",children:es.map((e,t)=>
1(0,n.jsx)("input",{value:e,onChange:e=>el(r=>r.map((r,n)=>n===t?e.target.value:r)),maxLength:24,"aria-label":`Player ${t+1} name`,className:"h-9 rounded-md border border-input bg-background px-2 text-sm text-foreground"},t))}),(0,n.jsx)(l.$,{type:"button",onClick:tw,children:"Start the duel"}),(0,n.jsxs)("p",{className:"text-xs text-muted-foreground",children:[ea," trials each, ",es.length," players, on this device. Duel sets are not added to your saved history."]})]}),er&&em&&!tN&&(0,n.jsxs)("div",{className:"space-y-2 text-sm text-foreground",children:[(0,n.jsxs)("p",{children:["Up now: ",(0,n.jsx)("strong",{children:es[ei]??`Player ${ei+1}`}),", trial"," ",Math.min(tr.consumed+1,ea)," of ",ea,"."]}),"done"===r&&(0,n.jsxs)(l.$,{type:"button",onClick:tj,children:["Pass to ",es[ei+1]??"the results"]})]}),er&&tN&&(0,n.jsxs)("div",{className:"space-y-2",children:[(0,n.jsxs)("table",{className:"w-full text-sm",children:[(0,n.jsx)("thead",{children:(0,n.jsxs)("tr",{className:"text-left text-muted-foreground",children:[(0,n.jsx)("th",{className:"py-1 font-medium",children:"Player"}),(0,n.jsx)("th",{className:"py-1 font-medium",children:"Median"}),(0,n.jsx)("th",{className:"py-1 font-medium",children:"Middle half"}),(0,n.jsx)("th",{className:"py-1 font-medium",children:"Counted"})]})}),(0,n.jsx)("tbody",{children:ed.map((e,t)=>(0,n.jsxs)("tr",{className:"border-t border-input",children:[(0,n.jsx)("td",{className:"py-1 text-foreground",children:e.name}),(0,n.jsx)("td",{className:"py-1 tabular-nums text-foreground",children:null===e.summary.medianMs?"no valid trials":`${(0,f.HK)(e.summary.medianMs)} ms`}),(0,n.jsx)("td",{className:"py-1 tabular-nums text-muted-foreground",children:null===e.summary.q1Ms||null===e.summary.q3Ms?"not enough trials":`${(0,f.HK)(e.summary.q1Ms)} to ${(0,f.HK)(e.summary.q3Ms)} ms`}),(0,n.jsx)("td",{className:"py-1 tabular-nums text-muted-foreground",children:e.summary.validCount})]},t))})]}),(0,n.jsx)("p",{className:"text-sm font-medium text-foreground",children:tM.verdict??"Not enough valid sets to compare."}),(0,n.jsx)("p",{className:"text-xs text-muted-foreground",children:"The rule: we only name a winner when the two middle halves do not overlap. It is a plain stated rule, not a significance test, and nobody should read it as one."}),(0,n.jsx)(l.$,{type:"button",variant:"outline",onClick:tw,children:"Run it again"})]})]}),ta.length>0&&(0,n.jsxs)("div",{className:"space-y-2 rounded-lg border border-input p-4",children:[(0,n.jsxs)("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[(0,n.jsxs)("h3",{className:"text-sm font-semibold text-foreground",children:["Your ",ez.name.toLowerCase()," history"]}),(0,n.jsxs)("span",{className:"text-xs text-muted-foreground",children:["keeping your last ",50," sets per test on this device, older ones drop off as new ones arrive"]})]}),(0,n.jsx)(F,{sessions:ta}),(0,n.jsxs)("p",{className:"text-sm text-muted-foreground",children:["Best single trial: ",null===to?"-":`${(0,f.HK)(to)} ms`," across"," ",ta.length," saved ",1===ta.length?"set":"sets",". A dashed break in the line is where your display or your input device changed, so the two stretches are not the same measurement."]}),eb?(0,n.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,n.jsxs)("span",{className:"text-sm text-foreground",children:["Delete all ",b.reduce((e,t)=>e+(U[t]?.length??0),0)," saved sets across every test?"]}),(0,n.jsx)(l.$,{type:"button",size:"sm",variant:"destructive",onClick:tv,children:"Yes, delete"}),(0,n.jsx)(l.$,{type:"button",size:"sm",variant:"ghost",onClick:()=>ey(!1),children:"Keep them"})]}):(0,n.jsx)(l.$,{type:"button",size:"sm",variant:"ghost",onClick:()=>ey(!0),children:"Clear history"})]}),null!==Z&&(0,n.jsx)("p",{className:"text-sm text-muted-foreground",children:Z}),B&&(0,n.jsx)("p",{className:"text-sm text-muted-foreground",children:"This browser refused to save that set, so your history will not carry over."}),"audio"===e_&&null===X&&ee&&(0,n.jsx)("p",{className:"text-sm text-foreground",children:"Sound could not be started in this browser, so the tone test is unavailable here."}),null!==ex&&(0,n.jsx)("p",{role:"status","aria-live":"polite",className:(0,o.cn)("text-sm",ex.bad?"max-w-[34rem] text-destructive":"text-foreground"),children:ex.text}),(0,n.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(0,n.jsx)(l.$,{type:"button",variant:"outline",onClick:tm,children:"Reset this set"}),(0,n.jsxs)("div",{className:"flex items-center gap-2",children:[(0,n.jsx)("label",{htmlFor:"rtt-slow",className:"text-sm text-muted-foreground",children:"Flag trials slower than"}),(0,n.jsx)("select",{id:"rtt-slow",value:e.slowThresholdMs,onChange:e=>tc({slowThresholdMs:Number(e.target.value)}),className:"h-9 rounded-md border border-input bg-background px-2 text-sm text-foreground",children:[400,500,600,800,1e3].map(e=>(0,n.jsxs)("option",{value:e,children:[e," ms"]},e))})]})]}),(0,n.jsx)(a.ToolActions,{getParams:ty,title:"Reaction Time Test"}),(0,n.jsxs)("p",{className:"text-xs text-muted-foreground",children:["The wait before each change is drawn at random between ",p.ey/1e3," and"," ",p._Z/1e3," seconds, so there is no rhythm to learn. Everything runs in your browser: the timing, the scoring, and your saved history. Nothing is uploaded and there is no ranking against other people, because we hold no such data."]})]})}function q({label:e,value:t,emphasize:r=!1}){return(0,n.jsxs)("div",{className:"rounded-md border border-input bg-background px-3 py-2 text-center",children:[(0,n.jsx)("div",{className:(0,o.cn)("font-semibold tabular-nums text-foreground",r?"text-2xl":"text-xl"),children:t}),(0,n.jsx)("div",{className:"text-xs text-muted-foreground",children:e})]})}function K({label:e,value:t}){return(0,n.jsxs)("div",{children:[(0,n.jsx)("dt",{className:"text-xs text-muted-foreground",children:e}),(0,n.jsx)("dd",{className:"font-medium tabular-nums text-foreground",children:t})]})}function F({sessions:e}){let t=e.map((e,t)=>({index:t,median:e.medianMs,rig:function(e){let t=null===e.refreshIntervalMs?"unknown":e.refreshIntervalMs.toFixed(1),r=e.inputTypes.length>0?[...e.inputTypes].sort().join("+"):"unknown";return`${t}|${r}`}(e)})).filter(e=>null!==e.median);if(t.length<2)return(0,n.jsx)("p",{className:"text-sm text-muted-foreground",children:"One saved set so far. A second one gives you something to compare it against."});let r=t.map(e=>e.median),s=Math.min(...r),l=Math.max(...r),a=l-s>1?l-s:1,o=t.length>1?640/(t.length-1):640,i=t.map((e,t)=>({x:t*o,y:110-(e.median-s)/a*96,rig:e.rig})),u=[],d=[],c=[];for(let e=0;e<i.length;e+=1){let t=i[e],r=e>0?i[e-1]:null;t&&(r&&r.rig!==t.rig&&(c.length>0&&u.push(c.join(" ")),d.push((r.x+t.x)/2),c=[]),c.push(`${t.x.toFixed(1)},${t.y.toFixed(1)}`))}return c.length>0&&u.push(c.join(" ")),(0,n.jsxs)("div",{className:"overflow-x-auto",children:[(0,n.jsxs)("svg",{viewBox:"0 0 640 120",className:"h-28 w-full min-w-[320px]",role:"img","aria-label":`Median reaction time across ${t.length} saved sets, from ${Math.round(s)} to ${Math.round(l)} milliseconds`,children:[d.map((e,t)=>(0,n.jsx)("line",{x1:e,y1:4,x2:e,y2:116,stroke:"currentColor",strokeDasharray:"4 4",className:"text-muted-foreground",strokeWidth:1},`b${t}`)),u.map((e,t)=>(0,n.jsx)("polyline",{points:e,fill:"none",stroke:"currentColor",className:"text-primary",strokeWidth:2},`s${t}`)),i.map((e,t)=>(0,n.jsx)("circle",{cx:e.x,cy:e.y,r:3,className:"fill-primary"},`p${t}`))]}),(0,n.jsxs)("div",{className:"flex justify-between text-xs text-muted-foreground",children:[(0,n.jsxs)("span",{children:[Math.round(l)," ms slowest set"]}),(0,n.jsxs)("span",{children:[Math.round(s)," ms fastest set"]})]})]})}},66567:(e,t,r)=>{r.d(t,{HK:()=>u,Om:()=>i,TZ:()=>l,YV:()=>s,ZX:()=>o,eh:()=>a,vp:()=>n});let n=5;function s(e,t){let r=e.length;if(0===r)return null;let n=e[0];if(1===r)return void 0===n?null:n;let s=(r-1)*(t<0?0:t>1?1:t),l=Math.floor(s),a=Math.ceil(s),o=e[l],i=e[a];return void 0===o||void 0===i?null:l===a?o:o+(i-o)*(s-l)}function l(e){return[...e].sort((e,t)=>
1e-t)}function a(e){let t=l(e),r=t.length;if(0===r)return{validCount:0,medianMs:null,q1Ms:null,q3Ms:null,iqrMs:null,bestMs:null,worstMs:null};let a=t[0],o=t[r-1],i=r>=n,u=i?s(t,.25):null,d=i?s(t,.75):null;return{validCount:r,medianMs:s(t,.5),q1Ms:u,q3Ms:d,iqrMs:null!==u&&null!==d?d-u:null,bestMs:void 0===a?null:a,worstMs:void 0===o?null:o}}function o(e,t){return null===e.q1Ms||null===e.q3Ms||null===t.q1Ms||null===t.q3Ms||e.q1Ms<=t.q3Ms&&t.q1Ms<=e.q3Ms}function i(e){return(Math.round(10*e)/10).toFixed(1)}function u(e){return String(Math.round(e))}},91134:(e,t,r)=>{r.d(t,{$I:()=>i,CQ:()=>c,IH:()=>x,SV:()=>d,ZP:()=>h,_Z:()=>a,_b:()=>s,ey:()=>l,fK:()=>m,jC:()=>p,jI:()=>f,pG:()=>o,q7:()=>u});var n=r(66567);let s=100,l=1500,a=4500,o=250,i=3e3,u=1200,d=250,c=40;function m(e,t){let r=[],s=null,l=0,a=!1,o=i=>{if(!a){if(null!==s){let e=i-s;e>0&&e<200&&r.push(e)}if(s=i,r.length>=e)return void t(function(e){let t=(0,n.TZ)(e),r=(0,n.YV)(t,.5);if(null===r||r<=0)return null;let s=Math.max(1,.1*r),l=e.filter(e=>Math.abs(e-r)<=s).length,a=e.length>0?l/e.length:0;return{intervalMs:r,stable:r>=3&&r<=40&&a>=.8,sampleCount:e.length,agreement:a}}(r));l=requestAnimationFrame(o)}};return l=requestAnimationFrame(o),()=>{a=!0,cancelAnimationFrame(l)}}function h(e){if(null===e)return"We could not measure your display timing on this device.";let t=Math.round(1e3/e.intervalMs),r=(Math.round(10*e.intervalMs)/10).toFixed(1);return e.stable?`Your display paints a new frame every ${r} ms, which is about ${t} Hz.`:`Frame timing on this device came back uneven (a typical gap of ${r} ms, but scattered), so treat the floor below as rough.`}function f(){let e=new Uint32Array(1);return crypto.getRandomValues(e),l+(e[0]??0)/0x100000000*(a-l)}function p(e,t){return!!Number.isFinite(e)&&!(e<=0)&&1e3>Math.abs(t-e)}function x(e){return null!==e&&e.stable?e.intervalMs/2:0}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.