Back to Murder Mice
SOURCE / PINNED RELEASE

Made of little things.

Murder Mice

Release
d2fa0e0e36c1…
Author-recorded commit
13dc3200cde5…
License
Check the original source
Author’s source reference
nostr://npub1ny7l44q0au5xkv3mjgt6zks5f0e2ydefslq4nhh2ve2xypgc2mqqat8qx2/wss%3A%2F%2Fgit.napplet.soy%2F/n-26115e0d25b

The hash-verified playable HTML, which may contain bundled or minified code. This view does not execute it.

index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Murder Mice</title>
    <script type="module" crossorigin>function te(){const n=window;if(!n.napplet)throw new Error("window.napplet is unavailable -- runtime did not inject the napplet namespace");return n.napplet}function ot(n){const a=te()[n];if(!a)throw new Error(`window.napplet.${String(n)} is unavailable -- runtime did not inject this domain`);return a}var at={registerAction(n){return ot("keys").registerAction(n)},unregisterAction(n){ot("keys").unregisterAction(n)},onAction(n,o){return ot("keys").onAction(n,o)},async register(n,o){const a=ot("keys"),t=await a.registerAction(n),i=a.onAction(n.id,o);return{...t,close(){i.close(),a.unregisterAction(n.id)}}}};function ee(n,o){return!!(n!=null&&n[o])}function ne(n){return ee(window.napplet,n)}const oe={moveX:[{axis:0},{button:14,scale:-1},{button:15}],moveY:[{axis:1},{button:12,scale:-1},{button:13}],aimX:[{axis:2}],aimY:[{axis:3}],jump:[{button:0}],fire:[{button:7}],pause:[{button:9}]};function It(n){const o=Object.create(null);for(const[a,t]of Object.entries(n))o[a]=t.map(i=>{const l="button"in i?i.button:i.axis;if(!Number.isInteger(l)||l<0||i.scale!==void 0&&![1,-1].includes(i.scale))throw new Error("Controller bindings need nonnegative integer indices and a scale of 1 or -1.");return{...i}});return o}const Lt=(n,o=-1)=>Number.isFinite(n)?Math.max(o,Math.min(1,n)):0;function ae(n=oe,o={}){const a=It(n),t=o.deadZone??.18,i=o.buttonThreshold??.5;if(!Number.isFinite(t)||t<0||t>=1||!Number.isFinite(i)||i<=0||i>1)throw new Error("Use a dead zone from 0 up to (excluding) 1 and a button threshold above 0 through 1.");let l=new Map,s=!1,r=!1;const c=new Map,u=()=>{s=!1},g=typeof window<"u"?window:void 0;g==null||g.addEventListener("blur",u),g==null||g.document.addEventListener("visibilitychange",u);const N=o.active??(()=>!!g&&!g.document.hidden&&g.document.hasFocus()),U=o.read??(()=>{if(typeof navigator>"u"||!navigator.getGamepads)throw new Error("Gamepad API unavailable");return navigator.getGamepads()});function T(B){const x=[...l.keys()];return l.clear(),c.clear(),s=!1,{status:B,players:[],connected:[],disconnected:x}}return{poll(){var vt;if(r)return T("closed");let B;try{B=Array.from(U()).filter(d=>!!(d!=null&&d.connected))}catch(d){return T(d instanceof Error&&d.name==="SecurityError"?"blocked":"unavailable")}const x=N(),R=new Map,xt=[],St=[];for(const d of B){const I=l.get(d.index),L=(I==null?void 0:I.id)===d.id&&I.mapping===d.mapping;L||(xt.push(d.index),c.delete(d.index));const O=c.get(d.index),Pt=d.mapping==="standard"||(O==null?void 0:O.id)===d.id,Qt=(O==null?void 0:O.bindings)??a,wt=d.axes.map(q=>x?Lt(q):0),kt=d.buttons.map(q=>x?Lt(q.value,0):0),Et=Object.create(null);for(const[q,Jt]of Object.entries(Qt)){let F=0,K=0;if(x&&Pt)for(const W of Jt){let z="button"in W?kt[W.button]??0:wt[W.axis]??0;"axis"in W&&(z=Math.sign(z)*Math.max(0,(Math.abs(z)-t)/(1-t))),z*=W.scale??1,F=Math.max(F,z),K=Math.min(K,z)}const Tt=F===-K?0:F>-K?F:K,ht=Math.abs(Tt)>=i,Rt=L&&!!((vt=I.actions[q])!=null&&vt.down);Et[q]={value:Tt,down:ht,pressed:x&&s&&L&&ht&&!Rt,released:Rt&&!ht}}R.set(d.index,{index:d.index,id:d.id,mapping:d.mapping,mapped:Pt,axes:wt,buttons:kt,actions:Et})}for(const[d,I]of l){const L=R.get(d);(!L||L.id!==I.id||L.mapping!==I.mapping)&&(St.push(d),L||c.delete(d))}return l=R,s=x,{status:x?R.size?"ready":"waiting":"inactive",players:structuredClone([...R.values()]),connected:xt,disconnected:St}},remap(B,x){const R=l.get(B);if(!R||r)throw new Error("Connect and poll this controller before mapping it.");c.set(B,{id:R.id,bindings:It(x)}),s=!1},reset:u,dispose(){r=!0,T("closed"),g==null||g.removeEventListener("blur",u),g==null||g.document.removeEventListener("visibilitychange",u)}}}const C=document.querySelector("#table"),e=C.getContext("2d"),ie=document.querySelector("#turnLeft"),le=document.querySelector("#turnRight"),se=document.querySelector("#shoot"),re=document.querySelector("#pause"),mt=document.querySelector("#menu"),ce=document.querySelector("#menuTitle"),gt=document.querySelector("#playButton"),Ht=document.querySelector("#restartButton"),fe=Math.PI*.8;let m=0,H=0;const de=14,he=14,b={rifle:{name:"Rifle",unlock:1,shots:1,reload:0,muzzle:1.95},revolver:{name:"Revolver",unlock:2,shots:6,reload:3,muzzle:1.25},shotgun:{name:"Shotgun",unlock:4,shots:1,reload:3,muzzle:1.55},rocket:{name:"Rocket Launcher",unlock:7,shots:1,reload:3,muzzle:1.6},beam:{name:"Energy Beam",unlock:9,shots:1,reload:1,muzzle:1.5}},ft=Object.keys(b),At=.25;let h="rifle";const Ct=Math.PI/180*15,ue=()=>ft.filter(n=>p>=b[n].unlock).at(-1)??"rifle",S={rifle:1,shotgun:1,revolver:6,rocket:1,beam:1},v={rifle:0,shotgun:0,revolver:0,rocket:0,beam:0},Dt=1,G=.22;let M=0;function ge(){M<=0||(M=0,v.beam=b.beam.reload)}const ye=8,_=2.4,Nt=1.6,Ut=.35;let J=0,D=0;const Ft=.3,lt=10,me=.12,Kt=n=>10*n,$t=Math.PI/180*85,pe=1.2,be=40,Me=()=>Math.max(.5,5-.5*(p-1)),xe=()=>.5*(p-1),X=.25,Bt=.35;function st(){return[{x:-w+X,y:0,inward:0},{x:0,y:-k+X,inward:Math.PI/2},{x:w-X,y:0,inward:Math.PI}]}const A=[],P=[],Z=[],y=[],V=[];let E=!1,it=.5,p=1,tt=Kt(p),w=5,k=4;const et=()=>k-pe,$={left:!1,right:!1},rt={left:!1,right:!1},ct={left:0,right:0},Gt=120;ne("keys")?(at.register({id:"gun.shoot",label:"Shoot",defaultKey:" "},()=>{E=!0}).catch(()=>j()),at.register({id:"game.pause",label:"Pause",defaultKey:"p"},dt).catch(()=>j()),at.register({id:"gun.turnLeft",label:"Turn gun left",defaultKey:"ArrowLeft"},()=>{ct.left=performance.now()+Gt}).catch(()=>j()),at.register({id:"gun.turnRight",label:"Turn gun right",defaultKey:"ArrowRight"},()=>{ct.right=performance.now()+Gt}).catch(()=>j())):j();window.addEventListener("keydown",n=>{n.code==="Escape"&&!n.repeat&&dt()});window.addEventListener("keydown",n=>{(n.code==="ShiftLeft"||n.code==="ShiftRight")&&!n.repeat&&(E=!0),n.code==="ArrowLeft"&&($.left=!0),n.code==="ArrowRight"&&($.right=!0)});window.addEventListener("keyup",n=>{n.code==="ArrowLeft"&&($.left=!1),n.code==="ArrowRight"&&($.right=!1)});window.addEventListener("blur",()=>{$.left=!1,$.right=!1});let _t=!1;function j(){_t||(_t=!0,window.addEventListener("keydown",n=>{if(n.code==="Space")n.repeat||(E=!0);else if(n.code==="KeyP")n.repeat||dt();else if(n.code!=="ArrowLeft"&&n.code!=="ArrowRight")return;n.preventDefault()}))}function Wt(n,o){n.addEventListener("pointerdown",a=>{rt[o]=!0,n.setPointerCapture(a.pointerId)});for(const a of["pointerup","pointercancel","lostpointercapture"])n.addEventListener(a,()=>rt[o]=!1)}Wt(ie,"left");Wt(le,"right");se.addEventListener("pointerdown",()=>E=!0);let nt=!0,Q=!1;function pt(){nt=!0,mt.hidden=!1,ce.textContent=Q?"Paused":"Murder Mice",gt.textContent=Q?"Resume":"Play",Ht.hidden=!Q,gt.focus()}function bt(){nt=!1,Q=!0,mt.hidden=!0,E=!1,C.focus()}function dt(){nt?Q&&bt():pt()}function Se(){p=0,y.length=0,Zt(),m=0,H=0,bt()}gt.addEventListener("click",bt);Ht.addEventListener("click",Se);re.addEventListener("click",pt);var zt;const Y=(zt=window.napplet)==null?void 0:zt.gamepad,Ot=(n,o)=>typeof n=="function"?n.call(Y):n??o,ve=ae({turn:[{axis:0},{button:14,scale:-1},{button:15}],shoot:[{button:0},{button:7}],pause:[{button:9}]},Y?{read:()=>{if(!Ot(Y.available,!0))throw new Error("Controllers unavailable");return Y.getGamepads()},active:()=>Ot(Y.focused,document.hasFocus())&&!document.hidden}:{});function Pe(n,o){var t;let a=0;($.left||rt.left||n<ct.left)&&(a-=1),($.right||rt.right||n<ct.right)&&(a+=1);for(const i of o){const l=((t=i.actions.turn)==null?void 0:t.value)??0;Math.abs(l)>.2&&(a+=l)}return Math.max(-1,Math.min(1,a))}function we(){if(h==="beam"){S.beam=0,M=Dt,D=At;return}const n=Math.sin(m),o=-Math.cos(m),a=b[h].muzzle,t=h==="shotgun"?[-Ct,0,Ct]:[0];for(const i of t){const l=Math.sin(m+i),s=-Math.cos(m+i),r=h==="rocket",c=r?ye:he;A.push({x:n*a,y:et()+o*a,dx:l*c,dy:s*c,rocket:r})}S[h]--,S[h]===0&&(b[h].reload>0?v[h]=b[h].reload:S[h]=b[h].shots),D=At}const ke=()=>D<=0&&v[h]<=0&&S[h]>0;function jt(n){const o=Math.floor(Math.random()*3),a=st()[o];n.x=a.x,n.y=a.y,n.heading=a.inward+(Math.random()-.5),n.turn=0,n.hole=o,n.state="scurry",n.timer=Me()}function Ee(){const n={x:0,y:0,heading:0,speed:(1.2+Math.random()*1.3)*(1+me*(p-1)),turn:0,wiggle:Math.random()*10,state:"scurry",timer:0,hole:0};jt(n),y.push(n)}function Yt(){const n=Math.sin(m),o=-Math.cos(m);return{x:n*b.beam.muzzle,y:et()+o*b.beam.muzzle,dx:n,dy:o}}function Te(){const n=Yt();for(let o=y.length-1;o>=0;o--){const a=y[o];if(a.state==="hiding")continue;const t=(a.x-n.x)*n.dx+(a.y-n.y)*n.dy,i=Math.abs((a.x-n.x)*n.dy-(a.y-n.y)*n.dx);t>0&&i<Ft+G/2&&(Mt(a.x,a.y),y.splice(o,1))}}function Re(n,o){const a=[];for(let l=0;l<22;l++){const s=Math.random()*Math.PI*2,r=Math.random()*_*.35,c=.5+Math.random()*2.2;a.push({x:Math.cos(s)*r,y:Math.sin(s)*r,vx:Math.cos(s)*c,vy:Math.sin(s)*c,size:.45+Math.random()*.6,delay:Math.random()*.12})}const t=[];for(let l=0;l<26;l++){const s=Math.random()*Math.PI*2,r=4+Math.random()*6;t.push({vx:Math.cos(s)*r,vy:Math.sin(s)*r,life:.25+Math.random()*.35})}P.push({x:n,y:o,age:0,puffs:a,sparks:t});const i=[{x:0,y:0,r:_*.45}];for(let l=0;l<8;l++){const s=Math.random()*Math.PI*2,r=_*(.2+Math.random()*.3);i.push({x:Math.cos(s)*r,y:Math.sin(s)*r,r:.15+Math.random()*.3})}Z.push({x:n,y:o,blobs:i}),Z.length>12&&Z.shift(),J=Ut;for(let l=y.length-1;l>=0;l--){const s=y[l];s.state!=="hiding"&&Math.hypot(s.x-n,s.y-o)<_&&(Mt(s.x,s.y),y.splice(l,1))}}function Mt(n,o){const a=[{x:0,y:0,r:.32}];for(let t=0;t<9;t++){const i=Math.random()*Math.PI*2,l=.2+Math.random()*.45;a.push({x:Math.cos(i)*l,y:Math.sin(i)*l,r:.04+Math.random()*.12})}V.push({x:n,y:o,blobs:a}),V.length>be&&V.shift()}const Xt=()=>tt===0&&y.length===0;function Zt(){p=p>=lt?1:p+1,h=ue();for(const n of ft)S[n]=b[n].shots,v[n]=0;V.length=0,A.length=0,P.length=0,Z.length=0,tt=Kt(p),it=.5,D=.3,M=0}function Ie(n){D-=n;for(const o of ft)v[o]<=0||(v[o]-=n,v[o]<=0&&(S[o]=b[o].shots));E&&(Xt()?D<=0&&Zt():ke()&&we()),E=!1,it-=n,it<=0&&tt>0&&(Ee(),tt--,it=.4+Math.random()*.8);for(const o of y){if(o.timer-=n,o.state==="hiding"){o.timer<=0&&jt(o);continue}if(o.state==="scurry"&&o.timer<=0){const i=st();let l=-1;i.forEach((s,r)=>{if(r===o.hole)return;const c=Math.hypot(s.x-o.x,s.y-o.y);(l<0||c<Math.hypot(i[l].x-o.x,i[l].y-o.y))&&(l=r)}),o.hole=l,o.state="toHole"}if(o.state==="toHole"){const i=st()[o.hole],l=i.x-o.x,s=i.y-o.y,r=o.speed*n;if(o.wiggle+=n*o.speed*6,Math.hypot(l,s)<=r){o.state="hiding",o.timer=xe();continue}o.heading=Math.atan2(s,l),o.x+=Math.cos(o.heading)*r,o.y+=Math.sin(o.heading)*r;continue}Math.random()<n*1.2&&(o.turn=(Math.random()-.5)*4),o.heading+=o.turn*n,o.x+=Math.cos(o.heading)*o.speed*n,o.y+=Math.sin(o.heading)*o.speed*n,o.wiggle+=n*o.speed*6,Math.abs(o.x)>w-.3&&(o.x=Math.sign(o.x)*(w-.3),o.heading=Math.PI-o.heading),Math.abs(o.y)>k-.3&&(o.y=Math.sign(o.y)*(k-.3),o.heading=-o.heading);const a=o.y-et(),t=Math.hypot(o.x,a);t<1.2&&(o.x=o.x/t*1.2,o.y=et()+a/t*1.2,o.heading=Math.atan2(a,o.x))}for(let o=A.length-1;o>=0;o--){const a=A[o];a.x+=a.dx*n,a.y+=a.dy*n;const t=y.findIndex(i=>i.state!=="hiding"&&Math.hypot(i.x-a.x,i.y-a.y)<Ft);if(a.rocket){const i=Math.abs(a.x)>w||Math.abs(a.y)>k;(t>=0||i)&&(Re(Math.max(-w,Math.min(w,a.x)),Math.max(-k,Math.min(k,a.y))),A.splice(o,1))}else t>=0?(Mt(y[t].x,y[t].y),y.splice(t,1),A.splice(o,1)):(Math.abs(a.x)>w+1||Math.abs(a.y)>k+1)&&A.splice(o,1)}M>0&&(Te(),M<=n?ge():M-=n),J=Math.max(0,J-n);for(let o=P.length-1;o>=0;o--){P[o].age+=n;for(const a of P[o].puffs)P[o].age<a.delay||(a.x+=a.vx*n,a.y+=a.vy*n,a.vx*=Math.exp(-3*n),a.vy*=Math.exp(-3*n));P[o].age>Nt&&P.splice(o,1)}}function Vt(){const n=window.devicePixelRatio||1;C.width=Math.round(C.clientWidth*n),C.height=Math.round(C.clientHeight*n)}window.addEventListener("resize",Vt);Vt();function Le(n,o){e.fillStyle="#8a5a34",e.fillRect(0,0,n,o);const a=Math.max(40,o/8);for(let i=0,l=0;i<o;i+=a,l++)e.fillStyle=l%2?"#7f5230":"#93613a",e.fillRect(0,i,n,a-2),e.fillStyle="#5e3b20",e.fillRect(0,i+a-2,n,2);const t=e.createRadialGradient(n/2,o/2,Math.min(n,o)*.3,n/2,o/2,Math.max(n,o)*.75);t.addColorStop(0,"rgba(0,0,0,0)"),t.addColorStop(1,"rgba(0,0,0,0.55)"),e.fillStyle=t,e.fillRect(0,0,n,o)}function f(n,o,a,t,i,l,s){e.beginPath(),e.roundRect(n,o,a,t,i),e.fillStyle=l,e.fill(),e.strokeStyle="#15171a",e.lineWidth=s,e.stroke()}function Ae(n,o,a){h==="shotgun"?_e(n,o,a):h==="revolver"?Ge(n,o,a):h==="rocket"?Be(n,o,a):h==="beam"?Ce(n,o,a):Oe(n,o,a)}function Ce(n,o,a){const t=a,i=t*.04,l=v.beam<=0,s=M>0?"#e8fdff":l?"#4de8ff":"#2a6670";e.save(),e.translate(n,o),e.rotate(m),e.fillStyle="rgba(0,0,0,0.3)",e.fillRect(-t*.2+t*.08,-t*1.5+t*.1,t*.4,t*2.3),f(-t*.13,t*.2,t*.26,t*.6,t*.08,"#2b2f3a",i),f(-t*.2,-t*.6,t*.4,t*.9,t*.12,"#5b4b8a",i),f(-t*.1,-t*1.45,t*.2,t*.9,t*.05,"#3a3550",i);for(let r=0;r<3;r++)f(-t*.15,-t*1.3+r*t*.25,t*.3,t*.08,t*.03,s,i);e.fillStyle=s,e.beginPath(),e.arc(0,-t*1.48,t*.09,0,Math.PI*2),e.fill(),e.stroke(),f(-t*.08,-t*.4,t*.16,t*.45,t*.04,s,i),e.restore()}function $e(){if(M<=0)return;const n=Yt(),o=60,a=.85+Math.random()*.3,t=Math.min(1,(Dt-M)/.05,M/.1);e.save(),e.translate(n.x,n.y),e.rotate(Math.atan2(n.dy,n.dx)),e.globalAlpha=t;for(const[l,s]of[[G*3.2,"rgba(77,232,255,0.15)"],[G*1.8,"rgba(77,232,255,0.35)"],[G,"rgba(160,245,255,0.85)"],[G*.4,"rgba(255,255,255,1)"]]){const r=l*a;e.fillStyle=s,e.fillRect(0,-r/2,o,r)}const i=e.createRadialGradient(0,0,0,0,0,G*3);i.addColorStop(0,"rgba(255,255,255,1)"),i.addColorStop(1,"rgba(77,232,255,0)"),e.fillStyle=i,e.beginPath(),e.arc(0,0,G*3,0,Math.PI*2),e.fill(),e.restore()}function Be(n,o,a){const t=a,i=t*.04;e.save(),e.translate(n,o),e.rotate(m),e.fillStyle="rgba(0,0,0,0.3)",e.fillRect(-t*.2+t*.08,-t*1.6+t*.1,t*.4,t*2.4),f(-t*.18,-t*1.5,t*.36,t*2.2,t*.08,"#5d6b3a",i),f(-t*.22,-t*1.6,t*.44,t*.18,t*.05,"#3f4a26",i),f(-t*.22,t*.58,t*.44,t*.18,t*.05,"#3f4a26",i),S.rocket>0&&(e.fillStyle="#c0392b",e.beginPath(),e.moveTo(-t*.12,-t*1.6),e.lineTo(0,-t*1.78),e.lineTo(t*.12,-t*1.6),e.closePath(),e.fill(),e.stroke()),f(t*.18,-t*.55,t*.12,t*.3,t*.03,"#23272b",i),e.fillStyle="#e0b23c",e.fillRect(-t*.18,-t*1.15,t*.36,t*.08),e.restore()}function Ge(n,o,a){const t=a,i=t*.04;e.save(),e.translate(n,o),e.rotate(m),e.fillStyle="rgba(0,0,0,0.3)",e.fillRect(-t*.12+t*.08,-t*1.25+t*.1,t*.3,t*2),e.beginPath(),e.moveTo(-t*.11,t*.15),e.lineTo(t*.11,t*.15),e.quadraticCurveTo(t*.2,t*.55,t*.14,t*.8),e.quadraticCurveTo(0,t*.9,-t*.14,t*.8),e.quadraticCurveTo(-t*.2,t*.55,-t*.11,t*.15),e.closePath(),e.fillStyle="#6b3a1f",e.fill(),e.strokeStyle="#15171a",e.lineWidth=i,e.stroke(),f(-t*.06,-t*1.25,t*.12,t*1.05,t*.03,"#9aa3ab",i),f(-t*.03,-t*1.22,t*.06,t*.1,t*.02,"#4c535a",i),f(-t*.12,-t*.3,t*.24,t*.5,t*.05,"#7d868e",i),f(-t*.04,t*.12,t*.08,t*.14,t*.02,"#4c535a",i),e.fillStyle="#8e979f",e.beginPath(),e.arc(0,-t*.05,t*.22,0,Math.PI*2),e.fill(),e.stroke();for(let l=0;l<6;l++){const s=l/6*Math.PI*2-Math.PI/2;e.fillStyle=l<S.revolver?"#e0b23c":"#1b1d20",e.beginPath(),e.arc(Math.cos(s)*t*.13,-t*.05+Math.sin(s)*t*.13,t*.045,0,Math.PI*2),e.fill()}e.restore()}function _e(n,o,a){const t=a,i=t*.04;e.save(),e.translate(n,o),e.rotate(m),e.fillStyle="rgba(0,0,0,0.3)",e.fillRect(-t*.18+t*.08,-t*1.5+t*.1,t*.4,t*2.6),e.beginPath(),e.moveTo(-t*.13,t*.2),e.lineTo(t*.13,t*.2),e.lineTo(t*.21,t*1),e.quadraticCurveTo(0,t*1.1,-t*.21,t*1),e.closePath(),e.fillStyle="#7a4320",e.fill(),e.strokeStyle="#15171a",e.lineWidth=i,e.stroke(),f(-t*.22,t*.94,t*.44,t*.12,t*.05,"#23272b",i),f(-t*.12,-t*1.55,t*.12,t*1.35,t*.03,"#3a3f45",i),f(0,-t*1.55,t*.12,t*1.35,t*.03,"#3a3f45",i),e.fillStyle="#0c0d0f",e.fillRect(-t*.09,-t*1.53,t*.06,t*.05),e.fillRect(t*.03,-t*1.53,t*.06,t*.05),f(-t*.17,-t*.95,t*.34,t*.45,t*.07,"#8a5228",i);for(let l=0;l<4;l++)e.fillStyle="#5a3218",e.fillRect(-t*.14,-t*.88+l*t*.1,t*.28,t*.03);f(-t*.15,-t*.25,t*.3,t*.5,t*.05,"#4c535a",i),e.fillStyle="#c0392b",e.fillRect(-t*.15,-t*.05,t*.3,t*.08),e.restore()}function Oe(n,o,a){const t=a,i=t*.04;e.save(),e.translate(n,o),e.rotate(m),e.save(),e.translate(t*.08,t*.1),e.fillStyle="rgba(0,0,0,0.3)",e.fillRect(-t*.2,-t*1.9,t*.4,t*3),e.restore(),e.beginPath(),e.moveTo(-t*.13,t*.2),e.lineTo(t*.13,t*.2),e.lineTo(t*.2,t*1.05),e.quadraticCurveTo(0,t*1.15,-t*.2,t*1.05),e.closePath(),e.fillStyle="#5a2e17",e.fill(),e.strokeStyle="#15171a",e.lineWidth=i,e.stroke(),f(-t*.21,t*.98,t*.42,t*.12,t*.05,"#23272b",i),f(-t*.12,-t*.85,t*.24,t*.7,t*.06,"#6b3a1f",i),f(-t*.05,-t*1.85,t*.1,t*1.2,t*.02,"#3a3f45",i),f(-t*.08,-t*1.95,t*.16,t*.12,t*.02,"#23272b",i),f(-t*.14,-t*.25,t*.28,t*.5,t*.05,"#4c535a",i),f(t*.12,-t*.05,t*.18,t*.07,t*.03,"#4c535a",i),e.fillStyle="#4c535a",e.beginPath(),e.arc(t*.32,-t*.015,t*.06,0,Math.PI*2),e.fill(),e.stroke(),f(-t*.08,-t*.55,t*.16,t*.6,t*.06,"#1f2226",i),f(-t*.1,-t*.6,t*.2,t*.1,t*.03,"#2c3035",i),e.fillStyle="#7fc3e8",e.fillRect(-t*.06,-t*.58,t*.12,t*.04),e.restore()}function qe(){for(const n of st())e.save(),e.translate(n.x,n.y),e.rotate(n.inward),e.beginPath(),e.arc(-X,0,Bt,-Math.PI/2,Math.PI/2),e.closePath(),e.fillStyle="#4a2c17",e.fill(),e.beginPath(),e.arc(-X,0,Bt*.8,-Math.PI/2,Math.PI/2),e.closePath(),e.fillStyle="#120a05",e.fill(),e.restore()}function ze(){for(const n of V){e.fillStyle="#9e0f12";for(const o of n.blobs)e.beginPath(),e.arc(n.x+o.x,n.y+o.y,o.r,0,Math.PI*2),e.fill();e.fillStyle="rgba(255,255,255,0.18)",e.beginPath(),e.arc(n.x-.1,n.y-.1,.08,0,Math.PI*2),e.fill()}}function He(n){e.save(),e.translate(n.x,n.y),e.rotate(n.heading),e.lineWidth=.03,e.strokeStyle="#3a3533",e.beginPath(),e.moveTo(-.25,0);const o=Math.sin(n.wiggle)*.12;e.quadraticCurveTo(-.45,o,-.65,-o),e.strokeStyle="#d99a9a",e.lineWidth=.04,e.stroke(),e.fillStyle="#8d8682",e.strokeStyle="#3a3533",e.lineWidth=.03,e.beginPath(),e.ellipse(0,0,.28,.17,0,0,Math.PI*2),e.fill(),e.stroke();for(const a of[-1,1])e.fillStyle="#e8a5a5",e.beginPath(),e.arc(.15,a*.12,.07,0,Math.PI*2),e.fill(),e.stroke();e.fillStyle="#8d8682",e.beginPath(),e.ellipse(.27,0,.12,.09,0,0,Math.PI*2),e.fill(),e.stroke(),e.fillStyle="#f07a8a",e.beginPath(),e.arc(.39,0,.03,0,Math.PI*2),e.fill(),e.restore()}function De(){for(const n of A){if(n.rocket){Ne(n);continue}e.fillStyle="#ffd34d",e.beginPath(),e.arc(n.x,n.y,.06,0,Math.PI*2),e.fill()}}function Ne(n){e.save(),e.translate(n.x,n.y),e.rotate(Math.atan2(n.dy,n.dx)),e.scale(1.7,1.7),e.fillStyle=Math.random()<.5?"#ffb02e":"#ff5a1f",e.beginPath(),e.moveTo(-.15,-.06),e.lineTo(-.3-Math.random()*.15,0),e.lineTo(-.15,.06),e.fill(),e.fillStyle="#5d6b3a",e.fillRect(-.15,-.06,.25,.12),e.fillStyle="#c0392b",e.beginPath(),e.moveTo(.1,-.06),e.lineTo(.2,0),e.lineTo(.1,.06),e.fill(),e.restore()}function Ue(){for(const n of Z)for(const o of n.blobs){const a=n.x+o.x,t=n.y+o.y,i=e.createRadialGradient(a,t,0,a,t,o.r);i.addColorStop(0,"rgba(20,12,6,0.6)"),i.addColorStop(1,"rgba(20,12,6,0)"),e.fillStyle=i,e.beginPath(),e.arc(a,t,o.r,0,Math.PI*2),e.fill()}}function Fe(n){const o=[[1,[255,250,220]],[.8,[255,210,80]],[.6,[255,130,30]],[.4,[190,50,20]],[.2,[70,60,55]],[0,[40,38,36]]];for(let a=1;a<o.length;a++){const[t,i]=o[a-1],[l,s]=o[a];if(n>=l){const r=(n-l)/(t-l);return[0,1,2].map(c=>Math.round(s[c]+(i[c]-s[c])*r))}}return o[o.length-1][1]}function Ke(){for(const n of P){const o=n.age;if(o<.12){const a=1-o/.12,t=e.createRadialGradient(n.x,n.y,0,n.x,n.y,_*1.6);t.addColorStop(0,`rgba(255,255,230,${a})`),t.addColorStop(1,"rgba(255,200,120,0)"),e.fillStyle=t,e.beginPath(),e.arc(n.x,n.y,_*1.6,0,Math.PI*2),e.fill()}if(o<.3){const a=o/.3;e.strokeStyle=`rgba(255,240,210,${.7*(1-a)})`,e.lineWidth=.12*(1-a)+.02,e.beginPath(),e.arc(n.x,n.y,_*(.3+.9*a),0,Math.PI*2),e.stroke()}for(const a of n.puffs){const t=o-a.delay;if(t<0)continue;const i=t/(Nt-a.delay);if(i>=1)continue;const l=Math.max(0,1-i*1.8-a.size*.1),[s,r,c]=Fe(l),u=a.size*(.5+1.1*Math.sqrt(i)),g=(l>.2?.95:.6)*(1-i),N=n.x+a.x,U=n.y+a.y,T=e.createRadialGradient(N,U,0,N,U,u);T.addColorStop(0,`rgba(${s},${r},${c},${g})`),T.addColorStop(.6,`rgba(${s},${r},${c},${g*.7})`),T.addColorStop(1,`rgba(${s},${r},${c},0)`),e.fillStyle=T,e.beginPath(),e.arc(N,U,u,0,Math.PI*2),e.fill()}e.lineCap="round";for(const a of n.sparks){if(o>a.life)continue;const t=o/a.life,i=n.x+a.vx*o,l=n.y+a.vy*o;e.strokeStyle=`rgba(255,${Math.round(220-120*t)},80,${1-t})`,e.lineWidth=.05,e.beginPath(),e.moveTo(i,l),e.lineTo(i-a.vx*.04,l-a.vy*.04),e.stroke()}e.lineCap="butt"}}function We(n,o){const a=Math.max(18,Math.min(n,o)*.07);e.textAlign="center",e.textBaseline="middle",e.fillStyle="rgba(0,0,0,0.45)";const t=p>=lt,i=t?void 0:ft.find(l=>b[l].unlock===p+1);e.fillRect(0,o*.3,n,a*(i?4.3:3)),e.fillStyle="#f4e3c1",e.font=`bold ${a}px sans-serif`,e.fillText(t?`You beat all ${lt} levels!`:`Level ${p} done!`,n/2,o*.3+a),e.font=`${a*.5}px sans-serif`,e.fillText(t?"Shoot to play again":`Shoot to start level ${p+1}`,n/2,o*.3+a*2.1),i&&(e.fillStyle="#ffd34d",e.font=`bold ${a*.55}px sans-serif`,e.fillText(`You unlocked the ${b[i].name.toUpperCase()}!`,n/2,o*.3+a*3.6))}function je(n,o){const a=Math.max(14,Math.min(n,o)*.04),t=a*.6;e.textAlign="left",e.textBaseline="top",e.font=`bold ${a}px sans-serif`;const i=tt+y.length,l=v[h]>0||M>0,s=b[h],r=[`Level ${p}/${lt}`,`Mice left: ${i}`,M>0?"Firing beam!":l?`Reloading: ${Math.ceil(v[h])}s`:`Ready to shoot!${s.shots>1?` (${S[h]}/${s.shots})`:""}`];r.push(`Gun: ${s.name}`);const c=Math.max(...r.map(u=>e.measureText(u).width))+t*2;e.fillStyle="rgba(0,0,0,0.45)",e.beginPath(),e.roundRect(t*.5,t*.5,c,a*1.3*r.length+t,t*.6),e.fill(),r.forEach((u,g)=>{e.fillStyle=g===2?l?"#ff8a6b":"#9be37a":"#f4e3c1",e.fillText(u,t*1.5,t+g*a*1.3)})}let ut=performance.now();function yt(n){const o=Math.min(.05,(n-ut)/1e3);ut=n;const a=C.width,t=C.height,i=Math.min(a,t)*.12;w=a/2/i,k=t/2/i;const l=ve.poll(),s=l.status==="ready"?l.players:[];if(s.some(c=>{var u;return(u=c.actions.pause)==null?void 0:u.pressed})&&dt(),nt){s.some(c=>{var u;return(u=c.actions.shoot)==null?void 0:u.pressed})&&mt.contains(document.activeElement)&&document.activeElement.click(),E=!1,H=0,ut=n,qt(a,t,i),requestAnimationFrame(yt);return}s.some(c=>{var u;return(u=c.actions.shoot)==null?void 0:u.pressed})&&(E=!0);const r=Pe(n,s)*fe;H+=(r-H)*Math.min(1,de*o),m+=H*o,Math.abs(m)>$t&&(m=Math.sign(m)*$t,H=0),Ie(o),qt(a,t,i),requestAnimationFrame(yt)}function qt(n,o,a){if(e.save(),J>0){const t=a*.15*(J/Ut);e.translate((Math.random()-.5)*t,(Math.random()-.5)*t)}Le(n,o),e.save(),e.translate(n/2,o/2),e.scale(a,a),qe(),Ue(),ze();for(const t of y)t.state!=="hiding"&&He(t);De(),Ke(),$e(),e.restore(),Ae(n/2,o/2+et()*a,a),e.restore(),je(n,o),Xt()&&!nt&&We(n,o)}requestAnimationFrame(yt);pt();
