SOURCE / PINNED RELEASE
Made of little things.
Obsidian Breaker
- Release
- 44c589c52043…
- Author-recorded commit
- 9ee5b61ce1c9…
- License
- Check the original source
- Author’s source reference
- nostr://npub1dmmh0futyszl4445mv4xrygkgsgj8vpu79yx3qyy9qxmz4pemtzqlqmc87/wss%3A%2F%2Fgit.napplet.soy%2F/n-de5dc6a02e7
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" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>Obsidian Breaker</title>
<script type="module" crossorigin>var Ze=Object.defineProperty;var et=(t,a,n)=>a in t?Ze(t,a,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[a]=n;var z=(t,a,n)=>et(t,typeof a!="symbol"?a+"":a,n);function tt(){const t=new Map;function a(s,l){if(t.has(s))throw new Error(`NAP domain "${s}" is already registered`);t.set(s,l)}function n(s){const l=s.type.indexOf(".");if(l<=0)return!1;const r=s.type.slice(0,l),c=t.get(r);return c?(c(s),!0):!1}function o(){return Array.from(t.keys())}return{registerNap:a,dispatch:n,getRegisteredDomains:o}}var at=tt(),it=at.registerNap,j="storage";it(j,t=>{});function nt(){const t=window;if(!t.napplet)throw new Error("window.napplet is unavailable -- runtime did not inject the napplet namespace");return t.napplet}function A(t){const n=nt()[t];if(!n)throw new Error(`window.napplet.${String(t)} is unavailable -- runtime did not inject this domain`);return n}var Fe={getItem(t){return A("storage").getItem(t)},setItem(t,a){return A("storage").setItem(t,a)},removeItem(t){return A("storage").removeItem(t)},keys(){return A("storage").keys()},instance:{getItem(t){return A("storage").instance.getItem(t)},setItem(t,a){return A("storage").instance.setItem(t,a)},removeItem(t){return A("storage").instance.removeItem(t)},keys(){return A("storage").instance.keys()}}},ce={get(){return A("config").get()},subscribe(t){return A("config").subscribe(t)},openSettings(t){A("config").openSettings(t)},registerSchema(t,a){return A("config").registerSchema(t,a)},onSchemaError(t){return A("config").onSchemaError(t)},get schema(){return A("config").schema}};function ot(t,a){return!!(t!=null&&t[a])}function K(t){return ot(window.napplet,t)}const st="http://json-schema.org/draft-07/schema#",rt=1,lt="object",ct=!1,dt={difficulty:{type:"string",title:"Run difficulty",description:"Chill grants more shields and a slower ball. Ruthless is a short fuse.",enum:["chill","standard","ruthless"],default:"standard","x-napplet-section":"Run","x-napplet-order":1},aimPace:{type:"string",title:"Seer sweep speed",description:"How fast the predicted launch angle sweeps across the seam.",enum:["slow","steady","quick"],default:"steady","x-napplet-section":"Run","x-napplet-order":2},showGuide:{type:"boolean",title:"Show bounce prediction",description:"Draw the reflected trajectory line, live slab catch ring and impact markers.",default:!0,"x-napplet-section":"Assist","x-napplet-order":1},screenShake:{type:"boolean",title:"Impact screen shake",description:"Shake the seam on hard impacts and chain explosions.",default:!0,"x-napplet-section":"Feedback","x-napplet-order":1}},ht={$schema:st,$version:rt,type:lt,additionalProperties:ct,properties:dt},Y={difficulty:"standard",aimPace:"steady",showGuide:!0,screenShake:!0},fe=new Set;let ue={...Y};function ft(t){const a=t.difficulty==="chill"||t.difficulty==="ruthless"?t.difficulty:"standard",n=t.aimPace==="slow"||t.aimPace==="quick"?t.aimPace:"steady";return{difficulty:a,aimPace:n,showGuide:typeof t.showGuide=="boolean"?t.showGuide:Y.showGuide,screenShake:typeof t.screenShake=="boolean"?t.screenShake:Y.screenShake}}function ut(t){return fe.add(t),t(ue),()=>{fe.delete(t)}}K("config")&&(async()=>{ce.schema||await ce.registerSchema(ht);const t=ce.subscribe(a=>{ue=ft(a);for(const n of fe)n(ue)});window.addEventListener("pagehide",()=>t.close(),{once:!0})})().catch(()=>{});const Ce=.24;class pt{constructor(){z(this,"context",null);z(this,"master",null);z(this,"noiseBuffer",null);z(this,"muted",!1);z(this,"lastPlayed",new Map)}unlock(){if(!this.muted)try{if(!this.context){this.context=new AudioContext,this.master=this.context.createGain(),this.master.gain.value=Ce,this.master.connect(this.context.destination);const a=Math.floor(this.context.sampleRate*.4),n=this.context.createBuffer(1,a,this.context.sampleRate),o=n.getChannelData(0);for(let s=0;s<a;s+=1)o[s]=Math.random()*2-1;this.noiseBuffer=n}this.context.state==="suspended"&&this.context.resume()}catch{this.context=null,this.master=null,this.noiseBuffer=null}}setMuted(a){if(this.muted=a,a){this.master&&this.context&&(this.master.gain.value=0);return}this.unlock(),this.master&&this.context&&(this.master.gain.value=Ce)}play(a){const n=this.context,o=this.master;if(this.muted||!n||!o)return;const s=n.currentTime,l=a==="brick"?.028:a==="paddle"?.04:0,r=this.lastPlayed.get(a);if(!(r!==void 0&&s-r<l)){this.lastPlayed.set(a,s);try{switch(a){case"launch":this.tone(320,760,.16,"triangle",.2),this.noise(.1,.12,1800);break;case"paddle":this.tone(190,330,.09,"square",.13);break;case"brick":this.tone(520+Math.random()*240,360,.055,"triangle",.1);break;case"crack":this.noise(.12,.18,2100),this.tone(170,80,.12,"sawtooth",.08);break;case"boom":this.noise(.42,.4,620),this.tone(130,32,.5,"sine",.42);break;case"power":this.tone(440,520,.09,"triangle",.14),this.tone(660,720,.09,"triangle",.13,.06),this.tone(880,980,.14,"triangle",.12,.12);break;case"life":this.tone(240,60,.5,"sawtooth",.18),this.noise(.18,.14,500);break;case"level":this.tone(392,392,.12,"triangle",.13),this.tone(523,523,.12,"triangle",.12,.1),this.tone(659,659,.2,"triangle",.12,.2);break;case"win":this.tone(523,523,.16,"triangle",.14),this.tone(659,659,.16,"triangle",.14,.12),this.tone(784,784,.16,"triangle",.14,.24),this.tone(1046,1046,.45,"triangle",.12,.36);break;case"bullet":this.tone(460,150,.55,"sine",.16),this.noise(.28,.08,620);break;case"warp":this.tone(170,980,.34,"sawtooth",.12),this.tone(340,1560,.28,"triangle",.08,.06),this.noise(.32,.16,2400);break}}catch{}}}tone(a,n,o,s,l,r=0){const c=this.context,h=this.master;if(!c||!h)return;const d=c.currentTime+r,u=c.createOscillator(),m=c.createGain();u.type=s,u.frequency.setValueAtTime(Math.max(20,a),d),u.frequency.exponentialRampToValueAtTime(Math.max(20,n),d+o),m.gain.setValueAtTime(1e-4,d),m.gain.exponentialRampToValueAtTime(l,d+.01),m.gain.exponentialRampToValueAtTime(1e-4,d+o),u.connect(m),m.connect(h),u.start(d),u.stop(d+o+.03)}noise(a,n,o){const s=this.context,l=this.master,r=this.noiseBuffer;if(!s||!l||!r)return;const c=s.currentTime,h=s.createBufferSource();h.buffer=r;const d=s.createBiquadFilter();d.type="lowpass",d.frequency.setValueAtTime(o,c),d.frequency.exponentialRampToValueAtTime(Math.max(80,o*.35),c+a);const u=s.createGain();u.gain.setValueAtTime(n,c),u.gain.exponentialRampToValueAtTime(1e-4,c+a),h.connect(d),d.connect(u),u.connect(l),h.start(c),h.stop(c+a+.02)}}const V=26,Ae=V/2,mt={1:{kind:"shale",hp:1},2:{kind:"granite",hp:2},3:{kind:"basalt",hp:3},v:{kind:"volatile",hp:1},x:{kind:"obsidian",hp:Number.POSITIVE_INFINITY}};function gt(t){return mt[t]??null}function N(t){const a=t.slice(0,Ae).padEnd(Ae,".");return a+[...a].reverse().join("")}const E=[{name:"Ashfall",tagline:"A wider warm-up seam",speed:1,rows:[".............",".11111111111.",".12222222221.",".12vvv3vvv21.",".12222222221.",".11111111111.",".12222222221.",".12v3333v221.",".12222222221.",".11111111111.",".12vvv3vvv21.",".12222222221.",".11111111111.","............."].map(N)},{name:"Fault Line",tagline:"The seam splits twice over",speed:1.04,rows:["..x.......x..",".11111111111.",".11112222111.",".1122v3v2211.",".11122322111.","..x..vvv..x..",".12222222221.",".11222v22211.",".11111111111.",".1122v3v2211.",".11122322111.",".12222222221.","..x..vvv..x..",".11111111111.",".11222v22211.",".12222222221.",".11111111111.","............."].map(N)},{name:"Mineshaft",tagline:"Bank through the doubled ribs",speed:1.08,rows:["......x......",".....131.....","....13231....","...132v231...","..132121231..",".13212v21231.","..132121231..","...132v231...","....13231....",".....131.....","......x......",".....131.....","....13231....","...132v231...","..132121231..",".13212v21231.","..132121231..","...132v231...","....13231....",".....131.....","......x......","......v......"].map(N)},{name:"Fusion Core",tagline:"Feed the doubled chain reaction",speed:1.12,rows:["x.111111111.x","..122222221..",".123vvvvv321.",".12v33333v21.",".123vvvvv321.","..122222221..","x.111111111.x","x.111111111.x","..122222221..",".123vvvvv321.",".12v33333v21.",".123vvvvv321.","..122222221..","x.111111111.x"].map(N)},{name:"Cascade",tagline:"A layered volatile lattice",speed:1.17,rows:[".1.1.1.1.1.1.",".2.2.2.2.2.2.","1.1.1.1.1.1.1",".2.v.3.v.2.v.","1.1.1.1.1.1.1",".2.3.v.3.3.2.","1.1.1.1.1.1.1",".2.v.3.v.2.v.","1.1.1.1.1.1.1",".2.3.3.3.3.2.","1.1.1.1.1.1.1",".x.........x.",".1.1.1.1.1.1.",".2.2.2.2.2.2.","1.1.1.1.1.1.1",".2.v.3.v.2.v.","1.1.1.1.1.1.1",".2.3.v.3.3.2.","1.1.1.1.1.1.1",".2.v.3.v.2.v.","1.1.1.1.1.1.1",".2.3.3.3.3.2.","1.1.1.1.1.1.1",".x.........x."].map(N)},{name:"Deep Vault",tagline:"The wide black galleries",speed:1.19,rows:["x.111111111.x","..122v3v221..",".12.12221.21.","12.1.3v3.1.21",".1.1222221.1.","x.1.v3v3v.1.x",".1.1222221.1.","12.1.3v3.1.21",".12.12221.21.","..122v3v221..","x.111111111.x","..x.......x..","x.111111111.x","..122v3v221..",".12.12221.21.","12.1.3v3.1.21",".1.1222221.1.","x.1.v3v3v.1.x",".1.1222221.1.","12.1.3v3.1.21",".12.12221.21.","..122v3v221..","x.111111111.x","..x.......x.."].map(N)},{name:"Obsidian Crown",tagline:"Crack the black shell twice",speed:1.23,rows:["xx.1111111.xx","x.122222221.x",".1.13v3v31.1.","12.1v3v3v1.21",".1.13v3v31.1.","12.1222221.21",".1.1111111.1.","x.1.2v3v2.1.x",".x.1222221.x.","..x.11111.x..","xx.1111111.xx","xx.1111111.xx","x.122222221.x",".1.13v3v31.1.","12.1v3v3v1.21",".1.13v3v31.1.","12.1222221.21",".1.1111111.1.","x.1.2v3v2.1.x",".x.1222221.x.","..x.11111.x..","xx.1111111.xx"].map(N)}],k=Math.PI*2,He=1.02,de=1/120,vt=4,Be=5,Oe="obsidian-breaker/best-v1",Le=.52,xt=1.5,ve={shale:{top:"#d9803f",bottom:"#743116",edge:"#ffbe84",glint:"rgba(255,225,190,0.55)",shard:"#ff9a4d"},granite:{top:"#a37b66",bottom:"#4b3228",edge:"#d9b7a0",glint:"rgba(255,230,215,0.35)",shard:"#c49a82"},basalt:{top:"#7f5d9b",bottom:"#31213f",edge:"#c3a0e0",glint:"rgba(230,210,255,0.35)",shard:"#a97ccc"},volatile:{top:"#3edc8e",bottom:"#0f4c33",edge:"#b9ffdc",glint:"rgba(210,255,235,0.65)",shard:"#5affae"},obsidian:{top:"#46576a",bottom:"#141a23",edge:"#9db4cc",glint:"rgba(210,230,255,0.3)",shard:"#7c93a8"}},pe={wide:{core:"#7ce8ff",glow:"rgba(80,210,255,0.55)",label:"W"},slow:{core:"#c9a2ff",glow:"rgba(180,120,255,0.55)",label:"C"},split:{core:"#b6ff7a",glow:"rgba(140,255,120,0.55)",label:"M"},blast:{core:"#ffb45c",glow:"rgba(255,120,40,0.65)",label:"B"}},b=new pt;function v(t){const a=document.querySelector(t);if(!a)throw new Error(`Missing required element: ${t}`);return a}const f={app:v("#app"),hud:v(".hud"),canvas:v("#board"),score:v("#scoreValue"),chain:v("#chainValue"),chainStat:v("#chainStat"),lives:v("#livesValue"),level:v("#levelValue"),best:v("#bestValue"),guideButton:v("#guideButton"),soundButton:v("#soundButton"),warpButton:v("#warpButton"),bulletButton:v("#bulletButton"),pauseButton:v("#pauseButton"),bulletMeter:v("#bulletMeter"),bulletFill:v("#bulletFill"),banner:v("#banner"),bannerKicker:v("#bannerKicker"),bannerTitle:v("#bannerTitle"),hint:v("#hint"),toast:v("#toast"),overlay:v("#overlay"),overlayKicker:v("#overlayKicker"),overlayTitle:v("#overlayTitle"),overlayBody:v("#overlayBody"),overlayActions:v("#overlayActions"),overlayNote:v("#overlayNote"),status:v("#gameStatus")},Ge=f.canvas.getContext("2d");if(!Ge)throw new Error("Canvas 2D is unavailable");const i=Ge,e={width:0,height:0,dpr:1,phase:"docked",resumePhase:"docked",level:0,score:0,best:0,lives:3,comboTimer:0,combo:0,time:0,aimT:0,aimDir:1,shake:0,flash:0,slowTimer:0,wideTimer:0,blastTimer:0,blastCooldown:0,levelClearTimer:0,bulletCharge:1,bulletActive:!1,warpActive:!1,warpReady:!1,warpHits:0,warpCooldown:0,warpTimeLeft:0,warpCheckTimer:0,warpHintShown:!1,lastBulletPercent:-1,hasSized:!1,pendingLaunch:!1,bestDirty:!1,storageWarned:!1,lastLives:-1,ballRadius:5,paddleBaseW:90,paddleH:10,bricks:[],balls:[],motes:[],particles:[],rings:[],floats:[],powerups:[],bursts:[],keys:{left:!1,right:!1},bounds:{top:40,bottom:560},paddle:{x:0,y:0,w:90,h:10,targetX:0,vx:0},settings:{...Y},guide:Y.showGuide,soundOn:!0,bg:null,acc:0},Re={id:0},Pe={id:0};function g(t,a,n){return Math.min(n,Math.max(a,t))}function F(t){return Math.round(t).toLocaleString("en-US")}function T(t){const a=Math.sin(t*12.9898)*43758.5453;return a-Math.floor(a)}function yt(){return window.matchMedia("(prefers-reduced-motion: reduce)").matches}function Z(t,a){t.textContent!==a&&(t.textContent=a)}function B(t){f.status.textContent=t}function oe(t,a,n=1500){window.clearTimeout(Re.id),f.bannerKicker.textContent=t,f.bannerTitle.textContent=a,f.banner.classList.add("is-visible"),Re.id=window.setTimeout(()=>f.banner.classList.remove("is-visible"),n)}function C(t,a=1500){window.clearTimeout(Pe.id),f.toast.textContent=t,f.toast.classList.add("is-visible"),Pe.id=window.setTimeout(()=>f.toast.classList.remove("is-visible"),a)}function xe(){f.guideButton.setAttribute("aria-pressed",String(e.guide)),f.guideButton.classList.toggle("is-off",!e.guide)}function Xe(){f.soundButton.setAttribute("aria-pressed",String(e.soundOn)),f.soundButton.classList.toggle("is-off",!e.soundOn),f.soundButton.setAttribute("aria-label",e.soundOn?"Mute sound":"Unmute sound")}function H(){const t=e.phase==="paused";f.pauseButton.setAttribute("aria-label",t?"Resume":"Pause"),f.pauseButton.title=t?"Resume":"Pause"}function O(){if(Z(f.score,F(e.score)),Z(f.best,F(Math.max(e.best,e.score))),Z(f.chain,e.combo>1?`x${Math.min(e.combo,99)}`:"—"),f.chainStat.classList.toggle("is-hot",e.combo>1),Z(f.level,`${String(e.level+1).padStart(2,"0")}/${String(E.length).padStart(2,"0")}`),e.lives!==e.lastLives){const t=g(e.lives,0,Be),a=Math.max(0,Be-t);f.lives.textContent="◆".repeat(t)+"◇".repeat(a),f.lives.setAttribute("aria-label",`${e.lives} shields`),e.lastLives=e.lives}}function wt(){let t=1;return e.bulletActive&&(t*=.34),e.warpActive&&(t*=2.4),t}function I(){const t=Math.round(e.bulletCharge*100);t!==e.lastBulletPercent&&(e.lastBulletPercent=t,f.bulletFill.style.transform=`scaleX(${e.bulletCharge.toFixed(3)})`,f.bulletMeter.setAttribute("aria-valuenow",String(t)),f.bulletButton.setAttribute("aria-label",`Bullet time (B): ${t}% charged`),f.bulletButton.title=`Bullet time (B): ${t}% charged`),f.bulletButton.classList.toggle("is-active",e.bulletActive),f.bulletButton.setAttribute("aria-pressed",String(e.bulletActive)),f.bulletMeter.classList.toggle("is-active",e.bulletActive),f.bulletMeter.classList.toggle("is-low",e.bulletCharge<.22);const a=e.warpActive||e.warpReady&&e.warpCooldown<=0;f.warpButton.classList.toggle("is-ready",e.warpReady&&!e.warpActive&&e.warpCooldown<=0),f.warpButton.classList.toggle("is-active",e.warpActive),f.warpButton.setAttribute("aria-pressed",String(e.warpActive)),f.warpButton.setAttribute("aria-disabled",String(!a));let n;e.warpActive?n="Warp active (F to release)":e.warpCooldown>0?n=`Warp recharging (${Math.ceil(e.warpCooldown)}s)`:e.warpReady?n="Warp ready (F): fast-forward through 3 impacts":n="Warp (F): needs 3 projected wall or stone impacts",f.warpButton.getAttribute("aria-label")!==n&&(f.warpButton.setAttribute("aria-label",n),f.warpButton.title=n)}function ee(t){e.warpReady!==t&&(e.warpReady=t,I(),t&&!e.warpHintShown&&(e.warpHintShown=!0,C("WARP READY · PRESS F OR TAP »",1500)))}function Ee(){if(e.bulletActive){e.bulletActive=!1,b.play("bullet"),C("Bullet time released",900),I();return}if(!(e.phase!=="docked"&&e.phase!=="playing")){if(e.bulletCharge<.12){C("Bullet time is recharging",1200);return}e.warpActive&&G(!1),e.bulletActive=!0,e.flash=Math.max(e.flash,.08),b.play("bullet"),C("BULLET TIME",900),I(),B("Bullet time engaged.")}}function $e(){if(e.warpActive){G(!1);return}if(e.phase!=="playing"){C("Warp needs a live ball",1100);return}if(e.warpCooldown>0){C("Warp is recharging",1100);return}if(!e.warpReady){C("Warp needs 3 projected impacts",1300);return}e.bulletActive=!1,e.warpActive=!0,e.warpReady=!1,e.warpHits=0,e.warpTimeLeft=4,e.flash=Math.max(e.flash,.18),e.shake=Math.max(e.shake,2),b.play("warp"),C("WARP · 3 IMPACTS",1e3),I(),B("Warp engaged. Fast-forwarding through three impacts.")}function G(t){e.warpActive&&(e.warpActive=!1,e.warpHits=0,e.warpCooldown=t?2.6:.6,t&&C("WARP SPENT",1e3),I())}function ie(){e.warpActive&&(e.warpHits+=1,e.warpHits>=3&&G(!0))}function bt(){if(e.phase!=="playing"||e.warpActive||e.warpCooldown>0){ee(!1);return}const t=e.balls[0];if(!t||t.stuck){ee(!1);return}const a=Math.hypot(t.vx,t.vy);if(a<.001){ee(!1);return}const n=Je(t.x,t.y,t.vx/a,t.vy/a,t.r,5);ee(n.impacts>=3)}function kt(t){e.bulletActive?(e.bulletCharge=Math.max(0,e.bulletCharge-t*.24),e.bulletCharge<=0&&(e.bulletActive=!1,C("Bullet time depleted",1200),B("Bullet time depleted."))):(e.phase==="docked"||e.phase==="playing"||e.phase==="levelClear")&&(e.bulletCharge=Math.min(1,e.bulletCharge+t*.1)),e.warpActive?(e.warpTimeLeft-=t,e.warpTimeLeft<=0&&G(!0)):e.warpCooldown>0&&(e.warpCooldown=Math.max(0,e.warpCooldown-t)),e.blastTimer>0&&(e.blastTimer=Math.max(0,e.blastTimer-t)),e.blastCooldown>0&&(e.blastCooldown=Math.max(0,e.blastCooldown-t)),e.warpCheckTimer-=t,e.warpCheckTimer<=0&&(e.warpCheckTimer=.12,bt()),I()}function me(t,a,n,o,s=""){f.overlayKicker.textContent=t,f.overlayTitle.textContent=a,f.overlayBody.textContent=n,f.overlayNote.textContent=s,f.overlayActions.replaceChildren();for(const l of o){const r=document.createElement("button");r.type="button",r.className=l.primary?"btn btn-primary":"btn",r.textContent=l.label,r.addEventListener("click",l.onClick),f.overlayActions.append(r)}f.overlay.hidden=!1}function ye(){f.overlay.hidden=!0}function ze(){return e.settings.difficulty==="chill"?5:e.settings.difficulty==="ruthless"?2:3}function Mt(){return e.settings.difficulty==="chill"?.86:e.settings.difficulty==="ruthless"?1.16:1}function _(){var o;const t=g(e.height*.58,120,330)*xt,a=((o=E[e.level])==null?void 0:o.speed)??1,n=e.slowTimer>0?.74:1;return t*a*Mt()*n}function Ve(t){const a=E[t];if(!a)return[];const n=[];return a.rows.forEach((o,s)=>{for(let l=0;l<V;l+=1){const r=gt(o[l]??".");r&&n.push({col:l,row:s,kind:r.kind,hp:r.hp,maxHp:r.kind==="obsidian"?1:r.hp,x:0,y:0,w:0,h:0,alive:!0,flash:0,seed:s*37+l*61+t*131+1})}}),n}function St(){const t=f.hud.getBoundingClientRect().bottom-f.app.getBoundingClientRect().top,a=g(e.height*.105,22,74),n=g(e.height*.012,2,8);e.bounds.top=g(Math.max(a,t+n),22,e.height*.46),e.ballRadius=g(Math.min(e.width,e.height)*.012,3.2,7),e.paddleBaseW=g(e.width*.15,42,150),e.paddleH=g(e.height*.02,7,14);const o=g(e.height*.065,16,46);e.paddle.y=e.height-o-e.paddleH,e.bounds.bottom=e.paddle.y}function J(){e.paddle.w=e.paddleBaseW*(e.wideTimer>0?1.45:1),e.paddle.h=e.paddleH;const t=e.paddle.w/2;e.paddle.x=g(e.paddle.x||e.width/2,t,e.width-t),e.paddle.targetX=g(e.paddle.targetX||e.paddle.x,t,e.width-t)}function se(){return{x:e.paddle.x-e.paddle.w/2,y:e.paddle.y,w:e.paddle.w,h:e.paddle.h}}function qe(){const t=E[e.level];if(!t)return;const a=t.rows.length,n=g(e.width*.006,.8,5),o=g(e.width*.02,3,24),s=e.bounds.top+g(e.height*.015,2,10),l=Math.max(40,(e.paddle.y-s)*.72),r=(e.width-o*2-n*(V-1))/V,c=(l-n*(a-1))/a,h=Math.max(3,Math.min(r,c/Le)),d=h*Le,u=V*h+n*(V-1),m=a*d+n*(a-1),p=(e.width-u)/2,x=s+Math.max(0,l-m)*.5;for(const y of e.bricks)y.x=p+y.col*(h+n),y.y=x+y.row*(d+n),y.w=h,y.h=d}function Tt(t,a){return{x:t,y:a,vx:0,vy:0,r:e.ballRadius,stuck:!0,trail:[]}}function re(){e.balls=[Tt(e.paddle.x,e.paddle.y-e.ballRadius-2)],e.aimT=0,e.aimDir=1}function Ct(){const t=Math.round(g(e.width*e.height/16e3,12,44));e.motes=Array.from({length:t},()=>({x:Math.random()*e.width,y:Math.random()*e.height,radius:.6+Math.random()*1.8,drift:6+Math.random()*16,speed:4+Math.random()*16,phase:Math.random()*k,alpha:.08+Math.random()*.24}))}function we(){const t=f.app.getBoundingClientRect(),a=Math.max(120,Math.round(t.width)),n=Math.max(100,Math.round(t.height)),o=e.width,s=e.height;if(e.width=a,e.height=n,e.dpr=Math.min(window.devicePixelRatio||1,2),f.canvas.width=Math.round(a*e.dpr),f.canvas.height=Math.round(n*e.dpr),St(),qe(),e.hasSized&&o>0&&s>0){const l=a/o,r=n/s;for(const c of e.balls)c.x*=l,c.y*=r,c.r=e.ballRadius;e.paddle.x*=l,e.paddle.targetX*=l;for(const c of e.powerups)c.x*=l,c.y*=r}J(),e.phase==="docked"&&re(),At(),Ct(),e.hasSized=!0}function At(){const t=document.createElement("canvas");t.width=Math.max(1,Math.round(e.width*e.dpr)),t.height=Math.max(1,Math.round(e.height*e.dpr));const a=t.getContext("2d");if(!a)return;a.scale(e.dpr,e.dpr);const n=e.width,o=e.height,s=a.createLinearGradient(0,0,0,o);s.addColorStop(0,"#130c14"),s.addColorStop(.45,"#0d0810"),s.addColorStop(1,"#060407"),a.fillStyle=s,a.fillRect(0,0,n,o);const l=a.createRadialGradient(n*.5,-o*.12,o*.04,n*.5,-o*.12,o*.95);l.addColorStop(0,"rgba(255,110,40,0.18)"),l.addColorStop(.5,"rgba(255,80,30,0.05)"),l.addColorStop(1,"rgba(255,60,20,0)"),a.fillStyle=l,a.fillRect(0,0,n,o);const r=a.createRadialGradient(n*.5,o*1.06,o*.02,n*.5,o*1.06,o*.7);r.addColorStop(0,"rgba(255,90,25,0.22)"),r.addColorStop(1,"rgba(255,50,10,0)"),a.fillStyle=r,a.fillRect(0,o*.55,n,o*.45),a.lineWidth=1;for(let d=0;d<24;d+=1){const u=o*(.14+d/24*.9),m=.016+T(d+3)*.04;a.strokeStyle=`rgba(255,150,90,${m})`,a.beginPath();for(let p=-10;p<=n+10;p+=n/12){const x=Math.sin(p*.012+d*1.7)*o*.006;p<=-10?a.moveTo(p,u+x):a.lineTo(p,u+x)}a.stroke()}for(let d=0;d<5;d+=1){const u=T(d*7)*n,m=o*(.35+T(d*11)*.45);a.strokeStyle=`rgba(255,92,35,${.035+T(d*5)*.03})`,a.lineWidth=1+T(d*13)*1.4,a.beginPath(),a.moveTo(u,m),a.bezierCurveTo(u+(T(d+4)-.5)*n*.3,m-o*.12,u+(T(d+9)-.5)*n*.4,m+o*.16,u+(T(d+2)-.5)*n*.5,m+o*.24),a.stroke()}const c=Math.round(g(n*o/9e3,40,150));for(let d=0;d<c;d+=1){const u=T(d*3+1)*n,m=T(d*7+2)*o,p=.04+T(d*5)*.12;a.fillStyle=`rgba(255,190,130,${p})`,a.fillRect(u,m,1,1)}const h=a.createRadialGradient(n/2,o/2,Math.min(n,o)*.25,n/2,o/2,Math.max(n,o)*.78);h.addColorStop(0,"rgba(0,0,0,0)"),h.addColorStop(1,"rgba(0,0,0,0.55)"),a.fillStyle=h,a.fillRect(0,0,n,o),e.bg=t}function U(){e.score=0,e.level=0,e.lives=ze(),e.combo=0,e.comboTimer=0,e.powerups=[],e.bursts=[],e.floats=[],e.rings=[],e.particles=[],e.bulletCharge=1,e.bulletActive=!1,e.warpActive=!1,e.warpReady=!1,e.warpHits=0,e.warpCooldown=0,e.warpTimeLeft=0,e.warpHintShown=!1,e.blastTimer=0,e.blastCooldown=0,ye(),Ye(0),I(),H(),B(`New run. ${e.lives} shields. Seam one, Ashfall.`)}function Ye(t){e.level=t,e.bricks=Ve(t),e.powerups=[],e.bursts=[],e.floats=[],e.rings=[],e.particles=[],e.slowTimer=0,e.wideTimer=0,e.blastTimer=0,e.blastCooldown=0,e.bulletActive=!1,e.warpActive=!1,e.warpReady=!1,e.warpHits=0,e.warpCooldown=0,e.warpTimeLeft=0,e.combo=0,e.comboTimer=0,e.phase="docked",e.acc=0,e.shake=0,e.flash=0,qe(),J(),re(),H(),O(),I();const a=E[t];a&&(oe(`SEAM ${String(t+1).padStart(2,"0")} / ${String(E.length).padStart(2,"0")}`,a.name.toUpperCase(),1700),B(`Seam ${t+1}: ${a.name}. ${a.tagline}.`))}function he(){if(e.phase!=="docked")return;const t=e.balls[0];if(!t)return;b.unlock();const a=e.aimT*He,n=_();t.stuck=!1,t.vx=Math.sin(a)*n,t.vy=-Math.cos(a)*n,t.trail=[],e.phase="playing",e.acc=0,b.play("launch"),B("Ball away.")}function te(){if(e.phase==="paused"){ge();return}e.phase!=="docked"&&e.phase!=="playing"||(e.resumePhase=e.phase,e.phase="paused",H(),me("Frozen","The seam waits","Every impact is held in place. Resume when you are ready.",[{label:"Resume",primary:!0,onClick:ge},{label:"Restart run",onClick:U}],K(j)?"":"Storage is unavailable here, so best scores last only for this session."),B("Paused."))}function ge(){e.phase==="paused"&&(e.phase=e.resumePhase,ye(),H(),B("Resumed."))}function _e(t){e.bulletActive=!1,G(!1),e.warpReady=!1,e.score>e.best&&(e.best=e.score,e.bestDirty=!0),e.bestDirty&&Ue(),ye(),O(),b.play(t?"win":"life"),t?(e.phase="victory",me("Crown broken","Every seam is dust",`Final score ${F(e.score)} · Best ${F(e.best)}. All ${E.length} strata are cleared.`,[{label:"Run again",primary:!0,onClick:U}],"Best scores save when the host provides storage."),B(`Victory. Final score ${e.score}.`)):(e.phase="gameOver",me("Run ended","The seam holds",`Score ${F(e.score)} · Best ${F(e.best)}. The volatile veins reset for the next descent.`,[{label:"Run again",primary:!0,onClick:U}],K(j)?"":"Storage is unavailable here, so best scores last only for this session."),B(`Run ended. Score ${e.score}. Best ${e.best}.`)),H()}function Bt(){if(e.phase!=="playing")return;e.bulletActive=!1,G(!1),e.warpReady=!1,e.phase="levelClear",e.levelClearTimer=2.1,e.acc=0;const t=400+(e.level+1)*200+e.lives*100;e.score+=t,e.score>e.best&&(e.best=e.score,e.bestDirty=!0),Ue(),e.flash=Math.max(e.flash,.2),e.shake=Math.max(e.shake,4),Ht(),b.play("level"),oe(`SEAM ${String(e.level+1).padStart(2,"0")} CLEAR`,`+${F(t)} FRAGMENTS`,1900),B(`Seam ${e.level+1} cleared. Bonus ${t}.`),O()}async function Ue(){if(K(j))try{await Fe.setItem(Oe,String(Math.floor(e.best))),e.bestDirty=!1}catch{e.storageWarned||(e.storageWarned=!0,C("This host did not save the best score",2400))}}async function Lt(){if(K(j)){try{const t=await Promise.race([Fe.getItem(Oe),new Promise(a=>{window.setTimeout(()=>a(null),2500)})]);if(typeof t=="string"){const a=Number.parseInt(t,10);Number.isFinite(a)&&(e.best=Math.max(0,a))}}catch{}O()}}function Rt(t){const a=e.settings.aimPace==="slow"?.34:e.settings.aimPace==="quick"?.92:.58;e.aimT+=e.aimDir*a*t,e.aimT>1?(e.aimT=1,e.aimDir=-1):e.aimT<-1&&(e.aimT=-1,e.aimDir=1)}function Pt(t){const a=Math.max(e.width*1.5,320);e.keys.left&&(e.paddle.targetX-=a*t),e.keys.right&&(e.paddle.targetX+=a*t);const n=e.paddle.w/2;e.paddle.targetX=g(e.paddle.targetX,n,e.width-n);const o=e.paddle.targetX-e.paddle.x,s=Math.max(e.width*2.4,620)*t,l=g(o,-s,s);if(e.paddle.vx=l/Math.max(t,1e-4),e.paddle.x=g(e.paddle.x+l,n,e.width-n),e.phase==="docked"){const r=e.balls[0];r&&(r.x=e.paddle.x,r.y=e.paddle.y-e.ballRadius-2)}}function Et(t,a){const n=_(),o=Math.hypot(t.vx,t.vy);!Number.isFinite(o)||o<.001?(t.vx=0,t.vy=-n):(t.vx=t.vx/o*n,t.vy=t.vy/o*n);const s=n*a,l=g(Math.ceil(s/Math.max(1,t.r*.65)),1,12),r=a/l;for(let c=0;c<l&&(t.x+=t.vx*r,t.y+=t.vy*r,$t(t),It(t),Dt(t),e.phase==="playing");c+=1);}function $t(t){const a=t.r,n=e.bounds.top+a;t.y<n&&(t.y=n,t.vy=Math.abs(t.vy),$(t.x,n,"255,160,90",2),ie()),t.x<a?(t.x=a,t.vx=Math.abs(t.vx),$(a,t.y,"255,160,90",2),ie()):t.x>e.width-a&&(t.x=e.width-a,t.vx=-Math.abs(t.vx),$(e.width-a,t.y,"255,160,90",2),ie())}function It(t){const a=se(),n=g(t.x,a.x,a.x+a.w),o=g(t.y,a.y,a.y+a.h),s=t.x-n,l=t.y-o;if(s*s+l*l>t.r*t.r)return;if(t.vy>0&&t.y<a.y+a.h*.9){const d=g((t.x-e.paddle.x)/(e.paddle.w/2),-1,1)*1.08,u=_();t.x=g(t.x,a.x+t.r,a.x+a.w-t.r),t.y=a.y-t.r-.5,t.vx=Math.sin(d)*u,t.vy=-Math.abs(Math.cos(d)*u),Wt();return}const r=t.x<e.paddle.x?-1:1,c=_();t.x=e.paddle.x+r*(e.paddle.w/2+t.r+.5),t.vx=r*Math.abs(t.vx||c*.4),t.vy=-Math.abs(t.vy||c*.4)}function Wt(){e.combo=0,e.comboTimer=0,e.shake=Math.max(e.shake,1.2),b.play("paddle"),$(e.paddle.x,e.paddle.y,"255,190,120",4)}function Dt(t){let a=null,n=0,o=0,s=0;for(const r of e.bricks){if(!r.alive)continue;const c=g(t.x,r.x,r.x+r.w),h=g(t.y,r.y,r.y+r.h),d=t.x-c,u=t.y-h,m=Math.hypot(d,u);let p=0,x=0,y=0;if(m>1e-4){if(m>=t.r)continue;p=d/m,x=u/m,y=t.r-m}else{const M=t.x-r.x,L=r.x+r.w-t.x,R=t.y-r.y,q=r.y+r.h-t.y,S=Math.min(M,L,R,q);S===M?(p=-1,y=M+t.r):S===L?(p=1,y=L+t.r):S===R?(x=-1,y=R+t.r):(x=1,y=q+t.r)}y>n&&(n=y,a=r,o=p,s=x)}if(!a)return;t.x+=o*(n+.05),t.y+=s*(n+.05);const l=t.vx*o+t.vy*s;if(l<0&&(t.vx-=2*l*o,t.vy-=2*l*s),ie(),e.blastTimer>0&&e.blastCooldown<=0){e.blastCooldown=.16;const r=Math.max(t.r*7,a.w*1.8);Ke(t.x,t.y,r,2),$(t.x,t.y,"255,180,100",9)}if(a.kind==="obsidian"){l<0&&(a.flash=.18,e.shake=Math.max(e.shake,1.4),$(t.x,t.y,"170,200,230",3),b.play("paddle"));return}je(a,1)}function je(t,a){if(!(!t.alive||t.kind==="obsidian")){if(t.flash=.2,t.hp-=a,t.hp>0){e.score+=15,e.comboTimer=Math.max(e.comboTimer,1.1),b.play("brick"),$(t.x+t.w/2,t.y+t.h/2,ve[t.kind].shard==="#ff9a4d"?"255,160,90":"220,190,190",3);return}Nt(t)}}function Nt(t){t.alive=!1,t.flash=0,e.combo+=1,e.comboTimer=3.2;const a=1+Math.min(e.combo-1,12)*.5,n=t.kind==="volatile"?140:t.kind==="basalt"?90:t.kind==="granite"?70:55;e.score+=Math.round(n*a);const o=t.x+t.w/2,s=t.y+t.h/2;if(Ft(o,s,ve[t.kind].shard,t.kind==="volatile"?18:10),e.shake=Math.max(e.shake,t.kind==="volatile"?4:2.4),b.play("crack"),t.kind==="volatile"){const l=Math.max(t.w,t.h)*2.9;e.bursts.push({x:o,y:s,radius:l,delay:.06})}else e.powerups.length<2&&Math.random()<.13&&Ot(o,s);e.combo>1&&e.combo%5===0&&e.floats.push({x:o,y:s,vy:-34,life:1,maxLife:1,text:`CHAIN x${e.combo}`,color:"#ffd7a1",size:g(e.height*.024,9,16)})}function Ke(t,a,n,o=3){e.rings.push({x:t,y:a,life:.55,maxLife:.55,from:n*.08,to:n,color:"90,255,165"}),e.rings.push({x:t,y:a,life:.3,maxLife:.3,from:n*.05,to:n*.6,color:"255,190,110"}),e.flash=Math.max(e.flash,.24),e.shake=Math.max(e.shake,7),b.play("boom");for(let l=0;l<22;l+=1){const r=Math.random()*k,c=40+Math.random()*Math.min(260,e.height*.5);e.particles.push({x:t,y:a,vx:Math.cos(r)*c,vy:Math.sin(r)*c,life:.4+Math.random()*.5,maxLife:.9,size:1.4+Math.random()*2.4,color:Math.random()<.45?"120,255,170":"255,165,80",gravity:120,kind:"spark",rotation:0,spin:0})}be();const s=e.bricks.filter(l=>{if(!l.alive||l.kind==="obsidian")return!1;const r=l.x+l.w/2-t,c=l.y+l.h/2-a;return Math.hypot(r,c)<=n});for(const l of s)je(l,o)}function $(t,a,n,o){for(let s=0;s<o;s+=1){const l=Math.random()*k,r=25+Math.random()*120;e.particles.push({x:t,y:a,vx:Math.cos(l)*r,vy:Math.sin(l)*r-20,life:.22+Math.random()*.32,maxLife:.55,size:.8+Math.random()*1.4,color:n,gravity:80,kind:"spark",rotation:0,spin:0})}be()}function Ft(t,a,n,o){for(let s=0;s<o;s+=1){const l=Math.random()*k,r=40+Math.random()*220;e.particles.push({x:t,y:a,vx:Math.cos(l)*r,vy:Math.sin(l)*r-40,life:.45+Math.random()*.55,maxLife:1,size:2+Math.random()*4,color:n,gravity:320,kind:"shard",rotation:Math.random()*k,spin:(Math.random()-.5)*18})}be()}function be(){e.particles.length>420&&e.particles.splice(0,e.particles.length-420)}function Ht(){for(let t=0;t<60;t+=1){const a=Math.random()*e.width,n=e.bounds.top+Math.random()*(e.paddle.y-e.bounds.top);$(a,n,t%3===0?"120,255,170":"255,180,90",1)}}function Ot(t,a){const n=Math.random(),o=n<.3?"wide":n<.56?"split":n<.8?"blast":"slow";e.powerups.push({x:g(t,16,e.width-16),y:a,vy:g(e.height*.12,36,90),kind:o,spin:Math.random()*k})}function Gt(){const t=e.balls.find(s=>!s.stuck)??e.balls[0];if(!t)return;if(e.balls.length>=vt){e.score+=150,C("SHARD CAP · +150",900);return}const a=_(),o=Math.atan2(t.vy||-a,t.vx||0)+(Math.random()<.5?.45:-.45);e.balls.push({x:t.x,y:t.y,vx:Math.cos(o)*a,vy:Math.sin(o)*a,r:e.ballRadius,stuck:!1,trail:[]}),$(t.x,t.y,"182,255,122",8),e.shake=Math.max(e.shake,1.6)}function Xt(t){b.play("power");const a=t.kind==="wide"?"WIDE SLAB":t.kind==="slow"?"COOLANT":t.kind==="split"?"SPLIT SHARD":"BLAST CORE";e.floats.push({x:t.x,y:t.y-8,vy:-42,life:1.1,maxLife:1.1,text:a,color:pe[t.kind].core,size:g(e.height*.026,10,17)}),C(a,1100),t.kind==="wide"?(e.wideTimer=20,J()):t.kind==="slow"?e.slowTimer=14:t.kind==="split"?Gt():t.kind==="blast"&&(e.blastTimer=12,e.blastCooldown=0,e.floats.push({x:t.x,y:t.y-24,vy:-36,life:1.2,maxLife:1.2,text:"BLAST RADIUS",color:pe.blast.core,size:g(e.height*.026,10,17)}))}function zt(t){const a=se();for(let n=e.powerups.length-1;n>=0;n-=1){const o=e.powerups[n];if(o){if(o.y+=o.vy*t,o.spin+=t*4,o.y+10>=a.y&&o.y-10<=a.y+a.h&&o.x+10>=a.x&&o.x-10<=a.x+a.w){Xt(o),e.powerups.splice(n,1);continue}o.y>e.height+30&&e.powerups.splice(n,1)}}}function Vt(t){for(const n of e.bursts)n.delay-=t;const a=e.bursts.filter(n=>n.delay<=0);if(a.length!==0){e.bursts=e.bursts.filter(n=>n.delay>0);for(const n of a)Ke(n.x,n.y,n.radius)}}function qt(){if(e.lives-=1,e.combo=0,e.comboTimer=0,e.wideTimer=0,e.slowTimer=0,e.blastTimer=0,e.blastCooldown=0,e.bulletActive=!1,G(!1),e.warpReady=!1,e.powerups=[],e.shake=Math.max(e.shake,9),e.flash=Math.max(e.flash,.4),b.play("life"),J(),O(),e.lives<=0){_e(!1);return}re(),e.phase="docked",oe("SHIELD LOST",`${e.lives} REMAIN`,1300),B(`Shield lost. ${e.lives} remain.`),H()}function Yt(){if(e.phase!=="playing")return;const t=e.balls.filter(a=>a.y-a.r<e.height+30);t.length!==e.balls.length&&(e.balls=t),e.balls.length===0&&qt()}function _t(){if(e.phase!=="playing")return;e.bricks.some(a=>a.alive&&a.kind!=="obsidian")||Bt()}function Ut(t){for(const a of e.balls){a.stuck||a.trail.push({x:a.x,y:a.y,life:1});for(const n of a.trail)n.life-=t*4.2;a.trail=a.trail.filter(n=>n.life>0).slice(-24)}}function jt(t){for(let a=e.particles.length-1;a>=0;a-=1){const n=e.particles[a];if(n){if(n.life-=t,n.life<=0){e.particles.splice(a,1);continue}n.vy+=n.gravity*t,n.x+=n.vx*t,n.y+=n.vy*t,n.rotation+=n.spin*t}}for(let a=e.rings.length-1;a>=0;a-=1){const n=e.rings[a];n&&(n.life-=t,n.life<=0&&e.rings.splice(a,1))}for(let a=e.floats.length-1;a>=0;a-=1){const n=e.floats[a];n&&(n.life-=t,n.y+=n.vy*t,n.life<=0&&e.floats.splice(a,1))}for(const a of e.bricks)a.flash>0&&(a.flash=Math.max(0,a.flash-t*4))}function Kt(t){for(const a of e.motes)a.y-=a.speed*t,a.x+=Math.sin(e.time*.6+a.phase)*a.drift*t,a.y<-6&&(a.y=e.height+6,a.x=Math.random()*e.width),a.x<-6&&(a.x=e.width+6),a.x>e.width+6&&(a.x=-6)}function Jt(t){if(e.phase==="docked"&&Rt(t),Pt(t),e.phase==="playing"){for(const a of e.balls)if(!a.stuck&&(Et(a,t),e.phase!=="playing"))break}Ut(t),zt(t),Vt(t),e.comboTimer>0&&(e.comboTimer-=t,e.comboTimer<=0&&(e.combo=0)),e.slowTimer>0&&(e.slowTimer=Math.max(0,e.slowTimer-t)),e.wideTimer>0&&(e.wideTimer=Math.max(0,e.wideTimer-t),e.wideTimer===0&&J()),e.phase==="playing"&&(_t(),e.phase==="playing"&&Yt())}function Qt(t){e.time+=t;const a=e.bulletActive?.5:e.warpActive?1.8:1;if(Kt(t*a),O(),e.phase==="paused")return;kt(t);const n=wt(),o=t*n;if(e.shake=Math.max(0,e.shake-o*26),e.flash=Math.max(0,e.flash-o*1.6),jt(o),e.phase==="levelClear"){e.levelClearTimer-=t,e.levelClearTimer<=0&&(e.level>=E.length-1?_e(!0):Ye(e.level+1));return}if(e.phase==="docked"||e.phase==="playing"){for(e.acc=Math.min(e.acc+o,.12);e.acc>=de;)if(Jt(de),e.acc-=de,e.phase!=="docked"&&e.phase!=="playing"){e.acc=0;break}}}function Zt(t,a,n,o,s,l,r,c){let h=Number.NEGATIVE_INFINITY,d=Number.POSITIVE_INFINITY;const u=[[t,n,s,s+r],[a,o,l,l+c]];for(const[m,p,x,y]of u){if(Math.abs(p)<1e-9){if(m<x||m>y)return null;continue}let M=(x-m)/p,L=(y-m)/p;if(M>L&&([M,L]=[L,M]),h=Math.max(h,M),d=Math.min(d,L),h>d)return null}return d>=Math.max(h,0)?Math.max(h,0):null}function Je(t,a,n,o,s,l){const r=Math.hypot(n,o)||1;let c=n/r,h=o/r,d=t,u=a;const m=[{x:d,y:u}],p=[];let x=null,y=!1,M=0,L=0;const R=se();for(let q=0;q<=l;q+=1){let S=Number.POSITIVE_INFINITY,W=null;if(c>1e-6){const w=(e.width-s-d)/c;w>1e-4&&(S=w,W="right")}else if(c<-1e-6){const w=(s-d)/c;w>1e-4&&(S=w,W="left")}if(h>1e-6){const w=(e.height-s-u)/h;w>1e-4&&w<S&&(S=w,W="bottom")}else if(h<-1e-6){const w=(e.bounds.top+s-u)/h;w>1e-4&&w<S&&(S=w,W="top")}let D=Number.POSITIVE_INFINITY,le=null;for(const w of e.bricks){if(!w.alive)continue;const P=Zt(d,u,c,h,w.x-s,w.y-s,w.w+s*2,w.h+s*2);P!==null&&P>1e-4&&P<D&&(D=P,le=w)}let Q=Number.POSITIVE_INFINITY,ke=0;if(h>1e-6){const P=(R.y-s-u)/h;if(P>1e-4){const X=d+c*P;X>=R.x-s&&X<=R.x+R.w+s&&(Q=P,ke=X)}}if(le&&D<=S&&D<=Q){x=le,L+=1,m.push({x:d+c*D,y:u+h*D});break}if(Q<S&&Q<D){const w=g(ke,R.x+s*.2,R.x+R.w-s*.2),P=R.y-s-.5,X={x:w,y:P};m.push(X),p.push(X);const Te=g((w-e.paddle.x)/(e.paddle.w/2),-1,1)*1.08;c=Math.sin(Te),h=-Math.cos(Te),d=w+c*.02,u=P+h*.02,M+=1;continue}if(!W||!Number.isFinite(S))break;const Me=d+c*S,Se=u+h*S;if(m.push({x:Me,y:Se}),W==="bottom"){y=!0;break}d=Me,u=Se,W==="left"||W==="right"?c=-c:h=-h,d+=c*.02,u+=h*.02,M+=1,L+=1}return{points:m,hitBrick:x,lost:y,bounces:M,catches:p,impacts:L}}function ne(t,a,n,o,s,l){const r=Math.max(0,Math.min(l,o/2,s/2));t.beginPath(),t.moveTo(a+r,n),t.lineTo(a+o-r,n),t.quadraticCurveTo(a+o,n,a+o,n+r),t.lineTo(a+o,n+s-r),t.quadraticCurveTo(a+o,n+s,a+o-r,n+s),t.lineTo(a+r,n+s),t.quadraticCurveTo(a,n+s,a,n+s-r),t.lineTo(a,n+r),t.quadraticCurveTo(a,n,a+r,n),t.closePath()}function ea(){i.save(),i.globalCompositeOperation="lighter";for(const t of e.motes){const a=.7+Math.sin(e.time*2+t.phase)*.3;i.fillStyle=`rgba(255,150,80,${t.alpha*a})`,i.beginPath(),i.arc(t.x,t.y,t.radius,0,k),i.fill()}i.restore()}function ta(t){for(const a of e.bricks){if(!a.alive)continue;const n=ve[a.kind],o=Math.min(4,a.h*.3);if(i.save(),a.kind==="volatile"){const r=.55+Math.sin(t*3.4+a.seed)*.25;i.shadowColor=`rgba(90,255,170,${.4*r})`,i.shadowBlur=9}ne(i,a.x,a.y,a.w,a.h,o);const s=i.createLinearGradient(0,a.y,0,a.y+a.h);if(s.addColorStop(0,n.top),s.addColorStop(1,n.bottom),i.fillStyle=s,i.fill(),i.shadowBlur=0,i.strokeStyle=n.edge,i.lineWidth=Math.max(.7,a.h*.05),i.stroke(),i.beginPath(),i.moveTo(a.x+o,a.y+Math.max(.7,a.h*.09)),i.lineTo(a.x+a.w-o,a.y+Math.max(.7,a.h*.09)),i.strokeStyle=n.glint,i.lineWidth=Math.max(.6,a.h*.04),i.stroke(),a.kind==="obsidian"){i.save(),ne(i,a.x,a.y,a.w,a.h,o),i.clip(),i.strokeStyle="rgba(190,220,245,0.13)",i.lineWidth=Math.max(.6,a.h*.03);for(let r=a.x-a.h;r<a.x+a.w+a.h;r+=a.h*.8)i.beginPath(),i.moveTo(r,a.y+a.h),i.lineTo(r+a.h,a.y),i.stroke();i.restore(),i.fillStyle="rgba(215,235,255,0.35)",i.beginPath(),i.arc(a.x+a.w*.16,a.y+a.h*.5,Math.max(.8,a.h*.07),0,k),i.arc(a.x+a.w*.84,a.y+a.h*.5,Math.max(.8,a.h*.07),0,k),i.fill()}if(a.kind==="volatile"){const r=a.x+a.w/2,c=a.y+a.h/2,h=Math.min(a.w,a.h)*.22,d=.7+Math.sin(t*5+a.seed)*.3;i.fillStyle=`rgba(200,255,225,${.7+d*.3})`,i.beginPath(),i.moveTo(r,c-h),i.lineTo(r+h,c),i.lineTo(r,c+h),i.lineTo(r-h,c),i.closePath(),i.fill()}if(a.maxHp>1&&a.maxHp<10){const r=Math.max(1,a.maxHp),c=Math.max(.8,a.h*.07);for(let h=0;h<r;h+=1)i.fillStyle=h<a.hp?"rgba(255,235,210,0.75)":"rgba(0,0,0,0.35)",i.fillRect(a.x+a.w*.14+h*c*2.6,a.y+a.h-c*2.2,c,c)}const l=1-a.hp/Math.max(1,a.maxHp);if(l>.05){const r=Math.min(4,Math.ceil(l*4));i.strokeStyle="rgba(8,5,8,0.6)",i.lineWidth=Math.max(.6,a.h*.035);for(let c=0;c<r;c+=1){const h=a.seed+c*17,d=a.x+a.w*(.2+T(h)*.6),u=a.y+a.h*(.2+T(h+3)*.6),m=T(h+7)*k,p=a.w*(.18+T(h+9)*.22);i.beginPath(),i.moveTo(d,u),i.lineTo(d+Math.cos(m)*p,u+Math.sin(m)*p*.65),i.stroke()}}a.flash>0&&(ne(i,a.x,a.y,a.w,a.h,o),i.fillStyle=`rgba(255,245,230,${a.flash*.8})`,i.fill()),i.restore()}}function aa(t){if(!e.guide||e.phase!=="docked"&&e.phase!=="playing"||e.balls.length===0)return;const a=e.phase==="docked";(a?e.balls.slice(0,1):e.balls.slice(0,2)).forEach((o,s)=>{if(!o)return;let l,r;if(a||o.stuck){const p=e.aimT*He;l=Math.sin(p),r=-Math.cos(p)}else{const p=Math.hypot(o.vx,o.vy);if(p<.001)return;l=o.vx/p,r=o.vy/p}const c=Je(o.x,o.y,l,r,o.r,a?5:3),h=a?.8:s===0?.32:.16,d=a?"255,186,105":"120,215,245",u=a?"125,255,190":"110,245,200";i.save(),i.lineCap="round",i.setLineDash(a?[5,7]:[3,9]);for(let p=0;p<c.points.length-1;p+=1){const x=c.points[p],y=c.points[p+1];!x||!y||(i.beginPath(),i.moveTo(x.x,x.y),i.lineTo(y.x,y.y),i.strokeStyle=`rgba(${d},${h*.18})`,i.lineWidth=3,i.stroke(),i.strokeStyle=`rgba(${d},${h})`,i.lineWidth=1,i.stroke())}i.setLineDash([]);for(let p=1;p<c.points.length-1;p+=1){const x=c.points[p];!x||c.catches.some(M=>Math.abs(M.x-x.x)<.5&&Math.abs(M.y-x.y)<.5)||(i.fillStyle=`rgba(${d},${h*.55})`,i.beginPath(),i.arc(x.x,x.y,1.6,0,k),i.fill())}for(const p of c.catches){const x=1+Math.sin(t*5)*.18;i.strokeStyle=`rgba(${u},${h})`,i.fillStyle=`rgba(${u},${h*.12})`,i.lineWidth=1.2,i.beginPath(),i.arc(p.x,p.y,o.r*1.9*x,0,k),i.fill(),i.stroke(),i.beginPath(),i.moveTo(p.x,p.y-o.r*2.2),i.lineTo(p.x,p.y-o.r*6),i.stroke(),i.fillStyle=`rgba(${u},${Math.min(1,h+.12)})`,i.fillRect(p.x-Math.max(1,o.r*.35),e.paddle.y-2,Math.max(2,o.r*.7),e.paddle.h+4),i.beginPath(),i.arc(p.x,p.y,Math.max(1,o.r*.42),0,k),i.fillStyle=`rgba(${u},${h})`,i.fill()}const m=c.points[c.points.length-1];if(m&&c.hitBrick){const p=1+Math.sin(t*6)*.15;i.strokeStyle=`rgba(${d},${h})`,i.lineWidth=1.2,i.beginPath(),i.arc(m.x,m.y,(a?5:3.5)*p,0,k),i.stroke(),i.beginPath(),i.arc(m.x,m.y,1.4,0,k),i.fillStyle=`rgba(${d},${h})`,i.fill()}else m&&c.lost&&(i.strokeStyle=`rgba(255,90,80,${h+.1})`,i.lineWidth=1.4,i.beginPath(),i.moveTo(m.x-3,m.y-3),i.lineTo(m.x+3,m.y+3),i.moveTo(m.x+3,m.y-3),i.lineTo(m.x-3,m.y+3),i.stroke());i.restore()})}function ia(t){for(const a of e.powerups){const n=pe[a.kind],o=g(e.width*.018,6.5,11),s=a.y+Math.sin(t*5+a.spin)*2;i.save(),i.translate(a.x,s),i.rotate(Math.PI/4),i.shadowColor=n.glow,i.shadowBlur=10;const l=i.createLinearGradient(-o,-o,o,o);l.addColorStop(0,"#ffffff"),l.addColorStop(.35,n.core),l.addColorStop(1,"#0a1210"),i.fillStyle=l,i.fillRect(-o/2,-o/2,o,o),i.shadowBlur=0,i.strokeStyle="rgba(255,255,255,0.65)",i.lineWidth=.8,i.strokeRect(-o/2,-o/2,o,o),i.restore(),i.fillStyle="#061014",i.font=`700 ${Math.round(o*1.15)}px ui-monospace, "SFMono-Regular", Menlo, monospace`,i.textAlign="center",i.textBaseline="middle",i.fillText(n.label,a.x,s+.5)}}function na(){for(const t of e.particles){const a=g(t.life/t.maxLife,0,1);i.save(),i.globalAlpha=a,t.kind==="spark"?(i.strokeStyle=`rgb(${t.color})`,i.lineWidth=t.size,i.beginPath(),i.moveTo(t.x,t.y),i.lineTo(t.x-t.vx*.018,t.y-t.vy*.018),i.stroke()):(i.translate(t.x,t.y),i.rotate(t.rotation),i.fillStyle=`rgb(${t.color})`,i.fillRect(-t.size/2,-t.size/2,t.size,t.size*.7)),i.restore()}}function oa(){for(const t of e.rings){const a=1-t.life/t.maxLife,n=t.from+(t.to-t.from)*a,o=Math.max(0,1-a);i.save(),i.strokeStyle=`rgba(${t.color},${o*.85})`,i.lineWidth=2,i.beginPath(),i.arc(t.x,t.y,n,0,k),i.stroke(),i.restore()}}function sa(){for(const t of e.floats){const a=g(t.life/t.maxLife,0,1);i.save(),i.globalAlpha=a,i.fillStyle=t.color,i.font=`700 ${Math.round(t.size)}px ui-monospace, "SFMono-Regular", Menlo, monospace`,i.textAlign="center",i.textBaseline="middle",i.fillText(t.text,t.x,t.y),i.restore()}}function ra(){const t=se(),a=e.wideTimer>0;i.save();const n=i.createLinearGradient(0,t.y,0,t.y+t.h);a?(n.addColorStop(0,"#bdf5ff"),n.addColorStop(.5,"#4fbfe0"),n.addColorStop(1,"#123746")):(n.addColorStop(0,"#e6c3a2"),n.addColorStop(.42,"#8c5a3c"),n.addColorStop(1,"#2a1712")),ne(i,t.x,t.y,t.w,t.h,t.h*.45),i.fillStyle=n,i.fill(),i.strokeStyle=a?"rgba(150,240,255,0.8)":"rgba(255,190,130,0.75)",i.lineWidth=1,i.stroke();const o=i.createLinearGradient(t.x,0,t.x+t.w,0);o.addColorStop(0,"rgba(255,120,40,0)"),o.addColorStop(.5,a?"rgba(170,245,255,0.95)":"rgba(255,150,60,0.95)"),o.addColorStop(1,"rgba(255,120,40,0)"),i.fillStyle=o,i.fillRect(t.x+t.w*.1,t.y+t.h*.42,t.w*.8,Math.max(1,t.h*.16)),i.fillStyle=a?"rgba(200,250,255,0.65)":"rgba(255,220,170,0.55)",i.fillRect(t.x+t.w*.07,t.y+t.h*.25,t.w*.03,t.h*.5),i.fillRect(t.x+t.w*.9,t.y+t.h*.25,t.w*.03,t.h*.5),i.restore()}function la(){for(const t of e.balls){i.save(),i.globalCompositeOperation="lighter";for(let n=0;n<t.trail.length;n+=1){const o=t.trail[n];if(!o)continue;const s=Math.max(0,o.life)*.38,l=t.r*(.35+Math.max(0,o.life)*.65);i.fillStyle=`rgba(255,120,45,${s})`,i.beginPath(),i.arc(o.x,o.y,l,0,k),i.fill()}i.restore();const a=i.createRadialGradient(t.x,t.y,t.r*.1,t.x,t.y,t.r*2.6);if(a.addColorStop(0,"rgba(255,250,230,1)"),a.addColorStop(.25,"rgba(255,200,120,0.95)"),a.addColorStop(.62,"rgba(255,110,40,0.4)"),a.addColorStop(1,"rgba(255,80,20,0)"),i.fillStyle=a,i.beginPath(),i.arc(t.x,t.y,t.r*2.6,0,k),i.fill(),i.fillStyle="#fff4dd",i.beginPath(),i.arc(t.x,t.y,t.r*.68,0,k),i.fill(),t.stuck){const n=.5+Math.sin(e.time*4)*.2;i.strokeStyle=`rgba(255,190,110,${n})`,i.lineWidth=1,i.beginPath(),i.arc(t.x,t.y,t.r*2.2,0,k),i.stroke()}if(e.blastTimer>0){const n=.6+Math.sin(e.time*8)*.18;i.strokeStyle=`rgba(255,170,85,${n})`,i.lineWidth=1.4,i.beginPath(),i.arc(t.x,t.y,t.r*2.7,0,k),i.stroke(),i.strokeStyle="rgba(255,215,150,0.9)",i.lineWidth=1.2,i.beginPath(),i.arc(t.x,t.y,t.r*3.2,-Math.PI/2,-Math.PI/2+k*g(e.blastTimer/12,0,1)),i.stroke()}else if(e.warpReady&&t===e.balls[0]&&!t.stuck){const n=.45+Math.sin(e.time*6)*.15;i.strokeStyle=`rgba(87,242,168,${n})`,i.lineWidth=1,i.setLineDash([3,4]),i.beginPath(),i.arc(t.x,t.y,t.r*2.4,0,k),i.stroke(),i.setLineDash([])}}}function ca(t){if(!e.hasSized)return;i.setTransform(e.dpr,0,0,e.dpr,0,0),i.clearRect(0,0,e.width,e.height);const n=e.settings.screenShake&&!yt()?Math.min(e.shake,14):0;if(i.save(),n>.1&&i.translate((Math.random()*2-1)*n,(Math.random()*2-1)*n),e.bg?i.drawImage(e.bg,0,0,e.width,e.height):(i.fillStyle="#08060a",i.fillRect(0,0,e.width,e.height)),ea(),ta(t),aa(t),ia(t),na(),ra(),la(),oa(),sa(),i.restore(),e.bulletActive){const o=e.width/2,s=e.height/2,l=i.createRadialGradient(o,s,Math.min(e.width,e.height)*.24,o,s,Math.max(e.width,e.height)*.74);l.addColorStop(0,"rgba(70,170,235,0)"),l.addColorStop(.7,"rgba(70,170,235,0.05)"),l.addColorStop(1,"rgba(120,225,255,0.18)"),i.fillStyle=l,i.fillRect(0,0,e.width,e.height)}if(e.warpActive){i.save(),i.globalCompositeOperation="lighter",i.lineWidth=1.6;for(let o=0;o<10;o+=1){const s=o%2===0,r=(e.time*900+o*91)%(e.height+90)-45,c=28+o%4*18,h=s?0:e.width,d=s?c*2.2:e.width-c*2.2,u=i.createLinearGradient(h,r,d,r);u.addColorStop(0,"rgba(255,175,95,0.28)"),u.addColorStop(1,"rgba(255,175,95,0)"),i.strokeStyle=u,i.beginPath(),i.moveTo(h,r),i.lineTo(d,r),i.stroke()}i.restore()}e.flash>0&&(i.fillStyle=`rgba(255,120,55,${e.flash*.16})`,i.fillRect(0,0,e.width,e.height))}function Ie(t){const a=f.canvas.getBoundingClientRect(),n=t.clientX-a.left;e.paddle.targetX=g(n,e.paddle.w/2,e.width-e.paddle.w/2)}function da(){f.canvas.addEventListener("pointermove",t=>{Ie(t)}),f.canvas.addEventListener("pointerdown",t=>{b.unlock(),Ie(t);try{f.canvas.setPointerCapture(t.pointerId)}catch{}t.pointerType==="mouse"&&e.phase==="docked"?he():t.pointerType!=="mouse"&&(e.pendingLaunch=!0),t.preventDefault()}),f.canvas.addEventListener("pointerup",t=>{e.pendingLaunch&&e.phase==="docked"&&he(),e.pendingLaunch=!1,t.preventDefault()}),f.canvas.addEventListener("pointercancel",()=>{e.pendingLaunch=!1}),f.canvas.addEventListener("pointerleave",()=>{e.pendingLaunch=!1}),f.canvas.addEventListener("contextmenu",t=>t.preventDefault()),window.addEventListener("keydown",t=>{const a=t.key.toLowerCase();["arrowleft","arrowright","arrowup","arrowdown"," "].includes(a)&&t.preventDefault(),a==="arrowleft"||a==="a"?(e.keys.left=!0,b.unlock()):a==="arrowright"||a==="d"?(e.keys.right=!0,b.unlock()):a===" "||a==="enter"?(b.unlock(),e.phase==="docked"?he():e.phase==="paused"?ge():(e.phase==="gameOver"||e.phase==="victory")&&U()):a==="p"||a==="escape"?te():a==="r"?U():a==="g"?We():a==="m"?De():a==="b"?(b.unlock(),Ee()):a==="f"&&(b.unlock(),$e())}),window.addEventListener("keyup",t=>{const a=t.key.toLowerCase();(a==="arrowleft"||a==="a")&&(e.keys.left=!1),(a==="arrowright"||a==="d")&&(e.keys.right=!1)}),window.addEventListener("blur",()=>{e.keys.left=!1,e.keys.right=!1,(e.phase==="docked"||e.phase==="playing")&&te()}),document.addEventListener("visibilitychange",()=>{document.hidden&&(e.phase==="docked"||e.phase==="playing")&&te()}),f.guideButton.addEventListener("click",()=>{We(),b.unlock()}),f.soundButton.addEventListener("click",()=>{De()}),f.warpButton.addEventListener("click",()=>{b.unlock(),$e()}),f.bulletButton.addEventListener("click",()=>{b.unlock(),Ee()}),f.pauseButton.addEventListener("click",()=>{te(),b.unlock()})}function We(){e.guide=!e.guide,xe(),C(e.guide?"Bounce prediction on":"Bounce prediction off")}function De(){e.soundOn=!e.soundOn,b.setMuted(!e.soundOn),Xe(),C(e.soundOn?"Sound on":"Sound muted")}const ha=ut(t=>{e.settings=t,e.guide=t.showGuide,xe()});window.addEventListener("pagehide",ha,{once:!0});e.bricks=Ve(0);we();re();e.phase="docked";e.lives=ze();O();xe();Xe();H();I();da();Lt();var Ne;oe(`SEAM 01 / ${String(E.length).padStart(2,"0")}`,((Ne=E[0])==null?void 0:Ne.name.toUpperCase())??"ASHFALL",1900);B("Obsidian Breaker. Drag to move, tap or press Space to launch. B slows time, F warps.");typeof ResizeObserver<"u"&&new ResizeObserver(()=>we()).observe(f.app);window.addEventListener("resize",we);let ae=0;function Qe(t){requestAnimationFrame(Qe),ae===0&&(ae=t);const a=g((t-ae)/1e3,.001,.05);ae=t,Qt(a),ca(t/1e3)}requestAnimationFrame(Qe);
</script>
<style>:root{--void: #060408;--rock: #100a10;--rock-2: #1a1016;--panel: rgba(16, 10, 15, .82);--ember: #ff7a2f;--ember-hot: #ffb35c;--ember-dim: rgba(255, 122, 47, .24);--lava: #ff3d1c;--jade: #57f2a8;--steel: #9db4cc;--bone: #f4e6d3;--muted: #a58d7a;--line: rgba(255, 145, 80, .22);--line-strong: rgba(255, 145, 80, .45);--shadow: 0 18px 50px rgba(0, 0, 0, .55);--mono: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;--sans: "Avenir Next", "Trebuchet MS", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;--napplet-text-selection: none;color-scheme:dark}*{box-sizing:border-box}:where(html,body,body *){-webkit-user-select:var(--napplet-text-selection, none);user-select:var(--napplet-text-selection, none)}html,body,#app{height:100%;margin:0;background:var(--void);color:var(--bone)}body{overflow:hidden;overscroll-behavior:none;-webkit-tap-highlight-color:transparent}button{font:inherit;color:inherit}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}.app{container-type:inline-size;position:relative;height:100%;overflow:hidden;background:radial-gradient(120% 80% at 50% -10%,rgba(255,104,36,.12),transparent 55%),var(--void);font-family:var(--sans);isolation:isolate}.app:after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;background:repeating-linear-gradient(to bottom,rgba(255,255,255,.018) 0,rgba(255,255,255,.018) 1px,transparent 1px,transparent 3px);content:"";pointer-events:none}.board{position:absolute;top:0;right:0;bottom:0;left:0;display:block;width:100%;height:100%;touch-action:none;cursor:crosshair}.hud{position:absolute;top:0;right:0;left:0;z-index:2;display:flex;align-items:flex-start;gap:14px;padding:max(9px,env(safe-area-inset-top)) max(10px,env(safe-area-inset-right)) 22px max(10px,env(safe-area-inset-left));background:linear-gradient(to bottom,rgba(8,5,10,.92),rgba(8,5,10,.55) 55%,transparent);pointer-events:none}.hud-brand{display:flex;align-items:center;gap:7px;min-width:0;padding-top:2px}.brand-mark{display:grid;width:24px;height:24px;place-items:center;border:1px solid var(--line-strong);border-radius:7px;background:radial-gradient(circle at 35% 30%,#ffa05059,#1e0c0ce6);box-shadow:0 0 16px #ff6c2447;color:var(--ember-hot);font-size:11px}.brand-text{color:var(--bone);font-size:9px;font-weight:800;line-height:1.05;letter-spacing:.18em;text-transform:uppercase;text-shadow:0 0 14px rgba(255,120,50,.3)}.hud-stats{display:flex;flex:1;align-items:flex-start;gap:10px;min-width:0;margin:0}.stat{display:grid;gap:1px;min-width:0}.stat+.stat{padding-left:10px;border-left:1px solid rgba(255,150,90,.14)}.stat dt{color:var(--muted);font-size:8px;font-weight:700;line-height:1.2;letter-spacing:.16em;text-transform:uppercase}.stat dd{margin:0;color:var(--bone);font-family:var(--mono);font-size:14px;font-variant-numeric:tabular-nums;font-weight:700;line-height:1.2;white-space:nowrap}.stat-score dd{color:var(--ember-hot);text-shadow:0 0 12px rgba(255,130,50,.3)}.stat-chain dd{color:var(--muted)}.stat-chain.is-hot dd{color:var(--jade);text-shadow:0 0 12px rgba(87,242,168,.55)}.stat-lives dd{color:var(--ember);font-size:12px;letter-spacing:.08em}.hud-actions{display:flex;gap:6px;margin-left:auto;pointer-events:auto}.icon-button{display:grid;width:32px;height:32px;padding:0;place-items:center;border:1px solid var(--line);border-radius:9px;background:var(--panel);color:var(--bone);font-size:13px;line-height:1;cursor:pointer;touch-action:manipulation;transition:border-color .14s ease,color .14s ease,background .14s ease}.icon-button:hover{border-color:var(--ember);color:var(--ember-hot);background:#261210eb}.icon-button.is-off{color:var(--muted);opacity:.55}.ability-button{position:relative}.ability-button.is-ready{border-color:#57f2a8bf;color:var(--jade);background:#123024c7;box-shadow:0 0 14px #57f2a859,0 0 0 1px #57f2a82e inset;animation:ability-pulse 1.4s ease-in-out infinite}.ability-button.is-active{border-color:#7de8ffe6;color:#c9f7ff;background:#102b36e6;box-shadow:0 0 16px #7de8ff66}.ability-button[aria-disabled=true]{color:var(--muted);opacity:.5}@keyframes ability-pulse{0%,to{box-shadow:0 0 10px #57f2a840}50%{box-shadow:0 0 20px #57f2a88c}}.icon-button:focus-visible,.btn:focus-visible{outline:2px solid var(--ember-hot);outline-offset:2px}.banner{position:absolute;top:17%;left:50%;z-index:3;display:grid;gap:2px;justify-items:center;width:max-content;max-width:90%;transform:translate(-50%,-8px);opacity:0;text-align:center;pointer-events:none;transition:opacity .24s ease,transform .24s ease}.banner.is-visible{transform:translate(-50%);opacity:1}.banner-kicker{color:var(--ember);font-family:var(--mono);font-size:9px;font-weight:700;letter-spacing:.3em;text-transform:uppercase}.banner-title{color:var(--bone);font-size:clamp(18px,4.2vmin,34px);font-weight:800;letter-spacing:.09em;line-height:1;text-transform:uppercase;text-shadow:0 0 26px rgba(255,112,40,.42),0 2px 12px rgba(0,0,0,.8)}.hint{position:absolute;bottom:calc(max(6px,env(safe-area-inset-bottom)) + 16px);left:50%;z-index:2;margin:0;transform:translate(-50%);color:var(--muted);font-family:var(--mono);font-size:9px;letter-spacing:.12em;text-align:center;text-transform:uppercase;white-space:nowrap;pointer-events:none;text-shadow:0 1px 8px rgba(0,0,0,.9)}.bullet-meter{position:absolute;bottom:max(4px,env(safe-area-inset-bottom));left:50%;z-index:2;display:flex;align-items:center;gap:6px;width:min(190px,46%);transform:translate(-50%);pointer-events:none}.bullet-label{color:var(--muted);font-family:var(--mono);font-size:7px;font-weight:700;letter-spacing:.18em;text-transform:uppercase}.bullet-track{flex:1;height:4px;overflow:hidden;border:1px solid rgba(125,232,255,.22);border-radius:999px;background:#081216cc}.bullet-fill{display:block;width:100%;height:100%;transform:scaleX(1);transform-origin:left center;background:linear-gradient(90deg,#58a8c8,#7ce8ff 60%,#d3f8ff);box-shadow:0 0 8px #7de8ff99;transition:transform 80ms linear}.bullet-meter.is-active .bullet-fill{background:linear-gradient(90deg,#7ce8ff,#e6fdff);box-shadow:0 0 14px #7de8ffe6}.bullet-meter.is-low .bullet-fill{background:linear-gradient(90deg,#ff5a3c,#ffb35c);box-shadow:0 0 10px #ff5a3cb3}.toast{position:absolute;bottom:calc(max(6px,env(safe-area-inset-bottom)) + 30px);left:50%;z-index:4;padding:6px 12px;transform:translate(-50%,6px);border:1px solid var(--line);border-radius:999px;background:#0e090deb;color:var(--ember-hot);font-family:var(--mono);font-size:9px;letter-spacing:.14em;text-transform:uppercase;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .18s ease,transform .18s ease}.toast.is-visible{transform:translate(-50%);opacity:1}.overlay{position:absolute;top:0;right:0;bottom:0;left:0;z-index:5;display:grid;padding:16px;place-items:center;background:radial-gradient(80% 60% at 50% 40%,rgba(255,90,30,.1),transparent 60%),#050307bd;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px)}.overlay[hidden]{display:none}.overlay-card{display:grid;width:min(360px,100%);max-height:100%;gap:7px;overflow:auto;padding:clamp(17px,4vw,26px);border:1px solid var(--line-strong);border-radius:14px;background:linear-gradient(160deg,rgba(255,120,45,.08),transparent 45%),linear-gradient(180deg,#180e14fa,#0a060afa);box-shadow:var(--shadow),0 0 0 1px #0006 inset;text-align:center}.overlay-kicker{margin:0;color:var(--ember);font-family:var(--mono);font-size:9px;font-weight:700;letter-spacing:.3em;text-transform:uppercase}.overlay-card h2{margin:0;color:var(--bone);font-size:clamp(20px,4vmin,28px);font-weight:800;letter-spacing:.03em;line-height:1.08;text-transform:uppercase;text-shadow:0 0 24px rgba(255,112,40,.35)}.overlay-body{margin:0;color:var(--muted);font-size:12px;line-height:1.55}.overlay-actions{display:grid;gap:8px;margin-top:6px}.overlay-note{min-height:1em;margin:4px 0 0;color:#a58d7abf;font-size:10px;line-height:1.4}.btn{min-height:42px;padding:0 16px;border:1px solid var(--line-strong);border-radius:9px;background:#ff8c460f;color:var(--bone);font-size:11px;font-weight:700;letter-spacing:.12em;text-transform:uppercase;cursor:pointer;touch-action:manipulation;transition:border-color .14s ease,background .14s ease,color .14s ease}.btn:hover{border-color:var(--ember);background:#ff8c461f;color:var(--ember-hot)}.btn-primary{border-color:transparent;background:linear-gradient(180deg,var(--ember-hot),var(--ember));color:#1a0a05;box-shadow:0 8px 24px #ff6e284d}.btn-primary:hover{border-color:transparent;background:linear-gradient(180deg,#ffd08f,#ff8b45);color:#180803}@container (max-width: 620px){.hud{gap:10px;padding-bottom:18px}.brand-text,.stat-best{display:none}.hud-stats{gap:8px}.stat+.stat{padding-left:8px}.stat dd{font-size:12px}.stat-lives dd{font-size:10px}}@container (max-width: 460px){.hud{gap:6px;padding-top:max(5px,env(safe-area-inset-top));padding-right:7px;padding-left:7px}.brand-mark{width:21px;height:21px;font-size:9px}.stat dt{display:none}.stat dd{font-size:11px}.stat-lives dd{font-size:9px}.icon-button{width:28px;height:28px;border-radius:8px;font-size:11px}#guideButton,#soundButton,.hint{display:none}.bullet-meter{width:min(150px,42%)}}@container (max-width: 330px){.stat-chain,.stat-best,#guideButton,#soundButton{display:none}.hud{gap:4px;padding-bottom:12px}.hud-stats{gap:5px}.stat+.stat{padding-left:5px}.stat dd{font-size:10px}.icon-button{width:24px;height:24px;font-size:10px}.bullet-label{display:none}.bullet-meter{width:84px}}@container (max-width: 260px){.brand-mark{display:none}}@media(max-height:300px){.hud{padding-top:max(4px,env(safe-area-inset-top));padding-bottom:12px}.hint,.brand-text,.stat-best{display:none}.banner{top:30%}.banner-title{font-size:clamp(15px,4.6vmin,24px)}.overlay-card{padding:14px 16px;gap:5px}.overlay-body,.overlay-note{font-size:10px}.overlay-actions{grid-auto-flow:column;justify-content:center}.btn{min-height:34px;padding:0 12px;font-size:9px}}@media(pointer:coarse){.icon-button{width:40px;height:40px;font-size:14px}.btn{min-height:46px}.board{cursor:none}}@media(max-height:300px){@container (max-width: 420px){.hud-actions{gap:4px}.icon-button{width:26px;height:26px;font-size:10px}}}@media(prefers-reduced-motion:reduce){.banner,.toast,.icon-button,.btn,.bullet-fill{transition-duration:.01ms}.ability-button.is-ready{animation:none}}
</style>
<meta name="napplet-type" content="my-napplet">
<meta name="napplet-config-schema" content="{"$schema":"http://json-schema.org/draft-07/schema#","$version":1,"type":"object","additionalProperties":false,"properties":{"difficulty":{"type":"string","title":"Run difficulty","description":"Chill grants more shields and a slower ball. Ruthless is a short fuse.","enum":["chill","standard","ruthless"],"default":"standard","x-napplet-section":"Run","x-napplet-order":1},"aimPace":{"type":"string","title":"Seer sweep speed","description":"How fast the predicted launch angle sweeps across the seam.","enum":["slow","steady","quick"],"default":"steady","x-napplet-section":"Run","x-napplet-order":2},"showGuide":{"type":"boolean","title":"Show bounce prediction","description":"Draw the reflected trajectory line, live slab catch ring and impact markers.","default":true,"x-napplet-section":"Assist","x-napplet-order":1},"screenShake":{"type":"boolean","title":"Impact screen shake","description":"Shake the seam on hard impacts and chain explosions.","default":true,"x-napplet-section":"Feedback","x-napplet-order":1}}}">
</head>
<body>
<main id="app" class="app">
<canvas id="board" class="board" aria-label="Obsidian Breaker playfield" role="img">
Obsidian Breaker: a breakout game about shattering volatile mineral seams.
</canvas>
<header class="hud">
<div class="hud-brand" aria-hidden="true">
<span class="brand-mark">◆</span>
<span class="brand-text">OBSIDIAN<br />BREAKER</span>
</div>
<dl class="hud-stats">
<div class="stat stat-score">
<dt>Score</dt>
<dd id="scoreValue">0</dd>
</div>
<div class="stat stat-chain" id="chainStat">
<dt>Chain</dt>
<dd id="chainValue">—</dd>
</div>
<div class="stat stat-lives">
<dt>Shields</dt>
<dd id="livesValue" aria-label="3 shields">◆◆◆</dd>
</div>
<div class="stat">
<dt>Seam</dt>
<dd id="levelValue">01/07</dd>
</div>
<div class="stat stat-best">
<dt>Best</dt>
<dd id="bestValue">0</dd>
</div>
</dl>
<div class="hud-actions">
<button id="warpButton" class="icon-button ability-button" type="button" aria-pressed="false" aria-label="Warp (F): needs 3 projected wall or stone impacts" title="Warp (F): fast-forward through 3 projected impacts">
<span aria-hidden="true">»</span>
</button>
<button id="bulletButton" class="icon-button ability-button" type="button" aria-pressed="false" aria-label="Bullet time (B): 100% charged" title="Bullet time (B)">
<span aria-hidden="true">◒</span>
</button>
<button id="guideButton" class="icon-button" type="button" aria-pressed="true" aria-label="Toggle bounce prediction" title="Toggle bounce prediction">
<span aria-hidden="true">✧</span>
</button>
<button id="soundButton" class="icon-button" type="button" aria-pressed="true" aria-label="Mute sound" title="Mute sound">
<span aria-hidden="true">♪</span>
</button>
<button id="pauseButton" class="icon-button" type="button" aria-label="Pause" title="Pause">
<span aria-hidden="true">❚❚</span>
</button>
</div>
</header>
<div id="banner" class="banner" aria-hidden="true">
<span id="bannerKicker" class="banner-kicker"></span>
<span id="bannerTitle" class="banner-title"></span>
</div>
<p id="hint" class="hint">Drag to steer · Space launch · B bullet time · F warp · P pause</p>
<div id="bulletMeter" class="bullet-meter" role="progressbar" aria-label="Bullet time charge" aria-valuemin="0" aria-valuemax="100" aria-valuenow="100">
<span class="bullet-label">Bullet</span>
<span class="bullet-track"><span id="bulletFill" class="bullet-fill"></span></span>
</div>
<div id="toast" class="toast" aria-live="polite"></div>
<div id="overlay" class="overlay" hidden>
<div class="overlay-card" role="dialog" aria-modal="true" aria-labelledby="overlayTitle">
<p id="overlayKicker" class="overlay-kicker">Paused</p>
<h2 id="overlayTitle">The seam waits</h2>
<p id="overlayBody" class="overlay-body"></p>
<div id="overlayActions" class="overlay-actions"></div>
<p id="overlayNote" class="overlay-note"></p>
</div>
</div>
<p id="gameStatus" class="sr-only" aria-live="polite"></p>
</main>
</body>
</html>
