|
2
|
2
|
`)){const s=o.trim();s===""||s.startsWith("#")||t.push(s)}const n=new Map,r=new Map;for(let o=0;o<t.length;o++){const s=t[o].length;n.has(s)||n.set(s,o),r.set(s,o+1)}const l=(o,s)=>{let c=-1,w=-1;for(let p=o;p<=s;p++){const d=n.get(p);d!==void 0&&(c===-1&&(c=d),w=r.get(p))}return c===-1?{start:0,end:t.length}:{start:c,end:w}},a=(o,s,c)=>{const{start:w,end:p}=l(o,s),d=p-w;if(d<=0)return"";const T=Math.floor(d*Math.pow(c(),1.4));return t[w+Math.min(T,d-1)]};return{size:t.length,pick:(o,s,c,w)=>{let p=a(o,s,c);if(w&&w.size>0)for(let d=0;d<3&&p!==""&&w.has(p[0]);d++)p=a(o,s,c);return p}}}async function Ye(e,t={}){const n=await $e(e,{signal:t.signal,credentials:"same-origin"},{windowId:t.windowId,source:t.source??"desktop-mode/games-dictionary"});if(!n.ok)throw new Error(`[openstation] Games dictionary failed to load (${n.status}).`);const r=Xe(await n.text());if(r.size===0)throw new Error("[openstation] Games dictionary is empty.");return r}const X=300,J=3,pe=600,me=["easy","medium","hard"],he={easy:{spawn:[3200,900],speed:[40,170],concurrentStart:1,concurrentSteps:[[20,2],[60,3],[120,4],[200,5]],bandStart:[3,4],bandSteps:[[30,3,5],[75,3,6],[150,4,8],[225,5,10],[300,6,12]]},medium:{spawn:[2400,700],speed:[75,230],concurrentStart:1,concurrentSteps:[[10,2],[40,3],[90,4],[150,5],[240,6]],bandStart:[3,5],bandSteps:[[20,4,6],[60,4,8],[120,5,10],[200,6,12],[300,7,12]]},hard:{spawn:[1700,550],speed:[110,300],concurrentStart:2,concurrentSteps:[[10,3],[30,4],[70,5],[120,6],[200,7]],bandStart:[4,6],bandSteps:[[15,5,8],[45,6,10],[90,7,12],[150,8,12]]}};function z(e){return!Number.isFinite(e)||e<0?0:Math.min(e,X)}function Y(e){return he[e]??he.easy}function Ve(e,t="easy"){const n=z(e),[r,l]=Y(t).spawn;return Math.round(r-(r-l)*n/X)}function qe(e,t="easy"){const n=z(e),[r,l]=Y(t).speed;return r+(l-r)*n/X}function je(e,t="easy"){const n=z(e),{concurrentStart:r,concurrentSteps:l}=Y(t);let a=r;for(const[o,s]of l)n>=o&&(a=s);return a}function Je(e,t="easy"){const n=z(e),{bandStart:r,bandSteps:l}=Y(t);let a={min:r[0],max:r[1]};for(const[o,s,c]of l)n>=o&&(a={min:s,max:c});return a}function Z(e){return Math.min(15,Math.floor(z(e)/20))}function ye(e,t="easy"){const n=Je(e,t);return{spawnIntervalMs:Ve(e,t),fallSpeed:qe(e,t),maxConcurrent:je(e,t),minLength:n.min,maxLength:n.max,level:Z(e)}}const Ze=900,we=.9;function Qe(e,t){const n=[];for(let r=0;r<e;r++){const l=e>1?r/(e-1)*2-1:0;n.push({vx:l*(80+t()*60),vy:-(120+t()*120),spin:(t()*2-1)*6})}return n}function et(e,t){const n=e.vy+Ze*t;return{dx:e.vx*t,dy:(e.vy+n)/2*t,dRotation:e.spin*t,vyNext:n}}function tt(e){return Math.max(0,1-e/we)}const M=2832981,Q=9323693,nt=16249832,ot=12375270,rt=15245729,V='Georgia, "Times New Roman", serif',N=26,ge=32;function ke(e,t,n){e.clear(),e.rect(0,0,t,n).fill({color:nt});for(let l=ge;l<n;l+=ge)e.moveTo(0,l).lineTo(t,l).stroke({color:ot,width:1,alpha:.55});const r=Math.min(64,Math.round(t*.08));e.moveTo(r,0).lineTo(r,n).stroke({color:rt,width:2,alpha:.7}),e.moveTo(0,n-6).lineTo(t,n-6).stroke({color:M,width:2,alpha:.25})}function at(e,t){const n=new e.Container,r={fill:M,fontSize:N,fontFamily:V},l=new e.Text({text:"",style:{...r,fill:Q}}),a=new e.Text({text:t,style:r});return n.addChild(l,a),{container:n,matched:l,rest:a,text:t,width:a.width}}function ve(e,t){const n=Math.max(0,Math.min(t,e.text.length));e.matched.text=e.text.slice(0,n),e.rest.text=e.text.slice(n),e.rest.x=n>0?e.matched.width:0}const ee=["♪","♫","♩","♬"],st=.18,te=1.1;function it(e,t,n=Math.random){const r=[],l=a=>{const o=r.indexOf(a);if(o>=0&&r.splice(o,1),a.kind==="scatter"){for(const s of a.chars)t.removeChild(s.node),s.node.destroy();return}t.removeChild(a.node),a.node.destroy()};return{launchNote(a,o,s,c,w){const p=ee[Math.floor(n()*ee.length)]??ee[0],d=new e.Text({text:p,style:{fill:Q,fontSize:30,fontFamily:V}});d.anchor.set(.5),d.x=a,d.y=o,d.zIndex=30,t.addChild(d),r.push({kind:"note",node:d,fromX:a,fromY:o,toX:s,toY:c,age:0,onArrive:w})},tearWord(a){const o=new e.Text({text:"",style:{fill:M,fontSize:N,fontFamily:V}}),s=[];for(let p=0;p<a.text.length;p++)o.text=a.text.slice(0,p),s.push(o.width);o.destroy();const c=Qe(a.text.length,n),w=[];for(let p=0;p<a.text.length;p++){const d=new e.Text({text:a.text[p],style:{fill:Q,fontSize:N,fontFamily:V}});d.anchor.set(.5),d.x=a.container.x+s[p]+7,d.y=a.container.y+N/2,d.zIndex=20,t.addChild(d),w.push({node:d,particle:c[p]})}t.removeChild(a.container),a.container.destroy({children:!0}),r.push({kind:"scatter",chars:w,age:0})},splashBlot(a,o){const s=new e.Graphics;s.circle(0,0,9).fill({color:M,alpha:.8}),s.ellipse(-12,3,4,2.5).fill({color:M,alpha:.6}),s.ellipse(11,-2,3,2).fill({color:M,alpha:.6}),s.circle(6,7,2.5).fill({color:M,alpha:.5}),s.x=a,s.y=o,s.zIndex=10,t.addChild(s),r.push({kind:"blot",node:s,age:0})},update(a){for(const o of r.slice()){if(o.age+=a,o.kind==="note"){const c=Math.min(1,o.age/st),w=1-(1-c)*(1-c);if(o.node.x=o.fromX+(o.toX-o.fromX)*w,o.node.y=o.fromY+(o.toY-o.fromY)*c*c,o.node.rotation=c*.6,c>=1){const p=o.onArrive;l(o),p()}continue}if(o.kind==="scatter"){for(const c of o.chars){const w=et(c.particle,a);c.node.x+=w.dx,c.node.y+=w.dy,c.node.rotation+=w.dRotation,c.particle.vy=w.vyNext,c.node.alpha=tt(o.age)}o.age>=we&&l(o);continue}const s=te*.4;if(o.age<=s)o.node.alpha=1;else{const c=(o.age-s)/(te-s);o.node.alpha=Math.max(0,1-c)}o.age>=te&&l(o)}},busy(){return r.length>0},clear(){for(const a of r.slice())l(a)}}}function ct(e,t){const n=document.createElement("input");n.type="text",n.autocomplete="off",n.autocapitalize="off",n.spellcheck=!1,n.setAttribute("aria-hidden","true"),n.tabIndex=-1,n.className="inkfall__key-capture",e.appendChild(n);const r=o=>{if(!(o.metaKey||o.ctrlKey||o.altKey)){if(o.key==="Backspace"){o.preventDefault(),t.onBackspace();return}if(o.key==="Escape"){o.preventDefault(),t.onEscape();return}o.key.length===1&&/[a-zA-Z]/.test(o.key)&&(o.preventDefault(),t.onLetter(o.key.toLowerCase()))}},l=()=>{n.value=""};n.addEventListener("keydown",r),n.addEventListener("input",l);const a=()=>{window.setTimeout(()=>n.focus(),0)};return e.addEventListener("pointerdown",a),{focus:()=>n.focus(),dispose:()=>{n.removeEventListener("keydown",r),n.removeEventListener("input",l),e.removeEventListener("pointerdown",a),n.remove()}}}function lt(){let e=null,t=0;const n=()=>{e=null,t=0};return{handleKey(r,l){const a=r.toLowerCase();if(a.length!==1||!/[a-z]/.test(a))return{kind:"ignored"};if(e===null){let s=null;for(const c of l)c.text[0]===a&&(!s||c.y>s.y)&&(s=c);if(!s)return{kind:"ignored"};if(e=s.id,t=1,s.text.length===1){const c=e;return n(),{kind:"completed",targetId:c}}return{kind:"locked",targetId:e,matchedCount:t}}const o=l.find(s=>s.id===e);if(!o)return n(),this.handleKey(a,l);if(o.text[t]!==a)return{kind:"typo",targetId:o.id};if(t++,t>=o.text.length){const s=o.id;return n(),{kind:"completed",targetId:s}}return{kind:"advanced",targetId:o.id,matchedCount:t}},handleBackspace(){e!==null&&(t=Math.max(1,t-1))},release:n,forget(r){e===r&&n()},state(){return{targetId:e,matchedCount:t}}}}function Ee(){return{score:0,wordsCompleted:0,streak:0,correctKeys:0,totalKeys:0,typoInCurrentWord:!1}}function dt(e){return 1+.1*Math.min(Math.max(0,e),10)}function ut(e,t,n){const r=Math.min(1,Math.max(0,t));return Math.round(10*e*(1+.5*r)*dt(n))}function Ce(e){e.correctKeys++,e.totalKeys++}function ft(e){e.totalKeys++,e.typoInCurrentWord=!0,e.streak=0}function pt(e,t,n){const r=ut(t,n,e.streak);return e.score+=r,e.wordsCompleted++,e.typoInCurrentWord?e.streak=0:e.streak++,e.typoInCurrentWord=!1,r}function mt(e){e.streak=0,e.typoInCurrentWord=!1}function _e(e){return e.totalKeys===0?100:Math.round(e.correctKeys/e.totalKeys*100)}function xe(e,t){return t<=0?0:Math.round(e.correctKeys/5*(60/t))}function ht(e,t,n,r){const l={words:e.wordsCompleted,wpm:xe(e,t),accuracy:_e(e),time:Math.round(t),level:n};return r&&(l.mode=r),{score:e.score,meta:l}}function yt(){return window.PIXI??null}const be="desktop-mode/inkfall-mode";function Se(e){switch(e){case"medium":return f("Medium");case"hard":return f("Hard");default:return f("Easy")}}function wt(e){switch(e){case"medium":return f("Brisk from the first word.");case"hard":return f("Fast ink, long words. Good luck.");default:return f("A gentle warm-up that builds.")}}function gt(){try{const e=window.localStorage.getItem(be);if(e&&me.includes(e))return e}catch{}return"easy"}function kt(e){try{window.localStorage.setItem(be,e)}catch{}}const vt=.05,Et=250;function Ct(e){const t=document.createElement("div");t.className="inkfall",e.container.appendChild(t);const n=Ke(),r=document.createElement("div");r.className="inkfall__hud";const l=document.createElement("span");l.className="inkfall__hud-score";const a=document.createElement("span");a.className="inkfall__hud-streak";const o=document.createElement("span");o.className="inkfall__hud-lives";const s=document.createElement("span");s.className="inkfall__hud-level";const c=document.createElement("button");c.type="button",c.className="inkfall__hud-sound";const w=()=>{c.textContent=n.isEnabled()?"🔊":"🔇",c.setAttribute("aria-label",n.isEnabled()?f("Mute sound effects"):f("Unmute sound effects")),c.setAttribute("aria-pressed",n.isEnabled()?"false":"true")};if(w(),c.addEventListener("click",()=>{n.setEnabled(!n.isEnabled()),w()}),r.append(l,a,o,s),e.challenge){const i=document.createElement("span");i.className="inkfall__hud-ribbon",i.textContent=K(f("Beat %1$s: %2$s"),e.challenge.challengerName,String(e.challenge.scoreToBeat)),r.appendChild(i)}r.appendChild(c),t.appendChild(r);const p=document.createElement("div");p.className="inkfall__stage",t.appendChild(p);const d=document.createElement("div");d.className="inkfall__overlay",d.hidden=!0,t.appendChild(d);const T=i=>{d.hidden=!1,d.innerHTML="";const u=document.createElement("p");u.className="inkfall__overlay-message",u.textContent=i,d.appendChild(u)};T(f("Loading the notebook…"));let x=!1,E="loading",m=null,I=null,b=null,P=null,oe=null,B=null,re=null,Ne=null,q=null;const L=lt();let C=Ee(),_=[],H=J,R=0,U=0,Te=0,ae=0,_t=1,A=gt();const F=()=>{l.textContent=K(f("Score %s"),String(C.score)),a.textContent=C.streak>1?`×${C.streak}`:"",o.textContent="●".repeat(H)+"○".repeat(Math.max(0,J-H)),s.textContent=K(f("Level %1$s · %2$s"),String(Z(R)),Se(A))},O=()=>m?.renderer.width??600,W=()=>m?.renderer.height??pe,Ie=()=>W()-10,xt=()=>_.map(i=>({id:i.id,text:i.text,y:i.sprite.container.y})),se=(i,u)=>{_=_.filter(g=>g.id!==i.id),L.forget(i.id),!u&&m&&(m.stage.removeChild(i.sprite.container),i.sprite.container.destroy({children:!0}))},Le=()=>{if(!m||!I||!oe)return;const i=ye(R,A),u=new Set(_.map(v=>v.text[0])),g=oe.pick(i.minLength,i.maxLength,Math.random,u);if(g==="")return;const y=at(I,g),h=Math.min(64,Math.round(O()*.08)),k=Math.max(h+8,O()-y.width-16);y.container.x=h+8+Math.random()*Math.max(1,k-h-8),y.container.y=-N-4,m.stage.addChild(y.container),_.push({id:_t++,text:g,sprite:y,jitter:.9+Math.random()*.2}),Te=ae},D=()=>{const i=L.state();for(const u of _)ve(u.sprite,u.id===i.targetId?i.matchedCount:0)},bt=i=>{const u=_.find(G=>G.id===i);if(!u||!b)return;const g=W(),y=(Ie()-u.sprite.container.y)/Math.max(1,g);pt(C,u.text.length,y);const h=u.sprite;se(u,!0),ve(h,h.text.length);const k=h.text[h.text.length-1],v=h.container.x+h.width/2,S=h.container.y+N/2;b.launchNote(O()/2,W()-12,v,S,()=>{b?.tearWord(h),n.wordBurst(k)}),F()},St=()=>{E="over",L.release(),D();const i=Math.min(R,X),u=ht(C,i,Z(R),A);d.hidden=!1,d.innerHTML="";const g=document.createElement("div");g.className="inkfall__over-panel";const y=document.createElement("p");y.className="inkfall__over-heading",e.challenge?y.textContent=u.score>e.challenge.scoreToBeat?f("Game Over — challenge beaten!"):f("Game Over — challenge missed."):y.textContent=f("Game Over"),g.appendChild(y);const h=document.createElement("p");h.className="inkfall__over-stats",h.textContent=K(f("Score %1$s — %2$s words, %3$s WPM, %4$s%% accuracy."),String(u.score),String(C.wordsCompleted),String(xe(C,Math.max(1,i))),String(_e(C))),g.appendChild(h);const k=document.createElement("p");k.className="inkfall__over-save",k.textContent=f("Saving your score…"),g.appendChild(k),e.submitScore(u).then(()=>{k.textContent=f("Score saved to the scoreboard.")},()=>{k.textContent=f("Your score could not be saved.")});const v=document.createElement("div");v.className="inkfall__over-actions";const S=document.createElement("button");S.type="button",S.className="inkfall__button inkfall__button--primary",S.textContent=f("Play again"),S.addEventListener("click",()=>{Ae(A)}),v.appendChild(S);const G=document.createElement("button");G.type="button",G.className="inkfall__button",G.textContent=f("Change difficulty"),G.addEventListener("click",()=>{Oe()}),v.appendChild(G);const $=document.createElement("button");$.type="button",$.className="inkfall__button",$.textContent=f("Close"),$.addEventListener("click",()=>e.close()),v.appendChild($),g.appendChild(v),d.appendChild(g)},Re=()=>{for(const i of _.slice())se(i,!1);b?.clear(),C=Ee(),H=J,R=0,U=0,L.release()},Ae=i=>{A=i,kt(i),Re(),d.hidden=!0,d.innerHTML="",E="playing",m?.ticker.start(),F(),B?.focus()},Oe=()=>{Re(),E="menu",F(),d.hidden=!1,d.innerHTML="";const i=document.createElement("div");i.className="inkfall__over-panel inkfall__menu";const u=document.createElement("p");if(u.className="inkfall__over-heading",u.textContent=f("Choose your pace"),i.appendChild(u),e.challenge){const y=document.createElement("p");y.className="inkfall__over-stats",y.textContent=K(f("Challenge from %1$s — beat %2$s."),e.challenge.challengerName,String(e.challenge.scoreToBeat)),i.appendChild(y)}const g=document.createElement("div");g.className="inkfall__menu-options";for(const y of me){const h=document.createElement("button");h.type="button",h.className="inkfall__menu-option",y===A&&h.classList.add("inkfall__menu-option--current");const k=document.createElement("span");k.className="inkfall__menu-option-label",k.textContent=Se(y),h.appendChild(k);const v=document.createElement("span");v.className="inkfall__menu-option-hint",v.textContent=wt(y),h.appendChild(v),h.addEventListener("click",S=>{S.stopPropagation(),Ae(y)}),g.appendChild(h)}i.appendChild(g),d.appendChild(i)},Mt=()=>{E==="playing"&&(E="paused",T(f("Paused — click to resume.")),m?.ticker.stop())},Nt=()=>{E==="paused"&&(E="playing",d.hidden=!0,m?.ticker.start(),B?.focus())};d.addEventListener("click",()=>{E==="paused"&&Nt()});const Tt=()=>{if(!m||!b)return;const i=Math.min(vt,m.ticker.deltaMS/1e3);if(ae+=m.ticker.deltaMS,b.update(i),E!=="playing")return;R+=i;const u=ye(R,A),g=W()/pe;U+=i*1e3,(_.length<u.maxConcurrent&&U>=u.spawnIntervalMs||_.length===0&&ae-Te>Et)&&(U=0,Le());const h=Ie();for(const k of _.slice())if(k.sprite.container.y+=u.fallSpeed*g*k.jitter*i,k.sprite.container.y+N>=h){const v=k.sprite.container.x+k.sprite.width/2;if(se(k,!1),b.splashBlot(v,h),n.miss(),mt(C),H--,D(),F(),H<=0){St();return}}},It=i=>{if(E!=="playing")return;const u=L.handleKey(i,xt());switch(u.kind){case"locked":case"advanced":Ce(C),n.letter(i),D();break;case"completed":Ce(C),n.letter(i),bt(u.targetId),D();break;case"typo":{ft(C),n.typo();const g=_.find(y=>y.id===u.targetId);g&&(g.sprite.container.alpha=.4,window.setTimeout(()=>{g.sprite.container.alpha=1},120)),F();break}}};return(async()=>{const i=ce();if(typeof i.loadModules!="function")throw new Error("[openstation] wp.os.loadModules missing.");const u=String(e.config.wordsUrl||"");if(u==="")throw new Error("[openstation] Inkfall config lacks wordsUrl.");const[,g]=await Promise.all([i.loadModules(["pixijs"]),Ye(u,{windowId:e.windowId,source:"desktop-mode/inkfall"})]);if(x)return;if(oe=g,I=yt(),!I)throw new Error("[openstation] PixiJS failed to load.");const y=new I.Application;if(await y.init({resizeTo:p,backgroundAlpha:0,antialias:!0,autoDensity:!0,resolution:Math.min(window.devicePixelRatio||1,2),sharedTicker:!1}),x){y.destroy({removeView:!0},{children:!0,texture:!0});return}m=y,m.canvas.className="inkfall__canvas",p.appendChild(m.canvas),m.stage.sortableChildren=!0,P=new I.Graphics,P.zIndex=0,m.stage.addChild(P),ke(P,O(),W()),b=it(I,m.stage),re=new ResizeObserver(()=>{if(!m||!P)return;m.resize(),ke(P,O(),W());const h=Math.min(64,Math.round(O()*.08));for(const k of _){const v=O()-k.sprite.width-16;k.sprite.container.x>v&&(k.sprite.container.x=Math.max(h+8,v))}}),re.observe(p),B=ct(t,{onLetter:It,onBackspace:()=>{L.handleBackspace(),D()},onEscape:()=>{L.release(),D()}}),Ne=ce().onWindow?.(e.windowId,{blurred:Mt,focused:()=>B?.focus()})??null,q=Tt,m.ticker.add(q),F(),Oe()})().catch(i=>{x||(T(i instanceof Error?i.message:f("Inkfall could not start.")),typeof console<"u"&&console.error("[openstation] Inkfall boot failed:",i))}),()=>{x||(x=!0,n.dispose(),Ne?.(),re?.disconnect(),B?.dispose(),m&&(q&&m.ticker.remove(q),m.ticker.stop(),b?.clear(),m.destroy({removeView:!0},{children:!0,texture:!0}),m=null),t.remove())}}const Me={id:"inkfall",title:f("Inkfall"),icon:"dashicons-edit",scoreColumns:[{key:"score",label:f("Score"),type:"number"},{key:"mode",label:f("Difficulty"),type:"text"},{key:"words",label:f("Words"),type:"number"},{key:"wpm",label:f("WPM"),type:"number"},{key:"accuracy",label:f("Accuracy"),type:"number"},{key:"time",label:f("Time"),type:"time"},{key:"level",label:f("Level"),type:"number"}],window:{width:820,height:620,minWidth:520,minHeight:420},render:e=>Ct(e)},ne=window;ne.openStationGames=ne.openStationGames||{},ne.openStationGames[Me.id]=Me})(); |