SOURCE / PINNED RELEASE
Made of little things.
Shiftfield
- Release
- 5a170c505f6b…
- Author-recorded commit
- a2e23e0171aa…
- License
- Check the original source
- Author’s source reference
- nostr://npub16nzc4ql748r2kv9202rgzrt0fmtk4n3tzsjl86yf6ty9agqe9u0qjcc7hc/wss%3A%2F%2Fgit.napplet.soy%2F/n-e4fb6341ccd
The hash-verified playable HTML, which may contain bundled or minified code. This view does not execute it.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" /><title>Shiftfield</title><meta name="description" content="— the Roguelike Minesweeper —
Clear sectors, bank scrap, die rich. Explore deeper depths for more riches, but watch out for hazards, and the ground that shifts beneath your feet.
Built 100% locally and offline with StrataLLM and Qwen3.8 Flash Next"><meta name="napplet-id" content="shiftfield"><meta name="napplet-optional" content="config cvm storage"><link rel="icon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAABMlBMVEUREREAERERESIzM0QzREQiM0QiMzNmd3eZqrtmd4hVd4hVZohVZndEZndEVXdEVWYzVWZVVWaIiJkzRGYzRFUiIjNEVUQRIiIiRFVVRDPMmSJERDMzRDMRIjNVVUSqiDN3ZiKqiCLdqiLuuxH/uxHuqhHdmRGqdxF3VSKZdyIiM1VEMyLMmRH/zBHdqhH/qhG7dxEzMyJVVTOIZhG7mSLuuyK7iBF3VRGZZhFVRCJERET/zCKIVRH/qgDMiBEzMzNmVTN3RBFVMxFEMxFmVSJmRBFVRBGZdxFmRABEIhEiIhEzIhGIVQDumRFmMwB3RACqZgC7dwCZZgCIRACZVQDdiBHMiCJ3ZkQzIiJVMwCqVQAiIiLMdwDddwD/mQDuiBGqdyIiERHuiADMZgC7ZgBERFUw1iuRAAAB2klEQVQ4y72TfVfTMBTG+5ZBlS2TpsQxKzKybsjaMcF1065Iy0DYaIdUdOuGOvT7fwWTTtpVPfzl8TknJzm5vzxp783luP8lnumBuCBKkiSCe+ViLcdXVmX50eO1tXyejkIBQliE8hMhAXLrcrxXVJBCx0JQVhNAzbNDLI42NlAinAWK6KkolTY3y8/oV8RAeoUGC1B5vvViu7JDSFWv1XfLL5lD8l8YQgXsNQyDmKapN5vN/RZYBnhM/V/VDsydw9fUoV3VrU4XZB2UNxWDkPbuW4NU7f1er+ccgQyA3lF/0jjudCzXc+glve5vwEnlkHo0Olb/oNEketvxcqKQJh/jU/ussm0yG0LDuv3+XJVSQDjCF4Nhv0YMgyGXtusH52rqwKtyoVRvXbjD0ZXjXH24DvzQ51SQOmiwWPp4s/Wp1Po8GIfhJAy8KE1jDCjl6ez28svN1/o33x97rlWWIExqwasQge70dj6fzaaB17fsYRABBNNMUgCBvdFsPp/e3X0fhcF4kMukWqMAApF3HI4mk+vAHUQgU6zYgRJAwxEV1mi5WbGEJYcfaIEsxFYZgFMRkkQExPunEs/pg6IEFgSBDTbheIUxt9QH/F/1Z+f82uM57qEm+uf6CVg3SPPLMev0AAAAAElFTkSuQmCC" type="image/png">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script type="module" crossorigin>var Mt=Object.defineProperty;var Et=(a,t,e)=>t in a?Mt(a,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):a[t]=e;var l=(a,t,e)=>Et(a,typeof t!="symbol"?t+"":t,e);function At(){const a=window;if(!a.napplet)throw new Error("window.napplet is unavailable -- runtime did not inject the napplet namespace");return a.napplet}function v(a){const e=At()[a];if(!e)throw new Error(`window.napplet.${String(a)} is unavailable -- runtime did not inject this domain`);return e}var rt={getItem(a){return v("storage").getItem(a)},setItem(a,t){return v("storage").setItem(a,t)},removeItem(a){return v("storage").removeItem(a)},keys(){return v("storage").keys()},instance:{getItem(a){return v("storage").instance.getItem(a)},setItem(a,t){return v("storage").instance.setItem(a,t)},removeItem(a){return v("storage").instance.removeItem(a)},keys(){return v("storage").instance.keys()}}},P={get(){return v("config").get()},subscribe(a){return v("config").subscribe(a)},openSettings(a){v("config").openSettings(a)},registerSchema(a,t){return v("config").registerSchema(a,t)},onSchemaError(a){return v("config").onSchemaError(a)},get schema(){return v("config").schema}},at={discover(a){return v("cvm").discover(a)},request(a,t,e){return v("cvm").request(a,t,e)},listTools(a,t){return v("cvm").listTools(a,t)},callTool(a,t,e,s){return v("cvm").callTool(a,t,e,s)},listResources(a,t){return v("cvm").listResources(a,t)},readResource(a,t,e){return v("cvm").readResource(a,t,e)},close(a){return v("cvm").close(a)},onEvent(a){return v("cvm").onEvent(a)},registry:{list(a){return v("cvm").registry.list(a)},has(a,t){return v("cvm").registry.has(a,t)},describe(a,t){return v("cvm").registry.describe(a,t)},call(a,t,e,s){return v("cvm").registry.call(a,t,e,s)}}};function Ct(a,t){return!!(a!=null&&a[t])}function z(a){return Ct(window.napplet,a)}const It="http://json-schema.org/draft-07/schema#",Lt=1,Bt="object",Rt=!1,$t={textSize:{type:"integer",title:"Text size",description:"Base text size in pixels. Changes apply while the napplet is running.",minimum:10,maximum:20,default:13,"x-napplet-section":"Appearance","x-napplet-order":1},controlSize:{type:"integer",title:"Control height",description:"Choose compact controls or larger touch targets, in pixels.",minimum:24,maximum:44,default:28,"x-napplet-section":"Appearance","x-napplet-order":2},selectText:{type:"boolean",title:"Allow text selection",description:"Allow selecting interface text. Text inputs always remain selectable.",default:!1,"x-napplet-section":"Interaction"}},Nt={$schema:It,$version:Lt,type:Bt,additionalProperties:Rt,properties:$t};z("config")&&(async()=>{P.schema||await P.registerSchema(Nt);const a=P.subscribe(t=>{const e=document.documentElement.style,s=typeof t.textSize=="number"?t.textSize:13,i=typeof t.controlSize=="number"?t.controlSize:28;e.fontSize=`${s}px`,e.setProperty("--control-h",`${i}px`),e.setProperty("--napplet-text-selection",t.selectText===!0?"text":"none")});window.addEventListener("pagehide",()=>a.close(),{once:!0})})().catch(()=>{});const E=10,Tt=Math.floor(E*E*.15),ot=[[1,0],[-1,0],[0,1],[0,-1],[1,1],[1,-1],[-1,1],[-1,-1]];class ht{constructor(t,e=Tt){l(this,"width",E);l(this,"height",E);l(this,"tiles");l(this,"rng");l(this,"explosion",null);this.rng=Dt(t),this.tiles=[];for(let s=0;s<this.height;s++)for(let i=0;i<this.width;i++)this.tiles.push({coord:[i,s],state:"HIDDEN",hasMine:!1});this.placeMines(Math.floor(e))}tileAt(t,e){return this.tiles[this.wrapY(e)*this.width+this.wrapX(t)]}wrapX(t){return this.mod(t,this.width)}wrapY(t){return this.mod(t,this.height)}mod(t,e){return(t%e+e)%e}placeMines(t){const e=new Set;for(;e.size<t;){const s=Math.floor(this.rng()*this.width),n=Math.floor(this.rng()*this.height)*this.width+s;e.has(n)||(e.add(n),this.tiles[n].hasMine=!0)}}shift(t,e){if(this.explosion)return{moved:0,blocked:0,collisionCell:null};const s=[],i=new Set;let n=0,o=0;for(let r=0;r<this.tiles.length;r++){const h=this.tiles[r];if(!h.hasMine)continue;const d=this.wrapY(h.coord[1]+e)*this.width+this.wrapX(h.coord[0]+t);if(h.state==="FLAGGED"||this.tiles[d].state==="FLAGGED"){o++,i.add(r);continue}n++,s.push({fromIdx:r,targetIdx:d})}for(const r of s)if(i.has(r.targetIdx))return this.explosion="MINE_ON_MINE",{moved:n,blocked:o,collisionCell:r.targetIdx};for(const r of s)this.tiles[r.fromIdx].hasMine=!1;for(const r of s)this.tiles[r.targetIdx].hasMine=!0;return this.checkExplosions(),{moved:n,blocked:o,collisionCell:null}}placeMineAt(t,e,s={}){const i=this.tileAt(this.wrapX(t),this.wrapY(e));return i.hasMine?(this.explosion="MINE_ON_MINE",this.explosion):(i.hasMine=!0,s.explode?this.checkExplosions():null)}checkExplosions(){if(this.explosion)return this.explosion;const t=new Set;for(const e of this.tiles){if(!e.hasMine)continue;const s=e.coord[0]*this.height+e.coord[1];if(t.has(s))return this.explosion="MINE_ON_MINE",this.explosion;if(t.add(s),e.state==="REVEALED")return this.explosion="MINE_ON_REVEALED",this.explosion}return null}isExploded(){return this.explosion!==null}reveal(t,e,s={}){const i=s.flood??!0,n=this.tileAt(t,e);if(n.state==="FLAGGED")return{result:"safe",cleared:0};n.state="REVEALED";let o=1;if(n.hasMine)return this.checkExplosions(),{result:"detonated",cleared:o};if(i&&this.adjacentMineCount(t,e)===0){const r=new Set([this.indexOf(t,e)]),h=[[this.wrapX(t),this.wrapY(e)]];for(;h.length>0;){const[d,c]=h.shift();for(const[p,m]of ot){const b=this.wrapX(d+p),x=this.wrapY(c+m),w=this.indexOf(b,x);if(r.has(w))continue;r.add(w);const f=this.tiles[w];f.state!=="HIDDEN"||f.hasMine||(f.state="REVEALED",o++,this.adjacentMineCount(b,x)===0&&h.push([b,x]))}}}return{result:"safe",cleared:o}}adjacentMineCount(t,e){let s=0;for(const[i,n]of ot)this.tileAt(t+i,e+n).hasMine&&s++;return s}indexOf(t,e){return this.wrapY(e)*this.width+this.wrapX(t)}flag(t,e){const s=this.tileAt(t,e);return s.state="FLAGGED",s.state}unflag(t,e){const s=this.tileAt(t,e);return s.state==="FLAGGED"&&(s.state="HIDDEN"),s.state}revealedCount(){return this.tiles.filter(t=>t.state==="REVEALED").length}flaggedCount(){return this.tiles.filter(t=>t.state==="FLAGGED").length}mineCount(){return this.tiles.filter(t=>t.hasMine).length}isBoardCleared(){for(const t of this.tiles)if(!t.hasMine&&t.state!=="REVEALED")return!1;return!0}}function Dt(a){let t=a>>>0;return()=>{let e=(t+=1831565813)>>>0;return e=Math.imul(e^e>>>15,e|1),e^=e+Math.imul(e^e>>>7,e|61),((e^e>>>14)>>>0)/4294967296}}const A={radar:{id:"radar",name:"Radar Array",maxLevel:3,group:"Intel",costs:[150,400,900],describe:a=>a===0?"Locked — Lv.1 unlocks a 3×3 safe sweep":a===1?"Sweep 3×3 (9 tiles) · next: 5×5 circle":a===2?"Sweep 5×5 circle (21 tiles) · next: full 5×5":"Sweep full 5×5 (25 tiles) · MAX"},seeker:{id:"seeker",name:"Seeker Drone",maxLevel:3,group:"Intel",costs:[60,200,500],describe:a=>a===0?"Locked — Lv.1 flags 1 mine per use":a<3?`Flags ${a} mine${a>1?"s":""} per use · next: ${a+1}`:"Flags 3 mines per use · MAX"},shield:{id:"shield",name:"Shield Generator",maxLevel:5,group:"Survival",costs:[50,120,250,430,700],describe:a=>a===0?"Locked — Lv.1 starts every run with 1 shield":a<5?`Run starts with ${a} shield${a>1?"s":""} · next: ${a+1}`:"Run starts with 5 shields · MAX"},stabilizer:{id:"stabilizer",name:"Stabilizer Array",maxLevel:4,group:"Survival",costs:[80,200,450,900],describe:a=>a===0?"Locked — Lv.1 freezes the Shifter 3 turns":a<4?`Freezes the Shifter ${X(a)}T · next: ${X(a+1)}T`:"Freezes the Shifter 10T · MAX — never fires"},economy:{id:"economy",name:"Data Ledger",maxLevel:4,group:"Economy",costs:[50,150,300,500],describe:a=>a===0?"Locked — Lv.1 grants +25% run scrap":`Run scrap +${a*25}%`+(a<4?` · next: +${(a+1)*25}%`:" · MAX")},flow_cap:{id:"flow_cap",name:"Flow Capacitor",maxLevel:4,group:"Economy",costs:[50,125,275,500],describe:a=>a===0?"Locked — Lv.1 raises Flow cap 100 → 200":a<4?`Flow ceiling ${V(a)} · next: ${V(a+1)}`:"Flow ceiling 500 · MAX"}},Ft=[{group:"Intel",ids:["radar","seeker"]},{group:"Survival",ids:["shield","stabilizer"]},{group:"Economy",ids:["economy","flow_cap"]}],Z={radar:0,seeker:0,shield:0,stabilizer:0,economy:0,flow_cap:0},lt={radar:3,seeker:3,shield:5,stabilizer:4,economy:4,flow_cap:4},ct=[3,5,7,10];function X(a){return a>=1&&a<=ct.length?ct[a-1]:0}function _t(a){return 1+.25*a.economy}const yt=100,vt=100;yt+vt*A.flow_cap.maxLevel;function V(a){const t=typeof a=="number"&&Number.isFinite(a)?Math.max(0,Math.min(A.flow_cap.maxLevel,a)):0;return yt+vt*t}function zt(a){const t=[],e=a>=1?a===1?1:2:0,s=a===2;if(a<=0)return t;for(let i=-e;i<=e;i++)for(let n=-e;n<=e;n++)s&&Math.abs(i)===2&&Math.abs(n)===2||t.push([i,n]);return t}function Y(a,t){const e=A[a];return t>=e.maxLevel?null:e.costs[t]}const T=6,dt=11,Ot=15,Gt=5,Pt={iceDuration:3,flowPerSafe:5,flowPerClear:20},R={radar:{cost:50,cooldown:2},seeker:{cost:50,cooldown:3},stabilizer:{cost:30,cooldown:10}},F=[[1,0],[-1,0],[0,1],[0,-1],[1,1],[1,-1],[-1,1],[-1,-1]];function H(a){return F[Math.floor(a()*F.length)]}function D(a){return Math.max(10,Math.floor(45/(1+a*.1)))}const Ht=50;function ut(a){return Math.min(Ht,Math.floor(E*E*.15+a*.02*E))}class ft{constructor(t=0,e=Pt,s=1,i,n=Z){l(this,"world");l(this,"cfg");l(this,"depth");l(this,"seed");l(this,"upgrades");l(this,"turn",0);l(this,"boardTurn",0);l(this,"flow",0);l(this,"ice",new Map);l(this,"fire",new Set);l(this,"nextShiftIn",0);l(this,"telegraph",null);l(this,"lastShiftCollision",null);l(this,"stabilizedFor",0);l(this,"cooldowns",new Map);l(this,"shieldCharges",0);l(this,"boardsCleared",0);l(this,"firstClickSafe",!0);l(this,"absorbed",null);l(this,"onBoardCleared",null);this.depth=t,this.cfg=e,this.world=i??new ht(s,ut(t)),this.seed=s,this.upgrades=n,this.shieldCharges=n.shield,this.depth>=T&&(this.nextShiftIn=D(this.depth),this.telegraph=H(this.world.rng))}idx(t,e){return this.world.wrapY(e)*this.world.width+this.world.wrapX(t)}tile(t,e){return this.world.tileAt(t,e)}iceAt(t,e){return this.ice.has(this.idx(t,e))}fireAt(t,e){return this.fire.has(this.idx(t,e))}canInteract(t,e){return!this.iceAt(t,e)&&this.tile(t,e).state!=="FLAGGED"}eligible(t,e){const s=this.tile(t,e),i=this.idx(t,e);return s.state==="HIDDEN"&&!s.hasMine&&!this.ice.has(i)&&!this.fire.has(i)}reveal(t,e){const s=this.idx(t,e);if(this.ice.has(s))return"blocked";this.protectOpeningReveal(t,e),this.fire.has(s)&&this.fire.delete(s);const i=this.floodReveal(t,e);return this.world.explosion?(this.resolveShield(),this.world.explosion||this.commit(),"detonated"):(i===0||(this.addFlow(this.cfg.flowPerSafe),this.commit()),"safe")}floodReveal(t,e){if(this.tile(t,e).state==="REVEALED")return 0;const s=this.world.reveal(t,e,{flood:!1});if(s.result==="detonated"||s.cleared===0||this.world.adjacentMineCount(t,e)!==0)return s.cleared;let i=s.cleared;const n=new Set([this.idx(t,e)]),o=[[this.world.wrapX(t),this.world.wrapY(e)]];for(;o.length>0;){const[r,h]=o.shift();for(const[d,c]of F){const p=this.world.wrapX(r+d),m=this.world.wrapY(h+c),b=this.idx(p,m);if(n.has(b)||(n.add(b),this.ice.has(b)))continue;const x=this.world.reveal(p,m,{flood:!1});x.cleared!==0&&(this.fire.has(b)&&this.fire.delete(b),i+=x.cleared,this.world.adjacentMineCount(p,m)===0&&o.push([p,m]))}}return i}protectOpeningReveal(t,e){if(!this.firstClickSafe)return;const s=this.tile(t,e);s.state==="HIDDEN"&&(s.hasMine&&this.relocateMineOff(t,e),this.firstClickSafe=!1)}relocateMineOff(t,e){const s=this.world,i=[],n=[];for(const h of s.tiles){if(h.hasMine||h.state!=="HIDDEN")continue;const d=h.coord[1]*s.width+h.coord[0];this.fire.has(d)||(this.ice.has(d)?n.push(d):i.push(d))}const o=i.length>0?i:n;if(o.length===0)return;const r=o[Math.floor(s.rng()*o.length)];s.tileAt(t,e).hasMine=!1,s.tiles[r].hasMine=!0}flag(t,e){const s=this.idx(t,e);if(this.ice.has(s))return"blocked";const i=this.tile(t,e);return i.state==="FLAGGED"?(this.world.unflag(t,e),this.commit(),"unflagged"):i.state!=="HIDDEN"?"rejected":(this.fire.has(s)&&this.fire.delete(s),this.world.flag(t,e),this.commit(),"flagged")}commit(){this.turn+=1,this.boardTurn+=1,this.tickCooldowns(),this.tickIce(),this.boardTurn%2===1&&this.tickFire(),this.tickShift(),this.resolveShield()}tickCooldowns(){for(const[t,e]of this.cooldowns)e<=0?this.cooldowns.delete(t):this.cooldowns.set(t,e-1)}tickIce(){const t=[];for(const[e,s]of this.ice){const i=s-1;i<=0?t.push(e):this.ice.set(e,i)}for(const e of t)this.ice.delete(e)}tickFire(){if(this.fire.size===0)return;const t=new Set;let e=!1;for(const s of this.fire){const i=s%this.world.width,n=Math.floor(s/this.world.width),o=[];for(const h of F){const d=this.world.wrapX(i+h[0]),c=this.world.wrapY(n+h[1]),p=c*this.world.width+d;this.world.tileAt(d,c).state==="HIDDEN"&&(this.ice.has(p)||this.fire.has(p)||t.has(p)||o.push(p))}const r=Math.min(2,o.length);for(let h=0;h<r;h++){const d=h+Math.floor(this.world.rng()*(o.length-h)),c=o[h];o[h]=o[d],o[d]=c;const p=o[h];t.add(p),this.world.tiles[p].hasMine&&(e=!0)}}for(const s of t)this.fire.add(s);e&&(this.world.explosion="MINE_ON_FIRE")}tickShift(){if(this.depth<T)return;if(this.stabilizedFor>0){this.stabilizedFor-=1;return}if(this.nextShiftIn-=1,this.nextShiftIn>0)return;const t=this.telegraph??[1,0],e=this.world.shift(t[0],t[1]);if(this.lastShiftCollision=e.collisionCell,this.nextShiftIn=D(this.depth),this.telegraph=H(this.world.rng),!this.world.explosion)for(const s of this.world.tiles){const i=s.coord[1]*this.world.width+s.coord[0];if(s.hasMine&&this.fire.has(i)){this.world.explosion="MINE_ON_FIRE";return}}}resolveShield(){if(this.world.explosion){if(this.world.explosion==="MINE_ON_FIRE"&&this.shieldCharges>0){this.shieldCharges-=1,this.world.explosion=null;let t=null;for(const e of[...this.fire]){const s=e%this.world.width,i=Math.floor(e/this.world.width);this.world.tileAt(s,i).hasMine&&(this.fire.delete(e),this.world.flag(s,i),t=e)}this.absorbed={cause:"MINE_ON_FIRE",x:t===null?4:t%this.world.width,y:t===null?4:Math.floor(t/this.world.width)}}else if(this.world.explosion==="MINE_ON_REVEALED"&&this.shieldCharges>0){this.shieldCharges-=1,this.world.explosion=null;let t=null;for(const e of this.world.tiles)e.hasMine&&e.state==="REVEALED"&&(this.world.flag(e.coord[0],e.coord[1]),t??(t=[e.coord[0],e.coord[1]]));this.absorbed={cause:"MINE_ON_REVEALED",x:t?t[0]:4,y:t?t[1]:4}}else if(this.world.explosion==="MINE_ON_MINE"&&this.shieldCharges>0){this.shieldCharges-=1,this.world.explosion=null;const t=this.lastShiftCollision;this.lastShiftCollision=null;let e=null;if(t!==null){const s=t%this.world.width,i=Math.floor(t/this.world.width);this.world.flag(s,i),e=[s,i]}this.absorbed={cause:"MINE_ON_MINE",x:e?e[0]:4,y:e?e[1]:4}}}}isUnlocked(t){return this.upgrades[t]>0}flowCap(){return V(this.upgrades.flow_cap)}addFlow(t){this.flow=Math.min(this.flowCap(),this.flow+t)}canUse(t,e){return this.world.explosion||t in this.upgrades&&!this.isUnlocked(t)?!1:this.flow>=e&&(this.cooldowns.get(t)??0)<=0}pay(t,e,s){return this.canUse(t,e)?(this.flow-=e,this.cooldowns.set(t,s),!0):!1}radar(t,e,s=R.radar){if(!this.pay("radar",s.cost,s.cooldown))return!1;for(const[i,n]of zt(this.upgrades.radar)){const o=this.world.wrapX(t+i),r=this.world.wrapY(e+n),h=this.tile(o,r);h.state!=="HIDDEN"||h.hasMine||this.iceAt(o,r)||this.fireAt(o,r)||this.world.reveal(o,r,{flood:!1})}return this.commit(),!0}seeker(t=R.seeker){const e=this.upgrades.seeker,s=this.world.tiles.filter(n=>n.hasMine&&n.state!=="FLAGGED");if(e<=0||s.length===0||!this.pay("seeker",t.cost,t.cooldown))return!1;const i=Math.min(e,s.length);for(let n=0;n<i;n++){const o=n+Math.floor(this.world.rng()*(s.length-n)),r=s[n];s[n]=s[o],s[o]=r,this.world.flag(s[n].coord[0],s[n].coord[1])}return this.commit(),!0}stabilizer(t=R.stabilizer){return this.pay("stabilizer",t.cost,t.cooldown)?(this.stabilizedFor=X(this.upgrades.stabilizer),this.commit(),!0):!1}spawnIce(t,e){return this.eligible(t,e)?(this.ice.set(this.idx(t,e),this.cfg.iceDuration),!0):!1}spawnFire(t,e){return this.eligible(t,e)?(this.fire.add(this.idx(t,e)),!0):!1}get isBoardCleared(){return this.world.isBoardCleared()&&this.fire.size===0}advance(){var t;if(this.isBoardCleared&&((t=this.onBoardCleared)==null||t.call(this,this.depth),this.addFlow(this.cfg.flowPerClear),this.depth+=1,this.boardsCleared+=1,this.seed=this.seed+2654435769>>>0,this.world=new ht(this.seed,ut(this.depth)),this.firstClickSafe=!0,this.ice.clear(),this.fire.clear(),this.stabilizedFor=0,this.lastShiftCollision=null,this.nextShiftIn=this.depth>=T?D(this.depth):0,this.telegraph=this.depth>=T?H(this.world.rng):null,this.depth>=dt)){const e=this.world.tiles.map(o=>o.coord),s=this.depth-dt+1,i=Math.min(s,Ot),n=Math.min(Math.ceil(s/2),Gt);for(let o=0;o<i;o++){const r=e.filter(([c,p])=>this.eligible(c,p));if(r.length===0)break;const[h,d]=r[Math.floor(this.world.rng()*r.length)];this.spawnIce(h,d)}for(let o=0;o<n;o++){const r=e.filter(([c,p])=>this.eligible(c,p));if(r.length===0)break;const[h,d]=r[Math.floor(this.world.rng()*r.length)];this.spawnFire(h,d)}}}isDead(){return this.world.explosion!==null}explosionCause(){return this.world.explosion}iceCount(){return this.ice.size}fireCount(){return this.fire.size}}const j=24;class Wt{constructor(t,e=()=>{},s=()=>{}){l(this,"game");l(this,"root");l(this,"focusedTile",[0,0]);l(this,"pendingTarget",null);l(this,"onAction",null);l(this,"onFx",null);l(this,"fxSnap",null);l(this,"pulseCells",[]);l(this,"revealFx",new Map);l(this,"paintCache",new Map);l(this,"inAction",!1);l(this,"keepJuiceOnce",!1);l(this,"onSync");l(this,"onEngine");this.onSync=e,this.onEngine=s,this.game=new ft(0,void 0,pt()),this.onEngine(this.game),this.root=document.createElement("div"),this.root.className="board",t.append(this.root),this.rebuild(),this.bindInput(),this.root.addEventListener("animationend",i=>{i.animationName==="cell-count-pulse"&&i.target.classList.remove("cell-count-pulse")})}newRun(){this.game=new ft(0,void 0,pt()),this.onEngine(this.game),this.rebuild()}rebuild(){this.focusedTile=[0,0],this.pendingTarget=null,this.fxSnap=null,this.revealFx.clear(),this.paintCache.clear(),this.root.innerHTML=this.buildGrid(),this.refresh()}clear(){this.root.remove()}buildGrid(){const t=[];for(let e=0;e<E;e++)for(let s=0;s<E;s++)t.push(`<span class="cell" role="button" tabindex="-1" data-x="${s}" data-y="${e}" data-cell="${s},${e}"></span>`);return t.join("")}cellAt(t,e){return this.root.querySelector(`.cell[data-x="${t}"][data-y="${e}"]`)}bindInput(){this.root.addEventListener("click",n=>{n.preventDefault();const o=this.cellFrom(n.target);if(!o)return;const r=Number(o.dataset.x),h=Number(o.dataset.y);this.setFocus(o),this.pendingTarget?this.pendingTarget(r,h)&&(this.pendingTarget=null):this.game.reveal(r,h),this.afterAction()});let t=null,e=null,s=!1,i=null;this.root.addEventListener("pointerdown",n=>{s=n.button===0,t=performance.now(),e=this.cellFrom(n.target),i=null}),this.root.addEventListener("pointercancel",()=>{t=null,e=null}),this.root.addEventListener("pointerup",n=>{if(t===null||e===null)return;const o=s&&n.button===0,r=performance.now()-t,h=this.cellFrom(n.target),d=h!==null&&h.dataset.x===e.dataset.x&&h.dataset.y===e.dataset.y;if(t=null,!o||!d||r<400)return;const c=Number(e.dataset.x),p=Number(e.dataset.y);e=null;const m=i!==null&&i.x===c&&i.y===p;i=null,m||(this.setFocus(this.cellAt(c,p)),this.pendingTarget=null,this.game.flag(c,p),this.afterAction());const b=x=>{x.stopPropagation(),x.preventDefault()};this.root.addEventListener("click",b,{once:!0,capture:!0})}),this.root.addEventListener("contextmenu",n=>{n.preventDefault();const o=this.cellFrom(n.target);if(!o)return;const r=Number(o.dataset.x),h=Number(o.dataset.y);i!==null&&i.x===r&&i.y===h||(i={x:r,y:h},this.setFocus(o),this.pendingTarget=null,this.game.flag(r,h),this.afterAction())}),this.root.addEventListener("keydown",n=>{const o=this.cellFrom(n.target)??this.cellAt(...this.focusedTile);if(!o)return;const r=Number(o.dataset.x),h=Number(o.dataset.y),d={ArrowRight:[1,0],ArrowLeft:[-1,0],ArrowDown:[0,1],ArrowUp:[0,-1]};if(d[n.key]){n.preventDefault();const c=this.game.world,p=c.wrapX(r+d[n.key][0]),m=c.wrapY(h+d[n.key][1]);this.setFocus(this.cellAt(p,m))}else n.key==="Escape"?(n.preventDefault(),this.pendingTarget&&(this.pendingTarget=null,this.refresh(),this.onSync(this.game))):n.key==="Enter"?(n.preventDefault(),this.pendingTarget?this.pendingTarget(r,h)&&(this.pendingTarget=null):this.game.reveal(r,h),this.afterAction()):n.key===" "&&(n.preventDefault(),this.pendingTarget=null,this.game.flag(r,h),this.afterAction())})}cellFrom(t){const e=t;return e!=null&&e.dataset&&e.dataset.x!==void 0?e:null}setFocus(t){t&&(this.focusedTile=[Number(t.dataset.x),Number(t.dataset.y)],t.focus({preventScroll:!0}),this.keepJuiceOnce=!0,this.refresh())}afterAction(){var t;this.inAction=!0;try{this.refresh()}finally{this.inAction=!1}this.onSync(this.game),(t=this.onAction)==null||t.call(this,this.game)}refresh(){const t=this.game,e=t.isDead(),s=this.inAction?this.diffFx():(this.seedFx(),[]),i=this.keepJuiceOnce;this.keepJuiceOnce=!1,this.root.classList.toggle("board-targeting",this.pendingTarget!==null);for(const n of Array.from(this.root.children)){const o=Number(n.dataset.x),r=Number(n.dataset.y),h=t.tile(o,r),d=["cell",`cell-${h.state.toLowerCase()}`];t.iceAt(o,r)&&d.push("cell-ice"),t.fireAt(o,r)&&d.push("cell-fire"),o===this.focusedTile[0]&&r===this.focusedTile[1]&&d.push("cell-focused"),e&&h.hasMine&&d.push("cell-mine-reveal");const c=`${o},${r}`;let p=this.revealFx.get(c);p===void 0&&i&&n.style.getPropertyValue("--juice-delay")&&(p=Number(n.style.getPropertyValue("--juice-delay").replace("ms",""))||0),p!==void 0?(d.push("juice-reveal"),n.style.setProperty("--juice-delay",`${p}ms`)):!i&&n.style.getPropertyValue("--juice-delay")&&n.style.removeProperty("--juice-delay");let m=0;h.state==="REVEALED"&&!h.hasMine&&(m=t.world.adjacentMineCount(o,r),m>0&&d.push(`cell-num-${m}`));const b=`${d.join(" ")}|${p??""}`;this.paintCache.get(c)!==b&&(this.paintCache.set(c,b),n.className=d.join(" "),n.textContent=m>0?String(m):"",n.setAttribute("aria-label",`${h.state}${t.iceAt(o,r)?" iced":""}${t.fireAt(o,r)?" burning":""}${m>0?` ${m} adjacent mines`:""} ${o} ${r}`))}if(this.revealFx.clear(),this.pulseCells.length>0){for(const n of this.pulseCells){const o=this.root.children[n];o&&o.classList.add("cell-count-pulse")}this.pulseCells=[]}if(this.onFx){const n=this.onFx;for(const o of s)try{n(o)}catch{}}}seedFx(){const t=this.game;t.absorbed=null,this.pulseCells=[];let e="",s="",i="";for(let n=0;n<t.world.tiles.length;n++){const o=t.world.tiles[n];e+=o.state==="REVEALED"?"r":o.state==="FLAGGED"?"f":o.hasMine?"M":"h",i+=o.state==="REVEALED"&&!o.hasMine?String(t.world.adjacentMineCount(o.coord[0],o.coord[1])):".",o.hasMine&&(s+=`${n},`)}this.fxSnap={world:t.world,cells:e,mines:s,exploded:t.isDead(),shiftIn:t.nextShiftIn,nums:i},this.revealFx.clear()}diffFx(){const t=this.game,e=t.world.tiles,s=t.world.width;let i="",n="",o="";for(let p=0;p<e.length;p++){const m=e[p];i+=m.state==="REVEALED"?"r":m.state==="FLAGGED"?"f":m.hasMine?"M":"h",o+=m.state==="REVEALED"&&!m.hasMine?String(t.world.adjacentMineCount(m.coord[0],m.coord[1])):".",m.hasMine&&(n+=`${p},`)}const r=this.fxSnap,h={world:t.world,cells:i,mines:n,exploded:t.isDead(),shiftIn:t.nextShiftIn,nums:o};if(!r||r.world!==t.world)return this.fxSnap=h,[];const d=[];if(r.cells!==i){const p=[];let m=-1,b=!1,x=-1;for(let w=0;w<i.length;w++){const f=r.cells[w],y=i[w];f!==y&&(y==="r"&&(f==="h"||f==="M")?p.push(w):y==="f"&&m<0?(m=w,b=e[w].hasMine):f==="f"&&y!=="f"&&x<0&&(x=w))}if(p.length>0){const w=p[0],f=w%s,y=(w-f)/s,S=t.world.height;this.revealFx.clear();for(const O of p){const C=O%s,B=(O-C)/s,G=Math.min(Math.abs(C-f),s-Math.abs(C-f)),N=Math.min(Math.abs(B-y),S-Math.abs(B-y));this.revealFx.set(`${C},${B}`,Math.min(480,G*50+N*50))}const k=w%s;d.push({kind:"reveal",cell:`${k},${y}`,count:p.length})}if(m>=0){const w=m%s;d.push({kind:"flag",cell:`${w},${(m-w)/s}`,mine:b})}if(x>=0){const w=x%s;d.push({kind:"unflag",cell:`${w},${(x-w)/s}`})}}const c=r.mines!==h.mines?this.shiftVector(r.mines,h.mines):null;if(c&&d.push({kind:"shift",dx:c[0],dy:c[1]}),this.pulseCells=[],r.nums!==h.nums){const p=[];for(let m=0;m<h.nums.length;m++)r.nums[m]!==h.nums[m]&&r.cells[m]==="r"&&i[m]==="r"&&p.push(m);if(p.length>j){const[m,b]=c??[0,0],x=w=>w%s*m+Math.floor(w/s)*b;p.sort((w,f)=>x(f)-x(w)||w-f),p.length=j}this.pulseCells=p}if(t.telegraph&&t.stabilizedFor===0&&h.shiftIn>0&&h.shiftIn<=3&&h.shiftIn!==r.shiftIn&&d.push({kind:"shift-warning",dx:t.telegraph[0],dy:t.telegraph[1],turns:h.shiftIn}),!r.exploded&&h.exploded){let p="4,4";for(let m=0;m<e.length;m++)if(e[m].hasMine&&e[m].state==="REVEALED"){const b=m%s;p=`${b},${(m-b)/s}`;break}d.push({kind:"boom",cell:p,cause:String(t.explosionCause()),shielded:!1})}return t.absorbed&&(d.push({kind:"boom",cell:`${t.absorbed.x},${t.absorbed.y}`,cause:String(t.absorbed.cause),shielded:!0}),t.absorbed=null),this.fxSnap=h,d}shiftVector(t,e){const s=this.game,i=s.world.width,n=s.world.height,o=d=>d?d.slice(0,-1).split(",").map(Number):[],r=o(t),h=new Set(o(e));if(r.length===0||r.length!==h.size)return null;for(let d=-1;d<=1;d++)for(let c=-1;c<=1;c++){if(d===0&&c===0)continue;let p=!0;for(const m of r){const b=m%i,x=(m-b)/i,w=(b+d+i)%i,f=(x+c+n)%n;if(!h.has(f*i+w)){p=!1;break}}if(p)return[d,c]}return null}}function pt(){return Math.floor(Math.random()*4294967296)}const W=Math.PI*2,M=class M{constructor(t,e,s,i,n){l(this,"canvas");l(this,"ctx2d");l(this,"dpr");l(this,"effects",[]);l(this,"raf",0);l(this,"rafDeadLogged",!1);l(this,"last",0);l(this,"shakeAmp",0);l(this,"shakeTtl",0);l(this,"shakeAge",0);l(this,"shakeEl",null);l(this,"chroma",0);l(this,"chromaTtl",0);l(this,"chromaAge",0);l(this,"chromaX",0);l(this,"chromaY",0);l(this,"wind",null);l(this,"canvasDirty",!1);l(this,"lastWindPaint",0);l(this,"ctxLostAt",0);l(this,"lostProbeAt",0);l(this,"rectCache",null);l(this,"rectAt",0);l(this,"fullCanvas",!0);l(this,"boxX",0);l(this,"boxY",0);l(this,"boxW",0);l(this,"boxH",0);l(this,"prevBBox",null);l(this,"bboxFull",!1);l(this,"chromaGrads",null);l(this,"sweepGrad",null);l(this,"bandGrads",null);l(this,"reduce",null);l(this,"resize",()=>{this.rectCache=null,this.fullCanvas=!0,this.boxX=0,this.boxY=0,this.boxW=Math.max(1,window.innerWidth),this.boxH=Math.max(1,window.innerHeight);const t=this.canvas.style;t.left="0px",t.top="0px",t.width=`${this.boxW}px`,t.height=`${this.boxH}px`,this.canvas.width=Math.floor(this.boxW*this.dpr),this.canvas.height=Math.floor(this.boxH*this.dpr),this.prevBBox=null,this.bboxFull=!1});l(this,"invalidateRect",()=>{this.rectCache=null});l(this,"frame",t=>{try{this.ctxLostAt&&this.lostWatchdog(t),this.runFrame(t)}catch(e){this.rafDeadLogged||(this.rafDeadLogged=!0,console.warn("[fx] draw frame threw — juice loop halted until next action:",e)),this.raf=0}});var o,r;this.center=e,this.boardEl=s,this.sfx=i,this.tap=n,this.canvas=document.createElement("canvas"),this.canvas.id="fx",this.canvas.setAttribute("aria-hidden","true"),t.prepend(this.canvas),this.ctx2d=this.canvas.getContext("2d"),this.dpr=Math.min(2,window.devicePixelRatio||1),this.resize(),window.addEventListener("resize",this.resize),window.addEventListener("scroll",this.invalidateRect,{capture:!0,passive:!0}),this.canvas.addEventListener("contextlost",h=>{h.preventDefault(),this.ctx2d=null,this.sweepGrad=null,this.bandGrads=null,this.chromaGrads=null,this.ctxLostAt=performance.now(),this.lostProbeAt=this.ctxLostAt}),this.canvas.addEventListener("contextrestored",()=>this.restoreContext()),window.matchMedia&&(this.reduce=window.matchMedia("(prefers-reduced-motion: reduce)"),(r=(o=this.reduce).addEventListener)==null||r.call(o,"change",()=>{this.calm&&this.stopAll()}))}get calm(){var t;return((t=this.reduce)==null?void 0:t.matches)??!1}layoutFx(){let t=this.chroma>.2;if(!t){for(const r of this.effects)if(!(r.age<0)&&("peak"in r?r.board||(t=!0):"ax"in r||(t=!0),t))break}let e=0,s=0,i=Math.max(1,window.innerWidth),n=Math.max(1,window.innerHeight);if(!t){const r=this.boardRect();if(!r)t=!0;else{const h=M.BOARD_MARGIN;e=Math.max(0,Math.floor(r.left-h)),s=Math.max(0,Math.floor(r.top-h)),i=Math.min(window.innerWidth,Math.ceil(r.right+h))-e,n=Math.min(window.innerHeight,Math.ceil(r.bottom+h))-s,(i<80||n<80)&&(t=!0)}}if(t){const r=Math.max(1,window.innerWidth),h=Math.max(1,window.innerHeight);if(this.fullCanvas&&this.boxW===r&&this.boxH===h)return;this.fullCanvas=!0,this.boxX=0,this.boxY=0,this.boxW=r,this.boxH=h}else{if(!this.fullCanvas&&e===this.boxX&&s===this.boxY&&i===this.boxW&&n===this.boxH)return;this.fullCanvas=!1,this.boxX=e,this.boxY=s,this.boxW=i,this.boxH=n}const o=this.canvas.style;o.left=`${this.boxX}px`,o.top=`${this.boxY}px`,o.width=`${this.boxW}px`,o.height=`${this.boxH}px`,this.canvas.width=Math.floor(this.boxW*this.dpr),this.canvas.height=Math.floor(this.boxH*this.dpr),this.prevBBox=null,this.bboxFull=!1}event(t){var e;try{(e=this.tap)==null||e.call(this,t),this.onEvent(t)}catch{}}onEvent(t){var e,s,i,n,o,r,h,d;switch(t.kind){case"reveal":{if((e=this.sfx)==null||e.play("reveal"),this.calm)return;const c=this.center(t.cell);if(!c)return;const p=Math.min(4+t.count,16);this.burst(c.x,c.y,{n:p,speed:.05,color:"#7fa6c9",grav:-2e-5,ttl:500,size:2});break}case"flag":{(s=this.sfx)==null||s.play("flag");const c=this.center(t.cell);if(!c)return;this.ring(c.x,c.y,{r1:26,ttl:380,color:t.mine?"#ff6b6b":"#e8b13c",width:2}),this.calm||this.burst(c.x,c.y,{n:t.mine?14:8,speed:.12,color:t.mine?"#ff6b6b":"#e8b13c",grav:2e-4,ttl:520,size:2});break}case"unflag":(i=this.sfx)==null||i.play("unflag");break;case"shift":{if((n=this.sfx)==null||n.play("shift"),this.calm)return;this.sweepBurst(t.dx,t.dy);break}case"shift-warning":{if((o=this.sfx)==null||o.play("shiftWarn"),this.calm)return;const c=t.turns<=1?.9:t.turns===2?.6:.4;this.sweep(t.dx,t.dy,c),this.flash("#e8b13c",.07+.05*(4-t.turns),340,!0),this.effects.push({age:0,ttl:460+c*220,ax:Math.sign(t.dx),ay:Math.sign(t.dy),strength:.4+.35*c});break}case"boom":{const c=this.center(t.cell)??this.boardCenter();if(t.shielded||t.cause==="SHIELDED"){(r=this.sfx)==null||r.play("shielded"),this.ring(c.x,c.y,{r1:60,ttl:500,color:"#4fd2ff",width:3}),this.flash("#4fd2ff",.16,260),this.shake(4,240);break}(h=this.sfx)==null||h.play("boom"),this.flash("#ffdfc4",.55,320),this.ring(c.x,c.y,{r1:160,ttl:520,color:"#ff7a29",width:5}),this.ring(c.x,c.y,{r1:90,ttl:380,color:"#ffe9d2",width:3}),this.calm?this.flash("#e5484d",.25,200):(this.burst(c.x,c.y,{n:46,speed:.42,color:"#ff7a29",grav:55e-5,ttl:900,size:3}),this.burst(c.x,c.y,{n:22,speed:.2,color:"#ffd9c0",grav:-12e-5,ttl:1100,size:2}),this.chromatic(9,520,c),this.shake(14,620));break}case"deny":(d=this.sfx)==null||d.play("deny");break}}celebrate(t){var s;if((s=this.sfx)==null||s.play("clear"),this.calm){this.flash("#7ee2a0",.14,300);return}const e=Math.max(1,Math.floor(t.length/24));for(let i=0;i<t.length;i+=e){const n=this.center(t[i]);n&&this.burst(n.x,n.y+4,{n:3,speed:.09,color:i%(e*2)===0?"#7ee2a0":"#e8b13c",grav:-28e-5,ttl:900+i%5*120,size:2,delay:i%10*40})}this.ringAtBoard({r1:220,ttl:700,color:"#7ee2a0",width:2})}reset(){this.stopAll()}stopAll(){this.effects.length=0,this.wind=null,this.shakeAmp=0,this.chroma=0,this.shakeEl&&(this.shakeEl.style.transform="",this.shakeEl.classList.remove("shaking")),this.stopRaf(),this.clearCanvas(),this.canvasDirty=!1}burst(t,e,s){const i=s.delay??0;for(let n=0;n<s.n;n++){const o=Math.random()*W,r=s.speed*(.35+Math.random()*.9);this.effects.push({x:t,y:e,vx:Math.cos(o)*r,vy:Math.sin(o)*r,age:-i*Math.random(),ttl:s.ttl*(.6+Math.random()*.7),size:s.size*(.6+Math.random()),color:s.color,grav:s.grav,drag:.985})}this.startRaf()}ring(t,e,s){this.effects.push({x:t,y:e,age:0,ttl:s.ttl,r0:2,r1:s.r1,width:s.width,color:s.color}),this.startRaf()}ringAtBoard(t){const e=this.boardCenter();this.ring(e.x,e.y,t)}flash(t,e,s,i=!1){this.effects.push({age:0,ttl:s,color:t,peak:e,board:i}),this.startRaf()}sweep(t,e,s=1){const i=Math.hypot(t,e)||1;this.effects.push({age:0,ttl:420,dx:t/i*s,dy:e/i*s}),this.startRaf()}sweepBurst(t,e){for(const s of[0,-110,-220]){const i=Math.hypot(t,e)||1;this.effects.push({age:s,ttl:420,dx:t/i,dy:e/i})}this.effects.push({age:0,ttl:560,ax:Math.sign(t),ay:Math.sign(e)}),this.startRaf()}setTelegraph(t){if(!t||this.calm){this.wind=null;return}const e=Math.max(0,Math.min(1,t.urgency));if(!this.wind||this.wind.dx!==t.dx||this.wind.dy!==t.dy){const s=[];for(let i=0;i<M.WIND_DOTS;i++)s.push({u:Math.random(),v:Math.random()});this.wind={dx:t.dx,dy:t.dy,urgency:e,frozen:t.frozen,dots:s}}else this.wind.urgency=e,this.wind.frozen=t.frozen;this.wind&&this.startRaf()}get telegraphWind(){return this.wind?{dx:this.wind.dx,dy:this.wind.dy,frozen:this.wind.frozen}:null}stepWind(t){const e=this.wind;if(!e||e.frozen)return;const s=Math.hypot(e.dx,e.dy)||1,i=18e-5+e.urgency*35e-5,n=e.dx/s*i*t,o=e.dy/s*i*t;for(const r of e.dots)r.u=((r.u+n)%1+1)%1,r.v=((r.v+o)%1+1)%1}introRumble(){this.shake(3.4,700)}shake(t,e){var s;this.calm||(this.shakeEl=document.getElementById("app"),this.shakeAmp=Math.max(this.shakeAmp,t),this.shakeTtl=e,this.shakeAge=0,(s=this.shakeEl)==null||s.classList.add("shaking"),this.startRaf())}chromatic(t,e,s){if(this.calm)return;const i=s??this.boardCenter();this.chromaX=i.x,this.chromaY=i.y,this.chroma=Math.max(this.chroma,t),this.chromaTtl=e,this.chromaAge=0,this.startRaf()}boardRect(){var s;const t=performance.now();if(this.rectCache&&t-this.rectAt<500)return this.rectCache;const e=((s=this.boardEl())==null?void 0:s.getBoundingClientRect())??null;return this.rectCache=e,this.rectAt=t,e}boardCenter(){const t=this.boardRect();return t?{x:t.left+t.width/2,y:t.top+t.height/2}:{x:innerWidth/2,y:innerHeight/2}}startRaf(){this.raf||(this.last=performance.now(),this.rafDeadLogged=!1,this.raf=requestAnimationFrame(this.frame))}stopRaf(){this.raf&&cancelAnimationFrame(this.raf),this.raf=0}restoreContext(){this.ctx2d=this.canvas.getContext("2d"),this.ctxLostAt=0,this.lostProbeAt=0,document.documentElement.classList.remove("fx-dead"),this.resize(),this.canvasDirty=!0,this.startRaf()}lostWatchdog(t){if(!(t-this.ctxLostAt<1200)&&(document.documentElement.classList.contains("fx-dead")||(console.warn("[fx] canvas context lost, contextrestored not received — DOM telegraph fallback armed, watchdog probing for recovery"),document.documentElement.classList.add("fx-dead"),this.canvas.width=this.canvas.width,this.canvas.height=this.canvas.height,this.prevBBox=null,this.bboxFull=!1,this.sweepGrad=null,this.bandGrads=null,this.chromaGrads=null),!(t-this.lostProbeAt<1e3))){this.lostProbeAt=t;try{const e=this.canvas.getContext("2d");if(!e)return;e.setTransform(1,0,0,1,0,0),e.clearRect(0,0,2,2),e.fillStyle="rgba(7,9,11,255)",e.fillRect(0,0,2,2);const s=e.getImageData(0,0,1,1).data;s[3]===255&&s[0]===7&&(console.warn("[fx] watchdog recovered live 2D context (contextrestored never fired)"),this.restoreContext())}catch{}}}runFrame(t){const e=Math.min(2e3,t-this.last);this.last=t,this.step(e);const s=this.wind!==null&&!this.calm,i=this.effects.length>0||this.chroma>.2||s,n=s&&this.effects.length===0&&this.chroma<=.2;if(i&&this.layoutFx(),i&&(!n||t-this.lastWindPaint>=33)?(this.draw(),this.lastWindPaint=t,this.canvasDirty=!0):i||this.canvasDirty&&(this.clearCanvas(),this.canvasDirty=!1),this.shakeAmp>.1){this.shakeAge+=e;const o=Math.max(0,1-this.shakeAge/this.shakeTtl),r=this.shakeAmp*o;this.shakeEl&&(this.shakeEl.style.transform=r<=.1?"":`translate(${(Math.random()*2-1)*r}px, ${(Math.random()*2-1)*r}px) rotate(${(Math.random()*2-1)*r*.06}deg)`,r<=.1&&this.shakeEl.classList.remove("shaking")),this.shakeAge>=this.shakeTtl&&(this.shakeAmp=0,this.shakeEl&&(this.shakeEl.style.transform="",this.shakeEl.classList.remove("shaking")))}this.chroma>.2&&(this.chromaAge+=e,this.chroma=Math.max(0,this.chroma*(1-e/this.chromaTtl)),this.chromaAge>=this.chromaTtl&&(this.chroma=0)),this.effects.length>0||this.shakeAmp>.1||this.chroma>.2||this.wind!==null&&!this.calm?this.raf=requestAnimationFrame(this.frame):(this.stopRaf(),this.clearCanvas(),this.canvasDirty=!1)}step(t){this.stepWind(t);for(let e=this.effects.length-1;e>=0;e--){const s=this.effects[e];if(s.age+=t,s.age>s.ttl){this.effects.splice(e,1);continue}if("vx"in s&&s.age>0){s.vy+=s.grav*t,s.x+=s.vx*t,s.y+=s.vy*t;const i=Math.pow(s.drag,t/16);s.vx*=i,s.vy*=i}}}clearCanvas(){var t;(t=this.ctx2d)==null||t.clearRect(0,0,this.canvas.width,this.canvas.height),this.prevBBox=null,this.bboxFull=!1}draw(){var m,b,x,w;const t=this.ctx2d;if(!t)return;const e=this.canvas.width/this.dpr,s=this.canvas.height/this.dpr;t.setTransform(this.dpr,0,0,this.dpr,-this.boxX*this.dpr,-this.boxY*this.dpr);const i=this.boardRect();let n=1/0,o=1/0,r=-1/0,h=-1/0,d=!1;const c=(f,y,S,k)=>{f<n&&(n=f),y<o&&(o=y),S>r&&(r=S),k>h&&(h=k)};if(this.chroma>.2){const f=M.CHROMA_RADIUS+this.chroma;c(this.chromaX-f,this.chromaY-f,this.chromaX+f,this.chromaY+f)}this.wind!==null&&!this.calm&&i&&c(i.left-16,i.top-16,i.right+16,i.bottom+16);for(const f of this.effects){if(f.age<0)continue;if("peak"in f){if(f.board){i&&c(i.left-8,i.top-8,i.right+8,i.bottom+8);continue}d=!0;break}if("dx"in f){const S=Math.max((i==null?void 0:i.width)??0,(i==null?void 0:i.height)??0)/2+14,k=26,C=((f.age<0?0:Math.min(1,f.age/f.ttl))-.5)*2*S*1.2,B=((i==null?void 0:i.left)??0)+((i==null?void 0:i.width)??e)/2,G=((i==null?void 0:i.top)??0)+((i==null?void 0:i.height)??0)/2,N=Math.hypot(f.dx,f.dy)||1,Q=f.dx/N,tt=f.dy/N;for(const et of[-1,1])for(const st of[-1,1]){const it=B+Q*(C+et*k)-tt*st*S,nt=G+tt*(C+et*k)+Q*st*S;c(it,nt,it,nt)}continue}if("ax"in f){i&&c(i.left-8,i.top-8,i.right+8,i.bottom+8);continue}const y=f.age/f.ttl;if("vx"in f){const S=f.size*(1-y*.5)+1;c(f.x-S,f.y-S,f.x+S,f.y+S)}else{const S=f.r0+(f.r1-f.r0)*(1-Math.pow(1-y,2))+f.width*(1-y*.6)/2+1;c(f.x-S,f.y-S,f.x+S,f.y+S)}}if(d||this.bboxFull)t.clearRect(this.boxX,this.boxY,e,s);else if(r>n&&h>o){let f=Math.min(n,((m=this.prevBBox)==null?void 0:m.x0)??n),y=Math.min(o,((b=this.prevBBox)==null?void 0:b.y0)??o),S=Math.max(r,((x=this.prevBBox)==null?void 0:x.x1)??r),k=Math.max(h,((w=this.prevBBox)==null?void 0:w.y1)??h);f=Math.max(this.boxX,Math.floor(f)),y=Math.max(this.boxY,Math.floor(y)),S=Math.min(this.boxX+e,Math.ceil(S)),k=Math.min(this.boxY+s,Math.ceil(k)),S>f&&k>y&&t.clearRect(f,y,S-f,k-y)}const p=this.chroma>.2;if(p){t.globalCompositeOperation="screen",this.drawChromaGhosts(t);for(const f of this.effects)f.age>=0&&"peak"in f&&this.drawFlash(t,f,i);t.globalCompositeOperation="source-over",t.globalAlpha=1}for(const f of this.effects){if(f.age<0)continue;const y=f.age/f.ttl;if("vx"in f){const S=1-y;t.globalAlpha=S,t.fillStyle=f.color,t.beginPath(),t.arc(f.x,f.y,f.size*(1-y*.5),0,W),t.fill()}else"r1"in f?(t.globalAlpha=(1-y)*.9,t.strokeStyle=f.color,t.lineWidth=f.width*(1-y*.6),t.beginPath(),t.arc(f.x,f.y,f.r0+(f.r1-f.r0)*(1-Math.pow(1-y,2)),0,W),t.stroke()):"dx"in f?this.drawSweep(t,f,y,i):"ax"in f?this.drawBand(t,f,y,i):p||this.drawFlash(t,f,i)}this.wind!==null&&!this.calm&&this.drawWind(t,i),t.globalAlpha=1,d?(this.bboxFull=!0,this.prevBBox=null):r>n&&h>o&&(this.bboxFull=!1,this.prevBBox={x0:Math.max(this.boxX,Math.floor(n)),y0:Math.max(this.boxY,Math.floor(o)),x1:Math.min(this.boxX+e,Math.ceil(r)),y1:Math.min(this.boxY+s,Math.ceil(h))})}drawFlash(t,e,s){t.globalAlpha=e.peak*(1-e.age/e.ttl),t.fillStyle=e.color,e.board&&s?t.fillRect(s.left,s.top,s.width,s.height):t.fillRect(0,0,this.canvas.width/this.dpr,this.canvas.height/this.dpr)}drawChromaGhosts(t){const e=Math.min(.35,this.chroma/30),s=M.CHROMA_RADIUS,i=this.canvas.width/this.dpr,n=this.canvas.height/this.dpr;let o=this.chromaGrads;if(!o||o.x!==this.chromaX||o.y!==this.chromaY){const h=d=>{const c=t.createRadialGradient(this.chromaX,this.chromaY,0,this.chromaX,this.chromaY,s);return c.addColorStop(0,`rgba(${d}, 1)`),c.addColorStop(.55,`rgba(${d}, 0.45)`),c.addColorStop(1,`rgba(${d}, 0)`),c};o={x:this.chromaX,y:this.chromaY,g:[h("255, 40, 60"),h("40, 120, 255")]},this.chromaGrads=o}const r=[this.chroma,-this.chroma];for(let h=0;h<2;h++){const d=r[h],c=this.chromaX+d,p=Math.max(0,c-s)-d,m=Math.min(i,c+s)-d,b=Math.max(0,this.chromaY-s),x=Math.min(n,this.chromaY+s);m<=p||x<=b||(t.save(),t.translate(d,0),t.globalAlpha=e,t.fillStyle=o.g[h],t.fillRect(p,b,m-p,x-b),t.restore())}}drawSweep(t,e,s,i){if(!i)return;const n=14,o=i.left+i.width/2,r=i.top+i.height/2,h=Math.max(i.width,i.height)/2+n,d=(s-.5)*2*h*1.2,c=26,p=Math.sin(Math.PI*s)*.5;if(t.save(),t.translate(o+e.dx*d,r+e.dy*d),t.rotate(Math.atan2(e.dy,e.dx)),!this.sweepGrad){const m=t.createLinearGradient(-c,0,c,0);m.addColorStop(0,"rgba(143, 212, 255, 0)"),m.addColorStop(.5,"rgba(143, 212, 255, 1)"),m.addColorStop(1,"rgba(143, 212, 255, 0)"),this.sweepGrad=m}t.globalAlpha=p,t.fillStyle=this.sweepGrad,t.fillRect(-c,-h,c*2,h*2),t.restore()}drawBand(t,e,s,i){if(!i)return;const n=22,o=Math.sin(Math.PI*s)*.55*(e.strength??1);let r=this.bandGrads;if(!r||r.left!==i.left||r.top!==i.top||r.right!==i.right||r.bottom!==i.bottom){const p=(m,b,x,w)=>{const f=t.createLinearGradient(m,b,x,w);return f.addColorStop(0,"rgba(232, 177, 60, 0)"),f.addColorStop(1,"rgba(232, 177, 60, 1)"),f};r={left:i.left,top:i.top,right:i.right,bottom:i.bottom,g:[p(i.right-n,0,i.right,0),p(i.left+n,0,i.left,0),p(0,i.bottom-n,0,i.bottom),p(0,i.top+n,0,i.top)]},this.bandGrads=r}const h=r.g,d=(p,m,b,x,w,f)=>{t.globalAlpha=o*f,t.fillStyle=h[p],t.fillRect(m,b,x-m,w-b)};e.ax>0&&d(0,i.right-n,i.top,i.right,i.bottom,1),e.ax<0&&d(1,i.left,i.top,i.left+n,i.bottom,1),e.ay>0&&d(2,i.left,i.bottom-n,i.right,i.bottom,1),e.ay<0&&d(3,i.left,i.top,i.right,i.top+n,1);const c=.55;e.ax>0&&d(1,i.left,i.top,i.left+n,i.bottom,c),e.ax<0&&d(0,i.right-n,i.top,i.right,i.bottom,c),e.ay>0&&d(3,i.left,i.top,i.right,i.top+n,c),e.ay<0&&d(2,i.left,i.bottom-n,i.right,i.bottom,c)}drawWind(t,e){const s=this.wind;if(!s||!e)return;const i=Math.hypot(s.dx,s.dy)||1,n=s.dx/i,o=s.dy/i,r=s.frozen?9:10+s.urgency*14,h=s.frozen?.42:.26+s.urgency*.34;t.save(),t.beginPath(),t.rect(e.left-4,e.top-4,e.width+8,e.height+8),t.clip(),t.strokeStyle=s.frozen?"rgb(79, 210, 255)":"rgb(232, 177, 60)",t.lineWidth=s.frozen?2.6:2+s.urgency*1.1,t.lineCap="round",t.globalAlpha=h,t.beginPath();for(const d of s.dots){const c=e.left+d.u*e.width,p=e.top+d.v*e.height;t.moveTo(c-n*r/2,p-o*r/2),t.lineTo(c+n*r/2,p+o*r/2)}t.stroke(),t.restore()}};l(M,"BOARD_MARGIN",32),l(M,"CHROMA_RADIUS",260),l(M,"WIND_DOTS",30);let U=M;class qt{constructor(t){l(this,"muted",!1);l(this,"onChanged",null);l(this,"ctx",null);l(this,"master",null);l(this,"broken",!1);this.button=t,this.button&&(this.button.addEventListener("click",()=>this.toggle()),this.render())}setMuted(t){this.muted=t,this.render()}toggle(){var t;this.muted=!this.muted,this.render(),this.muted||this.play("ui"),(t=this.onChanged)==null||t.call(this,this.muted)}render(){this.button&&(this.button.classList.toggle("muted",this.muted),this.button.setAttribute("aria-pressed",String(this.muted)),this.button.title=this.muted?"Sound off — click to unmute":"Sound on — click to mute")}ensure(){if(this.muted||this.broken)return null;if(this.ctx)return this.ctx.state==="suspended"&&this.ctx.resume().catch(()=>{}),this.ctx;try{const t=window.AudioContext??window.webkitAudioContext;if(!t)throw new Error("no AudioContext");return this.ctx=new t,this.master=this.ctx.createGain(),this.master.gain.value=.5,this.master.connect(this.ctx.destination),this.ctx}catch{return this.broken=!0,null}}tone(t,e){const{type:s="sine",f0:i,f1:n=i,dur:o}=e,r=e.t??0,h=t.createOscillator(),d=t.createGain();h.type=s;const c=t.currentTime;h.frequency.setValueAtTime(Math.max(20,i),c+r),h.frequency.exponentialRampToValueAtTime(Math.max(20,n),c+r+o),d.gain.setValueAtTime(1e-4,c+r),d.gain.exponentialRampToValueAtTime(e.gain??.25,c+r+.008),d.gain.exponentialRampToValueAtTime(1e-4,c+r+o),h.connect(d).connect(this.master),h.start(c+r),h.stop(c+r+o+.05)}noise(t,e){const{dur:s,filter:i="lowpass",f0:n,f1:o=n,q:r=1}=e,h=e.t??0,d=t.currentTime,c=Math.max(1,Math.floor(t.sampleRate*s)),p=t.createBuffer(1,c,t.sampleRate),m=p.getChannelData(0);for(let f=0;f<c;f++)m[f]=Math.random()*2-1;const b=t.createBufferSource();b.buffer=p;const x=t.createBiquadFilter();x.type=i,x.Q.value=r,x.frequency.setValueAtTime(Math.max(20,n),d+h),x.frequency.exponentialRampToValueAtTime(Math.max(20,o),d+h+s);const w=t.createGain();w.gain.setValueAtTime(e.gain??.4,d+h),w.gain.exponentialRampToValueAtTime(1e-4,d+h+s),b.connect(x).connect(w).connect(this.master),b.start(d+h),b.stop(d+h+s+.05)}play(t){const e=this.ensure();if(!(!e||!this.master))try{switch(t){case"reveal":{const s=760+Math.random()*240;this.tone(e,{type:"triangle",f0:s,f1:s*.6,dur:.09,gain:.1});break}case"flag":this.tone(e,{type:"square",f0:420,f1:660,dur:.07,gain:.1}),this.tone(e,{type:"square",f0:880,dur:.05,t:.06,gain:.05});break;case"unflag":this.tone(e,{type:"square",f0:520,f1:220,dur:.09,gain:.09});break;case"shift":this.noise(e,{dur:.28,gain:.12,f0:900,f1:180,filter:"bandpass",q:1.4}),this.tone(e,{type:"sine",f0:130,f1:90,dur:.22,gain:.12});break;case"shiftWarn":this.tone(e,{type:"square",f0:520,f1:780,dur:.1,gain:.12}),this.tone(e,{type:"square",f0:920,dur:.07,t:.13,gain:.09});break;case"awaken":{this.tone(e,{type:"sine",f0:74,f1:30,dur:1.5,gain:.38}),this.noise(e,{dur:1.4,gain:.22,f0:220,f1:32}),this.tone(e,{type:"sawtooth",f0:55,f1:52,dur:1.7,t:.12,gain:.05}),this.tone(e,{type:"sawtooth",f0:58.3,f1:55.3,dur:1.7,t:.12,gain:.05}),this.tone(e,{type:"triangle",f0:320,f1:226,dur:.6,t:.55,gain:.045});break}case"boom":this.noise(e,{dur:.7,gain:.6,f0:1600,f1:40}),this.tone(e,{type:"sawtooth",f0:150,f1:28,dur:.6,gain:.4}),this.tone(e,{type:"sine",f0:60,f1:24,dur:.9,t:.05,gain:.5});break;case"shielded":this.noise(e,{dur:.22,gain:.18,f0:500,f1:120}),this.tone(e,{type:"sine",f0:660,dur:.18,gain:.16}),this.tone(e,{type:"sine",f0:990,dur:.26,t:.07,gain:.14});break;case"ability":this.tone(e,{type:"square",f0:440,f1:880,dur:.09,gain:.08}),this.tone(e,{type:"square",f0:660,f1:1320,dur:.09,t:.07,gain:.06});break;case"deny":this.tone(e,{type:"sawtooth",f0:180,f1:120,dur:.12,gain:.1});break;case"clear":{[523.25,659.25,783.99,1046.5].forEach((i,n)=>this.tone(e,{type:"triangle",f0:i,dur:.3,t:n*.09,gain:.14}));break}case"ui":this.tone(e,{type:"triangle",f0:880,dur:.05,gain:.08});break}}catch{}}}const Xt="naddr1qvzqqqyf8ypzp4x932pla2wx4vc2575xsyxk7nkhdt8zk9p9705gn5kgt6spjtc7qqxkutt9x3nxyd3nxsckxcmyswuskf",Vt={pubkey:"60517f64548279854ee9a122531e4d1a413e3dc956e2d0a450217420623ca688",relays:["wss://relay.napplet.soy/"]},J={napplet:Xt,provider:Vt},Yt="depth-v1",St={napplet:J.napplet,board:Yt},jt="unnamed-runner";function L(){return z("cvm")&&!0}async function kt(a,t={}){const e=J.provider?await at.callTool(J.provider,a,t):await at.registry.call("soy.boards.v1",a,t);if(e.isError){const s=(e.content??[]).filter(i=>i.type==="text").map(i=>i.text).join(`
`);throw new Error(s||"Backend request failed")}return e.structuredContent??{}}async function Ut(a,t){if(!L())return{ok:!1,reason:"unavailable"};if(!Number.isInteger(a)||a<1)return{ok:!1,reason:"no-clear"};const e=t.trim().slice(0,24).trim()||jt;try{const s=await kt("soy_board_submit",{...St,score:a,name:e});return s.error?{ok:!1,reason:String(s.error)}:{ok:!0,score:a,better:s.better!==!1}}catch{return{ok:!1,reason:"offline"}}}async function mt(a=20,t){if(!L())return{ok:!1,reason:"unavailable"};try{const e=await kt("soy_board_read",{...St,limit:a,...typeof t=="number"?{offset:t}:{}});return{rows:e.rows??[],own:e.own??null,trust:String(e.trust??"client-reported"),...typeof e.nextOffset=="number"?{nextOffset:e.nextOffset}:{}}}catch{return{ok:!1,reason:"offline"}}}const gt="minesweeper-dx.save.v1",bt="minesweeper-dx.settings.v1",K=1;function $(){return{version:K,scrap:0,depth:0,name:"",name_prompted:!1,upgrades:{...Z}}}const I={radar:"radar_lvl",seeker:"seeker_lvl",shield:"shield_lvl",stabilizer:"stabilizer_lvl",economy:"economy_multiplier_lvl",flow_cap:"flow_cap_lvl"};function Jt(a,t){const e=typeof a=="number"&&Number.isFinite(a)?Math.floor(a):0;return Math.max(0,Math.min(t,e))}function Zt(a){if(typeof a!="object"||a===null)return $();const t=a,e=t.upgrades??{},s={...Z};for(const n of Object.keys(A))s[n]=Jt(e[I[n]],A[n].maxLevel);const i=n=>typeof n=="number"&&Number.isFinite(n)?Math.max(0,Math.floor(n)):0;return{version:K,scrap:i(t.scrap),depth:i(t.depth),name:typeof t.name=="string"?t.name.trim().slice(0,24).trim():"",name_prompted:t.name_prompted===!0,upgrades:s}}class _{constructor(t=null,e=t!==null){l(this,"enabled");l(this,"port");l(this,"lastError",null);this.port=t,this.enabled=e}static fromRuntime(){return z("storage")?new _({getItem:t=>rt.getItem(t),setItem:(t,e)=>rt.setItem(t,e)},!0):new _(null,!1)}get error(){return this.lastError}async load(){if(!this.enabled||!this.port)return $();try{const t=await this.port.getItem(gt);return t===null?$():Zt(JSON.parse(t))}catch(t){return this.lastError=t instanceof Error?t.message:String(t),$()}}async loadSettings(){if(!this.enabled||!this.port)return{muted:!1};try{const t=await this.port.getItem(bt);if(t===null)return{muted:!1};const e=JSON.parse(t);return{muted:typeof(e==null?void 0:e.muted)=="boolean"?e.muted:!1}}catch{return{muted:!1}}}async saveSettings(t){if(!(!this.enabled||!this.port))try{await this.port.setItem(bt,JSON.stringify({muted:t.muted}))}catch(e){this.lastError=e instanceof Error?e.message:String(e)}}async save(t){if(!this.enabled||!this.port)return!1;try{const e={version:K,scrap:t.scrap,depth:t.depth,name:t.name,name_prompted:t.name_prompted,upgrades:{[I.radar]:t.upgrades.radar,[I.seeker]:t.upgrades.seeker,[I.shield]:t.upgrades.shield,[I.stabilizer]:t.upgrades.stabilizer,[I.economy]:t.upgrades.economy,[I.flow_cap]:t.upgrades.flow_cap}};return await this.port.setItem(gt,JSON.stringify(e)),!0}catch(e){return this.lastError=e instanceof Error?e.message:String(e),!1}}}const Kt=10;function Qt(a,t){return Math.round(Kt*Math.pow(1.1,a)*_t(t))}function te(a){const t=Math.floor(a/2);return{salvaged:t,destroyed:a-t}}class ee{constructor(t=$()){l(this,"save");l(this,"runScrap",0);l(this,"runDepth",0);l(this,"runClears",0);this.save=t}get scrap(){return this.save.scrap}get levels(){return this.save.upgrades}abilityLevels(){return{...this.save.upgrades}}startRun(){this.runScrap=0,this.runDepth=0,this.runClears=0}creditClear(t){this.runDepth=t,this.runClears+=1;const e=Qt(t,this.levels);return this.runScrap+=e,t+1>this.save.depth&&(this.save.depth=t+1),e}bank(){const t=this.runScrap;return this.save.scrap+=t,this.runScrap=0,t}endRunOnDeath(){const{salvaged:t,destroyed:e}=te(this.runScrap);this.save.scrap+=t;const s=this.runScrap;return this.runScrap=0,{earned:s,salvaged:t,destroyed:e}}canAfford(t){const e=Y(t,this.levels[t]);return e!==null&&this.scrap>=e}purchase(t){if(!A[t])return{ok:!1,reason:"unknown"};const s=this.levels[t]??0,i=Y(t,s);return i===null?{ok:!1,reason:"maxed",level:s}:this.scrap<i?{ok:!1,reason:"affordable",level:s,cost:i}:(this.save.scrap-=i,this.save.upgrades[t]=s+1,{ok:!0,level:s+1,cost:i})}get everythingMaxed(){return Object.keys(lt).every(t=>this.levels[t]>=lt[t])}}z("storage");const se={MINE_ON_REVEALED:"Shield charge absorbed the blast",MINE_ON_MINE:"Shield charge absorbed a mine collision",MINE_ON_FIRE:"Shield charge smothered a burning mine"},ie={MINE_ON_REVEALED:"mine detonated",MINE_ON_MINE:"mines collided",MINE_ON_FIRE:"mine ignited"};function ne(a){return se[a]??"Shield charge absorbed the blast"}function wt(a){return a&&ie[a]||"fatal blast"}const q=["Boom","KA-BLOOEY!","Well, That Wasn't In The Plan","Scrapped!","Mmm... Crispy","That Tile Looked Safe","Oops. All Mines","The Deep Collects Its Due","Should've Banked"];function g(a){const t=document.querySelector(a);if(!t)throw new Error(`missing element: ${a}`);return t}const u={boardHost:g("#boardHost"),depth:g("#depth"),turn:g("#turn"),flow:g("#flow"),runScrap:g("#runScrap"),scrap:g("#scrap"),shiftStat:g("#shiftStat"),shiftArrow:g("#shiftArrow"),shiftIn:g("#shiftIn"),shifterIntro:g("#shifterIntro"),shifterIntroOk:g("#shifterIntroOk"),shieldStat:g("#shieldStat"),shieldCount:g("#shieldCount"),status:g("#status"),sfxToggle:g("#sfxToggle"),helpToggle:g("#helpToggle"),abilityButtons:Array.from(document.querySelectorAll("[data-ability]")),menuScreen:g("#menuScreen"),menuStats:g("#menuStats"),startRunBtn:g("#startRunBtn"),menuWorkshopBtn:g("#menuWorkshopBtn"),menuFieldManualBtn:g("#menuFieldManualBtn"),menuLeaderboardBtn:g("#menuLeaderboardBtn"),saveNote:g("#saveNote"),decisionScreen:g("#decisionScreen"),decisionTitle:g("#decisionTitle"),decisionBody:g("#decisionBody"),bankBtn:g("#bankBtn"),continueBtn:g("#continueBtn"),summaryScreen:g("#summaryScreen"),summaryTitle:g("#summaryTitle"),summaryCause:g("#summaryCause"),summaryLedger:g("#summaryLedger"),playerNameInput:g("#playerNameInput"),nameGate:g("#nameGate"),nameGateInput:g("#nameGateInput"),nameGateOk:g("#nameGateOk"),summaryBoardNote:g("#summaryBoardNote"),summaryWorkshopBtn:g("#summaryWorkshopBtn"),summaryBoardBtn:g("#summaryBoardBtn"),summaryMenuBtn:g("#summaryMenuBtn"),boardScreen:g("#boardScreen"),boardRows:g("#boardRows"),boardNote:g("#boardNote"),boardRefreshBtn:g("#boardRefreshBtn"),boardCloseBtn:g("#boardCloseBtn"),shopScreen:g("#shopScreen"),helpScreen:g("#helpScreen"),helpTabButtons:{basics:g("#helpTabBasics"),hazards:g("#helpTabHazards"),tools:g("#helpTabTools")},helpPanels:{basics:g("#helpPanelBasics"),hazards:g("#helpPanelHazards"),tools:g("#helpPanelTools")},helpBackBtn:g("#helpBackBtn"),shopScrap:g("#shopScrap"),upgradeList:g("#upgradeList"),shopStartBtn:g("#shopStartBtn"),shopBackBtn:g("#shopBackBtn")},xt={"1,0":"→","-1,0":"←","0,1":"↓","0,-1":"↑","1,1":"↘","1,-1":"↗","-1,1":"↙","-1,-1":"↖"};class re{constructor(){l(this,"board");l(this,"progression",new ee);l(this,"saves");l(this,"sfx");l(this,"fx");l(this,"shifterIntroShown",!1);l(this,"phase","menu");l(this,"deathTitles",q);l(this,"shopReturn","menu");l(this,"boardReturn","menu");l(this,"lastBoardReadMs",0);l(this,"pendingPost",null);l(this,"clearCredited",!1);this.board=new Wt(u.boardHost,t=>this.sync(t),t=>{this.applyUpgrades(t),t.shieldCharges=t.upgrades.shield}),this.saves=_.fromRuntime(),this.sfx=new qt(u.sfxToggle),this.sfx.onChanged=t=>{this.saves.saveSettings({muted:t})},this.fx=new U(g("#app"),t=>{const e=document.querySelector(`.cell[data-cell="${t}"]`);if(!e)return null;const s=e.getBoundingClientRect();return{x:s.left+s.width/2,y:s.top+s.height/2}},()=>document.querySelector(".board"),this.sfx,t=>{var e;t.kind==="boom"&&!t.shielded&&((e=u.summaryScreen.querySelector(".panel-anim"))==null||e.classList.add("death-blast"))}),this.board.onFx=t=>{if(this.fx.event(t),t.kind==="boom"&&t.shielded&&this.setStatus(ne(t.cause),"warn"),t.kind==="shift-warning"){const e=xt[`${t.dx},${t.dy}`]??"?";this.setStatus(`⚠ Mines drift ${e} in ${t.turns} turn${t.turns===1?"":"s"}`,"warn")}},u.shifterIntroOk.addEventListener("click",()=>{u.shifterIntro.hidden=!0}),this.bindAbilities(),this.bindScreens(),this.board.onAction=t=>this.checkTransition(t),this.boot()}async boot(){const t=await this.saves.load();this.progression.save=t;const e=await this.saves.loadSettings();this.sfx.setMuted(e.muted),this.showScreen("menu"),this.renderMenu(),this.saves.enabled||(u.saveNote.hidden=!1,u.saveNote.textContent="No storage domain from the host — progress lasts for this page only.");const s=L();u.menuLeaderboardBtn.hidden=!s,u.summaryBoardBtn.hidden=!s,u.playerNameInput.value=this.progression.save.name}applyUpgrades(t){t.upgrades=this.progression.abilityLevels(),t.onBoardCleared=e=>this.progression.creditClear(e)}startRun(){if(L()&&!this.progression.save.name_prompted){u.nameGateInput.value=this.progression.save.name,u.nameGate.hidden=!1,u.nameGateInput.focus();return}this.progression.startRun(),this.clearCredited=!1,this.shifterIntroShown=!1,u.shifterIntro.hidden=!0,this.fx.reset(),this.sfx.play("ui"),this.board.newRun(),this.showScreen("running"),this.phase="running",this.setStatus("Run started — clear the sector","ok"),this.sync(this.board.game)}persist(){this.saves.save(this.progression.save)}commitNameGate(){this.progression.save.name=u.nameGateInput.value.trim().slice(0,24).trim(),this.progression.save.name_prompted=!0,this.persist(),u.nameGate.hidden=!0,u.playerNameInput.value=this.progression.save.name,this.startRun()}showScreen(t){var e;this.pendingPost!==null&&t!=="summary"&&this.flushPendingPost(),t!=="summary"&&((e=u.summaryScreen.querySelector(".panel-anim"))==null||e.classList.remove("death-blast")),u.menuScreen.hidden=t!=="menu",u.decisionScreen.hidden=t!=="decision",u.summaryScreen.hidden=t!=="summary",u.shopScreen.hidden=t!=="shop",u.boardScreen.hidden=t!=="board"}renderMenu(){const t=this.progression;u.menuStats.textContent=`⛃ ${t.scrap} scrap banked · deepest depth ${t.save.depth}`,u.startRunBtn.textContent="Enter The Depths"}renderSummary(t,e,s){u.summaryTitle.textContent=t,u.summaryCause.textContent=e,u.summaryLedger.innerHTML=s.map(([i,n])=>`<div class="ledger-row"><dt>${i}</dt><dd>${n}</dd></div>`).join("")}postRunEnd(){if(u.playerNameInput.value=this.progression.save.name,this.pendingPost=null,!L()){u.summaryBoardNote.hidden=!1,u.summaryBoardNote.textContent="Leaderboard unavailable on this host.";return}if(this.progression.runClears<1){u.summaryBoardNote.hidden=!0;return}this.pendingPost=this.progression.runDepth+1,u.summaryBoardNote.hidden=!1,u.summaryBoardNote.textContent="Add a runner name — posts when you leave, or press Enter to post now."}flushPendingPost(){const t=this.pendingPost;this.pendingPost=null,!(t===null||!L())&&(this.progression.save.name=u.playerNameInput.value.trim().slice(0,24).trim(),u.playerNameInput.value=this.progression.save.name,this.persist(),u.summaryBoardNote.hidden=!1,u.summaryBoardNote.textContent="Posting score…",Ut(t,this.progression.save.name).then(async e=>{var n;if(!e.ok||this.phase!=="summary"){e.ok===!1&&this.phase==="summary"&&(u.summaryBoardNote.textContent=`Score not posted (${e.reason}).`);return}const s=await mt(20);if("ok"in s||this.phase!=="summary"){u.summaryBoardNote.textContent=`Deepest depth ${t} posted.`;return}const i=s.rows.findIndex(o=>{var r;return o.actor===((r=s.own)==null?void 0:r.actor)});u.summaryBoardNote.textContent=i>=0?`#${i+1} · Deepest Depth ${((n=s.own)==null?void 0:n.score)??t}`:`Deepest depth ${t} posted.`}))}loadBoard(t=!0){const e=Date.now();t&&e-this.lastBoardReadMs<2e3||(this.lastBoardReadMs=e,u.boardNote.textContent="Loading…",u.boardRows.textContent="",mt(20).then(s=>{if(!u.boardScreen.hidden){if("ok"in s){u.boardNote.textContent=s.reason==="unavailable"?"Leaderboard unavailable on this host.":"Board unreachable — check the connection, then Refresh.";return}if(s.rows.length===0){u.boardNote.textContent="No scores yet — be the first down here.";return}u.boardNote.textContent=`Top ${s.rows.length} · ${s.trust}`,u.boardRows.textContent="",s.rows.forEach((i,n)=>{const o=document.createElement("div");o.className="ledger-row",s.own&&i.actor===s.own.actor&&o.classList.add("own");const r=document.createElement("dt");r.textContent=`#${n+1} ${i.name||"unnamed-runner"}`;const h=document.createElement("dd");h.textContent=String(i.score),o.append(r,h),u.boardRows.append(o)})}}))}openBoard(t){this.boardReturn=t,this.showScreen("board"),this.loadBoard(!1)}openHelp(t){this.setHelpTab(t),u.helpScreen.hidden=!1,this.sfx.play("ui")}setHelpTab(t){for(const e of["basics","hazards","tools"]){const s=e===t;u.helpTabButtons[e].classList.toggle("active",s),u.helpTabButtons[e].setAttribute("aria-selected",String(s)),u.helpPanels[e].hidden=!s}}bindScreens(){u.startRunBtn.addEventListener("click",()=>this.startRun()),u.menuWorkshopBtn.addEventListener("click",()=>this.openShop("menu")),u.menuFieldManualBtn.addEventListener("click",()=>this.openHelp("basics")),u.helpToggle.addEventListener("click",()=>this.openHelp("tools"));for(const t of["basics","hazards","tools"])u.helpTabButtons[t].addEventListener("click",()=>this.setHelpTab(t));u.helpBackBtn.addEventListener("click",()=>{u.helpScreen.hidden=!0}),u.summaryWorkshopBtn.addEventListener("click",()=>this.openShop("summary")),u.summaryMenuBtn.addEventListener("click",()=>{this.phase="menu",this.showScreen("menu"),this.renderMenu()}),u.menuLeaderboardBtn.addEventListener("click",()=>this.openBoard("menu")),u.summaryBoardBtn.addEventListener("click",()=>this.openBoard("summary")),u.boardRefreshBtn.addEventListener("click",()=>this.loadBoard()),u.boardCloseBtn.addEventListener("click",()=>{this.showScreen(this.boardReturn),this.boardReturn==="menu"&&this.renderMenu()}),u.playerNameInput.addEventListener("change",()=>{this.progression.save.name=u.playerNameInput.value.trim().slice(0,24).trim(),u.playerNameInput.value=this.progression.save.name,this.persist()}),u.playerNameInput.addEventListener("keydown",t=>{t.key==="Enter"&&this.flushPendingPost()}),u.bankBtn.addEventListener("click",()=>{const t=this.progression.bank();this.persist(),this.renderSummary("Banked",`You walked out of depth ${this.board.game.depth} with the scrap.`,[["Boards cleared",String(this.progression.runClears)],["Scrap banked",`+${t} ⛃`],["Vault total",`${this.progression.scrap} ⛃`]]),this.phase="summary",this.showScreen("summary"),this.postRunEnd(),this.setStatus(`Banked ${t} scrap`,"ok")}),u.continueBtn.addEventListener("click",()=>{const t=this.board.game;t.onBoardCleared=null,t.advance(),this.applyUpgrades(t),this.clearCredited=!1,this.persist(),this.phase="running",this.showScreen("running"),this.setStatus(`Depth ${t.depth} — mines ahead`,"idle"),this.board.rebuild(),this.sync(t)}),u.shopStartBtn.addEventListener("click",()=>this.startRun()),u.nameGateOk.addEventListener("click",()=>this.commitNameGate()),u.nameGateInput.addEventListener("keydown",t=>{t.key==="Enter"&&this.commitNameGate()}),u.shopBackBtn.addEventListener("click",()=>{this.phase=this.shopReturn,this.showScreen(this.shopReturn),this.shopReturn==="menu"&&this.renderMenu()})}openShop(t){this.shopReturn=t,this.phase="shop",this.showScreen("shop"),this.renderShop()}renderShop(){u.shopScrap.textContent=String(this.progression.scrap),u.upgradeList.innerHTML=Ft.map(({group:t,ids:e})=>{const s=e.map(i=>this.renderShopCard(i)).join("");return`<section class="shop-group" data-group="${t}">
<h3 class="shop-group-title">${t}</h3>
<div class="shop-cards">${s}</div>
</section>`}).join("");for(const t of u.upgradeList.querySelectorAll("[data-buy]"))t.addEventListener("click",()=>{const e=t.dataset.buy,s=this.progression.purchase(e);s.ok&&(this.applyUpgrades(this.board.game),this.persist(),this.setStatus(`${A[e].name} → Lv.${s.level}`,"ok"),this.renderShop())})}renderShopCard(t){const e=A[t],s=this.progression.levels[t],i=Y(t,s),n=i===null,o=i!==null&&this.progression.scrap>=i,r=s===0?"locked":"unlocked",h=n?"maxed":o?"affordable":"unaffordable",d=Array.from({length:e.maxLevel},(c,p)=>`<span class="pip${p<s?" pip-on":""}"></span>`).join("");return`<div class="upgrade-row ${r} ${h}" data-upgrade="${t}">
<div class="upgrade-info">
<div class="upgrade-name">
<span class="upgrade-title">${e.name}</span>
<span class="upgrade-lvl">Lv.${s}</span>
<span class="upgrade-pips" aria-hidden="true">${d}</span>
</div>
<div class="upgrade-desc">${e.describe(s)}</div>
</div>
<button type="button" class="buy-btn" data-buy="${t}"
${n||!o?"disabled":""}
aria-label="${n?`${e.name} maxed`:`Buy ${e.name} level ${s+1} for ${i} scrap`}">
${n?"MAX":`Buy ${i} ⛃`}
</button>
</div>`}bindAbilities(){for(const t of u.abilityButtons)t.addEventListener("click",()=>{var n;const e=t.dataset.ability;if(!e||this.phase!=="running")return;const s=this.board.game;if(e==="radar"){if(this.board.pendingTarget)this.board.pendingTarget=null,this.setStatus("Radar cancelled","idle");else{const o=R.radar,r=s.cooldowns.get("radar")??0;s.isUnlocked("radar")?s.flow<o.cost||r>0?(this.setStatus("Ability unavailable (Flow or cooldown)","warn"),this.fx.event({kind:"deny"})):(this.board.pendingTarget=(h,d)=>{const c=s.radar(h,d);return c?this.setStatus(`Radar fired at ${h},${d}`,"ok"):this.setStatus("Ability unavailable (Flow or cooldown)","warn"),c},this.setStatus("Radar armed — click a tile to target","ok")):this.setStatus("Radar locked — buy it in the Workshop","warn")}this.board.refresh(),this.sync(s);return}let i=!1;e==="seeker"?i=s.seeker():e==="stabilizer"&&(i=s.stabilizer()),i?this.sfx.play("ability"):s.isUnlocked(e)?(this.setStatus("Ability unavailable (Flow or cooldown)","warn"),this.fx.event({kind:"deny"})):this.setStatus(`${(n=e[0])==null?void 0:n.toUpperCase()}${e.slice(1)} locked — buy it in the Workshop`,"warn"),this.board.refresh(),this.sync(s)})}checkTransition(t){if(this.phase!=="running")return!1;if(t.isDead()){this.phase="summary",this.setStatus(`Boom — ${wt(t.explosionCause())}`,"error");const{earned:e,salvaged:s,destroyed:i}=this.progression.endRunOnDeath();return this.persist(),this.renderSummary(q[Math.floor(Math.random()*q.length)],`Depth ${t.depth} · ${wt(t.explosionCause())}. Half the unbanked scrap was destroyed in the blast.`,[["Boards cleared",String(this.progression.runClears)],["Run scrap",`${e} ◈`],["Salvaged (−50%)",`+${s} ⛃`],["Destroyed",`−${i} ◈`],["Vault total",`${this.progression.scrap} ⛃`]]),this.showScreen("summary"),this.postRunEnd(),!0}if(t.isBoardCleared&&!this.clearCredited){const e=t.world.tiles.filter(i=>i.state==="REVEALED").map(i=>`${i.coord[0]},${i.coord[1]}`);this.fx.celebrate(e),this.clearCredited=!0,this.phase="decision";const s=this.progression.creditClear(t.depth);return this.persist(),this.setStatus(`Sector ${t.depth} clear`,"ok"),u.decisionTitle.textContent=`Sector ${t.depth} Cleared`,u.decisionBody.textContent=`+${s} scrap salvaged. You're hauling ${this.progression.runScrap} total. The deeper sectors run richer — and more dangerous.`,this.sync(t),this.showScreen("decision"),!0}return!1}setStatus(t,e){u.status.textContent=t,u.status.className=`status status-${e}`}hudBump(t,e){t.textContent!==e&&(t.textContent=e,t.classList.remove("stat-pop"),requestAnimationFrame(()=>requestAnimationFrame(()=>t.classList.add("stat-pop"))))}sync(t){if(this.hudBump(u.depth,String(t.depth)),this.hudBump(u.turn,String(t.turn)),this.hudBump(u.flow,`${t.flow}/${t.flowCap()}`),this.hudBump(u.runScrap,String(this.progression.runScrap)),this.hudBump(u.scrap,String(this.progression.scrap)),t.telegraph?(u.shiftStat.hidden=!1,u.shiftArrow.textContent=xt[`${t.telegraph[0]},${t.telegraph[1]}`]??"?",u.shiftIn.textContent=t.stabilizedFor>0?`held ${t.stabilizedFor}`:String(Math.max(0,t.nextShiftIn))):u.shiftStat.hidden=!0,t.telegraph&&this.phase==="running"){this.board.root.classList.add("board-shift-telegraph");const e=D(t.depth),s=t.stabilizedFor>0?1:Math.min(1,Math.max(0,1-(t.nextShiftIn-1)/Math.max(1,e-1)));this.fx.setTelegraph({dx:t.telegraph[0],dy:t.telegraph[1],urgency:s,frozen:t.stabilizedFor>0}),this.shifterIntroShown||(this.shifterIntroShown=!0,u.shifterIntro.hidden=!1,u.shifterIntroOk.focus(),this.sfx.play("awaken"),this.fx.introRumble())}else this.fx.setTelegraph(null),this.board.root.classList.remove("board-shift-telegraph");u.shieldStat.hidden=t.shieldCharges===0,u.shieldCount.textContent=`${t.shieldCharges}/${t.upgrades.shield}`;for(const e of u.abilityButtons){const s=e.dataset.ability,i=R[s],n=t.cooldowns.get(s)??0,o=e.querySelector(`.cd[data-cd="${s}"]`);o&&(o.textContent=n>0?`⏳${n}`:"");const r=!t.isUnlocked(s),h=e.querySelector(".cost");h&&(h.textContent=r?"🔒":`${i.cost}f`),e.classList.toggle("ability-locked",r),e.disabled=this.phase!=="running"||r||t.flow<i.cost||n>0,e.classList.toggle("armed",s==="radar"&&this.board.pendingTarget!==null)}this.checkTransition(t)}}const ae=new re;window.minesweeperDx=ae;window.countPulseCap=j;
</script>
<style>:root{--bg: #14181d;--panel: #1c222a;--grid: #232b35;--fg: #d7dee7;--muted: #7d8896;--border: #303b48;--primary: #e8b13c;--safe: #46586c;--safe-hi: #5a7188;--ice: #4fd2ff;--fire: #ff7a29;--mine: #e5484d;--flag: #e8b13c;--focus: #f2f5f8;--space-1: 4px;--space-2: 8px;--space-3: 12px;--control-h: 30px;--board-min: 120px;--radius: 4px;color-scheme:dark;font:13px/1.4 SFMono-Regular,Consolas,Liberation Mono,monospace}*{box-sizing:border-box}:where(html,body,body *){-webkit-user-select:none;user-select:none}html,body,#app{height:100%;margin:0;background:var(--bg);color:var(--fg)}body:before{content:"";position:fixed;top:-20%;right:-20%;bottom:-20%;left:-20%;z-index:-2;background:radial-gradient(60% 55% at 18% 88%,rgba(232,177,60,.055),transparent 70%),radial-gradient(70% 60% at 85% 10%,rgba(79,210,255,.05),transparent 70%),radial-gradient(90% 80% at 50% 45%,rgba(28,42,58,.55),transparent 100%);animation:bgDrift 36s ease-in-out infinite alternate;pointer-events:none}body:after{content:"";position:fixed;top:0;right:0;bottom:0;left:0;z-index:-1;background-image:linear-gradient(rgba(125,155,185,.045) 1px,transparent 1px),linear-gradient(90deg,rgba(125,155,185,.045) 1px,transparent 1px);background-size:26px 26px;mask-image:radial-gradient(120% 100% at 50% 40%,#000 30%,transparent 100%);-webkit-mask-image:radial-gradient(120% 100% at 50% 40%,#000 30%,transparent 100%);pointer-events:none}@keyframes bgDrift{to{transform:translate3d(2.5%,-2%,0) scale(1.06)}}#app{position:relative}#app.shaking{will-change:transform}#fx{position:fixed;top:0;right:0;bottom:0;left:0;z-index:9;pointer-events:none}button{font:inherit;color:inherit}.app{container-type:size;display:grid;grid-template-rows:auto minmax(0,1fr) auto;gap:var(--space-2);padding:var(--space-2);overflow:hidden}.hud{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-1) var(--space-3);min-height:var(--control-h);padding:0 var(--space-1);border:1px solid color-mix(in srgb,var(--border) 60%,transparent);border-radius:var(--radius);background:linear-gradient(180deg,color-mix(in srgb,var(--panel) 55%,transparent),transparent);box-shadow:0 0 24px #4fd2ff09,inset 0 1px #ffffff08}.stat-pop{display:inline-block;animation:statPop .32s ease-out}@keyframes statPop{0%{transform:scale(1.4)}to{transform:scale(1)}}.icon-btn{font:inherit;height:22px;padding:0 var(--space-2);border:1px solid color-mix(in srgb,var(--border) 70%,transparent);border-radius:999px;background:var(--panel);color:var(--muted);line-height:1;cursor:pointer}.icon-btn:hover{color:var(--fg);border-color:var(--primary)}.stat{color:var(--muted);font-size:12px;white-space:nowrap}.stat b{color:var(--fg);font-weight:700}.stat-key{display:none}@container (min-width: 360px){.stat-key{display:inline}}.stat-shift{color:var(--primary)}.stat-shift b{color:var(--primary);text-shadow:0 0 10px rgba(232,177,60,.45)}.stat-shield{color:var(--ice)}.status{margin-left:auto;padding:0 var(--space-2);border-radius:999px;font-size:12px;font-weight:600;line-height:22px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.status-idle{color:var(--muted);background:var(--panel)}.status-ok{color:#7ee2a0;background:color-mix(in srgb,#7ee2a0 14%,var(--bg))}.status-warn{color:var(--primary);background:color-mix(in srgb,var(--primary) 16%,var(--bg))}.status-error{color:var(--mine);background:color-mix(in srgb,var(--mine) 16%,var(--bg))}.board-host{display:grid;place-items:center;min-height:0;min-width:0}.intro-modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:12;display:grid;place-items:center;background:#080a0d9e}.intro-modal[hidden]{display:none}.intro-panel{display:flex;flex-direction:column;gap:10px;max-width:min(92vw,430px);padding:16px 20px;border-radius:12px;background:#120e07f7;border:1px solid var(--primary);color:#f2d89b;font-size:13px;line-height:1.5;text-align:center;box-shadow:0 8px 30px #0009;animation:intro-drop .32s ease-out}.intro-panel b{color:var(--primary);letter-spacing:.06em}.intro-panel button,.intro-panel .summary-name{align-self:center}@keyframes intro-drop{0%{opacity:0;transform:translateY(-10px)}to{opacity:1;transform:translateY(0)}}.cell-count-pulse:before{content:"";position:absolute;top:-1px;right:-1px;bottom:-1px;left:-1px;z-index:3;border-radius:2px;background:#e8b13c42;box-shadow:inset 0 0 0 1px #e8b13ce6,0 0 10px #e8b13c80;pointer-events:none;animation:cell-count-pulse .62s ease-out both}@keyframes cell-count-pulse{0%{opacity:1}to{opacity:0}}.board{display:grid;grid-template-columns:repeat(10,minmax(0,1fr));grid-template-rows:repeat(10,minmax(0,1fr));gap:2px;width:max(var(--board-min),min(100cqw - var(--space-2) * 2,100cqh - var(--control-h) * 5));aspect-ratio:1;max-height:100%;padding:var(--space-1);position:relative;background:radial-gradient(120% 120% at 50% 0%,color-mix(in srgb,var(--panel) 70%,transparent),transparent 90%),var(--grid);border:1px solid var(--border);border-radius:var(--radius);box-shadow:0 0 0 1px #00000073,inset 0 0 28px #00000080,0 14px 40px #00000073,0 0 60px #4fd2ff0b}.board:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;pointer-events:none;--brk: color-mix(in srgb, var(--flag) 62%, transparent);background:linear-gradient(var(--brk),var(--brk)) 0 0 / 16px 2px no-repeat,linear-gradient(var(--brk),var(--brk)) 0 0 / 2px 16px no-repeat,linear-gradient(var(--brk),var(--brk)) 100% 0 / 16px 2px no-repeat,linear-gradient(var(--brk),var(--brk)) 100% 0 / 2px 16px no-repeat,linear-gradient(var(--brk),var(--brk)) 0 100% / 16px 2px no-repeat,linear-gradient(var(--brk),var(--brk)) 0 100% / 2px 16px no-repeat,linear-gradient(var(--brk),var(--brk)) 100% 100% / 16px 2px no-repeat,linear-gradient(var(--brk),var(--brk)) 100% 100% / 2px 16px no-repeat,url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2026%2018'%3E%3Cpath%20d='M6%203%2014%209%206%2015M15%203%2023%209%2015%2015'%20fill='none'%20stroke='%23e8b13c'%20stroke-width='2.6'%20stroke-opacity='0.7'%20stroke-linecap='round'%20stroke-linejoin='round'/%3E%3C/svg%3E") right 6px center / 26px 18px no-repeat,url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2026%2018'%3E%3Cpath%20d='M20%203%2012%209%2020%2015M11%203%203%209%2011%2015'%20fill='none'%20stroke='%23e8b13c'%20stroke-width='2.6'%20stroke-opacity='0.7'%20stroke-linecap='round'%20stroke-linejoin='round'/%3E%3C/svg%3E") 6px center / 26px 18px no-repeat,url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2018%2026'%3E%3Cpath%20d='M3%2020%209%2012%2015%2020M3%2011%209%203%2015%2011'%20fill='none'%20stroke='%23e8b13c'%20stroke-width='2.6'%20stroke-opacity='0.7'%20stroke-linecap='round'%20stroke-linejoin='round'/%3E%3C/svg%3E") center 6px / 18px 26px no-repeat,url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2018%2026'%3E%3Cpath%20d='M3%206%209%2014%2015%206M3%2015%209%2023%2015%2015'%20fill='none'%20stroke='%23e8b13c'%20stroke-width='2.6'%20stroke-opacity='0.7'%20stroke-linecap='round'%20stroke-linejoin='round'/%3E%3C/svg%3E") center bottom 6px / 18px 26px no-repeat}.cell{border-radius:2px;background:linear-gradient(180deg,#ffffff0f,#0000002e),var(--safe);cursor:pointer;outline:none;position:relative;min-width:0;min-height:0;box-shadow:inset 0 1px #ffffff12,inset 0 -1px #00000040}.cell:hover:not(.cell-revealed){background:var(--safe-hi);box-shadow:inset 0 1px #ffffff12,inset 0 -1px #00000040,0 0 0 1px color-mix(in srgb,var(--ice) 40%,transparent),0 0 10px color-mix(in srgb,var(--ice) 22%,transparent)}.cell:active:not(.cell-revealed){transform:translateY(1px);box-shadow:inset 0 2px 4px #00000073}.cell-revealed{background:radial-gradient(110% 110% at 50% 120%,rgba(79,210,255,.05),transparent 70%),color-mix(in srgb,var(--safe) 30%,var(--bg));box-shadow:inset 0 1px 3px #00000080;cursor:default;display:grid;place-items:center;font-size:clamp(8px,2.4cqi,16px);font-weight:700;line-height:1}.cell-num-1{color:#6cb6ff;text-shadow:0 0 9px rgba(108,182,255,.6)}.cell-num-2{color:#7ee2a0;text-shadow:0 0 9px rgba(126,226,160,.6)}.cell-num-3{color:#ff8a8a;text-shadow:0 0 9px rgba(255,138,138,.6)}.cell-num-4{color:#c792ea;text-shadow:0 0 9px rgba(199,146,234,.6)}.cell-num-5{color:#e8b13c;text-shadow:0 0 9px rgba(232,177,60,.6)}.cell-num-6{color:#4fd2ff;text-shadow:0 0 9px rgba(79,210,255,.6)}.cell-num-7{color:#d7dee7;text-shadow:0 0 9px rgba(215,222,231,.5)}.cell-num-8{color:var(--muted);text-shadow:0 0 9px rgba(125,136,150,.5)}.cell-flagged{background:radial-gradient(100% 100% at 50% 130%,rgba(232,177,60,.25),transparent 75%),color-mix(in srgb,var(--flag) 28%,var(--safe));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--flag) 55%,transparent);animation:flagDrop .22s cubic-bezier(.2,1.4,.4,1)}@keyframes flagDrop{0%{transform:scale(1.5) rotate(-8deg)}to{transform:scale(1) rotate(0)}}.cell-flagged:after{content:"⚑";position:absolute;top:0;right:0;bottom:0;left:0;display:grid;place-items:center;color:var(--flag);font-size:clamp(8px,2.4cqi,16px)}.cell-ice{background:repeating-linear-gradient(135deg,rgba(255,255,255,.07) 0 2px,transparent 2px 6px),color-mix(in srgb,var(--ice) 35%,var(--safe));box-shadow:inset 0 0 0 1px var(--ice),0 0 10px #4fd2ff38}.cell-ice:not(.cell-mine-reveal):before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(135deg,rgba(255,255,255,.16),transparent 55%,rgba(150,230,255,.14));animation:iceSheen 3.2s ease-in-out infinite}@keyframes iceSheen{0%,to{opacity:0}50%{opacity:1}}.cell-ice:after{content:"❄";position:absolute;top:0;right:0;bottom:0;left:0;display:grid;place-items:center;color:#0b3a4a;font-size:clamp(7px,2.2cqi,14px)}.cell-fire{background:color-mix(in srgb,var(--fire) 45%,var(--safe));box-shadow:inset 0 0 4px 1px var(--fire)}.cell-fire:not(.cell-mine-reveal):before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:radial-gradient(125% 125% at 50% 50%,transparent 35%,color-mix(in srgb,var(--fire) 55%,#fff) 100%);opacity:.4;animation:fireGlow 1.2s ease-in-out infinite}@keyframes fireGlow{0%{opacity:.45}30%{opacity:1}55%{opacity:.3}80%{opacity:1}to{opacity:.45}}.cell-fire:after{content:"▲";position:absolute;top:0;right:0;bottom:0;left:0;display:grid;place-items:center;color:#ffd9c0;font-size:clamp(7px,2.2cqi,14px)}.cell-focused{box-shadow:inset 0 0 0 2px var(--focus)}.board-targeting .cell{cursor:crosshair}.board-targeting .cell-focused{box-shadow:inset 0 0 0 2px var(--primary)}.abilitybar button.armed{border-color:var(--primary);outline:2px solid var(--primary);outline-offset:1px;animation:armedBreath 1.4s ease-in-out infinite}@keyframes armedBreath{50%{outline-offset:3px}}.juice-reveal{animation:juiceReveal .3s ease-out backwards;animation-delay:var(--juice-delay, 0ms)}@keyframes juiceReveal{0%{opacity:.1;transform:scale(.82)}to{opacity:1;transform:scale(1)}}html.fx-dead .board-shift-telegraph{box-shadow:0 0 0 1px #00000073,inset 0 0 28px #00000080,0 0 26px #e8b13c59}.cell-mine-reveal{background:radial-gradient(90% 90% at 50% 40%,#e5484dcc,color-mix(in srgb,var(--mine) 45%,var(--bg)) 75%);box-shadow:inset 0 0 0 1px var(--mine),0 0 14px #e5484d80;animation:mineRevealPop .5s cubic-bezier(.2,1.3,.4,1) backwards}@keyframes mineRevealPop{0%{opacity:0;transform:scale(.6)}}.board .cell:nth-child(1).cell-mine-reveal{animation-delay:0ms}.board .cell:nth-child(2).cell-mine-reveal{animation-delay:26ms}.board .cell:nth-child(3).cell-mine-reveal{animation-delay:52ms}.board .cell:nth-child(4).cell-mine-reveal{animation-delay:78ms}.board .cell:nth-child(5).cell-mine-reveal{animation-delay:104ms}.board .cell:nth-child(6).cell-mine-reveal{animation-delay:.13s}.board .cell:nth-child(7).cell-mine-reveal{animation-delay:156ms}.board .cell:nth-child(8).cell-mine-reveal{animation-delay:182ms}.board .cell:nth-child(9).cell-mine-reveal{animation-delay:208ms}.board .cell:nth-child(10).cell-mine-reveal{animation-delay:234ms}.board .cell:nth-child(11).cell-mine-reveal{animation-delay:26ms}.board .cell:nth-child(12).cell-mine-reveal{animation-delay:52ms}.board .cell:nth-child(13).cell-mine-reveal{animation-delay:78ms}.board .cell:nth-child(14).cell-mine-reveal{animation-delay:104ms}.board .cell:nth-child(15).cell-mine-reveal{animation-delay:.13s}.board .cell:nth-child(16).cell-mine-reveal{animation-delay:156ms}.board .cell:nth-child(17).cell-mine-reveal{animation-delay:182ms}.board .cell:nth-child(18).cell-mine-reveal{animation-delay:208ms}.board .cell:nth-child(19).cell-mine-reveal{animation-delay:234ms}.board .cell:nth-child(20).cell-mine-reveal{animation-delay:.26s}.board .cell:nth-child(21).cell-mine-reveal{animation-delay:52ms}.board .cell:nth-child(22).cell-mine-reveal{animation-delay:78ms}.board .cell:nth-child(23).cell-mine-reveal{animation-delay:104ms}.board .cell:nth-child(24).cell-mine-reveal{animation-delay:.13s}.board .cell:nth-child(25).cell-mine-reveal{animation-delay:156ms}.board .cell:nth-child(26).cell-mine-reveal{animation-delay:182ms}.board .cell:nth-child(27).cell-mine-reveal{animation-delay:208ms}.board .cell:nth-child(28).cell-mine-reveal{animation-delay:234ms}.board .cell:nth-child(29).cell-mine-reveal{animation-delay:.26s}.board .cell:nth-child(30).cell-mine-reveal{animation-delay:286ms}.board .cell:nth-child(31).cell-mine-reveal{animation-delay:78ms}.board .cell:nth-child(32).cell-mine-reveal{animation-delay:104ms}.board .cell:nth-child(33).cell-mine-reveal{animation-delay:.13s}.board .cell:nth-child(34).cell-mine-reveal{animation-delay:156ms}.board .cell:nth-child(35).cell-mine-reveal{animation-delay:182ms}.board .cell:nth-child(36).cell-mine-reveal{animation-delay:208ms}.board .cell:nth-child(37).cell-mine-reveal{animation-delay:234ms}.board .cell:nth-child(38).cell-mine-reveal{animation-delay:.26s}.board .cell:nth-child(39).cell-mine-reveal{animation-delay:286ms}.board .cell:nth-child(40).cell-mine-reveal{animation-delay:312ms}.board .cell:nth-child(41).cell-mine-reveal{animation-delay:104ms}.board .cell:nth-child(42).cell-mine-reveal{animation-delay:.13s}.board .cell:nth-child(43).cell-mine-reveal{animation-delay:156ms}.board .cell:nth-child(44).cell-mine-reveal{animation-delay:182ms}.board .cell:nth-child(45).cell-mine-reveal{animation-delay:208ms}.board .cell:nth-child(46).cell-mine-reveal{animation-delay:234ms}.board .cell:nth-child(47).cell-mine-reveal{animation-delay:.26s}.board .cell:nth-child(48).cell-mine-reveal{animation-delay:286ms}.board .cell:nth-child(49).cell-mine-reveal{animation-delay:312ms}.board .cell:nth-child(50).cell-mine-reveal{animation-delay:338ms}.board .cell:nth-child(51).cell-mine-reveal{animation-delay:.13s}.board .cell:nth-child(52).cell-mine-reveal{animation-delay:156ms}.board .cell:nth-child(53).cell-mine-reveal{animation-delay:182ms}.board .cell:nth-child(54).cell-mine-reveal{animation-delay:208ms}.board .cell:nth-child(55).cell-mine-reveal{animation-delay:234ms}.board .cell:nth-child(56).cell-mine-reveal{animation-delay:.26s}.board .cell:nth-child(57).cell-mine-reveal{animation-delay:286ms}.board .cell:nth-child(58).cell-mine-reveal{animation-delay:312ms}.board .cell:nth-child(59).cell-mine-reveal{animation-delay:338ms}.board .cell:nth-child(60).cell-mine-reveal{animation-delay:364ms}.board .cell:nth-child(61).cell-mine-reveal{animation-delay:156ms}.board .cell:nth-child(62).cell-mine-reveal{animation-delay:182ms}.board .cell:nth-child(63).cell-mine-reveal{animation-delay:208ms}.board .cell:nth-child(64).cell-mine-reveal{animation-delay:234ms}.board .cell:nth-child(65).cell-mine-reveal{animation-delay:.26s}.board .cell:nth-child(66).cell-mine-reveal{animation-delay:286ms}.board .cell:nth-child(67).cell-mine-reveal{animation-delay:312ms}.board .cell:nth-child(68).cell-mine-reveal{animation-delay:338ms}.board .cell:nth-child(69).cell-mine-reveal{animation-delay:364ms}.board .cell:nth-child(70).cell-mine-reveal{animation-delay:.39s}.board .cell:nth-child(71).cell-mine-reveal{animation-delay:182ms}.board .cell:nth-child(72).cell-mine-reveal{animation-delay:208ms}.board .cell:nth-child(73).cell-mine-reveal{animation-delay:234ms}.board .cell:nth-child(74).cell-mine-reveal{animation-delay:.26s}.board .cell:nth-child(75).cell-mine-reveal{animation-delay:286ms}.board .cell:nth-child(76).cell-mine-reveal{animation-delay:312ms}.board .cell:nth-child(77).cell-mine-reveal{animation-delay:338ms}.board .cell:nth-child(78).cell-mine-reveal{animation-delay:364ms}.board .cell:nth-child(79).cell-mine-reveal{animation-delay:.39s}.board .cell:nth-child(80).cell-mine-reveal{animation-delay:416ms}.board .cell:nth-child(81).cell-mine-reveal{animation-delay:208ms}.board .cell:nth-child(82).cell-mine-reveal{animation-delay:234ms}.board .cell:nth-child(83).cell-mine-reveal{animation-delay:.26s}.board .cell:nth-child(84).cell-mine-reveal{animation-delay:286ms}.board .cell:nth-child(85).cell-mine-reveal{animation-delay:312ms}.board .cell:nth-child(86).cell-mine-reveal{animation-delay:338ms}.board .cell:nth-child(87).cell-mine-reveal{animation-delay:364ms}.board .cell:nth-child(88).cell-mine-reveal{animation-delay:.39s}.board .cell:nth-child(89).cell-mine-reveal{animation-delay:416ms}.board .cell:nth-child(90).cell-mine-reveal{animation-delay:442ms}.board .cell:nth-child(91).cell-mine-reveal{animation-delay:234ms}.board .cell:nth-child(92).cell-mine-reveal{animation-delay:.26s}.board .cell:nth-child(93).cell-mine-reveal{animation-delay:286ms}.board .cell:nth-child(94).cell-mine-reveal{animation-delay:312ms}.board .cell:nth-child(95).cell-mine-reveal{animation-delay:338ms}.board .cell:nth-child(96).cell-mine-reveal{animation-delay:364ms}.board .cell:nth-child(97).cell-mine-reveal{animation-delay:.39s}.board .cell:nth-child(98).cell-mine-reveal{animation-delay:416ms}.board .cell:nth-child(99).cell-mine-reveal{animation-delay:442ms}.board .cell:nth-child(100).cell-mine-reveal{animation-delay:468ms}.cell-mine-reveal:before{content:"✸";position:absolute;top:0;right:0;bottom:0;left:0;display:grid;place-items:center;color:#ffe0e0;font-size:clamp(8px,2.4cqi,16px)}.abilitybar{display:flex;flex-wrap:wrap;gap:var(--space-1);min-height:var(--control-h)}.abilitybar button{display:inline-flex;align-items:center;gap:var(--space-1);height:var(--control-h);padding:0 var(--space-2);flex:1 1 auto;border:1px solid var(--border);border-radius:var(--radius);background:linear-gradient(180deg,color-mix(in srgb,var(--panel) 80%,#fff 4%),var(--panel));cursor:pointer;white-space:nowrap;overflow:hidden;transition:border-color 80ms linear,box-shadow .12s ease}.abilitybar button:hover:not(:disabled){box-shadow:0 0 12px #e8b13c1f}.abilitybar button:hover:not(:disabled){border-color:var(--primary)}.abilitybar button:disabled{opacity:.4;cursor:not-allowed}.abilitybar button:focus-visible{outline:2px solid var(--primary);outline-offset:1px}.cost{color:var(--muted);font-size:11px}.cd{color:var(--primary);font-size:11px;min-width:2.5em;text-align:right}@container (max-width: 320px){.abilitybar button{justify-content:center;padding:0}.abilitybar .label,.abilitybar .cost{display:none}}.overlay{position:fixed;top:0;right:0;bottom:0;left:0;display:grid;place-items:center;background:radial-gradient(120% 90% at 50% 30%,transparent 40%,rgba(0,0,0,.35) 100%),color-mix(in srgb,var(--bg) 82%,transparent);-webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);z-index:10;animation:overlayIn .25s ease-out}@keyframes overlayIn{0%{opacity:0}}.overlay[hidden]{display:none}.panel{display:grid;gap:var(--space-2);justify-items:center;max-width:min(90%,340px);padding:var(--space-3);background:radial-gradient(140% 90% at 50% 0%,color-mix(in srgb,var(--border) 45%,transparent),transparent 80%),var(--panel);border:1px solid var(--border);border-radius:var(--radius);box-shadow:0 24px 80px #0009,inset 0 1px #ffffff0d;text-align:center}.panel-anim{animation:panelIn .34s cubic-bezier(.16,1,.3,1) backwards}.overlay:has(#summaryScreen:not([hidden])) .panel-anim.death-blast{animation:panelBreatheIn 1s cubic-bezier(.16,1,.3,1)}.overlay:has(.death-blast){backdrop-filter:none;-webkit-backdrop-filter:none}@keyframes panelBreatheIn{0%,45%{opacity:.03;transform:translateY(10px) scale(.98)}to{opacity:1;transform:none}}@keyframes panelIn{0%{opacity:0;transform:translateY(14px) scale(.97)}}.panel h2{margin:0;font-size:16px;letter-spacing:.06em;text-transform:uppercase}.panel p{margin:0;color:var(--muted);font-size:12px}.panel button{height:calc(var(--control-h) + 4px);padding:0 var(--space-3);border:1px solid var(--primary);border-radius:var(--radius);background:color-mix(in srgb,var(--primary) 18%,var(--panel));color:var(--fg);font-weight:700;cursor:pointer}.panel button:hover{background:color-mix(in srgb,var(--primary) 30%,var(--panel))}#menuScreen .panel-actions{display:grid;gap:var(--space-2);width:min(100%,220px);margin-inline:auto}#shopScreen.overlay:not([hidden]){display:flex;align-items:center;justify-content:center;padding:var(--space-2)}.panel-shop{width:min(94vw,760px);max-width:94vw;justify-items:stretch;text-align:left;max-height:calc(100dvh - 2 * var(--space-2));grid-template-rows:auto auto minmax(0,1fr) auto}.panel-shop h2,.shop-scrap{text-align:center}#shopScrap{color:var(--fg);font-size:15px}.upgrade-list{display:grid;gap:var(--space-3);min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--border) transparent}.shop-group{display:grid;gap:var(--space-2)}.shop-group-title{margin:0;padding-bottom:var(--space-1);border-bottom:1px solid var(--border);font-size:10px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}.shop-cards{display:grid;gap:var(--space-2);grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}.upgrade-row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:var(--space-3);padding:var(--space-2) var(--space-3) var(--space-3);background:color-mix(in srgb,var(--grid) 45%,var(--panel));border:1px solid var(--border);border-radius:var(--radius)}.upgrade-info{display:grid;gap:2px;min-width:0}.upgrade-name{display:flex;align-items:center;gap:var(--space-2);min-width:0}.upgrade-title{font-weight:700;flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.upgrade-lvl{font-size:11px;color:var(--muted)}.upgrade-pips{display:inline-flex;gap:3px;margin-left:auto}.pip{width:7px;height:7px;border:1px solid var(--border);background:transparent;transform:rotate(45deg)}.pip-on{border-color:var(--primary);background:var(--primary)}.upgrade-desc{font-size:11px;color:var(--muted)}.upgrade-row.locked{border-style:dashed}.upgrade-row.locked .upgrade-title{color:var(--muted)}.upgrade-row.unaffordable{opacity:.55}.upgrade-row.affordable{border-color:color-mix(in srgb,var(--primary) 55%,var(--border))}.upgrade-row.maxed{border-color:color-mix(in srgb,var(--primary) 40%,var(--border))}.buy-btn{white-space:nowrap}.panel .buy-btn:disabled{cursor:not-allowed;pointer-events:none;border-color:var(--border);background:transparent;color:var(--muted)}.panel .upgrade-row.maxed .buy-btn:disabled{border-color:color-mix(in srgb,var(--primary) 40%,var(--border));color:var(--primary)}@media(max-width:640px){.panel-shop{max-width:94vw}.shop-cards{grid-template-columns:1fr}}@media(max-width:240px){.upgrade-lvl{display:none}.upgrade-title{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.upgrade-row{gap:var(--space-2)}.panel .buy-btn{padding:0 var(--space-2);gap:4px;font-size:11px}}@media(max-height:300px){.panel-shop{gap:4px;padding:8px}.shop-group-title{display:none}.shop-actions button{min-height:26px;padding:4px var(--space-3);font-size:12px}.upgrade-list{gap:var(--space-1)}.upgrade-row{padding:2px var(--space-2)}.upgrade-desc{display:none}.shop-group-title{padding-bottom:0;border-bottom:none}.panel .buy-btn{height:var(--control-h)}}#helpScreen.overlay:not([hidden]){display:flex;align-items:center;justify-content:center;padding:var(--space-2)}.panel-help{width:min(94vw,560px);max-width:94vw;justify-items:stretch;text-align:left;max-height:calc(100dvh - 2 * var(--space-2));grid-template-rows:auto auto minmax(0,1fr) auto}.panel-help h2{text-align:center}.help-tabs{display:flex;gap:var(--space-1);border-bottom:1px solid var(--border);padding-bottom:var(--space-1)}.panel-help .help-tab{height:auto;padding:5px var(--space-2);border-color:var(--border);background:var(--panel);color:var(--muted);font-size:11px}.panel-help .help-tab:hover{color:var(--fg);background:color-mix(in srgb,var(--primary) 10%,var(--panel))}.panel-help .help-tab.active{border-color:var(--primary);background:color-mix(in srgb,var(--primary) 18%,var(--panel));color:var(--fg)}.help-body{min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--border) transparent}.help-tab-panel{display:grid;gap:var(--space-1)}.help-tab-panel[hidden]{display:none}.help-tab-panel h3{margin:var(--space-2) 0 0;padding-bottom:var(--space-1);border-bottom:1px solid var(--border);font-size:10px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}.help-tab-panel h3:first-child{margin-top:0}.help-tab-panel p{margin:0}.help-tab-panel ul,.help-tab-panel ol{margin:0;padding-left:1.2em;display:grid;gap:3px;color:var(--muted);font-size:12px}.help-tab-panel b{color:var(--fg)}@media(max-width:640px){.help-tabs{flex-wrap:wrap}}@media(max-height:300px){.panel-help{gap:6px;padding:8px}.panel-help .help-tab{padding:2px var(--space-1);font-size:10px}.help-tab-panel h3{margin-top:var(--space-1);padding-bottom:0;border-bottom:none}}.stub-note{font-size:10px!important;color:color-mix(in srgb,var(--muted) 70%,var(--bg))!important}@media(max-height:260px){.app{gap:var(--space-1);padding:var(--space-1)}.stub-note{display:none}}@media(max-height:200px){:root{--control-h: 22px;--space-2: 4px;--board-min: 96px}@media(max-height:170px){:root{--board-min: 76px}}.hud{border-color:transparent;background:none;box-shadow:none;padding:0}.app{gap:2px;padding:var(--space-1)}.status,.stat{font-size:10px}.abilitybar button{font-size:11px;padding:0 var(--space-1)}.cost,.cd,.icon-btn{display:none}}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}body:before{animation:none}}.summary-name{display:grid;gap:4px;width:100%;max-width:240px;margin-top:var(--space-1);color:var(--muted);font-size:11px;text-align:left}.summary-name input{height:var(--control-h);padding:0 var(--space-2);border:1px solid var(--border);border-radius:var(--radius);background:color-mix(in srgb,var(--bg) 60%,var(--panel));color:var(--fg);font:inherit}.summary-name input:focus{outline:1px solid var(--primary)}#boardScreen .panel{max-height:86dvh}.board-rows{display:grid;gap:4px;width:100%;max-width:300px;max-height:46dvh;overflow-y:auto;margin:0;font-size:12px;text-align:left}.board-rows .ledger-row{display:flex;justify-content:space-between;gap:var(--space-2);padding:2px var(--space-2);border-radius:calc(var(--radius) / 2)}.board-rows .ledger-row dt,.board-rows .ledger-row dd{margin:0;min-width:0}.board-rows .ledger-row dt{color:var(--fg);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.board-rows .ledger-row dd{color:var(--muted);font-variant-numeric:tabular-nums}.board-rows .ledger-row.own{background:color-mix(in srgb,var(--primary) 16%,transparent)}.board-rows .ledger-row.own dd{color:var(--fg);font-weight:700}
</style>
</head>
<body>
<!--
Shiftfield — playable game shell (Phase 2.5 integration).
Boots the real GameEngine (chaos layer over the Phase 1 world).
Phase 3 replaces the cleared/dead overlays with the decision,
summary, and workshop screens.
-->
<main id="app" class="app">
<header class="hud" aria-label="Run status">
<span class="stat">D<span class="stat-key">epth</span> <b id="depth">0</b></span>
<span class="stat">T<span class="stat-key">urn</span> <b id="turn">0</b></span>
<span class="stat">F<span class="stat-key">low</span> <b id="flow">0</b></span>
<span class="stat stat-runscrap" id="runScrapStat" title="Scrap earned this run (lost on death unless banked)">◈<span class="stat-key"> Scrap</span> <b id="runScrap">0</b></span>
<span class="stat stat-scrap" title="Banked scrap (safe)">⛃<span class="stat-key"> Banked</span> <b id="scrap">0</b></span>
<span class="stat stat-shift" id="shiftStat" hidden>
⇄ <b id="shiftArrow">—</b> in <b id="shiftIn">—</b>
</span>
<span class="stat stat-shield" id="shieldStat" hidden>⛨ <b id="shieldCount">0</b></span>
<output id="status" class="status status-idle" aria-live="polite">Ready</output>
<button type="button" id="sfxToggle" class="icon-btn" aria-label="Toggle sound" aria-pressed="false" title="Sound on — click to mute">🔊</button>
<!-- Field Manual mid-run entry (ruling 2026-10-28, how_to_play_menu_ruling):
opens the manual landed on Tools & Workshop, without disturbing the run. -->
<button type="button" id="helpToggle" class="icon-btn" aria-label="Field Manual" title="Field Manual — tools & workshop reference">?</button>
</header>
<section id="boardHost" class="board-host" aria-label="Shiftfield grid"></section>
<!-- Depth-6 Shifter awakening (ruling 2026-10-28): one-shot per run.
BLOCKING modal — the run cannot continue until acknowledged (ruling
2026-10-28 follow-up). Full-viewport backdrop intercepts board
clicks; panel sits centered so the board and its wind stay visible
behind the dim. -->
<div id="shifterIntro" class="intro-modal" hidden>
<div class="intro-panel" role="dialog" aria-modal="true" aria-labelledby="shifterIntroTitle">
<b id="shifterIntroTitle">You sense a tremble beneath the earth</b>
<span>The mines are shifting. Watch the telegraphed movement —
and know that your actions aren't the only thing that wraps around
the board...</span>
<button type="button" id="shifterIntroOk">Understood</button>
</div>
</div>
<!-- ONE-TIME runner-name gate (first-run name gate ruling 2026-10-29):
interrupts the FIRST run only — while the board is available and the
player has never been asked (save.name_prompted). Blank + confirm is
a valid choice (run unnamed); the gate never interrupts again. The
summary field stays as the later rename path. -->
<div id="nameGate" class="intro-modal" hidden>
<div class="intro-panel" role="dialog" aria-modal="true" aria-labelledby="nameGateTitle">
<b id="nameGateTitle">Choose your runner name</b>
<span>The leaderboard remembers your deepest depth. Your name rides
every score you post — leave it blank to run unnamed.</span>
<label class="summary-name" for="nameGateInput">Runner name
<input id="nameGateInput" type="text" maxlength="24" placeholder="unnamed-runner" autocomplete="off" spellcheck="false">
</label>
<button type="button" id="nameGateOk">Enter The Depths</button>
</div>
</div>
<footer class="abilitybar" role="toolbar" aria-label="Abilities">
<button type="button" data-ability="radar" title="Radar Pulse — select, then click a tile: reveals safe tiles in a sweep around it">
<span class="label">Radar</span> <span class="cost">50f</span><span class="cd" data-cd="radar"></span>
</button>
<button type="button" data-ability="seeker" title="Seeker Drone — flags one mine per Workshop level">
<span class="label">Seeker</span> <span class="cost">50f</span><span class="cd" data-cd="seeker"></span>
</button>
<button type="button" data-ability="stabilizer" title="Stabilizer — freezes the Shifter countdown (buy levels in the Workshop)">
<span class="label">Stabilize</span> <span class="cost">30f</span><span class="cd" data-cd="stabilizer"></span>
</button>
</footer>
<!-- MENU — first screen of the §4.1 loop; Enter The Depths → RUNNING, Workshop → SHOP -->
<div id="menuScreen" class="overlay" hidden>
<div class="panel panel-anim" role="dialog" aria-modal="true" aria-labelledby="menuTitle">
<h2 id="menuTitle">Shiftfield</h2>
<p class="menu-tag">Roguelike minefield — clear sectors, bank scrap, die rich.</p>
<p class="menu-stats" id="menuStats"></p>
<div class="panel-actions">
<button id="startRunBtn" type="button">Enter The Depths</button>
<button id="menuWorkshopBtn" class="btn-secondary" type="button">Workshop</button>
<!-- Field Manual entry (ruling 2026-10-28, how_to_play_menu_ruling). -->
<button id="menuFieldManualBtn" class="btn-secondary" type="button">Field Manual</button>
<button id="menuLeaderboardBtn" class="btn-secondary" type="button" hidden>Leaderboard</button>
</div>
<p class="stub-note" id="saveNote" hidden></p>
</div>
</div>
<!-- DECISION — bank vs continue after a sector clear (§4.1 step 2) -->
<div id="decisionScreen" class="overlay" hidden>
<div class="panel panel-anim" role="dialog" aria-modal="true" aria-labelledby="decisionTitle">
<h2 id="decisionTitle">Sector cleared</h2>
<p id="decisionBody"></p>
<div class="panel-actions">
<button id="bankBtn" type="button">Bank & Exit</button>
<button id="continueBtn" class="btn-secondary" type="button">Go Deeper</button>
</div>
<p class="stub-note">Scrap in the vault is safe. An explosion destroys some of what you carry.</p>
</div>
</div>
<!-- SUMMARY — run over (§4.1 steps 3/5): banked exit or 50% death salvage -->
<div id="summaryScreen" class="overlay" hidden>
<div class="panel panel-anim" role="dialog" aria-modal="true" aria-labelledby="summaryTitle">
<h2 id="summaryTitle">Boom</h2>
<p id="summaryCause"></p>
<dl class="summary-ledger" id="summaryLedger"></dl>
<label class="summary-name" for="playerNameInput">Runner name
<input id="playerNameInput" type="text" maxlength="24" placeholder="unnamed-runner" autocomplete="off" spellcheck="false">
</label>
<p class="stub-note" id="summaryBoardNote" hidden></p>
<div class="panel-actions">
<button id="summaryWorkshopBtn" type="button">Workshop</button>
<button id="summaryBoardBtn" class="btn-secondary" type="button" hidden>Leaderboard</button>
<button id="summaryMenuBtn" class="btn-secondary" type="button">Menu</button>
</div>
</div>
</div>
<!-- LEADERBOARD — shared depth-v1 board (ruling 2026-10-28). Row names are
untrusted public data: rendered with textContent, never innerHTML. -->
<div id="boardScreen" class="overlay" hidden>
<div class="panel panel-anim" role="dialog" aria-modal="true" aria-labelledby="boardTitle">
<h2 id="boardTitle">Deepest Depth</h2>
<p id="boardNote"></p>
<dl class="summary-ledger board-rows" id="boardRows"></dl>
<p class="stub-note">Scores are player-reported; each runner's deepest board counts. Your identity lives in this browser — clearing site data starts a fresh runner.</p>
<div class="panel-actions">
<button id="boardRefreshBtn" type="button">Refresh</button>
<button id="boardCloseBtn" class="btn-secondary" type="button">Back</button>
</div>
</div>
</div>
<!-- FIELD MANUAL — how-to-play overlay (ruling 2026-10-28,
how_to_play_menu_ruling): 3 merged tabs, static approved copy only
(no engine reads). MENU opens Basics; the HUD `?` lands on Tools &
Workshop mid-run; Back hands the underlying screen straight back —
no §4.1 state change, the overlay never alters the phase.
The three explosion causes reuse the ratified player-word labels
(ui_status_copy_ruling); engine enums must never appear here —
pinned by the field-manual-tabs browser check. -->
<div id="helpScreen" class="overlay" hidden>
<div class="panel panel-help panel-anim" role="dialog" aria-modal="true" aria-labelledby="helpTitle">
<h2 id="helpTitle">Field Manual</h2>
<div class="help-tabs" role="tablist" aria-label="Manual sections">
<button type="button" class="help-tab active" role="tab" id="helpTabBasics" aria-selected="true">Basics</button>
<button type="button" class="help-tab" role="tab" id="helpTabHazards" aria-selected="false">Hazards & The Shift</button>
<button type="button" class="help-tab" role="tab" id="helpTabTools" aria-selected="false">Tools & Workshop</button>
</div>
<div class="help-body">
<section class="help-tab-panel" id="helpPanelBasics" role="tabpanel" aria-labelledby="helpTabBasics">
<h3>The Loop</h3>
<p>Your task is to clear a mine field one sector at a time, and maybe get some loot while you're at it.</p>
<ul>
<li><b>Left-click / tap</b> — Reveals a tile. A safe move rewards you with <b>Flow</b> and <b>Scrap</b>.</li>
<li><b>Right-click / long-press</b> — Places a flag which locks down the tile. A locked tile prevents anything from moving in, out, or through it.</li>
<li>Open every non-mine tile and the sector clears.</li>
<li>After each sector clear, you may choose to <b>Bank & Exit</b> to save your scrap, or <b>Go Deeper</b> to risk the depths for higher rewards.</li>
<li><b>Death destroys half of what you carry.</b> Bank your scrap to protect it.</li>
<li>Between runs, the <b>Workshop</b> turns banked scrap into upgrades and abilities.</li>
</ul>
<p>Controls: left-click reveal · right-click flag · tap reveal · long-press flag.</p>
<h3>Flow & Scrap</h3>
<p><b>Flow</b> powers abilities.</p>
<ul>
<li>Ceiling 100 by default. Raise the cap with upgrades in the Workshop.</li>
</ul>
<p><b>Scrap</b> is the run economy.</p>
<ul>
<li>Deeper sectors have more scrap, at the risk of more hazards.</li>
<li><b>Bank</b> at any decision screen to move your haul to the vault.</li>
<li>Scrap is spent in the Workshop on upgrades and abilities to aid later runs.</li>
</ul>
<h3>Death & Banking</h3>
<p>A run ends when something explodes. You, specifically. There are a few ways an explosion can occur:</p>
<ol>
<li><b>A mine detonated</b> — you opened a mine, or a shifted mine landed on ground you had already opened.</li>
<li><b>Mines collided</b> — a shift forced two mines into one tile.</li>
<li><b>A mine ignited</b> — fire spread to a mine, or a mine shifted beneath the flames.</li>
</ol>
<p>A shield charge absorbs any one of these (see Tools & Workshop). Without one — <b>Boom</b>: half of your unbanked scrap burns with you.</p>
<p><b>Turn law:</b> only <i>effective</i> actions advance the world. Blocked clicks, rejected flags and refused abilities cost nothing and tick nothing.</p>
</section>
<section class="help-tab-panel" id="helpPanelHazards" role="tabpanel" aria-labelledby="helpTabHazards" hidden>
<h3>The Shift</h3>
<p>From <b>depth 6</b> the mines themselves start moving.</p>
<ul>
<li>Every shift is <b>telegraphed</b>: the HUD chip shows <b>⇄ direction — in N turns</b>, and amber wind streaks blow across the board in that direction. The last three turns warn loudly.</li>
<li>The board <b>wraps</b>: mines leaving one edge re-enter at the opposite edge. The amber edge chevrons and corner brackets are permanent — <b>wrap is always on</b>, at every depth.</li>
<li>A <b>flag is an anchor</b>. Mines can never move into or out of a flagged tile — a mine whose path is blocked by your flag simply stays put.</li>
<li>Shifting can force two mines into the same tile if they are blocked by a flag. This will trigger an explosion unless a <b>shield charge</b> eats it and protects you.</li>
<li>Ice does <b>not</b> stop mines. Only flags do.</li>
<li>Shift cadence tightens with depth; the <b>Stabilizer</b> freezes the countdown.</li>
</ul>
<h3>Hazards</h3>
<p>Sectors past depth 10 come with additional hazards.</p>
<p><b>Ice</b> — frozen ground that is too hard to dig through for a time. It blocks <i>you</i>, not the mines.</p>
<p><b>Fire</b> — burns on hidden ground.</p>
<ul>
<li>On <b>odd turns</b>, each fire has a chance to ignite some neighboring tiles. Clear it before it spreads too far.</li>
<li>Fire is only deadly when it shares a tile <b>with a mine</b> — fire next to a mine does nothing, forever.</li>
<li>Revealing a fire tile puts it out. <b>Flagging</b> a fire tile puts it out as well (and anchors the ground).</li>
<li>Ice blocks fire spread.</li>
</ul>
</section>
<section class="help-tab-panel" id="helpPanelTools" role="tabpanel" aria-labelledby="helpTabTools" hidden>
<p>Abilities start <b>locked</b> — buy upgrades in the Workshop to use them.</p>
<ul>
<li><b>Radar Pulse</b> <i>(50f, 2T cooldown)</i> — select, then click: sweeps a patch of safe tiles open around the target. Grows with Radar Array levels: 3×3 → 21-tile circle → full 5×5. Radar pays <b>no Flow</b> and never floods its own sweep.</li>
<li><b>Seeker Drone</b> <i>(50f, 3T cooldown)</i> — auto-flags 1 mine (2, then 3 at higher levels).</li>
<li><b>Stabilizer</b> <i>(30f, 10T cooldown)</i> — freezes the shift countdown for 3 turns (5 / 7 / 10 at higher levels). At max, recast on cooldown and the Shifter never fires.</li>
<li><b>Shield Generator</b> — increases the number of <b>shield charges the run starts with</b>. A charge absorbs one mine hit, then it's gone until the next run. Charges carry across sectors.</li>
<li><b>Data Ledger</b> — +25% run scrap per level.</li>
<li><b>Flow Capacitor</b> — +100 Flow ceiling per level.</li>
</ul>
</section>
</div>
<div class="panel-actions">
<button id="helpBackBtn" type="button">Back</button>
</div>
</div>
</div>
<!-- SHOP / Workshop — tiered permanent upgrades (§4.1 step 6) -->
<div id="shopScreen" class="overlay" hidden>
<div class="panel panel-shop panel-anim" role="dialog" aria-modal="true" aria-labelledby="shopTitle">
<h2 id="shopTitle">Workshop</h2>
<p class="shop-scrap">⛃ <b id="shopScrap">0</b> scrap</p>
<div class="upgrade-list" id="upgradeList"></div>
<div class="panel-actions">
<button id="shopStartBtn" type="button">Enter The Depths</button>
<button id="shopBackBtn" class="btn-secondary" type="button">Back</button>
</div>
</div>
</div>
</main>
</body>
</html>
