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,680
Folders
433
Scanned Size
3.89 GB
PHP Files
988
Editable Text Files
12,938
File viewer
guarded to /htdocs
/downloads/MIND.html
<?php declare(strict_types=1); ?><!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" /> <title>Mind Firewall — SIMON Arcade</title> <meta name="theme-color" content="#070b16" /> <style> :root{ --bg:#060912; --bg2:#0b1222; --panel:rgba(255,255,255,.06); --panel2:rgba(255,255,255,.04); --edge:rgba(255,255,255,.14); --text:#eaf2ff; --muted:#aab7d8; --cyan:#73e5ff; --indigo:#7b62ff; --pink:#ff4fd6; --green:#44d18a; --warn:#ffcc4d; --bad:#ff607d; --shadow:0 22px 60px rgba(0,0,0,.45); --r:18px; } *{box-sizing:border-box} html,body{height:100%} body{ margin:0; overflow:hidden; color:var(--text); font-family:Inter,system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif; background: radial-gradient(1000px 600px at 20% 10%, rgba(123,98,255,.18), transparent 60%), radial-gradient(900px 500px at 85% 80%, rgba(255,79,214,.12), transparent 60%), radial-gradient(800px 600px at 50% 50%, rgba(115,229,255,.07), transparent 60%), linear-gradient(180deg, var(--bg2), var(--bg)); } #fx, #game{position:fixed; inset:0; width:100%; height:100%; display:block} #fx{opacity:.75} .hud{ position:fixed; inset:18px auto auto 18px; z-index:20; width:min(390px, calc(100vw - 36px)); border:1px solid var(--edge); border-radius:22px; background:linear-gradient(180deg, rgba(255,255,255,.09), rgba(255,255,255,.04)); backdrop-filter: blur(18px) saturate(130%); box-shadow:var(--shadow); padding:14px; transition:transform .28s ease, opacity .28s ease; } .hud.hidden{transform:translateX(calc(-100% - 30px)); opacity:.25; pointer-events:none} .top{display:flex; align-items:center; gap:12px; margin-bottom:10px} .orb{width:14px; height:14px; border-radius:50%; background:var(--cyan); box-shadow:0 0 0 7px rgba(115,229,255,.10),0 0 28px rgba(115,229,255,.35)} .title{display:flex; flex-direction:column; gap:3px; min-width:0} .title b{font-size:14px; letter-spacing:.12em} .title span{font-size:12px; color:var(--muted)} .spacer{flex:1} .btn, button{ appearance:none; border:1px solid var(--edge); color:var(--text); background:rgba(0,0,0,.22); border-radius:14px; padding:10px 12px; cursor:pointer; box-shadow:0 10px 26px rgba(0,0,0,.25); } .btn.primary{background:linear-gradient(180deg, rgba(123,98,255,.34), rgba(115,229,255,.16)); border-color:rgba(115,229,255,.28)} .toggle{ position:fixed; left:18px; top:18px; z-index:25; opacity:0; pointer-events:none; transition:opacity .28s ease; } .toggle.show{opacity:1; pointer-events:auto} .grid{display:grid; grid-template-columns:1fr 1fr; gap:10px} .card{border:1px solid var(--edge); border-radius:16px; background:rgba(0,0,0,.18); padding:12px} .card h3{margin:0 0 8px; font-size:12px; text-transform:uppercase; letter-spacing:.12em; color:var(--muted)} .metric{display:flex; justify-content:space-between; gap:10px; font-size:13px; padding:6px 0; border-bottom:1px solid rgba(255,255,255,.06)} .metric:last-child{border-bottom:none} .bar{height:10px; border-radius:999px; background:rgba(255,255,255,.08); overflow:hidden; border:1px solid rgba(255,255,255,.08)} .fill{height:100%; width:0%; background:linear-gradient(90deg, var(--indigo), var(--cyan))} .row{display:flex; gap:8px; flex-wrap:wrap} .legend{display:grid; grid-template-columns:1fr 1fr; gap:8px; font-size:12px; color:var(--muted)} .tag{display:inline-flex; align-items:center; gap:8px} .dot{width:10px; height:10px; border-radius:50%} .hint{font-size:12px; color:var(--muted); line-height:1.45} .banner{ position:fixed; right:18px; top:18px; z-index:18; max-width:min(420px, calc(100vw - 36px)); border:1px solid var(--edge); border-radius:18px; background:rgba(8,12,24,.72); backdrop-filter: blur(12px); padding:12px 14px; box-shadow:var(--shadow); } .banner b{display:block; margin-bottom:4px} .banner small{color:var(--muted)} @media (max-width:820px){.grid{grid-template-columns:1fr}} </style> </head> <body> <canvas id="fx"></canvas> <canvas id="game"></canvas> <button id="menuToggle" class="toggle btn primary">☰ Mind Menu</button> <div id="hud" class="hud"> <div class="top"> <div class="orb"></div> <div class="title"><b>MIND FIREWALL</b><span>puzzle + tower defense signal blocker</span></div> <div class="spacer"></div> <button id="hideBtn" title="Hide menu">✕</button> </div> <div class="grid"> <div class="card"> <h3>Threat State</h3> <div class="metric"><span>Core integrity</span><b id="integrityTxt">100%</b></div> <div class="bar"><div id="integrityBar" class="fill"></div></div> <div class="metric"><span>Signal energy</span><b id="energyTxt">140</b></div> <div class="metric"><span>Wave</span><b id="waveTxt">1</b></div> <div class="metric"><span>Score</span><b id="scoreTxt">0</b></div> <div class="metric"><span>Puzzle chain</span><b id="chainTxt">0</b></div> </div> <div class="card"> <h3>Build Nodes</h3> <div class="row"> <button class="btn primary" data-build="pulse">Pulse 25</button> <button class="btn" data-build="slow">Slow 35</button> <button class="btn" data-build="scrambler">Scrambler 55</button> </div> <div class="hint" style="margin-top:8px">Click an empty defense socket to place the selected node. Pulse = damage. Slow = delay. Scrambler = area burst.</div> </div> <div class="card"> <h3>Puzzle Matrix</h3> <div class="metric"><span>Rule</span><b>Match 3+ same glyphs</b></div> <div class="metric"><span>Reward</span><b>energy + chain + EMP</b></div> <div class="metric"><span>Control</span><b>swap adjacent cells</b></div> <div class="hint">Every match powers the firewall and shocks incoming thought-hack packets.</div> </div> <div class="card"> <h3>Legend</h3> <div class="legend"> <span class="tag"><i class="dot" style="background:#73e5ff"></i>Defense sockets</span> <span class="tag"><i class="dot" style="background:#ff607d"></i>Hack packets</span> <span class="tag"><i class="dot" style="background:#44d18a"></i>Core</span> <span class="tag"><i class="dot" style="background:#ffcc4d"></i>EMP burst</span> </div> </div> </div> </div> <div class="banner"><b>How to play</b><small>Build firewall nodes on the path. Then solve the glyph puzzle on the right by clicking two adjacent cells to swap them. Matches send defensive pulses into the network.</small></div> <script> (() => { 'use strict'; const fx = document.getElementById('fx'); const game = document.getElementById('game'); const fctx = fx.getContext('2d'); const ctx = game.getContext('2d'); const DPR = Math.max(1, Math.min(2, window.devicePixelRatio || 1)); const hud = document.getElementById('hud'); const menuToggle = document.getElementById('menuToggle'); const hideBtn = document.getElementById('hideBtn'); const ui = { integrityTxt: document.getElementById('integrityTxt'), integrityBar: document.getElementById('integrityBar'), energyTxt: document.getElementById('energyTxt'), waveTxt: document.getElementById('waveTxt'), scoreTxt: document.getElementById('scoreTxt'), chainTxt: document.getElementById('chainTxt') }; let W=0,H=0; function resize(){ W = innerWidth; H = innerHeight; for (const c of [fx, game]) { c.width = Math.floor(W * DPR); c.height = Math.floor(H * DPR); c.style.width = W + 'px'; c.style.height = H + 'px'; } fctx.setTransform(DPR,0,0,DPR,0,0); ctx.setTransform(DPR,0,0,DPR,0,0); recalcLayout(); } addEventListener('resize', resize, {passive:true}); const state = { integrity: 100, energy: 140, wave: 1, score: 0, chain: 0, selectedBuild: 'pulse', gameOver: false, message: 'DEFEND THE CORE', messageUntil: 0, sockets: [], towers: [], enemies: [], projectiles: [], particles: [], pulses: [], spawnBudget: 8, lastSpawn: 0, nextWaveAt: 0, path: [], board: [], boardSel: null, emp: 0, }; const costs = { pulse:25, slow:35, scrambler:55 }; const towerDefs = { pulse:{range:130, fireRate:0.75, damage:14, color:'#73e5ff'}, slow:{range:120, fireRate:1.05, damage:6, color:'#7b62ff', slow:0.55}, scrambler:{range:145, fireRate:1.6, damage:9, color:'#ff4fd6', splash:42} }; const boardCfg = { cols:7, rows:7, size:48, gap:5, x:0, y:0 }; const glyphs = ['◈','△','⬡','✦','⬢']; const glyphColors = ['#73e5ff','#7b62ff','#ff4fd6','#44d18a','#ffcc4d']; function recalcLayout(){ const leftW = Math.min(560, Math.max(420, W*0.54)); const rightPad = 36; boardCfg.size = Math.max(34, Math.min(54, Math.floor((W - leftW - rightPad*2)/boardCfg.cols) - boardCfg.gap)); boardCfg.x = W - (boardCfg.cols * boardCfg.size + (boardCfg.cols-1)*boardCfg.gap) - rightPad; boardCfg.y = Math.max(120, Math.floor(H*0.20)); const pathX = [70, 170, 170, 310, 310, leftW-70, leftW-70, leftW-160, leftW-160, leftW-40]; const pathY = [H-90, H-90, H*0.66, H*0.66, H*0.34, H*0.34, H*0.18, H*0.18, H*0.52, H*0.52]; state.path = pathX.map((x,i)=>({x,y:pathY[i]})); const core = state.path[state.path.length-1]; state.sockets = [ {x:120,y:H-135,tower:null},{x:210,y:H*0.77,tower:null},{x:245,y:H*0.52,tower:null}, {x:375,y:H*0.60,tower:null},{x:leftW-115,y:H*0.43,tower:null},{x:leftW-105,y:H*0.22,tower:null}, {x:leftW-205,y:H*0.28,tower:null},{x:leftW-220,y:H*0.58,tower:null} ]; if (state.towers.length) { state.towers.forEach((t,i)=>{ const s = state.sockets[i]; if(s){t.x=s.x; t.y=s.y;} }); } } function resetBoard(){ state.board = []; for (let r=0;r<boardCfg.rows;r++){ const row=[]; for (let c=0;c<boardCfg.cols;c++) row.push(randInt(0,glyphs.length-1)); state.board.push(row); } cleanseBoard(); } function randInt(a,b){ return Math.floor(a + Math.random()*(b-a+1)); } function dist(ax,ay,bx,by){ const dx=ax-bx, dy=ay-by; return Math.hypot(dx,dy); } function lerp(a,b,t){ return a + (b-a)*t; } function showMessage(text, ms=1400){ state.message = text; state.messageUntil = performance.now() + ms; } function addTowerAt(socket, type){ const cost = costs[type]; if (state.energy < cost || socket.tower) return false; state.energy -= cost; const def = towerDefs[type]; const tower = {type,x:socket.x,y:socket.y,cooldown:0, ...def}; socket.tower = tower; state.towers.push(tower); burst(socket.x, socket.y, def.color, 18); showMessage(type.toUpperCase() + ' NODE ONLINE'); return true; } function spawnEnemy(now){ if (state.spawnBudget <= 0 || now - state.lastSpawn < 650) return; state.lastSpawn = now; state.spawnBudget--; const base = 38 + state.wave*10; state.enemies.push({ x: state.path[0].x, y: state.path[0].y, hp: base, maxHp: base, speed: 36 + state.wave*4 + Math.random()*10, pathIndex:0, slowMul:1, damage: 5 + Math.floor(state.wave/2), phase: Math.random()*6.28 }); } function nextWave(now){ state.wave++; state.spawnBudget = 8 + state.wave * 2; state.nextWaveAt = now + 9000; state.energy += 18; showMessage('WAVE ' + state.wave + ' INCOMING', 1700); } function moveEnemy(e, dt){ const next = state.path[e.pathIndex+1]; if (!next) { state.integrity = Math.max(0, state.integrity - e.damage); burst(e.x,e.y,'#ff607d',22); return false; } const ang = Math.atan2(next.y-e.y, next.x-e.x); const sp = e.speed * (e.slowMul || 1) * dt; e.x += Math.cos(ang)*sp; e.y += Math.sin(ang)*sp; if (dist(e.x,e.y,next.x,next.y) < 8) e.pathIndex++; e.slowMul = lerp(e.slowMul || 1, 1, dt*2.4); return true; } function fireTower(t, now){ if (t.cooldown > now) return; let target = null, best = Infinity; for (const e of state.enemies){ const d = dist(t.x,t.y,e.x,e.y); if (d < t.range && e.pathIndex < best) { best = e.pathIndex; target = e; } } if (!target) return; t.cooldown = now + t.fireRate * 1000; state.projectiles.push({x:t.x,y:t.y,target,damage:t.damage,color:t.color,speed:340,type:t.type,slow:t.slow||0,splash:t.splash||0}); state.pulses.push({x1:t.x,y1:t.y,x2:target.x,y2:target.y,color:t.color,life:.12}); } function damageEnemy(enemy, dmg, color, slow=0, splash=0){ enemy.hp -= dmg; if (slow) enemy.slowMul = Math.min(enemy.slowMul || 1, slow); burst(enemy.x, enemy.y, color, splash ? 16 : 10); if (splash) { for (const other of state.enemies) { if (other !== enemy && dist(enemy.x,enemy.y,other.x,other.y) <= splash) { other.hp -= dmg * .55; burst(other.x, other.y, color, 8); } } } } function burst(x,y,color,count){ for (let i=0;i<count;i++) state.particles.push({x,y,vx:(Math.random()-.5)*160,vy:(Math.random()-.5)*160,life:.7+Math.random()*.4,t:0,color,size:2+Math.random()*2}); } function updateProjectiles(dt){ state.projectiles = state.projectiles.filter(p => { if (!p.target || p.target.hp <= 0) return false; const a = Math.atan2(p.target.y-p.y, p.target.x-p.x); p.x += Math.cos(a)*p.speed*dt; p.y += Math.sin(a)*p.speed*dt; if (dist(p.x,p.y,p.target.x,p.target.y) < 12){ damageEnemy(p.target, p.damage, p.color, p.slow, p.splash); return false; } return true; }); } function cleanupEnemies(){ const alive=[]; for (const e of state.enemies){ if (e.hp > 0) alive.push(e); else { state.score += 10; state.energy += 4; burst(e.x,e.y,'#44d18a',14); } } state.enemies = alive; } function updateParticles(dt){ state.particles = state.particles.filter(p => { p.t += dt; p.x += p.vx*dt; p.y += p.vy*dt; p.vx *= .985; p.vy *= .985; return p.t < p.life; }); state.pulses = state.pulses.filter(p => (p.life -= dt) > 0); } function findMatches(){ const found=[]; for (let r=0;r<boardCfg.rows;r++){ let run=1; for (let c=1;c<=boardCfg.cols;c++){ if (c<boardCfg.cols && state.board[r][c] === state.board[r][c-1]) run++; else { if (run>=3) for(let k=0;k<run;k++) found.push([r,c-1-k]); run=1; } } } for (let c=0;c<boardCfg.cols;c++){ let run=1; for (let r=1;r<=boardCfg.rows;r++){ if (r<boardCfg.rows && state.board[r][c] === state.board[r-1][c]) run++; else { if (run>=3) for(let k=0;k<run;k++) found.push([r-1-k,c]); run=1; } } } const uniq = new Map(); found.forEach(([r,c])=>uniq.set(r+','+c,[r,c])); return [...uniq.values()]; } function collapseBoard(matches){ if (!matches.length) return 0; matches.forEach(([r,c])=>{ state.board[r][c] = -1; }); let cleared = matches.length; for (let c=0;c<boardCfg.cols;c++){ const vals=[]; for (let r=boardCfg.rows-1;r>=0;r--) if (state.board[r][c] !== -1) vals.push(state.board[r][c]); for (let r=boardCfg.rows-1;r>=0;r--) state.board[r][c] = vals[boardCfg.rows-1-r] ?? randInt(0,glyphs.length-1); } return cleared; } function cleanseBoard(){ let safe=0; while (safe++ < 16) { const m = findMatches(); if (!m.length) return; collapseBoard(m); } } function resolveBoardChain(){ let total = 0, chain = 0; while (true) { const matches = findMatches(); if (!matches.length) break; chain++; const cleared = collapseBoard(matches); total += cleared; state.energy += cleared * (2 + chain); state.score += cleared * 18 * chain; for (const e of state.enemies) e.hp -= cleared * (1.2 + chain*.3); burst(boardCfg.x + boardCfg.cols*boardCfg.size*.5, boardCfg.y + boardCfg.rows*boardCfg.size*.5, '#ffcc4d', 20 + chain*8); } if (chain) { state.chain = chain; showMessage('PUZZLE CHAIN x' + chain, 1300); } } function boardCellAt(mx,my){ for (let r=0;r<boardCfg.rows;r++){ for (let c=0;c<boardCfg.cols;c++){ const x = boardCfg.x + c*(boardCfg.size+boardCfg.gap); const y = boardCfg.y + r*(boardCfg.size+boardCfg.gap); if (mx>=x && mx<=x+boardCfg.size && my>=y && my<=y+boardCfg.size) return {r,c,x,y}; } } return null; } function swapCells(a,b){ const t = state.board[a.r][a.c]; state.board[a.r][a.c] = state.board[b.r][b.c]; state.board[b.r][b.c] = t; } function handleBoardClick(cell){ if (!state.boardSel){ state.boardSel = cell; return true; } const a = state.boardSel; const adjacent = Math.abs(a.r-cell.r) + Math.abs(a.c-cell.c) === 1; if (!adjacent) { state.boardSel = cell; return true; } swapCells(a, cell); const matches = findMatches(); if (matches.length) resolveBoardChain(); else swapCells(a, cell); state.boardSel = null; return true; } function handleSocketClick(mx,my){ for (const socket of state.sockets) { if (dist(mx,my,socket.x,socket.y) <= 18) { if (!socket.tower) addTowerAt(socket, state.selectedBuild); else showMessage('SOCKET ALREADY IN USE', 900); return true; } } return false; } game.addEventListener('click', (e) => { if (state.gameOver) { restart(); return; } const rect = game.getBoundingClientRect(); const mx = e.clientX - rect.left; const my = e.clientY - rect.top; const cell = boardCellAt(mx,my); if (cell) return handleBoardClick(cell); handleSocketClick(mx,my); }); document.querySelectorAll('[data-build]').forEach(btn => { btn.addEventListener('click', () => { state.selectedBuild = btn.dataset.build; document.querySelectorAll('[data-build]').forEach(b => b.classList.remove('primary')); btn.classList.add('primary'); }); }); menuToggle.addEventListener('click', ()=>{ hud.classList.remove('hidden'); menuToggle.classList.remove('show'); }); hideBtn.addEventListener('click', ()=>{ hud.classList.add('hidden'); menuToggle.classList.add('show'); }); function update(now, dt){ if (state.gameOver) return; spawnEnemy(now); if (state.spawnBudget <= 0 && !state.enemies.length && now > state.nextWaveAt) nextWave(now); for (const e of state.enemies) if (!moveEnemy(e, dt)) e.hp = -999; for (const t of state.towers) fireTower(t, now); if (state.emp > 0){ state.emp -= dt; for (const e of state.enemies) e.hp -= 26*dt; } updateProjectiles(dt); cleanupEnemies(); updateParticles(dt); if (state.integrity <= 0) { state.gameOver = true; showMessage('MIND FIREWALL BREACHED', 100000); } } function drawFX(now){ fctx.clearRect(0,0,W,H); const t = now*0.001; for (let i=0;i<70;i++) { const x = (i*97 + now*0.03) % (W+200) - 100; const y = 60 + (i*43 % (H-120)); fctx.strokeStyle = `rgba(115,229,255,${0.03 + (i%7)*0.004})`; fctx.beginPath(); fctx.moveTo(x, y); fctx.lineTo(x+80*Math.sin(t+i), y+18*Math.cos(t*1.6+i)); fctx.stroke(); } } function drawPath(){ ctx.lineWidth = 16; ctx.strokeStyle = 'rgba(115,229,255,.12)'; ctx.beginPath(); ctx.moveTo(state.path[0].x, state.path[0].y); for (let i=1;i<state.path.length;i++) ctx.lineTo(state.path[i].x, state.path[i].y); ctx.stroke(); ctx.lineWidth = 2; ctx.strokeStyle = 'rgba(115,229,255,.55)'; ctx.setLineDash([8,8]); ctx.beginPath(); ctx.moveTo(state.path[0].x, state.path[0].y); for (let i=1;i<state.path.length;i++) ctx.lineTo(state.path[i].x, state.path[i].y); ctx.stroke(); ctx.setLineDash([]); } function drawCore(now){ const c = state.path[state.path.length-1]; const pulse = 1 + Math.sin(now*0.006)*0.08; ctx.save(); ctx.translate(c.x, c.y); ctx.scale(pulse,pulse); const g = ctx.createRadialGradient(0,0,8,0,0,42); g.addColorStop(0,'rgba(68,209,138,.95)'); g.addColorStop(1,'rgba(68,209,138,0)'); ctx.fillStyle = g; ctx.beginPath(); ctx.arc(0,0,42,0,Math.PI*2); ctx.fill(); ctx.strokeStyle = 'rgba(68,209,138,.9)'; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(0,0,18,0,Math.PI*2); ctx.stroke(); ctx.rotate(now*0.002); ctx.strokeStyle = 'rgba(115,229,255,.55)'; ctx.beginPath(); ctx.rect(-26,-26,52,52); ctx.stroke(); ctx.restore(); } function drawSockets(){ for (const s of state.sockets){ ctx.beginPath(); ctx.arc(s.x,s.y,14,0,Math.PI*2); ctx.fillStyle = s.tower ? 'rgba(255,255,255,.08)' : 'rgba(115,229,255,.15)'; ctx.fill(); ctx.strokeStyle = s.tower ? 'rgba(255,255,255,.3)' : 'rgba(115,229,255,.75)'; ctx.lineWidth = 2; ctx.stroke(); } } function drawTowers(){ for (const t of state.towers){ ctx.fillStyle = t.color; ctx.beginPath(); ctx.arc(t.x,t.y,10,0,Math.PI*2); ctx.fill(); ctx.strokeStyle = 'rgba(255,255,255,.7)'; ctx.stroke(); ctx.beginPath(); ctx.moveTo(t.x,t.y); ctx.lineTo(t.x+14,t.y-14); ctx.stroke(); } } function drawEnemies(now){ for (const e of state.enemies){ const wob = Math.sin(now*0.008 + e.phase)*2; ctx.fillStyle = '#ff607d'; ctx.beginPath(); ctx.arc(e.x, e.y+wob, 10, 0, Math.PI*2); ctx.fill(); ctx.strokeStyle = 'rgba(255,255,255,.7)'; ctx.lineWidth = 1.5; ctx.stroke(); ctx.fillStyle = 'rgba(0,0,0,.4)'; ctx.fillRect(e.x-14, e.y-19+wob, 28, 4); ctx.fillStyle = '#44d18a'; ctx.fillRect(e.x-14, e.y-19+wob, 28*(e.hp/e.maxHp), 4); } } function drawProjectiles(){ for (const p of state.projectiles){ ctx.fillStyle = p.color; ctx.beginPath(); ctx.arc(p.x,p.y,4,0,Math.PI*2); ctx.fill(); } for (const p of state.pulses){ ctx.strokeStyle = p.color.replace(')', `, ${Math.max(0,p.life*6)})`).replace('rgb','rgba'); if (p.color[0] === '#') ctx.strokeStyle = hexToRgba(p.color, Math.max(0,p.life*6)); ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(p.x1,p.y1); ctx.lineTo(p.x2,p.y2); ctx.stroke(); } } function hexToRgba(hex,a){ const v = hex.replace('#',''); const n = parseInt(v,16); const r = (n>>16)&255, g = (n>>8)&255, b = n&255; return `rgba(${r},${g},${b},${a})`; } function drawParticles(){ for (const p of state.particles){ const alpha = 1 - p.t/p.life; ctx.fillStyle = p.color[0] === '#' ? hexToRgba(p.color, alpha) : p.color; ctx.beginPath(); ctx.arc(p.x,p.y,p.size,0,Math.PI*2); ctx.fill(); } } function drawBoard(){ const bw = boardCfg.cols * boardCfg.size + (boardCfg.cols-1)*boardCfg.gap; const bh = boardCfg.rows * boardCfg.size + (boardCfg.rows-1)*boardCfg.gap; ctx.fillStyle = 'rgba(7,11,22,.55)'; roundRect(ctx, boardCfg.x-18, boardCfg.y-54, bw+36, bh+72, 20, true, false); ctx.strokeStyle = 'rgba(255,255,255,.12)'; roundRect(ctx, boardCfg.x-18, boardCfg.y-54, bw+36, bh+72, 20, false, true); ctx.fillStyle = 'rgba(234,242,255,.9)'; ctx.font = '700 13px Inter, sans-serif'; ctx.fillText('GLYPH DEFENSE MATRIX', boardCfg.x, boardCfg.y-26); for (let r=0;r<boardCfg.rows;r++){ for (let c=0;c<boardCfg.cols;c++){ const x = boardCfg.x + c*(boardCfg.size+boardCfg.gap); const y = boardCfg.y + r*(boardCfg.size+boardCfg.gap); const val = state.board[r][c]; roundRect(ctx,x,y,boardCfg.size,boardCfg.size,12,true,false,'rgba(255,255,255,.06)'); if (state.boardSel && state.boardSel.r===r && state.boardSel.c===c) { ctx.strokeStyle = 'rgba(255,204,77,.95)'; ctx.lineWidth = 3; roundRect(ctx,x,y,boardCfg.size,boardCfg.size,12,false,true); } else { ctx.strokeStyle = 'rgba(255,255,255,.10)'; ctx.lineWidth = 1; roundRect(ctx,x,y,boardCfg.size,boardCfg.size,12,false,true); } ctx.fillStyle = glyphColors[val]; ctx.font = `700 ${Math.floor(boardCfg.size*.50)}px Inter, sans-serif`; ctx.textAlign='center'; ctx.textBaseline='middle'; ctx.fillText(glyphs[val], x+boardCfg.size/2, y+boardCfg.size/2+1); } } ctx.textAlign='left'; ctx.textBaseline='alphabetic'; } function roundRect(ctx,x,y,w,h,r,fill,stroke,fillStyle){ ctx.beginPath(); ctx.moveTo(x+r,y); ctx.arcTo(x+w,y,x+w,y+h,r); ctx.arcTo(x+w,y+h,x,y+h,r); ctx.arcTo(x,y+h,x,y,r); ctx.arcTo(x,y,x+w,y,r); ctx.closePath(); if (fill) { if (fillStyle) ctx.fillStyle = fillStyle; ctx.fill(); } if (stroke) ctx.stroke(); } function drawMessage(now){ if (now > state.messageUntil) return; ctx.save(); ctx.globalAlpha = Math.min(1, (state.messageUntil-now)/1000 + .2); ctx.fillStyle = 'rgba(0,0,0,.42)'; roundRect(ctx, W/2-180, 28, 360, 46, 16, true, false); ctx.fillStyle = '#eaf2ff'; ctx.font = '700 16px Inter, sans-serif'; ctx.textAlign = 'center'; ctx.fillText(state.message, W/2, 57); ctx.restore(); ctx.textAlign = 'left'; } function drawGameOver(){ ctx.fillStyle = 'rgba(0,0,0,.58)'; ctx.fillRect(0,0,W,H); ctx.fillStyle = '#fff'; ctx.textAlign='center'; ctx.font='700 40px Inter, sans-serif'; ctx.fillText('MIND FIREWALL BREACHED', W/2, H/2-20); ctx.font='500 18px Inter, sans-serif'; ctx.fillText('Click anywhere to reboot the defense grid.', W/2, H/2+18); ctx.textAlign='left'; } function render(now){ ctx.clearRect(0,0,W,H); drawPath(); drawSockets(); drawTowers(); drawCore(now); drawEnemies(now); drawProjectiles(); drawParticles(); drawBoard(); drawMessage(now); if (state.gameOver) drawGameOver(); } function updateUI(){ ui.integrityTxt.textContent = Math.max(0, Math.round(state.integrity)) + '%'; ui.integrityBar.style.width = Math.max(0, state.integrity) + '%'; ui.energyTxt.textContent = Math.round(state.energy); ui.waveTxt.textContent = String(state.wave); ui.scoreTxt.textContent = String(state.score); ui.chainTxt.textContent = String(state.chain); } function restart(){ state.integrity = 100; state.energy = 140; state.wave = 1; state.score = 0; state.chain = 0; state.gameOver = false; state.towers = []; state.enemies = []; state.projectiles = []; state.particles = []; state.pulses = []; state.spawnBudget = 8; state.lastSpawn = 0; state.nextWaveAt = performance.now() + 9000; state.emp = 0; recalcLayout(); state.sockets.forEach(s=>s.tower=null); resetBoard(); showMessage('DEFEND THE CORE', 1600); } let last = performance.now(); function frame(now){ const dt = Math.min(.033, (now-last)/1000); last = now; drawFX(now); update(now, dt); render(now); updateUI(); requestAnimationFrame(frame); } restart(); resize(); requestAnimationFrame(frame); })(); </script> </body> </html>
Save file
Quick jump
open a path
Open