Back to Hush Orchard
SOURCE / PINNED RELEASE

Made of little things.

Hush Orchard

Release
86644e12d175…
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.

index.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" /><title>Hush Orchard</title><meta name="description" content="Cultivate a hidden six-letter seed word by reading knots, directional twigs, and empty rings across seven deliberate guesses."><meta name="napplet-id" content="hush-orchard"><meta name="napplet-optional" content="keys storage theme">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    
    
    <script type="module" crossorigin>const de="hush-orchard",he="Hush Orchard",me="Arrows choose letters · Space submits · X clears · R restarts",ge=["left","right","up","down","primary","secondary"],O={slug:de,title:he,help:me,actions:ge};function fe(e,n,i=0){const r=Math.max(1,e.width-i*2),t=Math.max(1,e.height-i*2),s=Math.min(r,t*n),a=s/n;return{x:(e.width-s)/2,y:(e.height-a)/2,width:s,height:a}}const d=6,Y=7,ye=1/60,ee=18,P="ABCDEFGHIJKLMNOPQRSTUVWXYZ",te=["PLANET","LETTER","BRANCH","CLOVER","GARDEN","MEADOW","ORCHID","PETALS","POLLEN","SPROUT","TIMBER","VIOLET"],ve=[...te,"ACORNS","BASKET","BREEZE","BRIGHT","CANOPY","CHERRY","COPPER","DAWNED","DEWING","EARTHY","FALLOW","FINCHY","FLORAL","GENTLE","GOLDEN","GROVES","HARVEST","LANTER","LEAFED","MELLOW","MOSSER","NESTLE","NIGHTY","PEBBLE","RUSSET","SHADED","SILKEN","STARRY","THORNS","VERNAL","WILLOW","WINDUP"],be=new Set(ve),h={sky:"#17243b",soil:"#543d35",paper:"#f3e6c8",sprout:"#73c982",twig:"#e2a85f",empty:"#8fa3b8",danger:"#db6b66"};function we(e,n){const i=Array.from({length:d},()=>"empty"),r=Array.from({length:d},()=>!0);for(let t=0;t<d;t+=1)n[t]===e[t]&&(i[t]="knot",r[t]=!1);for(let t=0;t<d;t+=1){if(i[t]==="knot")continue;let s=-1,a=1/0;for(let o=0;o<d;o+=1){if(!r[o]||e[o]!==n[t])continue;const p=Math.abs(o-t);(p<a||p===a&&o<s)&&(s=o,a=p)}s>=0&&(r[s]=!1,i[t]=s<t?"left":"right")}return i}function ne(e){return{hidden:e.pick(te),edit:Array.from({length:d},()=>null),cursor:0,rows:[],attemptsRemaining:Y,result:"active",points:0,invalid:!1,revealTicks:0}}function re(e){if(e.result!=="active"||e.edit.some(p=>p===null))return e;const n=e.edit.join("");if(!be.has(n))return{...e,invalid:!0};const i=we(e.hidden,n),r=[...e.rows,{word:n,feedback:i}],t=e.attemptsRemaining-1,s=n===e.hidden,a=s?"won":t===0?"lost":"active",o=s?Math.max(100,700-e.rows.length*100):0;return{...e,rows:r,attemptsRemaining:t,result:a,points:o,invalid:!1,revealTicks:ee,edit:Array.from({length:d},()=>null),cursor:0}}function ie(e,n){const i=[...e.edit];i[e.cursor]=n;let r=e.cursor;for(let t=1;t<=d;t+=1){const s=(e.cursor+t)%d;if(i[s]===null){r=s;break}}return{...e,edit:i,cursor:r,invalid:!1}}function se(e){const n=Math.max(5,Math.min(18,Math.min(e.width,e.height)*.025)),i=Math.max(1,e.width-n*2),r=Math.max(1,e.height-n*2),t=fe(e,i/r,n),s=Math.max(1,Math.min(4,t.height*.008)),a=t.height>=430,o=a?Math.min(34,t.height*.062):t.height*.065,p=a?Math.min(50,Math.max(44,t.height*.088)):t.height*.07,m=a?Math.min(48,Math.max(44,t.height*.083)):t.height*.078,w=t.height-o-p-m*5-s*4;return{x:t.x,y:t.y,width:t.width,rowTop:t.y+o+s,rowH:w/Y,keyboardTop:t.y+o+s+w+s,keyW:t.width/6,keyH:m,submitY:t.y+t.height-p,submitH:p}}function ke(e,n,i){let r=e;const t=se(i);for(const s of n.taps){if(s.x<t.x||s.x>t.x+t.width)continue;const a=Math.floor((s.y-t.rowTop)/t.rowH);if(a===r.rows.length&&a<Y){const o=Math.floor((s.x-t.x)/(t.width/d));r={...r,cursor:Math.max(0,Math.min(d-1,o)),invalid:!1};continue}if(s.y>=t.keyboardTop&&s.y<t.keyboardTop+t.keyH*5){const o=Math.floor((s.y-t.keyboardTop)/t.keyH),p=Math.floor((s.x-t.x)/t.keyW),m=o*6+p;m<P.length&&(r=ie(r,P[m]));continue}s.y>=t.submitY&&s.y<=t.submitY+t.submitH&&(r=re(r))}return r}function Ee(e,n,i,r){if(n.pressed.has("restart"))return ne(i);if(n.pressed.has("pause"))return e;let t=e.revealTicks>0?{...e,revealTicks:e.revealTicks-1}:e;if(t.result!=="active")return t;t=ke(t,n,r);for(const s of n.pressed)if(s==="left")t={...t,cursor:(t.cursor+d-1)%d,invalid:!1};else if(s==="right")t={...t,cursor:(t.cursor+1)%d,invalid:!1};else if(s==="up"||s==="down"){const a=t.edit[t.cursor]??null,o=a===null?s==="up"?0:25:P.indexOf(a),p=s==="up"?(o+(a===null?0:1))%26:(o+(a===null?0:25))%26;t=ie({...t,cursor:t.cursor},P[p])}else if(s==="secondary"){const a=[...t.edit];a[t.cursor]=null,t={...t,edit:a,cursor:(t.cursor+d-1)%d,invalid:!1}}else s==="primary"&&(t=t.edit.every(a=>a!==null)?re(t):{...t,cursor:(t.cursor+1)%d,invalid:!1});return t}function C(e,n,i,r,t,s){e.beginPath(),e.roundRect(n,i,r,t,Math.min(s,r/2,t/2))}function Ae(e,n,i,r,t,s=1){if(!(s<=0)){if(e.save(),e.translate(i,r),e.scale(s,s),e.translate(-i,-r),e.lineWidth=Math.max(1,t*.1),e.strokeStyle=n==="knot"?h.sprout:n==="empty"?h.empty:h.twig,e.fillStyle=e.strokeStyle,n==="knot")e.beginPath(),e.arc(i,r,t*.24,0,Math.PI*2),e.stroke(),e.beginPath(),e.moveTo(i-t*.25,r-t*.25),e.lineTo(i+t*.25,r+t*.25),e.moveTo(i+t*.25,r-t*.25),e.lineTo(i-t*.25,r+t*.25),e.stroke();else if(n==="empty")e.beginPath(),e.arc(i,r,t*.24,0,Math.PI*2),e.stroke();else{const a=n==="left"?-1:1;e.beginPath(),e.moveTo(i-a*t*.25,r),e.lineTo(i+a*t*.28,r),e.lineTo(i+a*t*.08,r-t*.18),e.moveTo(i+a*t*.28,r),e.lineTo(i+a*t*.08,r+t*.18),e.stroke()}e.restore()}}function Me(e,n,i,r){if(e.fillStyle=h.sky,e.fillRect(0,0,i.width,i.height),i.width<20||i.height<20)return;const t=se(i),s=Math.max(6,Math.min(t.rowH*.48,t.width*.052));C(e,t.x,t.y,t.width,t.submitY+t.submitH-t.y,Math.max(4,t.width*.035)),e.fillStyle=h.soil,e.fill(),e.textBaseline="middle",e.font=`700 ${s}px ui-rounded, system-ui, sans-serif`,e.fillStyle=h.paper,e.textAlign="left",e.fillText(`${n.attemptsRemaining} GUESSES`,t.x+t.width*.03,t.y+(t.rowTop-t.y)*.46),e.textAlign="right",e.fillStyle=n.invalid?h.danger:h.empty,e.fillText(n.invalid?"NOT IN SEED LIST":"KNOT · TWIG · RING",t.x+t.width*.97,t.y+(t.rowTop-t.y)*.46);const a=t.width/d,o=n.revealTicks/ee,p=r.reducedMotion?1:1-o,m=r.reducedMotion?0:-Math.min(12,t.rowH*.45)*o**3;for(let u=0;u<Y;u+=1){const S=u===n.rows.length-1&&n.revealTicks>0,A=t.rowTop+u*t.rowH+(S?m:0),k=n.rows[u];for(let g=0;g<d;g+=1){const L=t.x+g*a,l=u===n.rows.length&&n.result==="active";C(e,L+1,A+1,a-2,t.rowH-2,Math.max(2,t.rowH*.18)),e.fillStyle=l&&g===n.cursor?h.paper:h.sky,e.globalAlpha=l&&g===n.cursor?1:.72,e.fill(),e.globalAlpha=1;const c=k?.word[g]??(l?n.edit[g]:null);c&&(e.fillStyle=l&&g===n.cursor?h.sky:h.paper,e.textAlign="center",e.font=`800 ${s}px ui-rounded, system-ui, sans-serif`,e.fillText(c,L+a*.36,A+t.rowH*.5)),k&&Ae(e,k.feedback[g],L+a*.75,A+t.rowH*.5,Math.min(a,t.rowH)*.72,S?p:1)}}const w=Math.max(6,Math.min(t.keyH*.42,t.keyW*.38));e.font=`700 ${w}px ui-rounded, system-ui, sans-serif`;for(let u=0;u<P.length;u+=1){const S=Math.floor(u/6),A=u%6,k=t.x+A*t.keyW,g=t.keyboardTop+S*t.keyH;C(e,k+1,g+1,t.keyW-2,t.keyH-2,Math.max(2,t.keyH*.18)),e.fillStyle=u%2===0?h.empty:h.paper,e.globalAlpha=.92,e.fill(),e.globalAlpha=1,e.fillStyle=h.sky,e.textAlign="center",e.fillText(P[u],k+t.keyW/2,g+t.keyH/2)}C(e,t.x,t.submitY+1,t.width,t.submitH-1,Math.max(3,t.submitH*.22)),e.fillStyle=n.invalid?h.danger:h.sprout,e.fill(),e.fillStyle=h.sky,e.textAlign="center",e.font=`800 ${Math.max(7,t.submitH*.42)}px ui-rounded, system-ui, sans-serif`,e.fillText(n.invalid?"TRY ANOTHER SEED":"PLANT GUESS",t.x+t.width/2,t.submitY+t.submitH/2)}const f={tickSeconds:ye,init:e=>ne(e),step:(e,n,i,r)=>Ee(e,n,i,r),isOver:e=>e.result!=="active",score:e=>e.points,draw:Me,status:e=>e.result==="active"?`${e.attemptsRemaining} guesses`:e.result==="won"?"Harvested":"Seed revealed"},J={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"},Se={left:"ArrowLeft",right:"ArrowRight",up:"ArrowUp",down:"ArrowDown",primary:"Space",secondary:"X",pause:"P",restart:"R"},Te=new Set(["left","right","up","down"]),Q=80,Re=24;function Le(e,n){const i=new Set,r=new Set;let t=[];const s=new Map,a=new Map;let o=null;const p=l=>i.add(l),m=l=>{const c=J[l.code];if(!c||(l.preventDefault(),r.add(c),l.repeat&&!Te.has(c)))return;const I=performance.now();I-(a.get(c)??-1/0)<Q||(s.set(c,I),p(c))},w=l=>{const c=J[l.code];c&&r.delete(c)},u=()=>r.clear(),S=l=>{const c=n.getBoundingClientRect();return{x:l.clientX-c.left,y:l.clientY-c.top}},A=l=>{if(!l.isPrimary)return;n.focus({preventScroll:!0});const c=S(l);o={id:l.pointerId,...c}},k=l=>{if(!o||l.pointerId!==o.id)return;const c=S(l),I=c.x-o.x,G=c.y-o.y;o=null,Math.hypot(I,G)>=Re?p(Math.abs(I)>Math.abs(G)?I>0?"right":"left":G>0?"down":"up"):t.push(c)},g=()=>{o=null},L=l=>l.preventDefault();return e.addEventListener("keydown",m),e.addEventListener("keyup",w),window.addEventListener("blur",u),n.addEventListener("pointerdown",A),n.addEventListener("pointerup",k),n.addEventListener("pointercancel",g),n.addEventListener("contextmenu",L),{take(){const l={pressed:new Set(i),held:new Set(r),taps:t};return i.clear(),t=[],l},shellAction(l){const c=performance.now();c-(s.get(l)??-1/0)<Q||(a.set(l,c),p(l))},pending(){return{pressed:i,taps:t}},dispose(){e.removeEventListener("keydown",m),e.removeEventListener("keyup",w),window.removeEventListener("blur",u),n.removeEventListener("pointerdown",A),n.removeEventListener("pointerup",k),n.removeEventListener("pointercancel",g),n.removeEventListener("contextmenu",L)}}}function ae(e){let n=e>>>0;const i=()=>{n=n+1831565813>>>0;let r=n;return r=Math.imul(r^r>>>15,r|1),r^=r+Math.imul(r^r>>>7,r|61),((r^r>>>14)>>>0)/4294967296};return{next:i,int:r=>Math.floor(i()*Math.max(1,Math.floor(r))),pick:r=>{if(r.length===0)throw new Error("pick() from an empty list");return r[Math.floor(i()*r.length)]}}}function Ie(){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 i=Ie()[e];if(!i)throw new Error(`window.napplet.${String(e)} is unavailable -- runtime did not inject this domain`);return i}var oe={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(oe))Object.freeze(e.proposal),Object.freeze(e.evidence),e.implementationReference&&Object.freeze(e.implementationReference),Object.freeze(e);Object.freeze(oe);function Pe(){const e=new Map;function n(t,s){if(e.has(t))throw new Error(`NAP domain "${t}" is already registered`);e.set(t,s)}function i(t){const s=t.type.indexOf(".");if(s<=0)return!1;const a=t.type.slice(0,s),o=e.get(a);return o?(o(t),!0):!1}function r(){return Array.from(e.keys())}return{registerNap:n,dispatch:i,getRegisteredDomains:r}}var Ne=Pe(),He=Ne.registerNap,Oe="theme";function le(){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 De(){return le().get()}function Ce(e){return le().onChanged(e)}He(Oe,e=>{});var ce={getItem(e){return y("storage").getItem(e)},setItem(e,n){return y("storage").setItem(e,n)},removeItem(e){return y("storage").removeItem(e)},keys(){return y("storage").keys()},instance:{getItem(e){return y("storage").instance.getItem(e)},setItem(e,n){return y("storage").instance.setItem(e,n)},removeItem(e){return y("storage").instance.removeItem(e)},keys(){return y("storage").instance.keys()}}},We={registerAction(e){return y("keys").registerAction(e)},unregisterAction(e){y("keys").unregisterAction(e)},onAction(e,n){return y("keys").onAction(e,n)},async register(e,n){const i=y("keys"),r=await i.registerAction(e),t=i.onAction(e.id,n);return{...r,close(){t.close(),i.unregisterAction(e.id)}}}};function _(e){const n=e.trim().match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);if(n){const r=n[1].length===3?n[1].split("").map(t=>t+t).join(""):n[1];return[parseInt(r.slice(0,2),16),parseInt(r.slice(2,4),16),parseInt(r.slice(4,6),16)]}const i=e.trim().match(/^rgba?\(\s*(\d+)[,\s]+(\d+)[,\s]+(\d+)/i);return i?[Number(i[1]),Number(i[2]),Number(i[3])]:null}function xe(e){const n=_(e);if(!n)return 0;const[i,r,t]=n.map(s=>{const a=s/255;return a<=.03928?a/12.92:((a+.055)/1.055)**2.4});return .2126*i+.7152*r+.0722*t}function Be(e,n,i){const r=_(e),t=_(n);if(!r||!t)return e;const s=r.map((a,o)=>Math.round(a+(t[o]-a)*i));return`rgb(${s[0]}, ${s[1]}, ${s[2]})`}function Ke(){return typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches}function V(e,n,i,r=Ke()){const t=xe(e)<.4;return{background:e,foreground:n,accent:i,muted:Be(n,e,.55),danger:t?"#ff6b6b":"#c62828",success:t?"#5ee38a":"#1b7f3b",dark:t,reducedMotion:r}}function Ye(e){return e?V("#11131a","#e8eaf2","#7aa2ff"):V("#f6f7fb","#1a1d26","#2f5fd0")}const $e={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,n){return new Promise((i,r)=>{const t=setTimeout(()=>r(new Error("timeout")),n);e.then(s=>{clearTimeout(t),i(s)},s=>{clearTimeout(t),r(s)})})}function U(e){const n=window.napplet;return!!(n&&n[e])}async function Ue(e,n){if(!U("keys"))return[];const i=[];for(const r of e)try{i.push(await $(We.register({id:`game.${r}`,label:$e[r],defaultKey:Se[r]},()=>n(r)),2e3))}catch{}return i}async function qe(e){if(!U("storage"))return 0;try{const n=await $(ce.getItem(`best:${e}`),2e3),i=n===null?0:Number(n);return Number.isFinite(i)&&i>0?i:0}catch{return 0}}function Ge(e,n){U("storage")&&$(ce.setItem(`best:${e}`,String(n)),2e3).catch(()=>{})}function Fe(e){const n=window.matchMedia("(prefers-color-scheme: dark)"),i=window.matchMedia("(prefers-reduced-motion: reduce)");let r=!1,t=null;const s=m=>{t=m,e(m)},a=()=>{r||s(Ye(n.matches))},o=()=>{t&&s({...t,reducedMotion:i.matches})};a(),n.addEventListener("change",a),i.addEventListener("change",o);let p=null;if(U("theme")){const m=w=>{const u=w?.colors;!u?.background||!u.text||(r=!0,s(V(u.background,u.text,u.primary||u.text,i.matches)))};$(De(),3e3).then(m,()=>{});try{p=Ce(m)}catch{p=null}}return{close:()=>{n.removeEventListener("change",a),i.removeEventListener("change",o),p?.close()}}}const q=document.getElementById("app"),T=document.getElementById("stage"),W=T.getContext("2d"),je=document.getElementById("score"),_e=document.getElementById("status"),Ve=document.getElementById("best"),Xe=document.getElementById("overlay"),F=document.getElementById("overlay-title"),j=document.getElementById("overlay-hint"),Ze=new Set([...O.actions,"primary","pause","restart"]),v=Le(document.body,T);let X,E={width:1,height:1},H=1,x=(Date.now()^Math.floor(Math.random()*2147483647))>>>0,D=ae(x),b=f.init(D),R="ready",M=0,B=0,K=performance.now(),Z=-1;function pe(){const e=q.getBoundingClientRect();H=Math.max(1,Math.min(3,window.devicePixelRatio||1)),E={width:Math.max(1,Math.floor(e.width)),height:Math.max(1,Math.floor(e.height))},T.width=Math.round(E.width*H),T.height=Math.round(E.height*H),T.style.width=`${E.width}px`,T.style.height=`${E.height}px`,R==="ready"&&(b=f.init(D))}function N(e){R=e,q.dataset.phase=e,Xe.hidden=e==="playing",e==="ready"?(F.textContent=O.title,j.textContent=`Tap or press Space to start · ${O.help}`):e==="paused"?(F.textContent="Paused",j.textContent="Press P or tap to resume"):e==="over"&&(F.textContent=`Score ${f.score(b)}`,j.textContent=`Best ${M} · Tap or press Space to play again`)}function z(){x=x*1664525+1013904223>>>0,D=ae(x),b=f.init(D),B=0,Z=-1,N("playing")}function Je(){const e=f.score(b);e!==Z&&(Z=e,je.textContent=String(e),q.dataset.score=String(e)),_e.textContent=f.status?f.status(b):"",Ve.textContent=M>0?`Best ${M}`:""}function Qe(){const{pressed:e,taps:n}=v.pending();if(e.has("restart")){v.take(),z();return}if(R==="ready"||R==="over"){e.has("primary")||n.length>0?(v.take(),z()):e.size&&v.take();return}if(R==="paused"){e.has("pause")||e.has("primary")||n.length>0?(v.take(),N("playing"),K=performance.now()):e.size&&v.take();return}e.has("pause")&&(v.take(),N("paused"))}function ue(e){if(Qe(),R==="playing"){B+=Math.min(.25,(e-K)/1e3);let n=0;for(;B>=f.tickSeconds&&n<8;)if(b=f.step(b,v.take(),D,E),B-=f.tickSeconds,n+=1,f.isOver(b)){const i=f.score(b);i>M&&(M=i,Ge(O.slug,M)),N("over");break}}K=e,W.setTransform(H,0,0,H,0,0),W.fillStyle=X.background,W.fillRect(0,0,E.width,E.height),f.draw(W,b,E,X),Je(),requestAnimationFrame(ue)}const ze=Fe(e=>{X=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&&R==="playing"&&N("paused")});new ResizeObserver(pe).observe(q);pe();N("ready");T.focus({preventScroll:!0});qe(O.slug).then(e=>{M=Math.max(M,e)});const et=Ue([...Ze],e=>v.shellAction(e));window.addEventListener("pagehide",()=>{v.dispose(),ze.close(),et.then(e=>e.forEach(n=>n.close()))});requestAnimationFrame(e=>{K=e,ue(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="Hush Orchard 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>