Back to Snake Scoreboard
SOURCE / PINNED RELEASE

Made of little things.

Snake Scoreboard

Release
bbbfff18e784…
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" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Snake Scoreboard</title>
    <script type="module" crossorigin>var X=new Map,E=new Map,ee=new Map,ze=!1,J=null,nn=new Set(["Tab","Shift+Tab","Escape"]);function Je(e,t){return e.type===t}function rn(e){var r;if(!(e instanceof Element))return!1;const t=e.tagName;if(t==="TEXTAREA"||t==="SELECT")return!0;if(t==="INPUT"){const o=((r=e.type)==null?void 0:r.toLowerCase())??"text";return new Set(["text","search","email","url","password","number","tel","date","datetime-local","month","time","week"]).has(o)||o===""}if(e.isContentEditable)return!0;const n=e.contentEditable;return n==="true"||n==="plaintext-only"}function on(e){return e==="Control"||e==="Alt"||e==="Shift"||e==="Meta"}function sn(e){const t=[];return e.altKey&&t.push("Alt"),e.ctrlKey&&t.push("Ctrl"),e.metaKey&&t.push("Meta"),e.shiftKey&&t.push("Shift"),t.push(e.key),t.join("+")}function an(e){X.clear();for(const t of e.bindings)X.set(t.key,t.actionId)}function cn(e){const t=ee.get(e.id);if(t){if(ee.delete(e.id),e.error){t.reject(new Error(e.error));return}t.resolve({actionId:e.actionId,binding:e.binding})}}function dn(e){const t=E.get(e.actionId);if(t)for(const n of t)n()}function St(e){if(rn(e.target)||on(e.key)||e.isComposing)return;const t=sn(e);if(!nn.has(t)&&X.has(t)){e.preventDefault();const r=X.get(t),o=E.get(r);if(o)for(const i of o)i();return}const n={type:"keys.forward",key:e.key,code:e.code,ctrl:e.ctrlKey,alt:e.altKey,shift:e.shiftKey,meta:e.metaKey};window.parent.postMessage(n,"*")}function ln(e){Je(e,"keys.bindings")?an(e):Je(e,"keys.registerAction.result")?cn(e):Je(e,"keys.action")&&dn(e)}function un(e){const t=crypto.randomUUID();return new Promise((n,r)=>{ee.set(t,{resolve:n,reject:r});const o={type:"keys.registerAction",id:t,action:e};window.parent.postMessage(o,"*"),setTimeout(()=>{ee.delete(t)&&r(new Error("keys.registerAction timed out"))},3e4)})}function fn(e){const t={type:"keys.unregisterAction",actionId:e};window.parent.postMessage(t,"*")}function pn(e,t){return E.has(e)||E.set(e,new Set),E.get(e).add(t),{close(){const n=E.get(e);n&&(n.delete(t),n.size===0&&E.delete(e))}}}function yn(){return ze&&J||(document.addEventListener("keydown",St,!0),ze=!0,J=()=>{document.removeEventListener("keydown",St,!0),X.clear(),E.clear(),ee.clear(),ze=!1,J=null}),J}var be=new Map,k=new Map,I=new Map,P=new Map,R=new Map;function G(e,t){return e.type===t}function mn(e){const t=be.get(e.id);if(!t)return;be.delete(e.id),clearTimeout(t.timeout);const n={};e.sessionId!==void 0&&(n.sessionId=e.sessionId),e.owner!==void 0&&(n.owner=e.owner),e.error!==void 0&&(n.error=e.error),t.resolve(n)}function gn(e){const t=k.get(e.sessionId);if(t)for(const n of t)n(e.action,e.value)}function wn(e){const t=I.get(e.sessionId);if(t)for(const n of t)n(e.controls)}function bn(e){const t=P.get(e.sessionId);if(!t)return;const n={status:e.status,position:e.position,duration:e.duration,volume:e.volume};for(const r of t)r(n)}function hn(e){const t=R.get(e.sessionId);if(t)for(const n of t)n(e.actions)}function vn(e){G(e,"media.session.create.result")?mn(e):G(e,"media.command")?gn(e):G(e,"media.controls")?wn(e):G(e,"media.state")?bn(e):G(e,"media.capabilities")&&hn(e)}function Mn(e){const t=crypto.randomUUID();return new Promise((n,r)=>{const o=setTimeout(()=>{be.delete(t)&&r(new Error("media.session.create timed out"))},3e4);be.set(t,{resolve:n,reject:r,timeout:o});const i={type:"media.session.create",id:t,...e};window.parent.postMessage(i,"*")})}function Sn(e,t){const n={type:"media.session.update",sessionId:e,metadata:t};window.parent.postMessage(n,"*")}function En(e){const t={type:"media.session.destroy",sessionId:e};window.parent.postMessage(t,"*"),k.delete(e),I.delete(e),P.delete(e),R.delete(e)}function xn(e,t){const n={type:"media.state",sessionId:e,...t};window.parent.postMessage(n,"*")}function Un(e,t){const n={type:"media.capabilities",sessionId:e,actions:t};window.parent.postMessage(n,"*")}function Tn(e,t,n){const r={type:"media.command",sessionId:e,action:t,...n===void 0?{}:{value:n}};window.parent.postMessage(r,"*")}function kn(e,t){return k.has(e)||k.set(e,new Set),k.get(e).add(t),{close(){const n=k.get(e);n&&(n.delete(t),n.size===0&&k.delete(e))}}}function In(e,t){return P.has(e)||P.set(e,new Set),P.get(e).add(t),{close(){const n=P.get(e);n&&(n.delete(t),n.size===0&&P.delete(e))}}}function Pn(e,t){return R.has(e)||R.set(e,new Set),R.get(e).add(t),{close(){const n=R.get(e);n&&(n.delete(t),n.size===0&&R.delete(e))}}}function Rn(e,t){return I.has(e)||I.set(e,new Set),I.get(e).add(t),{close(){const n=I.get(e);n&&(n.delete(t),n.size===0&&I.delete(e))}}}var At=3e4,he=new Map,ve=new Map,Ve=new Set,Ye=new Set,Xe=new Set,et=new Set;function L(e,t){return e.type===t}function Cn(e){const t=he.get(e.id);if(t){if(he.delete(e.id),e.error){t.reject(new Error(e.error));return}e.notificationId?t.resolve({notificationId:e.notificationId}):t.reject(new Error("notify.send.result missing notificationId"))}}function An(e){const t=ve.get(e.id);t&&(ve.delete(e.id),t.resolve({granted:e.granted}))}function Dn(e){for(const t of Ve)t(e.notificationId,e.actionId)}function Nn(e){for(const t of Ye)t(e.notificationId)}function $n(e){for(const t of Xe)t(e.notificationId,e.reason)}function Ln(e){for(const t of et)t(e.controls)}function Bn(e){L(e,"notify.send.result")?Cn(e):L(e,"notify.permission.result")?An(e):L(e,"notify.action")?Dn(e):L(e,"notify.clicked")?Nn(e):L(e,"notify.dismissed")?$n(e):L(e,"notify.controls")&&Ln(e)}function On(e){const t=crypto.randomUUID();return new Promise((n,r)=>{he.set(t,{resolve:n,reject:r});const o={type:"notify.send",id:t,...e};window.parent.postMessage(o,"*"),setTimeout(()=>{he.delete(t)&&r(new Error("notify.send timed out"))},At)})}function _n(e){const t={type:"notify.dismiss",notificationId:e};window.parent.postMessage(t,"*")}function qn(e){const t={type:"notify.badge",count:e};window.parent.postMessage(t,"*")}function Hn(e){const t={type:"notify.channel.register",...e};window.parent.postMessage(t,"*")}function jn(e){const t=crypto.randomUUID();return new Promise((n,r)=>{ve.set(t,{resolve:n,reject:r});const o={type:"notify.permission.request",id:t,channel:e};window.parent.postMessage(o,"*"),setTimeout(()=>{ve.delete(t)&&r(new Error("notify.permission.request timed out"))},At)})}function Kn(e){return Ve.add(e),{close(){Ve.delete(e)}}}function Fn(e){return Ye.add(e),{close(){Ye.delete(e)}}}function zn(e){return Xe.add(e),{close(){Xe.delete(e)}}}function Jn(e){return et.add(e),{close(){et.delete(e)}}}var te=new Map,Gn=5e3;function Et(e){if(e.source!==window.parent)return;const t=e.data;if(typeof t!="object"||t===null||typeof t.type!="string"||!t.type.startsWith("storage.")||!t.type.endsWith(".result"))return;const n=t.id;if(!n)return;const r=te.get(n);if(r){if(te.delete(n),t.error){r.reject(new Error(t.error));return}r.resolve(t)}}function ae(e){return new Promise((t,n)=>{te.set(e.id,{resolve:t,reject:n}),window.parent.postMessage(e,"*"),setTimeout(()=>{te.delete(e.id)&&n(new Error("State request timed out"))},Gn)})}var M={async getItem(e){const t={type:"storage.get",id:crypto.randomUUID(),key:e};return(await ae(t)).value},async setItem(e,t){const n={type:"storage.set",id:crypto.randomUUID(),key:e,value:t};await ae(n)},async removeItem(e){const t={type:"storage.remove",id:crypto.randomUUID(),key:e};await ae(t)},async keys(){const e={type:"storage.keys",id:crypto.randomUUID()};return(await ae(e)).keys}};function Qn(){return window.addEventListener("message",Et),()=>{window.removeEventListener("message",Et),te.clear()}}var Wn=3e4,fe=new Map,_=new Map;function xt(e,t){return e.type===t}function Zn(e){if(xt(e,"resource.bytes.result")){const t=e,n=_.get(t.id);if(!n)return;_.delete(t.id),n.resolve(t.blob)}else if(xt(e,"resource.bytes.error")){const t=e,n=_.get(t.id);if(!n)return;_.delete(t.id),n.reject(new Error(t.message?`${t.error}: ${t.message}`:t.error))}}function Vn(e,t){return new Promise((n,r)=>{_.set(t,{resolve:n,reject:r,url:e});const o={type:"resource.bytes",id:t,url:e};window.parent.postMessage(o,"*"),setTimeout(()=>{_.delete(t)&&r(new Error(`resource.bytes timed out for ${e}`))},Wn)})}function Ut(e){const t={type:"resource.cancel",id:e};window.parent.postMessage(t,"*")}function Yn(e){return fetch(e).then(t=>t.blob())}function Tt(e,t,n=null){return t?t.aborted?(n&&Ut(n),Promise.reject(new DOMException("Aborted","AbortError"))):new Promise((r,o)=>{const i=()=>{n&&Ut(n),o(new DOMException("Aborted","AbortError"))};t.addEventListener("abort",i,{once:!0}),e.then(a=>{t.removeEventListener("abort",i),r(a)},a=>{t.removeEventListener("abort",i),o(a)})}):e}function Dt(e,t){var i;if((i=t==null?void 0:t.signal)!=null&&i.aborted)return Promise.reject(new DOMException("Aborted","AbortError"));const n=fe.get(e);if(n)return Tt(n,t==null?void 0:t.signal);let r,o=null;try{new URL(e).protocol==="data:"?r=Yn(e):(o=crypto.randomUUID(),r=Vn(e,o))}catch{return Promise.reject(new Error(`invalid URL: ${e}`))}return r=r.finally(()=>{fe.delete(e)}),fe.set(e,r),Tt(r,t==null?void 0:t.signal,o)}function Xn(e){const t={url:"",revoke:()=>{}};let n=null,r=!1;const o=Dt(e).then(i=>{if(!r)return n=URL.createObjectURL(i),t.url=n,n});return t.revoke=()=>{r||(r=!0,n&&(URL.revokeObjectURL(n),n=null))},Object.defineProperty(t,"ready",{value:o,enumerable:!1,writable:!1}),t}function er(e){if(!(!e||e.length===0))for(const t of e)fe.set(t.url,Promise.resolve(t.blob))}function tr(e,t,n,r){const o=Array.isArray(e)?e:[e],i=crypto.randomUUID();function a(d){if(d.source!==window.parent)return;const u=d.data;if(typeof u!="object"||u===null||typeof u.type!="string"||!u.type.startsWith("relay."))return;const v=u;if(!(!("subId"in v)||v.subId!==i))if(u.type==="relay.event"){const Mt=u;er(Mt.resources),t(Mt.event)}else u.type==="relay.eose"?n():u.type==="relay.closed"&&window.removeEventListener("message",a)}window.addEventListener("message",a);const c={type:"relay.subscribe",id:crypto.randomUUID(),subId:i,filters:o,...r!=null&&r.relay?{relay:r.relay}:{}};return window.parent.postMessage(c,"*"),{close(){const d={type:"relay.close",id:crypto.randomUUID(),subId:i};window.parent.postMessage(d,"*"),window.removeEventListener("message",a)}}}function nr(e,t){const n=crypto.randomUUID();return new Promise((r,o)=>{function i(c){if(c.source!==window.parent)return;const d=c.data;if(typeof d!="object"||d===null||typeof d.type!="string"||d.type!=="relay.publish.result"&&d.type!=="relay.publish.error")return;const u=d;u.id===n&&(window.removeEventListener("message",i),u.error||d.type==="relay.publish.error"?o(new Error(u.error||"relay:write denied")):u.event?r(u.event):o(new Error("relay.publish.result missing event")))}window.addEventListener("message",i);const a={type:"relay.publish",id:n,event:e};window.parent.postMessage(a,"*")})}function rr(e,t,n="nip44"){const r=crypto.randomUUID();return new Promise((o,i)=>{function a(d){if(d.source!==window.parent)return;const u=d.data;if(typeof u!="object"||u===null||typeof u.type!="string"||u.type!=="relay.publishEncrypted.result")return;const v=u;v.id===r&&(window.removeEventListener("message",a),v.error?i(new Error(v.error)):v.event?o(v.event):i(new Error("relay.publishEncrypted.result missing event")))}window.addEventListener("message",a);const c={type:"relay.publishEncrypted",id:r,event:e,recipient:t,encryption:n};window.parent.postMessage(c,"*")})}function or(e){const t=Array.isArray(e)?e:[e],n=crypto.randomUUID();return new Promise((r,o)=>{function i(c){if(c.source!==window.parent)return;const d=c.data;if(typeof d!="object"||d===null||typeof d.type!="string"||d.type!=="relay.query.result")return;const u=d;u.id===n&&(window.removeEventListener("message",i),u.error?o(new Error(u.error)):r(u.events))}window.addEventListener("message",i);const a={type:"relay.query",id:n,filters:t};window.parent.postMessage(a,"*")})}var ir=3e4,j=new Map,tt=new Set,sr=new Set(["identity.getPublicKey.result","identity.getRelays.result","identity.getProfile.result","identity.getFollows.result","identity.getList.result","identity.getZaps.result","identity.getMutes.result","identity.getBlocked.result","identity.getBadges.result","identity.changed","identity.getPublicKey","identity.getRelays","identity.getProfile","identity.getFollows","identity.getList","identity.getZaps","identity.getMutes","identity.getBlocked","identity.getBadges"]);function ar(e){return sr.has(e.type)}function cr(e){if(ar(e))switch(e.type){case"identity.getPublicKey.result":dr(e.id,e.pubkey);return;case"identity.changed":lr(e.pubkey);return;case"identity.getRelays.result":S(e.id,e.relays,e.error);return;case"identity.getProfile.result":S(e.id,e.profile,e.error);return;case"identity.getFollows.result":S(e.id,e.pubkeys,e.error);return;case"identity.getList.result":S(e.id,e.entries,e.error);return;case"identity.getZaps.result":S(e.id,e.zaps,e.error);return;case"identity.getMutes.result":S(e.id,e.pubkeys,e.error);return;case"identity.getBlocked.result":S(e.id,e.pubkeys,e.error);return;case"identity.getBadges.result":S(e.id,e.badges,e.error);return;case"identity.getPublicKey":case"identity.getRelays":case"identity.getProfile":case"identity.getFollows":case"identity.getList":case"identity.getZaps":case"identity.getMutes":case"identity.getBlocked":case"identity.getBadges":return;default:return}}function dr(e,t){const n=j.get(e);n&&(j.delete(e),clearTimeout(n.timeout),n.resolve(t))}function S(e,t,n){const r=j.get(e);r&&(j.delete(e),clearTimeout(r.timeout),n?r.reject(new Error(n)):r.resolve(t))}function lr(e){if(typeof e=="string")for(const t of tt)t(e)}function h(e){return new Promise((t,n)=>{const r=setTimeout(()=>{j.delete(e.id)&&n(new Error(`${e.type} timed out`))},ir);j.set(e.id,{resolve:t,reject:n,timeout:r}),window.parent.postMessage(e,"*")})}function ur(){const e={type:"identity.getPublicKey",id:crypto.randomUUID()};return h(e)}function fr(e){tt.add(e);let t=!1;return{close(){t||(t=!0,tt.delete(e))}}}function pr(){const e={type:"identity.getRelays",id:crypto.randomUUID()};return h(e)}function yr(){const e={type:"identity.getProfile",id:crypto.randomUUID()};return h(e)}function mr(){const e={type:"identity.getFollows",id:crypto.randomUUID()};return h(e)}function gr(e){const t={type:"identity.getList",id:crypto.randomUUID(),listType:e};return h(t)}function wr(){const e={type:"identity.getZaps",id:crypto.randomUUID()};return h(e)}function br(){const e={type:"identity.getMutes",id:crypto.randomUUID()};return h(e)}function hr(){const e={type:"identity.getBlocked",id:crypto.randomUUID()};return h(e)}function vr(){const e={type:"identity.getBadges",id:crypto.randomUUID()};return h(e)}var Mr=3e4,Me=new Map,nt=new Set,Sr=new Set(["theme.get.result","theme.changed","theme.get"]);function Er(e){return Sr.has(e.type)}function xr(e){if(Er(e))switch(e.type){case"theme.get.result":Ur(e.id,e.theme,e.error);return;case"theme.changed":Tr(e.theme);return;case"theme.get":return;default:return}}function Ur(e,t,n){const r=Me.get(e);r&&(Me.delete(e),clearTimeout(r.timeout),n?r.reject(new Error(n)):r.resolve(t))}function Tr(e){for(const t of nt)t(e)}function kr(e){return new Promise((t,n)=>{const r=setTimeout(()=>{Me.delete(e.id)&&n(new Error(`${e.type} timed out`))},Mr);Me.set(e.id,{resolve:t,reject:n,timeout:r}),window.parent.postMessage(e,"*")})}function Ir(){const e={type:"theme.get",id:crypto.randomUUID()};return kr(e)}function Pr(e){nt.add(e);let t=!1;return{close(){t||(t=!0,nt.delete(e))}}}var O=new Map;function Rr(e,t=[],n=""){let r;try{r=n?JSON.parse(n):void 0}catch{r=n||void 0}const o={type:"inc.emit",topic:e,...r!==void 0?{payload:r}:{}};window.parent.postMessage(o,"*")}function Cr(e,t){const n=(o,i)=>{const a={id:"",pubkey:i,created_at:Math.floor(Date.now()/1e3),kind:0,tags:[["t",e]],content:typeof o=="string"?o:JSON.stringify(o??{}),sig:""};t(o,a)};O.has(e)||O.set(e,[]),O.get(e).push(n);const r={type:"inc.subscribe",id:crypto.randomUUID(),topic:e};return window.parent.postMessage(r,"*"),{close(){const o=O.get(e);if(o){const a=o.indexOf(n);a>=0&&o.splice(a,1),o.length===0&&O.delete(e)}const i={type:"inc.unsubscribe",topic:e};window.parent.postMessage(i,"*")}}}function Ar(e){const t=O.get(e.topic);if(t){const n=e.payload??{},r=e.sender??"";for(const o of t)o(n,r)}}function Ge(e,t){return e.type===t}var Nt=3e4,Dr="napplet-config-schema",pe=null,Se=null,C=new Set,Ee=new Set,ne=new Map,re=new Map,Qe=!1;function Nr(e){Ge(e,"config.registerSchema.result")?$r(e):Ge(e,"config.values")?Lr(e):Ge(e,"config.schemaError")&&Br(e)}function $r(e){const t=re.get(e.id);if(t)if(re.delete(e.id),e.ok)pe=t.schema,t.resolve();else{const n=e.code??"invalid-schema",r=e.error??"schema rejected";t.reject(new Error(`${n}: ${r}`))}}function Lr(e){if(Se=e.values,typeof e.id=="string"){const t=ne.get(e.id);if(!t)return;ne.delete(e.id),t.resolve(e.values);return}for(const t of C)try{t(e.values)}catch{}}function Br(e){const t={code:e.code,error:e.error};for(const n of Ee)try{n(t)}catch{}}function Or(){if(typeof document>"u")return null;const e=document.querySelector(`meta[name="${Dr}"]`);if(!e)return null;const t=e.getAttribute("content");if(!t)return null;try{return JSON.parse(t)}catch{return null}}function $t(e,t){const n=crypto.randomUUID();return new Promise((r,o)=>{re.set(n,{resolve:r,reject:o,schema:e});const i=t===void 0?{type:"config.registerSchema",id:n,schema:e}:{type:"config.registerSchema",id:n,schema:e,version:t};window.parent.postMessage(i,"*"),setTimeout(()=>{re.delete(n)&&o(new Error("config.registerSchema timed out"))},Nt)})}function Lt(){const e=crypto.randomUUID();return new Promise((t,n)=>{ne.set(e,{resolve:t,reject:n});const r={type:"config.get",id:e};window.parent.postMessage(r,"*"),setTimeout(()=>{ne.delete(e)&&n(new Error("config.get timed out"))},Nt)})}function Bt(e){const t=C.size===0;if(C.add(e),t){const n={type:"config.subscribe"};window.parent.postMessage(n,"*")}else if(Se!==null){const n=Se;queueMicrotask(()=>{if(C.has(e)&&n!==null)try{e(n)}catch{}})}return{close(){if(C.delete(e)&&C.size===0){const r={type:"config.unsubscribe"};window.parent.postMessage(r,"*")}}}}function Ot(e){const t=e==null?void 0:e.section,n=t===void 0?{type:"config.openSettings"}:{type:"config.openSettings",section:t};window.parent.postMessage(n,"*")}function _t(e){return Ee.add(e),()=>{Ee.delete(e)}}function _r(){if(Qe)return()=>{};pe=Or();const e=window,t=e.napplet??(e.napplet={}),n={registerSchema:$t,get:Lt,subscribe:Bt,openSettings:Ot,onSchemaError:_t};return Object.defineProperty(n,"schema",{get:()=>pe,enumerable:!0,configurable:!1}),t.config=n,Qe=!0,()=>{C.clear(),Ee.clear(),ne.clear(),re.clear(),Se=null,pe=null,t.config===n&&delete t.config,Qe=!1}}var ft=3e4,xe=new Map,Ue=new Map,Te=new Map,rt=new Set;function ce(e,t){return e.type===t}function qr(e){const t=xe.get(e.id);if(t){if(xe.delete(e.id),clearTimeout(t.timeout),e.error!==void 0){t.reject(new Error(e.error));return}t.resolve(Array.isArray(e.servers)?e.servers:[])}}function Hr(e){const t=Ue.get(e.id);if(t){if(Ue.delete(e.id),clearTimeout(t.timeout),e.error!==void 0){t.reject(new Error(e.error));return}if(e.message===void 0){t.reject(new Error("cvm.request.result missing message"));return}t.resolve(e.message)}}function jr(e){const t=Te.get(e.id);if(t){if(Te.delete(e.id),clearTimeout(t.timeout),e.error!==void 0){t.reject(new Error(e.error));return}t.resolve()}}function Kr(e){if(!(!e.server||!e.message))for(const t of rt)t(e.server,e.message)}function Fr(e){ce(e,"cvm.discover.result")?qr(e):ce(e,"cvm.request.result")?Hr(e):ce(e,"cvm.close.result")?jr(e):ce(e,"cvm.event")&&Kr(e)}function zr(e){const t=crypto.randomUUID();return new Promise((n,r)=>{const o=setTimeout(()=>{xe.delete(t)&&r(new Error("cvm.discover timed out"))},ft);xe.set(t,{resolve:n,reject:r,timeout:o});const i={type:"cvm.discover",id:t,...e===void 0?{}:{query:e}};window.parent.postMessage(i,"*")})}function qt(e,t,n){const r=crypto.randomUUID(),o=(n==null?void 0:n.timeoutMs)??ft;return new Promise((i,a)=>{const c=setTimeout(()=>{Ue.delete(r)&&a(new Error("cvm.request timed out"))},o);Ue.set(r,{resolve:i,reject:a,timeout:c});const d={type:"cvm.request",id:r,server:e,message:t,...n===void 0?{}:{options:n}};window.parent.postMessage(d,"*")})}async function je(e,t,n,r){const o=await qt(e,{jsonrpc:"2.0",id:crypto.randomUUID(),method:t,...n===void 0?{}:{params:n}},r);if(o.error!==void 0){const i=o.error;throw new Error(i!=null&&i.message?`${t}: ${i.message}`:`${t} failed`)}return o.result}async function Jr(e,t){const n=await je(e,"tools/list",void 0,t);return(n==null?void 0:n.tools)??[]}function Gr(e,t,n,r){return je(e,"tools/call",{name:t,...n===void 0?{}:{arguments:n}},r)}async function Qr(e,t){const n=await je(e,"resources/list",void 0,t);return(n==null?void 0:n.resources)??[]}async function Wr(e,t,n){var i;const r=await je(e,"resources/read",{uri:t},n),o=(i=r==null?void 0:r.contents)==null?void 0:i[0];if(!o)throw new Error(`resources/read returned no contents for ${t}`);return o}function Zr(e){const t=crypto.randomUUID();return new Promise((n,r)=>{const o=setTimeout(()=>{Te.delete(t)&&r(new Error("cvm.close timed out"))},ft);Te.set(t,{resolve:n,reject:r,timeout:o});const i={type:"cvm.close",id:t,server:e};window.parent.postMessage(i,"*")})}function Vr(e){return rt.add(e),{close(){rt.delete(e)}}}var pt=3e4,ke=new Map,Ie=new Map,Pe=new Map,K=new Map;function B(e,t){return e.type===t}function Yr(e){const t=ke.get(e.id);if(!t)return;ke.delete(e.id),clearTimeout(t.timeout);const n={events:Array.isArray(e.events)?e.events:[],relays:e.relays??{}};e.incomplete!==void 0&&(n.incomplete=e.incomplete),e.error!==void 0&&(n.error=e.error),t.resolve(n)}function Xr(e){const t=Ie.get(e.id);if(!t)return;Ie.delete(e.id),clearTimeout(t.timeout);const n={ok:e.ok};e.event!==void 0&&(n.event=e.event),e.eventId!==void 0&&(n.eventId=e.eventId),e.relays!==void 0&&(n.relays=e.relays),e.error!==void 0&&(n.error=e.error),t.resolve(n)}function eo(e){const t=Pe.get(e.id);if(t){if(Pe.delete(e.id),clearTimeout(t.timeout),e.error!==void 0){t.reject(new Error(e.error));return}if(!e.plan){t.reject(new Error("outbox.resolveRelays.result missing plan"));return}t.resolve(e.plan)}}function to(e){const t=K.get(e.subId);if(t)for(const n of t.event)n(e.event,e.relay)}function no(e){const t=K.get(e.subId);if(t)for(const n of t.eose)n()}function ro(e){const t=K.get(e.subId);if(t){for(const n of t.closed)n(e.reason);K.delete(e.subId)}}function oo(e){B(e,"outbox.query.result")?Yr(e):B(e,"outbox.publish.result")?Xr(e):B(e,"outbox.resolveRelays.result")?eo(e):B(e,"outbox.event")?to(e):B(e,"outbox.eose")?no(e):B(e,"outbox.closed")&&ro(e)}function io(e,t){const n=crypto.randomUUID(),r=(t==null?void 0:t.timeoutMs)??pt;return new Promise((o,i)=>{const a=setTimeout(()=>{ke.delete(n)&&i(new Error("outbox.query timed out"))},r);ke.set(n,{resolve:o,reject:i,timeout:a});const c={type:"outbox.query",id:n,filters:e,...t===void 0?{}:{options:t}};window.parent.postMessage(c,"*")})}function so(e,t){const n=crypto.randomUUID(),r=crypto.randomUUID(),o={event:new Set,eose:new Set,closed:new Set};K.set(r,o);const i={type:"outbox.subscribe",id:n,subId:r,filters:e,...t===void 0?{}:{options:t}};window.parent.postMessage(i,"*");function a(c,d){c==="event"?o.event.add(d):c==="eose"?o.eose.add(d):c==="closed"&&o.closed.add(d)}return{on:a,close(){if(!K.delete(r))return;const c={type:"outbox.close",id:crypto.randomUUID(),subId:r};window.parent.postMessage(c,"*")}}}function ao(e,t){const n=crypto.randomUUID();return new Promise((r,o)=>{const i=setTimeout(()=>{Ie.delete(n)&&o(new Error("outbox.publish timed out"))},pt);Ie.set(n,{resolve:r,reject:o,timeout:i});const a={type:"outbox.publish",id:n,event:e,...t===void 0?{}:{options:t}};window.parent.postMessage(a,"*")})}function co(e){const t=crypto.randomUUID();return new Promise((n,r)=>{const o=setTimeout(()=>{Pe.delete(t)&&r(new Error("outbox.resolveRelays timed out"))},pt);Pe.set(t,{resolve:n,reject:r,timeout:o});const i={type:"outbox.resolveRelays",id:t,target:e};window.parent.postMessage(i,"*")})}var Ht=3e4,Re=new Map,Ce=new Map,ot=new Set;function We(e,t){return e.type===t}function lo(e){const t=Re.get(e.id);if(t){if(Re.delete(e.id),clearTimeout(t.timeout),e.result!==void 0){t.resolve(e.result);return}t.reject(new Error(e.error??"upload failed"))}}function uo(e){const t=Ce.get(e.id);if(t){if(Ce.delete(e.id),clearTimeout(t.timeout),e.status!==void 0){t.resolve(e.status);return}t.reject(new Error(e.error??"upload status unavailable"))}}function fo(e){if(e.status)for(const t of ot)t(e.status)}function po(e){We(e,"upload.upload.result")?lo(e):We(e,"upload.status.result")?uo(e):We(e,"upload.status.changed")&&fo(e)}function yo(e){const t=crypto.randomUUID();return new Promise((n,r)=>{const o=setTimeout(()=>{Re.delete(t)&&r(new Error("upload.upload timed out"))},Ht);Re.set(t,{resolve:n,reject:r,timeout:o});const i={type:"upload.upload",id:t,request:e};window.parent.postMessage(i,"*")})}function mo(e){const t=crypto.randomUUID();return new Promise((n,r)=>{const o=setTimeout(()=>{Ce.delete(t)&&r(new Error("upload.status timed out"))},Ht);Ce.set(t,{resolve:n,reject:r,timeout:o});const i={type:"upload.status",id:t,uploadId:e};window.parent.postMessage(i,"*")})}function go(e){return ot.add(e),{close(){ot.delete(e)}}}var yt=3e4,Ae=new Map,De=new Map,Ne=new Map,it=new Set;function de(e,t){return e.type===t}function wo(e){const t=Ae.get(e.id);if(t){if(Ae.delete(e.id),clearTimeout(t.timeout),e.result!==void 0){t.resolve(e.result);return}t.reject(new Error(e.error??"invoke failed"))}}function bo(e){const t=De.get(e.id);if(t){if(De.delete(e.id),clearTimeout(t.timeout),e.availability!==void 0){t.resolve(e.availability);return}t.reject(new Error(e.error??"intent availability unavailable"))}}function ho(e){const t=Ne.get(e.id);if(t){if(Ne.delete(e.id),clearTimeout(t.timeout),e.handlers!==void 0){t.resolve(e.handlers);return}t.reject(new Error(e.error??"intent handlers unavailable"))}}function vo(e){if(e.availability)for(const t of it)t(e.availability)}function Mo(e){de(e,"intent.invoke.result")?wo(e):de(e,"intent.available.result")?bo(e):de(e,"intent.handlers.result")?ho(e):de(e,"intent.changed")&&vo(e)}function jt(e){const t=crypto.randomUUID();return new Promise((n,r)=>{const o=setTimeout(()=>{Ae.delete(t)&&r(new Error("intent.invoke timed out"))},yt);Ae.set(t,{resolve:n,reject:r,timeout:o});const i={type:"intent.invoke",id:t,request:e};window.parent.postMessage(i,"*")})}function So(e,t,n){return jt({archetype:e,action:"open",payload:t,...n})}function Eo(e){const t=crypto.randomUUID();return new Promise((n,r)=>{const o=setTimeout(()=>{De.delete(t)&&r(new Error("intent.available timed out"))},yt);De.set(t,{resolve:n,reject:r,timeout:o});const i={type:"intent.available",id:t,archetype:e};window.parent.postMessage(i,"*")})}function xo(){const e=crypto.randomUUID();return new Promise((t,n)=>{const r=setTimeout(()=>{Ne.delete(e)&&n(new Error("intent.handlers timed out"))},yt);Ne.set(e,{resolve:t,reject:n,timeout:r});const o={type:"intent.handlers",id:e};window.parent.postMessage(o,"*")})}function Uo(e){return it.add(e),{close(){it.delete(e)}}}function mt(e){return Array.isArray(e)?e.filter(t=>typeof t=="string"):[]}function To(e){if(typeof e!="object"||e===null||Array.isArray(e))return{};const t={};for(const[n,r]of Object.entries(e))t[n]=mt(r);return t}function ko(e){if(typeof e!="object"||e===null)return{domains:[],protocols:{}};const t=e;return{domains:mt(t.domains),protocols:To(t.protocols)}}function Io(e){const t=e,n=t.class;return{capabilities:ko(t.capabilities),services:mt(t.services),class:typeof n=="number"?n:null}}function Po(e){const t=new Set(e.capabilities.domains);return(n,r)=>{if(typeof n!="string")return!1;if(r!==void 0){const o=e.capabilities.protocols[n];return Array.isArray(o)&&o.includes(r)}return t.has(n)}}function Ro(e,t){return!1}var oe=new Map,ye=new Map,kt=new Map;function st(e,t,n){const r=crypto.randomUUID(),o={type:"nostrdb.request",id:r,method:e,content:t,...n?{subId:n}:{}};return window.parent.postMessage(o,"*"),r}function Q(e,t,n){return new Promise((r,o)=>{const i=st(e,t,n);oe.set(i,{resolve:r,reject:o}),setTimeout(()=>{oe.delete(i)&&o(new Error(`NIPDB request '${e}' timed out`))},1e4)})}function Co(e){const t=oe.get(e.id);if(t){oe.delete(e.id);try{const n=e.content?JSON.parse(e.content):void 0;t.resolve(n)}catch{t.resolve(void 0)}}}function Ao(e){const t=ye.get(e.subId);if(t)try{const n=JSON.parse(e.content);t(n)}catch{}}var Do=["query","add","event","replaceable","count","subscribe"];function It(e){const t=e.data;typeof t!="object"||t===null||typeof t.type!="string"||t.type.startsWith("nostrdb.")&&(t.type==="nostrdb.result"?Co(t):t.type==="nostrdb.event-push"&&Ao(t))}function No(){return window.addEventListener("message",It),window.nostrdb={async query(e){const t=Array.isArray(e)?e:[e];return await Q("query",JSON.stringify(t))??[]},async add(e){return await Q("add",JSON.stringify(e))===!0},async event(e){const t=await Q("event",JSON.stringify({id:e}));return t??void 0},async replaceable(e,t,n){const r={kind:e,author:t};n!==void 0&&(r.identifier=n);const o=await Q("replaceable",JSON.stringify(r));return o??void 0},async count(e){const t=Array.isArray(e)?e:[e],n=await Q("count",JSON.stringify(t));return typeof n=="number"?n:0},async supports(){return[...Do]},async*subscribe(e){const t=crypto.randomUUID(),n=Array.isArray(e)?e:[e];st("subscribe",JSON.stringify(n),t);const r=[];let o=null;function i(){if(o){const a=o;o=null,a()}}ye.set(t,a=>{r.push(a),i()}),kt.set(t,i);try{for(;;)r.length>0?yield r.shift():await new Promise(a=>{o=a})}finally{ye.delete(t),kt.delete(t),st("unsubscribe",JSON.stringify({subId:t}),t)}}},()=>{window.removeEventListener("message",It),delete window.nostrdb,oe.clear(),ye.clear()}}var $o="https://github.com/nostr-protocol/nips/pull/2303/files",Kt="https://napplet.run",Lo="https://github.com/kehto/web",Bo=3e3,Ft=!1,Pt=!1,Z;function Oo(){Ft=!0,Z!==void 0&&(clearTimeout(Z),Z=void 0)}function _o(){if(!Ho()){if(qo()){Rt();return}Z=setTimeout(()=>{Z=void 0,Ft||Rt()},Bo)}}function qo(){try{return window.self===window.top}catch{return!1}}function Ho(){try{if(window.__NAPPLET_ALLOW_STANDALONE__===!0||typeof document<"u"&&document.querySelector('meta[name="napplet-allow-standalone"]'))return!0}catch{}return!1}function Rt(){console.error(`[napplet] No napplet runtime detected. This app is a sandboxed napplet and must run inside a napplet runtime (shell) that proxies Nostr access over postMessage per NIP-5D. Open it in a runtime — see ${Kt}`),jo(Ko)}function jo(e){typeof document>"u"||(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",e,{once:!0}):e())}function Ko(){if(Pt||typeof document>"u"||!document.body)return;Pt=!0;const e=document.createElement("div");e.setAttribute("data-napplet-runtime-error",""),e.style.cssText="position:fixed;inset:0;z-index:2147483647;";const t=e.attachShadow?e.attachShadow({mode:"open"}):e,n=document.createElement("style");n.textContent=zo,t.appendChild(n),t.appendChild(Fo()),document.body.appendChild(e)}function g(e,t={}){const n=document.createElement(e);if(t.className&&(n.className=t.className),t.text!==void 0&&(n.textContent=t.text),t.attrs)for(const[r,o]of Object.entries(t.attrs))n.setAttribute(r,o);return t.children&&n.append(...t.children),n}function Ze(e,t,n){const r=g("span",{text:t,children:[g("small",{text:n})]}),o=g("span",{className:"arrow",text:"→",attrs:{"aria-hidden":"true"}});return g("a",{attrs:{href:e,target:"_blank",rel:"noopener noreferrer"},children:[r,o]})}function Fo(){const e=g("span",{className:"badge",text:"Napplet · no runtime"}),t=g("h1",{text:"This napplet can’t run here",attrs:{id:"napplet-rg-title"}}),n=g("p");n.append("Napplets are sandboxed apps that run ",g("strong",{text:"inside a napplet runtime"}),". The runtime proxies all Nostr access — relays, signing, storage — over ",g("code",{text:"postMessage"}),", the wire format defined by NIP-5D. Opened on its own (for example directly from a NIP-5A nsite gateway), there’s no runtime to talk to, so nothing loads.");const r=g("p",{text:"To use this napplet, open it inside a napplet runtime:"}),o=g("div",{className:"links",children:[Ze(Kt,"napplet.run","Find a runtime to open napplets in"),Ze(Lo,"kehto/web","The reference web runtime — open source"),Ze($o,"NIP-5D specification","How napplets and runtimes talk")]}),i=g("div",{className:"card",children:[e,t,n,r,o]});return g("div",{className:"overlay",attrs:{role:"dialog","aria-modal":"true","aria-labelledby":"napplet-rg-title"},children:[i]})}var zo=`
  :host { all: initial; }
  * { box-sizing: border-box; }
  .overlay {
    position: fixed; inset: 0;
    display: flex; align-items: center; justify-content: center;
    padding: 24px;
    background: rgba(9, 9, 14, 0.82);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  }
  .card {
    width: 100%; max-width: 480px;
    background: #14141b; color: #e9e9f1;
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 16px;
    padding: 32px;
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.5);
    line-height: 1.55;
  }
  .badge {
    display: inline-block;
    font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase;
    color: #8b8ba7; margin-bottom: 14px;
  }
  h1 { margin: 0 0 12px; font-size: 20px; font-weight: 650; letter-spacing: -0.01em; }
  p { margin: 0 0 14px; font-size: 14px; color: #c5c5d6; }
  code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px; padding: 1px 5px; border-radius: 5px;
    background: rgba(255, 255, 255, 0.07); color: #d8d8e6;
  }
  .links { display: flex; flex-direction: column; gap: 8px; margin-top: 20px; }
  a {
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    padding: 12px 14px; border-radius: 10px; text-decoration: none;
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.07);
    color: #e9e9f1; font-size: 13px; font-weight: 550;
    transition: background 0.15s ease, border-color 0.15s ease;
  }
  a:hover { background: rgba(124, 108, 255, 0.14); border-color: rgba(124, 108, 255, 0.45); }
  a small { display: block; margin-top: 2px; font-size: 11px; font-weight: 400; color: #8b8ba7; }
  a .arrow { color: #8b8ba7; flex: none; }
`,F=null,zt=[],$e=new Set;function Jo(e){if(F)return;const t=Io(e);F=t;const n=window.napplet;if(n){const r=n.shell;r.supports=Po(t),r.services=t.services,r.class=t.class}for(const r of zt.splice(0))r(t);for(const r of $e)r(t);$e.clear()}function Go(){return F?Promise.resolve(F):new Promise(e=>{zt.push(e)})}function Qo(e){if(F)return e(F),{close(){}};$e.add(e);let t=!1;return{close(){t||(t=!0,$e.delete(e))}}}var Wo=[["keys.",ln],["media.",vn],["notify.",Bn],["resource.",Zn],["cvm.",Fr],["outbox.",oo],["upload.",po],["intent.",Mo],["identity.",cr],["theme.",xr],["config.",Nr]];function Zo(e){if(e.source!==window.parent)return;const t=e.data;if(typeof t!="object"||t===null||typeof t.type!="string")return;Oo();const n=t.type;if(n==="shell.init"){Jo(t);return}if(n==="inc.event"){Ar(t);return}const r=t;for(const[o,i]of Wo)if(n.startsWith(o)){i(r);return}}window.napplet={relay:{subscribe:tr,publish:nr,publishEncrypted:rr,query:or},inc:{emit:Rr,on:Cr},storage:{getItem:M.getItem.bind(M),setItem:M.setItem.bind(M),removeItem:M.removeItem.bind(M),keys:M.keys.bind(M)},keys:{registerAction:un,unregisterAction:fn,onAction:pn},media:{createSession:Mn,updateSession:Sn,destroySession:En,reportState:xn,reportCapabilities:Un,sendCommand:Tn,onCommand:kn,onState:In,onCapabilities:Pn,onControls:Rn},notify:{send:On,dismiss:_n,badge:qn,registerChannel:Hn,requestPermission:jn,onAction:Kn,onClicked:Fn,onDismissed:zn,onControls:Jn},identity:{getPublicKey:ur,onChanged:fr,getRelays:pr,getProfile:yr,getFollows:mr,getList:gr,getZaps:wr,getMutes:br,getBlocked:hr,getBadges:vr},theme:{get:Ir,onChanged:Pr},config:{registerSchema:$t,get:Lt,subscribe:Bt,openSettings:Ot,onSchemaError:_t,schema:null},resource:{bytes:Dt,bytesAsObjectURL:Xn},cvm:{discover:zr,request:qt,listTools:Jr,callTool:Gr,listResources:Qr,readResource:Wr,close:Zr,onEvent:Vr},outbox:{query:io,subscribe:so,publish:ao,resolveRelays:co},upload:{upload:yo,status:mo,onStatus:go},intent:{invoke:jt,open:So,available:Eo,handlers:xo,onChanged:Uo},shell:{supports:Ro,services:[],class:null,ready:Go,onReady:Qo}};window.addEventListener("message",Zo);window.parent.postMessage({type:"shell.ready"},"*");_o();No();yn();Qn();_r();function Vo(){const e=new Map;function t(o,i){if(e.has(o))throw new Error(`NAP domain "${o}" is already registered`);e.set(o,i)}function n(o){const i=o.type.indexOf(".");if(i<=0)return!1;const a=o.type.slice(0,i),c=e.get(a);return c?(c(o),!0):!1}function r(){return Array.from(e.keys())}return{registerNap:t,dispatch:n,getRegisteredDomains:r}}var Yo=Vo(),Xo=Yo.registerNap,ei="theme";function Jt(){var t;const e=window;if(!((t=e.napplet)!=null&&t.theme))throw new Error("window.napplet.theme not installed -- import @napplet/shim first");return e.napplet.theme}function ti(){return Jt().get()}function ni(e){return Jt().onChanged(e)}Xo(ei,e=>{});function f(){const e=window;if(!e.napplet)throw new Error("window.napplet not installed -- import @napplet/shim first");return e.napplet}var z={getItem(e){return f().storage.getItem(e)},setItem(e,t){return f().storage.setItem(e,t)},removeItem(e){return f().storage.removeItem(e)},keys(){return f().storage.keys()}},Le={getPublicKey(){return f().identity.getPublicKey()},onChanged(e){return f().identity.onChanged(e)},getRelays(){return f().identity.getRelays()},getProfile(){return f().identity.getProfile()},getFollows(){return f().identity.getFollows()},getList(e){return f().identity.getList(e)},getZaps(){return f().identity.getZaps()},getMutes(){return f().identity.getMutes()},getBlocked(){return f().identity.getBlocked()},getBadges(){return f().identity.getBadges()}},ri={bytes(e){return f().resource.bytes(e)},bytesAsObjectURL(e){return f().resource.bytesAsObjectURL(e)}},at={query(e,t){return f().outbox.query(e,t)},subscribe(e,t){return f().outbox.subscribe(e,t)},publish(e,t){return f().outbox.publish(e,t)},resolveRelays(e){return f().outbox.resolveRelays(e)}};const U=24,Be=30315,Oe="snake-scoreboard",ct="snake-best-v1",dt="snake-published-scores-v1",oi=112,s={canvas:l("#gameCanvas"),overlay:l("#overlay"),overlayTitle:l("#overlayTitle"),overlayText:l("#overlayText"),score:l("#scoreValue"),best:l("#bestValue"),status:l("#status"),leaderboardSync:l("#leaderboardSync"),leaderboard:l("#leaderboardList"),startButton:l("#startButton"),reviewButton:l("#reviewButton"),refreshButton:l("#refreshButton"),upButton:l("#upButton"),rightButton:l("#rightButton"),downButton:l("#downButton"),leftButton:l("#leftButton"),scoreDialog:l("#scoreDialog"),closeDialogButton:l("#closeDialogButton"),profileAvatar:l("#profileAvatar"),avatarFallback:l("#avatarFallback"),profileName:l("#profileName"),profileMeta:l("#profileMeta"),dialogScore:l("#dialogScore"),dialogApples:l("#dialogApples"),dialogTime:l("#dialogTime"),cancelSubmitButton:l("#cancelSubmitButton"),confirmSubmitButton:l("#confirmSubmitButton")},Gt=s.canvas.getContext("2d");if(!Gt)throw new Error("Canvas is unavailable");const p=Gt;let b=[],N={x:0,y:0},q="right",H="right",gt="ready",m=0,$=0,lt=0,_e=0,D={score:0,apples:0,seconds:0,at:0},me=null,ge=null,A={pubkey:"",name:"Not signed in",avatarUrl:""},V="",Y=!1,x=[],le=0;function l(e){const t=document.querySelector(e);if(!t)throw new Error(`Missing required element: ${e}`);return t}function y(e){s.status.textContent=e}function T(e,t){s.leaderboardSync.dataset.state=e,s.leaderboardSync.textContent=t}async function w(e,t,n=5e3){let r=0;const o=new Promise((i,a)=>{r=window.setTimeout(()=>{a(new Error(`${t} did not resolve within ${n}ms`))},n)});try{return await Promise.race([e,o])}finally{window.clearTimeout(r)}}function Qt(){const e=[];for(let t=0;t<U;t+=1)for(let n=0;n<U;n+=1)b.some(r=>r.x===n&&r.y===t)||e.push({x:n,y:t});return e[Math.floor(Math.random()*e.length)]??{x:0,y:0}}function ii(e){return q==="up"&&e==="down"||q==="down"&&e==="up"||q==="left"&&e==="right"||q==="right"&&e==="left"}function se(e){gt!=="running"||ii(e)||(H=e,s.canvas.focus())}function si(){const e=b[0]??{x:0,y:0};return H==="up"?{x:e.x,y:e.y-1}:H==="down"?{x:e.x,y:e.y+1}:H==="left"?{x:e.x-1,y:e.y}:{x:e.x+1,y:e.y}}function ai(e,t=b){return e.x<0||e.y<0||e.x>=U||e.y>=U||t.some(n=>n.x===e.x&&n.y===e.y)}function Ke(){s.score.textContent=String(m),s.best.textContent=String(Math.max(m,D.score))}function wt(e,t="",n=""){s.overlay.hidden=!e,s.overlayTitle.textContent=t,s.overlayText.textContent=n}function Fe(){const e=s.canvas.width,t=e/U;p.clearRect(0,0,e,e),p.fillStyle=W("--board-bg"),p.fillRect(0,0,e,e),p.strokeStyle=W("--grid"),p.lineWidth=1;for(let n=1;n<U;n+=1){const r=n*t;p.beginPath(),p.moveTo(r,0),p.lineTo(r,e),p.moveTo(0,r),p.lineTo(e,r),p.stroke()}p.fillStyle=W("--apple"),p.beginPath(),p.arc((N.x+.5)*t,(N.y+.5)*t,t*.34,0,Math.PI*2),p.fill(),b.forEach((n,r)=>{p.fillStyle=W(r===0?"--ink":"--snake-body");const o=r===0?2:3;p.fillRect(n.x*t+o,n.y*t+o,t-o*2,t-o*2)})}function W(e){return getComputedStyle(document.documentElement).getPropertyValue(e).trim()}function bt(){return lt?Math.max(1,Math.round((Date.now()-lt)/1e3)):0}async function ci(){const e=bt();m<=0||Yt(m,e,D.score,D.seconds)&&(D={score:m,apples:$,seconds:e,at:Date.now()},Ke(),await w(z.setItem(ct,JSON.stringify(D)),"storage.setItem"))}function di(){q=H;const e=si(),t=e.x===N.x&&e.y===N.y,n=t?b:b.slice(0,-1);if(ai(e,n)){ui();return}b.unshift(e),t?($+=1,m+=10+Math.floor($/5)*5,N=Qt()):b.pop(),Ke(),Fe()}function li(){window.clearInterval(_e),b=[{x:8,y:12},{x:7,y:12},{x:6,y:12}],q="right",H="right",m=0,$=0,lt=Date.now(),gt="running",N=Qt(),Ke(),Fe(),wt(!1),y("Playing"),s.startButton.textContent="Restart",s.canvas.focus(),_e=window.setInterval(di,oi)}async function ui(){window.clearInterval(_e),gt="over",await ci(),wt(!0,"Game Over",`${m} points`),m>0?(y("Review score"),await tn()):y("Ready")}function ue(e,t){var n;return((n=e.tags.find(r=>r[0]===t))==null?void 0:n[1])??""}function ut(e,t,n=0){if(e.kind!==Be||ue(e,"d")!==Oe)return null;const r=Number(ue(e,"score"));if(!Number.isFinite(r)||r<0)return null;const o=Number(ue(e,"apples")),i=Number(ue(e,"duration"));return{id:e.id,author:e.pubkey,name:qe(e.pubkey),score:Math.floor(r),apples:Number.isFinite(o)?Math.floor(o):0,seconds:Number.isFinite(i)?Math.floor(i):0,createdAt:e.created_at,source:t,relayCount:n}}function fi(e){var t,n;return pi(e)?ut(e.event,"nostr",((n=(t=e.sidecar)==null?void 0:t.relayHints)==null?void 0:n.length)??0):Wt(e)?ut(e,"nostr"):null}function pi(e){return typeof e=="object"&&e!==null&&"event"in e&&Wt(e.event)}function Wt(e){if(typeof e!="object"||e===null)return!1;const t=e;return typeof t.id=="string"&&typeof t.pubkey=="string"&&typeof t.kind=="number"&&typeof t.created_at=="number"&&Array.isArray(t.tags)}function qe(e){return e?`${e.slice(0,6)}...${e.slice(-4)}`:"Guest"}function we(e){x=Vt(e);const t=x.sort((n,r)=>r.score-n.score||n.seconds-r.seconds||r.createdAt-n.createdAt).slice(0,8);if(t.length===0){const n=document.createElement("li");n.className="empty-row",n.textContent="No scores yet",s.leaderboard.replaceChildren(n);return}s.leaderboard.replaceChildren(...t.map(n=>{const r=document.createElement("li"),o=document.createElement("span"),i=document.createElement("small"),a=document.createElement("em"),c=document.createElement("b");return o.textContent=n.name,a.className=`source-badge source-${n.source}`,a.textContent=n.source==="nostr"?"nostr":"cached",i.append(document.createTextNode(`${n.apples} apples - ${n.seconds}s `),a),c.textContent=String(n.score),r.append(o,i,c),r}))}async function ht(){const e=++le;s.refreshButton.disabled=!0,y("Loading scores"),T("loading","Fetching Nostr scores");try{const t=await wi();if(e!==le)return;t.length>0&&(we([...x,...t]),T("cache","Showing cached scores while Nostr loads"));const n=await Zt(),r=await yi(n);if(e!==le)return;const o=r.entries;we([...x,...o]),o.length>0&&await Xt(o);const i=new Date().toLocaleTimeString([],{hour:"2-digit",minute:"2-digit"});r.incomplete?(y("Partial scores loaded"),T("loaded",`Partial Nostr load at ${i}`)):o.length>0?(y("Scores loaded"),T("loaded",`Nostr scores loaded at ${i}`)):(y("No Nostr scores found"),T("loaded",`Nostr loaded at ${i}; no scores found`))}catch(t){we(x);const n=t instanceof Error?t.message:"Scores unavailable";y(n),T("error","Nostr fetch failed; showing cached scores")}finally{e===le&&(s.refreshButton.disabled=!1)}}async function yi(e){const t=[w(at.query([{kinds:[Be],"#d":[Oe],limit:100}],{limit:100,timeoutMs:5e3}),"outbox.query scoreboard",7e3).then(i=>({scope:"scoreboard",result:i}))];e&&t.push(w(at.query([{kinds:[Be],authors:[e],"#d":[Oe],limit:20}],{authors:[e],limit:20,timeoutMs:5e3}),"outbox.query author",7e3).then(i=>({scope:"author",result:i})));const n=await Promise.allSettled(t),r=n.filter(i=>i.status==="fulfilled").map(i=>i.value);if(r.length===0){const i=n.find(a=>a.status==="rejected");throw(i==null?void 0:i.reason)instanceof Error?i.reason:new Error("Scores unavailable")}return{entries:r.flatMap(({result:i})=>i.events).map(fi).filter(i=>!!i),incomplete:r.some(({result:i})=>i.incomplete)||n.some(i=>i.status==="rejected")}}async function mi(){if(m<=0){y("Play first");return}const e={game:"snake",score:m,apples:$,duration:bt(),board:`${U}x${U}`,mode:"classic"},t={kind:Be,created_at:Math.floor(Date.now()/1e3),tags:[["d",Oe],["title","Snake Scoreboard"],["score",String(m)],["apples",String($)],["duration",String(e.duration)],["mode",e.mode],["board",e.board],["client","snake-scoreboard"]],content:JSON.stringify(e)};y("Submitting score"),Y=!0,s.confirmSubmitButton.disabled=!0,s.cancelSubmitButton.disabled=!0;const n=await Zt(),r=n?x.find(a=>a.author===n)??null:null,o=[D,r].filter(a=>!!a).filter(a=>!Yt(m,e.duration,a.score,a.seconds)).sort((a,c)=>c.score-a.score||a.seconds-c.seconds)[0];if(o){Y=!1,s.confirmSubmitButton.disabled=!1,s.cancelSubmitButton.disabled=!1,y(`Existing high score ${o.score} kept`),ie();return}const i=await w(at.publish(t),"outbox.publish",8e3);if(Y=!1,s.confirmSubmitButton.disabled=!1,s.cancelSubmitButton.disabled=!1,!i.ok){y(i.error??"Submit failed");return}if(i.event){const a=ut(i.event,"cache",0);a&&(we([a,...x]),await Xt(x),T("cache","Published score cached; fetching Nostr"))}y("Score submitted"),ie(),await ht()}async function Zt(){return A.pubkey?A.pubkey:w(Le.getPublicKey(),"identity.getPublicKey",3e3).catch(()=>"")}function Vt(e){const t=new Map;for(const n of e){const r=t.get(n.author);(!r||gi(r,n))&&t.set(n.author,n)}return[...t.values()]}function gi(e,t){return t.score!==e.score?t.score>e.score:t.seconds!==e.seconds?t.seconds<e.seconds:t.source!==e.source?t.source==="nostr":t.createdAt!==e.createdAt?t.createdAt>e.createdAt:t.relayCount>e.relayCount}function Yt(e,t,n,r){return e!==n?e>n:r<=0?!0:t<=r}async function wi(){const e=await w(z.getItem(dt),"storage.getItem",3e3).catch(()=>null);if(!e)return[];try{const t=JSON.parse(e);return Array.isArray(t)?t.map(n=>bi(n)).filter(n=>!!n):[]}catch{return await z.removeItem(dt).catch(()=>{}),[]}}async function Xt(e){const t=Vt(e).slice(0,20);await w(z.setItem(dt,JSON.stringify(t)),"storage.setItem",3e3).catch(()=>{})}function bi(e){if(typeof e!="object"||e===null)return null;const t=e;return typeof t.id!="string"||typeof t.author!="string"||typeof t.score!="number"||!Number.isFinite(t.score)?null:{id:t.id,author:t.author,name:typeof t.name=="string"?t.name:qe(t.author),score:Math.max(0,Math.floor(t.score)),apples:Math.max(0,Math.floor(t.apples??0)),seconds:Math.max(0,Math.floor(t.seconds??0)),createdAt:Math.max(0,Math.floor(t.createdAt??0)),source:"cache",relayCount:Math.max(0,Math.floor(t.relayCount??0))}}async function hi(){const e=await w(z.getItem(ct),"storage.getItem");if(e)try{const t=JSON.parse(e);typeof t.score=="number"&&t.score>=0&&(D={score:Math.floor(t.score),apples:Math.max(0,Math.floor(t.apples??0)),seconds:Math.max(0,Math.floor(t.seconds??0)),at:Math.max(0,Math.floor(t.at??0))},Ke())}catch{await z.removeItem(ct)}}async function en(){s.profileName.textContent="Loading profile",s.profileMeta.textContent="Preparing score preview",He("?");const e=await w(Le.getPublicKey(),"identity.getPublicKey",5e3);if(!e){A={pubkey:"",name:"Not signed in",avatarUrl:""},s.profileName.textContent=A.name,s.profileMeta.textContent="Connect an identity to submit",s.confirmSubmitButton.disabled=!0;return}const t=await w(Le.getProfile(),"identity.getProfile",5e3).catch(()=>null),n=(t==null?void 0:t.displayName)||(t==null?void 0:t.name)||qe(e);A={pubkey:e,name:n,avatarUrl:(t==null?void 0:t.picture)??""},s.profileName.textContent=n,s.profileMeta.textContent=qe(e),s.confirmSubmitButton.disabled=Y,He(n),A.avatarUrl&&await vi(A.avatarUrl,n)}async function vi(e,t){vt();try{const n=await w(ri.bytes(e),"resource.bytes",6e3);V=URL.createObjectURL(n),s.profileAvatar.src=V,s.profileAvatar.alt=`${t} avatar`,s.profileAvatar.hidden=!1,s.avatarFallback.hidden=!0}catch{He(t)}}function He(e){vt(),s.profileAvatar.hidden=!0,s.profileAvatar.removeAttribute("src"),s.avatarFallback.hidden=!1,s.avatarFallback.textContent=e.trim().slice(0,1).toUpperCase()||"?"}function vt(){V&&(URL.revokeObjectURL(V),V="")}async function tn(){if(m<=0){y("Play first");return}s.dialogScore.textContent=String(m),s.dialogApples.textContent=String($),s.dialogTime.textContent=`${bt()}s`,s.scoreDialog.hidden=!1,s.confirmSubmitButton.disabled=!0,s.closeDialogButton.focus(),await en().catch(e=>{s.profileName.textContent="Profile unavailable",s.profileMeta.textContent=e instanceof Error?e.message:"Unable to load identity",s.confirmSubmitButton.disabled=!0,He("?")})}function ie(){Y||(s.scoreDialog.hidden=!0,s.canvas.focus())}function Ct(e){const t=document.documentElement;t.style.setProperty("--app-bg",e.background),t.style.setProperty("--text",e.text),t.style.setProperty("--accent",e.primary),t.style.setProperty("--surface",`color-mix(in srgb, ${e.background} 88%, ${e.text})`),t.style.setProperty("--surface-raised",`color-mix(in srgb, ${e.background} 78%, ${e.text})`),t.style.setProperty("--surface-muted",`color-mix(in srgb, ${e.background} 70%, ${e.text})`),t.style.setProperty("--border",`color-mix(in srgb, ${e.text} 24%, ${e.background})`),t.style.setProperty("--muted",`color-mix(in srgb, ${e.text} 62%, ${e.background})`),t.style.setProperty("--accent-strong",`color-mix(in srgb, ${e.primary} 78%, ${e.text})`),t.style.setProperty("--control-bg",`color-mix(in srgb, ${e.background} 82%, ${e.text})`),t.style.setProperty("--control-hover",`color-mix(in srgb, ${e.background} 70%, ${e.primary})`),t.style.setProperty("--control-text",e.text),t.style.setProperty("--ink",e.text),t.style.setProperty("--snake-body",`color-mix(in srgb, ${e.text} 70%, ${e.primary})`),t.style.setProperty("--apple",`color-mix(in srgb, #d85f58 74%, ${e.primary})`),t.style.setProperty("--board-bg",`color-mix(in srgb, ${e.background} 84%, ${e.text})`),t.style.setProperty("--grid",`color-mix(in srgb, ${e.text} 10%, ${e.background})`),t.style.setProperty("--dialog-scrim",`color-mix(in srgb, ${e.background} 72%, transparent)`),Fe()}async function Mi(){const e=await w(ti(),"theme.get",5e3);Ct(e.colors),me=ni(t=>{Ct(t.colors)})}function Si(){ge=Le.onChanged(()=>{s.scoreDialog.hidden||en()})}s.startButton.addEventListener("click",li);s.reviewButton.addEventListener("click",()=>void tn());s.refreshButton.addEventListener("click",()=>void ht());s.upButton.addEventListener("click",()=>se("up"));s.rightButton.addEventListener("click",()=>se("right"));s.downButton.addEventListener("click",()=>se("down"));s.leftButton.addEventListener("click",()=>se("left"));s.closeDialogButton.addEventListener("click",ie);s.cancelSubmitButton.addEventListener("click",ie);s.confirmSubmitButton.addEventListener("click",()=>void mi());s.scoreDialog.addEventListener("click",e=>{e.target===s.scoreDialog&&ie()});s.canvas.addEventListener("keydown",e=>{const n={ArrowUp:"up",w:"up",W:"up",ArrowRight:"right",d:"right",D:"right",ArrowDown:"down",s:"down",S:"down",ArrowLeft:"left",a:"left",A:"left"}[e.key];n&&(e.preventDefault(),se(n))});window.addEventListener("beforeunload",()=>{window.clearInterval(_e),me==null||me.close(),ge==null||ge.close(),vt()});wt(!0,"Ready","Press Start");b=[{x:11,y:12},{x:10,y:12},{x:9,y:12}];N={x:15,y:12};Fe();hi();ht();Mi().catch(()=>{y("Theme unavailable")});Si();</script>
    <style rel="stylesheet" crossorigin>:root{color-scheme:dark;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;background:var(--app-bg);color:var(--text);--app-bg: #172a27;--surface: #213431;--surface-raised: #263b37;--surface-muted: #2c423e;--border: #405653;--text: #d3dcda;--muted: #98aaa6;--accent: #48b06c;--accent-strong: #70c98c;--control-bg: #203430;--control-hover: #28423c;--control-text: #d3dcda;--ink: #d3dcda;--snake-body: #b3c1bd;--apple: #c76a63;--board-bg: #203431;--grid: #2d433f;--dialog-scrim: rgb(7 18 16 / 62%);--napplet-text-selection: none}*{box-sizing:border-box}:where(html,body,body *){-webkit-user-select:var(--napplet-text-selection, none);user-select:var(--napplet-text-selection, none)}:where(input,textarea,select,[contenteditable],[data-napplet-select=text]){-webkit-user-select:text;user-select:text}:where([data-napplet-select=all]){-webkit-user-select:all;user-select:all}body{margin:0;min-width:260px}button{min-height:2.4rem;border:1px solid var(--border);border-radius:6px;background:var(--control-bg);color:var(--control-text);padding:.55rem .75rem;cursor:pointer;font:inherit;font-weight:800}button:hover{background:var(--control-hover);border-color:color-mix(in srgb,var(--accent) 42%,var(--border))}button:focus-visible,canvas:focus-visible{outline:3px solid color-mix(in srgb,var(--accent) 35%,transparent);outline-offset:2px}.app-shell{min-height:100vh;padding:clamp(.5rem,2vw,1.25rem);background:var(--app-bg);color:var(--text)}.game-shell{width:min(980px,100%);margin:0 auto;display:grid;gap:.75rem}.topbar{display:flex;align-items:center;justify-content:space-between;gap:.75rem;min-height:2rem}h1,h2{margin-top:0;letter-spacing:0}h1{margin-bottom:0;font-size:clamp(1.45rem,5vw,2rem);line-height:1.05}h2{margin-bottom:0;font-size:.95rem}.score-strip{display:flex;flex-wrap:wrap;justify-content:end;gap:.5rem}.score-strip span{min-width:5.2rem;border:1px solid var(--border);border-radius:6px;background:var(--surface-raised);padding:.36rem .5rem;color:var(--muted);font-size:.72rem;font-weight:800;text-transform:uppercase}.score-strip b{display:block;color:var(--text);font-size:1.12rem;line-height:1}.play-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(13rem,17rem);gap:.8rem;align-items:start}.board-wrap{position:relative;width:min(100%,78vh);aspect-ratio:1;justify-self:center;border:1px solid var(--border);border-radius:6px;background:var(--surface-raised);overflow:hidden}canvas{display:block;width:100%;height:100%;touch-action:none}.overlay{position:absolute;top:0;right:0;bottom:0;left:0;display:grid;place-content:center;gap:.35rem;background:color-mix(in srgb,var(--board-bg) 78%,transparent);text-align:center}.overlay[hidden]{display:none}.overlay strong{font-size:clamp(1.4rem,2rem,2rem)}.overlay span{color:var(--muted);font-weight:700}.side-panel{display:grid;gap:.75rem}.actions,.section-head{display:flex;gap:.5rem}.actions button{flex:1 1 0;min-height:2.5rem}.pad{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:.45rem}.pad button{aspect-ratio:1;min-height:0;padding:0;font-size:1.1rem}.leaderboard{display:grid;gap:.55rem;border:1px solid var(--border);border-radius:6px;background:var(--surface-raised);padding:.75rem}.section-head{align-items:center;justify-content:space-between}.section-head button{min-height:2rem;padding:.35rem .55rem;font-size:.78rem}.sync-line{min-height:1.2rem;margin:-.2rem 0 0;color:var(--muted);font-size:.72rem;font-weight:800;overflow-wrap:anywhere}.sync-line[data-state=loading],.sync-line[data-state=cache]{color:color-mix(in srgb,var(--accent) 72%,var(--muted))}.sync-line[data-state=loaded]{color:var(--accent-strong)}.sync-line[data-state=error]{color:color-mix(in srgb,var(--apple) 76%,var(--text))}ol{display:grid;gap:.4rem;margin:0;padding:0;list-style:none}li{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.15rem .5rem;align-items:center;border-radius:6px;background:var(--surface-muted);padding:.45rem .55rem}li span,li small{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}li small{grid-column:1;color:var(--muted)}.source-badge{display:inline-block;border:1px solid var(--border);border-radius:999px;padding:.05rem .35rem;color:var(--muted);font-size:.62rem;font-style:normal;font-weight:900;line-height:1.2;text-transform:uppercase}.source-nostr{border-color:color-mix(in srgb,var(--accent) 48%,var(--border));color:var(--accent-strong)}.source-cache{border-color:color-mix(in srgb,var(--muted) 36%,var(--border))}li b{grid-column:2;grid-row:1 / span 2}.empty-row{grid-template-columns:1fr;color:var(--muted);font-weight:700}.status{min-height:2rem;border:1px solid var(--border);border-radius:6px;background:var(--surface-raised);padding:.45rem .55rem;color:var(--muted);font-size:.82rem;font-weight:800;overflow-wrap:anywhere}.score-dialog{position:fixed;top:0;right:0;bottom:0;left:0;z-index:20;display:grid;place-items:center;padding:1rem;background:var(--dialog-scrim)}.score-dialog[hidden]{display:none}.dialog-panel{width:min(23rem,100%);display:grid;gap:.85rem;border:1px solid var(--border);border-radius:8px;background:var(--surface-raised);color:var(--text);padding:.9rem;box-shadow:0 1rem 2.4rem color-mix(in srgb,#000000 24%,transparent)}.dialog-head,.dialog-actions{display:flex;align-items:center;justify-content:space-between;gap:.6rem}.icon-button{width:2rem;min-height:2rem;padding:0;font-size:1.25rem;line-height:1}.player-preview{display:grid;grid-template-columns:auto minmax(0,1fr);gap:.65rem;align-items:center}.player-preview strong,.player-preview small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.player-preview small{margin-top:.15rem;color:var(--muted)}.avatar{width:3.4rem;aspect-ratio:1;display:grid;place-items:center;border:1px solid var(--border);border-radius:50%;background:var(--surface-muted);color:var(--accent-strong);overflow:hidden;font-size:1.35rem;font-weight:900}.avatar img{width:100%;height:100%;object-fit:cover}.score-preview{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:.5rem;margin:0}.score-preview div{border-radius:6px;background:var(--surface-muted);padding:.55rem}.score-preview dt,.score-preview dd{margin:0}.score-preview dt{color:var(--muted);font-size:.7rem;font-weight:800;text-transform:uppercase}.score-preview dd{margin-top:.2rem;font-size:1.15rem;font-weight:900}.dialog-actions button{flex:1 1 0}button:disabled{cursor:not-allowed;opacity:.52}@media(max-width:680px){.app-shell{padding:.5rem}.topbar,.play-grid{display:grid;grid-template-columns:1fr}.score-strip{justify-content:stretch}.score-strip span{flex:1 1 0}.board-wrap{width:min(100%,70vh)}.side-panel{grid-template-columns:minmax(0,1fr) minmax(8.5rem,12rem)}.leaderboard{grid-column:1 / -1}}@media(max-width:360px),(max-height:520px){.game-shell{gap:.45rem}h1{font-size:1.25rem}.score-strip span{min-width:0;font-size:.66rem}.score-strip b{font-size:1rem}.play-grid,.side-panel{gap:.45rem}.board-wrap{width:min(100%,62vh)}.leaderboard{padding:.55rem}ol{max-height:7.25rem;overflow:auto}li{padding:.35rem .45rem}.score-dialog{padding:.5rem}.dialog-panel{gap:.55rem;padding:.65rem}.avatar{width:2.8rem}.score-preview{gap:.35rem}.score-preview div{padding:.4rem}}</style>
    <meta name="napplet-type" content="snake-scoreboard">
    <meta name="napplet-requires" content="storage,outbox,identity,theme,resource">
    <meta name="napplet-config-schema" content="{&quot;type&quot;:&quot;object&quot;,&quot;properties&quot;:{&quot;accentColor&quot;:{&quot;type&quot;:&quot;string&quot;,&quot;enum&quot;:[&quot;blue&quot;,&quot;green&quot;,&quot;amber&quot;],&quot;default&quot;:&quot;blue&quot;,&quot;x-napplet-section&quot;:&quot;appearance&quot;,&quot;x-napplet-order&quot;:1},&quot;defaultRelayLimit&quot;:{&quot;type&quot;:&quot;integer&quot;,&quot;minimum&quot;:1,&quot;maximum&quot;:50,&quot;default&quot;:5,&quot;x-napplet-section&quot;:&quot;relay&quot;,&quot;x-napplet-order&quot;:1}},&quot;required&quot;:[&quot;accentColor&quot;]}">
  </head>
  <body>
    <main id="app" class="app-shell">
      <section class="game-shell" aria-labelledby="app-title">
        <header class="topbar">
          <h1 id="app-title">Snake</h1>
          <div class="score-strip" aria-label="Scores">
            <span><b id="scoreValue">0</b> Score</span>
            <span><b id="bestValue">0</b> Best</span>
          </div>
        </header>

        <section class="play-grid">
          <section class="board-wrap" aria-label="Game board">
            <canvas
              id="gameCanvas"
              width="480"
              height="480"
              tabindex="0"
              role="img"
              aria-label="Snake game board"
            ></canvas>
            <div id="overlay" class="overlay" aria-live="polite">
              <strong id="overlayTitle">Ready</strong>
              <span id="overlayText">Press Start</span>
            </div>
          </section>

          <aside class="side-panel" aria-label="Game controls and leaderboard">
            <div class="actions">
              <button id="startButton" type="button">Start</button>
              <button id="reviewButton" type="button">Review</button>
            </div>

            <div class="pad" aria-label="Direction controls">
              <span></span>
              <button id="upButton" type="button" aria-label="Move up">↑</button>
              <span></span>
              <button id="leftButton" type="button" aria-label="Move left">←</button>
              <button id="downButton" type="button" aria-label="Move down">↓</button>
              <button id="rightButton" type="button" aria-label="Move right">→</button>
            </div>

            <section class="leaderboard" aria-labelledby="leaderboardTitle">
              <div class="section-head">
                <h2 id="leaderboardTitle">High Scores</h2>
                <button id="refreshButton" type="button">Refresh</button>
              </div>
              <p id="leaderboardSync" class="sync-line" data-state="loading">Fetching Nostr scores</p>
              <ol id="leaderboardList"></ol>
            </section>

            <output id="status" class="status">Loading scores</output>
          </aside>
        </section>

        <div id="scoreDialog" class="score-dialog" role="dialog" aria-modal="true" aria-labelledby="dialogTitle" hidden>
          <section class="dialog-panel">
            <header class="dialog-head">
              <h2 id="dialogTitle">Submit Score</h2>
              <button id="closeDialogButton" class="icon-button" type="button" aria-label="Close">×</button>
            </header>

            <div class="player-preview">
              <div class="avatar">
                <img id="profileAvatar" alt="" hidden />
                <span id="avatarFallback">?</span>
              </div>
              <div>
                <strong id="profileName">Loading profile</strong>
                <small id="profileMeta">Preparing score preview</small>
              </div>
            </div>

            <dl class="score-preview">
              <div>
                <dt>Score</dt>
                <dd id="dialogScore">0</dd>
              </div>
              <div>
                <dt>Apples</dt>
                <dd id="dialogApples">0</dd>
              </div>
              <div>
                <dt>Time</dt>
                <dd id="dialogTime">0s</dd>
              </div>
            </dl>

            <div class="dialog-actions">
              <button id="cancelSubmitButton" type="button">Cancel</button>
              <button id="confirmSubmitButton" type="button">Submit</button>
            </div>
          </section>
        </div>
      </section>
    </main>
  </body>
</html>