</script>
    <style>*{box-sizing:border-box}html,body,#app{margin:0;height:100%;overflow:hidden;background:#2b1d14}.app{position:relative;width:100%;height:100%}canvas{display:block;width:100%;height:100%;outline:none;touch-action:none}.controls{position:absolute;left:0;right:0;bottom:12px;display:flex;justify-content:space-between;padding:0 12px;pointer-events:none}.controls .group{display:flex;gap:12px}.controls button[hidden]{display:none}.controls button{pointer-events:auto;width:64px;height:64px;border-radius:50%;border:2px solid #f4e3c1;background:#2b1d1499;color:#f4e3c1;font-size:30px;cursor:pointer;user-select:none;-webkit-user-select:none;touch-action:none}.controls button:active{background:#f4e3c14d}.pause{position:absolute;top:10px;right:10px;width:44px;height:44px;border-radius:10px;border:2px solid #f4e3c1;background:#2b1d1499;color:#f4e3c1;font-size:16px;cursor:pointer;letter-spacing:-2px}.menu{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;background:#140c068c;padding:12px}.menu[hidden]{display:none}.menu-panel{background:#3a2717;color:#f4e3c1;border:3px solid #f4e3c1;border-radius:16px;padding:18px 24px;max-width:420px;width:100%;max-height:100%;overflow:auto;font-family:sans-serif;text-align:center;box-shadow:0 10px 30px #00000080}.menu-title{margin:0 0 10px;font-size:34px;font-weight:700;color:#ff6b5a;text-shadow:2px 2px 0 #000}.how{list-style:none;padding:0;margin:0 0 16px;line-height:1.6;font-size:15px}.how b{color:#ffd34d}.menu-buttons{display:flex;gap:12px;justify-content:center;flex-wrap:wrap}.menu-buttons button{font-size:20px;font-weight:700;padding:10px 28px;border-radius:12px;cursor:pointer;border:2px solid #f4e3c1;background:#c0392b;color:#fff}.menu-buttons button:hover,.menu-buttons button:focus-visible{background:#e04a3a;outline:none}.menu-buttons button[hidden]{display:none}
