SOURCE / PINNED RELEASE
Made of little things.
Sprout Audit
- Release
- ee28bbf7c9be…
- Author-recorded commit
- Not provided
- License
- Check the original source
The hash-verified playable HTML, which may contain bundled or minified code. This view does not execute it.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" /><title>Sprout Audit</title><meta name="description" content="Complete a pocket greenhouse ledger by cycling four growth glyphs into a 4×4 bed, while one directed dew-link makes two cells advance in consecutive stages."><meta name="napplet-id" content="sprout-audit"><meta name="napplet-optional" content="keys storage theme">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script type="module" crossorigin>const fe="sprout-audit",ge="Sprout Audit",ye="Tap to cycle · Arrows select · Space cycles · R restarts",ve=["left","right","up","down","primary","restart"],D={slug:fe,title:ge,help:ye,actions:ve};function be(e,t,a=0){const i=Math.max(1,e.width-a*2),n=Math.max(1,e.height-a*2),s=Math.min(i,n*t),r=s/t;return{x:(e.width-s)/2,y:(e.height-r)/2,width:s,height:r}}function Q(e){let t=e>>>0;const a=()=>{t=t+1831565813>>>0;let i=t;return i=Math.imul(i^i>>>15,i|1),i^=i+Math.imul(i^i>>>7,i|61),((i^i>>>14)>>>0)/4294967296};return{next:a,int:i=>Math.floor(a()*Math.max(1,Math.floor(i))),pick:i=>{if(i.length===0)throw new Error("pick() from an empty list");return i[Math.floor(a()*i.length)]}}}const we=1/60,m={soil:"#352b2a",paper:"#f4e7c3",seed:"#e6a84a",shoot:"#63b36d",leaf:"#277c74",flower:"#c65373",dew:"#64a9d8",conflict:"#b43a36"},ke=[m.seed,m.shoot,m.leaf,m.flower],Me=[0,1,2,3,2,3,0,1,1,2,3,0,3,0,1,2],Ae=1397772884;function L(e,t){for(let a=e.length-1;a>0;a--){const i=t.int(a+1);[e[a],e[i]]=[e[i],e[a]]}return e}function te(e){const t=[];for(const a of L([0,1],e))for(const i of L([0,1],e))t.push(a*2+i);return t}function x(e,t){return t===(e+1)%4}function ne(e,t){const a=s=>{const r=new Set;for(const o of s){const c=e[o];if(c!=null){if(r.has(c))return!1;r.add(c)}}return!0};for(let s=0;s<4;s++)if(!a([0,1,2,3].map(r=>s*4+r)))return!1;for(let s=0;s<4;s++)if(!a([0,1,2,3].map(r=>r*4+s)))return!1;for(let s=0;s<2;s++)for(let r=0;r<2;r++)if(!a([0,1,2,3].map(o=>(s*2+Math.floor(o/2))*4+r*2+o%2)))return!1;const i=e[t.tail],n=e[t.head];return i==null||n===null||n===void 0||x(i,n)}function Ee(e,t,a=2){const i=[...e];let n=0;const s=()=>{if(n>=a)return;const r=i.indexOf(null);if(r<0){n++;return}for(let o=0;o<4;o++)i[r]=o,ne(i,t)&&s(),i[r]=null};return ne(i,t)&&s(),n}function Se(e){const t=te(e),a=te(e),i=L([0,1,2,3],e),n=[];for(let l=0;l<4;l++)for(let u=0;u<4;u++)n.push(i[Me[t[l]*4+a[u]]]);const s=[];for(let l=0;l<16;l++)for(let u=0;u<16;u++)l!==u&&x(n[l],n[u])&&s.push({tail:l,head:u});const r=L(s,e)[0],o=Array(16).fill(!0);o[r.tail]=o[r.head]=!1;let c=14;for(const l of L(Array.from({length:16},(u,f)=>f).filter(u=>u!==r.tail&&u!==r.head),e)){if(c<=7)break;o[l]=!1;const u=n.map((f,S)=>o[S]?f:null);Ee(u,r)===1?c--:o[l]=!0}return{solution:n,clues:o,link:r}}function ee(e,t){const a=new Set,i=r=>{for(let o=0;o<r.length;o++)for(let c=o+1;c<r.length;c++){const l=r[o],u=r[c];e[l]!==null&&e[l]===e[u]&&(a.add(l),a.add(u))}};for(let r=0;r<4;r++)i([0,1,2,3].map(o=>r*4+o));for(let r=0;r<4;r++)i([0,1,2,3].map(o=>o*4+r));for(let r=0;r<2;r++)for(let o=0;o<2;o++)i([0,1,2,3].map(c=>(r*2+Math.floor(c/2))*4+o*2+c%2));const n=e[t.tail],s=e[t.head];return n!=null&&s!==null&&s!==void 0&&!x(n,s)&&(a.add(t.tail),a.add(t.head)),a}function Pe(e,t){return e.every(a=>a!==null)&&ee(e,t).size===0}function Ie(e){const t=Se(Q(Ae));return{...t,values:t.solution.map((a,i)=>t.clues[i]?a:null),selected:t.clues.findIndex(a=>!a),points:400,won:!1,changed:null,pulse:0}}function Te(e){return{...e,values:e.solution.map((t,a)=>e.clues[a]?t:null),selected:e.clues.findIndex(t=>!t),points:400,won:!1,changed:null,pulse:0}}function ae(e,t){if(e.won||e.clues[t])return{...e,selected:t};const a=[...e.values],i=a[t]??null;a[t]=i===null?0:i===3?null:i+1;const n=ee(a,e.link).has(t)?Math.max(0,e.points-10):e.points;return{...e,values:a,selected:t,points:n,won:Pe(a,e.link),changed:t,pulse:1}}function oe(e){return be(e,1,Math.max(6,Math.min(e.width,e.height)*.045))}function Re(e,t,a){const i=oe(e);return t<i.x||a<i.y||t>=i.x+i.width||a>=i.y+i.height?null:Math.floor((a-i.y)/(i.height/4))*4+Math.floor((t-i.x)/(i.width/4))}const Le={left:[0,-1],right:[0,1],up:[-1,0],down:[1,0]};function Ce(e,t,a,i={width:320,height:400}){if(t.pressed.has("restart"))return Te(e);let n=e.pulse>0?{...e,pulse:Math.max(0,e.pulse-.12)}:e;for(const s of t.pressed){const r=Le[s];if(r){const o=Math.floor(n.selected/4),c=n.selected%4;n={...n,selected:Math.max(0,Math.min(3,o+r[0]))*4+Math.max(0,Math.min(3,c+r[1]))}}}t.pressed.has("primary")&&(n=ae(n,n.selected));for(const s of t.taps){const r=Re(i,s.x,s.y);r!==null&&(n=ae(n,r))}return n}function Y(e,t,a,i,n,s){e.beginPath(),e.moveTo(t+s,a),e.lineTo(t+i-s,a),e.quadraticCurveTo(t+i,a,t+i,a+s),e.lineTo(t+i,a+n-s),e.quadraticCurveTo(t+i,a+n,t+i-s,a+n),e.lineTo(t+s,a+n),e.quadraticCurveTo(t,a+n,t,a+n-s),e.lineTo(t,a+s),e.quadraticCurveTo(t,a,t+s,a),e.closePath()}function De(e,t,a,i,n){if(e.fillStyle=ke[t],e.strokeStyle=m.soil,e.lineWidth=Math.max(1.5,n*.1),t===0)e.beginPath(),e.ellipse(a,i,n*.48,n*.68,-.45,0,Math.PI*2),e.fill(),e.stroke();else if(t===1){e.beginPath(),e.moveTo(a,i+n*.7),e.quadraticCurveTo(a-n*.05,i,a,i-n*.65),e.stroke();for(const[s,r,o]of[[-.3,-.1,.55],[.3,-.35,-.55]])e.beginPath(),e.ellipse(a+n*s,i+n*r,n*.32,n*.18,o,0,Math.PI*2),e.fill(),e.stroke()}else if(t===2)e.beginPath(),e.moveTo(a-n*.72,i+n*.45),e.quadraticCurveTo(a-n*.5,i-n*.7,a+n*.75,i-n*.45),e.quadraticCurveTo(a+n*.45,i+n*.65,a-n*.72,i+n*.45),e.closePath(),e.fill(),e.stroke(),e.beginPath(),e.moveTo(a-n*.45,i+n*.3),e.lineTo(a+n*.5,i-n*.3),e.stroke();else{for(let s=0;s<5;s++){const r=s*Math.PI*2/5-Math.PI/2;e.beginPath(),e.ellipse(a+Math.cos(r)*n*.43,i+Math.sin(r)*n*.43,n*.35,n*.23,r,0,Math.PI*2),e.fill(),e.stroke()}e.beginPath(),e.arc(a,i,n*.24,0,Math.PI*2),e.fillStyle=m.seed,e.fill(),e.stroke()}}function qe(e,t,a,i){const n=oe(a),s=n.width/4;e.fillStyle=m.paper,e.fillRect(0,0,a.width,a.height),e.fillStyle=m.soil,Y(e,n.x-s*.04,n.y-s*.04,n.width+s*.08,n.height+s*.08,s*.12),e.fill();const r=ee(t.values,t.link);for(let h=0;h<16;h++){const R=Math.floor(h/4),me=h%4,N=n.x+me*s,B=n.y+R*s,A=s*.065;e.fillStyle=m.paper,Y(e,N+A,B+A,s-2*A,s-2*A,s*.12),e.fill(),t.clues[h]||(e.globalAlpha=.11,e.fillStyle=m.dew,e.fill(),e.globalAlpha=1),h===t.selected&&(e.strokeStyle=m.dew,e.lineWidth=Math.max(2,s*.045),e.stroke()),t.clues[h]&&(e.fillStyle=m.soil,e.beginPath(),e.arc(N+s*.18,B+s*.18,s*.045,0,Math.PI*2),e.fill());const F=t.values[h];F!=null&&De(e,F,N+s/2,B+s/2,s*.28*(!i.reducedMotion&&t.changed===h?1+t.pulse*.08:1)),r.has(h)&&(e.strokeStyle=m.conflict,e.lineWidth=Math.max(2,s*.045),e.setLineDash([s*.1,s*.07]),Y(e,N+A*1.4,B+A*1.4,s-A*2.8,s-A*2.8,s*.1),e.stroke(),e.setLineDash([]))}e.strokeStyle=m.paper,e.lineWidth=Math.max(3,s*.07),e.beginPath(),e.moveTo(n.x+n.width/2,n.y),e.lineTo(n.x+n.width/2,n.y+n.height),e.moveTo(n.x,n.y+n.height/2),e.lineTo(n.x+n.width,n.y+n.height/2),e.stroke();const o=h=>({x:n.x+(h%4+.5)*s,y:n.y+(Math.floor(h/4)+.5)*s}),c=o(t.link.tail),l=o(t.link.head),u=l.x-c.x,f=l.y-c.y,S=Math.max(1,Math.hypot(u,f)),w=u/S,k=f/S,q=c.x+w*s*.29,O=c.y+k*s*.29,p=l.x-w*s*.3,d=l.y-k*s*.3;e.strokeStyle=m.dew,e.fillStyle=m.dew,e.lineWidth=Math.max(2,s*.04),e.setLineDash([s*.08,s*.07]),e.beginPath(),e.moveTo(q,O),e.lineTo(p,d),e.stroke(),e.setLineDash([]),e.beginPath(),e.moveTo(p,d),e.lineTo(p-w*s*.14-k*s*.1,d-k*s*.14+w*s*.1),e.lineTo(p-w*s*.14+k*s*.1,d-k*s*.14-w*s*.1),e.closePath(),e.fill();for(const h of[c,l])e.save(),e.translate(h.x,h.y),e.rotate(Math.PI/4),e.strokeStyle=m.dew,e.strokeRect(-s*.34,-s*.34,s*.68,s*.68),e.restore()}const g={tickSeconds:we,init:e=>Ie(),step:Ce,isOver:e=>e.won,score:e=>e.points,draw:qe,status:e=>e.won?"Ledger complete":`${e.values.filter(t=>t!==null).length}/16 recorded`},se={ArrowLeft:"left",KeyA:"left",ArrowRight:"right",KeyD:"right",ArrowUp:"up",KeyW:"up",ArrowDown:"down",KeyS:"down",Space:"primary",Enter:"primary",KeyX:"secondary",KeyZ:"secondary",KeyP:"pause",Escape:"pause",KeyR:"restart"},Oe={left:"ArrowLeft",right:"ArrowRight",up:"ArrowUp",down:"ArrowDown",primary:"Space",secondary:"X",pause:"P",restart:"R"},Ne=new Set(["left","right","up","down"]),ie=80,Be=24;function Ke(e,t){const a=new Set,i=new Set;let n=[];const s=new Map,r=new Map;let o=null;const c=p=>a.add(p),l=p=>{const d=se[p.code];if(!d||(p.preventDefault(),i.add(d),p.repeat&&!Ne.has(d)))return;const h=performance.now();h-(r.get(d)??-1/0)<ie||(s.set(d,h),c(d))},u=p=>{const d=se[p.code];d&&i.delete(d)},f=()=>i.clear(),S=p=>{const d=t.getBoundingClientRect();return{x:p.clientX-d.left,y:p.clientY-d.top}},w=p=>{if(!p.isPrimary)return;t.focus({preventScroll:!0});const d=S(p);o={id:p.pointerId,...d}},k=p=>{if(!o||p.pointerId!==o.id)return;const d=S(p),h=d.x-o.x,R=d.y-o.y;o=null,Math.hypot(h,R)>=Be?c(Math.abs(h)>Math.abs(R)?h>0?"right":"left":R>0?"down":"up"):n.push(d)},q=()=>{o=null},O=p=>p.preventDefault();return e.addEventListener("keydown",l),e.addEventListener("keyup",u),window.addEventListener("blur",f),t.addEventListener("pointerdown",w),t.addEventListener("pointerup",k),t.addEventListener("pointercancel",q),t.addEventListener("contextmenu",O),{take(){const p={pressed:new Set(a),held:new Set(i),taps:n};return a.clear(),n=[],p},shellAction(p){const d=performance.now();d-(s.get(p)??-1/0)<ie||(r.set(p,d),c(p))},pending(){return{pressed:a,taps:n}},dispose(){e.removeEventListener("keydown",l),e.removeEventListener("keyup",u),window.removeEventListener("blur",f),t.removeEventListener("pointerdown",w),t.removeEventListener("pointerup",k),t.removeEventListener("pointercancel",q),t.removeEventListener("contextmenu",O)}}}function ze(){const e=window;if(!e.napplet)throw new Error("window.napplet is unavailable -- runtime did not inject the napplet namespace");return e.napplet}function y(e){const a=ze()[e];if(!a)throw new Error(`window.napplet.${String(e)} is unavailable -- runtime did not inject this domain`);return a}var le={shell:{proposal:{url:"https://github.com/napplet/naps/pull/101",status:"merged",checkedAt:"2026-10-31"},maturity:"supported",rationale:"Optional readiness and environment binding has focused shim and reference-runtime coverage.",evidence:["packages/nap/src/shell/shim.test.ts"]},relay:{proposal:{url:"https://github.com/napplet/naps/pull/2",status:"in-review",checkedAt:"2026-10-31"},maturity:"supported",rationale:"Relay reads, publishing, and subscription lifecycle have maintained shim and integration tests; upstream naming remains under review.",evidence:["packages/nap/src/relay/shim.test.ts"],implementationReference:{revision:"cb8f8eabb2d09439e9d174cb9eb6ef2caad0eca1",evidence:".planning/quick/260703-ghg-align-napplet-packages-with-nap-relayeve/260703-ghg-SUMMARY.md",scope:"RelayEventResult sidecar migration; subsequent changes are not covered."}},identity:{proposal:{url:"https://github.com/napplet/naps/pull/102",status:"merged",checkedAt:"2026-10-31"},maturity:"supported",rationale:"Read-only identity queries and updates have focused binding and reference-runtime coverage.",evidence:["packages/nap/src/identity/shim.test.ts"]},storage:{proposal:{url:"https://github.com/napplet/naps/pull/3",status:"in-review",checkedAt:"2026-10-31"},maturity:"supported",rationale:"Key-value binding has focused request/result tests and tutorial usage; host storage isolation remains the host responsibility.",evidence:["packages/nap/src/storage/shim.test.ts"]},inc:{proposal:{url:"https://github.com/napplet/naps/pull/104",status:"merged",checkedAt:"2026-10-31"},maturity:"supported",rationale:"Topic and channel bindings have compatibility tests and authenticated-endpoint reference-runtime coverage.",evidence:["packages/nap/src/inc/shim.ts","packages/conformance-cli/src/browser.test.ts"]},theme:{proposal:{url:"https://github.com/napplet/naps/pull/103",status:"merged",checkedAt:"2026-10-31"},maturity:"supported",rationale:"Theme queries and pushed updates have focused binding and opaque-iframe integration coverage.",evidence:["packages/nap/src/theme/shim.test.ts","packages/conformance-cli/src/browser.test.ts"]},keys:{proposal:{url:"https://github.com/napplet/naps/pull/9",status:"in-review",checkedAt:"2026-10-31"},maturity:"experimental",rationale:"Keyboard binding ships, but dedicated domain regression coverage is limited; retain explicit evaluation opt-in.",evidence:["packages/nap/src/keys/shim.ts"]},media:{proposal:{url:"https://github.com/napplet/naps/pull/10",status:"in-review",checkedAt:"2026-10-31"},maturity:"supported",rationale:"Session creation and playback binding have focused regression coverage; proposed torrent playback amendments are not implied.",evidence:["packages/nap/src/media/shim.test.ts"]},notify:{proposal:{url:"https://github.com/napplet/naps/pull/11",status:"in-review",checkedAt:"2026-10-31"},maturity:"experimental",rationale:"Notification binding ships with limited dedicated domain regression evidence; broader host integration confidence is unverified.",evidence:["packages/nap/src/notify/shim.ts"]},config:{proposal:{url:"https://github.com/napplet/naps/pull/14",status:"draft",checkedAt:"2026-10-31"},maturity:"exploratory",rationale:"Configuration binding is tested, but the upstream draft is marked MAY DEFER; use for design exploration.",evidence:["packages/nap/src/config/shim.test.ts"]},resource:{proposal:{url:"https://github.com/napplet/naps/pull/80",status:"in-review",checkedAt:"2026-10-31"},maturity:"supported",rationale:"Resource fetching has focused shim and type tests; the reverted merge and reopened proposal do not change that implementation assessment.",evidence:["packages/nap/src/resource/shim.test.ts"],implementationReference:{revision:"8e75eadf317d7fe809415e4755cd48f63cfb63f3",evidence:".planning/quick/260703-ghg-align-napplet-packages-with-nap-relayeve/260703-ghg-SUMMARY.md",scope:"Shared ResourceSidecarEntry types during the sidecar migration; subsequent resource changes are not covered."}},cvm:{proposal:{url:"https://github.com/napplet/naps/pull/31",status:"in-review",checkedAt:"2026-10-31"},maturity:"experimental",rationale:"Session and registry bindings are tested; upstream explicitly marks the contract NEEDS WORK.",evidence:["packages/nap/src/cvm/shim.test.ts"]},outbox:{proposal:{url:"https://github.com/napplet/naps/pull/32",status:"in-review",checkedAt:"2026-10-31"},maturity:"supported",rationale:"Routing and subscription bindings have focused tests and an integration regression for structured-clone boundaries.",evidence:["packages/nap/src/outbox/shim.test.ts","packages/nap/src/boundary-integration.test.ts"],implementationReference:{revision:"57db924bac022cc14c67d9fca1a4d92cf1bd4725",evidence:".planning/quick/260703-ghg-align-napplet-packages-with-nap-relayeve/260703-ghg-SUMMARY.md",scope:"RelayEventResult and stream lifecycle migration; subsequent changes are not covered."}},upload:{proposal:{url:"https://github.com/napplet/naps/pull/33",status:"in-review",checkedAt:"2026-10-31"},maturity:"supported",rationale:"Upload binding has focused tests for request/result behavior; host rail support remains runtime-specific.",evidence:["packages/nap/src/upload/shim.test.ts"]},intent:{proposal:{url:"https://github.com/napplet/naps/pull/91",status:"merged",checkedAt:"2026-10-31"},maturity:"supported",rationale:"Convention normalization and dispatch have focused binding and reference-runtime integration tests.",evidence:["packages/nap/src/intent/shim.test.ts"]},ble:{proposal:{url:"https://github.com/napplet/naps/pull/62",status:"in-review",checkedAt:"2026-10-31"},maturity:"experimental",rationale:"GATT binding has unit coverage; real-device behavior across host backends is not established by these tests.",evidence:["packages/nap/src/ble/shim.test.ts"]},webrtc:{proposal:{url:"https://github.com/napplet/naps/pull/59",status:"in-review",checkedAt:"2026-10-31"},maturity:"experimental",rationale:"Signaling and session binding have unit coverage; interoperable live peer sessions remain unverified here.",evidence:["packages/nap/src/webrtc/shim.test.ts"]},link:{proposal:{url:"https://github.com/napplet/naps/pull/53",status:"in-review",checkedAt:"2026-10-31"},maturity:"supported",rationale:"The small link-opening binding has focused regression coverage; navigation authorization stays with the host.",evidence:["packages/nap/src/link/shim.test.ts"]},count:{proposal:{url:"https://github.com/napplet/naps/pull/69",status:"in-review",checkedAt:"2026-10-31"},maturity:"experimental",rationale:"Count binding is tested, but accuracy and aggregation across real relay backends remain outside that evidence.",evidence:["packages/nap/src/count/shim.test.ts"]},lists:{proposal:{url:"https://github.com/napplet/naps/pull/68",status:"in-review",checkedAt:"2026-10-31"},maturity:"experimental",rationale:"List mutation binding is tested; concurrent real-host mutation behavior is not established by binding tests.",evidence:["packages/nap/src/lists/shim.test.ts"]},serial:{proposal:{url:"https://github.com/napplet/naps/pull/61",status:"in-review",checkedAt:"2026-10-31"},maturity:"experimental",rationale:"Serial binding has unit coverage; device and native-host integration evidence is limited.",evidence:["packages/nap/src/serial/shim.test.ts"]},fs:{proposal:{url:"https://github.com/napplet/naps/pull/88",status:"in-review",checkedAt:"2026-10-31"},maturity:"experimental",rationale:"Filesystem binding has MVP coverage; portability and authorization across storage backends remain under evaluation.",evidence:["packages/nap/src/fs/shim.test.ts"]},common:{proposal:{url:"https://github.com/napplet/naps/pull/67",status:"in-review",checkedAt:"2026-10-31"},maturity:"experimental",rationale:"Social action binding is tested; broader host integration and evolving action semantics warrant explicit opt-in.",evidence:["packages/nap/src/common/shim.test.ts"]},dm:{proposal:{url:"https://github.com/napplet/naps/pull/74",status:"draft",checkedAt:"2026-10-31"},maturity:"experimental",rationale:"Direct-message binding has shim and type coverage; transport interoperability is not established by those tests.",evidence:["packages/nap/src/dm/shim.test.ts"]},gamepad:{proposal:{url:"https://github.com/napplet/naps/pull/130",status:"in-review",checkedAt:"2026-10-31"},maturity:"supported",rationale:"Cross-repository delivery includes binding, security, runtime, and conformance verification; the reopened proposal is still subject to change.",evidence:["packages/nap/src/gamepad/shim.test.ts",".planning/phases/163-nap-gamepad-across-repositories/163-VERIFICATION.md"],implementationReference:{revision:"0b28f77e3dfa0f07dd7c0f9d61a7090ee99f3a5e",evidence:".planning/phases/163-nap-gamepad-across-repositories/163-SECURITY.md",scope:"Phase 163 binding, runtime isolation, and conformance verification."}},llm:{proposal:{url:"https://github.com/napplet/naps/pull/134",status:"draft",checkedAt:"2026-10-31"},maturity:"exploratory",rationale:"Early published draft has binding and mock-runtime tests; provider behavior and API design remain exploratory.",evidence:["packages/nap/src/llm/shim.test.ts","packages/conformance-cli/src/browser.test.ts"]}};for(const e of Object.values(le))Object.freeze(e.proposal),Object.freeze(e.evidence),e.implementationReference&&Object.freeze(e.implementationReference),Object.freeze(e);Object.freeze(le);function $e(){const e=new Map;function t(n,s){if(e.has(n))throw new Error(`NAP domain "${n}" is already registered`);e.set(n,s)}function a(n){const s=n.type.indexOf(".");if(s<=0)return!1;const r=n.type.slice(0,s),o=e.get(r);return o?(o(n),!0):!1}function i(){return Array.from(e.keys())}return{registerNap:t,dispatch:a,getRegisteredDomains:i}}var Ue=$e(),_e=Ue.registerNap,je="theme";function ce(){const e=window;if(!e.napplet?.theme)throw new Error("window.napplet.theme is unavailable -- runtime did not inject this domain");return e.napplet.theme}function We(){return ce().get()}function Fe(e){return ce().onChanged(e)}_e(je,e=>{});var pe={getItem(e){return y("storage").getItem(e)},setItem(e,t){return y("storage").setItem(e,t)},removeItem(e){return y("storage").removeItem(e)},keys(){return y("storage").keys()},instance:{getItem(e){return y("storage").instance.getItem(e)},setItem(e,t){return y("storage").instance.setItem(e,t)},removeItem(e){return y("storage").instance.removeItem(e)},keys(){return y("storage").instance.keys()}}},Ye={registerAction(e){return y("keys").registerAction(e)},unregisterAction(e){y("keys").unregisterAction(e)},onAction(e,t){return y("keys").onAction(e,t)},async register(e,t){const a=y("keys"),i=await a.registerAction(e),n=a.onAction(e.id,t);return{...i,close(){n.close(),a.unregisterAction(e.id)}}}};function Z(e){const t=e.trim().match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);if(t){const i=t[1].length===3?t[1].split("").map(n=>n+n).join(""):t[1];return[parseInt(i.slice(0,2),16),parseInt(i.slice(2,4),16),parseInt(i.slice(4,6),16)]}const a=e.trim().match(/^rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/i);return a?[Number(a[1]),Number(a[2]),Number(a[3])]:null}function Ve(e){const t=Z(e);if(!t)return 0;const[a,i,n]=t.map(s=>{const r=s/255;return r<=.03928?r/12.92:((r+.055)/1.055)**2.4});return .2126*a+.7152*i+.0722*n}function Xe(e,t,a){const i=Z(e),n=Z(t);if(!i||!n)return e;const s=i.map((r,o)=>Math.round(r+(n[o]-r)*a));return`rgb(${s[0]}, ${s[1]}, ${s[2]})`}function Ze(){return typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches}function G(e,t,a,i=Ze()){const n=Ve(e)<.4;return{background:e,foreground:t,accent:a,muted:Xe(t,e,.55),danger:n?"#ff6b6b":"#c62828",success:n?"#5ee38a":"#1b7f3b",dark:n,reducedMotion:i}}function Ge(e){return e?G("#11131a","#e8eaf2","#7aa2ff"):G("#f6f7fb","#1a1d26","#2f5fd0")}const He={left:"Move left",right:"Move right",up:"Move up",down:"Move down",primary:"Start / main action",secondary:"Secondary action",pause:"Pause",restart:"Restart"};function _(e,t){return new Promise((a,i)=>{const n=setTimeout(()=>i(new Error("timeout")),t);e.then(s=>{clearTimeout(n),a(s)},s=>{clearTimeout(n),i(s)})})}function j(e){const t=window.napplet;return!!(t&&t[e])}async function Je(e,t){if(!j("keys"))return[];const a=[];for(const i of e)try{a.push(await _(Ye.register({id:`game.${i}`,label:He[i],defaultKey:Oe[i]},()=>t(i)),2e3))}catch{}return a}async function Qe(e){if(!j("storage"))return 0;try{const t=await _(pe.getItem(`best:${e}`),2e3),a=t===null?0:Number(t);return Number.isFinite(a)&&a>0?a:0}catch{return 0}}function xe(e,t){j("storage")&&_(pe.setItem(`best:${e}`,String(t)),2e3).catch(()=>{})}function et(e){const t=window.matchMedia("(prefers-color-scheme: dark)"),a=window.matchMedia("(prefers-reduced-motion: reduce)");let i=!1,n=null;const s=l=>{n=l,e(l)},r=()=>{i||s(Ge(t.matches))},o=()=>{n&&s({...n,reducedMotion:a.matches})};r(),t.addEventListener("change",r),a.addEventListener("change",o);let c=null;if(j("theme")){const l=u=>{const f=u?.colors;!f?.background||!f.text||(i=!0,s(G(f.background,f.text,f.primary||f.text,a.matches)))};_(We(),3e3).then(l,()=>{});try{c=Fe(l)}catch{c=null}}return{close:()=>{t.removeEventListener("change",r),a.removeEventListener("change",o),c?.close()}}}const W=document.getElementById("app"),P=document.getElementById("stage"),K=P.getContext("2d"),tt=document.getElementById("score"),nt=document.getElementById("status"),at=document.getElementById("best"),st=document.getElementById("overlay"),V=document.getElementById("overlay-title"),X=document.getElementById("overlay-hint"),it=new Set([...D.actions,"primary","pause","restart"]),v=Ke(document.body,P);let H,M={width:1,height:1},C=1,z=(Date.now()^Math.floor(Math.random()*2147483647))>>>0,ue=Q(z),b=g.init(),I="ready",E=0,$=0,U=performance.now(),J=-1;function de(){const e=W.getBoundingClientRect();C=Math.max(1,Math.min(3,window.devicePixelRatio||1)),M={width:Math.max(1,Math.floor(e.width)),height:Math.max(1,Math.floor(e.height))},P.width=Math.round(M.width*C),P.height=Math.round(M.height*C),P.style.width=`${M.width}px`,P.style.height=`${M.height}px`,I==="ready"&&(b=g.init())}function T(e){I=e,W.dataset.phase=e,st.hidden=e==="playing",e==="ready"?(V.textContent=D.title,X.textContent=`Tap or press Space to start · ${D.help}`):e==="paused"?(V.textContent="Paused",X.textContent="Press P or tap to resume"):e==="over"&&(V.textContent=`Score ${g.score(b)}`,X.textContent=`Best ${E} · Tap or press Space to play again`)}function re(){z=z*1664525+1013904223>>>0,ue=Q(z),b=g.init(),$=0,J=-1,T("playing")}function rt(){const e=g.score(b);e!==J&&(J=e,tt.textContent=String(e),W.dataset.score=String(e)),nt.textContent=g.status?g.status(b):"",at.textContent=E>0?`Best ${E}`:""}function ot(){const{pressed:e,taps:t}=v.pending();if(e.has("restart")){v.take(),re();return}if(I==="ready"||I==="over"){e.has("primary")||t.length>0?(v.take(),re()):e.size&&v.take();return}if(I==="paused"){e.has("pause")||e.has("primary")||t.length>0?(v.take(),T("playing"),U=performance.now()):e.size&&v.take();return}e.has("pause")&&(v.take(),T("paused"))}function he(e){if(ot(),I==="playing"){$+=Math.min(.25,(e-U)/1e3);let t=0;for(;$>=g.tickSeconds&&t<8;)if(b=g.step(b,v.take(),ue,M),$-=g.tickSeconds,t+=1,g.isOver(b)){const a=g.score(b);a>E&&(E=a,xe(D.slug,E)),T("over");break}}U=e,K.setTransform(C,0,0,C,0,0),K.fillStyle=H.background,K.fillRect(0,0,M.width,M.height),g.draw(K,b,M,H),rt(),requestAnimationFrame(he)}const lt=et(e=>{H=e,document.documentElement.style.setProperty("--bg",e.background),document.documentElement.style.setProperty("--fg",e.foreground),document.documentElement.style.setProperty("--accent",e.accent),document.documentElement.style.setProperty("--muted",e.muted),document.documentElement.dataset.scheme=e.dark?"dark":"light"});document.addEventListener("visibilitychange",()=>{document.hidden&&I==="playing"&&T("paused")});new ResizeObserver(de).observe(W);de();T("ready");P.focus({preventScroll:!0});Qe(D.slug).then(e=>{E=Math.max(E,e)});const ct=Je([...it],e=>v.shellAction(e));window.addEventListener("pagehide",()=>{v.dispose(),lt.close(),ct.then(e=>e.forEach(t=>t.close()))});requestAnimationFrame(e=>{U=e,he(e)});
</script>
<style>:root{--bg: #11131a;--fg: #e8eaf2;--accent: #7aa2ff;--muted: #8a8fa3;color-scheme:light dark}*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden;background:var(--bg);color:var(--fg);font:500 14px/1.3 system-ui,-apple-system,Segoe UI,sans-serif}#app{position:relative;width:100%;height:100%;overflow:hidden;container-type:size;background:var(--bg);touch-action:none;user-select:none;-webkit-user-select:none}#stage{position:absolute;inset:0;display:block;outline:none}.hud{position:absolute;top:0;left:0;right:0;display:flex;gap:.6em;justify-content:space-between;align-items:center;padding:clamp(2px,1.2cqmin,10px) clamp(4px,2cqmin,14px);font-size:clamp(10px,3.6cqmin,20px);font-variant-numeric:tabular-nums;pointer-events:none;white-space:nowrap}.hud span{overflow:hidden;text-overflow:ellipsis}#score{font-weight:700}#status{color:var(--muted);flex:1;text-align:center}#best{color:var(--muted)}.overlay{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);max-width:calc(100% - 12px);padding:clamp(4px,2.5cqmin,18px) clamp(8px,4cqmin,28px);border-radius:clamp(6px,2cqmin,14px);background:color-mix(in srgb,var(--bg) 82%,transparent);border:1px solid color-mix(in srgb,var(--fg) 18%,transparent);text-align:center;pointer-events:none;backdrop-filter:blur(3px)}.overlay[hidden]{display:none}#overlay-title{margin:0;font-weight:800;font-size:clamp(12px,6cqmin,34px);color:var(--accent)}#overlay-hint{margin:.3em 0 0;font-size:clamp(9px,3cqmin,16px);color:var(--fg)}
</style>
</head>
<body>
<div id="app" data-phase="loading">
<canvas id="stage" tabindex="0" aria-label="Sprout Audit play area"></canvas>
<div class="hud" aria-live="polite">
<span id="score">0</span>
<span id="status"></span>
<span id="best"></span>
</div>
<div class="overlay" id="overlay" role="status">
<p id="overlay-title"></p>
<p id="overlay-hint"></p>
</div>
</div>
</body>
</html>
