@onceworlds/cli 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/emulator.js CHANGED
@@ -1,6 +1,6 @@
1
- "use strict";(()=>{var He=["#f6dcc5","#ebc29f","#d4a07a","#b07a55","#875839","#5e3d29","#93c9bb","#a8c595","#94bcd3","#ab9bd9","#dca3ad","#b3bccb"],be=["#c95f56","#dc8a4f","#e2bd52","#9cc27a","#5a8a5e","#4f9a8e","#6fb7c8","#5b87c5","#465089","#8b68a8","#dc8fae","#f2eee6","#a9a8ad","#5d5c63","#2b2a30","#80604d"],Fe=["Happy","Grin","Shades","Wink","Surprised","Determined","Joyful","Shy"],qe=["None","Sprout","Cap","Beanie","Crown","Headphones","Horns","Antenna","Bow","Halo","Party hat","Cat ears","Short hair","Tuft","Bob","Spikes","Bun","Curls","Long","Mop"];var Ke=["Plain","Band","Spots","Stripes","Star"];var jo={skin:He.length,shirt:be.length,pants:be.length,face:Fe.length,hat:qe.length,top:Ke.length},Xt=[[5,6],[3,4],[6,7],[9,8],[10,0],[12,13]];function Ve(t){let e=2166136261;for(let c of t)e=Math.imul(e^c.charCodeAt(0),16777619)>>>0;let o=c=>(e=Math.imul(e^e>>>13,2654435761)>>>0,e%c),r=o(He.length),n=r>=6?Xt[r-6][o(2)]:o(be.length),a=o(2)===0?n:[8,13,14,15][o(4)],i=o(5)===0?1:12+o(qe.length-12),s=o(2)===0?0:o(Ke.length);return{skin:r,shirt:n,pants:a,face:o(Fe.length),hat:i,top:s}}var E={roomMaxPlayersDefault:16,roomMaxPlayersCap:30,teamsMax:8,messageBytes:16*1024,presenceBytes:1024,stateKeyLength:64,stateValueBytes:16*1024,stateKeys:256,privateKeys:16,privateValueBytes:4*1024,privateBytesPerPlayer:8*1024,messagesPerSecond:60,messagesPerSecondMin:10,roomBytesPerSecond:128*1024,roomBytesPerSecondMin:16*1024,presenceHz:20,presenceHzMin:5,roomMessagesPerSecond:900,roomBytesPerSecondTotal:2*1024*1024,matchCountdownMs:3e3,matchCountdownMaxMs:1e4,matchIdleMs:45e3,matchAbandonMs:12e4,idleAfterMs:3e4,idleAbandonMs:5*6e4,rankedPairMatchesPerDay:5,chatLength:200,chatIntervalMs:750,chatHistory:50,chatChannelsPerPlayer:10,saveValueBytes:64*1024,saveKeysPerGame:100,saveBytesPerGame:1024*1024,guestSaveBytesPerGame:128*1024,serverDataKeys:128,serverDataBytes:512*1024,serverDataServersPerGame:5e3,serverDataBytesPerGame:64*1024*1024,serverDataIdleDays:120,gameDataKeys:32,gameDataListMax:100,gameDataItemBytes:1024,gameDataValueBytes:8*1024,gameDataMapFields:1e3,gameDataMapPerPlayer:100,gameDataNumberMax:1e15,gameDataWritesPerMinute:30,deployFiles:2e3,deployFileBytes:25*1024*1024,deployHtmlBytes:10*1024*1024,deployPathLength:200,deployTotalBytes:100*1024*1024,envVars:50,envValueBytes:4*1024,envTotalBytes:16*1024,proxyRequestBytes:1024*1024,proxyTextRequestBytes:256*1024,proxySpeechRequestBytes:16*1024,proxyResponseBytes:10*1024*1024,proxyMaxOutputTokens:4096,proxyCallsPerDay:2e3,proxyCallsPerDayMax:1e5,proxyCallsPerPlayerPerDay:100,proxyCallsPerNetworkPerDay:1e3,proxyMediaCallsPerDay:100,proxyMediaCallsPerDayMax:1e4,proxyMediaCallsPerPlayerPerDay:20,proxyMediaCallsPerNetworkPerDay:25,proxyUnitsPerDay:2e6,proxyUnitsPerDayMax:1e8,proxyUnitsPerPlayerPerDay:25e4,proxyUnitsPerGuestPerDay:1e5,proxyUnitsPerNetworkPerDay:5e5,errorReportsPerPage:10,consoleLineLength:2e3,consoleLinesPerSecond:50,gameNameLength:60,gameDescriptionLength:1e3,galleryItems:5,mediaImageBytes:5*1024*1024,mediaVideoBytes:30*1024*1024,mediaGifBytes:1024*1024,mediaGifSide:512,mediaUploadsPerDay:200,mediaBytesPerDay:500*1024*1024,badgesPerGame:50,controlHints:12,controlHintKeys:4,controlKeyLength:12,controlActionLength:40,helpLength:400,badgeNameLength:50,badgeDescriptionLength:200,leaderboardsPerGame:25,leaderboardTop:100,gamesPerOwner:50,gamesCreatedPerCreatorPerDay:3,uploadAccountAgeMs:10080*6e4,deploysPerOwnerPerDay:100,draftsPerOwnerPerDay:1e3,draftsKeptPerGame:3,cliTokensPerAccount:10,cliDeviceCodeSeconds:600,deploymentsKeptPerGame:10,deployBytesPerOwnerPerDay:2*1024*1024*1024,deployEntriesPerOwnerPerDay:1e5,bioLength:500,exportRows:2e3,exportSaveChars:4*1024*1024,exportEveryMs:60*6e4,friendsMax:200,friendRequestsMax:100,friendRequestsIncomingMax:50,friendRequestsPerDay:20,friendRequestsPerNetworkPerDay:120,friendRequestPushesPerDay:5,passwordMinLength:8,passwordMaxLength:72,signupsPerNetworkPerDay:100,signupsPerWidePerDay:300,guestsPerNetworkPerDay:300,guestsPerWidePerDay:1500,visitsPerNetworkPerDay:10,visitSeconds:10,errorsPerDeployment:100,controlButtons:4,controlLabelLength:12,partySize:6,partyChatHistory:50,messageLength:500,messagesPerPage:50,groupChatMembers:20,groupChatNameLength:40,conversationsListed:50,notificationsListed:50,invitesPerRequest:10,inviteLiveMs:30*6e4,partyInviteMs:60*6e4};var Ye=/^[a-z0-9]{12}$/,Z=2;function we(t){return`${t.mode}.${t.max}.${t.teams}.${t.ranked?1:0}${t.min!==Z?`.${t.min}`:""}`}var Zt=/^pub:([a-z0-9-]+)\.(\d+)\.(\d)\.([01])(?:\.(\d+))?:(\d+)$/;function Je(t){let e=Zt.exec(t);if(!e)return null;let o={mode:e[1],max:Number(e[2]),teams:Number(e[3]),ranked:e[4]==="1",min:e[5]===void 0?Z:Number(e[5])};return{...o,name:we(o),n:Number(e[6])}}function Xe(t,e,o){let r=typeof t=="string"?Number(t):t;return typeof r!="number"||!Number.isInteger(r)?Math.min(o,e):Math.min(e,Math.max(1,r))}var We=t=>t.replace(/[\u0000-\u001f\u007f]/g," ").replace(/\s+/g," ").trim();var Qt=/^[a-z0-9][a-z0-9-]{0,19}$/;function Ze(t){if(!t||typeof t!="object")return null;let e=t,o=[];for(let n of Array.isArray(e.buttons)?e.buttons:[]){if(o.length>=E.controlButtons)break;let{id:a,label:i}=n??{};if(typeof a!="string"||!Qt.test(a)||o.some(c=>c.id===a))continue;let s=typeof i=="string"?i.replace(/[\u0000-\u001f\u007f]/g,"").replace(/\s+/g," ").trim().slice(0,E.controlLabelLength).trim():"";o.push({id:a,label:s||a})}let r=e.stick===!0;return r||o.length>0?{stick:r,zone:e.zone==="corner"?"corner":"wide",buttons:o}:null}var K="host:ow.settings",eo=/^[a-z][a-zA-Z0-9]{0,15}$/;function Qe(t){if(!Array.isArray(t))return[];let e=[];for(let o of t){if(e.length>=6)break;let{id:r,label:n,options:a,default:i}=o??{};if(typeof r!="string"||!eo.test(r)||e.some(d=>d.id===r)||!Array.isArray(a))continue;let s=[];for(let d of a){let p=typeof d=="object"&&d!==null?d.value:d;if(typeof p!="string"&&typeof p!="number"||typeof p=="number"&&!Number.isFinite(p)||s.length>=8||s.some(u=>u.value===p))continue;let m=typeof d=="object"&&d!==null&&typeof d.label=="string"?d.label:String(p);s.push({value:p,label:We(m).slice(0,20)||String(p)})}if(s.length<2)continue;let c=typeof n=="string"?We(n).slice(0,24):"";e.push({id:r,label:c||r,options:s,default:s.some(d=>d.value===i)?i:s[0].value})}return e}function et(t,e){let o=e!==null&&typeof e=="object"?e[t.id]:void 0;return t.options.some(r=>r.value===o)?o:t.default}var ve='{"t":"ping"}';var tt=4001,ot=4003,rt=4008,nt=4006;var at="onceworlds:hello",it="onceworlds:connect",to=/^[a-z0-9]{1,32}~(?:(?:0|[1-9]\d{0,3})\.(?:0|[1-9]\d{0,4})\.(?:0|[1-9]\d{0,5}))?~[0-9a-z]{1,11}~[lp]~[A-Za-z0-9_-]{43}$/;function st(t){let e=t!==null&&typeof t=="object"?t.build:void 0;return typeof e=="string"&&to.test(e)?e:null}var lt="onceworlds-console";var pt="allow-scripts allow-pointer-lock allow-forms allow-orientation-lock",dt=["autoplay","gamepad","screen-wake-lock","accelerometer","gyroscope"].map(t=>`${t} *`).join("; "),ct=100*1024*1024;var gt=new Set(["server","join","deploy","watch","player","embed","k","console","tester","live","demo",lt]);var ye="onceworlds:page",oo=["ran","blocked","failed","threw","loaded"];function ht(t){if(t===null||typeof t!="object")return null;let{what:e,detail:o}=t;return typeof e!="string"||!oo.includes(e)?null:{what:e,detail:typeof o=="string"?o.slice(0,200):""}}var ro=`(function(){var n=0,s=function(w,d){if(n++<12)try{parent.postMessage({type:${JSON.stringify(ye)},what:w,detail:String(d).slice(0,200)},"*")}catch(e){}};s("ran",document.readyState);addEventListener("securitypolicyviolation",function(e){s("blocked",e.effectiveDirective+" "+e.blockedURI)});addEventListener("error",function(e){var t=e.target;t&&t.tagName?s("failed",t.tagName+" "+(t.src||t.href)):s("threw",e.message+" "+e.filename+":"+e.lineno)},true);addEventListener("load",function(){s("loaded",typeof onceworlds)})})();`,Fo=`<script>${ro}<\/script>`;function ut(t){return t?.ownCountdown!==!0}var no=/^#(?:[0-9a-f]{3}){1,2}$/i,C={panel:"#111216",text:"#f7f7f8",accent:"#d33e00"},Se=4.5,ke=3,Q=["#4ade80","#15803d"];function xt(t){let e=[];if(t==null)return{look:null,notes:e};if(typeof t!="object")return{look:null,notes:["a look is { accent, panel, text } or null"]};let o=t,r=p=>{let m=o[p];return m==null?null:typeof m=="string"&&no.test(m)?ft(m):(e.push(`${p} is not #rgb or #rrggbb`),null)},n=r("panel"),a=r("text"),i=r("accent");if(n||a){let p=A(a??C.text,n??C.panel);p<Se&&(e.push(`text on panel is ${p.toFixed(1)}:1, under ${Se}:1: the platform's are used`),n=a=null)}let s=n??C.panel,c=a??C.text;if(i){let p=A(i,s);p<ke&&(e.push(`accent on panel is ${p.toFixed(1)}:1, under ${ke}:1: the platform's is used`),i=null)}if(!i&&n&&A(C.accent,n)<ke&&(i=c),!n&&!a&&!i)return{look:null,notes:e};let d=i?ao(i):null;return{look:{panel:n,text:a,muted:n||a?io(c,s):null,accent:i,accentHover:i?bt(i,d==="#ffffff"?"#000000":"#ffffff",.14):null,onAccent:d,ok:n?A(Q[0],n)>=A(Q[1],n)?Q[0]:Q[1]:null},notes:e}}function ft(t){let e=t.slice(1).toLowerCase();return`#${e.length===3?[...e].map(o=>o+o).join(""):e}`}function V(t){let e=ft(t);return[parseInt(e.slice(1,3),16),parseInt(e.slice(3,5),16),parseInt(e.slice(5,7),16)]}function mt(t){let[e,o,r]=V(t).map(n=>{let a=n/255;return a<=.04045?a/12.92:((a+.055)/1.055)**2.4});return .2126*e+.7152*o+.0722*r}function A(t,e){let[o,r]=[mt(t),mt(e)].sort((n,a)=>a-n);return(o+.05)/(r+.05)}function ao(t){return A(t,"#000000")>A(t,"#ffffff")?"#000000":"#ffffff"}function bt(t,e,o){let[r,n]=[V(t),V(e)];return`#${r.map((a,i)=>Math.round(a+(n[i]-a)*o).toString(16).padStart(2,"0")).join("")}`}function io(t,e){for(let o=.3;o>.001;o-=.05){let r=bt(t,e,o);if(A(r,e)>=Se)return r}return t}var ee=class{port=null;handlers;events;onConnect;solo;demo;onActivity;onPage;constructor(e,o,r,n,a={}){this.handlers=o,this.events=r,this.onConnect=n,this.solo=a.solo===!0,this.demo=a.demo===!0,this.onActivity=a.onActivity??null,this.onPage=a.onPage??null,window.addEventListener("message",i=>{if(!(i.source!==e.contentWindow||i.origin!=="null")&&(i.data?.type===at&&this.connect(i.source,st(i.data)),i.data?.type===ye)){let s=ht(i.data);s&&this.onPage?.(s)}})}activity(e,o){try{this.onActivity?.(e,o)}catch{}}emit(e,o){this.port?.postMessage({kind:"event",name:e,data:o})}connect(e,o){this.port?.close();let r=new MessageChannel,n=r.port1;this.port=n,n.onmessage=i=>this.receive(n,i.data);let a={type:it,...this.solo?{solo:!0}:{},...this.demo?{demo:!0}:{}};e.postMessage(a,"*",[r.port2]),this.onConnect(o)}async receive(e,o){if(!(e!==this.port||!o||typeof o!="object")){if(o.kind==="event"){Object.hasOwn(this.events,o.name)&&(this.events[o.name](o.data),this.activity(String(o.name),null));return}if(o.kind==="req")try{if(!Object.hasOwn(this.handlers,o.method))throw new Error(`Unknown method: ${String(o.method)}`);let r=this.handlers[o.method],n=await r(o.params);e.postMessage({kind:"res",id:o.id,ok:!0,result:n??null}),this.activity(String(o.method),null)}catch(r){let n=r instanceof Error?r.message:String(r);e.postMessage({kind:"res",id:o.id,ok:!1,error:n}),this.activity(String(o.method),n)}}}};var so="ow-game-storage",lo=256,wt="guest";function ze(t){let e=0,o=new Set,r=[t];for(;r.length;){let n=r.pop();if(typeof n=="string")e+=n.length*2;else if(typeof n!="object"||n===null)e+=8;else{if(o.has(n))continue;if(o.add(n),n instanceof ArrayBuffer)e+=n.byteLength;else if(ArrayBuffer.isView(n))e+=n.buffer.byteLength;else if(typeof Blob=="function"&&n instanceof Blob)e+=n.size;else if(n instanceof Map)for(let[a,i]of n)r.push(a,i);else if(n instanceof Set)for(let a of n)r.push(a);else if(Array.isArray(n))r.push(...n.slice(0,1e5),...n.length>1e5?[n.slice(1e5)]:[]);else for(let[a,i]of Object.entries(n))r.push(a,i)}}return e}function $(t){if(typeof t!="string"||t.length>lo)throw new Error("Bad name");return t}function vt(t){if(t===null)return null;if(typeof t=="string")return $(t);if(Array.isArray(t)&&t.length<=32)return t.map($);throw new Error("Bad key path")}function po(t){if(typeof t!="object"||t===null)throw new Error("Bad stores");let e=Object.keys(t);if(e.length>1e3)throw new Error("Too many stores");let o={};for(let r of e){let n=t[$(r)];if(typeof n!="object"||n===null||!Array.isArray(n.indexes)||n.indexes.length>1e3)throw new Error("Bad store");let a=Number(n.nextKey),i=n.indexes.map(s=>{let c=vt(s?.keyPath);if(c===null)throw new Error("Bad index");return{name:$(s.name),keyPath:c,unique:s.unique===!0,multiEntry:s.multiEntry===!0}});o[r]={keyPath:vt(n.keyPath),autoIncrement:n.autoIncrement===!0,nextKey:Number.isFinite(a)?a:1,indexes:i}}return o}function y(t){return new Promise((e,o)=>{t.onsuccess=()=>e(t.result),t.onerror=()=>o(t.error??new Error("Storage failed"))})}function Re(t){return new Promise((e,o)=>{t.oncomplete=()=>e(),t.onabort=t.onerror=()=>o(t.error??new Error("Storage failed"))})}var te=class{database=null;named=null;owner;factory;limit;constructor(e,o={}){this.owner=e,this.factory=o.factory??(()=>indexedDB),this.limit=o.limit??ct}open(){return this.database??=new Promise((e,o)=>{let r=this.factory().open(so,1);r.onupgradeneeded=()=>{r.result.createObjectStore("dbs",{keyPath:["owner","db"]}),r.result.createObjectStore("records")},r.onsuccess=()=>e(r.result),r.onerror=()=>o(r.error??new Error("No storage in this browser"))}),this.database}who(){return this.named??=this.owner().then(async({game:e,player:o})=>{let r=`${e}
2
- ${o??wt}`;return o!==null&&await this.adopt(`${e}
3
- ${wt}`,r),r}),this.named}async adopt(e,o){let n=(await this.open()).transaction(["dbs","records"],"readwrite"),a=Re(n),i=n.objectStore("dbs"),s=n.objectStore("records"),c=await y(i.getAll(this.ownerRange(e)));for(let d of c){let p=this.dbRange(e,d.db);if(!await y(i.getKey([o,d.db]))){let m=await y(s.getAllKeys(p));for(let u of m){let R=await y(s.get(u));s.put(R,[o,...u.slice(1)])}i.put({...d,owner:o})}s.delete(p),i.delete([e,d.db])}await a}ownerRange(e){return IDBKeyRange.bound([e],[`${e}\0`],!1,!0)}dbRange(e,o){return IDBKeyRange.bound([e,o],[e,`${o}\0`],!1,!0)}storeRange(e,o,r){return IDBKeyRange.bound([e,o,r],[e,o,`${r}\0`],!1,!0)}async read({db:e}){$(e);let o=await this.who(),n=(await this.open()).transaction(["dbs","records"]),a=await y(n.objectStore("dbs").get([o,e]));if(!a)return null;let i=n.objectStore("records"),s=this.dbRange(o,e),[c,d]=await Promise.all([y(i.getAllKeys(s)),y(i.getAll(s))]);return{version:a.version,stores:a.stores,records:c.map((p,m)=>({store:p[2],key:p[3],value:d[m].value}))}}async commit(e){let o=$(e?.db),r=Number(e.version);if(!Number.isFinite(r)||r<0)throw new Error("Bad version");let n=e.ops;if(!Array.isArray(n))throw new Error("Bad changes");let a=e.stores===void 0?void 0:po(e.stores),i=await this.who(),c=(await this.open()).transaction(["dbs","records"],"readwrite"),d=Re(c),p=c.objectStore("dbs"),m=c.objectStore("records");try{let u=await y(p.getAll(this.ownerRange(i))),R=u.find(l=>l.db===o)??{owner:i,db:o,version:r,stores:{},bytes:0},q=u.reduce((l,x)=>l+(x===R?0:x.bytes),0),b=R.bytes;for(let l of n){let x=$(l?.store);if("clear"in l){b-=await this.sum(m,this.storeRange(i,o,x)),m.delete(this.storeRange(i,o,x));continue}let fe=[i,o,x,"put"in l?l.put:l.remove],Ge=await y(m.get(fe));if(Ge&&(b-=Ge.bytes),"put"in l){let Ue=ze(l.value)+ze(l.put)+64;b+=Ue,m.put({value:l.value,bytes:Ue},fe)}else m.delete(fe)}if(a)for(let l of Object.keys(R.stores))Object.hasOwn(a,l)||(b-=await this.sum(m,this.storeRange(i,o,l)),m.delete(this.storeRange(i,o,l)));if(q+=ze(a??R.stores),q+b>this.limit)throw new Error("QuotaExceededError");p.put({owner:i,db:o,version:r,stores:a??R.stores,bytes:Math.max(0,b)})}catch(u){try{c.abort()}catch{}throw d.catch(()=>{}),u}return await d,null}async sum(e,o){return(await y(e.getAll(o))).reduce((n,a)=>n+a.bytes,0)}async remove({db:e}){$(e);let o=await this.who(),n=(await this.open()).transaction(["dbs","records"],"readwrite"),a=Re(n);return n.objectStore("records").delete(this.dbRange(o,e)),n.objectStore("dbs").delete([o,e]),await a,null}async list(){let e=await this.who(),o=await this.open();return(await y(o.transaction("dbs").objectStore("dbs").getAll(this.ownerRange(e)))).map(n=>({name:n.db,version:n.version}))}};var oe=class{pressed=new Set;press(e){this.pressed.add(e)}take(e){let o=new Set(e),r=!1;for(let n of this.pressed)o.has(n)||(o.add(n),r=!0);return this.pressed.clear(),{buttons:[...o],owed:r}}};var yt=["--ow-panel","--ow-face-edge","--ow-hover","--ow-on-accent","--text","--muted","--line","--line-strong","--fill","--fill-strong","--ok","--accent-fill","--accent-fill-hover","--accent-hover"],kt=["--ow-ctl-edge","--ow-ctl-fill","--ow-ctl-stick","--ow-ctl-knob","--ow-ctl-label","--ow-ctl-shadow","--ow-ctl-held","--ow-ctl-main","--ow-ctl-main-edge","--ow-ctl-main-label","--ow-ctl-main-shadow","--ow-ctl-main-held"];function w(t,e){return`rgba(${V(t).join(", ")}, ${e})`}function St(t){let e={};if(!t)return e;if(t.panel&&(e["--ow-panel"]=t.panel,e["--ow-face-edge"]=t.panel),t.ok&&(e["--ok"]=t.ok),t.panel||t.text){let o=t.text??C.text;t.text&&(e["--text"]=t.text),t.muted&&(e["--muted"]=t.muted),e["--line"]=w(o,.09),e["--line-strong"]=w(o,.16),e["--fill"]=w(o,.06),e["--fill-strong"]=w(o,.1),e["--ow-hover"]=w(o,.16)}return t.accent&&t.accentHover&&t.onAccent&&(e["--accent-fill"]=t.accent,e["--accent-fill-hover"]=t.accentHover,e["--accent-hover"]=t.accent,e["--ow-on-accent"]=t.onAccent),e}function zt(t){let e={};return t&&(t.text&&(e["--ow-ctl-edge"]=w(t.text,.4),e["--ow-ctl-knob"]=w(t.text,.9),e["--ow-ctl-label"]=t.text,e["--ow-ctl-held"]=w(t.text,.35)),t.panel&&(e["--ow-ctl-fill"]=w(t.panel,.4),e["--ow-ctl-stick"]=w(t.panel,.3),e["--ow-ctl-shadow"]=w(t.panel,.5)),t.accent&&t.accentHover&&t.onAccent&&(e["--ow-ctl-main"]=w(t.accent,.85),e["--ow-ctl-main-edge"]=t.accent,e["--ow-ctl-main-label"]=t.onAccent,e["--ow-ctl-main-shadow"]="none",e["--ow-ctl-main-held"]=t.accentHover)),e}function re(t,e,o){for(let r of e)Object.hasOwn(o,r)?t.style.setProperty(r,o[r]):t.style.removeProperty(r);t.toggleAttribute("data-look",e.some(r=>Object.hasOwn(o,r)))}var L=52,Ee=.12,Pe=96,co=400,go=2e4,ne=class{parent;root;zone;base;knob;buttonBox;send;onLayout;layout=null;touch;blocked=!1;keyboard=!1;stickPointer=null;center={x:0,y:0};x=0;y=0;buttons=new Map;held=new Map;fingers=new Map;report=new oe;queued=0;sent="";placed={stick:null,buttons:null};constructor(e,o,r=()=>{}){this.send=o,this.onLayout=r,this.parent=e,this.touch=matchMedia("(pointer: coarse)").matches,this.root=T("div","ow-controls"),this.root.hidden=!0,this.zone=T("div","ow-stick-zone"),this.base=T("div","ow-stick"),this.knob=T("div","ow-stick-knob"),this.base.append(this.knob),this.zone.append(this.base),this.buttonBox=T("div","ow-control-buttons"),this.root.append(this.zone,this.buttonBox),e.append(this.root),this.zone.addEventListener("pointerdown",n=>this.stickDown(n)),this.zone.addEventListener("pointermove",n=>this.stickMove(n));for(let n of["pointerup","pointercancel","lostpointercapture"])this.zone.addEventListener(n,a=>this.stickUp(a));this.root.addEventListener("contextmenu",n=>n.preventDefault())}setLayout(e){this.layout=Ze(e);let o=this.buttons;this.buttons=new Map,(this.layout?.buttons??[]).forEach((r,n)=>{let a=o.get(r.id)??this.button(r.id);o.delete(r.id),a.className=`ow-control-btn ow-control-btn-${n}`,a.classList.toggle("ow-held",[...this.held.values()].includes(r.id)),a.textContent=r.label,a.isConnected||this.buttonBox.append(a),this.buttons.set(r.id,a)});for(let[r,n]of o){for(let[a,i]of this.held)i===r&&this.letGo(a);n.remove()}this.zone.hidden=!this.layout?.stick,this.zone.classList.toggle("ow-stick-corner",this.layout?.zone==="corner"),this.zone.hidden&&this.stickPointer!==null&&this.letGo(this.stickPointer),this.render(),this.onLayout()}setLook(e){re(this.root,kt,e)}reservedBoxes(){if(this.root.hidden)return this.blocked&&this.layout&&this.touch&&!this.keyboard?this.placed:{stick:null,buttons:null};let e=this.zone.hidden?null:Et(this.base),o=null;for(let r of this.buttons.values()){let n=Et(r);!n.width||!n.height||(o=o?uo(o,n):n)}return this.placed={stick:e,buttons:o},this.placed}setTouch(e){this.touch=e,this.render()}setKeyboard(e){this.keyboard=e,this.render()}setBlocked(e){this.blocked=e,this.render()}render(){let e=!!this.layout&&this.touch&&!this.blocked&&!this.keyboard;this.root.hidden!==!e&&(e||this.reset(),this.root.hidden=!e,this.onLayout())}reset(){for(let e of[...this.held.keys()])this.letGo(e);this.stickPointer!==null&&this.letGo(this.stickPointer),this.endStick()}letGo(e){e===this.stickPointer&&this.endStick();let o=this.held.get(e);this.held.delete(e),o!==void 0&&![...this.held.values()].includes(o)&&this.buttons.get(o)?.classList.remove("ow-held");let r=this.fingers.get(e);this.fingers.delete(e),r&&ho(this.parent,e,r),this.changed()}button(e){let o=T("button","ow-control-btn");o.type="button",o.tabIndex=-1,o.addEventListener("pointerdown",n=>{n.preventDefault(),Rt(o,n.pointerId),o.classList.add("ow-held"),this.held.set(n.pointerId,e),this.report.press(e),this.fingers.set(n.pointerId,{x:n.clientX,y:n.clientY}),this.changed()}),o.addEventListener("pointermove",n=>{this.held.get(n.pointerId)===e&&this.fingers.set(n.pointerId,{x:n.clientX,y:n.clientY})});let r=n=>{this.held.get(n.pointerId)===e&&(this.held.delete(n.pointerId),this.fingers.delete(n.pointerId),[...this.held.values()].includes(e)||o.classList.remove("ow-held"),this.changed())};for(let n of["pointerup","pointercancel","lostpointercapture"])o.addEventListener(n,a=>r(a));return o}stickDown(e){if(this.stickPointer!==null)return;e.preventDefault(),Rt(this.zone,e.pointerId),this.stickPointer=e.pointerId,this.fingers.set(e.pointerId,{x:e.clientX,y:e.clientY});let o=this.zone.getBoundingClientRect(),r=this.base.getBoundingClientRect(),n=L+8;this.center={x:Math.min(Math.max(e.clientX,o.left+n),o.right-n),y:Math.min(Math.max(e.clientY,o.top+n),o.bottom-n)},this.base.style.transform=`translate(${this.center.x-(r.left+r.width/2)}px, ${this.center.y-(r.top+r.height/2)}px)`,this.base.classList.add("ow-active"),this.stickMove(e)}stickMove(e){if(e.pointerId!==this.stickPointer)return;this.fingers.set(e.pointerId,{x:e.clientX,y:e.clientY});let o=e.clientX-this.center.x,r=e.clientY-this.center.y,n=Math.hypot(o,r);n>L&&(o=o/n*L,r=r/n*L),this.knob.style.transform=`translate(${o}px, ${r}px)`;let a=Math.min(n,L)/L,i=a<Ee?0:(a-Ee)/(1-Ee)/(a||1);this.x=Pt(o/L*i),this.y=Pt(r/L*i),this.changed()}stickUp(e){e.pointerId===this.stickPointer&&(this.fingers.delete(e.pointerId),this.endStick())}endStick(){this.stickPointer=null,this.x=0,this.y=0,this.base.classList.remove("ow-active"),this.base.style.transform="",this.knob.style.transform="",this.changed()}changed(){this.queued||(this.queued=requestAnimationFrame(()=>{this.queued=0;let{buttons:e,owed:o}=this.report.take(this.held.values()),r={x:this.x,y:this.y,buttons:e};o&&this.changed();let n=JSON.stringify(r);n!==this.sent&&(this.sent=n,this.send(r))}))}};function ho(t,e,o){let r=T("div","ow-tap-shield");r.style.left=`${o.x-Pe/2}px`,r.style.top=`${o.y-Pe/2}px`,r.style.width=r.style.height=`${Pe}px`,t.append(r);let n=setTimeout(()=>r.remove(),go),a=()=>{clearTimeout(n),n=setTimeout(()=>r.remove(),co)};for(let i of["pointerup","pointercancel","lostpointercapture"])r.addEventListener(i,s=>s.pointerId===e&&a());r.addEventListener("contextmenu",i=>i.preventDefault());try{r.setPointerCapture(e)}catch{a()}}function Rt(t,e){try{t.setPointerCapture(e)}catch{}}function Et(t){let e=t.getBoundingClientRect(),o=getComputedStyle(t).transform;if(!o||o==="none")return e;let r=new DOMMatrixReadOnly(o);if(r.b||r.c||!r.a||!r.d)return e;let n=e.width/Math.abs(r.a),a=e.height/Math.abs(r.d),i=e.left+e.width/2-r.e,s=e.top+e.height/2-r.f;return{left:i-n/2,top:s-a/2,width:n,height:a}}function uo(t,e){let o=Math.min(t.left,e.left),r=Math.min(t.top,e.top);return{left:o,top:r,width:Math.max(t.left+t.width,e.left+e.width)-o,height:Math.max(t.top+t.height,e.top+e.height)-r}}function Pt(t){return Math.round(t*1e3)/1e3}function T(t,e,o){let r=document.createElement(t);return r.className=e,o!==void 0&&(r.textContent=o),r}function mo(t,e){return t!=="lobby"?{surface:t,hidden:!1}:{surface:e?"none":"lobby",hidden:e}}var Me=t=>Math.max(0,Math.ceil(t/1e3)),Ae=t=>{let e=t.slice(0,2).map(o=>o.name);return t.length>2?`${e.join(", ")} +${t.length-2}`:e.join(", ")};function xo(t){let{you:e,host:o,kind:r,match:n,now:a}=t,i=[...t.players].sort((l,x)=>l.id===e?-1:x.id===e?1:l.id===o?-1:x.id===o?1:l.name.localeCompare(x.name)).map(l=>({id:l.id,name:l.name,ready:!!l.ready,host:l.id===o,you:l.id===e,away:l.connected===!1,idle:l.connected!==!1&&!!l.idle})),s=t.players.filter(l=>l.connected!==!1),c=t.players.find(l=>l.id===e),d=o===e&&n.phase==="lobby",p=t.schema.map(l=>({id:l.id,label:l.label,value:et(l,t.written),options:l.options,editable:d})),m={n:s.filter(l=>l.ready||r!=="public"&&l.id===o).length,of:s.length},u={surface:"none",rows:i,count:`${t.players.length} / ${t.max}`,ready:m,status:"",action:{kind:"none",label:"",disabled:!0},settings:p,seconds:null},R=n.participants?.includes(e)??!1,q=o===e&&r!=="public";if(n.phase==="starting"){if(!R)return{...u,surface:"watching",status:"Watching"};let l=q?{kind:"stop",label:"Stop",disabled:!1}:{kind:"unready",label:"Not ready",disabled:!1};return t.ownCountdown?{...u,surface:"countdown",action:l,seconds:null,status:"Get ready"}:{...u,surface:"countdown",action:l,seconds:Math.max(1,Me((n.startsAt??a)-a)),status:"Get ready"}}if(n.phase==="playing"){if(n.paused){let l=s.filter(x=>n.participants?.includes(x.id)).length;return n.paused.reason==="host"?{...u,surface:"paused",status:"Paused"}:{...u,surface:"paused",seconds:Me(n.paused.since+E.matchAbandonMs-a),status:`Waiting for players \xB7 ${l} / ${n.min}`}}return R?u:{...u,surface:"watching",status:"Watching"}}let b=s.filter(l=>!l.ready&&(r==="public"||l.id!==o));if(u.surface="lobby",q){let l=s.length<n.min;return u.action={kind:"start",label:"Start",disabled:l||b.length>0},u.status=l?`Need ${n.min} players`:b.length?`Waiting for ${Ae(b)}`:"",u}if(u.action={kind:c?.ready?"unready":"ready",label:c?.ready?"Not ready":"Ready",disabled:!1},r==="public")s.length<n.min?u.status=`Need ${n.min} players`:n.waitUntil!==void 0&&b.length?(u.seconds=Me(n.waitUntil-a),u.status=`${Ae(b)} \xB7 ${u.seconds}s`):u.status=`${s.length-b.length} / ${s.length} ready`;else{let l=b.filter(x=>x.id!==e);u.status=c?.ready?l.length?`Waiting for ${Ae(l)}`:`Waiting for ${t.players.find(x=>x.id===o)?.name??"the host"}`:""}return u}function h(t,e="",o){let r=document.createElement(t);return e&&(r.className=e),o!==void 0&&(r.textContent=o),r}var ae=class{mode=null;ownCountdown=!1;hiddenByGame=!1;state=null;schema=[];timer=0;opts;root;card;title;count;list;settingsBox;status;action;invite;bar;barFaces;barStatus;barAction;countdown;numeral;cancel;pause;pauseStatus;pauseTimer;chip;last=null;lastNumeral=-1;listSignature="";settingsSignature="";faceSignature="";constructor(e,o){this.opts=o,this.root=h("div","ow-lobby"),this.root.hidden=!0,this.card=h("section","ow-lobby-card"),this.card.setAttribute("aria-label","Lobby");let r=h("header","ow-lobby-head");this.title=h("h2","","Lobby"),this.count=h("span","ow-lobby-count"),r.append(this.title,this.count),this.list=h("ul","ow-lobby-players"),this.settingsBox=h("div","ow-lobby-settings"),this.status=h("p","ow-lobby-status"),this.status.setAttribute("role","status"),this.action=this.button("ow-btn ow-btn-primary ow-lobby-action",()=>this.press()),this.invite=this.button("ow-btn ow-lobby-invite",()=>this.opts.onInvite()),this.invite.textContent="Invite";let n=h("div","ow-lobby-actions");n.append(this.action,this.invite),this.card.append(r,this.list,this.settingsBox,this.status,n),this.bar=h("div","ow-lobby-bar"),this.barFaces=h("span","ow-lobby-faces"),this.barStatus=h("span","ow-lobby-bar-status"),this.barStatus.setAttribute("role","status"),this.barAction=this.button("ow-btn ow-btn-primary ow-lobby-action",()=>this.press()),this.bar.append(this.barFaces,this.barStatus,this.barAction),this.countdown=h("div","ow-lobby-countdown"),this.numeral=h("b"),this.numeral.setAttribute("aria-hidden","true");let a=h("span","","Get ready");a.setAttribute("aria-hidden","true"),this.cancel=this.button("ow-btn ow-lobby-cancel",()=>this.press()),this.countdown.append(this.numeral,a,this.cancel),this.pause=h("div","ow-lobby-pause");let i=h("div","ow-lobby-pause-box");this.pauseStatus=h("p","ow-lobby-pause-status"),this.pauseStatus.setAttribute("role","status"),this.pauseTimer=h("p","ow-lobby-pause-timer"),i.append(this.pauseStatus,this.pauseTimer),this.pause.append(i),this.chip=h("div","ow-lobby-chip"),this.chip.setAttribute("role","status"),this.root.append(this.card,this.bar,this.countdown,this.pause,this.chip),e.append(this.root)}setSchema(e){this.schema=e,this.render()}enable(e,o={}){this.mode=e,this.ownCountdown=o.ownCountdown===!0,this.root.dataset.mode=e??"",e||this.reset(),this.render()}setLook(e){re(this.root,yt,e)}reservedBox(){if(this.root.hidden)return null;let e=!this.countdown.hidden&&this.countdown.classList.contains("ow-lobby-countdown-bare"),o=this.bar.hidden?e&&!this.cancel.hidden?this.cancel:null:this.bar;return o?o.getBoundingClientRect():null}setHidden(e){this.hiddenByGame=e,this.render()}setRoom(e,o=0){if(!e)return this.reset();this.state={you:e.you,host:e.host,kind:e.room.startsWith("private:")?"private":e.room.startsWith("room:")?"named":"public",max:Math.max(o,e.players.length),players:e.players.map(r=>({...r})),match:e.match??{phase:"lobby",n:0,min:1},written:e.state?.[K]},this.render()}handle(e){let o=this.state;if(o){switch(e.t){case"join":o.players=[...o.players.filter(r=>r.id!==e.player.id),{...e.player}];break;case"leave":o.players=o.players.filter(r=>r.id!==e.id);break;case"away":o.players=o.players.map(r=>r.id===e.id?{...r,connected:!1}:r);break;case"back":o.players=o.players.map(r=>{if(r.id!==e.id)return r;let{connected:n,...a}=r;return a});break;case"ready":o.players=o.players.map(r=>{if(r.id!==e.id)return r;let{ready:n,...a}=r;return e.ready?{...a,ready:!0}:a});break;case"unready":o.players=o.players.map(({ready:r,...n})=>n);break;case"host":o.host=e.id;break;case"idle":o.players=o.players.map(r=>{if(r.id!==e.id)return r;let{idle:n,...a}=r;return e.idle?{...a,idle:!0}:a});break;case"name":o.players=o.players.map(r=>r.id===e.id?{...r,name:e.name}:r);break;case"match":o.match=e.match;break;case"state":if(e.k!==K)return;o.written=e.v;break;default:return}this.render()}}chooseSetting(e,o){let r=this.state,n=this.schema.find(i=>i.id===e);if(!r||!n||!n.options.some(i=>i.value===o))return null;let a={...r.written!==null&&typeof r.written=="object"?r.written:{},[e]:o};return r.written=a,this.render(),a}setReadyLocal(e){let o=this.state?.players.find(r=>r.id===this.state?.you);o&&(e?o.ready=!0:delete o.ready,this.render())}button(e,o){let r=h("button",e);return r.type="button",r.addEventListener("pointerdown",n=>n.preventDefault()),r.addEventListener("click",o),r}press(){let e=this.last?.action.kind;e==="start"?this.opts.onStart():e==="stop"?this.opts.onStop():e==="ready"?(this.setReadyLocal(!0),this.opts.onReady(!0)):e==="unready"&&(this.setReadyLocal(!1),this.opts.onReady(!1))}reset(){this.state=null,this.last=null,this.hiddenByGame=!1,window.clearInterval(this.timer),this.timer=0,this.root.hidden=!0,this.opts.onLayout?.()}render(){this.draw(),this.opts.onLayout?.()}draw(){if(!this.mode||!this.state){this.root.hidden=!0;return}let e=xo({...this.state,schema:this.schema,now:this.opts.now(),ownCountdown:this.ownCountdown});this.last=e;let o=mo(e.surface,this.hiddenByGame);this.hiddenByGame=o.hidden;let r=o.surface==="lobby";if(this.root.hidden=o.surface==="none",this.card.hidden=!(r&&this.mode==="card"),this.bar.hidden=!(r&&this.mode==="bar"),this.countdown.hidden=e.surface!=="countdown",this.countdown.classList.toggle("ow-lobby-countdown-bare",e.surface==="countdown"&&e.seconds===null),this.pause.hidden=e.surface!=="paused",this.chip.hidden=e.surface!=="watching",this.root.classList.toggle("ow-lobby-dim",r&&this.mode==="card"),r&&this.mode==="card"&&this.renderCard(e),r&&this.mode==="bar"&&this.renderBar(e),e.surface==="countdown"&&e.seconds!==null&&e.seconds!==this.lastNumeral&&(this.lastNumeral=e.seconds,this.numeral.textContent=String(e.seconds),this.numeral.style.animation="none",this.numeral.offsetWidth,this.numeral.style.animation=""),e.surface==="countdown"&&(this.cancel.textContent=e.action.label),e.surface!=="countdown"&&(this.lastNumeral=-1),e.surface==="paused"&&(this.pauseStatus.textContent=e.status,this.pauseTimer.textContent=e.seconds!==null?`Ends in ${fo(e.seconds)}`:""),e.surface==="watching"){let[a,...i]=e.status.split(" \xB7 "),s=h("span","ow-lobby-chip-more",i.length?` \xB7 ${i.join(" \xB7 ")}`:"");this.chip.replaceChildren(a,s)}let n=e.seconds!==null;n&&!this.timer?this.timer=window.setInterval(()=>this.render(),250):!n&&this.timer&&(window.clearInterval(this.timer),this.timer=0)}renderCard(e){this.count.textContent=e.count,this.settingsBox.hidden=e.settings.length===0,this.status.textContent=e.status,this.status.hidden=e.status==="",this.setAction(this.action,e);let o=JSON.stringify(e.settings);o!==this.settingsSignature&&(this.settingsSignature=o,this.settingsBox.replaceChildren(...e.settings.map(n=>this.settingRow(n))));let r=JSON.stringify(e.rows);r!==this.listSignature&&(this.listSignature=r,this.list.replaceChildren(...e.rows.map(n=>{let a=h("li",n.away||n.idle?"ow-lobby-away":n.ready?"ow-lobby-ready":""),i=h("img","ow-headshot");i.src=`/avatar/${encodeURIComponent(n.id)}/head.svg`,i.alt="";let s=h("span","ow-lobby-name",n.name),c=[n.away?"Reconnecting":n.idle?"Idle":"",n.you?"You":"",n.host?"Host":""].filter(Boolean).join(" \xB7 ");return a.append(i,s),c&&a.append(h("span","ow-player-notes",c)),a.append(h("span",`ow-lobby-check${n.ready?" ow-lobby-check-on":""}`,n.ready?"Ready":"")),a})))}settingRow(e){let o=h("div","ow-lobby-setting");if(o.append(h("span","ow-lobby-setting-label",e.label)),!e.editable)return o.append(h("span","ow-lobby-setting-value",e.options.find(n=>n.value===e.value)?.label??String(e.value))),o;let r=h("div","ow-segments");r.setAttribute("role","radiogroup"),r.setAttribute("aria-label",e.label);for(let n of e.options){let a=this.button(`ow-segment${n.value===e.value?" ow-segment-on":""}`,()=>this.opts.onSetting(e.id,n.value));a.textContent=n.label,a.setAttribute("role","radio"),a.setAttribute("aria-checked",String(n.value===e.value)),r.append(a)}return o.append(r),o}renderBar(e){this.barStatus.textContent=e.status||`${e.ready.n} / ${e.ready.of} ready`,this.setAction(this.barAction,e);let o=JSON.stringify(e.rows.slice(0,5).map(r=>[r.id,r.ready]));o!==this.faceSignature&&(this.faceSignature=o,this.barFaces.replaceChildren(...e.rows.slice(0,5).map(r=>{let n=h("img",`ow-headshot${r.ready?" ow-lobby-face-ready":""}`);return n.src=`/avatar/${encodeURIComponent(r.id)}/head.svg`,n.alt="",n})))}setAction(e,o){e.hidden=o.action.kind==="none",e.textContent=o.action.label,e.disabled=o.action.disabled,e.classList.toggle("ow-btn-primary",o.action.kind!=="unready")}};function fo(t){return`${Math.floor(t/60)}:${String(t%60).padStart(2,"0")}`}function Mt(t,e){let o=[];for(let{kind:r,box:n}of e){if(!n||!(n.width>0)||!(n.height>0))continue;let a=Math.max(0,n.left-t.left),i=Math.max(0,n.top-t.top),s=Math.min(t.width,n.left+n.width-t.left),c=Math.min(t.height,n.top+n.height-t.top);if(!(s-a>.01&&c-i>.01))continue;let d=Math.floor(a+.01),p=Math.floor(i+.01);o.push({kind:r,x:d,y:p,w:Math.ceil(s-.01)-d,h:Math.ceil(c-.01)-p})}return o}var ie=class{measure;send;nextFrame;queued=!1;sent=null;constructor(e,o,r=n=>requestAnimationFrame(n)){this.measure=e,this.send=o,this.nextFrame=r}changed(){this.queued||(this.queued=!0,this.nextFrame(()=>{this.queued=!1;let e=this.measure();if(!e)return;let o=JSON.stringify(e);o!==this.sent&&(this.sent=o,this.send(e))}))}resend(){this.sent=null,this.changed()}};var $e=t=>`ow:resume:${t}`;function _(t,e){try{e?sessionStorage.setItem($e(t),JSON.stringify({...e,at:Date.now()})):sessionStorage.removeItem($e(t))}catch{}}function At(t){try{let e=sessionStorage.getItem($e(t));if(!e)return null;let o=performance.getEntriesByType("navigation")[0];if(o&&o.type!=="reload"&&o.type!=="back_forward")return se(t);let r=JSON.parse(e);return typeof r.key!="string"||typeof r.rejoin!="string"||typeof r.at!="number"||Date.now()-r.at>18e4||Number(r.rejoin.split(".")[0])*1e3<Date.now()?se(t):{key:r.key,rejoin:r.rejoin}}catch{return null}}var bo=t=>`ow:own-server:${t}`;function $t(t,e){try{sessionStorage.setItem(bo(t),e)}catch{}}function se(t){return _(t,null),null}var le=class extends Error{constructor(o,r=0,n=0,a=""){super(o);this.status=r;this.retryAfterMs=n;this.code=a;this.name="RequestError"}status;retryAfterMs;code;get retryable(){return this.status===0||this.status===408||this.status===429||this.status>=500}};var pe=class{constructor(e=48*1024){this.max=e}max;used=0;async run(e,o){let r=new TextEncoder().encode(e).byteLength;if(this.used+r>this.max)throw new le("Progress is waiting to sync when you return to the game.");this.used+=r;try{return await o()}finally{this.used-=r}}};var Le=/^[2-9]$/.test(new URLSearchParams(location.search).get("player")??"")?new URLSearchParams(location.search).get("player"):null;var wo=null;function Lt(){return wo?.n??null}var yr=new pe;var Ct=[300,700,1500,3e3,5e3,8e3],vo=9e4,yo=1e4,Dt=()=>Array.from(crypto.getRandomValues(new Uint8Array(10)),t=>"abcdefghijklmnopqrstuvwxyz0123456789"[t%36]).join(""),Ot=Dt();function _t(){Ot=Dt()}var Tt=null;var It=!1;function jt(t){It=t}var ko=5e4,So=4e3,de=class{ws=null;key=null;rejoin=null;you="";closed=!1;attempt=0;droppedAt=0;sleeping=!1;lastHeard=0;heardCount=0;pingAt=0;hintedHidden=!1;pingTimer=0;watchTimer=0;retryTimer=0;page=Ot;slug;options;callbacks;onVisible=()=>{this.hint(),document.visibilityState==="visible"&&this.wake()};onOnline=()=>this.wake();onShow=e=>e.persisted&&this.wake();onHide=()=>this.hint();constructor(e,o,r){this.slug=e,this.options=o,this.callbacks=r,this.key=o.key??null,this.rejoin=o.rejoin??null,document.addEventListener("visibilitychange",this.onVisible),addEventListener("online",this.onOnline),addEventListener("pageshow",this.onShow),addEventListener("pagehide",this.onHide)}join(){return new Promise((e,o)=>this.open(e,o))}send(e){this.ws?.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(e))}hint(){let e=document.visibilityState==="hidden";e===this.hintedHidden||this.ws?.readyState!==WebSocket.OPEN||(this.hintedHidden=e,this.send({t:"vis",hidden:e}))}ping(){let e=this.ws;e?.readyState===WebSocket.OPEN&&(this.pingAt&&Date.now()-this.pingAt<5e3||(this.pingAt=Date.now(),e.send(ve)))}close(){this.closed=!0,_(this.slug,null),this.dispose(),this.ws?.close(1e3)}reload(){return this.closed=!0,this.dispose(),this.ws?.close(nt,"Updating"),this.key?.startsWith("pub:")&&this.rejoin?{key:this.key,rejoin:this.rejoin}:null}open(e,o){this.retryTimer=0;let r=new URL(`/api/games/${this.slug}/rooms/connect`,location.href);r.protocol=location.protocol==="https:"?"wss:":"ws:",this.key?(r.searchParams.set("key",this.key),this.rejoin&&r.searchParams.set("rejoin",this.rejoin)):this.options.server?(r.searchParams.set("server",this.options.server),this.options.private&&r.searchParams.set("own","1")):this.options.name?r.searchParams.set("name",this.options.name):(this.options.mode&&r.searchParams.set("mode",this.options.mode),this.options.follow&&r.searchParams.set("follow",this.options.follow)),this.options.maxPlayers&&r.searchParams.set("max",String(this.options.maxPlayers)),this.options.minPlayers&&r.searchParams.set("min",String(this.options.minPlayers)),this.options.countdown!==void 0&&r.searchParams.set("countdown",String(this.options.countdown)),this.options.teams&&r.searchParams.set("teams",String(this.options.teams)),this.options.ranked&&r.searchParams.set("ranked","1"),Le&&r.searchParams.set("devplayer",Le);let n=Lt();n&&r.searchParams.set("tester",n),r.searchParams.set("page",this.page),this.options.build&&r.searchParams.set("build",this.options.build),Tt&&r.searchParams.set("pass",Tt),It&&r.searchParams.set("demo","1");let a=new WebSocket(r);this.ws=a,this.lastHeard=Date.now();let i=!1;a.onopen=()=>{this.lastHeard=Date.now(),window.clearInterval(this.pingTimer),window.clearInterval(this.watchTimer),this.pingTimer=window.setInterval(()=>a.readyState===WebSocket.OPEN&&this.ping(),yo),this.watchTimer=window.setInterval(()=>{a===this.ws&&a.readyState===WebSocket.OPEN&&Date.now()-this.lastHeard>ko&&this.lost(a)},5e3)},a.onmessage=d=>{this.lastHeard=Date.now(),this.heardCount++;let p;try{p=JSON.parse(d.data)}catch{return}if(p.t==="pong"){this.pingAt&&this.callbacks.onRtt?.(Date.now()-this.pingAt),this.pingAt=0;return}if(p.t==="welcome"&&(p.rejoin&&(this.rejoin=p.rejoin),delete p.rejoin,p.room.startsWith("pub:")&&this.rejoin&&_(this.slug,{key:p.room,rejoin:this.rejoin})),p.t==="leave"&&p.kicked&&p.id===this.you){this.close(),this.finish("kicked");return}if(p.t==="welcome"&&(this.hintedHidden=!1,this.hint()),p.t==="welcome"&&!i&&(i=!0,this.key=p.room,this.you=p.you,this.attempt=0,this.droppedAt&&(this.droppedAt=0,this.callbacks.onConnection?.(!0)),e)){let{t:m,...u}=p;e(u),e=void 0;return}this.callbacks.onMessage(p)};let s=!1,c=d=>{if(!s&&(s=!0,window.clearInterval(this.pingTimer),window.clearInterval(this.watchTimer),!(this.closed||a!==this.ws))){if(d.code===tt)return this.finish("replaced");if(d.code===rt&&i&&document.visibilityState==="hidden"){this.sleeping=!0,this.droppedAt||(this.droppedAt=Date.now(),this.callbacks.onConnection?.(!1));return}if(d.code===ot){if(i||!o)return this.finish("kicked");this.closed=!0,this.dispose(),_(this.slug,null),o(Object.assign(new Error("You were removed from this server."),{kicked:!0}));return}if(!i&&o){this.closed=!0,this.dispose(),o(new Error(this.options.name||this.options.server?"Could not join that server. It may be full.":"Could not join a server. Try again soon."));return}this.retry()}};a.onclose=c,a.onerror=()=>{window.setTimeout(()=>{a.readyState===WebSocket.CLOSED&&c({code:1006})},1e3)}}retry(){if(this.closed)return;let e=Date.now();if(this.droppedAt||(this.droppedAt=e,this.callbacks.onConnection?.(!1)),e-this.droppedAt>vo)return this.finish("disconnected");if(this.retryTimer)return;let o=Ct[Math.min(this.attempt++,Ct.length-1)]*(.5+Math.random());this.retryTimer=window.setTimeout(()=>{this.retryTimer=0,this.closed||this.open()},o)}lost(e){if(!(e!==this.ws||this.closed)){e.onopen=e.onmessage=e.onclose=e.onerror=null,window.clearInterval(this.pingTimer),window.clearInterval(this.watchTimer);try{e.close()}catch{}this.retry()}}wake(){if(this.closed)return;if(this.sleeping){this.sleeping=!1,this.droppedAt=Date.now(),this.attempt=0,this.open();return}let e=this.ws;if(!e||e.readyState===WebSocket.CLOSING||e.readyState===WebSocket.CLOSED){this.retryTimer&&(window.clearTimeout(this.retryTimer),this.retryTimer=0,this.open());return}if(e.readyState!==WebSocket.OPEN)return;let o=this.heardCount;try{e.send(ve)}catch{return this.lost(e)}window.setTimeout(()=>e===this.ws&&!this.closed&&this.heardCount===o&&this.lost(e),So)}dispose(){window.clearInterval(this.pingTimer),window.clearInterval(this.watchTimer),window.clearTimeout(this.retryTimer),this.retryTimer=0,document.removeEventListener("visibilitychange",this.onVisible),removeEventListener("online",this.onOnline),removeEventListener("pageshow",this.onShow),removeEventListener("pagehide",this.onHide)}finish(e){this.closed=!0,this.dispose(),_(this.slug,null),this.callbacks.onClose(e)}};var Bt=`:root {
1
+ "use strict";(()=>{var He=["#f6dcc5","#ebc29f","#d4a07a","#b07a55","#875839","#5e3d29","#93c9bb","#a8c595","#94bcd3","#ab9bd9","#dca3ad","#b3bccb"],ye=["#c95f56","#dc8a4f","#e2bd52","#9cc27a","#5a8a5e","#4f9a8e","#6fb7c8","#5b87c5","#465089","#8b68a8","#dc8fae","#f2eee6","#a9a8ad","#5d5c63","#2b2a30","#80604d"],Fe=["Happy","Grin","Shades","Wink","Surprised","Determined","Joyful","Shy"],qe=["None","Sprout","Cap","Beanie","Crown","Headphones","Horns","Antenna","Bow","Halo","Party hat","Cat ears","Short hair","Tuft","Bob","Spikes","Bun","Curls","Long","Mop"];var Ke=["Plain","Band","Spots","Stripes","Star"];var Ho={skin:He.length,shirt:ye.length,pants:ye.length,face:Fe.length,hat:qe.length,top:Ke.length},ro=[[5,6],[3,4],[6,7],[9,8],[10,0],[12,13]];function Ve(t){let e=2166136261;for(let d of t)e=Math.imul(e^d.charCodeAt(0),16777619)>>>0;let o=d=>(e=Math.imul(e^e>>>13,2654435761)>>>0,e%d),r=o(He.length),a=r>=6?ro[r-6][o(2)]:o(ye.length),n=o(2)===0?a:[8,13,14,15][o(4)],i=o(5)===0?1:12+o(qe.length-12),s=o(2)===0?0:o(Ke.length);return{skin:r,shirt:a,pants:n,face:o(Fe.length),hat:i,top:s}}var P={roomMaxPlayersDefault:16,roomMaxPlayersCap:30,teamsMax:8,messageBytes:16*1024,presenceBytes:1024,stateKeyLength:64,stateValueBytes:16*1024,stateKeys:256,privateKeys:16,privateValueBytes:4*1024,privateBytesPerPlayer:8*1024,messagesPerSecond:60,messagesPerSecondMin:10,roomBytesPerSecond:128*1024,roomBytesPerSecondMin:16*1024,presenceHz:20,presenceHzMin:5,roomMessagesPerSecond:900,roomBytesPerSecondTotal:2*1024*1024,matchCountdownMs:3e3,matchCountdownMaxMs:1e4,matchIdleMs:45e3,matchAbandonMs:12e4,idleAfterMs:3e4,idleAbandonMs:5*6e4,rankedPairMatchesPerDay:5,chatLength:200,chatIntervalMs:750,chatHistory:50,chatChannelsPerPlayer:10,saveValueBytes:64*1024,saveKeysPerGame:100,saveBytesPerGame:1024*1024,guestSaveBytesPerGame:128*1024,serverDataKeys:128,serverDataBytes:512*1024,serverDataServersPerGame:5e3,serverDataBytesPerGame:64*1024*1024,serverDataIdleDays:120,gameDataKeys:32,gameDataListMax:100,gameDataItemBytes:1024,gameDataValueBytes:8*1024,gameDataMapFields:1e3,gameDataMapPerPlayer:100,gameDataNumberMax:1e15,gameDataWritesPerMinute:30,deployFiles:2e3,deployFileBytes:25*1024*1024,deployHtmlBytes:10*1024*1024,deployPathLength:200,deployTotalBytes:100*1024*1024,envVars:50,envValueBytes:4*1024,envTotalBytes:16*1024,proxyRequestBytes:1024*1024,proxyTextRequestBytes:256*1024,proxySpeechRequestBytes:16*1024,proxyResponseBytes:10*1024*1024,proxyMaxOutputTokens:4096,proxyCallsPerDay:2e3,proxyCallsPerDayMax:1e5,proxyCallsPerPlayerPerDay:100,proxyCallsPerNetworkPerDay:1e3,proxyMediaCallsPerDay:100,proxyMediaCallsPerDayMax:1e4,proxyMediaCallsPerPlayerPerDay:20,proxyMediaCallsPerNetworkPerDay:25,proxyUnitsPerDay:2e6,proxyUnitsPerDayMax:1e8,proxyUnitsPerPlayerPerDay:25e4,proxyUnitsPerGuestPerDay:1e5,proxyUnitsPerNetworkPerDay:5e5,errorReportsPerPage:10,consoleLineLength:2e3,consoleLinesPerSecond:50,gameNameLength:60,gameDescriptionLength:1e3,galleryItems:5,mediaImageBytes:5*1024*1024,mediaVideoBytes:30*1024*1024,mediaGifBytes:1024*1024,mediaGifSide:512,mediaUploadsPerDay:200,mediaBytesPerDay:500*1024*1024,badgesPerGame:50,controlHints:12,controlHintKeys:4,controlKeyLength:12,controlActionLength:40,helpLength:400,badgeNameLength:50,badgeDescriptionLength:200,leaderboardsPerGame:25,leaderboardTop:100,gamesPerOwner:50,gamesCreatedPerCreatorPerDay:3,uploadAccountAgeMs:10080*6e4,deploysPerOwnerPerDay:100,draftsPerOwnerPerDay:1e3,draftsKeptPerGame:3,cliTokensPerAccount:10,cliDeviceCodeSeconds:600,deploymentsKeptPerGame:10,deployBytesPerOwnerPerDay:2*1024*1024*1024,deployEntriesPerOwnerPerDay:1e5,bioLength:500,exportRows:2e3,exportSaveChars:4*1024*1024,exportEveryMs:60*6e4,friendsMax:200,friendRequestsMax:100,friendRequestsIncomingMax:50,friendRequestsPerDay:20,friendRequestsPerNetworkPerDay:120,friendRequestPushesPerDay:5,passwordMinLength:8,passwordMaxLength:72,signupsPerNetworkPerDay:100,signupsPerWidePerDay:300,guestsPerNetworkPerDay:300,guestsPerWidePerDay:1500,visitsPerNetworkPerDay:10,visitSeconds:10,errorsPerDeployment:100,controlButtons:4,controlLabelLength:12,partySize:6,partyChatHistory:50,messageLength:500,messagesPerPage:50,groupChatMembers:20,groupChatNameLength:40,conversationsListed:50,notificationsListed:50,invitesPerRequest:10,inviteLiveMs:30*6e4,partyInviteMs:60*6e4};var Ye=/^[a-z0-9]{12}$/,ee=2;function ke(t){return`${t.mode}.${t.max}.${t.teams}.${t.ranked?1:0}${t.min!==ee?`.${t.min}`:""}`}var ao=/^pub:([a-z0-9-]+)\.(\d+)\.(\d)\.([01])(?:\.(\d+))?:(\d+)$/;function Je(t){let e=ao.exec(t);if(!e)return null;let o={mode:e[1],max:Number(e[2]),teams:Number(e[3]),ranked:e[4]==="1",min:e[5]===void 0?ee:Number(e[5])};return{...o,name:ke(o),n:Number(e[6])}}function Xe(t,e,o){let r=typeof t=="string"?Number(t):t;return typeof r!="number"||!Number.isInteger(r)?Math.min(o,e):Math.min(e,Math.max(1,r))}var We=t=>t.replace(/[\u0000-\u001f\u007f]/g," ").replace(/\s+/g," ").trim();var no=/^[a-z0-9][a-z0-9-]{0,19}$/;function Ze(t){if(!t||typeof t!="object")return null;let e=t,o=[];for(let a of Array.isArray(e.buttons)?e.buttons:[]){if(o.length>=P.controlButtons)break;let{id:n,label:i}=a??{};if(typeof n!="string"||!no.test(n)||o.some(d=>d.id===n))continue;let s=typeof i=="string"?i.replace(/[\u0000-\u001f\u007f]/g,"").replace(/\s+/g," ").trim().slice(0,P.controlLabelLength).trim():"";o.push({id:n,label:s||n})}let r=e.stick===!0;return r||o.length>0?{stick:r,zone:e.zone==="corner"?"corner":"wide",buttons:o}:null}var V="host:ow.settings",io=/^[a-z][a-zA-Z0-9]{0,15}$/;function Qe(t){if(!Array.isArray(t))return[];let e=[];for(let o of t){if(e.length>=6)break;let{id:r,label:a,options:n,default:i}=o??{};if(typeof r!="string"||!io.test(r)||e.some(c=>c.id===r)||!Array.isArray(n))continue;let s=[];for(let c of n){let p=typeof c=="object"&&c!==null?c.value:c;if(typeof p!="string"&&typeof p!="number"||typeof p=="number"&&!Number.isFinite(p)||s.length>=8||s.some(m=>m.value===p))continue;let x=typeof c=="object"&&c!==null&&typeof c.label=="string"?c.label:String(p);s.push({value:p,label:We(x).slice(0,20)||String(p)})}if(s.length<2)continue;let d=typeof a=="string"?We(a).slice(0,24):"";e.push({id:r,label:d||r,options:s,default:s.some(c=>c.value===i)?i:s[0].value})}return e}function et(t,e){let o=e!==null&&typeof e=="object"?e[t.id]:void 0;return t.options.some(r=>r.value===o)?o:t.default}var Se='{"t":"ping"}';var tt=4001,ot=4003,rt=4008,at=4006;var nt="onceworlds:hello",it="onceworlds:connect",so=/^[a-z0-9]{1,32}~(?:(?:0|[1-9]\d{0,3})\.(?:0|[1-9]\d{0,4})\.(?:0|[1-9]\d{0,5}))?~[0-9a-z]{1,11}~[lp]~[A-Za-z0-9_-]{43}$/;function st(t){let e=t!==null&&typeof t=="object"?t.build:void 0;return typeof e=="string"&&so.test(e)?e:null}var lt="onceworlds-console";var pt="allow-scripts allow-pointer-lock allow-forms allow-orientation-lock",dt=["autoplay","gamepad","screen-wake-lock","accelerometer","gyroscope"].map(t=>`${t} *`).join("; "),ct=100*1024*1024;var gt=new Set(["server","join","deploy","watch","player","embed","k","console","tester","live","demo",lt]);var ze="onceworlds:page",lo=["ran","blocked","failed","threw","loaded"];function ht(t){if(t===null||typeof t!="object")return null;let{what:e,detail:o}=t;return typeof e!="string"||!lo.includes(e)?null:{what:e,detail:typeof o=="string"?o.slice(0,200):""}}var po=`(function(){var n=0,s=function(w,d){if(n++<12)try{parent.postMessage({type:${JSON.stringify(ze)},what:w,detail:String(d).slice(0,200)},"*")}catch(e){}};s("ran",document.readyState);addEventListener("securitypolicyviolation",function(e){s("blocked",e.effectiveDirective+" "+e.blockedURI)});addEventListener("error",function(e){var t=e.target;t&&t.tagName?s("failed",t.tagName+" "+(t.src||t.href)):s("threw",e.message+" "+e.filename+":"+e.lineno)},true);addEventListener("load",function(){s("loaded",typeof onceworlds)})})();`,Yo=`<script>${po}<\/script>`;function mt(t){return t?.ownCountdown!==!0}function ut(t){let e=t?.lobby;return e==="card"||e==="bar"?e:e==="game"?null:typeof t?.minPlayers=="number"||typeof t?.minPlayersPrivate=="number"||typeof t?.countdown=="number"||Array.isArray(t?.settings)&&t.settings.length>0?"bar":null}function xt(t){return t.key==="Enter"&&!t.isComposing&&!t.ctrlKey&&!t.metaKey&&!t.altKey&&!t.shiftKey}var ft=400;var co=/^#(?:[0-9a-f]{3}){1,2}$/i,T={panel:"#111216",text:"#f7f7f8",accent:"#d33e00"},Ee=4.5,Re=3,te=["#4ade80","#15803d"];function wt(t){let e=[];if(t==null)return{look:null,notes:e};if(typeof t!="object")return{look:null,notes:["a look is { accent, panel, text } or null"]};let o=t,r=p=>{let x=o[p];return x==null?null:typeof x=="string"&&co.test(x)?vt(x):(e.push(`${p} is not #rgb or #rrggbb`),null)},a=r("panel"),n=r("text"),i=r("accent");if(a||n){let p=A(n??T.text,a??T.panel);p<Ee&&(e.push(`text on panel is ${p.toFixed(1)}:1, under ${Ee}:1: the platform's are used`),a=n=null)}let s=a??T.panel,d=n??T.text;if(i){let p=A(i,s);p<Re&&(e.push(`accent on panel is ${p.toFixed(1)}:1, under ${Re}:1: the platform's is used`),i=null)}if(!i&&a&&A(T.accent,a)<Re&&(i=d),!a&&!n&&!i)return{look:null,notes:e};let c=i?go(i):null;return{look:{panel:a,text:n,muted:a||n?ho(d,s):null,accent:i,accentHover:i?yt(i,c==="#ffffff"?"#000000":"#ffffff",.14):null,onAccent:c,ok:a?A(te[0],a)>=A(te[1],a)?te[0]:te[1]:null},notes:e}}function vt(t){let e=t.slice(1).toLowerCase();return`#${e.length===3?[...e].map(o=>o+o).join(""):e}`}function W(t){let e=vt(t);return[parseInt(e.slice(1,3),16),parseInt(e.slice(3,5),16),parseInt(e.slice(5,7),16)]}function bt(t){let[e,o,r]=W(t).map(a=>{let n=a/255;return n<=.04045?n/12.92:((n+.055)/1.055)**2.4});return .2126*e+.7152*o+.0722*r}function A(t,e){let[o,r]=[bt(t),bt(e)].sort((a,n)=>n-a);return(o+.05)/(r+.05)}function go(t){return A(t,"#000000")>A(t,"#ffffff")?"#000000":"#ffffff"}function yt(t,e,o){let[r,a]=[W(t),W(e)];return`#${r.map((n,i)=>Math.round(n+(a[i]-n)*o).toString(16).padStart(2,"0")).join("")}`}function ho(t,e){for(let o=.3;o>.001;o-=.05){let r=yt(t,e,o);if(A(r,e)>=Ee)return r}return t}var oe=class{port=null;handlers;events;onConnect;solo;demo;onActivity;onPage;constructor(e,o,r,a,n={}){this.handlers=o,this.events=r,this.onConnect=a,this.solo=n.solo===!0,this.demo=n.demo===!0,this.onActivity=n.onActivity??null,this.onPage=n.onPage??null,window.addEventListener("message",i=>{if(!(i.source!==e.contentWindow||i.origin!=="null")&&(i.data?.type===nt&&this.connect(i.source,st(i.data)),i.data?.type===ze)){let s=ht(i.data);s&&this.onPage?.(s)}})}activity(e,o){try{this.onActivity?.(e,o)}catch{}}emit(e,o){this.port?.postMessage({kind:"event",name:e,data:o})}connect(e,o){this.port?.close();let r=new MessageChannel,a=r.port1;this.port=a,a.onmessage=i=>this.receive(a,i.data);let n={type:it,...this.solo?{solo:!0}:{},...this.demo?{demo:!0}:{}};e.postMessage(n,"*",[r.port2]),this.onConnect(o)}async receive(e,o){if(!(e!==this.port||!o||typeof o!="object")){if(o.kind==="event"){Object.hasOwn(this.events,o.name)&&(this.events[o.name](o.data),this.activity(String(o.name),null));return}if(o.kind==="req")try{if(!Object.hasOwn(this.handlers,o.method))throw new Error(`Unknown method: ${String(o.method)}`);let r=this.handlers[o.method],a=await r(o.params);e.postMessage({kind:"res",id:o.id,ok:!0,result:a??null}),this.activity(String(o.method),null)}catch(r){let a=r instanceof Error?r.message:String(r);e.postMessage({kind:"res",id:o.id,ok:!1,error:a}),this.activity(String(o.method),a)}}}};var mo="ow-game-storage",uo=256,kt="guest";function Pe(t){let e=0,o=new Set,r=[t];for(;r.length;){let a=r.pop();if(typeof a=="string")e+=a.length*2;else if(typeof a!="object"||a===null)e+=8;else{if(o.has(a))continue;if(o.add(a),a instanceof ArrayBuffer)e+=a.byteLength;else if(ArrayBuffer.isView(a))e+=a.buffer.byteLength;else if(typeof Blob=="function"&&a instanceof Blob)e+=a.size;else if(a instanceof Map)for(let[n,i]of a)r.push(n,i);else if(a instanceof Set)for(let n of a)r.push(n);else if(Array.isArray(a))r.push(...a.slice(0,1e5),...a.length>1e5?[a.slice(1e5)]:[]);else for(let[n,i]of Object.entries(a))r.push(n,i)}}return e}function L(t){if(typeof t!="string"||t.length>uo)throw new Error("Bad name");return t}function St(t){if(t===null)return null;if(typeof t=="string")return L(t);if(Array.isArray(t)&&t.length<=32)return t.map(L);throw new Error("Bad key path")}function xo(t){if(typeof t!="object"||t===null)throw new Error("Bad stores");let e=Object.keys(t);if(e.length>1e3)throw new Error("Too many stores");let o={};for(let r of e){let a=t[L(r)];if(typeof a!="object"||a===null||!Array.isArray(a.indexes)||a.indexes.length>1e3)throw new Error("Bad store");let n=Number(a.nextKey),i=a.indexes.map(s=>{let d=St(s?.keyPath);if(d===null)throw new Error("Bad index");return{name:L(s.name),keyPath:d,unique:s.unique===!0,multiEntry:s.multiEntry===!0}});o[r]={keyPath:St(a.keyPath),autoIncrement:a.autoIncrement===!0,nextKey:Number.isFinite(n)?n:1,indexes:i}}return o}function k(t){return new Promise((e,o)=>{t.onsuccess=()=>e(t.result),t.onerror=()=>o(t.error??new Error("Storage failed"))})}function Me(t){return new Promise((e,o)=>{t.oncomplete=()=>e(),t.onabort=t.onerror=()=>o(t.error??new Error("Storage failed"))})}var re=class{database=null;named=null;owner;factory;limit;constructor(e,o={}){this.owner=e,this.factory=o.factory??(()=>indexedDB),this.limit=o.limit??ct}open(){return this.database??=new Promise((e,o)=>{let r=this.factory().open(mo,1);r.onupgradeneeded=()=>{r.result.createObjectStore("dbs",{keyPath:["owner","db"]}),r.result.createObjectStore("records")},r.onsuccess=()=>e(r.result),r.onerror=()=>o(r.error??new Error("No storage in this browser"))}),this.database}who(){return this.named??=this.owner().then(async({game:e,player:o})=>{let r=`${e}
2
+ ${o??kt}`;return o!==null&&await this.adopt(`${e}
3
+ ${kt}`,r),r}),this.named}async adopt(e,o){let a=(await this.open()).transaction(["dbs","records"],"readwrite"),n=Me(a),i=a.objectStore("dbs"),s=a.objectStore("records"),d=await k(i.getAll(this.ownerRange(e)));for(let c of d){let p=this.dbRange(e,c.db);if(!await k(i.getKey([o,c.db]))){let x=await k(s.getAllKeys(p));for(let m of x){let E=await k(s.get(m));s.put(E,[o,...m.slice(1)])}i.put({...c,owner:o})}s.delete(p),i.delete([e,c.db])}await n}ownerRange(e){return IDBKeyRange.bound([e],[`${e}\0`],!1,!0)}dbRange(e,o){return IDBKeyRange.bound([e,o],[e,`${o}\0`],!1,!0)}storeRange(e,o,r){return IDBKeyRange.bound([e,o,r],[e,o,`${r}\0`],!1,!0)}async read({db:e}){L(e);let o=await this.who(),a=(await this.open()).transaction(["dbs","records"]),n=await k(a.objectStore("dbs").get([o,e]));if(!n)return null;let i=a.objectStore("records"),s=this.dbRange(o,e),[d,c]=await Promise.all([k(i.getAllKeys(s)),k(i.getAll(s))]);return{version:n.version,stores:n.stores,records:d.map((p,x)=>({store:p[2],key:p[3],value:c[x].value}))}}async commit(e){let o=L(e?.db),r=Number(e.version);if(!Number.isFinite(r)||r<0)throw new Error("Bad version");let a=e.ops;if(!Array.isArray(a))throw new Error("Bad changes");let n=e.stores===void 0?void 0:xo(e.stores),i=await this.who(),d=(await this.open()).transaction(["dbs","records"],"readwrite"),c=Me(d),p=d.objectStore("dbs"),x=d.objectStore("records");try{let m=await k(p.getAll(this.ownerRange(i))),E=m.find(l=>l.db===o)??{owner:i,db:o,version:r,stores:{},bytes:0},K=m.reduce((l,f)=>l+(f===E?0:f.bytes),0),b=E.bytes;for(let l of a){let f=L(l?.store);if("clear"in l){b-=await this.sum(x,this.storeRange(i,o,f)),x.delete(this.storeRange(i,o,f));continue}let ve=[i,o,f,"put"in l?l.put:l.remove],Ge=await k(x.get(ve));if(Ge&&(b-=Ge.bytes),"put"in l){let Ue=Pe(l.value)+Pe(l.put)+64;b+=Ue,x.put({value:l.value,bytes:Ue},ve)}else x.delete(ve)}if(n)for(let l of Object.keys(E.stores))Object.hasOwn(n,l)||(b-=await this.sum(x,this.storeRange(i,o,l)),x.delete(this.storeRange(i,o,l)));if(K+=Pe(n??E.stores),K+b>this.limit)throw new Error("QuotaExceededError");p.put({owner:i,db:o,version:r,stores:n??E.stores,bytes:Math.max(0,b)})}catch(m){try{d.abort()}catch{}throw c.catch(()=>{}),m}return await c,null}async sum(e,o){return(await k(e.getAll(o))).reduce((a,n)=>a+n.bytes,0)}async remove({db:e}){L(e);let o=await this.who(),a=(await this.open()).transaction(["dbs","records"],"readwrite"),n=Me(a);return a.objectStore("records").delete(this.dbRange(o,e)),a.objectStore("dbs").delete([o,e]),await n,null}async list(){let e=await this.who(),o=await this.open();return(await k(o.transaction("dbs").objectStore("dbs").getAll(this.ownerRange(e)))).map(a=>({name:a.db,version:a.version}))}};var ae=class{pressed=new Set;press(e){this.pressed.add(e)}take(e){let o=new Set(e),r=!1;for(let a of this.pressed)o.has(a)||(o.add(a),r=!0);return this.pressed.clear(),{buttons:[...o],owed:r}}};var zt=["--ow-panel","--ow-face-edge","--ow-hover","--ow-on-accent","--text","--muted","--line","--line-strong","--fill","--fill-strong","--ok","--accent-fill","--accent-fill-hover","--accent-hover"],Rt=["--ow-ctl-edge","--ow-ctl-fill","--ow-ctl-stick","--ow-ctl-knob","--ow-ctl-label","--ow-ctl-shadow","--ow-ctl-held","--ow-ctl-main","--ow-ctl-main-edge","--ow-ctl-main-label","--ow-ctl-main-shadow","--ow-ctl-main-held"];function v(t,e){return`rgba(${W(t).join(", ")}, ${e})`}function Et(t){let e={};if(!t)return e;if(t.panel&&(e["--ow-panel"]=t.panel,e["--ow-face-edge"]=t.panel),t.ok&&(e["--ok"]=t.ok),t.panel||t.text){let o=t.text??T.text;t.text&&(e["--text"]=t.text),t.muted&&(e["--muted"]=t.muted),e["--line"]=v(o,.09),e["--line-strong"]=v(o,.16),e["--fill"]=v(o,.06),e["--fill-strong"]=v(o,.1),e["--ow-hover"]=v(o,.16)}return t.accent&&t.accentHover&&t.onAccent&&(e["--accent-fill"]=t.accent,e["--accent-fill-hover"]=t.accentHover,e["--accent-hover"]=t.accent,e["--ow-on-accent"]=t.onAccent),e}function Pt(t){let e={};return t&&(t.text&&(e["--ow-ctl-edge"]=v(t.text,.4),e["--ow-ctl-knob"]=v(t.text,.9),e["--ow-ctl-label"]=t.text,e["--ow-ctl-held"]=v(t.text,.35)),t.panel&&(e["--ow-ctl-fill"]=v(t.panel,.4),e["--ow-ctl-stick"]=v(t.panel,.3),e["--ow-ctl-shadow"]=v(t.panel,.5)),t.accent&&t.accentHover&&t.onAccent&&(e["--ow-ctl-main"]=v(t.accent,.85),e["--ow-ctl-main-edge"]=t.accent,e["--ow-ctl-main-label"]=t.onAccent,e["--ow-ctl-main-shadow"]="none",e["--ow-ctl-main-held"]=t.accentHover)),e}function ne(t,e,o){for(let r of e)Object.hasOwn(o,r)?t.style.setProperty(r,o[r]):t.style.removeProperty(r);t.toggleAttribute("data-look",e.some(r=>Object.hasOwn(o,r)))}var $=52,Ae=.12,Le=96,fo=400,bo=2e4,ie=class{parent;root;zone;base;knob;buttonBox;send;onLayout;layout=null;touch;blocked=!1;keyboard=!1;stickPointer=null;center={x:0,y:0};x=0;y=0;buttons=new Map;held=new Map;fingers=new Map;report=new ae;queued=0;sent="";placed={stick:null,buttons:null};constructor(e,o,r=()=>{}){this.send=o,this.onLayout=r,this.parent=e,this.touch=matchMedia("(pointer: coarse)").matches,this.root=D("div","ow-controls"),this.root.hidden=!0,this.zone=D("div","ow-stick-zone"),this.base=D("div","ow-stick"),this.knob=D("div","ow-stick-knob"),this.base.append(this.knob),this.zone.append(this.base),this.buttonBox=D("div","ow-control-buttons"),this.root.append(this.zone,this.buttonBox),e.append(this.root),this.zone.addEventListener("pointerdown",a=>this.stickDown(a)),this.zone.addEventListener("pointermove",a=>this.stickMove(a));for(let a of["pointerup","pointercancel","lostpointercapture"])this.zone.addEventListener(a,n=>this.stickUp(n));this.root.addEventListener("contextmenu",a=>a.preventDefault())}setLayout(e){this.layout=Ze(e);let o=this.buttons;this.buttons=new Map,(this.layout?.buttons??[]).forEach((r,a)=>{let n=o.get(r.id)??this.button(r.id);o.delete(r.id),n.className=`ow-control-btn ow-control-btn-${a}`,n.classList.toggle("ow-held",[...this.held.values()].includes(r.id)),n.textContent=r.label,n.isConnected||this.buttonBox.append(n),this.buttons.set(r.id,n)});for(let[r,a]of o){for(let[n,i]of this.held)i===r&&this.letGo(n);a.remove()}this.zone.hidden=!this.layout?.stick,this.zone.classList.toggle("ow-stick-corner",this.layout?.zone==="corner"),this.zone.hidden&&this.stickPointer!==null&&this.letGo(this.stickPointer),this.render(),this.onLayout()}setLook(e){ne(this.root,Rt,e)}reservedBoxes(){if(this.root.hidden)return this.blocked&&this.layout&&this.touch&&!this.keyboard?this.placed:{stick:null,buttons:null};let e=this.zone.hidden?null:At(this.base),o=null;for(let r of this.buttons.values()){let a=At(r);!a.width||!a.height||(o=o?vo(o,a):a)}return this.placed={stick:e,buttons:o},this.placed}setTouch(e){this.touch=e,this.render()}setKeyboard(e){this.keyboard=e,this.render()}setBlocked(e){this.blocked=e,this.render()}render(){let e=!!this.layout&&this.touch&&!this.blocked&&!this.keyboard;this.root.hidden!==!e&&(e||this.reset(),this.root.hidden=!e,this.onLayout())}reset(){for(let e of[...this.held.keys()])this.letGo(e);this.stickPointer!==null&&this.letGo(this.stickPointer),this.endStick()}letGo(e){e===this.stickPointer&&this.endStick();let o=this.held.get(e);this.held.delete(e),o!==void 0&&![...this.held.values()].includes(o)&&this.buttons.get(o)?.classList.remove("ow-held");let r=this.fingers.get(e);this.fingers.delete(e),r&&wo(this.parent,e,r),this.changed()}button(e){let o=D("button","ow-control-btn");o.type="button",o.tabIndex=-1,o.addEventListener("pointerdown",a=>{a.preventDefault(),Mt(o,a.pointerId),o.classList.add("ow-held"),this.held.set(a.pointerId,e),this.report.press(e),this.fingers.set(a.pointerId,{x:a.clientX,y:a.clientY}),this.changed()}),o.addEventListener("pointermove",a=>{this.held.get(a.pointerId)===e&&this.fingers.set(a.pointerId,{x:a.clientX,y:a.clientY})});let r=a=>{this.held.get(a.pointerId)===e&&(this.held.delete(a.pointerId),this.fingers.delete(a.pointerId),[...this.held.values()].includes(e)||o.classList.remove("ow-held"),this.changed())};for(let a of["pointerup","pointercancel","lostpointercapture"])o.addEventListener(a,n=>r(n));return o}stickDown(e){if(this.stickPointer!==null)return;e.preventDefault(),Mt(this.zone,e.pointerId),this.stickPointer=e.pointerId,this.fingers.set(e.pointerId,{x:e.clientX,y:e.clientY});let o=this.zone.getBoundingClientRect(),r=this.base.getBoundingClientRect(),a=$+8;this.center={x:Math.min(Math.max(e.clientX,o.left+a),o.right-a),y:Math.min(Math.max(e.clientY,o.top+a),o.bottom-a)},this.base.style.transform=`translate(${this.center.x-(r.left+r.width/2)}px, ${this.center.y-(r.top+r.height/2)}px)`,this.base.classList.add("ow-active"),this.stickMove(e)}stickMove(e){if(e.pointerId!==this.stickPointer)return;this.fingers.set(e.pointerId,{x:e.clientX,y:e.clientY});let o=e.clientX-this.center.x,r=e.clientY-this.center.y,a=Math.hypot(o,r);a>$&&(o=o/a*$,r=r/a*$),this.knob.style.transform=`translate(${o}px, ${r}px)`;let n=Math.min(a,$)/$,i=n<Ae?0:(n-Ae)/(1-Ae)/(n||1);this.x=Lt(o/$*i),this.y=Lt(r/$*i),this.changed()}stickUp(e){e.pointerId===this.stickPointer&&(this.fingers.delete(e.pointerId),this.endStick())}endStick(){this.stickPointer=null,this.x=0,this.y=0,this.base.classList.remove("ow-active"),this.base.style.transform="",this.knob.style.transform="",this.changed()}changed(){this.queued||(this.queued=requestAnimationFrame(()=>{this.queued=0;let{buttons:e,owed:o}=this.report.take(this.held.values()),r={x:this.x,y:this.y,buttons:e};o&&this.changed();let a=JSON.stringify(r);a!==this.sent&&(this.sent=a,this.send(r))}))}};function wo(t,e,o){let r=D("div","ow-tap-shield");r.style.left=`${o.x-Le/2}px`,r.style.top=`${o.y-Le/2}px`,r.style.width=r.style.height=`${Le}px`,t.append(r);let a=setTimeout(()=>r.remove(),bo),n=()=>{clearTimeout(a),a=setTimeout(()=>r.remove(),fo)};for(let i of["pointerup","pointercancel","lostpointercapture"])r.addEventListener(i,s=>s.pointerId===e&&n());r.addEventListener("contextmenu",i=>i.preventDefault());try{r.setPointerCapture(e)}catch{n()}}function Mt(t,e){try{t.setPointerCapture(e)}catch{}}function At(t){let e=t.getBoundingClientRect(),o=getComputedStyle(t).transform;if(!o||o==="none")return e;let r=new DOMMatrixReadOnly(o);if(r.b||r.c||!r.a||!r.d)return e;let a=e.width/Math.abs(r.a),n=e.height/Math.abs(r.d),i=e.left+e.width/2-r.e,s=e.top+e.height/2-r.f;return{left:i-a/2,top:s-n/2,width:a,height:n}}function vo(t,e){let o=Math.min(t.left,e.left),r=Math.min(t.top,e.top);return{left:o,top:r,width:Math.max(t.left+t.width,e.left+e.width)-o,height:Math.max(t.top+t.height,e.top+e.height)-r}}function Lt(t){return Math.round(t*1e3)/1e3}function D(t,e,o){let r=document.createElement(t);return r.className=e,o!==void 0&&(r.textContent=o),r}function Y(t,e){let o=[];for(let{kind:r,box:a}of e){if(!a||!(a.width>0)||!(a.height>0))continue;let n=Math.max(0,a.left-t.left),i=Math.max(0,a.top-t.top),s=Math.min(t.width,a.left+a.width-t.left),d=Math.min(t.height,a.top+a.height-t.top);if(!(s-n>.01&&d-i>.01))continue;let c=Math.floor(n+.01),p=Math.floor(i+.01);o.push({kind:r,x:c,y:p,w:Math.ceil(s-.01)-c,h:Math.ceil(d-.01)-p})}return o}var se=class{measure;send;nextFrame;queued=!1;sent=null;constructor(e,o,r=a=>requestAnimationFrame(a)){this.measure=e,this.send=o,this.nextFrame=r}changed(){this.queued||(this.queued=!0,this.nextFrame(()=>{this.queued=!1;let e=this.measure();if(!e)return;let o=JSON.stringify(e);o!==this.sent&&(this.sent=o,this.send(e))}))}resend(){this.sent=null,this.changed()}};var $t=t=>Math.max(0,Math.ceil(t/1e3)),$e=t=>{let e=t.slice(0,2).map(o=>o.name);return t.length>2?`${e.join(", ")} +${t.length-2}`:e.join(", ")};function yo(t){let{you:e,host:o,kind:r,match:a,now:n}=t,i=[...t.players].sort((l,f)=>l.id===e?-1:f.id===e?1:l.id===o?-1:f.id===o?1:l.name.localeCompare(f.name)).map(l=>({id:l.id,name:l.name,ready:!!l.ready,host:l.id===o,you:l.id===e,away:l.connected===!1,idle:l.connected!==!1&&!!l.idle})),s=t.players.filter(l=>l.connected!==!1),d=t.players.find(l=>l.id===e),c=o===e&&a.phase==="lobby",p=t.schema.map(l=>({id:l.id,label:l.label,value:et(l,t.written),options:l.options,editable:c})),x={n:s.filter(l=>l.ready||r!=="public"&&l.id===o).length,of:s.length},m={surface:"none",rows:i,count:`${t.players.length} / ${t.max}`,ready:x,status:"",action:{kind:"none",label:"",disabled:!0},settings:p,seconds:null},E=a.participants?.includes(e)??!1,K=o===e&&r!=="public";if(a.phase==="starting"){if(!E)return{...m,surface:"watching",status:"Watching"};let l=K?{kind:"stop",label:"Stop",disabled:!1}:{kind:"unready",label:"Not ready",disabled:!1};return t.ownCountdown?{...m,surface:"countdown",action:l,seconds:null,status:"Get ready"}:{...m,surface:"countdown",action:l,seconds:Math.max(1,$t((a.startsAt??n)-n)),status:"Get ready"}}if(a.phase==="playing"){if(a.paused){let l=s.filter(f=>a.participants?.includes(f.id)).length;return a.paused.reason==="host"?{...m,surface:"paused",status:"Paused"}:{...m,surface:"paused",seconds:$t(a.paused.since+P.matchAbandonMs-n),status:`Waiting for players \xB7 ${l} / ${a.min}`}}return E?m:{...m,surface:"watching",status:"Watching"}}let b=s.filter(l=>!l.ready&&(r==="public"||l.id!==o));if(m.surface="lobby",K){let l=s.length<a.min;return m.action={kind:"start",label:"Start",disabled:l||b.length>0},m.status=l?`Need ${a.min} players`:b.length?`Waiting for ${$e(b)}`:"",m}if(m.action={kind:d?.ready?"unready":"ready",label:d?.ready?"Not ready":"Ready",disabled:!1},r==="public")s.length<a.min?m.status=`Need ${a.min} players`:a.waitUntil!==void 0&&b.length?m.status=b.some(l=>l.id===e)?"Waiting for you":`Waiting for ${$e(b)}`:m.status=`${s.length-b.length} / ${s.length} ready`;else{let l=b.filter(f=>f.id!==e);m.status=d?.ready?l.length?`Waiting for ${$e(l)}`:`Waiting for ${t.players.find(f=>f.id===o)?.name??"the host"}`:""}return m}function h(t,e="",o){let r=document.createElement(t);return e&&(r.className=e),o!==void 0&&(r.textContent=o),r}var le=class{mode=null;ownCountdown=!1;targetOk=!1;targetTimer=0;lastKey=-1/0;state=null;schema=[];timer=0;opts;root;card;title;count;list;settingsBox;status;action;invite;bar;barFaces;barStatus;barAction;countdown;numeral;cancel;pause;pauseStatus;pauseTimer;chip;last=null;lastNumeral=-1;listSignature="";settingsSignature="";faceSignature="";constructor(e,o){this.opts=o,this.root=h("div","ow-lobby"),this.root.hidden=!0;for(let s of["pointerdown","mousedown"])this.root.addEventListener(s,d=>d.preventDefault());this.card=h("section","ow-lobby-card"),this.card.setAttribute("aria-label","Lobby");let r=h("header","ow-lobby-head");this.title=h("h2","","Lobby"),this.count=h("span","ow-lobby-count"),r.append(this.title,this.count),this.list=h("ul","ow-lobby-players"),this.settingsBox=h("div","ow-lobby-settings"),this.status=h("p","ow-lobby-status"),this.status.setAttribute("role","status"),this.action=this.button("ow-btn ow-btn-primary ow-lobby-action",()=>this.press()),this.invite=this.button("ow-btn ow-lobby-invite",()=>this.opts.onInvite()),this.invite.textContent="Invite";let a=h("div","ow-lobby-actions");a.append(this.action,this.invite),this.card.append(r,this.list,this.settingsBox,this.status,a),this.bar=h("div","ow-lobby-bar"),this.barFaces=h("span","ow-lobby-faces"),this.barStatus=h("span","ow-lobby-bar-status"),this.barStatus.setAttribute("role","status"),this.barAction=this.button("ow-btn ow-btn-primary ow-lobby-action",()=>this.press()),this.bar.append(this.barFaces,this.barStatus,this.barAction),this.countdown=h("div","ow-lobby-countdown"),this.numeral=h("b"),this.numeral.setAttribute("aria-hidden","true");let n=h("span","","Get ready");n.setAttribute("aria-hidden","true"),this.cancel=this.button("ow-btn ow-lobby-cancel",()=>this.press()),this.countdown.append(this.numeral,n,this.cancel),this.pause=h("div","ow-lobby-pause");let i=h("div","ow-lobby-pause-box");this.pauseStatus=h("p","ow-lobby-pause-status"),this.pauseStatus.setAttribute("role","status"),this.pauseTimer=h("p","ow-lobby-pause-timer"),i.append(this.pauseStatus,this.pauseTimer),this.pause.append(i),this.chip=h("div","ow-lobby-chip"),this.chip.setAttribute("role","status"),this.root.append(this.card,this.bar,this.countdown,this.pause,this.chip),e.append(this.root)}setSchema(e){this.schema=e,this.render()}enable(e,o={}){this.mode=e,this.ownCountdown=o.ownCountdown===!0,this.root.dataset.mode=e??"",e||this.reset(),this.render()}setLook(e){ne(this.root,zt,e)}reservedBox(){if(this.root.hidden)return null;let e=!this.countdown.hidden&&this.countdown.classList.contains("ow-lobby-countdown-bare"),o=this.bar.hidden?e&&!this.cancel.hidden?this.cancel:null:this.bar;return o?o.getBoundingClientRect():null}link(e){let o=!this.root.hidden&&(this.last?.surface==="lobby"||this.last?.surface==="countdown"),r=this.pressable()?.getBoundingClientRect()??null;if(!(r!==null&&r.left>=e.left&&r.top>=e.top&&r.left+r.width<=e.left+e.width&&r.top+r.height<=e.top+e.height))return{keys:o,button:null,cover:null};let[n]=Y(e,[{kind:"lobby",box:r}]),[i]=this.mode==="bar"?Y(e,[{kind:"lobby",box:this.bar.getBoundingClientRect()}]):[{x:0,y:0,w:e.width,h:e.height}];return!n||!i?{keys:o,button:null,cover:null}:{keys:o,button:{x:n.x,y:n.y,w:n.w,h:n.h},cover:{x:i.x,y:i.y,w:i.w,h:i.h}}}setTarget(e,o){window.clearTimeout(this.targetTimer),e&&(this.targetTimer=window.setTimeout(()=>this.setTarget(!1,!1),1600)),this.targetOk!==e&&(this.targetOk=e,this.applyTarget()),this.pressable()?.classList.toggle("ow-lobby-hover",e&&o)}pressKey(e=performance.now()){this.pressable()===null||e-this.lastKey<ft||(this.lastKey=e,this.press())}pressTarget(){this.pressable()&&this.press()}setKeyMark(e){this.root.classList.toggle("ow-lobby-keys",e)}pressable(){let e=this.last;return this.root.hidden||!e||e.surface!=="lobby"||e.action.kind==="none"||e.action.disabled?null:this.mode==="card"?this.action:this.barAction}applyTarget(){let e=this.targetOk&&this.pressable()!==null;if(this.root.classList.toggle("ow-lobby-through",e),!e)for(let o of[this.action,this.barAction])o.classList.remove("ow-lobby-hover")}setRoom(e,o=0){if(!e)return this.reset();this.state={you:e.you,host:e.host,kind:e.room.startsWith("private:")?"private":e.room.startsWith("room:")?"named":"public",max:Math.max(o,e.players.length),players:e.players.map(r=>({...r})),match:e.match??{phase:"lobby",n:0,min:1},written:e.state?.[V]},this.render()}handle(e){let o=this.state;if(o){switch(e.t){case"join":o.players=[...o.players.filter(r=>r.id!==e.player.id),{...e.player}];break;case"leave":o.players=o.players.filter(r=>r.id!==e.id);break;case"away":o.players=o.players.map(r=>r.id===e.id?{...r,connected:!1}:r);break;case"back":o.players=o.players.map(r=>{if(r.id!==e.id)return r;let{connected:a,...n}=r;return n});break;case"ready":o.players=o.players.map(r=>{if(r.id!==e.id)return r;let{ready:a,...n}=r;return e.ready?{...n,ready:!0}:n});break;case"unready":o.players=o.players.map(({ready:r,...a})=>a);break;case"host":o.host=e.id;break;case"idle":o.players=o.players.map(r=>{if(r.id!==e.id)return r;let{idle:a,...n}=r;return e.idle?{...n,idle:!0}:n});break;case"name":o.players=o.players.map(r=>r.id===e.id?{...r,name:e.name}:r);break;case"match":o.match=e.match;break;case"state":if(e.k!==V)return;o.written=e.v;break;default:return}this.render()}}chooseSetting(e,o){let r=this.state,a=this.schema.find(i=>i.id===e);if(!r||!a||!a.options.some(i=>i.value===o))return null;let n={...r.written!==null&&typeof r.written=="object"?r.written:{},[e]:o};return r.written=n,this.render(),n}setReadyLocal(e){let o=this.state?.players.find(r=>r.id===this.state?.you);o&&(e?o.ready=!0:delete o.ready,this.render())}button(e,o){let r=h("button",e);return r.type="button",r.addEventListener("pointerdown",a=>a.preventDefault()),r.addEventListener("click",o),r}press(){let e=this.last?.action.kind;e==="start"?this.opts.onStart():e==="stop"?this.opts.onStop():e==="ready"?(this.setReadyLocal(!0),this.opts.onReady(!0)):e==="unready"&&(this.setReadyLocal(!1),this.opts.onReady(!1)),this.opts.onPressed?.()}reset(){this.state=null,this.last=null,this.setTarget(!1,!1),window.clearInterval(this.timer),this.timer=0,this.root.hidden=!0,this.opts.onLayout?.()}render(){this.draw(),this.opts.onLayout?.()}draw(){if(!this.mode||!this.state){this.root.hidden=!0;return}let e=yo({...this.state,schema:this.schema,now:this.opts.now(),ownCountdown:this.ownCountdown});this.last=e;let o=e.surface==="lobby";if(this.root.hidden=e.surface==="none",this.card.hidden=!(o&&this.mode==="card"),this.bar.hidden=!(o&&this.mode==="bar"),this.countdown.hidden=e.surface!=="countdown",this.countdown.classList.toggle("ow-lobby-countdown-bare",e.surface==="countdown"&&e.seconds===null),this.pause.hidden=e.surface!=="paused",this.chip.hidden=e.surface!=="watching",this.root.classList.toggle("ow-lobby-dim",o&&this.mode==="card"),o&&this.mode==="card"&&this.renderCard(e),o&&this.mode==="bar"&&this.renderBar(e),this.applyTarget(),e.surface==="countdown"&&e.seconds!==null&&e.seconds!==this.lastNumeral&&(this.lastNumeral=e.seconds,this.numeral.textContent=String(e.seconds),this.numeral.style.animation="none",this.numeral.offsetWidth,this.numeral.style.animation=""),e.surface==="countdown"&&(this.cancel.textContent=e.action.label),e.surface!=="countdown"&&(this.lastNumeral=-1),e.surface==="paused"&&(this.pauseStatus.textContent=e.status,this.pauseTimer.textContent=e.seconds!==null?`Ends in ${ko(e.seconds)}`:""),e.surface==="watching"){let[a,...n]=e.status.split(" \xB7 "),i=h("span","ow-lobby-chip-more",n.length?` \xB7 ${n.join(" \xB7 ")}`:"");this.chip.replaceChildren(a,i)}let r=e.seconds!==null;r&&!this.timer?this.timer=window.setInterval(()=>this.render(),250):!r&&this.timer&&(window.clearInterval(this.timer),this.timer=0)}renderCard(e){this.count.textContent=e.count,this.settingsBox.hidden=e.settings.length===0,this.status.textContent=e.status,this.status.hidden=e.status==="",this.setAction(this.action,e);let o=JSON.stringify(e.settings);o!==this.settingsSignature&&(this.settingsSignature=o,this.settingsBox.replaceChildren(...e.settings.map(a=>this.settingRow(a))));let r=JSON.stringify(e.rows);r!==this.listSignature&&(this.listSignature=r,this.list.replaceChildren(...e.rows.map(a=>{let n=h("li",a.away||a.idle?"ow-lobby-away":a.ready?"ow-lobby-ready":""),i=h("img","ow-headshot");i.src=`/avatar/${encodeURIComponent(a.id)}/head.svg`,i.alt="";let s=h("span","ow-lobby-name",a.name),d=[a.away?"Reconnecting":a.idle?"Idle":"",a.you?"You":"",a.host?"Host":""].filter(Boolean).join(" \xB7 ");return n.append(i,s),d&&n.append(h("span","ow-player-notes",d)),n.append(h("span",`ow-lobby-check${a.ready?" ow-lobby-check-on":""}`,a.ready?"Ready":"")),n})))}settingRow(e){let o=h("div","ow-lobby-setting");if(o.append(h("span","ow-lobby-setting-label",e.label)),!e.editable)return o.append(h("span","ow-lobby-setting-value",e.options.find(a=>a.value===e.value)?.label??String(e.value))),o;let r=h("div","ow-segments");r.setAttribute("role","radiogroup"),r.setAttribute("aria-label",e.label);for(let a of e.options){let n=this.button(`ow-segment${a.value===e.value?" ow-segment-on":""}`,()=>this.opts.onSetting(e.id,a.value));n.textContent=a.label,n.setAttribute("role","radio"),n.setAttribute("aria-checked",String(a.value===e.value)),r.append(n)}return o.append(r),o}renderBar(e){this.barStatus.textContent=e.status||`${e.ready.n} / ${e.ready.of} ready`,this.setAction(this.barAction,e);let o=JSON.stringify(e.rows.slice(0,5).map(r=>[r.id,r.ready]));o!==this.faceSignature&&(this.faceSignature=o,this.barFaces.replaceChildren(...e.rows.slice(0,5).map(r=>{let a=h("img",`ow-headshot${r.ready?" ow-lobby-face-ready":""}`);return a.src=`/avatar/${encodeURIComponent(r.id)}/head.svg`,a.alt="",a})))}setAction(e,o){e.hidden=o.action.kind==="none",e.textContent=o.action.label,e.disabled=o.action.disabled,e.setAttribute("aria-keyshortcuts","Enter"),e.classList.toggle("ow-btn-primary",o.action.kind!=="unready")}};function ko(t){return`${Math.floor(t/60)}:${String(t%60).padStart(2,"0")}`}var Ce=t=>`ow:resume:${t}`;function j(t,e){try{e?sessionStorage.setItem(Ce(t),JSON.stringify({...e,at:Date.now()})):sessionStorage.removeItem(Ce(t))}catch{}}function Ct(t){try{let e=sessionStorage.getItem(Ce(t));if(!e)return null;let o=performance.getEntriesByType("navigation")[0];if(o&&o.type!=="reload"&&o.type!=="back_forward")return pe(t);let r=JSON.parse(e);return typeof r.key!="string"||typeof r.rejoin!="string"||typeof r.at!="number"||Date.now()-r.at>18e4||Number(r.rejoin.split(".")[0])*1e3<Date.now()?pe(t):{key:r.key,rejoin:r.rejoin}}catch{return null}}var So=t=>`ow:own-server:${t}`;function Tt(t,e){try{sessionStorage.setItem(So(t),e)}catch{}}function pe(t){return j(t,null),null}var de=class extends Error{constructor(o,r=0,a=0,n=""){super(o);this.status=r;this.retryAfterMs=a;this.code=n;this.name="RequestError"}status;retryAfterMs;code;get retryable(){return this.status===0||this.status===408||this.status===429||this.status>=500}};var ce=class{constructor(e=48*1024){this.max=e}max;used=0;async run(e,o){let r=new TextEncoder().encode(e).byteLength;if(this.used+r>this.max)throw new de("Progress is waiting to sync when you return to the game.");this.used+=r;try{return await o()}finally{this.used-=r}}};var Te=/^[2-9]$/.test(new URLSearchParams(location.search).get("player")??"")?new URLSearchParams(location.search).get("player"):null;var zo=null;function Dt(){return zo?.n??null}var Mr=new ce;var Ot=[300,700,1500,3e3,5e3,8e3],Ro=9e4,Eo=1e4,jt=()=>Array.from(crypto.getRandomValues(new Uint8Array(10)),t=>"abcdefghijklmnopqrstuvwxyz0123456789"[t%36]).join(""),It=jt();function Bt(){It=jt()}var _t=null;var Nt=!1;function Gt(t){Nt=t}var Po=5e4,Mo=4e3,ge=class{ws=null;key=null;rejoin=null;you="";closed=!1;attempt=0;droppedAt=0;sleeping=!1;lastHeard=0;heardCount=0;pingAt=0;hintedHidden=!1;pingTimer=0;watchTimer=0;retryTimer=0;page=It;slug;options;callbacks;onVisible=()=>{this.hint(),document.visibilityState==="visible"&&this.wake()};onOnline=()=>this.wake();onShow=e=>e.persisted&&this.wake();onHide=()=>this.hint();constructor(e,o,r){this.slug=e,this.options=o,this.callbacks=r,this.key=o.key??null,this.rejoin=o.rejoin??null,document.addEventListener("visibilitychange",this.onVisible),addEventListener("online",this.onOnline),addEventListener("pageshow",this.onShow),addEventListener("pagehide",this.onHide)}join(){return new Promise((e,o)=>this.open(e,o))}send(e){this.ws?.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(e))}hint(){let e=document.visibilityState==="hidden";e===this.hintedHidden||this.ws?.readyState!==WebSocket.OPEN||(this.hintedHidden=e,this.send({t:"vis",hidden:e}))}ping(){let e=this.ws;e?.readyState===WebSocket.OPEN&&(this.pingAt&&Date.now()-this.pingAt<5e3||(this.pingAt=Date.now(),e.send(Se)))}close(){this.closed=!0,j(this.slug,null),this.dispose(),this.ws?.close(1e3)}reload(){return this.closed=!0,this.dispose(),this.ws?.close(at,"Updating"),this.key?.startsWith("pub:")&&this.rejoin?{key:this.key,rejoin:this.rejoin}:null}open(e,o){this.retryTimer=0;let r=new URL(`/api/games/${this.slug}/rooms/connect`,location.href);r.protocol=location.protocol==="https:"?"wss:":"ws:",this.key?(r.searchParams.set("key",this.key),this.rejoin&&r.searchParams.set("rejoin",this.rejoin)):this.options.server?(r.searchParams.set("server",this.options.server),this.options.private&&r.searchParams.set("own","1")):this.options.name?r.searchParams.set("name",this.options.name):(this.options.mode&&r.searchParams.set("mode",this.options.mode),this.options.follow&&r.searchParams.set("follow",this.options.follow)),this.options.maxPlayers&&r.searchParams.set("max",String(this.options.maxPlayers)),this.options.minPlayers&&r.searchParams.set("min",String(this.options.minPlayers)),this.options.countdown!==void 0&&r.searchParams.set("countdown",String(this.options.countdown)),this.options.teams&&r.searchParams.set("teams",String(this.options.teams)),this.options.ranked&&r.searchParams.set("ranked","1"),Te&&r.searchParams.set("devplayer",Te);let a=Dt();a&&r.searchParams.set("tester",a),r.searchParams.set("page",this.page),this.options.build&&r.searchParams.set("build",this.options.build),_t&&r.searchParams.set("pass",_t),Nt&&r.searchParams.set("demo","1");let n=new WebSocket(r);this.ws=n,this.lastHeard=Date.now();let i=!1;n.onopen=()=>{this.lastHeard=Date.now(),window.clearInterval(this.pingTimer),window.clearInterval(this.watchTimer),this.pingTimer=window.setInterval(()=>n.readyState===WebSocket.OPEN&&this.ping(),Eo),this.watchTimer=window.setInterval(()=>{n===this.ws&&n.readyState===WebSocket.OPEN&&Date.now()-this.lastHeard>Po&&this.lost(n)},5e3)},n.onmessage=c=>{this.lastHeard=Date.now(),this.heardCount++;let p;try{p=JSON.parse(c.data)}catch{return}if(p.t==="pong"){this.pingAt&&this.callbacks.onRtt?.(Date.now()-this.pingAt),this.pingAt=0;return}if(p.t==="welcome"&&(p.rejoin&&(this.rejoin=p.rejoin),delete p.rejoin,p.room.startsWith("pub:")&&this.rejoin&&j(this.slug,{key:p.room,rejoin:this.rejoin})),p.t==="leave"&&p.kicked&&p.id===this.you){this.close(),this.finish("kicked");return}if(p.t==="welcome"&&(this.hintedHidden=!1,this.hint()),p.t==="welcome"&&!i&&(i=!0,this.key=p.room,this.you=p.you,this.attempt=0,this.droppedAt&&(this.droppedAt=0,this.callbacks.onConnection?.(!0)),e)){let{t:x,...m}=p;e(m),e=void 0;return}this.callbacks.onMessage(p)};let s=!1,d=c=>{if(!s&&(s=!0,window.clearInterval(this.pingTimer),window.clearInterval(this.watchTimer),!(this.closed||n!==this.ws))){if(c.code===tt)return this.finish("replaced");if(c.code===rt&&i&&document.visibilityState==="hidden"){this.sleeping=!0,this.droppedAt||(this.droppedAt=Date.now(),this.callbacks.onConnection?.(!1));return}if(c.code===ot){if(i||!o)return this.finish("kicked");this.closed=!0,this.dispose(),j(this.slug,null),o(Object.assign(new Error("You were removed from this server."),{kicked:!0}));return}if(!i&&o){this.closed=!0,this.dispose(),o(new Error(this.options.name||this.options.server?"Could not join that server. It may be full.":"Could not join a server. Try again soon."));return}this.retry()}};n.onclose=d,n.onerror=()=>{window.setTimeout(()=>{n.readyState===WebSocket.CLOSED&&d({code:1006})},1e3)}}retry(){if(this.closed)return;let e=Date.now();if(this.droppedAt||(this.droppedAt=e,this.callbacks.onConnection?.(!1)),e-this.droppedAt>Ro)return this.finish("disconnected");if(this.retryTimer)return;let o=Ot[Math.min(this.attempt++,Ot.length-1)]*(.5+Math.random());this.retryTimer=window.setTimeout(()=>{this.retryTimer=0,this.closed||this.open()},o)}lost(e){if(!(e!==this.ws||this.closed)){e.onopen=e.onmessage=e.onclose=e.onerror=null,window.clearInterval(this.pingTimer),window.clearInterval(this.watchTimer);try{e.close()}catch{}this.retry()}}wake(){if(this.closed)return;if(this.sleeping){this.sleeping=!1,this.droppedAt=Date.now(),this.attempt=0,this.open();return}let e=this.ws;if(!e||e.readyState===WebSocket.CLOSING||e.readyState===WebSocket.CLOSED){this.retryTimer&&(window.clearTimeout(this.retryTimer),this.retryTimer=0,this.open());return}if(e.readyState!==WebSocket.OPEN)return;let o=this.heardCount;try{e.send(Se)}catch{return this.lost(e)}window.setTimeout(()=>e===this.ws&&!this.closed&&this.heardCount===o&&this.lost(e),Mo)}dispose(){window.clearInterval(this.pingTimer),window.clearInterval(this.watchTimer),window.clearTimeout(this.retryTimer),this.retryTimer=0,document.removeEventListener("visibilitychange",this.onVisible),removeEventListener("online",this.onOnline),removeEventListener("pageshow",this.onShow),removeEventListener("pagehide",this.onHide)}finish(e){this.closed=!0,this.dispose(),j(this.slug,null),this.callbacks.onClose(e)}};var Ut=`:root {
4
4
  color-scheme: light;
5
5
  /* Site pages: as wide as this at most, with gutters that grow with the window. */
6
6
  --page: 1440px;
@@ -59,9 +59,8 @@ ${wt}`,r),r}),this.named}async adopt(e,o){let n=(await this.open()).transaction(
59
59
  --play-fill-hover: #ff8366;
60
60
  --play-ink: #121418;
61
61
  --play-text: var(--ok);
62
- /* Live: players in a game right now. The store's signal colour: a dot, a count, a Join. */
62
+ /* Live: players in a game right now. The store's signal colour: a dot, a count. */
63
63
  --live: #3ddc84;
64
- --live-ink: #0b2a17;
65
64
  --live-text: #12813f;
66
65
  /* The phone tab bar (and popups opened from it). */
67
66
  --dock-bg: var(--bar);
@@ -74,9 +73,9 @@ ${wt}`,r),r}),this.named}async adopt(e,o){let n=(await this.open()).transaction(
74
73
  /* The top bar's height (popups opened from it hang under it). */
75
74
  --bar-h: 60px;
76
75
  --radius: 8px;
77
- /* Panels, sheets and big pictures; a game's picture is --radius-art. */
76
+ /* Panels and sheets; every picture, game icon and video is --radius-art. */
78
77
  --radius-widget: 12px;
79
- --radius-art: 6px;
78
+ --radius-art: 2px;
80
79
  --font: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
81
80
  --font-display: var(--font);
82
81
  --mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
@@ -96,25 +95,25 @@ ${wt}`,r),r}),this.named}async adopt(e,o){let n=(await this.open()).transaction(
96
95
  */
97
96
  :root[data-theme='dark'] {
98
97
  color-scheme: dark;
99
- --bg: #0f1114;
100
- --bg-soft: #171a1f;
101
- --bg-raised: #1a1d23;
102
- --bg-card: #1a1d23;
103
- --bg-hover: #23272e;
104
- --bar: #0a0b0d;
98
+ --bg: #121212;
99
+ --bg-soft: #1a1a1a;
100
+ --bg-raised: #1e1e1e;
101
+ --bg-card: #1e1e1e;
102
+ --bg-hover: #282828;
103
+ --bar: #0a0a0a;
105
104
  --line: rgba(255, 255, 255, 0.07);
106
105
  --line-strong: rgba(255, 255, 255, 0.14);
107
- --text: #eceef1;
108
- --muted: #959ba6;
106
+ --text: #ececec;
107
+ --muted: #9e9e9e;
109
108
  --accent: #ff7a5c;
110
109
  --accent-hover: #ff9177;
111
110
  --accent-fill: #d23c1b;
112
111
  --accent-fill-hover: #b93314;
113
112
  --accent-soft: rgba(255, 122, 92, 0.16);
114
113
  --accent-text: #ff9a80;
115
- --ink: #eceef1;
114
+ --ink: #ececec;
116
115
  --ink-hover: #ffffff;
117
- --ink-text: #0f1114;
116
+ --ink-text: #121212;
118
117
  --ok: #3ddc84;
119
118
  --ok-soft: rgba(61, 220, 132, 0.14);
120
119
  --online: #60a5fa;
@@ -130,12 +129,12 @@ ${wt}`,r),r}),this.named}async adopt(e,o){let n=(await this.open()).transaction(
130
129
  --stat-play: #60a5fa;
131
130
  --play-text: #5fe08f;
132
131
  --live-text: #3ddc84;
133
- --dock-bg: #0a0b0d;
132
+ --dock-bg: #0a0a0a;
134
133
  --dock-edge: rgba(255, 255, 255, 0.08);
135
- --edit-page: #0f0f12;
136
- --edit-bar: #17171c;
137
- --edit-screen: #1a1a1f;
138
- --edit-field: #1f1f25;
134
+ --edit-page: #121212;
135
+ --edit-bar: #1a1a1a;
136
+ --edit-screen: #1e1e1e;
137
+ --edit-field: #222222;
139
138
  --edit-line: rgba(255, 255, 255, 0.08);
140
139
  --edit-line-strong: rgba(255, 255, 255, 0.15);
141
140
  --edit-go: #d33e00;
@@ -144,25 +143,25 @@ ${wt}`,r),r}),this.named}async adopt(e,o){let n=(await this.open()).transaction(
144
143
  @media (prefers-color-scheme: dark) {
145
144
  :root:not([data-theme='light']) {
146
145
  color-scheme: dark;
147
- --bg: #0f1114;
148
- --bg-soft: #171a1f;
149
- --bg-raised: #1a1d23;
150
- --bg-card: #1a1d23;
151
- --bg-hover: #23272e;
152
- --bar: #0a0b0d;
146
+ --bg: #121212;
147
+ --bg-soft: #1a1a1a;
148
+ --bg-raised: #1e1e1e;
149
+ --bg-card: #1e1e1e;
150
+ --bg-hover: #282828;
151
+ --bar: #0a0a0a;
153
152
  --line: rgba(255, 255, 255, 0.07);
154
153
  --line-strong: rgba(255, 255, 255, 0.14);
155
- --text: #eceef1;
156
- --muted: #959ba6;
154
+ --text: #ececec;
155
+ --muted: #9e9e9e;
157
156
  --accent: #ff7a5c;
158
157
  --accent-hover: #ff9177;
159
158
  --accent-fill: #d23c1b;
160
159
  --accent-fill-hover: #b93314;
161
160
  --accent-soft: rgba(255, 122, 92, 0.16);
162
161
  --accent-text: #ff9a80;
163
- --ink: #eceef1;
162
+ --ink: #ececec;
164
163
  --ink-hover: #ffffff;
165
- --ink-text: #0f1114;
164
+ --ink-text: #121212;
166
165
  --ok: #3ddc84;
167
166
  --ok-soft: rgba(61, 220, 132, 0.14);
168
167
  --online: #60a5fa;
@@ -178,12 +177,12 @@ ${wt}`,r),r}),this.named}async adopt(e,o){let n=(await this.open()).transaction(
178
177
  --stat-play: #60a5fa;
179
178
  --play-text: #5fe08f;
180
179
  --live-text: #3ddc84;
181
- --dock-bg: #0a0b0d;
180
+ --dock-bg: #0a0a0a;
182
181
  --dock-edge: rgba(255, 255, 255, 0.08);
183
- --edit-page: #0f0f12;
184
- --edit-bar: #17171c;
185
- --edit-screen: #1a1a1f;
186
- --edit-field: #1f1f25;
182
+ --edit-page: #121212;
183
+ --edit-bar: #1a1a1a;
184
+ --edit-screen: #1e1e1e;
185
+ --edit-field: #222222;
187
186
  --edit-line: rgba(255, 255, 255, 0.08);
188
187
  --edit-line-strong: rgba(255, 255, 255, 0.15);
189
188
  --edit-go: #d33e00;
@@ -385,12 +384,33 @@ h3 {
385
384
  color: var(--danger);
386
385
  }
387
386
 
388
- .btn-small {
387
+ /* (A row's buttons are always this size: a list reads the same whatever its row can do.) */
388
+ .btn-small,
389
+ .row-actions .btn {
389
390
  min-height: 32px;
390
391
  padding: 0 12px;
391
392
  font-size: 13px;
392
393
  }
393
394
 
395
+ /* Fingers: small buttons and links stay easy to hit (padding on the links, which doesn't move anything around them). */
396
+ @media (pointer: coarse) {
397
+ .btn-small,
398
+ .row-actions .btn {
399
+ min-height: 40px;
400
+ }
401
+
402
+ .see-all,
403
+ .clear-search {
404
+ margin: -10px 0;
405
+ padding: 10px 0;
406
+ }
407
+
408
+ .label-row a,
409
+ .auth-switch a {
410
+ padding: 10px 4px;
411
+ }
412
+ }
413
+
394
414
  /* A plain file picker (a form that posts its file, like Upload files' Cover): its button is a small .btn. */
395
415
  input[type='file'] {
396
416
  max-width: 100%;
@@ -440,10 +460,14 @@ input[type='file']::file-selector-button:hover {
440
460
 
441
461
  .link-btn {
442
462
  padding: 6px 10px;
463
+ border: 0;
464
+ background: none;
443
465
  color: var(--text);
466
+ font-family: inherit;
444
467
  font-weight: 600;
445
468
  font-size: 14px;
446
469
  text-decoration: none;
470
+ cursor: pointer;
447
471
  }
448
472
 
449
473
  .link-btn:hover {
@@ -463,6 +487,7 @@ input[type='text'],
463
487
  input[type='email'],
464
488
  input[type='password'],
465
489
  input[type='search'],
490
+ input[type='number'],
466
491
  input:not([type]),
467
492
  select,
468
493
  textarea {
@@ -771,11 +796,10 @@ label.check input {
771
796
  background: var(--bar);
772
797
  }
773
798
 
799
+ /* Edge to edge at any width: the mark lines up with the sidebar's words, the account the same distance from the right. */
774
800
  .topbar-inner {
775
- max-width: var(--page);
776
- margin: 0 auto;
777
801
  height: var(--bar-h);
778
- padding: 0 var(--gutter);
802
+ padding: 0 22px;
779
803
  display: flex;
780
804
  align-items: center;
781
805
  gap: 8px;
@@ -1270,6 +1294,15 @@ button.icon-btn:hover {
1270
1294
  background: var(--accent);
1271
1295
  }
1272
1296
 
1297
+ /* The chat is open over the page: one tab is lit, Chat. */
1298
+ .tabbar:has(> button[aria-expanded='true']) > a[aria-current='page'] {
1299
+ color: var(--dock-muted);
1300
+ }
1301
+
1302
+ .tabbar:has(> button[aria-expanded='true']) > a[aria-current='page']::before {
1303
+ content: none;
1304
+ }
1305
+
1273
1306
  .tabbar > a:focus-visible,
1274
1307
  .tabbar > button:focus-visible {
1275
1308
  outline: 2px solid var(--accent);
@@ -1530,7 +1563,6 @@ body:has(.tabbar) .footer-inner {
1530
1563
  color: var(--muted);
1531
1564
  font-size: 14px;
1532
1565
  font-weight: 600;
1533
- font-variant-numeric: tabular-nums;
1534
1566
  }
1535
1567
 
1536
1568
  .see-all {
@@ -1773,6 +1805,21 @@ body:has(.tabbar) .footer-inner {
1773
1805
  left: 0;
1774
1806
  }
1775
1807
 
1808
+ @media (max-width: 560px) {
1809
+ .storenav {
1810
+ flex-wrap: wrap;
1811
+ height: auto;
1812
+ }
1813
+ .storenav-links {
1814
+ flex: 1 1 100%;
1815
+ height: 46px;
1816
+ }
1817
+ .storenav-filters,
1818
+ .storenav-random {
1819
+ height: 40px;
1820
+ }
1821
+ }
1822
+
1776
1823
  .storenav-filters {
1777
1824
  display: flex;
1778
1825
  }
@@ -1940,6 +1987,34 @@ body:has(.tabbar) .footer-inner {
1940
1987
  .store-shell > .page {
1941
1988
  padding-bottom: 56px;
1942
1989
  }
1990
+
1991
+ /* Home: Featured fills the column to the sidebar, the top bar and the window's edge; the rows under it keep the page's width. */
1992
+ .store-shell > .page.store:has(> .spotlight) {
1993
+ max-width: none;
1994
+ padding-top: 0;
1995
+ }
1996
+
1997
+ .page.store:has(> .spotlight) > * {
1998
+ max-width: calc(1680px - 2 * clamp(20px, 2.6vw, 44px));
1999
+ }
2000
+
2001
+ .page.store > .spotlight {
2002
+ max-width: none;
2003
+ margin-inline: calc(-1 * clamp(20px, 2.6vw, 44px));
2004
+ }
2005
+
2006
+ .page.store > .spotlight .spot-slide {
2007
+ border-radius: 0;
2008
+ }
2009
+
2010
+ /* Its words and dots line up with the rows under it. */
2011
+ .page.store > .spotlight .spot-info {
2012
+ padding-left: clamp(20px, 2.6vw, 44px);
2013
+ }
2014
+
2015
+ .page.store > .spotlight .spot-dots {
2016
+ right: clamp(20px, 2.6vw, 44px);
2017
+ }
1943
2018
  }
1944
2019
 
1945
2020
  .store-side {
@@ -2063,8 +2138,8 @@ body:has(.tabbar) .footer-inner {
2063
2138
  position: relative;
2064
2139
  min-width: 0;
2065
2140
  overflow: hidden;
2066
- border-radius: var(--radius-widget);
2067
- background: #0a0b0d;
2141
+ border-radius: var(--radius-art);
2142
+ background: #0a0a0a;
2068
2143
  opacity: 0;
2069
2144
  visibility: hidden;
2070
2145
  transition: opacity 0.45s ease, visibility 0s linear 0.45s;
@@ -2425,17 +2500,6 @@ body:has(.tabbar) .footer-inner {
2425
2500
  background: var(--live);
2426
2501
  }
2427
2502
 
2428
- .btn.btn-live {
2429
- border-color: transparent;
2430
- background: var(--live);
2431
- color: var(--live-ink);
2432
- font-weight: 700;
2433
- }
2434
-
2435
- .btn.btn-live:hover {
2436
- background: color-mix(in srgb, var(--live) 85%, #fff);
2437
- }
2438
-
2439
2503
  /* Every game: its title (with See all on home, Clear on a search), the filters that are on, and the grid. */
2440
2504
  .browse-head {
2441
2505
  display: flex;
@@ -2665,7 +2729,7 @@ body:has(.tabbar) .footer-inner {
2665
2729
  height: auto;
2666
2730
  margin: auto;
2667
2731
  aspect-ratio: 1;
2668
- border-radius: 22%;
2732
+ border-radius: var(--radius-art);
2669
2733
  }
2670
2734
 
2671
2735
  .app-art:has(.app-img-icon) {
@@ -2900,6 +2964,14 @@ body:has(.tabbar) .footer-inner {
2900
2964
  padding: 96px 16px;
2901
2965
  }
2902
2966
 
2967
+ .lib-empty-actions {
2968
+ display: flex;
2969
+ flex-wrap: wrap;
2970
+ justify-content: center;
2971
+ gap: 10px;
2972
+ margin-top: 20px;
2973
+ }
2974
+
2903
2975
  /* A repo whose first deploy failed, in the side: its square says so. */
2904
2976
  .lib-icon.mine-failed {
2905
2977
  color: var(--danger);
@@ -2950,13 +3022,17 @@ body:has(.tabbar) .footer-inner {
2950
3022
  place-items: center;
2951
3023
  width: clamp(22px, 10.4cqi, 36px);
2952
3024
  height: clamp(22px, 10.4cqi, 36px);
2953
- border-radius: 5px;
3025
+ border-radius: var(--radius-art);
2954
3026
  object-fit: cover;
2955
- background: var(--fill);
2956
3027
  font-size: 12px;
2957
3028
  font-weight: 800;
2958
3029
  }
2959
3030
 
3031
+ /* (A letter's square keeps its game's colour.) */
3032
+ .lib-icon:not(.gcard-letter) {
3033
+ background: var(--fill);
3034
+ }
3035
+
2960
3036
  @container side (min-width: 320px) {
2961
3037
  .lib-list a {
2962
3038
  min-height: 42px;
@@ -2964,7 +3040,6 @@ body:has(.tabbar) .footer-inner {
2964
3040
  }
2965
3041
 
2966
3042
  .lib-icon {
2967
- border-radius: 7px;
2968
3043
  font-size: 15px;
2969
3044
  }
2970
3045
  }
@@ -2993,7 +3068,7 @@ body:has(.tabbar) .footer-inner {
2993
3068
  height: clamp(220px, 27vw, 440px);
2994
3069
  aspect-ratio: auto;
2995
3070
  overflow: hidden;
2996
- border-radius: var(--radius-widget);
3071
+ border-radius: var(--radius-art);
2997
3072
  }
2998
3073
 
2999
3074
  .lib-hero::after {
@@ -3639,7 +3714,7 @@ body:has(.tabbar) .footer-inner {
3639
3714
  flex: none;
3640
3715
  width: 60px;
3641
3716
  height: 60px;
3642
- border-radius: 12px;
3717
+ border-radius: var(--radius-art);
3643
3718
  object-fit: cover;
3644
3719
  box-shadow: var(--ring);
3645
3720
  font-size: 28px;
@@ -3685,6 +3760,11 @@ body:has(.tabbar) .footer-inner {
3685
3760
  align-items: start;
3686
3761
  }
3687
3762
 
3763
+ /* A page with nothing else: More like this keeps the gap it has under the game's body. */
3764
+ .game-top + .section {
3765
+ margin-top: 44px;
3766
+ }
3767
+
3688
3768
  /* One column: Play comes first in the panel, right under the gallery. */
3689
3769
  @media (max-width: 1000px) {
3690
3770
  .game-top {
@@ -3709,7 +3789,7 @@ body:has(.tabbar) .footer-inner {
3709
3789
  overflow-x: auto;
3710
3790
  scroll-snap-type: x mandatory;
3711
3791
  scrollbar-width: none;
3712
- border-radius: var(--radius-widget);
3792
+ border-radius: var(--radius-art);
3713
3793
  background: var(--bg-soft);
3714
3794
  box-shadow: var(--ring);
3715
3795
  aspect-ratio: 16 / 9;
@@ -3750,7 +3830,7 @@ body:has(.tabbar) .footer-inner {
3750
3830
  width: 30%;
3751
3831
  height: auto;
3752
3832
  aspect-ratio: 1;
3753
- border-radius: 22%;
3833
+ border-radius: var(--radius-art);
3754
3834
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25);
3755
3835
  }
3756
3836
 
@@ -3780,7 +3860,7 @@ body:has(.tabbar) .footer-inner {
3780
3860
  place-items: center;
3781
3861
  width: 100%;
3782
3862
  aspect-ratio: 16 / 9;
3783
- border-radius: 5px;
3863
+ border-radius: var(--radius-art);
3784
3864
  color: var(--text);
3785
3865
  text-decoration: none;
3786
3866
  opacity: 0.55;
@@ -3981,6 +4061,12 @@ body:has(.tabbar) .footer-inner {
3981
4061
  list-style: none;
3982
4062
  }
3983
4063
 
4064
+ .play-pay > summary,
4065
+ a.play-pay {
4066
+ padding: 6px 0;
4067
+ text-decoration: underline;
4068
+ }
4069
+
3984
4070
  .play-pay[open] > summary {
3985
4071
  margin-bottom: 8px;
3986
4072
  }
@@ -4367,9 +4453,11 @@ body:has(.tabbar) .footer-inner {
4367
4453
  }
4368
4454
 
4369
4455
  .fold-count {
4456
+ margin-left: 4px;
4370
4457
  color: var(--muted);
4371
4458
  font-size: 14px;
4372
4459
  font-variant-numeric: tabular-nums;
4460
+ white-space: nowrap;
4373
4461
  }
4374
4462
 
4375
4463
  .fold-caret {
@@ -4549,6 +4637,11 @@ body:has(.tabbar) .footer-inner {
4549
4637
  margin: 0 0 18px;
4550
4638
  }
4551
4639
 
4640
+ /* An order: its Play or Open, then what was bought. */
4641
+ .market-order .form-row:has(+ .kv) {
4642
+ margin-bottom: 18px;
4643
+ }
4644
+
4552
4645
  .kv dt {
4553
4646
  color: var(--muted);
4554
4647
  font-size: 12px;
@@ -4960,7 +5053,7 @@ body:has(.auth) .footer {
4960
5053
  aspect-ratio: 16 / 9;
4961
5054
  object-fit: cover;
4962
5055
  border: 1px solid var(--line);
4963
- border-radius: 10px;
5056
+ border-radius: var(--radius-art);
4964
5057
  background: var(--bg-soft);
4965
5058
  }
4966
5059
 
@@ -5728,7 +5821,7 @@ label.admin-changed > span::before {
5728
5821
  .admin-game .dash-icon {
5729
5822
  width: 36px;
5730
5823
  height: 36px;
5731
- border-radius: 10px;
5824
+ border-radius: var(--radius-art);
5732
5825
  }
5733
5826
 
5734
5827
  @media (max-width: 760px) {
@@ -5901,7 +5994,7 @@ label.admin-changed > span::before {
5901
5994
  flex: none;
5902
5995
  width: 44px;
5903
5996
  height: 44px;
5904
- border-radius: 12px;
5997
+ border-radius: var(--radius-art);
5905
5998
  object-fit: cover;
5906
5999
  display: grid;
5907
6000
  place-items: center;
@@ -6415,7 +6508,7 @@ label.admin-changed > span::before {
6415
6508
  flex: none;
6416
6509
  width: 96px;
6417
6510
  height: 96px;
6418
- border-radius: 22px;
6511
+ border-radius: var(--radius-art);
6419
6512
  object-fit: cover;
6420
6513
  display: grid;
6421
6514
  place-items: center;
@@ -6569,7 +6662,7 @@ label.admin-changed > span::before {
6569
6662
 
6570
6663
  .dz-shot {
6571
6664
  position: relative;
6572
- border-radius: 14px;
6665
+ border-radius: var(--radius-art);
6573
6666
  background: var(--bg-soft);
6574
6667
  cursor: grab;
6575
6668
  user-select: none;
@@ -6583,7 +6676,7 @@ label.admin-changed > span::before {
6583
6676
  width: 100%;
6584
6677
  aspect-ratio: 16 / 9;
6585
6678
  object-fit: cover;
6586
- border-radius: 14px;
6679
+ border-radius: var(--radius-art);
6587
6680
  pointer-events: none;
6588
6681
  }
6589
6682
 
@@ -6752,7 +6845,9 @@ label.admin-changed > span::before {
6752
6845
 
6753
6846
  .sell-modes label {
6754
6847
  display: inline-flex;
6848
+ flex-direction: row;
6755
6849
  align-items: center;
6850
+ justify-content: center;
6756
6851
  min-height: 32px;
6757
6852
  padding: 0 14px;
6758
6853
  border-radius: 999px;
@@ -6779,13 +6874,107 @@ label.admin-changed > span::before {
6779
6874
  outline-offset: 1px;
6780
6875
  }
6781
6876
 
6782
- .sell-form:has(input[name='mode'][value='free']:checked) :is(.sell-fixed, .sell-pwyw),
6877
+ .sell-form:has(input[name='mode'][value='free']:checked) :is(.sell-fixed, .sell-pwyw, .sell-setup),
6783
6878
  .sell-form:has(input[name='mode'][value='fixed']:checked) .sell-pwyw,
6784
6879
  .sell-form:has(input[name='mode'][value='pwyw']:checked) .sell-fixed,
6785
6880
  .sell-form:not(:has(select[name='license'] option[value='custom']:checked)) .sell-custom {
6786
6881
  display: none;
6787
6882
  }
6788
6883
 
6884
+ /* Sell: every card is its heading and then its contents, in the same two columns, so they all line up down the page. */
6885
+ .sell-card {
6886
+ display: grid;
6887
+ grid-template-columns: 160px minmax(0, 480px);
6888
+ gap: 12px 32px;
6889
+ align-items: start;
6890
+ }
6891
+
6892
+ .sell-card h2,
6893
+ .page .sell-card h2 {
6894
+ display: flex;
6895
+ flex-wrap: wrap;
6896
+ align-items: center;
6897
+ gap: 8px;
6898
+ margin: 0;
6899
+ line-height: 38px;
6900
+ }
6901
+
6902
+ .sell-body {
6903
+ min-width: 0;
6904
+ }
6905
+
6906
+ .sell-body > * {
6907
+ margin: 0;
6908
+ }
6909
+
6910
+ .sell-body > * + * {
6911
+ margin-top: 14px;
6912
+ }
6913
+
6914
+ .sell-body > p:first-child {
6915
+ line-height: 38px;
6916
+ }
6917
+
6918
+ .sell-body > .sell-fine {
6919
+ margin-top: 2px;
6920
+ font-size: 13px;
6921
+ }
6922
+
6923
+ .sell-body .stack-form {
6924
+ max-width: none;
6925
+ }
6926
+
6927
+ .sell-body :is(select, .inline-form input) {
6928
+ border-radius: 10px;
6929
+ background-color: var(--bg);
6930
+ }
6931
+
6932
+ .sell-body .inline-form label {
6933
+ color: var(--text);
6934
+ }
6935
+
6936
+ .sell-body .sell-modes {
6937
+ display: flex;
6938
+ align-self: stretch;
6939
+ }
6940
+
6941
+ .sell-body .sell-modes label {
6942
+ flex: 1 1 auto;
6943
+ }
6944
+
6945
+ .sell-actions,
6946
+ .sell-setup {
6947
+ display: flex;
6948
+ flex-wrap: wrap;
6949
+ align-items: center;
6950
+ gap: 8px;
6951
+ }
6952
+
6953
+ .sell-setup {
6954
+ gap: 8px 12px;
6955
+ }
6956
+
6957
+ .sell-actions form {
6958
+ margin: 0;
6959
+ }
6960
+
6961
+ .sell-body .inline-form {
6962
+ margin-bottom: 0;
6963
+ }
6964
+
6965
+ @media (max-width: 720px) {
6966
+ .sell-card {
6967
+ grid-template-columns: minmax(0, 1fr);
6968
+ gap: 8px;
6969
+ }
6970
+
6971
+ .sell-card h2,
6972
+ .page .sell-card h2,
6973
+ .sell-body > p:first-child {
6974
+ line-height: 1.4;
6975
+ }
6976
+ }
6977
+
6789
6978
  .sell-now {
6790
6979
  display: flex;
6791
6980
  flex-wrap: wrap;
@@ -7213,8 +7402,7 @@ body.play {
7213
7402
  body.play .ow-menu,
7214
7403
  body.play .ow-social-body,
7215
7404
  body.play .ow-chat-log,
7216
- body.play .ow-players,
7217
- body.play .ow-badges {
7405
+ body.play .ow-players {
7218
7406
  touch-action: pan-y;
7219
7407
  }
7220
7408
 
@@ -7744,9 +7932,11 @@ body.play input {
7744
7932
  padding-bottom: 16px;
7745
7933
  }
7746
7934
 
7935
+ /* Its first line level with the head's buttons. */
7747
7936
  .ow-menu-title {
7748
7937
  flex: 1;
7749
7938
  min-width: 0;
7939
+ padding-top: 4px;
7750
7940
  }
7751
7941
 
7752
7942
  .ow-menu-head h2 {
@@ -7799,8 +7989,7 @@ body.play input {
7799
7989
  }
7800
7990
 
7801
7991
  .ow-section[hidden],
7802
- .ow-setting[hidden],
7803
- .ow-guest[hidden] {
7992
+ .ow-setting[hidden] {
7804
7993
  display: none;
7805
7994
  }
7806
7995
 
@@ -7863,11 +8052,14 @@ body.play input {
7863
8052
  overflow-y: auto;
7864
8053
  }
7865
8054
 
8055
+ /* A row with more buttons than room takes a second line for them: the name stays readable. */
7866
8056
  .ow-players li {
7867
8057
  display: flex;
8058
+ flex-wrap: wrap;
7868
8059
  align-items: center;
7869
- gap: 10px;
8060
+ gap: 2px 10px;
7870
8061
  min-height: 40px;
8062
+ padding: 2px 0;
7871
8063
  font-size: 14px;
7872
8064
  }
7873
8065
 
@@ -7903,8 +8095,10 @@ a.ow-player-name:focus-visible {
7903
8095
  }
7904
8096
 
7905
8097
  .ow-player-actions {
7906
- flex: none;
8098
+ flex: 0 1 auto;
7907
8099
  display: flex;
8100
+ flex-wrap: wrap;
8101
+ justify-content: flex-end;
7908
8102
  gap: 6px;
7909
8103
  margin-left: auto;
7910
8104
  }
@@ -8046,17 +8240,6 @@ a.ow-player-name:focus-visible {
8046
8240
  white-space: nowrap;
8047
8241
  }
8048
8242
 
8049
- .ow-server {
8050
- display: flex;
8051
- flex-wrap: wrap;
8052
- align-items: center;
8053
- justify-content: space-between;
8054
- gap: 4px 12px;
8055
- margin-top: 6px;
8056
- color: var(--muted);
8057
- font-size: 13px;
8058
- }
8059
-
8060
8243
  .ow-text-btn {
8061
8244
  padding: 0;
8062
8245
  border: 0;
@@ -8120,24 +8303,6 @@ button.ow-setting-icon:hover {
8120
8303
  outline-offset: 2px;
8121
8304
  }
8122
8305
 
8123
- .ow-guest {
8124
- margin: 0;
8125
- padding: 12px 0;
8126
- border-top: 1px solid var(--line);
8127
- color: var(--muted);
8128
- font-size: 13px;
8129
- }
8130
-
8131
- .ow-guest a {
8132
- color: var(--accent-text);
8133
- font-weight: 600;
8134
- text-decoration: none;
8135
- }
8136
-
8137
- .ow-guest a:hover {
8138
- text-decoration: underline;
8139
- }
8140
-
8141
8306
  .ow-menu-foot {
8142
8307
  display: flex;
8143
8308
  gap: 8px;
@@ -8307,113 +8472,47 @@ button.ow-setting-icon:hover {
8307
8472
  letter-spacing: 0.06em;
8308
8473
  }
8309
8474
 
8310
- .ow-badges {
8311
- list-style: none;
8312
- margin: 0;
8313
- padding: 0;
8314
- display: flex;
8315
- flex-wrap: wrap;
8316
- gap: 8px;
8475
+ .ow-toast .ow-badge-blank {
8476
+ display: grid;
8477
+ place-items: center;
8478
+ background: var(--accent-soft);
8479
+ color: var(--accent-hover);
8480
+ font-size: 20px;
8317
8481
  }
8318
8482
 
8319
- .ow-badge-item {
8320
- display: block;
8321
- padding: 0;
8322
- border: 0;
8323
- border-radius: 50%;
8324
- background: none;
8325
- cursor: pointer;
8326
- opacity: 0.35;
8327
- filter: grayscale(1);
8328
- transition: opacity 0.15s ease;
8483
+ /* loading card shown until the game's page has loaded */
8484
+ .ow-loading {
8485
+ position: fixed;
8486
+ inset: var(--ow-vv-top, 0px) 0 var(--ow-vv-bottom, 0px);
8487
+ z-index: 15;
8488
+ display: grid;
8489
+ place-items: center;
8490
+ overflow: hidden;
8491
+ /* Always dark, whatever the theme: the game's cover is darkened behind it. */
8492
+ background: #0e0f12;
8493
+ color: #fff;
8494
+ transition: opacity 0.35s ease;
8329
8495
  }
8330
8496
 
8331
- .ow-badge-item:hover {
8332
- opacity: 0.55;
8497
+ /* Gone once faded, not just see-through: Chrome (GPU) left most of the game's frame unpainted under the invisible card and
8498
+ its blurred cover, the top strip alone showing until a reload. */
8499
+ .ow-loading.ow-hidden {
8500
+ opacity: 0;
8501
+ visibility: hidden;
8502
+ pointer-events: none;
8503
+ transition: opacity 0.35s ease, visibility 0s linear 0.35s;
8333
8504
  }
8334
8505
 
8335
- .ow-badge-item.ow-earned {
8336
- opacity: 1;
8337
- filter: none;
8506
+ /* A page that never ran: what's in the frame (a device's filter page, an error) stays in view and can be tapped, the card below it. */
8507
+ .ow-loading.ow-loading-clear {
8508
+ align-items: end;
8509
+ background: none;
8510
+ pointer-events: none;
8338
8511
  }
8339
8512
 
8340
- .ow-badge-item:focus-visible {
8341
- outline: 2px solid var(--accent-hover);
8342
- outline-offset: 2px;
8343
- }
8344
-
8345
- .ow-badge-icon {
8346
- display: block;
8347
- width: 40px;
8348
- height: 40px;
8349
- border-radius: 50%;
8350
- object-fit: cover;
8351
- }
8352
-
8353
- .ow-badge-blank {
8354
- display: grid;
8355
- place-items: center;
8356
- background: var(--fill-strong);
8357
- color: var(--text);
8358
- font-size: 18px;
8359
- }
8360
-
8361
- .ow-earned .ow-badge-blank,
8362
- .ow-toast .ow-badge-blank {
8363
- background: var(--accent-soft);
8364
- color: var(--accent-hover);
8365
- }
8366
-
8367
- .ow-toast .ow-badge-blank {
8368
- font-size: 20px;
8369
- }
8370
-
8371
- .ow-badge-caption {
8372
- margin: 10px 0 0;
8373
- color: var(--muted);
8374
- font-size: 13px;
8375
- line-height: 1.45;
8376
- }
8377
-
8378
- .ow-badge-caption b {
8379
- margin-right: 2px;
8380
- color: var(--text);
8381
- font-weight: 600;
8382
- }
8383
-
8384
- /* loading card shown until the game's page has loaded */
8385
- .ow-loading {
8386
- position: fixed;
8387
- inset: var(--ow-vv-top, 0px) 0 var(--ow-vv-bottom, 0px);
8388
- z-index: 15;
8389
- display: grid;
8390
- place-items: center;
8391
- overflow: hidden;
8392
- /* Always dark, whatever the theme: the game's cover is darkened behind it. */
8393
- background: #0e0f12;
8394
- color: #fff;
8395
- transition: opacity 0.35s ease;
8396
- }
8397
-
8398
- /* Gone once faded, not just see-through: Chrome (GPU) left most of the game's frame unpainted under the invisible card and
8399
- its blurred cover, the top strip alone showing until a reload. */
8400
- .ow-loading.ow-hidden {
8401
- opacity: 0;
8402
- visibility: hidden;
8403
- pointer-events: none;
8404
- transition: opacity 0.35s ease, visibility 0s linear 0.35s;
8405
- }
8406
-
8407
- /* A page that never ran: what's in the frame (a device's filter page, an error) stays in view and can be tapped, the card below it. */
8408
- .ow-loading.ow-loading-clear {
8409
- align-items: end;
8410
- background: none;
8411
- pointer-events: none;
8412
- }
8413
-
8414
- .ow-loading-clear .ow-loading-cover,
8415
- .ow-loading-clear .ow-loading-icon {
8416
- display: none;
8513
+ .ow-loading-clear .ow-loading-cover,
8514
+ .ow-loading-clear .ow-loading-icon {
8515
+ display: none;
8417
8516
  }
8418
8517
 
8419
8518
  .ow-loading-clear .ow-loading-inner {
@@ -8469,7 +8568,7 @@ button.ow-setting-icon:hover {
8469
8568
  .ow-loading-icon {
8470
8569
  width: 120px;
8471
8570
  height: 120px;
8472
- border-radius: 28px;
8571
+ border-radius: var(--radius-art);
8473
8572
  object-fit: cover;
8474
8573
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.12), 0 24px 60px rgba(0, 0, 0, 0.6);
8475
8574
  }
@@ -8611,6 +8710,26 @@ button.ow-setting-icon:hover {
8611
8710
  .ow-menu {
8612
8711
  padding: 16px;
8613
8712
  }
8713
+ .ow-menu-title {
8714
+ padding-top: 8px;
8715
+ }
8716
+ }
8717
+
8718
+ /* The narrowest phones: rows wrap instead of pushing the menu sideways. */
8719
+ @media (max-width: 360px) {
8720
+ .ow-menu {
8721
+ padding: 12px;
8722
+ overflow-x: hidden;
8723
+ }
8724
+ .ow-menu-foot {
8725
+ flex-wrap: wrap;
8726
+ }
8727
+ .ow-menu-foot .ow-btn {
8728
+ flex: 1 1 100%;
8729
+ }
8730
+ .ow-section-head {
8731
+ flex-wrap: wrap;
8732
+ }
8614
8733
  }
8615
8734
 
8616
8735
  @media (prefers-reduced-motion: reduce) {
@@ -8654,7 +8773,7 @@ button.ow-setting-icon:hover {
8654
8773
  place-items: center;
8655
8774
  width: 44px;
8656
8775
  height: 44px;
8657
- border-radius: 12px;
8776
+ border-radius: var(--radius-art);
8658
8777
  object-fit: cover;
8659
8778
  color: #fff;
8660
8779
  font-size: 18px;
@@ -8996,7 +9115,7 @@ a.pager-btn:hover {
8996
9115
  place-items: center;
8997
9116
  width: 44px;
8998
9117
  height: 44px;
8999
- border-radius: 12px;
9118
+ border-radius: var(--radius-art);
9000
9119
  object-fit: cover;
9001
9120
  color: #fff;
9002
9121
  font-size: 18px;
@@ -10437,6 +10556,38 @@ body.play .ow-card {
10437
10556
  border-radius: 999px;
10438
10557
  }
10439
10558
 
10559
+ /* Enter presses the button: one small mark, for keyboards. */
10560
+ .ow-lobby-keys .ow-lobby-action:not(:disabled)::after {
10561
+ content: '\\21B5' / '';
10562
+ margin-left: 8px;
10563
+ font-size: 0.85em;
10564
+ font-weight: 400;
10565
+ opacity: 0.65;
10566
+ }
10567
+
10568
+ /* A press goes through to the target under the button, inside the game's frame (shell/lobby.ts, link): it is then the player's gesture
10569
+ there. The strip goes through whole, and the card's own parts stay on this page. */
10570
+ .ow-lobby.ow-lobby-through,
10571
+ .ow-lobby-through > .ow-lobby-card,
10572
+ .ow-lobby-through > .ow-lobby-bar,
10573
+ .ow-lobby-through .ow-lobby-actions,
10574
+ .ow-lobby-through .ow-lobby-action {
10575
+ pointer-events: none;
10576
+ }
10577
+
10578
+ .ow-lobby-through > .ow-lobby-card > :not(.ow-lobby-actions),
10579
+ .ow-lobby-through .ow-lobby-invite {
10580
+ pointer-events: auto;
10581
+ }
10582
+
10583
+ .ow-lobby-hover.ow-btn {
10584
+ background: rgba(255, 255, 255, 0.16);
10585
+ }
10586
+
10587
+ .ow-lobby-hover.ow-btn-primary {
10588
+ background: var(--accent-fill-hover);
10589
+ }
10590
+
10440
10591
  /* 3, 2, 1. */
10441
10592
  .ow-lobby-countdown {
10442
10593
  position: fixed;
@@ -10846,6 +10997,32 @@ body.ow-calm .ow-fold-caret {
10846
10997
  overflow-wrap: anywhere;
10847
10998
  }
10848
10999
 
11000
+ .upload-kinds dl {
11001
+ margin: 0;
11002
+ font-size: 14px;
11003
+ }
11004
+
11005
+ .upload-kinds dl > div {
11006
+ display: grid;
11007
+ grid-template-columns: 7.5em minmax(0, 1fr);
11008
+ gap: 12px;
11009
+ padding: 7px 0;
11010
+ border-top: 1px solid var(--line);
11011
+ }
11012
+
11013
+ .upload-kinds dt {
11014
+ font-weight: 600;
11015
+ }
11016
+
11017
+ .upload-kinds dd {
11018
+ margin: 0;
11019
+ color: var(--muted);
11020
+ }
11021
+
11022
+ .upload-kinds .fine {
11023
+ margin: 7px 0 12px;
11024
+ }
11025
+
10849
11026
  .upload-form .flash {
10850
11027
  margin: 0;
10851
11028
  }
@@ -11363,53 +11540,164 @@ body.ow-calm .ow-fold-caret {
11363
11540
 
11364
11541
  /* ------------------------------------------------------------ Create: describe a game, your games, Developers (create.ts) */
11365
11542
 
11366
- /* The top: the pitch, big, and the box under it, beside a game being made; on phones the game comes after. */
11367
- .create-intro {
11368
- display: grid;
11369
- grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
11543
+ /*
11544
+ * Create is a workbench in Edit's shape: the game's screen with the box under it (what Edit opens to), beside the games to start from.
11545
+ * The screen stays 16:9 and shrinks with a short window, so the box never falls below the fold; the rail is as tall as the screen and
11546
+ * box, and scrolls. On phones the rail is a strip under the box.
11547
+ */
11548
+ .make.page {
11549
+ padding-top: clamp(14px, 2.4vh, 28px);
11550
+ }
11551
+
11552
+ .make-head {
11553
+ display: flex;
11554
+ flex-wrap: wrap;
11370
11555
  align-items: center;
11371
- gap: clamp(28px, 4vw, 64px);
11372
- margin: clamp(8px, 4vh, 48px) 0 clamp(48px, 7vw, 96px);
11556
+ justify-content: space-between;
11557
+ gap: 10px 24px;
11558
+ margin-bottom: clamp(14px, 2vh, 22px);
11373
11559
  }
11374
11560
 
11375
- .create-pitch h1 {
11376
- margin: 0 0 30px;
11377
- font-size: clamp(44px, 5.2vw, 78px);
11561
+ .page .make-head h1 {
11562
+ margin: 0;
11563
+ font-size: clamp(30px, 3.9vw, 56px);
11378
11564
  font-weight: 700;
11379
- line-height: 0.98;
11565
+ line-height: 1;
11380
11566
  letter-spacing: -0.045em;
11381
11567
  }
11382
11568
 
11383
- .create-pitch h1 span {
11384
- display: block;
11569
+ .make-head h1 span {
11570
+ margin-left: 0.24em;
11385
11571
  color: var(--accent-text);
11386
11572
  }
11387
11573
 
11388
- .create-reel {
11574
+ .make-bench {
11575
+ display: grid;
11576
+ grid-template-columns: minmax(0, min(62%, max(520px, calc((100svh - 330px) * 16 / 9)))) minmax(0, 1fr);
11577
+ gap: clamp(16px, 1.8vw, 26px);
11578
+ }
11579
+
11580
+ .make-screen {
11581
+ position: relative;
11389
11582
  overflow: hidden;
11390
11583
  aspect-ratio: 16 / 9;
11391
- border-radius: var(--radius-widget);
11392
- background: #121318;
11393
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12), 0 30px 70px rgba(0, 0, 0, 0.18);
11584
+ border: 1px solid var(--line);
11585
+ border-radius: var(--radius-art);
11586
+ background: var(--edit-screen);
11587
+ }
11588
+
11589
+ /* The new game's empty place, as in Edit's first frame. */
11590
+ .make-empty {
11591
+ position: absolute;
11592
+ inset: 0;
11593
+ display: flex;
11594
+ flex-direction: column;
11595
+ align-items: center;
11596
+ justify-content: center;
11597
+ gap: 16px;
11598
+ color: var(--muted);
11599
+ }
11600
+
11601
+ .make-slot {
11602
+ display: grid;
11603
+ place-items: center;
11604
+ width: 84px;
11605
+ height: 84px;
11606
+ box-sizing: border-box;
11607
+ border: 2px dashed var(--edit-line-strong);
11608
+ border-radius: var(--radius-art);
11609
+ }
11610
+
11611
+ .make-slot svg {
11612
+ width: 30px;
11613
+ height: 30px;
11614
+ }
11615
+
11616
+ .make-empty strong {
11617
+ font-size: clamp(18px, 2vw, 26px);
11618
+ font-weight: 650;
11619
+ letter-spacing: -0.02em;
11394
11620
  }
11395
11621
 
11396
- .create-reel video,
11397
- .create-reel img {
11622
+ .make-shot {
11623
+ position: absolute;
11624
+ inset: 0;
11398
11625
  display: block;
11399
11626
  width: 100%;
11400
11627
  height: 100%;
11401
11628
  object-fit: cover;
11402
11629
  }
11403
11630
 
11404
- @media (max-width: 900px) {
11405
- .create-intro {
11406
- grid-template-columns: minmax(0, 1fr);
11407
- }
11631
+ .make-shot[hidden],
11632
+ .make-picked[hidden] {
11633
+ display: none;
11634
+ }
11635
+
11636
+ .make-picked {
11637
+ position: absolute;
11638
+ inset: auto 0 0;
11639
+ display: flex;
11640
+ align-items: center;
11641
+ gap: 12px;
11642
+ margin: 0;
11643
+ padding: 56px 18px 16px;
11644
+ background: linear-gradient(transparent, rgba(0, 0, 0, 0.82));
11645
+ color: #fff;
11646
+ }
11647
+
11648
+ .make-picked strong {
11649
+ overflow: hidden;
11650
+ min-width: 0;
11651
+ font-size: clamp(18px, 2.1vw, 30px);
11652
+ font-weight: 700;
11653
+ letter-spacing: -0.03em;
11654
+ text-overflow: ellipsis;
11655
+ white-space: nowrap;
11656
+ }
11657
+
11658
+ .make-remix-mark {
11659
+ display: inline-flex;
11660
+ flex: none;
11661
+ align-items: center;
11662
+ gap: 5px;
11663
+ padding: 4px 10px 4px 7px;
11664
+ border-radius: 999px;
11665
+ background: var(--play-fill);
11666
+ color: var(--play-ink);
11667
+ font-size: 13px;
11668
+ font-weight: 700;
11669
+ }
11670
+
11671
+ .make-remix-mark svg {
11672
+ width: 15px;
11673
+ height: 15px;
11674
+ }
11675
+
11676
+ .make-picked .btn {
11677
+ flex: none;
11678
+ margin-left: auto;
11679
+ border-color: rgba(255, 255, 255, 0.34);
11680
+ background: rgba(0, 0, 0, 0.35);
11681
+ color: #fff;
11408
11682
  }
11409
11683
 
11410
- /* The box: a sheet raised off the wallpaper, the one thing to do here. */
11684
+ .make-picked .btn:hover {
11685
+ background: rgba(0, 0, 0, 0.6);
11686
+ }
11687
+
11688
+ .make-problem {
11689
+ margin: 10px 0 0;
11690
+ }
11691
+
11692
+ /* The box: under the game, the one thing to do here. */
11411
11693
  .create-box {
11694
+ display: grid;
11695
+ grid-template-columns: minmax(0, 1fr) auto;
11696
+ align-items: end;
11697
+ gap: 8px;
11412
11698
  width: 100%;
11699
+ margin-top: 10px;
11700
+ padding: 8px;
11413
11701
  border: 1px solid var(--line);
11414
11702
  border-radius: var(--radius-widget);
11415
11703
  background: var(--bg-raised);
@@ -11426,14 +11714,13 @@ body.ow-calm .ow-fold-caret {
11426
11714
  .create-box textarea {
11427
11715
  display: block;
11428
11716
  width: 100%;
11429
- min-height: 150px;
11430
- max-height: 42vh;
11431
- padding: 24px 26px 8px;
11717
+ min-height: 56px;
11718
+ max-height: 24vh;
11719
+ padding: 8px 10px;
11432
11720
  border: 0;
11433
- border-radius: var(--radius-widget);
11434
11721
  background: none;
11435
11722
  color: var(--text);
11436
- font: 500 clamp(18px, 2vw, 22px) / 1.4 var(--font);
11723
+ font: 500 clamp(16px, 1.3vw, 18px) / 1.4 var(--font);
11437
11724
  resize: none;
11438
11725
  }
11439
11726
 
@@ -11446,22 +11733,16 @@ body.ow-calm .ow-fold-caret {
11446
11733
  opacity: 1;
11447
11734
  }
11448
11735
 
11449
- .create-box-foot {
11450
- display: flex;
11451
- justify-content: flex-end;
11452
- padding: 0 14px 14px;
11453
- }
11454
-
11455
11736
  .btn.create-go {
11456
- gap: 10px;
11457
- min-width: 160px;
11458
- min-height: 54px;
11459
- padding: 0 26px;
11737
+ gap: 8px;
11738
+ min-width: 132px;
11739
+ min-height: 52px;
11740
+ padding: 0 20px;
11460
11741
  border: 0;
11461
- border-radius: 18px;
11742
+ border-radius: 10px;
11462
11743
  background: var(--play-fill);
11463
11744
  color: var(--play-ink);
11464
- font-size: 18px;
11745
+ font-size: 17px;
11465
11746
  font-weight: 700;
11466
11747
  }
11467
11748
 
@@ -11485,25 +11766,175 @@ body.ow-calm .ow-fold-caret {
11485
11766
  flex-wrap: wrap;
11486
11767
  align-items: center;
11487
11768
  gap: 12px;
11488
- margin: 22px 0 0;
11769
+ margin: 0;
11489
11770
  color: var(--muted);
11490
11771
  font-size: 14px;
11491
11772
  }
11492
11773
 
11493
- /* Signed in: the creator's games, under the box. */
11494
- .create-page .create-games {
11495
- margin: 0 0 clamp(40px, 5vw, 64px);
11496
- }
11497
-
11498
- .create-home {
11499
- width: 100%;
11500
- max-width: var(--page);
11501
- margin: 0 auto;
11502
- padding: 0 var(--gutter) 56px;
11774
+ /* The games to start from: as tall as the screen and box, however many there are. */
11775
+ .make-rail {
11776
+ display: flex;
11777
+ flex-direction: column;
11778
+ min-width: 0;
11779
+ contain: size;
11503
11780
  }
11504
11781
 
11505
- .create-home > .section {
11506
- margin-bottom: clamp(72px, 9vw, 128px);
11782
+ .make-rail-head {
11783
+ display: flex;
11784
+ flex: none;
11785
+ align-items: center;
11786
+ justify-content: space-between;
11787
+ gap: 12px;
11788
+ margin-bottom: 12px;
11789
+ }
11790
+
11791
+ .make-rail-head h2 {
11792
+ flex: none;
11793
+ margin: 0;
11794
+ font-size: 18px;
11795
+ font-weight: 700;
11796
+ letter-spacing: -0.02em;
11797
+ }
11798
+
11799
+ .make-rail-head input {
11800
+ flex: 0 1 200px;
11801
+ min-width: 0;
11802
+ height: 34px;
11803
+ padding: 0 12px;
11804
+ border: 1px solid var(--line);
11805
+ border-radius: 999px;
11806
+ background: var(--bg-raised);
11807
+ color: var(--text);
11808
+ font: 500 14px var(--font);
11809
+ }
11810
+
11811
+ .make-rail-head input[hidden] {
11812
+ display: none;
11813
+ }
11814
+
11815
+ .make-tiles {
11816
+ display: grid;
11817
+ flex: 1;
11818
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
11819
+ align-content: start;
11820
+ gap: 14px 12px;
11821
+ min-height: 0;
11822
+ margin: 0;
11823
+ padding: 3px 6px 3px 3px;
11824
+ overflow-y: auto;
11825
+ list-style: none;
11826
+ scrollbar-width: thin;
11827
+ }
11828
+
11829
+ .make-tiles li[hidden] {
11830
+ display: none;
11831
+ }
11832
+
11833
+ .make-tile {
11834
+ display: block;
11835
+ color: var(--muted);
11836
+ font-size: 13px;
11837
+ font-weight: 600;
11838
+ text-decoration: none;
11839
+ }
11840
+
11841
+ .make-tile > span:last-child {
11842
+ display: block;
11843
+ overflow: hidden;
11844
+ margin-top: 6px;
11845
+ text-overflow: ellipsis;
11846
+ white-space: nowrap;
11847
+ }
11848
+
11849
+ .make-tile-art {
11850
+ display: grid;
11851
+ place-items: center;
11852
+ overflow: hidden;
11853
+ aspect-ratio: 16 / 9;
11854
+ border-radius: var(--radius-art);
11855
+ background: var(--edit-screen);
11856
+ transition: box-shadow 0.12s ease;
11857
+ }
11858
+
11859
+ .make-tile-art img {
11860
+ display: block;
11861
+ width: 100%;
11862
+ height: 100%;
11863
+ object-fit: cover;
11864
+ }
11865
+
11866
+ .make-tile-blank {
11867
+ box-sizing: border-box;
11868
+ border: 1.5px dashed var(--edit-line-strong);
11869
+ color: var(--muted);
11870
+ }
11871
+
11872
+ .make-tile-blank svg {
11873
+ width: 26px;
11874
+ height: 26px;
11875
+ }
11876
+
11877
+ .make-tile:hover {
11878
+ color: var(--text);
11879
+ }
11880
+
11881
+ .make-tile:hover .make-tile-art {
11882
+ box-shadow: 0 0 0 2px var(--text);
11883
+ }
11884
+
11885
+ .make-tile[aria-current] {
11886
+ color: var(--text);
11887
+ }
11888
+
11889
+ .make-tile[aria-current] .make-tile-art {
11890
+ box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent);
11891
+ }
11892
+
11893
+ @media (max-width: 900px) {
11894
+ .make-head h1 span {
11895
+ display: block;
11896
+ margin: 2px 0 0;
11897
+ }
11898
+
11899
+ .make-bench {
11900
+ grid-template-columns: minmax(0, 1fr);
11901
+ }
11902
+
11903
+ .make-rail {
11904
+ contain: none;
11905
+ }
11906
+
11907
+ .make-tiles {
11908
+ display: flex;
11909
+ gap: 10px;
11910
+ margin: 0 calc(var(--gutter) * -1);
11911
+ padding: 3px var(--gutter) 8px;
11912
+ overflow: auto hidden;
11913
+ }
11914
+
11915
+ .make-tiles li {
11916
+ flex: 0 0 148px;
11917
+ }
11918
+
11919
+ .btn.create-go {
11920
+ min-width: 0;
11921
+ }
11922
+ }
11923
+
11924
+ /* Signed in: the creator's games, under the box. */
11925
+ .create-page .create-games {
11926
+ margin: 0 0 clamp(40px, 5vw, 64px);
11927
+ }
11928
+
11929
+ .create-home {
11930
+ width: 100%;
11931
+ max-width: var(--page);
11932
+ margin: 0 auto;
11933
+ padding: 0 var(--gutter) 56px;
11934
+ }
11935
+
11936
+ .create-home > .section {
11937
+ margin-bottom: clamp(72px, 9vw, 128px);
11507
11938
  }
11508
11939
 
11509
11940
  .create-home .section-head {
@@ -11605,7 +12036,7 @@ html.is-launching {
11605
12036
  height: 84px;
11606
12037
  box-sizing: border-box;
11607
12038
  border: 2px dashed var(--edit-line-strong);
11608
- border-radius: 20px;
12039
+ border-radius: var(--radius-art);
11609
12040
  color: var(--muted);
11610
12041
  }
11611
12042
 
@@ -11616,6 +12047,29 @@ html.is-launching {
11616
12047
  letter-spacing: -0.02em;
11617
12048
  }
11618
12049
 
12050
+ /* A remix: the game itself, and the word for what's happening over it. */
12051
+ .create-launch-stage.is-remix {
12052
+ position: relative;
12053
+ overflow: hidden;
12054
+ }
12055
+
12056
+ .create-launch-stage.is-remix img {
12057
+ position: absolute;
12058
+ inset: 0;
12059
+ width: 100%;
12060
+ height: 100%;
12061
+ object-fit: cover;
12062
+ opacity: 0.42;
12063
+ }
12064
+
12065
+ .create-launch-stage.is-remix strong {
12066
+ position: relative;
12067
+ color: #fff;
12068
+ font-size: clamp(40px, 7vw, 96px);
12069
+ font-weight: 800;
12070
+ letter-spacing: -0.045em;
12071
+ }
12072
+
11619
12073
  /* The box under it, where the description lands. */
11620
12074
  .create-launch-ask {
11621
12075
  display: flex;
@@ -11890,6 +12344,30 @@ html.is-launching {
11890
12344
  height: 30px;
11891
12345
  }
11892
12346
 
12347
+ /* Remix isn't an upload: its own row, with the dashed edge of the place a game is about to go. */
12348
+ .up-choices:has(.up-choice-remix) {
12349
+ grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
12350
+ }
12351
+
12352
+ .up-choice-remix {
12353
+ grid-column: 1 / -1;
12354
+ }
12355
+
12356
+ .up-choice-remix .up-choice {
12357
+ border: 1.5px dashed var(--accent);
12358
+ }
12359
+
12360
+ .up-choice-remix .up-choice:hover {
12361
+ border-style: solid;
12362
+ border-color: var(--accent);
12363
+ background: var(--accent-soft);
12364
+ }
12365
+
12366
+ .up-choice-remix .up-choice > svg:first-child,
12367
+ .up-choice-remix .up-chevron {
12368
+ color: var(--accent-text);
12369
+ }
12370
+
11893
12371
  .up-choice-text {
11894
12372
  display: flex;
11895
12373
  flex-direction: column;
@@ -12023,14 +12501,12 @@ html.is-launching {
12023
12501
  margin: 0 0 8px;
12024
12502
  }
12025
12503
 
12026
- /* Upload's helper, bottom right: a speech bubble beside a little character standing on the footer's line (the page's 24px bottom
12027
- padding and the footer's 56px margin, taken back). It hops in, then idles (its SVG breathes, blinks and looks around). */
12504
+ /* Upload's helper, bottom right: a card that sits just above the footer's line. */
12028
12505
  .up-buddy {
12029
12506
  display: flex;
12030
12507
  align-items: flex-end;
12031
12508
  justify-content: flex-end;
12032
- gap: 4px;
12033
- margin: 40px 0 -80px;
12509
+ margin: 40px 0 -16px;
12034
12510
  pointer-events: none;
12035
12511
  }
12036
12512
 
@@ -12041,7 +12517,6 @@ html.is-launching {
12041
12517
  .up-buddy-bubble {
12042
12518
  position: relative;
12043
12519
  width: min(380px, 100%);
12044
- margin-bottom: 64px;
12045
12520
  padding: 14px 16px;
12046
12521
  border: 1px solid var(--line);
12047
12522
  border-radius: 16px;
@@ -12052,20 +12527,6 @@ html.is-launching {
12052
12527
  animation: up-bubble 0.32s 0.45s cubic-bezier(0.2, 1.4, 0.4, 1) both;
12053
12528
  }
12054
12529
 
12055
- /* Its tail, pointing at the character's head. */
12056
- .up-buddy-bubble::after {
12057
- position: absolute;
12058
- right: -7px;
12059
- bottom: 18px;
12060
- width: 12px;
12061
- height: 12px;
12062
- border-top: 1px solid var(--line);
12063
- border-right: 1px solid var(--line);
12064
- background: var(--bg-raised);
12065
- transform: rotate(45deg);
12066
- content: '';
12067
- }
12068
-
12069
12530
  .up-buddy-head {
12070
12531
  margin: 0 0 8px;
12071
12532
  font-weight: 700;
@@ -12124,20 +12585,6 @@ html.is-launching {
12124
12585
  height: 14px;
12125
12586
  }
12126
12587
 
12127
- .up-buddy-face {
12128
- flex: none;
12129
- width: 64px;
12130
- height: 112px;
12131
- animation: up-hop 0.5s cubic-bezier(0.3, 1.5, 0.5, 1) both;
12132
- }
12133
-
12134
- @keyframes up-hop {
12135
- from {
12136
- opacity: 0;
12137
- transform: translateY(24px);
12138
- }
12139
- }
12140
-
12141
12588
  @keyframes up-bubble {
12142
12589
  from {
12143
12590
  opacity: 0;
@@ -12146,8 +12593,7 @@ html.is-launching {
12146
12593
  }
12147
12594
 
12148
12595
  @media (prefers-reduced-motion: reduce) {
12149
- .up-buddy-bubble,
12150
- .up-buddy-face {
12596
+ .up-buddy-bubble {
12151
12597
  animation: none;
12152
12598
  }
12153
12599
  }
@@ -12871,7 +13317,7 @@ body:not(:has(.tabbar)) .toast {
12871
13317
  .cover-wall-tile {
12872
13318
  overflow: hidden;
12873
13319
  aspect-ratio: 16 / 9;
12874
- border-radius: 12px;
13320
+ border-radius: var(--radius-art);
12875
13321
  background: rgba(255, 255, 255, 0.06);
12876
13322
  }
12877
13323
 
@@ -13470,7 +13916,7 @@ body.share-frame {
13470
13916
  place-items: center;
13471
13917
  aspect-ratio: 16 / 9;
13472
13918
  overflow: hidden;
13473
- border-radius: 14px;
13919
+ border-radius: var(--radius-art);
13474
13920
  background: #000;
13475
13921
  }
13476
13922
 
@@ -13530,7 +13976,7 @@ body.share-frame {
13530
13976
  position: relative;
13531
13977
  aspect-ratio: 16 / 9;
13532
13978
  overflow: hidden;
13533
- border-radius: 14px;
13979
+ border-radius: var(--radius-art);
13534
13980
  background: #000;
13535
13981
  }
13536
13982
 
@@ -13572,35 +14018,6 @@ body.share-frame {
13572
14018
  }
13573
14019
  }
13574
14020
 
13575
- /* The play page's menu: Share (the server's invite link, on X or a phone's share sheet), and for guests one quiet line. */
13576
- .ow-share {
13577
- display: flex;
13578
- gap: 8px;
13579
- padding: 12px 0;
13580
- border-top: 1px solid var(--line);
13581
- }
13582
-
13583
- .ow-share .ow-pill {
13584
- text-decoration: none;
13585
- }
13586
-
13587
- .ow-made {
13588
- margin: 0;
13589
- padding: 0 0 12px;
13590
- color: var(--muted);
13591
- font-size: 12px;
13592
- }
13593
-
13594
- .ow-made a {
13595
- color: inherit;
13596
- font-weight: 600;
13597
- text-decoration: none;
13598
- }
13599
-
13600
- .ow-made a:hover {
13601
- color: var(--text);
13602
- }
13603
-
13604
14021
  /* A clip frame records the game alone: the platform's corner buttons and chat stay out of the creator's view. */
13605
14022
  .ow-clip-frame .ow-hud,
13606
14023
  .ow-clip-frame .ow-stats {
@@ -13721,7 +14138,7 @@ body.share-frame {
13721
14138
  display: block;
13722
14139
  width: 100%;
13723
14140
  border: 0;
13724
- border-radius: 12px;
14141
+ border-radius: var(--radius-art);
13725
14142
  background: #000;
13726
14143
  }
13727
14144
 
@@ -13767,7 +14184,7 @@ body.share-frame {
13767
14184
  .file-page img {
13768
14185
  max-width: 100%;
13769
14186
  max-height: 82vh;
13770
- border-radius: 8px;
14187
+ border-radius: var(--radius-art);
13771
14188
  }
13772
14189
 
13773
14190
  .file-pager {
@@ -13777,7 +14194,531 @@ body.share-frame {
13777
14194
  gap: 16px;
13778
14195
  margin-top: 12px;
13779
14196
  }
13780
- `;var Y=JSON.parse(document.getElementById("ow-data").textContent),J=Y.game.slug,j=new URL(location.href).searchParams,De=/^[2-9]$/.test(j.get("player")??"")?j.get("player"):null,Ce=j.get("demo"),I=Ce===null||Ce==="0"?null:{paid:Ce!=="free"};jt(I!==null);var Ht=document.createElement("style");Ht.textContent=`${Bt}
14197
+
14198
+
14199
+ /*
14200
+ * A game's About (protocol about.ts): text straight on the page and the creator's own pictures, the same way for every game, as on
14201
+ * Steam or itch. The art does the designing: nothing here adds a box, a colour or a shadow.
14202
+ */
14203
+ .game-about {
14204
+ scroll-margin-top: calc(var(--bar-h) + 16px);
14205
+ }
14206
+
14207
+ .game-about-text {
14208
+ max-width: 72ch;
14209
+ white-space: pre-line;
14210
+ }
14211
+
14212
+ .game-about-text + .game-about-more {
14213
+ margin-top: 20px;
14214
+ }
14215
+
14216
+ .ab {
14217
+ display: flow-root;
14218
+ color: var(--text);
14219
+ font-size: 15px;
14220
+ line-height: 1.6;
14221
+ overflow-wrap: anywhere;
14222
+ }
14223
+
14224
+ .ab-fig,
14225
+ .ab-quote {
14226
+ margin: 0;
14227
+ }
14228
+
14229
+ .ab > * + *,
14230
+ .ab-column > * + *,
14231
+ .ab-fold-body > * + *,
14232
+ .ab-quote > * + * {
14233
+ margin-top: 16px;
14234
+ }
14235
+
14236
+ :is(.ab, .ab-column, .ab-fold-body) > * + .ab-fig,
14237
+ :is(.ab, .ab-column, .ab-fold-body) > .ab-fig + * {
14238
+ margin-top: 24px;
14239
+ }
14240
+
14241
+ .ab p {
14242
+ margin: 0;
14243
+ font-size: inherit;
14244
+ line-height: inherit;
14245
+ white-space: normal;
14246
+ }
14247
+
14248
+ /* Pictures fill the column, like the gallery above them; lines of text stop where they're still easy to read. */
14249
+ .ab :is(p, ul, ol, figcaption),
14250
+ .ab-quote {
14251
+ max-width: 760px;
14252
+ }
14253
+
14254
+ .ab h3,
14255
+ .page .ab h3 {
14256
+ margin: 32px 0 0;
14257
+ color: var(--text);
14258
+ font-size: 17px;
14259
+ font-weight: 700;
14260
+ line-height: 1.3;
14261
+ letter-spacing: 0;
14262
+ }
14263
+
14264
+ .ab h4,
14265
+ .page .ab h4 {
14266
+ margin: 20px 0 0;
14267
+ color: var(--text);
14268
+ font-size: 15px;
14269
+ font-weight: 700;
14270
+ line-height: 1.4;
14271
+ letter-spacing: 0;
14272
+ }
14273
+
14274
+ .ab :is(h3, h4):first-child,
14275
+ .page .ab :is(h3, h4):first-child {
14276
+ margin-top: 0;
14277
+ }
14278
+
14279
+ .ab :is(h3, h4) + * {
14280
+ margin-top: 6px;
14281
+ }
14282
+
14283
+ .ab a:not(.btn, .ab-youtube) {
14284
+ color: inherit;
14285
+ text-decoration: underline;
14286
+ text-underline-offset: 2px;
14287
+ }
14288
+
14289
+ .ab a:not(.btn, .ab-youtube):hover {
14290
+ text-decoration-thickness: 2px;
14291
+ }
14292
+
14293
+ .ab-spoiler {
14294
+ background: var(--muted);
14295
+ color: transparent;
14296
+ cursor: default;
14297
+ }
14298
+
14299
+ .ab-spoiler:hover,
14300
+ .ab-spoiler:focus {
14301
+ background: none;
14302
+ color: inherit;
14303
+ }
14304
+
14305
+ .ab ul,
14306
+ .ab ol {
14307
+ margin-bottom: 0;
14308
+ padding-left: 20px;
14309
+ }
14310
+
14311
+ .ab li + li {
14312
+ margin-top: 4px;
14313
+ }
14314
+
14315
+ .ab hr {
14316
+ height: 1px;
14317
+ margin: 24px 0;
14318
+ border: 0;
14319
+ background: var(--line);
14320
+ }
14321
+
14322
+ /* A picture shows at its own size, up to the column. */
14323
+ .ab-fig img {
14324
+ display: block;
14325
+ max-width: 100%;
14326
+ height: auto;
14327
+ border-radius: var(--radius-art);
14328
+ }
14329
+
14330
+ .ab-fig video,
14331
+ .ab-youtube {
14332
+ display: block;
14333
+ width: 100%;
14334
+ aspect-ratio: 16 / 9;
14335
+ border-radius: var(--radius-art);
14336
+ background: #000;
14337
+ }
14338
+
14339
+ .ab-fig figcaption {
14340
+ margin-top: 6px;
14341
+ color: var(--muted);
14342
+ font-size: 13px;
14343
+ line-height: 1.4;
14344
+ }
14345
+
14346
+ .ab-youtube {
14347
+ position: relative;
14348
+ overflow: hidden;
14349
+ color: #fff;
14350
+ }
14351
+
14352
+ .ab-fig .ab-youtube img {
14353
+ width: 100%;
14354
+ height: 100%;
14355
+ border-radius: 0;
14356
+ object-fit: cover;
14357
+ }
14358
+
14359
+ .ab-yt-play {
14360
+ position: absolute;
14361
+ inset: 0;
14362
+ display: grid;
14363
+ width: 56px;
14364
+ height: 56px;
14365
+ margin: auto;
14366
+ border-radius: 50%;
14367
+ background: rgba(0, 0, 0, 0.6);
14368
+ place-items: center;
14369
+ }
14370
+
14371
+ .ab-youtube:hover .ab-yt-play {
14372
+ background: #000;
14373
+ }
14374
+
14375
+ .ab-yt-mark {
14376
+ width: 28px;
14377
+ height: 28px;
14378
+ }
14379
+
14380
+ .ab-missing {
14381
+ padding: 18px;
14382
+ border: 1.5px dashed var(--line-strong);
14383
+ color: var(--muted);
14384
+ font-size: 13px;
14385
+ text-align: center;
14386
+ }
14387
+
14388
+ .ab-gallery {
14389
+ display: grid;
14390
+ gap: 8px;
14391
+ }
14392
+
14393
+ .ab-gallery-2 {
14394
+ grid-template-columns: repeat(2, minmax(0, 1fr));
14395
+ }
14396
+
14397
+ .ab-gallery-3 {
14398
+ grid-template-columns: repeat(3, minmax(0, 1fr));
14399
+ }
14400
+
14401
+ .ab-gallery .ab-fig img {
14402
+ width: 100%;
14403
+ aspect-ratio: 16 / 9;
14404
+ object-fit: cover;
14405
+ }
14406
+
14407
+ .ab-buttons {
14408
+ display: flex;
14409
+ flex-wrap: wrap;
14410
+ gap: 8px;
14411
+ }
14412
+
14413
+ .ab-columns {
14414
+ display: grid;
14415
+ gap: 24px;
14416
+ align-items: start;
14417
+ }
14418
+
14419
+ .ab-columns-2 {
14420
+ grid-template-columns: repeat(2, minmax(0, 1fr));
14421
+ }
14422
+
14423
+ .ab-columns-3 {
14424
+ grid-template-columns: repeat(3, minmax(0, 1fr));
14425
+ }
14426
+
14427
+ .ab-column {
14428
+ min-width: 0;
14429
+ }
14430
+
14431
+ .ab-quote {
14432
+ padding-left: 20px;
14433
+ font-style: italic;
14434
+ }
14435
+
14436
+ .ab-quote cite {
14437
+ display: block;
14438
+ margin-top: 4px;
14439
+ color: var(--muted);
14440
+ font-size: 13px;
14441
+ font-style: normal;
14442
+ }
14443
+
14444
+ .ab-table {
14445
+ overflow-x: auto;
14446
+ }
14447
+
14448
+ .ab-table table {
14449
+ width: 100%;
14450
+ border-collapse: collapse;
14451
+ font-size: 14px;
14452
+ }
14453
+
14454
+ .ab-table th,
14455
+ .ab-table td {
14456
+ padding: 8px 16px 8px 0;
14457
+ border-bottom: 1px solid var(--line);
14458
+ text-align: left;
14459
+ vertical-align: top;
14460
+ }
14461
+
14462
+ .ab-table th {
14463
+ font-weight: 700;
14464
+ }
14465
+
14466
+ .ab-fold > summary {
14467
+ font-weight: 700;
14468
+ cursor: pointer;
14469
+ }
14470
+
14471
+ .ab-fold-body {
14472
+ padding-top: 8px;
14473
+ }
14474
+
14475
+ .game-about-more.is-folded {
14476
+ position: relative;
14477
+ overflow: hidden;
14478
+ max-height: 1400px;
14479
+ mask-image: linear-gradient(180deg, #000 calc(100% - 120px), transparent);
14480
+ }
14481
+
14482
+ .about-more {
14483
+ margin-top: 8px;
14484
+ }
14485
+
14486
+ @media (max-width: 720px) {
14487
+ .ab-columns-2,
14488
+ .ab-columns-3 {
14489
+ grid-template-columns: minmax(0, 1fr);
14490
+ }
14491
+
14492
+ .ab-gallery-3 {
14493
+ grid-template-columns: repeat(2, minmax(0, 1fr));
14494
+ }
14495
+ }
14496
+
14497
+ /* The dashboard's About tab: the source, the same drawing beside it, then its files and the commands. */
14498
+ .about-editor {
14499
+ padding-bottom: 16px;
14500
+ }
14501
+
14502
+ .about-tools {
14503
+ display: flex;
14504
+ flex-wrap: wrap;
14505
+ gap: 4px;
14506
+ margin-bottom: 10px;
14507
+ }
14508
+
14509
+ .about-tool {
14510
+ min-height: 30px;
14511
+ padding: 0 10px;
14512
+ border: 1px solid var(--line);
14513
+ border-radius: 6px;
14514
+ background: transparent;
14515
+ color: var(--text);
14516
+ font: 600 13px var(--font);
14517
+ cursor: pointer;
14518
+ }
14519
+
14520
+ .about-tool:hover {
14521
+ background: var(--bg-hover);
14522
+ }
14523
+
14524
+ .about-tool-example {
14525
+ margin-left: auto;
14526
+ color: var(--muted);
14527
+ }
14528
+
14529
+ .about-panes {
14530
+ display: grid;
14531
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
14532
+ gap: 12px;
14533
+ height: min(72vh, 780px);
14534
+ min-height: 420px;
14535
+ }
14536
+
14537
+ .about-pane {
14538
+ min-width: 0;
14539
+ min-height: 0;
14540
+ }
14541
+
14542
+ .about-source {
14543
+ display: block;
14544
+ width: 100%;
14545
+ height: 100%;
14546
+ padding: 14px;
14547
+ border: 1px solid var(--line-strong);
14548
+ border-radius: var(--radius);
14549
+ background: var(--bg-soft);
14550
+ color: var(--text);
14551
+ font: 13px/1.6 var(--mono);
14552
+ resize: none;
14553
+ tab-size: 2;
14554
+ }
14555
+
14556
+ .about-source:focus {
14557
+ border-color: var(--accent);
14558
+ outline: 1px solid var(--accent);
14559
+ outline-offset: -2px;
14560
+ }
14561
+
14562
+ .about-preview {
14563
+ overflow-y: auto;
14564
+ padding: 16px 18px;
14565
+ border: 1px solid var(--line);
14566
+ border-radius: var(--radius);
14567
+ background: var(--bg);
14568
+ }
14569
+
14570
+ .about-preview-empty {
14571
+ margin: 0;
14572
+ color: var(--muted);
14573
+ }
14574
+
14575
+ .about-problems {
14576
+ display: flex;
14577
+ flex-direction: column;
14578
+ gap: 4px;
14579
+ margin: 10px 0 0;
14580
+ padding: 10px 12px;
14581
+ border-radius: var(--radius);
14582
+ background: var(--danger-soft);
14583
+ font-size: 13px;
14584
+ list-style: none;
14585
+ }
14586
+
14587
+ .about-problem {
14588
+ margin-right: 4px;
14589
+ padding: 0;
14590
+ border: 0;
14591
+ background: none;
14592
+ color: var(--danger);
14593
+ font: 700 13px var(--font);
14594
+ cursor: pointer;
14595
+ text-decoration: underline;
14596
+ }
14597
+
14598
+ .about-foot {
14599
+ display: flex;
14600
+ align-items: center;
14601
+ gap: 12px;
14602
+ margin-top: 12px;
14603
+ }
14604
+
14605
+ .about-state {
14606
+ color: var(--muted);
14607
+ font-size: 13px;
14608
+ }
14609
+
14610
+ .about-count {
14611
+ margin-left: auto;
14612
+ color: var(--muted);
14613
+ font-size: 12px;
14614
+ font-variant-numeric: tabular-nums;
14615
+ }
14616
+
14617
+ .about-count.is-over {
14618
+ color: var(--danger);
14619
+ font-weight: 700;
14620
+ }
14621
+
14622
+ .about-files {
14623
+ display: grid;
14624
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
14625
+ gap: 10px;
14626
+ margin: 0;
14627
+ padding: 0;
14628
+ list-style: none;
14629
+ }
14630
+
14631
+ .about-file {
14632
+ position: relative;
14633
+ min-width: 0;
14634
+ }
14635
+
14636
+ .about-file-pick {
14637
+ display: block;
14638
+ overflow: hidden;
14639
+ width: 100%;
14640
+ padding: 0;
14641
+ border: 1px solid var(--line);
14642
+ border-radius: var(--radius);
14643
+ background: var(--bg-soft);
14644
+ color: var(--text);
14645
+ cursor: pointer;
14646
+ text-align: left;
14647
+ }
14648
+
14649
+ .about-file-pick:hover {
14650
+ border-color: var(--line-strong);
14651
+ }
14652
+
14653
+ .about-file-pick img,
14654
+ .about-file-video {
14655
+ display: flex;
14656
+ align-items: center;
14657
+ justify-content: center;
14658
+ width: 100%;
14659
+ aspect-ratio: 16 / 10;
14660
+ object-fit: cover;
14661
+ color: var(--muted);
14662
+ font-size: 22px;
14663
+ }
14664
+
14665
+ .about-file-name {
14666
+ display: block;
14667
+ overflow: hidden;
14668
+ padding: 6px 8px;
14669
+ font: 12px var(--mono);
14670
+ text-overflow: ellipsis;
14671
+ white-space: nowrap;
14672
+ }
14673
+
14674
+ .about-file-add .dz-wide {
14675
+ aspect-ratio: auto;
14676
+ min-height: 118px;
14677
+ }
14678
+
14679
+ .about-reference h3 {
14680
+ margin: 16px 0 6px;
14681
+ color: var(--muted);
14682
+ font-size: 12px;
14683
+ font-weight: 700;
14684
+ }
14685
+
14686
+ .about-commands {
14687
+ width: 100%;
14688
+ border-collapse: collapse;
14689
+ font-size: 13px;
14690
+ }
14691
+
14692
+ .about-commands td {
14693
+ padding: 6px 12px 6px 0;
14694
+ border-top: 1px solid var(--line);
14695
+ vertical-align: top;
14696
+ }
14697
+
14698
+ .about-commands td:first-child {
14699
+ width: 48%;
14700
+ overflow-wrap: anywhere;
14701
+ }
14702
+
14703
+ .about-commands td:last-child {
14704
+ color: var(--muted);
14705
+ }
14706
+
14707
+ @media (max-width: 860px) {
14708
+ .about-panes {
14709
+ grid-template-columns: minmax(0, 1fr);
14710
+ height: auto;
14711
+ }
14712
+
14713
+ .about-source {
14714
+ height: 52vh;
14715
+ }
14716
+
14717
+ .about-preview {
14718
+ max-height: 70vh;
14719
+ }
14720
+ }
14721
+ `;var X=JSON.parse(document.getElementById("ow-data").textContent),Z=X.game.slug,N=new URL(location.href).searchParams,_e=/^[2-9]$/.test(N.get("player")??"")?N.get("player"):null,De=N.get("demo"),I=De===null||De==="0"?null:{paid:De!=="free"};Gt(I!==null);var Vt=document.createElement("style");Vt.textContent=`${Ut}
13781
14722
  .ow-local-panel { position: fixed; z-index: 30; top: 64px; left: 10px; max-width: min(340px, calc(100vw - 20px)); padding: 12px 14px; border-radius: 12px; background: var(--glass); border: 1px solid var(--line); color: var(--text); font: 14px/1.45 var(--font); backdrop-filter: blur(12px); }
13782
14723
  .ow-local-panel[hidden] { display: none; }
13783
14724
  .ow-local-panel b { font-weight: 650; }
@@ -13785,4 +14726,4 @@ body.share-frame {
13785
14726
  .ow-local-panel p { margin: 0 0 6px; }
13786
14727
  .ow-local-toast { position: fixed; z-index: 40; left: 50%; bottom: 24px; transform: translateX(-50%); max-width: calc(100vw - 32px); padding: 10px 14px; border-radius: 10px; background: var(--glass); color: var(--text); font: 14px/1.4 var(--font); border: 1px solid var(--line); }
13787
14728
  .ow-local-toast[hidden] { display: none; }
13788
- `;document.head.append(Ht);document.body.classList.add("play");var M=document.getElementById("ow-game");M.setAttribute("sandbox",pt);M.setAttribute("allow",dt);var F=document.getElementById("ow-root");function Ro(){let t=new URLSearchParams;for(let[o,r]of j)gt.has(o)||t.append(o,r);let e=t.toString();return e&&e.length<=400?`?${e}`:""}var B=S("div","ow-hud ow-pos-top-left"),_e=S("div","ow-bar"),Eo='<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="12" cy="12" r="3" fill="currentColor"/></svg>',Po='<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v8a2.5 2.5 0 0 1-2.5 2.5H10l-4.2 3.4c-.5.4-1.3 0-1.3-.6V16A2.5 2.5 0 0 1 4 13.5z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg>',Mo='<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="10" cy="8" r="3.5" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M3.5 19.5c.8-3.4 3.3-5.5 6.5-5.5s5.7 2.1 6.5 5.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M19 7.5v6M16 10.5h6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',Ft=Ie("Local platform",Eo),qt=Ie("Chat (not on this computer)",Po),Kt=Ie("Another player",Mo);_e.append(Ft,qt,Kt);B.append(_e);var D=S("div","ow-local-panel");D.hidden=!0;F.append(B,D);var W=S("div","ow-local-toast");W.hidden=!0;F.append(W);var Nt=0;function N(t){W.textContent=t,W.hidden=!1,window.clearTimeout(Nt),Nt=window.setTimeout(()=>W.hidden=!0,4e3)}Ft.addEventListener("click",()=>{D.hidden=!D.hidden,D.hidden||Ao()});qt.addEventListener("click",()=>N("Chat is the platform\u2019s: it isn\u2019t on this computer."));Kt.addEventListener("click",()=>{window.open(X(),"_blank")});function X(){let t=new URL(location.href),e=Math.min(9,Number(De??1)+1);return t.searchParams.set("player",String(e)),P&&t.searchParams.set("server",P),t.href}function Ao(){D.replaceChildren();let t=e=>{let o=document.createElement("p");o.innerHTML=e,D.append(o)};t(`<b>${ce(Y.game.name)}</b> on this computer`),t(`You are <b>${ce(Vt?.name??"\u2026")}</b>${G?` in <code>${ce(G)}</code>`:""}`),t(`<a href="${ce(X())}" target="_blank">Open another player</a> (up to 9: <code>?player=2</code>\u2026)`),t("Saves, badges and scores are kept by <code>onceworlds serve</code> while it runs.")}function S(t,e=""){let o=document.createElement(t);return e&&(o.className=e),o}function Ie(t,e){let o=S("button","ow-icon-btn");return o.type="button",o.title=t,o.setAttribute("aria-label",t),o.innerHTML=e,o}function ce(t){return t.replace(/[&<>"']/g,e=>`&#${e.charCodeAt(0)};`)}async function k(t,e={}){let o=new URL(`/__emulator/api/${t}`,location.href);De&&o.searchParams.set("player",De);let r=await fetch(o,{...e,headers:e.body?{"Content-Type":"application/json"}:void 0}),n=await r.json().catch(()=>null);if(!r.ok)throw new Error(n?.message??`The local platform answered ${r.status}`);return n}var $o=Date.now(),me=k("me").then(({player:t,now:e})=>({player:t,timeOffset:Math.round(e-($o+Date.now())/2)})),Vt=null;me.then(t=>Vt=t.player).catch(()=>{});var g=null,G=null,U=null,xe="",je=null,Be=!1,Oe=0,Wt=0;me.then(t=>Wt=t.timeOffset).catch(()=>{});var P=Ye.test(j.get("server")??"")?j.get("server"):null,ge={value:At(J)},z={hellos:0,joins:0,errors:[],lastAlive:0,closed:null},f=new ae(F,{now:()=>Date.now()+Wt,onSetting:(t,e)=>{let o=f.chooseSetting(t,e);o&&(g?.send({t:"state",k:K,v:o}),g?.send({t:"unready"}),f.handle({t:"unready"}),v.emit("room",{t:"unready"}))},onReady:t=>Yt(t),onStart:()=>g?.send({t:"start"}),onStop:()=>g?.send({t:"end"}),onInvite:()=>window.open(X(),"_blank"),onLayout:()=>H.changed()}),O=new ne(F,t=>v.emit("controls",t),()=>H.changed());function Yt(t){g?.send({t:"ready",ready:t}),f.setReadyLocal(t),v.emit("room",{t:"ready",id:xe,ready:t})}function Ne(t,e=!1){O.touch!==t&&(O.setTouch(t),e||v.emit("input",{touch:t}))}addEventListener("pointerdown",t=>t.isTrusted&&Ne(t.pointerType!=="mouse"),!0);addEventListener("keydown",t=>t.isTrusted&&Ne(!1),!0);var H=new ie(Jt,t=>v.emit("reserved",t)),he=new te(async()=>({game:J,player:null})),v=new ee(M,{async"session.get"(){let{player:t,timeOffset:e}=await me;return{player:t,game:{slug:J,name:Y.game.name,owner:"local"},audio:{volume:1,muted:!1},settings:{quality:"auto",reducedMotion:!1,showStats:!1},timeOffset:e,platformOrigin:location.origin}},"save.get":({key:t})=>k(`save?key=${encodeURIComponent(String(t))}`),"save.set":({key:t,value:e})=>k(`save?key=${encodeURIComponent(String(t))}`,{method:"PUT",body:JSON.stringify({value:e??null})}).then(()=>null),"save.delete":({key:t})=>k(`save?key=${encodeURIComponent(String(t))}`,{method:"DELETE"}).then(()=>null),"save.list":()=>k("saves"),"storage.open":t=>he.read(t),"storage.commit":t=>he.commit(t),"storage.delete":t=>he.remove(t),"storage.list":()=>he.list(),async"room.join"(t){await me;let e=t??{};if(je=e.lobby==="card"||e.lobby==="bar"?e.lobby:null,Be=!ut(e),f.setSchema(Qe(e.settings)),e.party&&g)throw new Error("Parties go into matchmaking on onceworlds.com: the local platform keeps everyone where they are.");ue();let o=!e.name&&!P&&!e.private&&ge.value&&To(ge.value.key,e)?ge.value:null;if(ge.value=null,o){let r=await Ut({key:o.key,rejoin:o.rejoin,maxPlayers:e.maxPlayers,minPlayers:e.minPlayers,countdown:e.countdown,teams:e.teams,ranked:e.ranked}).catch(()=>null);if(r)return r;ue()}return Ut(Oo(e))},"room.leave"(){return ue(),null},"net.fetch":()=>{throw new Error("onceworlds.fetch fills in secrets on onceworlds.com: it does nothing on this computer. Push a draft (onceworlds deploy) to try it.")},"badges.list":()=>k("badges"),"badges.award":({key:t})=>I?.paid?{awarded:!1}:k("badges",{method:"POST",body:JSON.stringify({key:String(t)})}).then(e=>(e.awarded&&N(`Badge earned: ${e.badge?.name??t}`),{awarded:e.awarded})),"leaderboards.submit":({board:t,score:e,lowerIsBetter:o})=>I?.paid?null:k("leaderboards",{method:"POST",body:JSON.stringify({board:String(t),score:Number(e),lowerIsBetter:o===!0})}),"leaderboards.top":({board:t,limit:e,lowerIsBetter:o})=>k(`leaderboards?board=${encodeURIComponent(String(t))}&limit=${Number(e)||10}&lower=${o===!0?1:0}`),"ratings.get":()=>null,"ratings.top":()=>[],"ui.invite"(){return window.open(X(),"_blank"),null},"ui.fullscreen"(){return document.documentElement.requestFullscreen?.().catch(()=>{}),null},"ui.tapped":()=>null,"ui.controls"(t){return O.setLayout(t),null},"ui.orientation":()=>null,"ui.menuPosition"(t){let e=t?.position;return(e==="top-left"||e==="top-right"||e==="bottom-left"||e==="bottom-right")&&(B.className=`ow-hud ow-pos-${e}`,H.changed()),null},"ui.look"(t){return Gt(t),null},"player.rename":()=>null,"demo.end":()=>I?Lo(I.paid):!1,"data.game.get":({key:t})=>k(`gamedata?key=${encodeURIComponent(String(t))}`),"data.game.op":t=>k("gamedata",{method:"POST",body:JSON.stringify(t)}),"player.avatar":t=>{let e=t?.id;return typeof e=="string"&&/^[A-Za-z0-9-]{1,64}$/.test(e)?Ve(e):null}},{"room.send":({d:t,to:e})=>g?.send({t:"msg",d:t,to:e}),"room.presence":({d:t})=>g?.send({t:"presence",d:t}),"room.hideLobby":({hidden:t})=>f.setHidden(t===!0),"room.state":({k:t,v:e,w:o})=>g?.send({t:"state",k:t,v:e,...typeof o=="number"&&Number.isSafeInteger(o)&&o>0&&o<=2147483647?{w:o}:{}}),"room.team":({id:t,team:e})=>g?.send({t:"team",id:String(t),team:Number(e)}),"room.open":({open:t})=>g?.send({t:"open",open:t===!0}),"room.result":({result:t})=>g?.send({t:"result",result:t}),"room.kick":({id:t})=>g?.send({t:"kick",id:String(t)}),"room.votekick":({id:t})=>g?.send({t:"votekick",id:String(t)}),"room.ready":({ready:t})=>{g?.send({t:"ready",ready:t===!0}),f.setReadyLocal(t===!0)},"room.unready":()=>{g?.send({t:"unready"}),f.handle({t:"unready"})},"room.transfer":({id:t})=>g?.send({t:"transfer",id:String(t)}),"room.start":({countdown:t,open:e})=>g?.send({t:"start",...Number.isFinite(t)?{countdown:Number(t)}:{},...e===!0?{open:!0}:{}}),"room.end":()=>g?.send({t:"end"}),"room.pause":({paused:t})=>g?.send({t:"pause",paused:t===!0}),"room.idle":({idle:t})=>g?.send({t:"idle",idle:t===!0}),"room.private":({k:t,v:e,id:o})=>typeof t=="string"&&g?.send({t:"private",k:t,v:e,...typeof o=="string"?{id:o}:{}}),"room.datareset":()=>g?.send({t:"datareset"}),"room.admit":({ids:t})=>g?.send({t:"admit",ids:Array.isArray(t)?t.filter(e=>typeof e=="string").slice(0,50):[]}),alive:()=>{z.lastAlive=Date.now()},stats:()=>{},hotkey:()=>{},vibrate:()=>{},input:t=>Ne(t?.touch===!0,!0),error:t=>{z.errors.length<20&&z.errors.push(String(t?.message??t).slice(0,300))},"input.first":()=>{},console:()=>{},debug:()=>{}},()=>{z.hellos++,_t(),ue(),B.className="ow-hud ow-pos-top-left",O.setLayout(null),Gt(null),v.emit("input",{touch:O.touch}),H.resend()},{demo:I!==null}),Te=null;function Lo(t){return Te??=new Promise(e=>{v.emit("pause",null);let o=S("div","ow-name ow-demo-end"),r=S("div","ow-name-form ow-demo-box"),n=S("h2");n.textContent="End of demo";let a=S("p","ow-demo-name");a.textContent=Y.game.name;let i=S("button","ow-btn ow-btn-primary");i.type="button",i.textContent=t?"Buy":"Favorite",i.addEventListener("click",()=>N(t?"On onceworlds.com, Buy opens the game\u2019s store page.":"On onceworlds.com, Favorite follows the game."));let s=S("button","ow-text-btn ow-name-login");s.type="button",s.textContent="Back to game",s.addEventListener("click",()=>{o.remove(),Te=null,v.emit("resume",null),M.focus(),e(!0)}),r.append(n,a,i,s),o.append(r),F.append(o),i.focus()}),Te}function Gt(t){let{look:e}=xt(t);f.setLook(St(e)),O.setLook(zt(e))}function Jt(){let t=M.getBoundingClientRect();if(!t.width||!t.height)return null;let{stick:e,buttons:o}=O.reservedBoxes(),r=_e.getBoundingClientRect(),n=parseFloat(getComputedStyle(B).paddingTop)||0,a=r.width&&r.height?{left:r.left-n,top:r.top-n,width:r.width+2*n,height:r.height+2*n}:null;return Mt(t,[{kind:"menu",box:B.hidden?null:a},{kind:"lobby",box:f.reservedBox()},{kind:"stick",box:e},{kind:"buttons",box:o}])}for(let t of["resize","orientationchange"])addEventListener(t,()=>H.changed());typeof ResizeObserver=="function"&&new ResizeObserver(()=>H.changed()).observe(M);function Co(t){return t.slice(0,t.lastIndexOf(":"))}function To(t,e){let o=(e.teams??0)>=2?e.teams:0,r=Math.min(E.roomMaxPlayersCap,Math.max(2,e.maxPlayers??16)),n=Xe(e.minPlayers,r,Z);return Co(t)===`pub:${we({mode:e.mode||"default",max:r,teams:o,ranked:e.ranked===!0,min:n})}`}function Do(){let t="abcdefghijklmnopqrstuvwxyz0123456789";return Array.from(crypto.getRandomValues(new Uint8Array(12)),e=>t[e%t.length]).join("")}function Oo(t){let{name:e,maxPlayers:o,minPlayers:r,countdown:n,mode:a,teams:i,ranked:s}=t,c=t.minPlayersPrivate??r;if(e)return{name:e,maxPlayers:o,minPlayers:c,countdown:n,teams:i};if(t.private&&!P){P=Do(),$t(J,P);let d=new URL(location.href);d.searchParams.set("server",P),history.replaceState(null,"",d.href)}return P?{server:P,maxPlayers:o,minPlayers:c,countdown:n,teams:i,private:t.private}:{mode:a,maxPlayers:o,minPlayers:r,countdown:n,teams:i,ranked:s}}async function Ut(t){let e=new de(J,t,{onMessage:n=>e===g&&_o(n),onClose:n=>e===g&&Io(n),onConnection:n=>{e===g&&(n||v.emit("room.connection",{connected:!1}))}});g=e;let o=await e.join();if(e!==g)throw new Error("Left the server while joining");let r=o.room.startsWith("private:")?{...o,invite:X()}:o;return xe=o.you,U=o.match??null,G=o.room,z.joins++,Oe=Je(o.room)?.max??Math.min(t.maxPlayers??E.roomMaxPlayersDefault,E.roomMaxPlayersCap),f.enable(je,{ownCountdown:Be}),f.setRoom(r,Oe),r}function _o(t){if(t.t==="update"||t.t==="move")return;if(t.t==="datareset"){N("This server\u2019s world was started over."),M.src=M.src;return}t.t==="chat"&&(t.entry.to||t.entry.channel)||t.t==="channel"||v.emit("room",t),t.t!=="welcome"&&f.handle(t),t.t==="welcome"?(f.setRoom(t,Oe),U=t.match??null):t.t==="match"?U=t.match:t.t==="error"&&t.code==="cooldown"&&(N(t.message),f.setReadyLocal(!1),v.emit("room",{t:"ready",id:xe,ready:!1}))}function Io(t){g=null,G=null,U=null,z.closed=t,f.setRoom(null),N(t==="replaced"?"You opened this game in another tab, so multiplayer moved there.":t==="kicked"?"You were removed from the server.":"Disconnected from the server."),v.emit("room.closed",{reason:t})}function ue(){g&&(g.close(),g=null,G=null,U=null,f.setRoom(null))}window.__owLocal={get you(){return xe},get room(){return G},get match(){return U},get lobby(){return je},get ownCountdown(){return Be},get hellos(){return z.hellos},get joins(){return z.joins},get errors(){return z.errors},get lastAlive(){return z.lastAlive},get closed(){return z.closed},get reserved(){return Jt()},ready(t){return g?(Yt(t),!0):!1},start(t){return g?(g.send({t:"start",...Number.isFinite(t)?{countdown:Number(t)}:{}}),!0):!1},chrome(t){F.style.visibility=t?"":"hidden"}};M.src=`${Y.gameOrigin}/${Ro()}`;})();
14729
+ `;document.head.append(Vt);document.body.classList.add("play");var y=document.getElementById("ow-game");y.setAttribute("sandbox",pt);y.setAttribute("allow",dt);var q=document.getElementById("ow-root");function Lo(){let t=new URLSearchParams;for(let[o,r]of N)gt.has(o)||t.append(o,r);let e=t.toString();return e&&e.length<=400?`?${e}`:""}var G=z("div","ow-hud ow-pos-top-left"),Ie=z("div","ow-bar"),$o='<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="2"/><circle cx="12" cy="12" r="3" fill="currentColor"/></svg>',Co='<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v8a2.5 2.5 0 0 1-2.5 2.5H10l-4.2 3.4c-.5.4-1.3 0-1.3-.6V16A2.5 2.5 0 0 1 4 13.5z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg>',To='<svg viewBox="0 0 24 24" aria-hidden="true"><circle cx="10" cy="8" r="3.5" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M3.5 19.5c.8-3.4 3.3-5.5 6.5-5.5s5.7 2.1 6.5 5.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M19 7.5v6M16 10.5h6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',Wt=Be("Local platform",$o),Yt=Be("Chat (not on this computer)",Co),Jt=Be("Another player",To);Ie.append(Wt,Yt,Jt);G.append(Ie);var O=z("div","ow-local-panel");O.hidden=!0;q.append(G,O);var J=z("div","ow-local-toast");J.hidden=!0;q.append(J);var Ht=0;function U(t){J.textContent=t,J.hidden=!1,window.clearTimeout(Ht),Ht=window.setTimeout(()=>J.hidden=!0,4e3)}Wt.addEventListener("click",()=>{O.hidden=!O.hidden,O.hidden||Do()});Yt.addEventListener("click",()=>U("Chat is the platform\u2019s: it isn\u2019t on this computer."));Jt.addEventListener("click",()=>{window.open(Q(),"_blank")});function Q(){let t=new URL(location.href),e=Math.min(9,Number(_e??1)+1);return t.searchParams.set("player",String(e)),M&&t.searchParams.set("server",M),t.href}function Do(){O.replaceChildren();let t=e=>{let o=document.createElement("p");o.innerHTML=e,O.append(o)};t(`<b>${he(X.game.name)}</b> on this computer`),t(`You are <b>${he(Xt?.name??"\u2026")}</b>${H?` in <code>${he(H)}</code>`:""}`),t(`<a href="${he(Q())}" target="_blank">Open another player</a> (up to 9: <code>?player=2</code>\u2026)`),t("Saves, badges and scores are kept by <code>onceworlds serve</code> while it runs.")}function z(t,e=""){let o=document.createElement(t);return e&&(o.className=e),o}function Be(t,e){let o=z("button","ow-icon-btn");return o.type="button",o.title=t,o.setAttribute("aria-label",t),o.innerHTML=e,o}function he(t){return t.replace(/[&<>"']/g,e=>`&#${e.charCodeAt(0)};`)}async function S(t,e={}){let o=new URL(`/__emulator/api/${t}`,location.href);_e&&o.searchParams.set("player",_e);let r=await fetch(o,{...e,headers:e.body?{"Content-Type":"application/json"}:void 0}),a=await r.json().catch(()=>null);if(!r.ok)throw new Error(a?.message??`The local platform answered ${r.status}`);return a}var Oo=Date.now(),fe=S("me").then(({player:t,now:e})=>({player:t,timeOffset:Math.round(e-(Oo+Date.now())/2)})),Xt=null;fe.then(t=>Xt=t.player).catch(()=>{});var g=null,H=null,_=null,be="",B=null,we=!1,Zt=!1,je=0,Qt=0;fe.then(t=>Qt=t.timeOffset).catch(()=>{});var M=Ye.test(N.get("server")??"")?N.get("server"):null,me={value:Ct(Z)},R={hellos:0,joins:0,errors:[],lastAlive:0,closed:null},u=new le(q,{now:()=>Date.now()+Qt,onSetting:(t,e)=>{let o=u.chooseSetting(t,e);o&&(g?.send({t:"state",k:V,v:o}),g?.send({t:"unready"}),u.handle({t:"unready"}),w.emit("room",{t:"unready"}))},onReady:t=>eo(t),onStart:()=>g?.send({t:"start"}),onStop:()=>g?.send({t:"end"}),onInvite:()=>window.open(Q(),"_blank"),onLayout:()=>F.changed(),onPressed:to}),C=new ie(q,t=>w.emit("controls",t),()=>F.changed());u.setKeyMark(!C.touch);function eo(t){g?.send({t:"ready",ready:t}),u.setReadyLocal(t),w.emit("room",{t:"ready",id:be,ready:t})}function Ne(t,e=!1){C.touch!==t&&(C.setTouch(t),u.setKeyMark(!t),e||w.emit("input",{touch:t}))}addEventListener("pointerdown",t=>t.isTrusted&&Ne(t.pointerType!=="mouse"),!0);addEventListener("keydown",t=>t.isTrusted&&Ne(!1),!0);addEventListener("keydown",t=>{let e=document.activeElement;t.isTrusted&&!t.repeat&&xt(t)&&(!e||e===document.body)&&u.pressKey()});function to(){y.focus(),y.contentWindow?.focus()}function Ft(){B||Zt||(B="bar",u.enable(B,{ownCountdown:we}))}var F=new se(()=>{let t=oo();return t&&{areas:t,link:u.link(y.getBoundingClientRect())}},({areas:t,link:e})=>{w.emit("reserved",t),w.emit("lobby",e)}),ue=new re(async()=>({game:Z,player:null})),w=new oe(y,{async"session.get"(){let{player:t,timeOffset:e}=await fe;return{player:t,game:{slug:Z,name:X.game.name,owner:"local"},audio:{volume:1,muted:!1},settings:{quality:"auto",reducedMotion:!1,showStats:!1},timeOffset:e,platformOrigin:location.origin}},"save.get":({key:t})=>S(`save?key=${encodeURIComponent(String(t))}`),"save.set":({key:t,value:e})=>S(`save?key=${encodeURIComponent(String(t))}`,{method:"PUT",body:JSON.stringify({value:e??null})}).then(()=>null),"save.delete":({key:t})=>S(`save?key=${encodeURIComponent(String(t))}`,{method:"DELETE"}).then(()=>null),"save.list":()=>S("saves"),"storage.open":t=>ue.read(t),"storage.commit":t=>ue.commit(t),"storage.delete":t=>ue.remove(t),"storage.list":()=>ue.list(),async"room.join"(t){await fe;let e=t??{};if(B=ut(e),Zt=e.lobby==="game",we=!mt(e),u.setSchema(Qe(e.settings)),e.party&&g)throw new Error("Parties go into matchmaking on onceworlds.com: the local platform keeps everyone where they are.");xe();let o=!e.name&&!M&&!e.private&&me.value&&Io(me.value.key,e)?me.value:null;if(me.value=null,o){let r=await Kt({key:o.key,rejoin:o.rejoin,maxPlayers:e.maxPlayers,minPlayers:e.minPlayers,countdown:e.countdown,teams:e.teams,ranked:e.ranked}).catch(()=>null);if(r)return r;xe()}return Kt(No(e))},"room.leave"(){return xe(),null},"net.fetch":()=>{throw new Error("onceworlds.fetch fills in secrets on onceworlds.com: it does nothing on this computer. Push a draft (onceworlds deploy) to try it.")},"badges.list":()=>S("badges"),"badges.award":({key:t})=>I?.paid?{awarded:!1}:S("badges",{method:"POST",body:JSON.stringify({key:String(t)})}).then(e=>(e.awarded&&U(`Badge earned: ${e.badge?.name??t}`),{awarded:e.awarded})),"leaderboards.submit":({board:t,score:e,lowerIsBetter:o})=>I?.paid?null:S("leaderboards",{method:"POST",body:JSON.stringify({board:String(t),score:Number(e),lowerIsBetter:o===!0})}),"leaderboards.top":({board:t,limit:e,lowerIsBetter:o})=>S(`leaderboards?board=${encodeURIComponent(String(t))}&limit=${Number(e)||10}&lower=${o===!0?1:0}`),"ratings.get":()=>null,"ratings.top":()=>[],"ui.invite"(){return window.open(Q(),"_blank"),null},"ui.fullscreen"(){return document.documentElement.requestFullscreen?.().catch(()=>{}),null},"ui.tapped":()=>null,"ui.controls"(t){return C.setLayout(t),null},"ui.orientation":()=>null,"ui.menuPosition"(t){let e=t?.position;return(e==="top-left"||e==="top-right"||e==="bottom-left"||e==="bottom-right")&&(G.className=`ow-hud ow-pos-${e}`,F.changed()),null},"ui.look"(t){return qt(t),null},"player.rename":()=>null,"demo.end":()=>I?_o(I.paid):!1,"data.game.get":({key:t})=>S(`gamedata?key=${encodeURIComponent(String(t))}`),"data.game.op":t=>S("gamedata",{method:"POST",body:JSON.stringify(t)}),"player.avatar":t=>{let e=t?.id;return typeof e=="string"&&/^[A-Za-z0-9-]{1,64}$/.test(e)?Ve(e):null}},{"room.send":({d:t,to:e})=>g?.send({t:"msg",d:t,to:e}),"room.presence":({d:t})=>g?.send({t:"presence",d:t}),"room.hideLobby":()=>{},"lobby.press":()=>u.pressTarget(),"lobby.target":t=>u.setTarget(t?.ok===!0,t?.hover===!0),"room.state":({k:t,v:e,w:o})=>g?.send({t:"state",k:t,v:e,...typeof o=="number"&&Number.isSafeInteger(o)&&o>0&&o<=2147483647?{w:o}:{}}),"room.team":({id:t,team:e})=>g?.send({t:"team",id:String(t),team:Number(e)}),"room.open":({open:t})=>g?.send({t:"open",open:t===!0}),"room.result":({result:t})=>g?.send({t:"result",result:t}),"room.kick":({id:t})=>g?.send({t:"kick",id:String(t)}),"room.votekick":({id:t})=>g?.send({t:"votekick",id:String(t)}),"room.ready":({ready:t})=>{Ft(),g?.send({t:"ready",ready:t===!0}),u.setReadyLocal(t===!0)},"room.unready":()=>{g?.send({t:"unready"}),u.handle({t:"unready"})},"room.transfer":({id:t})=>g?.send({t:"transfer",id:String(t)}),"room.start":({countdown:t,open:e})=>{Ft(),g?.send({t:"start",...Number.isFinite(t)?{countdown:Number(t)}:{},...e===!0?{open:!0}:{}})},"room.end":()=>g?.send({t:"end"}),"room.pause":({paused:t})=>g?.send({t:"pause",paused:t===!0}),"room.idle":({idle:t})=>g?.send({t:"idle",idle:t===!0}),"room.private":({k:t,v:e,id:o})=>typeof t=="string"&&g?.send({t:"private",k:t,v:e,...typeof o=="string"?{id:o}:{}}),"room.datareset":()=>g?.send({t:"datareset"}),"room.admit":({ids:t})=>g?.send({t:"admit",ids:Array.isArray(t)?t.filter(e=>typeof e=="string").slice(0,50):[]}),alive:()=>{R.lastAlive=Date.now()},stats:()=>{},hotkey:({key:t})=>{t==="Enter"&&u.pressKey()},vibrate:()=>{},input:t=>Ne(t?.touch===!0,!0),error:t=>{R.errors.length<20&&R.errors.push(String(t?.message??t).slice(0,300))},"input.first":()=>{},console:()=>{},debug:()=>{}},()=>{R.hellos++,Bt(),xe(),G.className="ow-hud ow-pos-top-left",C.setLayout(null),qt(null),w.emit("input",{touch:C.touch}),F.resend()},{demo:I!==null}),Oe=null;function _o(t){return Oe??=new Promise(e=>{w.emit("pause",null);let o=z("div","ow-name ow-demo-end"),r=z("div","ow-name-form ow-demo-box"),a=z("h2");a.textContent="End of demo";let n=z("p","ow-demo-name");n.textContent=X.game.name;let i=z("button","ow-btn ow-btn-primary");i.type="button",i.textContent=t?"Buy":"Favorite",i.addEventListener("click",()=>U(t?"On onceworlds.com, Buy opens the game\u2019s store page.":"On onceworlds.com, Favorite follows the game."));let s=z("button","ow-text-btn ow-name-login");s.type="button",s.textContent="Back to game",s.addEventListener("click",()=>{o.remove(),Oe=null,w.emit("resume",null),y.focus(),e(!0)}),r.append(a,n,i,s),o.append(r),q.append(o),i.focus()}),Oe}function qt(t){let{look:e}=wt(t);u.setLook(Et(e)),C.setLook(Pt(e))}function oo(){let t=y.getBoundingClientRect();if(!t.width||!t.height)return null;let{stick:e,buttons:o}=C.reservedBoxes(),r=Ie.getBoundingClientRect(),a=parseFloat(getComputedStyle(G).paddingTop)||0,n=r.width&&r.height?{left:r.left-a,top:r.top-a,width:r.width+2*a,height:r.height+2*a}:null;return Y(t,[{kind:"menu",box:G.hidden?null:n},{kind:"lobby",box:u.reservedBox()},{kind:"stick",box:e},{kind:"buttons",box:o}])}for(let t of["resize","orientationchange"])addEventListener(t,()=>F.changed());typeof ResizeObserver=="function"&&new ResizeObserver(()=>F.changed()).observe(y);function jo(t){return t.slice(0,t.lastIndexOf(":"))}function Io(t,e){let o=(e.teams??0)>=2?e.teams:0,r=Math.min(P.roomMaxPlayersCap,Math.max(2,e.maxPlayers??16)),a=Xe(e.minPlayers,r,ee);return jo(t)===`pub:${ke({mode:e.mode||"default",max:r,teams:o,ranked:e.ranked===!0,min:a})}`}function Bo(){let t="abcdefghijklmnopqrstuvwxyz0123456789";return Array.from(crypto.getRandomValues(new Uint8Array(12)),e=>t[e%t.length]).join("")}function No(t){let{name:e,maxPlayers:o,minPlayers:r,countdown:a,mode:n,teams:i,ranked:s}=t,d=t.minPlayersPrivate??r;if(e)return{name:e,maxPlayers:o,minPlayers:d,countdown:a,teams:i};if(t.private&&!M){M=Bo(),Tt(Z,M);let c=new URL(location.href);c.searchParams.set("server",M),history.replaceState(null,"",c.href)}return M?{server:M,maxPlayers:o,minPlayers:d,countdown:a,teams:i,private:t.private}:{mode:n,maxPlayers:o,minPlayers:r,countdown:a,teams:i,ranked:s}}async function Kt(t){let e=new ge(Z,t,{onMessage:a=>e===g&&Go(a),onClose:a=>e===g&&Uo(a),onConnection:a=>{e===g&&(a||w.emit("room.connection",{connected:!1}))}});g=e;let o=await e.join();if(e!==g)throw new Error("Left the server while joining");let r=o.room.startsWith("private:")?{...o,invite:Q()}:o;return be=o.you,_=o.match??null,H=o.room,R.joins++,je=Je(o.room)?.max??Math.min(t.maxPlayers??P.roomMaxPlayersDefault,P.roomMaxPlayersCap),u.enable(B,{ownCountdown:we}),u.setRoom(r,je),r}function Go(t){if(t.t==="update"||t.t==="move")return;if(t.t==="datareset"){U("This server\u2019s world was started over."),y.src=y.src;return}t.t==="chat"&&(t.entry.to||t.entry.channel)||t.t==="channel"||w.emit("room",t),t.t!=="welcome"&&u.handle(t),t.t==="welcome"?(u.setRoom(t,je),_=t.match??null):t.t==="match"?(t.match.phase!=="lobby"&&t.match.phase!==_?.phase&&to(),_=t.match):t.t==="error"&&t.code==="cooldown"&&(U(t.message),u.setReadyLocal(!1),w.emit("room",{t:"ready",id:be,ready:!1}))}function Uo(t){g=null,H=null,_=null,R.closed=t,u.setRoom(null),U(t==="replaced"?"You opened this game in another tab, so multiplayer moved there.":t==="kicked"?"You were removed from the server.":"Disconnected from the server."),w.emit("room.closed",{reason:t})}function xe(){g&&(g.close(),g=null,H=null,_=null,u.setRoom(null))}window.__owLocal={get you(){return be},get room(){return H},get match(){return _},get lobby(){return B},get ownCountdown(){return we},get hellos(){return R.hellos},get joins(){return R.joins},get errors(){return R.errors},get lastAlive(){return R.lastAlive},get closed(){return R.closed},get reserved(){return oo()},ready(t){return g?(eo(t),!0):!1},start(t){return g?(g.send({t:"start",...Number.isFinite(t)?{countdown:Number(t)}:{}}),!0):!1},chrome(t){q.style.visibility=t?"":"hidden"}};y.src=`${X.gameOrigin}/${Lo()}`;})();