Muse Ember
by MetaReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 15.1 s
- Cost
- $0.0045
- Output tokens
- 8,968
- Spent thinking
- 1,311 tokens
- Speed
- 688.5 tok/s
- Console errors
- 0
- First token after
- 2.0 s
- First visible text after
- 4.7 s
- Prompt tokens
- 510
- Code size
- 23.3 KB
- Finish reason
- stop
- Served by
- Meta
- Model id
meta/muse-ember- Reasoning sent
{"effort":"low"}- Max output
- 943,718 tokens
- Generated
- Nov 12, 2026
What it was asked
The prompt
Build a polished Snake game in a single HTML file using vanilla JavaScript and a <canvas>. Requirements: - A grid of at least 20×20 cells, centered on the page. - Arrow keys and WASD control the snake. Quick successive key presses must both be honored, but the snake can never reverse onto itself. - Eating food adds 10 points and grows the snake by one cell; new food never spawns on the snake. - Game over when the snake hits a wall or itself, with an overlay showing the score; Space or Enter restarts, P pauses. - Visual polish: smooth movement, a snake with a distinct head and eyes facing its direction, and a small particle burst when eating. - Display the current score and the best score of the session.
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task. Output rules: - Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block. - Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task. - Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw. - The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar. - Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction. - Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session. - Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing. - The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message. - The code must run with zero errors in the console of the latest Chrome.
Code run-1.html · 535 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Neon Snake</title>
<style>
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{height:100%}
body{
font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
background:#070b12;
background-image:radial-gradient(900px 500px at 20% -10%,rgba(34,197,94,.18),transparent 60%),radial-gradient(800px 500px at 90% 110%,rgba(14,165,233,.16),transparent 60%),radial-gradient(600px 400px at 80% 0%,rgba(244,114,182,.08),transparent 60%);
color:#e6f0e9;min-height:100vh;display:flex;flex-direction:column;align-items:center;
padding:16px 12px 20px;overflow-x:hidden;
}
header{width:min(620px,96vw);display:flex;align-items:flex-end;justify-content:space-between;gap:12px;margin-bottom:12px}
.logo{display:flex;align-items:center;gap:10px}
.logo-icon{width:40px;height:40px;border-radius:11px;background:linear-gradient(135deg,#22c55e,#0ea5e9);display:grid;place-items:center;font-size:22px;box-shadow:0 8px 24px rgba(34,197,94,.35)}
h1{font-size:26px;letter-spacing:.14em;font-weight:900;line-height:1}
h1 span{color:#4ade80}
.sub{font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:#8aa096;margin-top:4px}
.scores{display:flex;gap:8px}
.card{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.09);backdrop-filter:blur(8px);border-radius:14px;padding:8px 14px;text-align:center;min-width:82px;box-shadow:inset 0 1px 0 rgba(255,255,255,.06)}
.card label{display:block;font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:#93a89e;font-weight:700}
.card b{font-size:24px;line-height:1.1;font-variant-numeric:tabular-nums}
.card.best b{color:#facc15}
.board-wrap{position:relative;width:min(620px,96vw)}
.frame{position:relative;border-radius:22px;padding:10px;background:linear-gradient(180deg,rgba(255,255,255,.10),rgba(255,255,255,.03));border:1px solid rgba(255,255,255,.12);box-shadow:0 24px 60px rgba(0,0,0,.55),0 0 0 1px rgba(0,0,0,.2),0 0 42px rgba(34,197,94,.10)}
canvas{display:block;width:100%;aspect-ratio:1/1;border-radius:14px;background:#0c1a14;touch-action:none;outline:none}
.hud{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:11px;flex-wrap:wrap}
.hint{font-size:12.5px;color:#9db3a8}
.hint kbd{background:rgba(255,255,255,.09);border:1px solid rgba(255,255,255,.14);border-bottom-width:2px;border-radius:6px;padding:1px 6px;font-family:inherit;font-size:11px;color:#e6f0e9;font-weight:700}
.actions{display:flex;gap:8px}
button{font:inherit;cursor:pointer;border:1px solid rgba(255,255,255,.14);background:rgba(255,255,255,.08);color:#e6f0e9;border-radius:999px;padding:8px 14px;font-size:13px;font-weight:800;letter-spacing:.02em;transition:.15s}
button:hover{background:rgba(255,255,255,.15);transform:translateY(-1px)}
button:active{transform:translateY(0)}
button.primary{background:#22c55e;border-color:#22c55e;color:#052e16}
button.primary:hover{background:#4ade80}
/* Overlay */
.overlay{position:absolute;inset:10px;border-radius:14px;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:20px;background:rgba(5,10,8,.62);backdrop-filter:blur(6px);z-index:5;transition:opacity .25s}
.overlay.hidden{opacity:0;pointer-events:none}
.overlay h2{font-size:32px;letter-spacing:.08em;font-weight:900;text-shadow:0 4px 20px rgba(0,0,0,.5)}
.overlay p{color:#b8d0c2;margin-top:8px;font-size:14px;line-height:1.5;max-width:360px}
.overlay .big-score{font-size:52px;font-weight:900;color:#4ade80;line-height:1.05;margin-top:6px}
.overlay .newbest{display:inline-block;margin-top:8px;background:#facc15;color:#422006;font-weight:900;font-size:12px;letter-spacing:.1em;padding:4px 10px;border-radius:999px}
.overlay .cta{margin-top:16px;display:flex;gap:8px;flex-wrap:wrap;justify-content:center}
.demo-badge{position:absolute;top:18px;left:50%;transform:translateX(-50%);z-index:6;background:rgba(34,197,94,.92);color:#052e16;font-size:11px;font-weight:900;letter-spacing:.14em;text-transform:uppercase;padding:6px 12px;border-radius:999px;box-shadow:0 6px 18px rgba(0,0,0,.35);white-space:nowrap;animation:pulse 1.6s ease-in-out infinite}
@keyframes pulse{0%,100%{transform:translateX(-50%) scale(1)}50%{transform:translateX(-50%) scale(1.04)}}
.demo-badge.hidden{display:none}
/* Controls */
.controls{width:min(620px,96vw);margin-top:14px;display:flex;justify-content:space-between;align-items:center;gap:14px}
.legend{display:flex;gap:14px;font-size:12px;color:#8aa096;flex-wrap:wrap}
.legend i{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:5px;vertical-align:-1px}
.dpad{display:grid;grid-template-columns:44px 44px 44px;grid-template-rows:44px 44px 44px;gap:4px;flex:0 0 auto}
.dpad button{padding:0;border-radius:12px;font-size:17px;display:grid;place-items:center}
.dpad .up{grid-column:2;grid-row:1}.dpad .left{grid-column:1;grid-row:2}.dpad .down{grid-column:2;grid-row:2}.dpad .right{grid-column:3;grid-row:2}
@media(max-width:560px){
h1{font-size:20px}.card{min-width:68px;padding:6px 10px}.card b{font-size:20px}
.controls{flex-direction:column}.legend{justify-content:center}
.overlay h2{font-size:26px}.overlay .big-score{font-size:42px}
header{align-items:flex-start}
}
</style>
</head>
<body>
<header>
<div class="logo">
<div class="logo-icon">🐍</div>
<div>
<h1>NEON <span>SNAKE</span></h1>
<div class="sub">Smooth • Fast • Classic</div>
</div>
</div>
<div class="scores">
<div class="card"><label>Score</label><b id="score">0</b></div>
<div class="card best"><label>★ Best</label><b id="best">0</b></div>
</div>
</header>
<div class="board-wrap">
<div class="frame">
<canvas id="game" width="660" height="660" aria-label="Snake game board" tabindex="0"></canvas>
<div class="demo-badge" id="demoBadge">▶ Demo playing — Click / Key to start</div>
<div class="overlay hidden" id="overlay">
<h2 id="ovTitle">GAME OVER</h2>
<div class="big-score" id="ovScore">0</div>
<div id="ovBestWrap"><span class="newbest" id="ovBest" style="display:none">★ NEW BEST!</span> <p id="ovBestText" style="margin-top:6px"></p></div>
<p id="ovText">You hit a wall.</p>
<div class="cta">
<button class="primary" id="btnRestart">↻ Play Again</button>
<button id="btnPauseOv">Pause</button>
</div>
<p style="margin-top:12px;font-size:12px;color:#8aa096"><kbd style="background:rgba(255,255,255,.1);padding:2px 7px;border-radius:6px;border:1px solid rgba(255,255,255,.2)">Space</kbd> / <kbd style="background:rgba(255,255,255,.1);padding:2px 7px;border-radius:6px;border:1px solid rgba(255,255,255,.2)">Enter</kbd> to restart • <kbd style="background:rgba(255,255,255,.1);padding:2px 7px;border-radius:6px;border:1px solid rgba(255,255,255,.2)">P</kbd> pause</p>
</div>
</div>
</div>
<div class="controls">
<div class="legend">
<span><i style="background:#4ade80"></i>Snake</span>
<span><i style="background:#fb7185"></i>Food +10</span>
<span><i style="background:#facc15"></i>Best this session</span>
</div>
<div>
<div class="hint" style="text-align:center;margin-bottom:8px"><kbd>←↑↓→</kbd> / <kbd>WASD</kbd> move <kbd>P</kbd> pause <kbd>Space</kbd> restart</div>
<div class="dpad" aria-label="On-screen controls">
<button class="up" data-dir="up" aria-label="Up">▲</button>
<button class="left" data-dir="left" aria-label="Left">◀</button>
<button class="down" data-dir="down" aria-label="Down">▼</button>
<button class="right" data-dir="right" aria-label="Right">▶</button>
</div>
</div>
</div>
<script>
(() => {
const COLS = 22, ROWS = 22;
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const bestEl = document.getElementById('best');
const overlay = document.getElementById('overlay');
const ovTitle = document.getElementById('ovTitle');
const ovScore = document.getElementById('ovScore');
const ovText = document.getElementById('ovText');
const ovBest = document.getElementById('ovBest');
const ovBestText = document.getElementById('ovBestText');
const demoBadge = document.getElementById('demoBadge');
const cell = canvas.width / COLS; // 30
let snake, prevSnake, dir, queue, food, particles, score, best = 0;
let state = 'demo'; // demo | playing | paused | over
let lastStep = 0, interval = 135, deathReason = '';
let wasNewBest = false;
function reset(toDemo) {
const cx = Math.floor(COLS/2), cy = Math.floor(ROWS/2);
snake = [{x:cx,y:cy},{x:cx-1,y:cy},{x:cx-2,y:cy},{x:cx-3,y:cy}];
prevSnake = snake.map(s=>({...s}));
dir = {x:1,y:0};
queue = [];
particles = [];
score = 0;
interval = 135;
wasNewBest = false;
updateScoreUI();
spawnFood();
lastStep = performance.now();
hideOverlay();
state = toDemo ? 'demo' : 'playing';
demoBadge.classList.toggle('hidden', !toDemo);
canvas.focus({preventScroll:true});
}
function spawnFood() {
const free = [];
const occupied = new Set(snake.map(s=>s.x+','+s.y));
for (let y=0;y<ROWS;y++) for (let x=0;x<COLS;x++) if (!occupied.has(x+','+y)) free.push({x,y});
food = free.length ? free[Math.floor(Math.random()*free.length)] : {x:0,y:0};
}
function updateScoreUI(){ scoreEl.textContent = score; bestEl.textContent = best; }
function showOverlay(title, text, showPauseBtn){
ovTitle.textContent = title;
ovScore.textContent = score;
ovText.textContent = text;
ovBest.style.display = wasNewBest ? 'inline-block' : 'none';
ovBestText.textContent = 'Best: ' + best + (best>0 ? ' • Length: ' + snake.length : '');
document.getElementById('btnPauseOv').style.display = showPauseBtn ? '' : 'none';
overlay.classList.remove('hidden');
}
function hideOverlay(){ overlay.classList.add('hidden'); }
function queueDir(nx, ny){
const last = queue.length ? queue[queue.length-1] : dir;
if (last.x === -nx && last.y === -ny) return; // reverse
if (last.x === nx && last.y === ny) return; // same
if (queue.length < 3) queue.push({x:nx,y:ny});
}
function isLethal(x,y, body){
if (x<0||x>=COLS||y<0||y>=ROWS) return true;
return body.some(s=>s.x===x&&s.y===y);
}
// ---- Demo AI: BFS to food, fallback to safest move ----
function demoDir(){
const head = snake[0];
const bodySet = new Set(snake.map(s=>s.x+','+s.y));
// BFS
const key = (x,y)=>x+','+y;
const prevMap = new Map();
const visited = new Set([key(head.x,head.y)]);
const q = [{x:head.x,y:head.y}];
let found=null;
const dirs=[{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}];
while(q.length && !found){
const cur=q.shift();
for(const d of dirs){
const nx=cur.x+d.x, ny=cur.y+d.y;
if(nx<0||nx>=COLS||ny<0||ny>=ROWS) continue;
const k=key(nx,ny);
if(visited.has(k)) continue;
// allow tail cell (it will move)
const isTail = snake[snake.length-1].x===nx && snake[snake.length-1].y===ny;
if(bodySet.has(k) && !isTail) continue;
visited.add(k);
prevMap.set(k,{x:cur.x,y:cur.y});
if(nx===food.x && ny===food.y){ found={x:nx,y:ny}; break; }
q.push({x:nx,y:ny});
}
}
if(found){
let cur=found;
while(prevMap.has(key(cur.x,cur.y))){
const p=prevMap.get(key(cur.x,cur.y));
if(p.x===head.x && p.y===head.y){
return {x:cur.x-head.x, y:cur.y-head.y};
}
cur=p;
}
}
// fallback: pick non-lethal move with most free neighbours / towards centre
let bestMove=null, bestVal=-1e9;
for(const d of dirs){
if(d.x===-dir.x && d.y===-dir.y) continue;
const nx=head.x+d.x, ny=head.y+d.y;
if(isLethal(nx,ny,snake.slice(0,-1))) continue;
let freeN=0;
for(const dd of dirs){ const ax=nx+dd.x, ay=ny+dd.y; if(ax>=0&&ax<COLS&&ay>=0&&ay<ROWS&&!bodySet.has(key(ax,ay))) freeN++; }
const dist = Math.abs(nx-food.x)+Math.abs(ny-food.y);
const centre = Math.abs(nx-COLS/2)+Math.abs(ny-ROWS/2);
const val = freeN*10 - dist*2 - centre*0.5;
if(val>bestVal){bestVal=val;bestMove=d;}
}
return bestMove || dir;
}
function burst(x,y){
const cx = (x+0.5)*cell, cy=(y+0.5)*cell;
const colors=['#fb7185','#fda4af','#facc15','#ffffff','#4ade80'];
for(let i=0;i<16;i++){
const a = Math.random()*Math.PI*2;
const sp = 1.2 + Math.random()*3.2;
particles.push({x:cx,y:cy,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp-0.6,life:1,decay:0.025+Math.random()*0.02,r:2+Math.random()*3.5,color:colors[i%colors.length]});
}
// ring
particles.push({x:cx,y:cy,vx:0,vy:0,life:1,decay:0.06,r:4,ring:true,color:'#fb7185'});
}
function step(now){
prevSnake = snake.map(s=>({...s}));
if(state==='demo'){
const d = demoDir();
if(!(d.x===-dir.x && d.y===-dir.y)) queueDir(d.x,d.y);
}
if(queue.length) dir = queue.shift();
const head = {x: snake[0].x + dir.x, y: snake[0].y + dir.y};
const willEat = head.x===food.x && head.y===food.y;
const bodyToCheck = willEat ? snake : snake.slice(0,-1);
if(head.x<0||head.x>=COLS||head.y<0||head.y>=ROWS){ gameOver('You hit the wall.'); return; }
if(bodyToCheck.some(s=>s.x===head.x&&s.y===head.y)){ gameOver('You bit yourself.'); return; }
snake.unshift(head);
if(willEat){
score += 10;
if(score>best){ if(!wasNewBest && best>0) wasNewBest=true; best=score; if(best===score) wasNewBest = best>0 && score>=best; }
// simpler newbest flag:
wasNewBest = (score===best && score>0);
updateScoreUI();
burst(food.x, food.y);
interval = Math.max(78, 135 - Math.floor(score/40)*6);
spawnFood();
} else {
snake.pop();
}
lastStep = now;
}
function gameOver(reason){
deathReason = reason;
state='over';
if(score>best) best=score;
updateScoreUI();
burst(snake[0].x, snake[0].y);
showOverlay('GAME OVER', reason + ' Score ' + score + ' points.', false);
demoBadge.classList.add('hidden');
}
function startGame(firstDir){
reset(false);
if(firstDir) queueDir(firstDir.x, firstDir.y);
hideOverlay();
}
// ---- Rendering ----
function roundRect(x,y,w,h,r){
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();
}
function draw(now){
// background checker
for(let y=0;y<ROWS;y++) for(let x=0;x<COLS;x++){
ctx.fillStyle = (x+y)%2===0 ? '#0e2119' : '#0c1a14';
ctx.fillRect(x*cell, y*cell, cell, cell);
}
// subtle vignette
const grad = ctx.createRadialGradient(canvas.width/2, canvas.height/2, 120, canvas.width/2, canvas.height/2, 520);
grad.addColorStop(0,'rgba(255,255,255,0.03)');
grad.addColorStop(1,'rgba(0,0,0,0.28)');
ctx.fillStyle=grad; ctx.fillRect(0,0,canvas.width,canvas.height);
// food (pulsing cherry)
if(food){
const fx=(food.x+0.5)*cell, fy=(food.y+0.5)*cell;
const pulse = 1 + Math.sin(now/180)*0.12;
ctx.save();
ctx.shadowColor='#fb7185'; ctx.shadowBlur=18;
ctx.fillStyle='#f43f5e';
ctx.beginPath(); ctx.arc(fx, fy+2, 9.5*pulse, 0, Math.PI*2); ctx.fill();
ctx.shadowBlur=0;
ctx.fillStyle='rgba(255,255,255,0.85)';
ctx.beginPath(); ctx.arc(fx-3, fy-1, 3, 0, Math.PI*2); ctx.fill();
ctx.strokeStyle='#16a34a'; ctx.lineWidth=2.5; ctx.lineCap='round';
ctx.beginPath(); ctx.moveTo(fx, fy-6); ctx.quadraticCurveTo(fx+4, fy-13, fx+8, fy-13); ctx.stroke();
ctx.fillStyle='#22c55e';
ctx.beginPath(); ctx.ellipse(fx+8, fy-13, 5, 2.6, -0.4, 0, Math.PI*2); ctx.fill();
ctx.restore();
}
// interpolation
let alpha = state==='paused'||state==='over' ? 1 : Math.min(1, Math.max(0,(now-lastStep)/interval));
// when tab was idle, don't jump
if(now-lastStep > interval*2) { alpha=1; }
function interpPos(i){
const cur = snake[i];
const prev = prevSnake[Math.min(i, prevSnake.length-1)] || cur;
return {x: prev.x + (cur.x-prev.x)*alpha, y: prev.y + (cur.y-prev.y)*alpha};
}
// snake body tail -> head
ctx.save();
for(let i=snake.length-1;i>=0;i--){
const p = interpPos(i);
const px = p.x*cell, py = p.y*cell;
const t = snake.length>1 ? i/(snake.length-1) : 0; // 0 head ->1 tail
// gradient head bright green -> tail teal
const hue = 145 - t*35;
const light = 58 - t*10;
ctx.shadowColor = i===0 ? 'rgba(74,222,128,0.7)' : 'rgba(34,197,94,0.35)';
ctx.shadowBlur = i===0 ? 14 : 6;
ctx.fillStyle = `hsl(${hue} 85% ${light}%)`;
const inset = i===0 ? 1.5 : 3 + t*1.5;
const r = i===0 ? 9 : 7;
roundRect(px+inset, py+inset, cell-inset*2, cell-inset*2, r);
ctx.fill();
ctx.shadowBlur=0;
// belly highlight
if(i>0){
ctx.fillStyle='rgba(255,255,255,0.10)';
roundRect(px+inset+3, py+inset+3, cell-inset*2-6, (cell-inset*2-6)*0.38, 5);
ctx.fill();
}
}
ctx.restore();
// head eyes facing direction
const hp = interpPos(0);
const hx = (hp.x+0.5)*cell, hy=(hp.y+0.5)*cell;
const ex = dir.x, ey = dir.y;
const pxv = -ey, pyv = ex;
ctx.save();
for(const side of [-1,1]){
const eyeX = hx + ex*4 + pxv*6*side;
const eyeY = hy + ey*4 + pyv*6*side;
ctx.fillStyle='#ffffff';
ctx.beginPath(); ctx.arc(eyeX, eyeY, 5.2, 0, Math.PI*2); ctx.fill();
ctx.fillStyle='#0f172a';
ctx.beginPath(); ctx.arc(eyeX+ex*2, eyeY+ey*2, 2.8, 0, Math.PI*2); ctx.fill();
ctx.fillStyle='#ffffff';
ctx.beginPath(); ctx.arc(eyeX+ex*2-1, eyeY+ey*2-1, 1, 0, Math.PI*2); ctx.fill();
}
// tongue occasionally
if(Math.sin(now/500)>0.85){
ctx.strokeStyle='#fb7185'; ctx.lineWidth=2; ctx.lineCap='round';
ctx.beginPath(); ctx.moveTo(hx+ex*13, hy+ey*13); ctx.lineTo(hx+ex*19, hy+ey*19); ctx.stroke();
}
ctx.restore();
// particles
for(let i=particles.length-1;i>=0;i--){
const pt = particles[i];
pt.x += pt.vx; pt.y += pt.vy; pt.vy += 0.06; pt.vx*=0.96; pt.vy*=0.96;
pt.life -= pt.decay;
if(pt.life<=0){ particles.splice(i,1); continue; }
ctx.save();
ctx.globalAlpha = Math.max(0, pt.life);
ctx.fillStyle = pt.color;
if(pt.ring){
ctx.strokeStyle = pt.color; ctx.lineWidth=2.5;
ctx.beginPath(); ctx.arc(pt.x, pt.y, pt.r + (1-pt.life)*26, 0, Math.PI*2); ctx.stroke();
} else {
ctx.beginPath(); ctx.arc(pt.x, pt.y, pt.r*pt.life+0.6, 0, Math.PI*2); ctx.fill();
}
ctx.restore();
}
// paused tint
if(state==='paused'){
ctx.fillStyle='rgba(5,10,8,0.45)'; ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle='#e6f0e9'; ctx.font='900 34px system-ui, sans-serif'; ctx.textAlign='center';
ctx.fillText('PAUSED', canvas.width/2, canvas.height/2 - 6);
ctx.font='500 15px system-ui, sans-serif'; ctx.fillStyle='#9db3a8';
ctx.fillText('Press P or tap Pause to resume', canvas.width/2, canvas.height/2+24);
}
}
function loop(now){
if(state==='demo' || state==='playing'){
if(now - lastStep >= interval) step(now);
} else if(state==='over'){
// keep lastStep far so alpha=1, particles still animate in draw
}
draw(now);
requestAnimationFrame(loop);
}
// ---- Input ----
const keyMap = {
ArrowUp:{x:0,y:-1}, ArrowDown:{x:0,y:1}, ArrowLeft:{x:-1,y:0}, ArrowRight:{x:1,y:0},
w:{x:0,y:-1}, s:{x:0,y:1}, a:{x:-1,y:0}, d:{x:1,y:0},
W:{x:0,y:-1}, S:{x:0,y:1}, A:{x:-1,y:0}, D:{x:1,y:0},
};
window.addEventListener('keydown', (e)=>{
const k = e.key;
if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight',' '].includes(k)) e.preventDefault();
if(k===' ' || k==='Enter'){
if(state==='over' || state==='demo'){ startGame(); return; }
if(state==='paused'){ togglePause(); return; }
return;
}
if(k==='p' || k==='P' || k==='Escape'){
if(state==='playing' || state==='paused') togglePause();
return;
}
const d = keyMap[k];
if(d){
if(state==='demo'){ startGame(d); return; }
if(state==='over'){ startGame(d); return; }
if(state==='paused'){ togglePause(); }
if(state==='playing') queueDir(d.x, d.y);
} else if(state==='demo' && k.length===1){
startGame();
}
});
function togglePause(){
if(state==='playing'){
state='paused';
showOverlay('PAUSED', 'Take a breath. Your snake is waiting.', true);
ovScore.textContent = score;
ovBest.style.display='none';
} else if(state==='paused'){
state='playing';
hideOverlay();
lastStep = performance.now();
}
}
// Buttons
document.getElementById('btnRestart').addEventListener('click', ()=>startGame());
document.getElementById('btnPauseOv').addEventListener('click', togglePause);
// pause / restart in HUD via overlay? add keyboard hint buttons using overlay pause button handled.
// Click canvas / overlay to start from demo
canvas.addEventListener('pointerdown', ()=>{ if(state==='demo') startGame(); canvas.focus({preventScroll:true}); });
overlay.addEventListener('pointerdown', (e)=>{ if(e.target===overlay && state==='demo') startGame(); });
// D-pad
document.querySelectorAll('.dpad button').forEach(btn=>{
btn.addEventListener('pointerdown', (e)=>{
e.preventDefault();
const map={up:{x:0,y:-1},down:{x:0,y:1},left:{x:-1,y:0},right:{x:1,y:0}};
const d=map[btn.dataset.dir];
if(state==='demo'||state==='over'){ startGame(d); return; }
if(state==='paused') togglePause();
if(state==='playing') queueDir(d.x,d.y);
});
});
// Swipe
let touchStart=null;
canvas.addEventListener('touchstart', e=>{ touchStart={x:e.touches[0].clientX,y:e.touches[0].clientY}; }, {passive:true});
canvas.addEventListener('touchend', e=>{
if(!touchStart) return;
const dx=e.changedTouches[0].clientX-touchStart.x, dy=e.changedTouches[0].clientY-touchStart.y;
touchStart=null;
if(Math.max(Math.abs(dx),Math.abs(dy))<18) { if(state==='demo') startGame(); return; }
const d = Math.abs(dx)>Math.abs(dy) ? {x:Math.sign(dx),y:0} : {x:0,y:Math.sign(dy)};
if(state==='demo'||state==='over'){ startGame(d); return; }
if(state==='paused') togglePause();
if(state==='playing') queueDir(d.x,d.y);
}, {passive:true});
// Add pause/restart buttons into header actions area dynamically (keeps layout tidy)
const hud = document.querySelector('.hud');
// (hud not present; buttons are in overlay + keyboard) - create small floating controls inside frame
const ctrl = document.createElement('div');
ctrl.style.cssText='position:absolute;right:18px;bottom:18px;z-index:6;display:flex;gap:6px';
ctrl.innerHTML='<button id="btnPause" style="background:rgba(7,11,18,.65);backdrop-filter:blur(6px)">❚❚ Pause</button><button id="btnRestart2" style="background:rgba(7,11,18,.65);backdrop-filter:blur(6px)">↻ Restart</button>';
document.querySelector('.frame').appendChild(ctrl);
document.getElementById('btnPause').addEventListener('click', ()=>{ if(state==='playing'||state==='paused') togglePause(); else if(state==='demo') startGame(); });
document.getElementById('btnRestart2').addEventListener('click', ()=>startGame());
reset(true);
requestAnimationFrame(loop);
})();
</script>
</body>
</html>Reasoning trace (1,311 tokens)
Balancing single-file HTML snake game requirements with demo mode overlay and smooth canvas rendering constraints.Adding CSS responsive layout, BFS demo AI, input queueing, interpolation, and particle effects.