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/BLOCKPUZZLE.HTML
<!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" /> <title>Sliding Block Puzzle</title> <style> :root{ --bg0:#070a12; --bg1:#0b1030; --card:rgba(255,255,255,.07); --edge:rgba(255,255,255,.14); --txt:rgba(255,255,255,.92); --mut:rgba(255,255,255,.68); --good:rgba(120,255,190,.95); --warn:rgba(255,220,120,.95); --bad: rgba(255,120,140,.95); } *{box-sizing:border-box} html,body{height:100%} body{ margin:0; font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial; color:var(--txt); background: radial-gradient(1200px 800px at 20% 10%, rgba(120,160,255,.18), transparent 55%), radial-gradient(1000px 700px at 80% 20%, rgba(255,120,220,.12), transparent 55%), radial-gradient(900px 600px at 50% 80%, rgba(120,255,210,.08), transparent 60%), linear-gradient(180deg, var(--bg1), var(--bg0)); overflow-x:hidden; } .wrap{ max-width:1100px; margin:0 auto; padding:18px 14px 24px; display:grid; grid-template-columns: 1fr; gap:14px; } header{ display:flex; align-items:flex-end; justify-content:space-between; gap:12px; padding:14px 14px 10px; border:1px solid var(--edge); background:linear-gradient(180deg, rgba(255,255,255,.08), rgba(255,255,255,.04)); border-radius:16px; backdrop-filter: blur(10px); } h1{ margin:0; font-size:18px; letter-spacing:.25px; } .sub{ margin-top:4px; font-size:12px; color:var(--mut); line-height:1.35; } .row{ display:flex; flex-wrap:wrap; gap:10px; justify-content:flex-end; align-items:center; } .pill{ display:flex; align-items:center; gap:8px; padding:8px 10px; border-radius:999px; border:1px solid var(--edge); background:rgba(255,255,255,.05); color:var(--mut); font-size:12px; user-select:none; } .pill strong{color:var(--txt); font-weight:600} .btn{ appearance:none; border:1px solid var(--edge); background:rgba(255,255,255,.06); color:var(--txt); padding:9px 12px; border-radius:12px; font-size:13px; cursor:pointer; transition: transform .06s ease, background .15s ease, border-color .15s ease; } .btn:hover{background:rgba(255,255,255,.09)} .btn:active{transform: translateY(1px) scale(.99)} .btn.primary{ background: linear-gradient(135deg, rgba(120,160,255,.26), rgba(255,120,220,.18)); border-color: rgba(255,255,255,.22); } .btn.good{background:rgba(120,255,190,.12); border-color:rgba(120,255,190,.25)} .btn.warn{background:rgba(255,220,120,.10); border-color:rgba(255,220,120,.22)} .btn.bad{background:rgba(255,120,140,.10); border-color:rgba(255,120,140,.22)} .grid{ display:grid; grid-template-columns: 1fr; gap:14px; } @media (min-width: 920px){ .grid{grid-template-columns: 1.3fr .7fr;} } .panel{ border:1px solid var(--edge); background:rgba(255,255,255,.05); border-radius:16px; padding:14px; backdrop-filter: blur(10px); } canvas{ width:100%; height:auto; display:block; border-radius:14px; border:1px solid rgba(255,255,255,.12); background: radial-gradient(900px 600px at 30% 20%, rgba(255,255,255,.06), transparent 55%), radial-gradient(700px 500px at 80% 80%, rgba(120,255,210,.05), transparent 55%), rgba(0,0,0,.18); } .side h2{margin:2px 0 10px; font-size:14px; color:rgba(255,255,255,.88)} .kvs{ display:grid; grid-template-columns: 1fr auto; gap:8px 10px; font-size:12px; color:var(--mut); margin-bottom:12px; } .kvs b{color:var(--txt); font-weight:600} .levels{ display:grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap:8px; } .levels button{ text-align:left; display:flex; flex-direction:column; gap:2px; } .levels small{color:var(--mut); font-size:11px} .help{ margin-top:12px; font-size:12px; color:var(--mut); line-height:1.5; } .toast{ margin-top:10px; padding:10px 12px; border-radius:12px; border:1px solid rgba(255,255,255,.14); background:rgba(0,0,0,.22); font-size:12px; color:var(--mut); min-height:38px; } .toast.ok{border-color:rgba(120,255,190,.25); background:rgba(120,255,190,.07)} .toast.warn{border-color:rgba(255,220,120,.25); background:rgba(255,220,120,.06)} </style> </head> <body> <div class="wrap"> <header> <div> <h1>Sliding Block Puzzle</h1> <div class="sub"> Move pieces on a grid to get the <b>MAIN</b> block onto the <b>GOAL</b> tiles. Click/tap a block, then click a direction or use arrows/WASD. </div> </div> <div class="row"> <div class="pill"><strong>Level</strong> <span id="uiLevel">1</span></div> <div class="pill"><strong>Moves</strong> <span id="uiMoves">0</span></div> <div class="pill"><strong>Undo</strong> <span id="uiUndo">0</span></div> <button class="btn warn" id="btnUndo">Undo</button> <button class="btn" id="btnReset">Reset</button> <button class="btn primary" id="btnNext">Next</button> <button class="btn" id="btnSound">Sound: On</button> </div> </header> <div class="grid"> <div class="panel"> <canvas id="cv" width="900" height="780"></canvas> </div> <div class="panel side"> <h2>Levels</h2> <div class="kvs"> <div>Goal type</div><b id="uiGoalType">Main-on-goal</b> <div>Selected</div><b id="uiSel">None</b> <div>Controls</div><b>Click + arrows / WASD</b> </div> <div class="levels" id="levels"></div> <div class="help"> <b>How to play</b><br/> • Click a block to select it (outline appears).<br/> • Use Arrow keys / WASD to slide that block one step.<br/> • Or click/tap near the block edge (directional nudge).<br/> • Goal tiles are outlined on the board.<br/><br/> <b>Edit levels</b>: Scroll this file to <code>const LEVELS</code> and add a new grid + blocks. </div> <div class="toast" id="toast">Ready.</div> </div> </div> </div> <script> (() => { // ------------------------- // Minimal sound engine // ------------------------- let audioOn = true; let AC = null; function ensureAudio(){ if (!audioOn) return null; if (!AC){ const Ctx = window.AudioContext || window.webkitAudioContext; if (!Ctx) return null; AC = new Ctx(); } if (AC.state === "suspended") AC.resume().catch(()=>{}); return AC; } function beep(type="move"){ const ac = ensureAudio(); if (!ac) return; const now = ac.currentTime; const o = ac.createOscillator(); const g = ac.createGain(); o.connect(g); g.connect(ac.destination); let f0=320, f1=220, dur=0.06; if (type==="move"){ f0=340; f1=260; dur=0.06; } if (type==="bump"){ f0=180; f1=120; dur=0.07; } if (type==="select"){ f0=520; f1=620; dur=0.04; } if (type==="win"){ f0=420; f1=840; dur=0.22; } o.type = (type==="bump") ? "square" : "triangle"; o.frequency.setValueAtTime(f0, now); o.frequency.exponentialRampToValueAtTime(Math.max(40,f1), now + dur); g.gain.setValueAtTime(0.0001, now); g.gain.exponentialRampToValueAtTime(0.18, now + 0.01); g.gain.exponentialRampToValueAtTime(0.0001, now + dur); o.start(now); o.stop(now + dur + 0.02); } // ------------------------- // Levels (grid + blocks) // Coordinate system: x,y are top-left cell, w,h in cells // Each level defines: // cols, rows // goals: array of cells [{x,y}] (goal region) // win: { type:"main_on_goals", mainId:"M" } // main block overlaps all goal cells // blocks: [{id,x,y,w,h,kind}] // // kinds: "main", "block" // ------------------------- const LEVELS = [ { name: "Tutorial 1", cols: 4, rows: 5, goals: [{x:1,y:4},{x:2,y:4}], // bottom middle 2 cells win: { type:"main_on_goals", mainId:"M" }, blocks: [ {id:"M", x:1, y:0, w:2, h:2, kind:"main"}, {id:"A", x:0, y:0, w:1, h:2, kind:"block"}, {id:"B", x:3, y:0, w:1, h:2, kind:"block"}, {id:"C", x:0, y:2, w:1, h:2, kind:"block"}, {id:"D", x:3, y:2, w:1, h:2, kind:"block"}, {id:"E", x:1, y:2, w:2, h:1, kind:"block"}, {id:"F", x:1, y:3, w:1, h:1, kind:"block"}, {id:"G", x:2, y:3, w:1, h:1, kind:"block"}, ] }, { name: "Cross Jam", cols: 5, rows: 6, goals: [{x:2,y:5}], // single goal cell (main block must cover it; main is 1x1 here) win: { type:"main_on_goals", mainId:"M" }, blocks: [ {id:"M", x:2, y:0, w:1, h:1, kind:"main"}, {id:"A", x:1, y:1, w:3, h:1, kind:"block"}, {id:"B", x:0, y:2, w:1, h:2, kind:"block"}, {id:"C", x:4, y:2, w:1, h:2, kind:"block"}, {id:"D", x:1, y:3, w:3, h:1, kind:"block"}, {id:"E", x:2, y:4, w:1, h:1, kind:"block"}, ] }, { name: "Wide Hall", cols: 6, rows: 6, goals: [{x:2,y:5},{x:3,y:5}], // 2 cells win: { type:"main_on_goals", mainId:"M" }, blocks: [ {id:"M", x:2, y:0, w:2, h:2, kind:"main"}, {id:"A", x:0, y:0, w:1, h:3, kind:"block"}, {id:"B", x:5, y:0, w:1, h:3, kind:"block"}, {id:"C", x:1, y:1, w:1, h:2, kind:"block"}, {id:"D", x:4, y:1, w:1, h:2, kind:"block"}, {id:"E", x:2, y:2, w:2, h:1, kind:"block"}, {id:"F", x:2, y:3, w:1, h:1, kind:"block"}, {id:"G", x:3, y:3, w:1, h:1, kind:"block"}, {id:"H", x:1, y:4, w:1, h:2, kind:"block"}, {id:"I", x:4, y:4, w:1, h:2, kind:"block"}, ] }, { name: "Narrow Escape", cols: 4, rows: 6, goals: [{x:1,y:5},{x:2,y:5}], win: { type:"main_on_goals", mainId:"M" }, blocks: [ {id:"M", x:1, y:1, w:2, h:2, kind:"main"}, {id:"A", x:0, y:0, w:1, h:2, kind:"block"}, {id:"B", x:3, y:0, w:1, h:2, kind:"block"}, {id:"C", x:0, y:2, w:1, h:2, kind:"block"}, {id:"D", x:3, y:2, w:1, h:2, kind:"block"}, {id:"E", x:1, y:0, w:2, h:1, kind:"block"}, {id:"F", x:1, y:3, w:1, h:2, kind:"block"}, {id:"G", x:2, y:3, w:1, h:2, kind:"block"}, ] }, ]; // ------------------------- // Game state // ------------------------- const cv = document.getElementById("cv"); const ctx = cv.getContext("2d"); const uiLevel = document.getElementById("uiLevel"); const uiMoves = document.getElementById("uiMoves"); const uiUndo = document.getElementById("uiUndo"); const uiSel = document.getElementById("uiSel"); const uiGoalType = document.getElementById("uiGoalType"); const toast = document.getElementById("toast"); const btnUndo = document.getElementById("btnUndo"); const btnReset = document.getElementById("btnReset"); const btnNext = document.getElementById("btnNext"); const btnSound = document.getElementById("btnSound"); const levelsBox = document.getElementById("levels"); let levelIndex = 0; let level = null; let blocks = []; let selectedId = null; let moves = 0; let history = []; // stack of snapshots let won = false; function deepClone(obj){ return JSON.parse(JSON.stringify(obj)); } function loadLevel(idx){ levelIndex = ((idx % LEVELS.length) + LEVELS.length) % LEVELS.length; level = deepClone(LEVELS[levelIndex]); blocks = deepClone(level.blocks); selectedId = null; moves = 0; history = []; won = false; uiLevel.textContent = String(levelIndex + 1); uiMoves.textContent = "0"; uiUndo.textContent = "0"; uiSel.textContent = "None"; uiGoalType.textContent = (level.win?.type || "main_on_goals"); toast.className = "toast"; toast.textContent = `Loaded: ${level.name}`; draw(); } function snapshot(){ return { blocks: deepClone(blocks), selectedId, moves, won }; } function pushHistory(){ history.push(snapshot()); uiUndo.textContent = String(history.length); } function popHistory(){ const s = history.pop(); uiUndo.textContent = String(history.length); if (!s) return; blocks = deepClone(s.blocks); selectedId = s.selectedId; moves = s.moves; won = s.won; uiMoves.textContent = String(moves); uiSel.textContent = selectedId ? selectedId : "None"; toast.className = "toast"; toast.textContent = "Undid last move."; draw(); } function blockById(id){ return blocks.find(b => b.id === id); } function cellOccupied(x,y, ignoreId=null){ for (const b of blocks){ if (ignoreId && b.id === ignoreId) continue; if (x >= b.x && x < b.x + b.w && y >= b.y && y < b.y + b.h) return b.id; } return null; } function canMove(id, dx, dy){ const b = blockById(id); if (!b) return false; const nx = b.x + dx; const ny = b.y + dy; if (nx < 0 || ny < 0 || nx + b.w > level.cols || ny + b.h > level.rows) return false; // check edge cells in direction if (dx === 1){ for (let yy = ny; yy < ny + b.h; yy++){ if (cellOccupied(nx + b.w - 1, yy, id)) return false; } } else if (dx === -1){ for (let yy = ny; yy < ny + b.h; yy++){ if (cellOccupied(nx, yy, id)) return false; } } else if (dy === 1){ for (let xx = nx; xx < nx + b.w; xx++){ if (cellOccupied(xx, ny + b.h - 1, id)) return false; } } else if (dy === -1){ for (let xx = nx; xx < nx + b.w; xx++){ if (cellOccupied(xx, ny, id)) return false; } } return true; } function tryMoveSelected(dx, dy){ if (won) return; if (!selectedId){ toast.className = "toast warn"; toast.textContent = "Select a block first."; beep("bump"); return; } if (!canMove(selectedId, dx, dy)){ toast.className = "toast warn"; toast.textContent = "Blocked."; beep("bump"); draw(); return; } pushHistory(); const b = blockById(selectedId); b.x += dx; b.y += dy; moves++; uiMoves.textContent = String(moves); toast.className = "toast"; toast.textContent = `Moved ${selectedId}.`; beep("move"); checkWin(); draw(); } function rectsOverlap(ax,ay,aw,ah, bx,by,bw,bh){ return ax < bx+bw && ax+aw > bx && ay < by+bh && ay+ah > by; } function checkWin(){ const w = level.win || {type:"main_on_goals", mainId:"M"}; if (w.type === "main_on_goals"){ const m = blockById(w.mainId); if (!m) return; // main must cover all goal cells let ok = true; for (const g of level.goals){ if (!(g.x >= m.x && g.x < m.x+m.w && g.y >= m.y && g.y < m.y+m.h)){ ok = false; break; } } if (ok){ won = true; toast.className = "toast ok"; toast.textContent = `Solved: ${level.name} in ${moves} moves.`; beep("win"); } } } // ------------------------- // Rendering // ------------------------- function fitCanvas(){ // keep internal resolution; CSS scales draw(); } window.addEventListener("resize", fitCanvas); function draw(){ const W = cv.width, H = cv.height; ctx.clearRect(0,0,W,H); // layout within canvas const pad = 26; const boardW = W - pad*2; const boardH = H - pad*2; // compute cell size (square) const cs = Math.floor(Math.min(boardW / level.cols, boardH / level.rows)); const ox = Math.floor((W - cs*level.cols)/2); const oy = Math.floor((H - cs*level.rows)/2); // background glow ctx.save(); ctx.globalAlpha = 0.20; ctx.fillStyle = "white"; ctx.beginPath(); ctx.arc(W*0.25, H*0.25, Math.min(W,H)*0.22, 0, Math.PI*2); ctx.fill(); ctx.globalAlpha = 0.08; ctx.beginPath(); ctx.arc(W*0.75, H*0.70, Math.min(W,H)*0.28, 0, Math.PI*2); ctx.fill(); ctx.restore(); // board roundRect(ctx, ox-10, oy-10, cs*level.cols+20, cs*level.rows+20, 16); ctx.fillStyle = "rgba(0,0,0,.18)"; ctx.fill(); ctx.strokeStyle = "rgba(255,255,255,.14)"; ctx.lineWidth = 2; ctx.stroke(); // grid lines ctx.save(); ctx.strokeStyle = "rgba(255,255,255,.08)"; ctx.lineWidth = 1; for (let x=0;x<=level.cols;x++){ const xx = ox + x*cs; ctx.beginPath(); ctx.moveTo(xx, oy); ctx.lineTo(xx, oy + level.rows*cs); ctx.stroke(); } for (let y=0;y<=level.rows;y++){ const yy = oy + y*cs; ctx.beginPath(); ctx.moveTo(ox, yy); ctx.lineTo(ox + level.cols*cs, yy); ctx.stroke(); } ctx.restore(); // goal cells for (const g of level.goals){ const gx = ox + g.x*cs; const gy = oy + g.y*cs; roundRect(ctx, gx+3, gy+3, cs-6, cs-6, 10); ctx.fillStyle = "rgba(120,255,190,.08)"; ctx.fill(); ctx.strokeStyle = "rgba(120,255,190,.35)"; ctx.lineWidth = 2; ctx.stroke(); } // blocks for (const b of blocks){ const x = ox + b.x*cs; const y = oy + b.y*cs; const w = b.w*cs; const h = b.h*cs; // gradient-ish fill using two layers const isMain = (b.kind === "main"); const isSel = (b.id === selectedId); // base roundRect(ctx, x+4, y+4, w-8, h-8, 14); ctx.fillStyle = isMain ? "rgba(255,220,120,.16)" : "rgba(120,160,255,.14)"; ctx.fill(); // sheen roundRect(ctx, x+8, y+8, w-16, Math.max(10, (h-16)*0.38), 12); ctx.fillStyle = "rgba(255,255,255,.10)"; ctx.fill(); // outline roundRect(ctx, x+4, y+4, w-8, h-8, 14); ctx.strokeStyle = isMain ? "rgba(255,220,120,.55)" : "rgba(255,255,255,.20)"; ctx.lineWidth = 2; ctx.stroke(); // selection ring if (isSel){ roundRect(ctx, x+1.5, y+1.5, w-3, h-3, 16); ctx.strokeStyle = won ? "rgba(120,255,190,.85)" : "rgba(255,255,255,.70)"; ctx.lineWidth = 3; ctx.stroke(); } // label ctx.fillStyle = "rgba(255,255,255,.92)"; ctx.font = `600 ${Math.max(16, Math.floor(cs*0.28))}px ui-sans-serif, system-ui`; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(isMain ? "MAIN" : b.id, x + w/2, y + h/2); } // win overlay if (won){ ctx.save(); ctx.fillStyle = "rgba(0,0,0,.35)"; ctx.fillRect(0,0,W,H); ctx.fillStyle = "rgba(255,255,255,.92)"; ctx.font = "700 46px ui-sans-serif, system-ui"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText("SOLVED", W/2, H/2 - 22); ctx.fillStyle = "rgba(255,255,255,.78)"; ctx.font = "500 18px ui-sans-serif, system-ui"; ctx.fillText("Press Next or pick another level.", W/2, H/2 + 26); ctx.restore(); } // store render metrics for hit-testing draw._metrics = { cs, ox, oy }; } function roundRect(c, x,y,w,h,r){ const rr = Math.min(r, w/2, h/2); c.beginPath(); c.moveTo(x+rr, y); c.arcTo(x+w, y, x+w, y+h, rr); c.arcTo(x+w, y+h, x, y+h, rr); c.arcTo(x, y+h, x, y, rr); c.arcTo(x, y, x+w, y, rr); c.closePath(); } function canvasToCell(px, py){ const m = draw._metrics; if (!m) return null; const {cs, ox, oy} = m; const x = Math.floor((px - ox) / cs); const y = Math.floor((py - oy) / cs); if (x<0 || y<0 || x>=level.cols || y>=level.rows) return null; return {x,y, cs, ox, oy}; } function blockAtCell(cx, cy){ for (let i=blocks.length-1;i>=0;i--){ const b = blocks[i]; if (cx>=b.x && cx<b.x+b.w && cy>=b.y && cy<b.y+b.h) return b; } return null; } // ------------------------- // Input: click/tap // ------------------------- function pointerPos(ev){ const r = cv.getBoundingClientRect(); const x = (ev.clientX - r.left) * (cv.width / r.width); const y = (ev.clientY - r.top) * (cv.height / r.height); return {x,y}; } cv.addEventListener("pointerdown", (ev) => { cv.setPointerCapture(ev.pointerId); const p = pointerPos(ev); const cell = canvasToCell(p.x, p.y); if (!cell) return; const b = blockAtCell(cell.x, cell.y); if (b){ if (selectedId !== b.id){ selectedId = b.id; uiSel.textContent = selectedId; toast.className = "toast"; toast.textContent = `Selected ${b.kind==="main" ? "MAIN" : b.id}.`; beep("select"); draw(); return; } // already selected: directional nudge based on where within block you clicked const m = draw._metrics; const bx = m.ox + b.x*m.cs; const by = m.oy + b.y*m.cs; const bw = b.w*m.cs; const bh = b.h*m.cs; const relx = (p.x - bx) / bw; // 0..1 const rely = (p.y - by) / bh; // choose direction toward nearest edge const left = relx; const right = 1-relx; const top = rely; const bottom = 1-rely; const min = Math.min(left,right,top,bottom); if (min === left) tryMoveSelected(-1,0); else if (min === right) tryMoveSelected(1,0); else if (min === top) tryMoveSelected(0,-1); else tryMoveSelected(0,1); } else { // clicked empty: deselect selectedId = null; uiSel.textContent = "None"; toast.className = "toast"; toast.textContent = "Deselected."; draw(); } }); // ------------------------- // Keyboard // ------------------------- window.addEventListener("keydown", (e) => { const k = e.key.toLowerCase(); if (k === "arrowup" || k === "w"){ e.preventDefault(); tryMoveSelected(0,-1); } if (k === "arrowdown" || k === "s"){ e.preventDefault(); tryMoveSelected(0, 1); } if (k === "arrowleft" || k === "a"){ e.preventDefault(); tryMoveSelected(-1,0); } if (k === "arrowright" || k === "d"){ e.preventDefault(); tryMoveSelected( 1,0); } if (k === "z"){ e.preventDefault(); popHistory(); } if (k === "r"){ e.preventDefault(); loadLevel(levelIndex); } if (k === "n"){ e.preventDefault(); loadLevel(levelIndex+1); } }, {passive:false}); // ------------------------- // UI buttons // ------------------------- btnUndo.addEventListener("click", () => popHistory()); btnReset.addEventListener("click", () => loadLevel(levelIndex)); btnNext.addEventListener("click", () => loadLevel(levelIndex+1)); btnSound.addEventListener("click", () => { audioOn = !audioOn; btnSound.textContent = `Sound: ${audioOn ? "On" : "Off"}`; toast.className = "toast"; toast.textContent = audioOn ? "Sound enabled." : "Sound disabled."; if (audioOn) beep("select"); }); // Level picker buttons function renderLevelButtons(){ levelsBox.innerHTML = ""; LEVELS.forEach((L, idx) => { const b = document.createElement("button"); b.className = "btn"; b.innerHTML = `<div><b>${idx+1}. ${escapeHtml(L.name)}</b></div><small>${L.cols}×${L.rows} • blocks: ${L.blocks.length}</small>`; b.addEventListener("click", () => loadLevel(idx)); levelsBox.appendChild(b); }); } function escapeHtml(s){ return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } // ------------------------- // Start // ------------------------- renderLevelButtons(); loadLevel(0); })(); </script> </body> </html>
Save file
Quick jump
open a path
Open