function t(t,e){return e.startsWith(t+".")}function e(t,e){if(t==e)return"";if(e.startsWith(t+"."))return e.slice(t.length+1);throw new Error(`Incompatible refs: ${t} / ${e}`)}const i=new RegExp(/^[a-zA-Z]\w+(\.\w+)*$/);function s(t){let e=i.exec(t);if(!e)throw new Error(`Invalid expression for var: "${t}"`);return{ref:e[0]}}function n(t,e){const{ref:i}=t;return e.pick(i)}const r=new RegExp(/^([\w.]+)( ([!=]=) (.+))?$/);function a(t){let e=r.exec(t);if(!e)throw new Error(`Invalid condition expression: "${t}"`);if(!i.exec(e[1]))throw new Error(`Invalid variable in condition expression: "${t}"`);let[s,n,a,o,h]=e;if(h)try{h=JSON.parse(h.replaceAll("'",'"'))}catch{throw new Error(`Invalid value in condition expression: ${t}`)}else h=void 0;return{ref:n,eq:o,val:h}}function o(t,e){const{ref:i,eq:s,val:n}=t;let r=e.pick(i);return void 0===s?!!r:"=="==s?r===n:"!="==s?r!==n:void 0}const h=new RegExp(/^([\w.]+) = (.+)?$/);function l(e,i){switch(i.type){case"ot.reset":let s=i.detail;return null==s||s.some((i=>i==e.ref||t(i,e.ref)));case"ot.update":return i.detail.affects(e.ref);default:return!1}}class c extends Map{constructor(t){t?super(Array.from(Object.entries(t))):super(),this.forEach(((t,e)=>s(e)))}prefix(t){let e=new c;for(let[i,s]of this.entries())e.set(`${t}.${i}`,s);return e}affects(e){if(e.endsWith(".*")){let i=e.slice(0,-2);return this.has(i)||Array.from(this.keys()).some((e=>t(i,e)))}return this.has(e)||Array.from(this.keys()).some((i=>t(i,e)))}pick(i){if(this.has(i))return this.get(i);let s=Array.from(this.keys()).filter((e=>t(i,e)));if(s.length){let t={};for(let n of s){t[e(i,n)]=this.get(n)}return t}let n=Array.from(this.keys()).filter((e=>t(e,i)&&void 0!==this.get(e)));if(n.length)for(let t of n){let s=e(t,i);return r=s,a=this.get(t),r.split(".").reduce(((t,e)=>t&&e in t?t[e]:void 0),a)}var r,a}patch(t){this.forEach(((e,i)=>{!function(t,e,i){const s=t.split("."),n=s.slice(0,-1),r=s[s.length-1];let a=n.reduce(((t,e)=>e in t?t[e]:function(t,e){return t[e]={}}(t,e)),e);if(void 0===a)throw new Error(`Incompatible ref ${t}`);void 0===i?delete a[r]:a[r]=i}(i,t,e)}))}}var u=Object.freeze({__proto__:null,Changes:c});function d(t){const e=new Image;return new Promise(((i,s)=>{e.onload=()=>i(e),e.onerror=s,e.src=t}))}function m(t,e){t.style.display=e?null:"none"}function p(t,e){t.disabled=e,t.classList.toggle("ot-disabled",e)}function g(t){return t.classList.contains("ot-disabled")}function f(t,e){t.innerText=null==e?"":e}function v(t,e){t.classList.remove(...t.classList),t.classList.add(...e)}function E(t,e,i){null==i?t.removeAttribute(e):t.setAttribute(e,i)}const w=["text","number","time","date"];function b(t){return"INPUT"==t.tagName&&w.includes(t.type)}var y=Object.freeze({__proto__:null,loadImage:d,toggleDisplay:m,toggleDisabled:p,isDisabled:g,setText:f,setClasses:v,setAttr:E,setChild:function(t,e){null==e?t.replaceChildren():t.replaceChildren(e)},isTextInput:b});var k=Object.freeze({__proto__:null,choice:function(t){return t[Math.floor(Math.random()*t.length)]}});async function x(t){return new Promise(((e,i)=>{setTimeout((()=>e()),t)}))}function _(t,e=0){return window.setTimeout(t,e)}function I(t){window.clearTimeout(t)}class ${constructor(){this.timers=new Map}delay(t,e,i=0){this.timers.has(t)&&I(this.timers.get(t)),this.timers.set(t,_(e,i))}cancel(...t){0!=t.length?t.forEach((t=>{I(this.timers.get(t)),this.timers.delete(t)})):(this.timers.forEach(((t,e)=>I(t))),this.timers.clear())}}var P=Object.freeze({__proto__:null,sleep:x,delay:_,cancel:I,Timers:$});async function T(t,e){for(let[i,s]of Object.entries(e)){if("image"!==s)throw new Error("Unsupported media type to preload");try{t[i]=await d(t[i])}catch{throw new Error(`Failed to load media ${t[i]}`)}}}var A=Object.freeze({__proto__:null,preloadMedia:T});var z=Object.freeze({__proto__:null,begin:function(t){const e=`otree.${t}.beg`;performance.clearMarks(e),performance.mark(e)},end:function(t){const e=`otree.${t}.end`;performance.clearMarks(e),performance.mark(e);const i=`otree.${t}.beg`,s=`otree.${t}.measure`;return performance.clearMeasures(s),performance.measure(s,i,e),performance.getEntriesByName(s)[0].duration}});const U=new Map;function C(t,e){U.set(t,e)}class O{getParam(t){return this.elem.getAttribute(`ot-${t}`)}hasParam(t){return this.elem.hasAttribute(`ot-${t}`)}constructor(t,e){this.page=t,this.elem=e,this.init()}init(){}onEvent(t,e){this.page.onEvent(t,e.bind(this))}onElemEvent(t,e){this.page.onElemEvent(this.elem,t,e.bind(this))}setup(){this.onReset&&this.onEvent("ot.reset",this.onReset),this.onUpdate&&this.onEvent("ot.update",this.onUpdate)}}class R extends O{init(){this.hasParam("enabled")?(this.cond=a(this.getParam("enabled")),this.enabled=!1):(this.cond=null,this.enabled=!0)}onReset(t,e){this.cond?l(this.cond,t)&&(this.enabled=!1):this.enabled=!0,p(this.elem,!this.enabled)}onUpdate(t,e){this.cond&&l(this.cond,t)&&(this.enabled=o(this.cond,e),p(this.elem,!this.enabled))}onFreezing(t,e){p(this.elem,!this.enabled||e)}}function j(t){return{click:t.hasParam("click")||"BUTTON"==t.elem.tagName,touch:t.hasParam("touch"),key:!!t.hasParam("key")&&t.getParam("key")}}C("[ot-input]:is(input, select, textarea)",class extends R{init(){super.init(),this.var=s(this.getParam("input"))}setup(){this.onEvent("ot.reset",this.onReset),this.onEvent("ot.update",this.onUpdate),this.onEvent("ot.freezing",this.onFreezing),b(this.elem)?this.onElemEvent("keydown",this.onKey):this.onElemEvent("change",this.onChange)}onReset(t,e){super.onReset(t,e),l(this.var,t)&&(this.elem.value=null)}onUpdate(t,e){super.onUpdate(t,e),l(this.var,t)&&(this.elem.value=n(this.var,e))}onChange(t){this.submit()}onKey(t){"Enter"==t.code&&this.submit()}submit(){this.page.emitEvent("ot.input",{name:this.var.ref,value:this.elem.value})}});C("[ot-input]:not(input, select, textarea)",class extends R{init(){super.init(),this.ass=function(t){let e=h.exec(t);if(!e)throw new Error(`Invalid input expression: "${t}"`);if(!i.exec(e[1]))throw new Error(`Invalid variable in input expression: "${t}"`);let[s,n,r]=e;try{r=JSON.parse(e[2].replaceAll("'",'"'))}catch{throw new Error(`Invalid value in assignment expression: ${t}`)}return{ref:n,val:r}}(this.getParam("input")),this.trigger=j(this)}setup(){if(!this.trigger.key&&!this.trigger.touch&&!this.trigger.click)throw new Error("custom ot-input missing any ot-click ot-key ot-touch");this.onEvent("ot.reset",this.onReset),this.onEvent("ot.update",this.onUpdate),this.onEvent("ot.freezing",this.onFreezing),this.trigger.key&&this.onEvent("keydown",this.onKey),this.trigger.touch&&this.onElemEvent("touchend",this.onClick),this.trigger.click&&this.onElemEvent("click",this.onClick)}onClick(t){g(this.elem)||(t.preventDefault(),this.submit())}onKey(t){g(this.elem)||t.code==this.trigger.key&&(t.preventDefault(),this.submit())}submit(){this.page.emitEvent("ot.input",{name:this.ass.ref,value:this.ass.val})}});C("[ot-emit]",class extends R{init(){super.init(),this.evtype=this.getParam("emit"),this.trigger=j(this)}setup(){this.onEvent("ot.reset",this.onReset),this.onEvent("ot.update",this.onUpdate),this.onEvent("ot.freezing",this.onFreezing),this.trigger.key&&this.onEvent("keydown",this.onKey),this.trigger.touch&&this.onElemEvent("touchend",this.onClick),this.trigger.click&&this.onElemEvent("click",this.onClick)}onClick(t){g(this.elem)||(t.preventDefault(),this.submit())}onKey(t){g(this.elem)||t.code==this.trigger.key&&(t.preventDefault(),this.submit())}submit(){this.page.emitEvent(this.evtype)}});C("[ot-class]",class extends O{init(){this.var=s(this.getParam("class")),this.defaults=Array.from(this.elem.classList)}onReset(t,e){l(this.var,t)&&v(this.elem,this.defaults)}onUpdate(t,e){if(l(this.var,t)){let t=this.defaults.slice(),i=n(this.var,e);i&&t.push(i),v(this.elem,t)}}});C("[ot-text]",class extends O{init(){this.var=s(this.getParam("text"))}onReset(t,e){l(this.var,t)&&f(this.elem,null)}onUpdate(t,e){l(this.var,t)&&f(this.elem,n(this.var,e))}});C("[ot-img]",class extends O{init(){this.var=s(this.getParam("img"))}onReset(t,e){l(this.var,t)&&this.replaceImg(new Image)}onUpdate(t,e){if(l(this.var,t)){let t=n(this.var,e);this.replaceImg(t)}}replaceImg(t){if(t&&!(t instanceof Image))throw new Error(`Invalid value for image: ${t}, expecting Imge instance`);let e=this.elem.attributes;this.elem.replaceWith(t),this.elem=t;for(let t of e)t.name.startsWith("ot-")||"src"==t.name||this.elem.setAttribute(t.name,t.value)}});class F extends O{get name(){throw new Error("name getter should be defined")}init(){this.var=s(this.getParam(this.name))}onReset(t,e){l(this.var,t)&&E(this.elem,this.name,null)}onUpdate(t,e){l(this.var,t)&&E(this.elem,this.name,n(this.var,e))}}["height","width","min","max","low","high","optimum","value"].forEach((t=>{C(`[ot-${t}]`,class extends F{get name(){return t}})}));C("[ot-if]",class extends O{init(){this.cond=a(this.getParam("if"))}onReset(t){l(this.cond,t)&&m(this.elem,!1)}onUpdate(t,e){l(this.cond,t)&&m(this.elem,o(this.cond,e))}});class L{constructor(t){this.body=t||document.body,this.init()}init(){let t=this;U.forEach(((e,i)=>{this.body.querySelectorAll(i).forEach((i=>{new e(t,i).setup()}))})),this.reset(),this.onEvent("ot.status",(t=>this.onStatus(t.detail))),this.onEvent("ot.input",(t=>this.onInput(t.detail.name,t.detail.value))),this.onEvent("ot.update",(t=>this.onUpdate(t.detail))),this.onEvent("ot.timeout",(t=>this.onTimeout(t.detail)))}onEvent(t,e){this.body.addEventListener(t,(t=>e(t,t.detail)))}onElemEvent(t,e,i){t.addEventListener(e,(t=>i(t,t.detail)))}waitForEvent(t){let e=this.body;return new Promise((i=>{e.addEventListener(t,(function s(n){i(n),e.removeEventListener(t,s)}))}))}waitForEvents(t){return Promise.race(t.map((t=>this.waitForEvent(t))))}emitEvent(t,e){setTimeout((()=>this.body.dispatchEvent(new CustomEvent(t,{detail:e}))))}emitElemEvent(t,e,i){setTimeout((()=>t.dispatchEvent(new CustomEvent(e,{detail:i}))))}reset(t){void 0===t||Array.isArray(t)||(t=[t]),this.emitEvent("ot.reset",t)}update(t){t instanceof c||(t=new c(t)),this.emitEvent("ot.update",t)}timeout(t){this.emitEvent("ot.timeout",t)}freezeInputs(){this.emitEvent("ot.freezing",!0)}unfreezeInputs(){this.emitEvent("ot.freezing",!1)}submitInputs(t){this.body.querySelectorAll(`[ot-input="${t}"]`).forEach((e=>{this.emitEvent("ot.input",{name:t,value:e.value})}))}submit(){this.body.querySelector("form").submit()}onStatus(t){}onInput(t,e){}onUpdate(t){}onTimeout(t){}}class S{constructor(t){this.page=t,this.config={},this.trial={},this.status={},this.feedback=void 0}setConfig(t){this.config=t,this.page.update({config:t})}resetTrial(){this.trial={},this.status={},this.feedback=void 0,this.page.reset(["trial","status","feedback"]),this.loadTrial()}async startTrial(t){this.trial=t,this.config.media_fields&&await T(t,this.config.media_fields),this.updateStatus({trialStarted:!0}),this.page.update({trial:t})}updateTrial(t){let e=new c(t);e.patch(this.trial),this.page.update(e.prefix("trial"))}updateStatus(t){let e=this.status;Object.assign(e,t),this.page.emitEvent("ot.status",t),t.trialStarted&&this.page.emitEvent("ot.trial.started"),t.trialCompleted&&this.page.emitEvent("ot.trial.completed"),t.gameOver&&this.page.emitEvent("ot.game.over"),this.page.update(new c(t).prefix("status"))}setFeedback(t){this.feedback=t,this.page.update({feedback:this.feedback})}clearFeedback(){this.feedback=void 0,this.page.reset("feedback")}setProgress(t){this.progress=t,this.page.update({progress:this.progress})}resetProgress(){this.progress=void 0,this.page.reset("progress")}loadTrial(){throw new Error("Implement the `game.loadTrial`")}async playTrial(){this.resetTrial(),await this.page.waitForEvents(["ot.trial.completed","ot.game.over"]),await x(this.config.post_trial_pause)}async playIterations(){for(;!this.status.gameOver;)await this.playTrial()}}class D{constructor(t){this.page=t,this.timers=new $,this.phases=[],this.timeout=null,this.page.onEvent("ot.trial.started",(()=>this.start())),this.page.onEvent("ot.trial.completed",(()=>this.stop()))}start(){this.phases&&this.phases.forEach(((t,e)=>{let i=Object.assign({},t);delete i.at,this.timers.delay(`phase-${e}`,(()=>{this.page.update(i)}),t.at)})),this.timeout&&this.timers.delay("timeout",(()=>{this.stop(),this.page.timeout(this.timeout)}),this.timeout)}stop(){this.timers.cancel()}}void 0===window.otree&&(window.otree={}),window.addEventListener("load",(function(){if(window.otree.page=new L(document.body),window.otree.game=new S(otree.page),window.otree.schedule=new D(otree.page),!window.main)throw new Error("You need to define global `function main()` to make otree work");window.main()})),Object.assign(window.otree,{utils:{dom:y,random:k,timers:P,measurement:z,changes:u,trials:A},directives:{DirectiveBase:O,registerDirective:C}});