SIMON Master Console Rebuild
True command center for SIMON + WEB360
This rebuild gives you a cleaner production spine: interactive tree, live viewer, guarded editor, dynamic graphics, architecture map, and note storage. It is designed to sit beside your existing
console.php
and become the stronger replacement path.
Dashboard
Tree
Viewer
System Map
File Registry
Notes
Files
15,338
Folders
408
Scanned Size
3.84 GB
PHP Files
890
Editable Text Files
12,599
File viewer
guarded to /htdocs
/web360/web360_workspace/demo/index.php
<?php declare(strict_types=1); header('Content-Type: text/html; charset=UTF-8'); header('X-Content-Type-Options: nosniff'); header('Referrer-Policy: strict-origin-when-cross-origin'); ?> <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,viewport-fit=cover"> <meta name="theme-color" content="#020712"> <meta name="impact-site-verification" content="e8ec6683-663c-421a-a30d-f8c0fe517345"> <title>SIMON ARCADE · Spatial Battleship</title> <script async src="https://www.googletagmanager.com/gtag/js?id=G-S6MEHB4BEY"></script> <script> window.dataLayer=window.dataLayer||[]; function gtag(){dataLayer.push(arguments)} gtag('js',new Date()); gtag('config','G-S6MEHB4BEY',{anonymize_ip:true,send_page_view:true}); window.SIMON_CONFIG=Object.assign({ GA4_ID:'G-S6MEHB4BEY', IMPACT_VERIFICATION:'e8ec6683-663c-421a-a30d-f8c0fe517345', WEBHOOK_BASE:'/connlink/test1/ui/webhook_inbox.php', VISIT_STREAM:'visits', PULSE_STREAM:'default', KEY:'', ARCADE_URL:'/arcade/' },window.SIMON_CONFIG||{}); </script> <style> :root{--cyan:#5cf5ff;--blue:#4169ff;--violet:#8d5cff;--pink:#ff4e9b;--red:#ff405c;--amber:#ffc14d;--green:#46f2a0;--ink:#effaff;--muted:#9fb4cb;--glass:rgba(6,18,40,.68);--line:rgba(126,231,255,.22);--cell:min(7.7vw,38px)} *{box-sizing:border-box}html,body{width:100%;min-height:100%;margin:0;background:#020712;color:var(--ink);font-family:Inter,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;overflow-x:hidden}button,a{font:inherit}.sr{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0} body:before{content:"";position:fixed;inset:-25%;z-index:-5;background:radial-gradient(circle at 20% 20%,rgba(66,100,255,.28),transparent 26%),radial-gradient(circle at 84% 12%,rgba(141,92,255,.22),transparent 25%),radial-gradient(circle at 50% 88%,rgba(0,225,255,.15),transparent 28%),linear-gradient(150deg,#02050e,#07152c 48%,#020710);animation:drift 18s ease-in-out infinite alternate} @keyframes drift{to{transform:translate3d(2%,-1%,0) scale(1.03)}} #space,#fx{position:fixed;inset:0;width:100%;height:100%;pointer-events:none}#space{z-index:-3}#fx{z-index:50} .scanlines{position:fixed;inset:0;pointer-events:none;z-index:70;opacity:.18;background:repeating-linear-gradient(180deg,transparent 0 3px,rgba(120,220,255,.04) 4px)} .shell{width:min(1500px,100%);margin:auto;padding:max(12px,env(safe-area-inset-top)) max(12px,env(safe-area-inset-right)) max(18px,env(safe-area-inset-bottom)) max(12px,env(safe-area-inset-left));position:relative} .topbar{position:sticky;top:10px;z-index:30;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid var(--line);border-radius:22px;background:rgba(2,10,27,.72);backdrop-filter:blur(22px) saturate(145%);box-shadow:0 18px 60px rgba(0,0,0,.38),inset 0 1px rgba(255,255,255,.08)} .brand{display:flex;align-items:center;gap:11px;min-width:0}.orb{width:40px;height:40px;border-radius:50%;position:relative;background:radial-gradient(circle at 34% 28%,#fff 0 4%,#62faff 8%,#3159ff 35%,#070f30 68%);box-shadow:0 0 25px rgba(92,245,255,.75),inset -8px -10px 18px rgba(0,0,0,.5);animation:orb 3.2s ease-in-out infinite}.orb:after{content:"";position:absolute;inset:-5px;border:1px solid rgba(92,245,255,.5);border-radius:50%;animation:ring 2.2s linear infinite}@keyframes orb{50%{transform:translateY(-2px) scale(1.06)}}@keyframes ring{to{transform:scale(1.32);opacity:0}} .brand h1{font-size:clamp(15px,2.8vw,25px);margin:0;letter-spacing:.14em;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.brand small{display:block;color:var(--cyan);letter-spacing:.2em;font-size:9px;margin-top:2px} .actions{display:flex;gap:8px}.iconbtn,.primary{border:1px solid var(--line);color:var(--ink);cursor:pointer;display:grid;place-items:center;transition:.22s ease}.iconbtn{width:42px;height:42px;border-radius:50%;background:rgba(255,255,255,.055);font-weight:900}.iconbtn:hover,.iconbtn:focus-visible{transform:translateY(-2px);border-color:var(--cyan);box-shadow:0 0 22px rgba(92,245,255,.28)} .hero{display:grid;grid-template-columns:1fr auto;gap:14px;align-items:end;margin:18px 2px 12px}.hero h2{font-size:clamp(30px,7vw,72px);line-height:.92;margin:0;text-transform:uppercase;letter-spacing:-.055em;text-shadow:0 0 28px rgba(92,245,255,.28)}.hero h2 span{display:block;color:transparent;-webkit-text-stroke:1px rgba(111,234,255,.85)}.hero p{color:var(--muted);max-width:670px;margin:10px 0 0}.energybox{min-width:220px;padding:12px 14px;border:1px solid var(--line);border-radius:18px;background:var(--glass);backdrop-filter:blur(15px)}.energyhead{display:flex;justify-content:space-between;font-size:11px;letter-spacing:.16em;text-transform:uppercase}.meter{height:12px;border-radius:999px;background:#071022;overflow:hidden;margin-top:9px;border:1px solid rgba(255,255,255,.08)}.meter>i{display:block;height:100%;width:100%;background:linear-gradient(90deg,var(--pink),var(--violet),var(--cyan));box-shadow:0 0 18px var(--cyan);transition:width .4s ease} .command{display:flex;flex-wrap:wrap;gap:9px;margin:12px 0 16px}.primary{min-height:42px;border-radius:14px;padding:0 15px;background:linear-gradient(135deg,rgba(92,245,255,.2),rgba(65,105,255,.28));box-shadow:inset 0 1px rgba(255,255,255,.1)}.primary:hover{transform:translateY(-2px);box-shadow:0 10px 28px rgba(0,100,255,.2),inset 0 1px rgba(255,255,255,.15)}.primary.danger{background:linear-gradient(135deg,rgba(255,64,92,.18),rgba(141,92,255,.25))} .boards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(12px,2vw,24px);perspective:1500px}.card{position:relative;isolation:isolate;padding:clamp(12px,1.8vw,20px);border:1px solid var(--line);border-radius:26px;background:linear-gradient(145deg,rgba(9,25,53,.82),rgba(4,12,30,.58));box-shadow:0 30px 80px rgba(0,0,0,.38),inset 0 1px rgba(255,255,255,.08);backdrop-filter:blur(18px);overflow:hidden;transform-style:preserve-3d}.card:before{content:"";position:absolute;inset:0;z-index:-1;background:linear-gradient(115deg,transparent 20%,rgba(92,245,255,.08) 48%,transparent 70%);transform:translateX(-120%);animation:sweep 8s linear infinite}@keyframes sweep{to{transform:translateX(120%)}}.card.enemy{transform:rotateY(-2.5deg) rotateX(1deg)}.card.player{transform:rotateY(2.5deg) rotateX(1deg)} .cardhead{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:12px}.cardhead h3{margin:0;font-size:clamp(15px,2vw,21px);letter-spacing:.1em;text-transform:uppercase}.tag{font-size:9px;letter-spacing:.16em;color:var(--cyan);border:1px solid rgba(92,245,255,.28);border-radius:999px;padding:6px 9px}.grid{display:grid;grid-template-columns:repeat(10,var(--cell));grid-auto-rows:var(--cell);gap:clamp(2px,.55vw,5px);justify-content:center;transform-style:preserve-3d;filter:drop-shadow(0 18px 25px rgba(0,0,0,.3))}.cell{position:relative;border:1px solid rgba(129,220,255,.15);border-radius:clamp(5px,.7vw,9px);background:linear-gradient(145deg,rgba(25,72,121,.72),rgba(4,22,54,.92));box-shadow:inset 2px 2px 6px rgba(110,230,255,.08),inset -3px -5px 10px rgba(0,0,0,.38);transition:.18s ease;overflow:visible}.cell:after{content:"";position:absolute;inset:2px;border-radius:inherit;background:linear-gradient(145deg,rgba(255,255,255,.06),transparent 45%)}.cell.btn{cursor:crosshair}.cell.btn:hover{transform:translateZ(12px) scale(1.08);z-index:2;border-color:var(--cyan);box-shadow:0 0 18px rgba(92,245,255,.45),inset 0 0 12px rgba(92,245,255,.16)}.cell.ship{background:linear-gradient(145deg,#5269ff,#1d2d77);box-shadow:inset 0 0 0 2px rgba(255,255,255,.08),0 0 10px rgba(80,110,255,.28)}.cell.miss:before{content:"";position:absolute;left:50%;top:50%;width:24%;aspect-ratio:1;border-radius:50%;background:#b9eaff;transform:translate(-50%,-50%);box-shadow:0 0 12px #56cfff;z-index:3;animation:ripple 1.3s ease-out infinite}.cell.hit{background:radial-gradient(circle,#fff 0 5%,#ffd45c 10%,#ff5a42 35%,#65102c 72%);border-color:#ffba73;animation:hitPulse .55s ease-out}.cell.hit:before{content:"";position:absolute;inset:-35%;z-index:4;background:conic-gradient(from 0deg,transparent,#fff4 10deg,transparent 22deg,#ffab5266 38deg,transparent 55deg);animation:spin 1s linear infinite}.cell.sunk{background:linear-gradient(145deg,#ff315d,#41091c);box-shadow:0 0 20px rgba(255,49,93,.72),inset 0 0 12px #ffad7d;animation:sunk 1s ease-in-out infinite alternate}@keyframes ripple{to{width:90%;opacity:0}}@keyframes hitPulse{50%{transform:scale(1.28);filter:brightness(1.8)}}@keyframes spin{to{transform:rotate(1turn)}}@keyframes sunk{to{filter:brightness(1.35)}} .lower{display:grid;grid-template-columns:minmax(0,1.6fr) minmax(260px,.8fr);gap:14px;margin-top:16px}.panel{border:1px solid var(--line);border-radius:22px;background:var(--glass);backdrop-filter:blur(18px);padding:14px;box-shadow:0 18px 50px rgba(0,0,0,.26)}.paneltitle{font-size:11px;color:var(--cyan);letter-spacing:.18em;text-transform:uppercase;margin-bottom:9px}.log{height:126px;overflow:auto;font-size:13px;color:#d8ebff;line-height:1.55;padding-right:4px}.log div{padding:4px 8px;border-left:2px solid rgba(92,245,255,.28);margin:3px 0;background:rgba(255,255,255,.025)}.stats{display:grid;grid-template-columns:repeat(2,1fr);gap:8px}.stat{padding:10px;border:1px solid rgba(255,255,255,.08);border-radius:14px;background:rgba(255,255,255,.035)}.stat b{display:block;font-size:22px}.stat span{font-size:9px;color:var(--muted);letter-spacing:.12em;text-transform:uppercase} .drawer{position:fixed;inset:0 0 0 auto;width:min(430px,94vw);z-index:100;transform:translateX(105%);transition:.34s cubic-bezier(.2,.8,.2,1);padding:max(18px,env(safe-area-inset-top)) 18px max(18px,env(safe-area-inset-bottom));background:rgba(2,8,22,.93);backdrop-filter:blur(30px);border-left:1px solid var(--line);box-shadow:-30px 0 90px #0008;overflow:auto}.drawer.open{transform:none}.drawerhead{display:flex;justify-content:space-between;align-items:center}.drawer h2{font-size:32px;margin:20px 0 6px}.drawer p,.drawer li{color:var(--muted);line-height:1.55}.navlinks{display:grid;gap:10px;margin:20px 0}.navlinks a{color:var(--ink);text-decoration:none;padding:14px;border-radius:15px;border:1px solid var(--line);background:rgba(255,255,255,.045);display:flex;justify-content:space-between}.navlinks a:hover{border-color:var(--cyan);background:rgba(92,245,255,.08)}.backdrop{position:fixed;inset:0;z-index:90;background:#0008;opacity:0;pointer-events:none;transition:.3s}.backdrop.show{opacity:1;pointer-events:auto}.toast{position:fixed;left:50%;top:14%;z-index:80;transform:translate(-50%,-25px) scale(.92);opacity:0;pointer-events:none;padding:16px 22px;border-radius:18px;border:1px solid rgba(255,255,255,.22);background:rgba(4,12,30,.86);backdrop-filter:blur(18px);font-weight:900;letter-spacing:.13em;text-transform:uppercase;text-align:center;box-shadow:0 0 60px rgba(92,245,255,.22);transition:.3s}.toast.show{opacity:1;transform:translate(-50%,0) scale(1)} footer{text-align:center;color:#6f86a4;font-size:10px;letter-spacing:.12em;text-transform:uppercase;padding:20px 8px 4px} @media(max-width:920px){.hero{grid-template-columns:1fr}.energybox{min-width:0}.boards{grid-template-columns:1fr}.card.enemy,.card.player{transform:none}.lower{grid-template-columns:1fr}} @media(max-width:520px){:root{--cell:min(8.55vw,35px)}.shell{padding-left:8px;padding-right:8px}.topbar{border-radius:17px}.brand small{display:none}.hero h2{font-size:45px}.card{padding:10px;border-radius:18px}.cardhead .tag{display:none}.grid{gap:2px}.cell{border-radius:5px}.actions .iconbtn{width:39px;height:39px}.lower{margin-top:10px}.command{display:grid;grid-template-columns:repeat(2,1fr)}.primary{padding:0 8px;font-size:12px}} @media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.001ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important}} </style> </head> <body> <canvas id="space"></canvas><canvas id="fx"></canvas><div class="scanlines"></div> <div class="shell"> <header class="topbar"> <div class="brand"><div class="orb" aria-hidden="true"></div><div><h1>SIMON ARCADE</h1><small>Spatial Battleship System</small></div></div> <div class="actions"> <button class="iconbtn" id="soundBtn" aria-label="Toggle sound" title="Sound">♪</button> <button class="iconbtn" id="infoBtn" aria-label="Information and links" title="Information">I</button> </div> </header> <section class="hero"> <div><h2>Naval <span>Energy War</span></h2><p>Command a spatial fleet, charge your weapons, and trigger reactive explosions across a dynamic 3D tactical ocean.</p></div> <div class="energybox"><div class="energyhead"><span>Weapon energy</span><strong id="energyText">100%</strong></div><div class="meter"><i id="energyBar"></i></div></div> </section> <nav class="command" aria-label="Game controls"> <button class="primary" id="newGameBtn">New Battle</button> <button class="primary" id="showShipsBtn">Hide Fleet</button> <button class="primary" id="chargeBtn">Charge Energy</button> <button class="primary danger" id="arcadeBtn">Back to Arcade</button> </nav> <main class="boards"> <article class="card player"><div class="cardhead"><h3>Your Fleet</h3><span class="tag">Defense Grid</span></div><div id="playerGrid" class="grid" aria-label="Your fleet board"></div></article> <article class="card enemy"><div class="cardhead"><h3>Enemy Waters</h3><span class="tag">Tap to Fire</span></div><div id="computerGrid" class="grid" aria-label="Enemy board"></div></article> </main> <section class="lower"> <article class="panel"><div class="paneltitle">Battle intelligence</div><div id="log" class="log" aria-live="polite"></div></article> <article class="panel"><div class="paneltitle">Combat telemetry</div><div class="stats"><div class="stat"><b id="enemySunk">0/5</b><span>Enemy sunk</span></div><div class="stat"><b id="mySunk">0/5</b><span>Fleet lost</span></div><div class="stat"><b id="myHits">0</b><span>Your hits</span></div><div class="stat"><b id="enemyHits">0</b><span>Enemy hits</span></div></div></article> </section> <footer>© <?php echo date('Y'); ?> Gaylord Sinclair · SIMON ARCADE · Spatial Battleship v2.0</footer> </div> <div class="backdrop" id="backdrop"></div> <aside class="drawer" id="drawer" aria-hidden="true"> <div class="drawerhead"><strong>SIMON INFORMATION</strong><button class="iconbtn" id="closeInfo" aria-label="Close">×</button></div> <h2>Spatial Battleship</h2> <p>Fire on enemy coordinates, preserve weapon energy, and destroy all five enemy ships. Hits create energy bursts; sinking a ship triggers a full spatial detonation.</p> <ul><li>Each shot consumes 8 energy.</li><li>Energy regenerates automatically.</li><li>Charge Energy immediately restores 35 energy.</li><li>Sound begins after your first interaction and can be disabled.</li></ul> <div class="navlinks"><a href="/arcade/"><span>SIMON Arcade</span><b>↗</b></a><a href="/"><span>Gaylord Sinclair Home</span><b>↗</b></a><a href="/sitemap.php"><span>Site Map</span><b>↗</b></a><a href="/message.php"><span>Contact / Message</span><b>↗</b></a></div> <p>This game sends privacy-conscious event telemetry to the shared SIMON tracker configuration for diagnostics, engagement reporting, and future game improvements.</p> </aside> <div class="toast" id="toast"></div> <script> (()=>{'use strict'; const SIZE=10,SHIPS=[['Carrier',5],['Battleship',4],['Cruiser',3],['Submarine',3],['Destroyer',2]]; const $=id=>document.getElementById(id),CFG=window.SIMON_CONFIG||{}; const E={pg:$('playerGrid'),cg:$('computerGrid'),log:$('log'),energy:$('energyBar'),energyText:$('energyText'),toast:$('toast'),drawer:$('drawer'),backdrop:$('backdrop'),fx:$('fx'),space:$('space')}; let state,audio=null,sound=true,regenTimer=0; function track(event,data={}){const payload={event,title:'SIMON Arcade Battleship',game:'spatial_battleship',url:location.href,path:location.pathname,at:new Date().toISOString(),...data};try{if(typeof gtag==='function')gtag('event',event,{game_name:'spatial_battleship',...data})}catch(_){}try{const key=CFG.KEY?'&key='+encodeURIComponent(CFG.KEY):'';fetch((CFG.WEBHOOK_BASE||'/connlink/test1/ui/webhook_inbox.php')+'?stream='+encodeURIComponent(CFG.PULSE_STREAM||'default')+key,{method:'POST',headers:{'Content-Type':'application/json'},keepalive:true,body:JSON.stringify(payload)}).catch(()=>{})}catch(_){}} function visit(){track('game_view',{stream:CFG.VISIT_STREAM||'visits',referrer:document.referrer||'',agent:navigator.userAgent})} function initAudio(){if(audio)return;audio=new (window.AudioContext||window.webkitAudioContext)()} function tone(freq,dur=.12,type='sine',gain=.07,delay=0){if(!sound)return;initAudio();const t=audio.currentTime+delay,o=audio.createOscillator(),g=audio.createGain();o.type=type;o.frequency.setValueAtTime(freq,t);g.gain.setValueAtTime(.0001,t);g.gain.exponentialRampToValueAtTime(gain,t+.01);g.gain.exponentialRampToValueAtTime(.0001,t+dur);o.connect(g).connect(audio.destination);o.start(t);o.stop(t+dur+.03)} function sfx(kind){if(kind==='fire'){tone(170,.1,'sawtooth',.08);tone(70,.18,'square',.05,.04)}else if(kind==='hit'){tone(110,.35,'sawtooth',.1);tone(58,.5,'square',.06,.06)}else if(kind==='miss'){tone(520,.08,'sine',.03);tone(360,.12,'sine',.025,.07)}else if(kind==='sink'){[90,70,52,42].forEach((f,i)=>tone(f,.7,'sawtooth',.1,i*.08))}else if(kind==='win'){[440,554,659,880].forEach((f,i)=>tone(f,.25,'triangle',.07,i*.13))}else if(kind==='lose'){[240,180,120,70].forEach((f,i)=>tone(f,.35,'sawtooth',.06,i*.14))}else if(kind==='charge'){[220,330,440,660].forEach((f,i)=>tone(f,.18,'sine',.045,i*.08))}} function board(){return Array.from({length:SIZE},()=>Array.from({length:SIZE},()=>({shipId:null,hit:false,miss:false,sunk:false})))} function canPlace(b,r,c,h,n){if((h&&c+n>SIZE)||(!h&&r+n>SIZE))return false;for(let k=0;k<n;k++)if(b[r+(h?0:k)][c+(h?k:0)].shipId!==null)return false;return true} function fleet(b){return SHIPS.map(([name,size],id)=>{let coords=[],ok=false;for(let tries=0;!ok&&tries<1500;tries++){const h=Math.random()<.5,r=Math.floor(Math.random()*SIZE),c=Math.floor(Math.random()*SIZE);if(canPlace(b,r,c,h,size)){coords=Array.from({length:size},(_,k)=>[r+(h?0:k),c+(h?k:0)]);coords.forEach(([rr,cc])=>b[rr][cc].shipId=id);ok=true}}if(!ok)throw Error('Fleet placement failed');return{id,name,size,coords,hits:0,sunk:false}})} function reset(){state={turn:'player',playerBoard:board(),computerBoard:board(),playerShips:null,computerShips:null,showShips:true,aiTried:new Set,aiTargets:[],gameOver:false,energy:100,score:{playerHits:0,compHits:0,playerSunk:0,compSunk:0}};state.playerShips=fleet(state.playerBoard);state.computerShips=fleet(state.computerBoard);E.log.innerHTML='';render();log('New spatial battle initialized. Select an enemy coordinate.');showToast('Battle initialized');track('game_start');} function cellClass(d,own){return'cell'+(own&&state.showShips&&d.shipId!==null?' ship':'')+(d.hit?' hit':'')+(d.miss?' miss':'')+(d.sunk?' sunk':'')} function render(){E.pg.innerHTML='';E.cg.innerHTML='';for(let r=0;r<SIZE;r++)for(let c=0;c<SIZE;c++){const p=document.createElement('div');p.className=cellClass(state.playerBoard[r][c],true);p.setAttribute('aria-label',`Your coordinate ${r+1},${c+1}`);E.pg.append(p);const q=document.createElement('button');q.type='button';q.className=cellClass(state.computerBoard[r][c],false)+' btn';q.dataset.r=r;q.dataset.c=c;q.setAttribute('aria-label',`Fire at coordinate ${r+1},${c+1}`);q.addEventListener('click',playerFire);E.cg.append(q)}$('enemySunk').textContent=state.score.playerSunk+'/5';$('mySunk').textContent=state.score.compSunk+'/5';$('myHits').textContent=state.score.playerHits;$('enemyHits').textContent=state.score.compHits;E.energy.style.width=state.energy+'%';E.energyText.textContent=Math.round(state.energy)+'%';$('showShipsBtn').textContent=state.showShips?'Hide Fleet':'Show Fleet'} function spendEnergy(n){state.energy=Math.max(0,state.energy-n);render()} function playerFire(){if(state.gameOver||state.turn!=='player')return;initAudio();const r=+this.dataset.r,c=+this.dataset.c,d=state.computerBoard[r][c];if(d.hit||d.miss||d.sunk){log('Coordinate already scanned. Choose another target.');return}if(state.energy<8){showToast('Energy too low');log('Weapon energy below firing threshold. Charge or allow regeneration.');track('energy_blocked');return}sfx('fire');spendEnergy(8);burstAt(this,'fire');if(d.shipId!==null){d.hit=true;state.score.playerHits++;const ship=state.computerShips[d.shipId];ship.hits++;log(`Direct hit on enemy ${ship.name} at ${r+1},${c+1}.`);sfx('hit');burstAt(this,'hit');track('shot_hit',{row:r+1,column:c+1,ship:ship.name});if(ship.hits>=ship.size){ship.sunk=true;ship.coords.forEach(([rr,cc])=>state.computerBoard[rr][cc].sunk=true);state.score.playerSunk++;log(`Enemy ${ship.name} destroyed.`);showToast(`${ship.name} destroyed`);sfx('sink');shockwave();track('ship_sunk',{ship:ship.name,side:'enemy'})}}else{d.miss=true;log(`Shot missed at ${r+1},${c+1}.`);sfx('miss');burstAt(this,'miss');track('shot_miss',{row:r+1,column:c+1})}render();checkWin();if(!state.gameOver){state.turn='computer';setTimeout(computerTurn,520)}} function computerTurn(){if(state.gameOver)return;let t;while(state.aiTargets.length&&!t){const x=state.aiTargets.shift();if(!state.aiTried.has(x.join(',')))t=x}if(!t){let a=[];for(let r=0;r<SIZE;r++)for(let c=0;c<SIZE;c++)if(!state.aiTried.has(`${r},${c}`)&&(r+c)%2===0)a.push([r,c]);if(!a.length)for(let r=0;r<SIZE;r++)for(let c=0;c<SIZE;c++)if(!state.aiTried.has(`${r},${c}`))a.push([r,c]);t=a[Math.floor(Math.random()*a.length)]}const[r,c]=t,d=state.playerBoard[r][c];state.aiTried.add(`${r},${c}`);if(d.shipId!==null){d.hit=true;state.score.compHits++;const ship=state.playerShips[d.shipId];ship.hits++;log(`Enemy impact on your ${ship.name} at ${r+1},${c+1}.`);const node=E.pg.children[r*SIZE+c];burstAt(node,'hit');sfx('hit');[[1,0],[-1,0],[0,1],[0,-1]].forEach(([dr,dc])=>{const rr=r+dr,cc=c+dc,k=`${rr},${cc}`;if(rr>=0&&rr<SIZE&&cc>=0&&cc<SIZE&&!state.aiTried.has(k)&&!state.aiTargets.some(x=>x[0]===rr&&x[1]===cc))state.aiTargets.push([rr,cc])});if(ship.hits>=ship.size){ship.sunk=true;ship.coords.forEach(([rr,cc])=>state.playerBoard[rr][cc].sunk=true);state.score.compSunk++;log(`Your ${ship.name} has been destroyed.`);showToast(`Fleet loss: ${ship.name}`);sfx('sink');shockwave();track('ship_sunk',{ship:ship.name,side:'player'})}}else{d.miss=true;log(`Enemy fire missed at ${r+1},${c+1}.`);sfx('miss');burstAt(E.pg.children[r*SIZE+c],'miss')}render();checkWin();if(!state.gameOver)state.turn='player'} function checkWin(){if(state.score.playerSunk===SHIPS.length){state.gameOver=true;showToast('Victory');log('All enemy vessels destroyed. You win.');sfx('win');shockwave(true);track('game_win',{hits:state.score.playerHits})}else if(state.score.compSunk===SHIPS.length){state.gameOver=true;showToast('Fleet destroyed');log('Your fleet has been destroyed.');sfx('lose');shockwave();track('game_loss',{hits:state.score.playerHits})}} function log(msg){const d=document.createElement('div');d.textContent=new Date().toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'})+' · '+msg;E.log.append(d);E.log.scrollTop=E.log.scrollHeight} let toastTimer;function showToast(text){E.toast.textContent=text;E.toast.classList.add('show');clearTimeout(toastTimer);toastTimer=setTimeout(()=>E.toast.classList.remove('show'),1400)} function burstAt(node,type){if(!node)return;const r=node.getBoundingClientRect(),x=r.left+r.width/2,y=r.top+r.height/2,count=type==='hit'?34:14;for(let i=0;i<count;i++)particles.push({x,y,vx:(Math.random()-.5)*(type==='hit'?8:3),vy:(Math.random()-.5)*(type==='hit'?8:3)-1,life:1,size:Math.random()*4+1,hue:type==='hit'?Math.random()*45:190+Math.random()*30})} function shockwave(big=false){waves.push({x:innerWidth/2,y:innerHeight/2,r:10,life:1,big});if(navigator.vibrate)navigator.vibrate(big?[60,30,90]:[35,20,50])} const particles=[],waves=[];const fctx=E.fx.getContext('2d');function resize(){[E.fx,E.space].forEach(c=>{c.width=innerWidth*devicePixelRatio;c.height=innerHeight*devicePixelRatio;c.style.width=innerWidth+'px';c.style.height=innerHeight+'px'});fctx.setTransform(devicePixelRatio,0,0,devicePixelRatio,0,0);setupStars()}addEventListener('resize',resize,{passive:true}); function fxLoop(){fctx.clearRect(0,0,innerWidth,innerHeight);for(let i=particles.length-1;i>=0;i--){const p=particles[i];p.x+=p.vx;p.y+=p.vy;p.vy+=.11;p.life-=.025;if(p.life<=0){particles.splice(i,1);continue}fctx.beginPath();fctx.fillStyle=`hsla(${p.hue},100%,65%,${p.life})`;fctx.shadowBlur=14;fctx.shadowColor=`hsla(${p.hue},100%,60%,${p.life})`;fctx.arc(p.x,p.y,p.size*p.life,0,Math.PI*2);fctx.fill()}for(let i=waves.length-1;i>=0;i--){const w=waves[i];w.r+=w.big?24:15;w.life-=.035;if(w.life<=0){waves.splice(i,1);continue}fctx.beginPath();fctx.strokeStyle=`rgba(92,245,255,${w.life})`;fctx.lineWidth=w.big?7:4;fctx.arc(w.x,w.y,w.r,0,Math.PI*2);fctx.stroke()}requestAnimationFrame(fxLoop)} let stars=[];const sctx=E.space.getContext('2d');function setupStars(){stars=Array.from({length:Math.min(180,Math.floor(innerWidth/6))},()=>({x:Math.random()*innerWidth,y:Math.random()*innerHeight,z:Math.random()*1+.2,s:Math.random()*1.7+.3}))}function starLoop(){sctx.setTransform(devicePixelRatio,0,0,devicePixelRatio,0,0);sctx.clearRect(0,0,innerWidth,innerHeight);for(const s of stars){s.y+=.08*s.z;if(s.y>innerHeight)s.y=0;sctx.beginPath();sctx.fillStyle=`rgba(115,225,255,${.22+s.z*.45})`;sctx.arc(s.x,s.y,s.s*s.z,0,Math.PI*2);sctx.fill()}requestAnimationFrame(starLoop)} function openInfo(open=true){E.drawer.classList.toggle('open',open);E.backdrop.classList.toggle('show',open);E.drawer.setAttribute('aria-hidden',String(!open));if(open)track('info_open')} $('newGameBtn').onclick=()=>{initAudio();reset()};$('showShipsBtn').onclick=()=>{state.showShips=!state.showShips;render();track('fleet_visibility',{visible:state.showShips})};$('chargeBtn').onclick=()=>{initAudio();state.energy=Math.min(100,state.energy+35);sfx('charge');showToast('Energy charged');render();track('energy_charge',{energy:state.energy})};$('arcadeBtn').onclick=()=>{track('arcade_return');location.href=CFG.ARCADE_URL||'/arcade/'};$('soundBtn').onclick=()=>{sound=!sound;$('soundBtn').textContent=sound?'♪':'×';showToast(sound?'Sound online':'Sound muted');track('sound_toggle',{enabled:sound})};$('infoBtn').onclick=()=>openInfo(true);$('closeInfo').onclick=()=>openInfo(false);E.backdrop.onclick=()=>openInfo(false);addEventListener('keydown',e=>{if(e.key==='Escape')openInfo(false)}); regenTimer=setInterval(()=>{if(state&&!state.gameOver&&state.energy<100){state.energy=Math.min(100,state.energy+1.4);render()}},700); resize();fxLoop();starLoop();reset();visit(); })(); </script> </body> </html>
Save file
Quick jump
open a path
Open