</style>
    <meta name="napplet-type" content="my-napplet">
    <meta name="napplet-config-schema" content="{&quot;$schema&quot;:&quot;http://json-schema.org/draft-07/schema#&quot;,&quot;$version&quot;:1,&quot;type&quot;:&quot;object&quot;,&quot;additionalProperties&quot;:false,&quot;properties&quot;:{}}">
  </head>
  <body>
    <main id="app" class="app">
      <canvas id="table" tabindex="0" aria-label="The tabletop. Turn the rifle with the arrow keys and shoot with Space or Shift."></canvas>
      <button id="pause" class="pause" type="button" aria-label="Pause">❚❚</button>
      <div id="menu" class="menu">
        <div class="menu-panel">
          <div id="menuTitle" class="menu-title">Murder Mice</div>
          <ul class="how">
            <li><b>← →</b> turn your gun</li>
            <li><b>Space</b> or <b>Shift</b> shoot</li>
            <li>Shoot the mice before they hide in their holes</li>
            <li>Beat levels to unlock new guns</li>
            <li><b>P</b> or <b>Esc</b> pause</li>
          </ul>
          <div class="menu-buttons">
            <button id="playButton" type="button">Play</button>
            <button id="restartButton" type="button" hidden>Restart</button>
          </div>
        </div>
      </div>
      <div class="controls">
        <div class="group">
          <button id="turnLeft" type="button" aria-label="Turn left">⟲</button>
          <button id="turnRight" type="button" aria-label="Turn right">⟳</button>
        </div>
        <div class="group">
          <button id="shoot" type="button" aria-label="Shoot">●</button>
        </div>
      </div>
    </main>
  </body>
</html>