Back to Glimmer Sluice
SOURCE / PINNED RELEASE

Made of little things.

Glimmer Sluice

Release
120232896eec…
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>Glimmer Sluice</title><meta name="description" content="Guide glimmer through a three-gate miniature waterwork. Tap the lit gates in order, spend gathered glimmer on hand and automatic upgrades, and fill the beacon before the two-minute round ends."><meta name="napplet-id" content="glimmer-sluice"><meta name="napplet-optional" content="keys storage theme">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    
    
    <script type="module" crossorigin>const me="glimmer-sluice",ge="Glimmer Sluice",fe="Arrows select · Space activates gate or upgrade · Tap controls · R restarts",ye=["left","right","primary"],$={slug:me,title:ge,help:fe,actions:ye};function be(e,t,i=0){const r=Math.max(1,e.width-i*2),n=Math.max(1,e.height-i*2),a=Math.min(r,n*t),c=a/t;return{x:(e.width-a)/2,y:(e.height-c)/2,width:a,height:c}}const ve=120,C=240,we=1/60,P=60,p={basin:"#142b3a",channel:"#28566b",glimmer:"#f6d66f",active:"#7ee6c4",ceramic:"#d8e3dc",warning:"#ef6f6c",ink:"#08171f"};function ae(){return{phase:"playing",spendable:0,lifetime:0,circuits:0,handLevel:0,rillLevel:0,required:0,remaining:ve,autoTicks:0,lastEvent:"none",keyboardSelection:0,eventTicks:99,rimFrom:0,rimTicks:99}}function ke(e,t){return ae()}function j(e){return 20*(e+1)}function Y(e){return 30*(e+1)}function Me(e){return 1+e.handLevel}function se(e){const t=e.height>e.width*1.25,i=be(e,t?.62:1.32,Math.max(5,Math.min(e.width,e.height)*.035)),r=Math.min(i.width,i.height),n=i.y+i.height*.43,a=t?Math.max(22,Math.min(i.width*.12,i.height*.075)):Math.max(18,Math.min(r*.105,i.width*.11)),c=i.y+i.height*(t?.79:.75),d=i.height*(t?.14:.18);return{board:i,gateCenters:[{x:i.x+i.width*(t?.36:.23),y:t?i.y+i.height*.27:n},{x:i.x+i.width*(t?.64:.5),y:t?i.y+i.height*.46:n},{x:i.x+i.width*(t?.36:.77),y:t?i.y+i.height*.65:n}],gateRadius:a,hand:{x:i.x+i.width*.05,y:c,width:i.width*.43,height:d},rill:{x:i.x+i.width*.52,y:c,width:i.width*.43,height:d},beacon:t?{x:i.x+i.width*.82,y:i.y+i.height*.18,width:i.width*.09,height:i.height*.45}:{x:i.x+i.width*.88,y:i.y+i.height*.08,width:i.width*.06,height:i.height*.23}}}function Z(e,t){return e.x>=t.x&&e.x<=t.x+t.width&&e.y>=t.y&&e.y<=t.y+t.height}function Ee(e,t){for(let i=0;i<3;i+=1){const r=t.gateCenters[i];if(Math.abs(e.x-r.x)<=t.gateRadius*1.2&&Math.abs(e.y-r.y)<=t.gateRadius*1.2)return i}return null}function J(e){return e.lifetime>=C?{...e,phase:"victory"}:e}function Q(e,t){if(t!==e.required)return{...e,required:0,lastEvent:"wrong",eventTicks:0,rimFrom:e.required,rimTicks:0};const i=Me(e);if(t===2){const r=i*4;return J({...e,spendable:e.spendable+r,lifetime:e.lifetime+r,circuits:e.circuits+1,required:0,lastEvent:"circuit",eventTicks:0,rimFrom:e.required,rimTicks:0})}return J({...e,spendable:e.spendable+i,lifetime:e.lifetime+i,required:t+1,lastEvent:"correct",eventTicks:0,rimFrom:e.required,rimTicks:0})}function ee(e){const t=j(e.handLevel);return e.spendable<t?e:{...e,spendable:e.spendable-t,handLevel:e.handLevel+1,lastEvent:"hand",eventTicks:0}}function te(e){const t=Y(e.rillLevel);return e.spendable<t?e:{...e,spendable:e.spendable-t,rillLevel:e.rillLevel+1,lastEvent:"rill",eventTicks:0}}function Se(e,t,i,r){if(e.phase!=="playing")return t.pressed.has("primary")?ae():e;let n={...e,eventTicks:Math.min(999,e.eventTicks+1),rimTicks:Math.min(999,e.rimTicks+1)};t.pressed.has("left")&&(n={...n,keyboardSelection:(n.keyboardSelection+2)%3}),t.pressed.has("right")&&(n={...n,keyboardSelection:(n.keyboardSelection+1)%3});const a=se(r);for(const s of t.taps){if(Z(s,a.hand))n=ee(n);else if(Z(s,a.rill))n=te(n);else{const o=Ee(s,a);o!==null&&(n=Q(n,o))}if(n.phase!=="playing")return n}if(t.pressed.has("primary")&&t.taps.length===0&&(n.keyboardSelection===1?n=ee(n):n.keyboardSelection===2?n=te(n):n=Q(n,n.required)),n.phase!=="playing")return n;const c=Math.max(0,Math.round(n.remaining*P)-1);let d=n.autoTicks+1,g=0;d>=P&&(g=Math.floor(d/P),d%=P);const u=g*n.rillLevel;return n={...n,spendable:n.spendable+u,lifetime:n.lifetime+u,remaining:c/P,autoTicks:d},n.lifetime>=C?{...n,phase:"victory"}:c===0?{...n,phase:"defeat"}:n}function W(e,t,i){e.beginPath(),e.roundRect(t.x,t.y,t.width,t.height,Math.min(i,t.width/2,t.height/2))}function Ae(e,t,i,r,n){e.beginPath(),t===0?e.arc(i,r,n,0,Math.PI*2):t===1?(e.moveTo(i,r-n),e.lineTo(i+n*.9,r+n*.75),e.lineTo(i-n*.9,r+n*.75),e.closePath()):e.rect(i-n*.72,r-n*.72,n*1.44,n*1.44),e.stroke()}function Te(e,t,i,r){const n=se(i),{board:a}=n;if(e.fillStyle=p.basin,e.fillRect(0,0,i.width,i.height),e.strokeStyle=p.channel,e.lineWidth=Math.max(9,a.height*.085),e.lineCap="round",e.beginPath(),e.moveTo(n.gateCenters[0].x,n.gateCenters[0].y),e.lineTo(n.gateCenters[1].x,n.gateCenters[1].y),e.lineTo(n.gateCenters[2].x,n.gateCenters[2].y),e.lineTo(n.beacon.x+n.beacon.width/2,n.beacon.y+n.beacon.height),e.stroke(),!r.reducedMotion&&(t.lastEvent==="correct"||t.lastEvent==="circuit")&&t.eventTicks<26){const s=n.gateCenters[t.rimFrom],o=n.gateCenters[t.required],w=t.eventTicks/26;e.fillStyle=p.glimmer;for(let f=0;f<3;f+=1){const v=Math.max(0,Math.min(1,w-f*.1)),m=s.x+(o.x-s.x)*v,S=s.y+(o.y-s.y)*v,l=Math.max(2,a.height*.012);e.beginPath(),f===0?e.arc(m,S,l,0,Math.PI*2):f===1?(e.moveTo(m,S-l),e.lineTo(m+l,S+l),e.lineTo(m-l,S+l),e.closePath()):e.rect(m-l,S-l,l*2,l*2),e.fill()}}const c=["N","E","W"];for(let s=0;s<3;s+=1){const o=n.gateCenters[s];e.fillStyle=p.ceramic,e.strokeStyle=p.channel,e.lineWidth=Math.max(2,n.gateRadius*.08),e.beginPath(),e.arc(o.x,o.y,n.gateRadius,0,Math.PI*2),e.fill(),e.stroke(),e.strokeStyle=p.ink,e.lineWidth=Math.max(2,n.gateRadius*.09),Ae(e,s,o.x,o.y,n.gateRadius*.34),e.fillStyle=p.ceramic,e.font=`700 ${Math.max(9,a.height*.045)}px system-ui`,e.textAlign="center",e.textBaseline="middle",e.fillText(c[s],o.x,o.y-n.gateRadius*1.42)}if(t.phase==="playing"){const s=n.gateCenters[t.rimFrom],o=n.gateCenters[t.required],w=r.reducedMotion?1:Math.min(1,t.rimTicks/15),f=1-(1-w)*(1-w)*(1-w),v=s.x+(o.x-s.x)*f,m=s.y+(o.y-s.y)*f;e.strokeStyle=p.active,e.lineWidth=Math.max(4,n.gateRadius*.16),e.beginPath(),e.arc(v,m,n.gateRadius*1.08,0,Math.PI*2),e.stroke(),t.keyboardSelection===0&&(e.fillStyle=p.glimmer,e.beginPath(),e.moveTo(o.x,o.y+n.gateRadius*1.5),e.lineTo(o.x-n.gateRadius*.18,o.y+n.gateRadius*1.25),e.lineTo(o.x+n.gateRadius*.18,o.y+n.gateRadius*1.25),e.closePath(),e.fill())}e.fillStyle=p.ink,W(e,n.beacon,n.beacon.width*.25),e.fill();const d=Math.min(1,t.lifetime/C),g={x:n.beacon.x+2,y:n.beacon.y+2+(n.beacon.height-4)*(1-d),width:Math.max(1,n.beacon.width-4),height:Math.max(1,(n.beacon.height-4)*d)};e.fillStyle=p.glimmer,W(e,g,Math.min(3,g.height/2)),e.fill();const u=(s,o,w,f,v,m)=>{e.fillStyle=v?p.ceramic:p.channel,e.strokeStyle=m?p.glimmer:v?p.active:p.warning,e.lineWidth=m?Math.max(4,a.height*.018):Math.max(2,a.height*.012),W(e,s,s.height*.22),e.fill(),e.stroke(),e.fillStyle=v?p.ink:p.ceramic,e.textAlign="center",e.textBaseline="middle",e.font=`700 ${Math.max(9,a.height*.042)}px system-ui`,e.fillText(`${m?"› ":""}${o} ${w}`,s.x+s.width/2,s.y+s.height*.35),e.font=`600 ${Math.max(8,a.height*.034)}px system-ui`,e.fillText(`◇ ${f}`,s.x+s.width/2,s.y+s.height*.7)};if(u(n.hand,"HAND",t.handLevel,j(t.handLevel),t.spendable>=j(t.handLevel),t.keyboardSelection===1),u(n.rill,"RILL",t.rillLevel,Y(t.rillLevel),t.spendable>=Y(t.rillLevel),t.keyboardSelection===2),!r.reducedMotion&&(t.lastEvent==="hand"||t.lastEvent==="rill")&&t.eventTicks<36){const s=t.lastEvent==="hand"?n.hand:n.rill,o=t.eventTicks/36;e.strokeStyle=p.active,e.lineWidth=Math.max(1.5,a.height*.009)*(1-o*.55),e.beginPath(),e.arc(s.x+s.width/2,s.y+s.height/2,Math.min(s.width,s.height)*(.42+o*.8),0,Math.PI*2),e.stroke()}e.fillStyle=p.glimmer,e.textAlign="left",e.textBaseline="top",e.font=`700 ${Math.max(10,a.height*.052)}px system-ui`,e.fillText(`◇ ${t.spendable}`,a.x+a.width*.06,a.y+a.height*.07),e.fillStyle=t.remaining<=15?p.warning:p.ceramic,e.textAlign="center",e.fillText(`${Math.ceil(t.remaining)}s`,a.x+a.width*.55,a.y+a.height*.07),e.textAlign="right",e.fillStyle=p.ceramic,e.fillText(`${Math.min(t.lifetime,C)}/${C}`,a.x+a.width*.84,a.y+a.height*.07)}function Re(e){return e.lifetime+e.circuits*5+(e.phase==="victory"?Math.floor(e.remaining)*2:0)}const y={tickSeconds:we,init:ke,step:Se,isOver:e=>e.phase!=="playing",score:Re,draw:Te,status:e=>`${Math.ceil(e.remaining)}s · ◇${e.spendable} · relay ${e.circuits}`},ne={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"},Ie={left:"ArrowLeft",right:"ArrowRight",up:"ArrowUp",down:"ArrowDown",primary:"Space",secondary:"X",pause:"P",restart:"R"},Le=new Set(["left","right","up","down"]),ie=80,Pe=24;function Ce(e,t){const i=new Set,r=new Set;let n=[];const a=new Map,c=new Map;let d=null;const g=l=>i.add(l),u=l=>{const h=ne[l.code];if(!h||(l.preventDefault(),r.add(h),l.repeat&&!Le.has(h)))return;const I=performance.now();I-(c.get(h)??-1/0)<ie||(a.set(h,I),g(h))},s=l=>{const h=ne[l.code];h&&r.delete(h)},o=()=>r.clear(),w=l=>{const h=t.getBoundingClientRect();return{x:l.clientX-h.left,y:l.clientY-h.top}},f=l=>{if(!l.isPrimary)return;t.focus({preventScroll:!0});const h=w(l);d={id:l.pointerId,...h}},v=l=>{if(!d||l.pointerId!==d.id)return;const h=w(l),I=h.x-d.x,_=h.y-d.y;d=null,Math.hypot(I,_)>=Pe?g(Math.abs(I)>Math.abs(_)?I>0?"right":"left":_>0?"down":"up"):n.push(h)},m=()=>{d=null},S=l=>l.preventDefault();return e.addEventListener("keydown",u),e.addEventListener("keyup",s),window.addEventListener("blur",o),t.addEventListener("pointerdown",f),t.addEventListener("pointerup",v),t.addEventListener("pointercancel",m),t.addEventListener("contextmenu",S),{take(){const l={pressed:new Set(i),held:new Set(r),taps:n};return i.clear(),n=[],l},shellAction(l){const h=performance.now();h-(a.get(l)??-1/0)<ie||(c.set(l,h),g(l))},pending(){return{pressed:i,taps:n}},dispose(){e.removeEventListener("keydown",u),e.removeEventListener("keyup",s),window.removeEventListener("blur",o),t.removeEventListener("pointerdown",f),t.removeEventListener("pointerup",v),t.removeEventListener("pointercancel",m),t.removeEventListener("contextmenu",S)}}}function oe(e){let t=e>>>0;const i=()=>{t=t+1831565813>>>0;let r=t;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 xe(){const e=window;if(!e.napplet)throw new Error("window.napplet is unavailable -- runtime did not inject the napplet namespace");return e.napplet}function b(e){const i=xe()[e];if(!i)throw new Error(`window.napplet.${String(e)} is unavailable -- runtime did not inject this domain`);return i}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,a){if(e.has(n))throw new Error(`NAP domain "${n}" is already registered`);e.set(n,a)}function i(n){const a=n.type.indexOf(".");if(a<=0)return!1;const c=n.type.slice(0,a),d=e.get(c);return d?(d(n),!0):!1}function r(){return Array.from(e.keys())}return{registerNap:t,dispatch:i,getRegisteredDomains:r}}var qe=$e(),Ne=qe.registerNap,De="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 Be(){return ce().get()}function Oe(e){return ce().onChanged(e)}Ne(De,e=>{});var de={getItem(e){return b("storage").getItem(e)},setItem(e,t){return b("storage").setItem(e,t)},removeItem(e){return b("storage").removeItem(e)},keys(){return b("storage").keys()},instance:{getItem(e){return b("storage").instance.getItem(e)},setItem(e,t){return b("storage").instance.setItem(e,t)},removeItem(e){return b("storage").instance.removeItem(e)},keys(){return b("storage").instance.keys()}}},Ke={registerAction(e){return b("keys").registerAction(e)},unregisterAction(e){b("keys").unregisterAction(e)},onAction(e,t){return b("keys").onAction(e,t)},async register(e,t){const i=b("keys"),r=await i.registerAction(e),n=i.onAction(e.id,t);return{...r,close(){n.close(),i.unregisterAction(e.id)}}}};function G(e){const t=e.trim().match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);if(t){const r=t[1].length===3?t[1].split("").map(n=>n+n).join(""):t[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 Fe(e){const t=G(e);if(!t)return 0;const[i,r,n]=t.map(a=>{const c=a/255;return c<=.03928?c/12.92:((c+.055)/1.055)**2.4});return .2126*i+.7152*r+.0722*n}function _e(e,t,i){const r=G(e),n=G(t);if(!r||!n)return e;const a=r.map((c,d)=>Math.round(c+(n[d]-c)*i));return`rgb(${a[0]}, ${a[1]}, ${a[2]})`}function We(){return typeof matchMedia=="function"&&matchMedia("(prefers-reduced-motion: reduce)").matches}function H(e,t,i,r=We()){const n=Fe(e)<.4;return{background:e,foreground:t,accent:i,muted:_e(t,e,.55),danger:n?"#ff6b6b":"#c62828",success:n?"#5ee38a":"#1b7f3b",dark:n,reducedMotion:r}}function ze(e){return e?H("#11131a","#e8eaf2","#7aa2ff"):H("#f6f7fb","#1a1d26","#2f5fd0")}const Ue={left:"Move left",right:"Move right",up:"Move up",down:"Move down",primary:"Start / main action",secondary:"Secondary action",pause:"Pause",restart:"Restart"};function O(e,t){return new Promise((i,r)=>{const n=setTimeout(()=>r(new Error("timeout")),t);e.then(a=>{clearTimeout(n),i(a)},a=>{clearTimeout(n),r(a)})})}function K(e){const t=window.napplet;return!!(t&&t[e])}async function je(e,t){if(!K("keys"))return[];const i=[];for(const r of e)try{i.push(await O(Ke.register({id:`game.${r}`,label:Ue[r],defaultKey:Ie[r]},()=>t(r)),2e3))}catch{}return i}async function Ye(e){if(!K("storage"))return 0;try{const t=await O(de.getItem(`best:${e}`),2e3),i=t===null?0:Number(t);return Number.isFinite(i)&&i>0?i:0}catch{return 0}}function Ge(e,t){K("storage")&&O(de.setItem(`best:${e}`,String(t)),2e3).catch(()=>{})}function He(e){const t=window.matchMedia("(prefers-color-scheme: dark)"),i=window.matchMedia("(prefers-reduced-motion: reduce)");let r=!1,n=null;const a=u=>{n=u,e(u)},c=()=>{r||a(ze(t.matches))},d=()=>{n&&a({...n,reducedMotion:i.matches})};c(),t.addEventListener("change",c),i.addEventListener("change",d);let g=null;if(K("theme")){const u=s=>{const o=s?.colors;!o?.background||!o.text||(r=!0,a(H(o.background,o.text,o.primary||o.text,i.matches)))};O(Be(),3e3).then(u,()=>{});try{g=Oe(u)}catch{g=null}}return{close:()=>{t.removeEventListener("change",c),i.removeEventListener("change",d),g?.close()}}}const F=document.getElementById("app"),T=document.getElementById("stage"),q=T.getContext("2d"),Xe=document.getElementById("score"),Ve=document.getElementById("status"),Ze=document.getElementById("best"),Je=document.getElementById("overlay"),z=document.getElementById("overlay-title"),U=document.getElementById("overlay-hint"),Qe=new Set([...$.actions,"primary","pause","restart"]),k=Ce(document.body,T);let X,E={width:1,height:1},x=1,N=(Date.now()^Math.floor(Math.random()*2147483647))>>>0,pe=oe(N),M=y.init(),R="ready",A=0,D=0,B=performance.now(),V=-1;function he(){const e=F.getBoundingClientRect();x=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*x),T.height=Math.round(E.height*x),T.style.width=`${E.width}px`,T.style.height=`${E.height}px`,R==="ready"&&(M=y.init())}function L(e){R=e,F.dataset.phase=e,Je.hidden=e==="playing",e==="ready"?(z.textContent=$.title,U.textContent=`Tap or press Space to start · ${$.help}`):e==="paused"?(z.textContent="Paused",U.textContent="Press P or tap to resume"):e==="over"&&(z.textContent=`Score ${y.score(M)}`,U.textContent=`Best ${A} · Tap or press Space to play again`)}function re(){N=N*1664525+1013904223>>>0,pe=oe(N),M=y.init(),D=0,V=-1,L("playing")}function et(){const e=y.score(M);e!==V&&(V=e,Xe.textContent=String(e),F.dataset.score=String(e)),Ve.textContent=y.status?y.status(M):"",Ze.textContent=A>0?`Best ${A}`:""}function tt(){const{pressed:e,taps:t}=k.pending();if(e.has("restart")){k.take(),re();return}if(R==="ready"||R==="over"){e.has("primary")||t.length>0?(k.take(),re()):e.size&&k.take();return}if(R==="paused"){e.has("pause")||e.has("primary")||t.length>0?(k.take(),L("playing"),B=performance.now()):e.size&&k.take();return}e.has("pause")&&(k.take(),L("paused"))}function ue(e){if(tt(),R==="playing"){D+=Math.min(.25,(e-B)/1e3);let t=0;for(;D>=y.tickSeconds&&t<8;)if(M=y.step(M,k.take(),pe,E),D-=y.tickSeconds,t+=1,y.isOver(M)){const i=y.score(M);i>A&&(A=i,Ge($.slug,A)),L("over");break}}B=e,q.setTransform(x,0,0,x,0,0),q.fillStyle=X.background,q.fillRect(0,0,E.width,E.height),y.draw(q,M,E,X),et(),requestAnimationFrame(ue)}const nt=He(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"&&L("paused")});new ResizeObserver(he).observe(F);he();L("ready");T.focus({preventScroll:!0});Ye($.slug).then(e=>{A=Math.max(A,e)});const it=je([...Qe],e=>k.shellAction(e));window.addEventListener("pagehide",()=>{k.dispose(),nt.close(),it.then(e=>e.forEach(t=>t.close()))});requestAnimationFrame(e=>{B=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="Glimmer Sluice 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>