Models 44

Muse Ember

Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
38.5 s
Cost
$0.010
Output tokens
20,496
Spent thinking
10,765 tokens
Speed
559.6 tok/s
Console errors
0
First token after
1.9 s
First visible text after
26.2 s
Prompt tokens
510
Code size
28.8 KB
Finish reason
stop
Served by
Meta
Model id
meta/muse-ember
Reasoning sent
{"effort":"xhigh"}
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 · 610 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 — Vanilla JS</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
:root{
  --bg:#070b14; --panel:rgba(255,255,255,.055); --border:rgba(255,255,255,.09);
  --green:#4ade80; --lime:#a3e635; --pink:#fb7185; --text:#f1f5f9; --muted:#94a3b8;
}
html,body{height:100%}
body{
  font-family:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  background:
    radial-gradient(700px 420px at 12% -6%, rgba(34,197,94,.20), transparent 60%),
    radial-gradient(800px 500px at 96% 12%, rgba(99,102,241,.18), transparent 60%),
    radial-gradient(600px 500px at 50% 115%, rgba(236,72,153,.12), transparent 60%),
    linear-gradient(180deg,#0a1120 0%,#070b14 55%,#05080f 100%);
  color:var(--text); min-height:100vh; display:flex; justify-content:center;
  padding:14px 12px 22px; overflow-x:hidden;
}
body::before{
  content:""; position:fixed; inset:0; pointer-events:none; opacity:.35;
  background-image:linear-gradient(rgba(255,255,255,.025) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.025) 1px,transparent 1px);
  background-size:34px 34px; mask-image:radial-gradient(ellipse at center,black 30%,transparent 78%);
}
.app{width:min(620px,100%); display:flex; flex-direction:column; align-items:center; gap:12px; position:relative; z-index:1}
.header{width:100%; display:flex; align-items:center; justify-content:space-between; gap:12px}
.logo{display:flex; align-items:center; gap:12px}
.logo-icon{font-size:34px; width:52px;height:52px; display:grid;place-items:center; border-radius:16px;
  background:linear-gradient(145deg,rgba(74,222,128,.22),rgba(163,230,53,.08)); border:1px solid rgba(74,222,128,.35);
  box-shadow:0 8px 24px rgba(34,197,94,.18)}
h1{font-size:26px; line-height:1; letter-spacing:.04em; font-weight:900}
h1 span{background:linear-gradient(90deg,#4ade80,#a3e635,#fde68a); -webkit-background-clip:text; background-clip:text; color:transparent}
.logo p{font-size:11px; color:var(--muted); letter-spacing:.12em; text-transform:uppercase; margin-top:4px; font-weight:700}
.header-badge{font-size:11px; font-weight:800; letter-spacing:.08em; padding:7px 10px; border-radius:999px;
  background:var(--panel); border:1px solid var(--border); color:#cbd5e1; white-space:nowrap}
.scoreboard{width:100%; display:grid; grid-template-columns:repeat(4,1fr); gap:10px}
.card{background:var(--panel); border:1px solid var(--border); border-radius:16px; padding:10px 12px;
  backdrop-filter:blur(8px); box-shadow:0 10px 24px rgba(0,0,0,.18)}
.card .label{display:block; font-size:10px; letter-spacing:.14em; text-transform:uppercase; color:var(--muted); font-weight:800; margin-bottom:2px}
.card strong{font-size:26px; line-height:1; font-weight:900; letter-spacing:.02em}
.score-card strong{color:#fde68a}
.score-card.bump{animation:bump .28s ease}
@keyframes bump{40%{transform:scale(1.06)}}
.board-wrap{
  position:relative; aspect-ratio:1/1; width:max(262px, min(540px, 94vw, calc(100vh - 235px)));
  border-radius:22px; overflow:hidden; border:1px solid rgba(74,222,128,.28);
  box-shadow:0 24px 70px rgba(0,0,0,.55), 0 0 0 6px rgba(15,23,42,.55), 0 0 42px rgba(34,197,94,.16);
  background:#0f1a2e; touch-action:none; user-select:none; -webkit-user-select:none;
}
.board-wrap.shake{animation:shake .42s ease}
@keyframes shake{20%{transform:translateX(-7px)}40%{transform:translateX(7px)}60%{transform:translateX(-5px)}80%{transform:translateX(4px)}}
.board-wrap::after{content:""; position:absolute; inset:0; pointer-events:none; border-radius:22px;
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.06), inset 0 0 40px rgba(0,0,0,.35)}
canvas{width:100%; height:100%; display:block}
.state-badge{position:absolute; top:10px; left:10px; z-index:5; display:flex; align-items:center; gap:7px;
  font-size:10px; font-weight:900; letter-spacing:.14em; padding:6px 9px; border-radius:999px;
  background:rgba(7,13,24,.72); border:1px solid rgba(255,255,255,.12); backdrop-filter:blur(6px)}
.state-badge .dot{width:8px;height:8px;border-radius:50%;background:#4ade80; box-shadow:0 0 10px #4ade80; animation:pulse 1.2s infinite}
.state-badge.paused .dot{background:#facc15; box-shadow:0 0 10px #facc15; animation:none}
.state-badge.over .dot{background:#fb7185; box-shadow:0 0 10px #fb7185; animation:none}
@keyframes pulse{50%{opacity:.35}}
.overlay{position:absolute; inset:0; z-index:6; display:flex; align-items:center; justify-content:center;
  background:rgba(5,9,18,.34); backdrop-filter:blur(2.5px); padding:16px}
.overlay.hidden{display:none}
.overlay-card{width:min(340px,92%); text-align:center; background:rgba(11,18,32,.90); border:1px solid rgba(255,255,255,.11);
  border-radius:22px; padding:22px 20px 18px; box-shadow:0 22px 60px rgba(0,0,0,.55); animation:pop .35s cubic-bezier(.2,1.4,.4,1)}
@keyframes pop{from{transform:scale(.86); opacity:0}}
.live-pill{display:inline-flex; align-items:center; gap:7px; font-size:10px; font-weight:900; letter-spacing:.16em;
  color:#86efac; background:rgba(34,197,94,.12); border:1px solid rgba(74,222,128,.35); padding:5px 10px; border-radius:999px; margin-bottom:10px}
.pulse-dot{width:7px;height:7px;border-radius:50%;background:#4ade80; animation:pulse 1s infinite}
.overlay-card h2{font-size:28px; font-weight:900; letter-spacing:.02em; margin-bottom:6px}
.overlay-card .sub{color:#cbd5e1; font-size:13.5px; line-height:1.45}
.controls-grid{display:grid; grid-template-columns:1.4fr 1fr 1fr; gap:8px; margin:16px 0 14px}
.controls-grid>div{background:rgba(255,255,255,.05); border:1px solid rgba(255,255,255,.07); border-radius:12px; padding:9px 6px}
.controls-grid span{display:block; font-size:10px; letter-spacing:.12em; text-transform:uppercase; color:var(--muted); font-weight:800; margin-top:6px}
kbd{display:inline-block; font-family:inherit; font-size:10px; font-weight:800; background:#1e293b; border:1px solid #475569;
  border-bottom-width:3px; border-radius:6px; padding:3px 6px; margin:1px; color:#e2e8f0}
.btn{font:inherit; font-weight:800; font-size:13px; color:var(--text); background:rgba(255,255,255,.07);
  border:1px solid rgba(255,255,255,.12); border-radius:999px; padding:10px 16px; cursor:pointer; transition:.15s}
.btn:hover{transform:translateY(-1px); background:rgba(255,255,255,.12)}
.btn:active{transform:translateY(0) scale(.98)}
.btn.primary{background:linear-gradient(135deg,#22c55e,#84cc16); border-color:rgba(255,255,255,.25); color:#052e16; box-shadow:0 10px 24px rgba(34,197,94,.35)}
.btn.primary:hover{filter:brightness(1.08)}
.btn.big{width:100%; padding:13px 16px; font-size:15px}
.tiny{font-size:11px; color:var(--muted); margin-top:12px; line-height:1.4}
.final-score{font-size:52px; font-weight:900; line-height:1; color:#fde68a; margin:8px 0 2px; text-shadow:0 0 26px rgba(253,230,138,.35)}
.best-line{font-size:13px; color:#cbd5e1; margin-bottom:14px}
.controls-bar{width:100%; display:flex; flex-wrap:wrap; align-items:center; justify-content:center; gap:8px}
.hint{width:100%; text-align:center; font-size:12px; color:var(--muted)}
.hint b{color:#e2e8f0}
.dpad{display:none; grid-template-columns:repeat(3,64px); grid-template-rows:repeat(3,56px); gap:6px; justify-content:center; margin-top:2px}
.dpad button{font-size:20px; border-radius:16px; border:1px solid rgba(255,255,255,.12); background:var(--panel); color:white; cursor:pointer}
.dpad button:active{background:rgba(74,222,128,.25)}
.dpad .up{grid-column:2; grid-row:1}.dpad .left{grid-column:1; grid-row:2}.dpad .right{grid-column:3; grid-row:2}.dpad .down{grid-column:2; grid-row:3}
@media (max-width:700px){
  .dpad{display:grid}
  .header-badge{display:none}
  h1{font-size:22px}
  .card strong{font-size:21px}
  .scoreboard{gap:7px}
  .card{padding:8px 9px; border-radius:13px}
  .board-wrap{width:min(94vw, 430px)}
}
</style>
</head>
<body>
<main class="app">
  <header class="header">
    <div class="logo">
      <div class="logo-icon">🐍</div>
      <div><h1>NEON <span>SNAKE</span></h1><p>Classic Arcade • Vanilla JS</p></div>
    </div>
    <div class="header-badge">20 × 20 GRID</div>
  </header>

  <section class="scoreboard">
    <div class="card score-card" id="scoreCard"><span class="label">⭐ Score</span><strong id="scoreEl">0</strong></div>
    <div class="card"><span class="label">🏆 Best</span><strong id="bestEl">0</strong></div>
    <div class="card"><span class="label">📏 Length</span><strong id="lengthEl">4</strong></div>
    <div class="card"><span class="label">⚡ Level</span><strong id="levelEl">1</strong></div>
  </section>

  <section class="board-wrap" id="boardWrap">
    <canvas id="game"></canvas>
    <div class="state-badge" id="stateBadge"><span class="dot"></span><span id="stateText">DEMO</span></div>

    <div class="overlay" id="overlayStart">
      <div class="overlay-card">
        <div class="live-pill"><span class="pulse-dot"></span> LIVE DEMO</div>
        <h2>🐍 Neon Snake</h2>
        <p class="sub">The AI is playing for you.<br>Click the board or press any key to take control.</p>
        <div class="controls-grid">
          <div><kbd>←↑↓→</kbd><kbd>WASD</kbd><span>Move</span></div>
          <div><kbd>P</kbd><span>Pause</span></div>
          <div><kbd>SPACE</kbd><span>Restart</span></div>
        </div>
        <button class="btn primary big" id="btnPlay">▶ &nbsp;Take Control</button>
        <p class="tiny">Eat the apple · +10 points · Grow by 1 · Don't hit walls or yourself</p>
      </div>
    </div>

    <div class="overlay hidden" id="overlayPause">
      <div class="overlay-card">
        <h2>⏸ Paused</h2>
        <p class="sub">Take a breath. The snake is waiting.</p>
        <div style="height:16px"></div>
        <button class="btn primary big" id="btnResume">▶ &nbsp;Resume</button>
        <p class="tiny">Press <b>P</b> or <b>Space</b> to resume</p>
      </div>
    </div>

    <div class="overlay hidden" id="overlayOver">
      <div class="overlay-card">
        <h2>💥 Game Over</h2>
        <p class="sub" id="causeText">You hit the wall!</p>
        <div class="final-score" id="finalScore">0</div>
        <p class="best-line">🏆 Best score: <b id="finalBest">0</b> &nbsp;·&nbsp; Length: <b id="finalLength">4</b></p>
        <button class="btn primary big" id="btnAgain">↻ &nbsp;Play Again</button>
        <p class="tiny">Press <b>Space</b> or <b>Enter</b> to restart</p>
      </div>
    </div>
  </section>

  <section class="controls-bar">
    <button class="btn" id="btnPause">⏸ Pause</button>
    <button class="btn" id="btnRestart">↻ Restart</button>
    <button class="btn" id="btnSound">🔊 Sound On</button>
    <span class="hint">Arrow keys / <b>WASD</b> to move &nbsp;·&nbsp; <b>P</b> pause &nbsp;·&nbsp; <b>Space</b> restart &nbsp;·&nbsp; Quick turns are queued — no reversing!</span>
  </section>

  <nav class="dpad" aria-label="On-screen controls">
    <button class="up" data-dir="0,-1">▲</button>
    <button class="left" data-dir="-1,0">◀</button>
    <button class="right" data-dir="1,0">▶</button>
    <button class="down" data-dir="0,1">▼</button>
  </nav>
</main>

<script>
"use strict";
const COLS = 20, ROWS = 20;
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const boardWrap = document.getElementById('boardWrap');
const scoreEl = document.getElementById('scoreEl');
const bestEl = document.getElementById('bestEl');
const lengthEl = document.getElementById('lengthEl');
const levelEl = document.getElementById('levelEl');
const scoreCard = document.getElementById('scoreCard');
const stateBadge = document.getElementById('stateBadge');
const stateText = document.getElementById('stateText');
const overlayStart = document.getElementById('overlayStart');
const overlayPause = document.getElementById('overlayPause');
const overlayOver = document.getElementById('overlayOver');

let cell = 24;
let snake = [], prevSnake = [], food = {x:5,y:5}, foodBorn = 0;
let direction = {x:1,y:0}, dirQueue = [];
let score = 0, best = 0, foodsEaten = 0;
let isDemo = true, isPaused = false, isOver = false;
let lastTick = performance.now(), lastFrame = performance.now();
let particles = [], floaters = [];
let audioCtx = null, muted = false;
const DIRS = [{x:0,y:-1},{x:0,y:1},{x:-1,y:0},{x:1,y:0}];

function keyOf(p){ return p.x + ',' + p.y; }
function tickMs(){ return Math.max(65, 140 - foodsEaten * 2.5); }

function resizeCanvas(){
  const r = canvas.getBoundingClientRect();
  const dpr = Math.min(window.devicePixelRatio || 1, 2);
  const size = Math.max(200, Math.round(r.width * dpr));
  canvas.width = size; canvas.height = size;
  cell = canvas.width / COLS;
}
window.addEventListener('resize', resizeCanvas);
if (window.ResizeObserver) new ResizeObserver(resizeCanvas).observe(boardWrap);

function initAudio(){
  try{
    if(!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    if(audioCtx.state === 'suspended') audioCtx.resume();
  }catch(e){}
}
function tone(f1, f2, dur, type, vol){
  if(muted || !audioCtx) return;
  try{
    const t = audioCtx.currentTime;
    const o = audioCtx.createOscillator(), g = audioCtx.createGain();
    o.type = type || 'sine'; o.frequency.setValueAtTime(f1, t);
    o.frequency.exponentialRampToValueAtTime(Math.max(30,f2), t + dur);
    g.gain.setValueAtTime(vol || .12, t); g.gain.exponentialRampToValueAtTime(.001, t + dur);
    o.connect(g); g.connect(audioCtx.destination); o.start(t); o.stop(t + dur);
  }catch(e){}
}
function soundEat(){ tone(540, 880, .12, 'sine', .14); setTimeout(()=>tone(880,1180,.09,'sine',.08),55); }
function soundDie(){ tone(280, 60, .45, 'sawtooth', .14); }

function spawnFood(){
  while(true){
    const x = Math.floor(Math.random()*COLS), y = Math.floor(Math.random()*ROWS);
    if(!snake.some(s=>s.x===x && s.y===y)){ food = {x,y}; foodBorn = performance.now(); return; }
  }
}
function spawnParticles(gx, gy){
  const colors = ['#fb7185','#f43f5e','#fda4af','#ffffff','#fde68a','#4ade80'];
  for(let i=0;i<20;i++){
    const a = Math.random()*Math.PI*2, sp = 1.6 + Math.random()*3.6;
    particles.push({ x:gx+.5, y:gy+.5, vx:Math.cos(a)*sp, vy:Math.sin(a)*sp,
      life:0, max:.55+Math.random()*.45, size:.06+Math.random()*.09, color:colors[i%colors.length] });
  }
  floaters.push({x:gx+.5, y:gy+.2, text:'+10', life:0, max:.9});
}
function updateFx(dt){
  particles = particles.filter(p=>p.life<p.max);
  for(const p of particles){ p.life+=dt; p.x+=p.vx*dt; p.y+=p.vy*dt; p.vx*=.92; p.vy*=.92; p.vy+=1.2*dt; }
  floaters = floaters.filter(f=>f.life<f.max);
  for(const f of floaters){ f.life+=dt; f.y-= .85*dt; }
}

function resetGame(demo){
  isDemo = !!demo; isPaused = false; isOver = false;
  snake = [{x:9,y:10},{x:8,y:10},{x:7,y:10},{x:6,y:10}];
  prevSnake = snake.map(s=>({...s}));
  direction = {x:1,y:0}; dirQueue = [];
  score = 0; foodsEaten = 0; particles = []; floaters = [];
  spawnFood();
  lastTick = performance.now();
  hideOverlays();
  if(isDemo) overlayStart.classList.remove('hidden');
  updateHUD(); updateBadge();
}
function hideOverlays(){ overlayStart.classList.add('hidden'); overlayPause.classList.add('hidden'); overlayOver.classList.add('hidden'); }
function updateBadge(){
  stateBadge.classList.remove('paused','over');
  if(isOver){ stateText.textContent='GAME OVER'; stateBadge.classList.add('over'); }
  else if(isPaused){ stateText.textContent='PAUSED'; stateBadge.classList.add('paused'); }
  else if(isDemo){ stateText.textContent='DEMO'; }
  else { stateText.textContent='PLAYING'; }
}
function updateHUD(){
  scoreEl.textContent = score; bestEl.textContent = best;
  lengthEl.textContent = snake.length; levelEl.textContent = 1 + Math.floor(foodsEaten/5);
}
function bumpScore(){ scoreCard.classList.remove('bump'); void scoreCard.offsetWidth; scoreCard.classList.add('bump'); }

function gameOver(cause){
  isOver = true; prevSnake = snake.map(s=>({...s}));
  if(score > best) best = score;
  soundDie();
  boardWrap.classList.remove('shake'); void boardWrap.offsetWidth; boardWrap.classList.add('shake');
  updateHUD(); updateBadge();
  if(isDemo){
    setTimeout(()=>{ if(isOver && isDemo) resetGame(true); }, 1700);
  }else{
    document.getElementById('causeText').textContent = cause;
    document.getElementById('finalScore').textContent = score;
    document.getElementById('finalBest').textContent = best;
    document.getElementById('finalLength').textContent = snake.length;
    hideOverlays(); overlayOver.classList.remove('hidden');
  }
}

/* ------- AI for demo mode ------- */
function bfsNextDir(){
  const head = snake[0], tail = snake[snake.length-1];
  const blocked = new Set(snake.map(keyOf)); blocked.delete(keyOf(tail)); blocked.delete(keyOf(head));
  const queue = [{...head}], parent = new Map(); parent.set(keyOf(head), null);
  let found = null;
  for(let qi=0; qi<queue.length; qi++){
    const cur = queue[qi];
    if(cur.x===food.x && cur.y===food.y){ found = cur; break; }
    for(const d of DIRS){
      const nx = cur.x+d.x, ny = cur.y+d.y;
      if(nx<0||ny<0||nx>=COLS||ny>=ROWS) continue;
      const k = nx+','+ny;
      if(blocked.has(k) || parent.has(k)) continue;
      parent.set(k, cur); queue.push({x:nx,y:ny});
    }
  }
  if(!found) return null;
  let cur = found;
  while(true){
    const p = parent.get(keyOf(cur));
    if(!p || (p.x===head.x && p.y===head.y)) break;
    cur = p;
  }
  return {x:cur.x-head.x, y:cur.y-head.y};
}
function floodCount(start){
  const tail = snake[snake.length-1];
  const blocked = new Set(snake.map(keyOf)); blocked.delete(keyOf(tail));
  const seen = new Set([keyOf(start)]), q = [{...start}];
  for(let i=0;i<q.length;i++){
    const cur = q[i];
    for(const d of DIRS){
      const nx=cur.x+d.x, ny=cur.y+d.y, k=nx+','+ny;
      if(nx<0||ny<0||nx>=COLS||ny>=ROWS||blocked.has(k)||seen.has(k)) continue;
      seen.add(k); q.push({x:nx,y:ny});
    }
  }
  return seen.size;
}
function aiChoose(){
  const nd = bfsNextDir();
  if(nd){ direction = nd; dirQueue = []; return; }
  const head = snake[0]; let bestDir = null, bestVal = -1e9;
  for(const d of DIRS){
    if(d.x===-direction.x && d.y===-direction.y) continue;
    const nx=head.x+d.x, ny=head.y+d.y;
    if(nx<0||ny<0||nx>=COLS||ny>=ROWS) continue;
    if(snake.slice(0,-1).some(s=>s.x===nx&&s.y===ny)) continue;
    const val = floodCount({x:nx,y:ny}) - (Math.abs(nx-food.x)+Math.abs(ny-food.y))*2;
    if(val>bestVal){ bestVal=val; bestDir=d; }
  }
  if(bestDir) direction = bestDir;
}

/* ------- Core step ------- */
function stepGame(){
  prevSnake = snake.map(s=>({...s}));
  if(isDemo) aiChoose();
  else if(dirQueue.length) direction = dirQueue.shift();

  const head = snake[0];
  const nh = {x:head.x+direction.x, y:head.y+direction.y};
  if(nh.x<0||nh.y<0||nh.x>=COLS||nh.y>=ROWS){ gameOver('You hit the wall! 🧱'); return; }
  const eating = (nh.x===food.x && nh.y===food.y);
  const body = eating ? snake : snake.slice(0,-1);
  if(body.some(s=>s.x===nh.x && s.y===nh.y)){ gameOver('You ran into yourself! 🌀'); return; }

  snake.unshift(nh);
  if(eating){
    score += 10; foodsEaten++;
    spawnParticles(food.x, food.y); soundEat();
    if(score>best) best = score;
    spawnFood(); updateHUD(); bumpScore();
  }else{
    snake.pop();
  }
  updateHUD();
}

/* ------- Input: queued turns, never reverse ------- */
function enqueueDir(nx, ny){
  const last = dirQueue.length ? dirQueue[dirQueue.length-1] : direction;
  if(nx===-last.x && ny===-last.y) return;
  if(nx===last.x && ny===last.y) return;
  if(dirQueue.length >= 3) return;
  dirQueue.push({x:nx,y:ny});
}
function takeControl(dir){
  if(!isDemo) return;
  isDemo = false;
  if(isOver){ resetGame(false); if(dir) direction = {x:dir.x,y:dir.y}; return; }
  hideOverlays(); lastTick = performance.now(); updateBadge();
}
function togglePause(){
  if(isOver) return;
  isPaused = !isPaused;
  if(isPaused){ hideOverlays(); overlayPause.classList.remove('hidden'); }
  else { hideOverlays(); lastTick = performance.now(); }
  updateBadge();
}

document.addEventListener('keydown', (e)=>{
  initAudio();
  const key = e.key, lower = key.toLowerCase();
  if(['ArrowUp','ArrowDown','ArrowLeft','ArrowRight',' '].includes(key)) e.preventDefault();
  let dir = null;
  if(key==='ArrowUp'||lower==='w') dir={x:0,y:-1};
  else if(key==='ArrowDown'||lower==='s') dir={x:0,y:1};
  else if(key==='ArrowLeft'||lower==='a') dir={x:-1,y:0};
  else if(key==='ArrowRight'||lower==='d') dir={x:1,y:0};

  if(isDemo){
    takeControl(dir);
    if(dir) enqueueDir(dir.x, dir.y);
    else if(lower==='p' && !isOver) togglePause();
    return;
  }
  if(dir){
    if(!isOver && !isPaused) enqueueDir(dir.x, dir.y);
    else if(isPaused){ togglePause(); enqueueDir(dir.x, dir.y); }
    return;
  }
  if(lower==='p' || key==='Escape'){ togglePause(); return; }
  if(key===' ' || key==='Enter'){
    if(isOver) resetGame(false);
    else if(isPaused) togglePause();
    return;
  }
  if(lower==='r') resetGame(false);
});

boardWrap.addEventListener('pointerdown', ()=>{ initAudio(); if(isDemo) takeControl(null); });
document.getElementById('btnPlay').addEventListener('click', ()=>{ initAudio(); takeControl(null); });
document.getElementById('btnResume').addEventListener('click', ()=>{ initAudio(); if(isPaused) togglePause(); });
document.getElementById('btnAgain').addEventListener('click', ()=>{ initAudio(); resetGame(false); });
document.getElementById('btnPause').addEventListener('click', ()=>{ initAudio(); if(isDemo) takeControl(null); togglePause(); document.getElementById('btnPause').textContent = isPaused ? '▶ Resume' : '⏸ Pause'; });
document.getElementById('btnRestart').addEventListener('click', ()=>{ initAudio(); resetGame(false); document.getElementById('btnPause').textContent = '⏸ Pause'; });
document.getElementById('btnSound').addEventListener('click', (ev)=>{ initAudio(); muted=!muted; ev.currentTarget.textContent = muted ? '🔇 Sound Off' : '🔊 Sound On'; });

document.querySelectorAll('.dpad button').forEach(btn=>{
  btn.addEventListener('pointerdown', (e)=>{
    e.preventDefault(); initAudio();
    const [x,y] = btn.dataset.dir.split(',').map(Number);
    if(isDemo){ takeControl({x,y}); enqueueDir(x,y); }
    else if(!isOver && !isPaused) enqueueDir(x,y);
  });
});

/* swipe */
let tsX=0, tsY=0;
boardWrap.addEventListener('touchstart', e=>{ const t=e.touches[0]; tsX=t.clientX; tsY=t.clientY; }, {passive:true});
boardWrap.addEventListener('touchend', e=>{
  const t=e.changedTouches[0], dx=t.clientX-tsX, dy=t.clientY-tsY;
  if(Math.max(Math.abs(dx),Math.abs(dy))<24) return;
  let dir = Math.abs(dx)>Math.abs(dy) ? {x:Math.sign(dx),y:0} : {x:0,y:Math.sign(dy)};
  initAudio();
  if(isDemo){ takeControl(dir); enqueueDir(dir.x,dir.y); }
  else if(!isOver && !isPaused) enqueueDir(dir.x,dir.y);
}, {passive:true});

/* ------- Rendering ------- */
function interpPoints(t){
  return snake.map((s,i)=>{
    const p = prevSnake[i] || prevSnake[prevSnake.length-1] || s;
    let dx = s.x-p.x, dy = s.y-p.y;
    if(Math.abs(dx)>1) dx=0; if(Math.abs(dy)>1) dy=0;
    return {x:p.x+dx*t, y:p.y+dy*t};
  });
}
function draw(now, t){
  const W = canvas.width, H = canvas.height;
  ctx.clearRect(0,0,W,H);
  // checkerboard
  for(let y=0;y<ROWS;y++) for(let x=0;x<COLS;x++){
    ctx.fillStyle = ((x+y)%2===0) ? '#101c31' : '#13223c';
    ctx.fillRect(x*cell, y*cell, cell+1, cell+1);
  }
  ctx.strokeStyle = 'rgba(255,255,255,.035)'; ctx.lineWidth = 1;
  for(let i=1;i<COLS;i++){ ctx.beginPath(); ctx.moveTo(i*cell,0); ctx.lineTo(i*cell,H); ctx.stroke(); }
  for(let i=1;i<ROWS;i++){ ctx.beginPath(); ctx.moveTo(0,i*cell); ctx.lineTo(W,i*cell); ctx.stroke(); }

  // food
  const age = now - foodBorn;
  let pop = age<220 ? (.55 + (age/220)*.45) : 1;
  const pulse = 1 + Math.sin(now/180)*.07;
  const fx = (food.x+.5)*cell, fy = (food.y+.5)*cell;
  const fr = cell*.32*pulse*pop;
  ctx.save();
  ctx.strokeStyle = 'rgba(251,113,133,.35)'; ctx.lineWidth = 2;
  ctx.beginPath(); ctx.arc(fx,fy, fr+cell*.12+Math.sin(now/220)*cell*.05, 0, Math.PI*2); ctx.stroke();
  ctx.shadowColor = 'rgba(244,63,94,.85)'; ctx.shadowBlur = cell*.6;
  const fg = ctx.createRadialGradient(fx-fr*.35, fy-fr*.4, fr*.15, fx, fy, fr);
  fg.addColorStop(0,'#fecdd3'); fg.addColorStop(.35,'#fb7185'); fg.addColorStop(1,'#e11d48');
  ctx.fillStyle = fg; ctx.beginPath(); ctx.arc(fx,fy,fr,0,Math.PI*2); ctx.fill();
  ctx.shadowBlur = 0;
  ctx.fillStyle = 'rgba(255,255,255,.85)'; ctx.beginPath(); ctx.arc(fx-fr*.35, fy-fr*.38, fr*.22, 0, Math.PI*2); ctx.fill();
  ctx.strokeStyle = '#92400e'; ctx.lineWidth = Math.max(2,cell*.06); ctx.lineCap='round';
  ctx.beginPath(); ctx.moveTo(fx, fy-fr*.85); ctx.quadraticCurveTo(fx+cell*.08, fy-fr*1.25, fx+cell*.18, fy-fr*1.3); ctx.stroke();
  ctx.fillStyle = '#4ade80'; ctx.beginPath(); ctx.ellipse(fx+cell*.2, fy-fr*1.18, cell*.13, cell*.07, -.5, 0, Math.PI*2); ctx.fill();
  ctx.restore();

  // particles
  for(const p of particles){
    const a = 1 - p.life/p.max;
    ctx.globalAlpha = a; ctx.fillStyle = p.color;
    ctx.beginPath(); ctx.arc(p.x*cell, p.y*cell, p.size*cell*(0.5+a*0.7), 0, Math.PI*2); ctx.fill();
  }
  ctx.globalAlpha = 1;
  // floaters
  ctx.font = `900 ${Math.round(cell*.55)}px ui-sans-serif,system-ui,sans-serif`;
  ctx.textAlign = 'center';
  for(const f of floaters){
    ctx.globalAlpha = 1 - f.life/f.max;
    ctx.fillStyle = '#fde68a'; ctx.shadowColor='rgba(0,0,0,.6)'; ctx.shadowBlur=6;
    ctx.fillText(f.text, f.x*cell, f.y*cell); ctx.shadowBlur=0;
  }
  ctx.globalAlpha = 1;

  // snake
  const pts = interpPoints(t);
  const px = pts.map(p=>({x:(p.x+.5)*cell, y:(p.y+.5)*cell}));
  if(px.length){
    const tail = px[px.length-1], headPt = px[0];
    // body path (tail -> head)
    ctx.save();
    ctx.lineCap='round'; ctx.lineJoin='round';
    ctx.shadowColor='rgba(0,0,0,.45)'; ctx.shadowBlur=cell*.25; ctx.shadowOffsetY=cell*.08;
    const bg = ctx.createLinearGradient(tail.x, tail.y, headPt.x, headPt.y);
    bg.addColorStop(0,'#15803d'); bg.addColorStop(.55,'#22c55e'); bg.addColorStop(1,'#a3e635');
    ctx.strokeStyle = bg; ctx.lineWidth = cell*.56;
    ctx.beginPath(); ctx.moveTo(tail.x, tail.y);
    for(let i=px.length-2;i>=0;i--) ctx.lineTo(px[i].x, px[i].y);
    ctx.stroke();
    ctx.shadowBlur=0; ctx.shadowOffsetY=0;
    // glossy core
    ctx.strokeStyle='rgba(220,252,231,.28)'; ctx.lineWidth=cell*.20;
    ctx.beginPath(); ctx.moveTo(tail.x, tail.y);
    for(let i=px.length-2;i>=0;i--) ctx.lineTo(px[i].x, px[i].y);
    ctx.stroke();
    // texture dots
    ctx.fillStyle='rgba(5,46,22,.18)';
    for(let i=1;i<px.length;i++){ ctx.beginPath(); ctx.arc(px[i].x, px[i].y, cell*.055, 0, Math.PI*2); ctx.fill(); }

    // head
    const hx=headPt.x, hy=headPt.y, hr=cell*.40;
    const hg = ctx.createRadialGradient(hx-hr*.3, hy-hr*.35, hr*.2, hx, hy, hr);
    hg.addColorStop(0,'#d9f99d'); hg.addColorStop(.45,'#84cc16'); hg.addColorStop(1,'#22c55e');
    ctx.fillStyle = hg; ctx.strokeStyle='#14532d'; ctx.lineWidth=Math.max(2,cell*.05);
    ctx.beginPath(); ctx.arc(hx,hy,hr,0,Math.PI*2); ctx.fill(); ctx.stroke();

    const d = direction, perp = {x:-d.y, y:d.x};
    // tongue flick
    if(!isOver && (now % 2800) < 340){
      const mx = hx + d.x*hr*.95, my = hy + d.y*hr*.95;
      ctx.strokeStyle='#f43f5e'; ctx.lineWidth=Math.max(2,cell*.055); ctx.lineCap='round';
      ctx.beginPath(); ctx.moveTo(mx,my); ctx.lineTo(mx+d.x*cell*.22, my+d.y*cell*.22);
      ctx.moveTo(mx+d.x*cell*.22, my+d.y*cell*.22); ctx.lineTo(mx+d.x*cell*.34+perp.x*cell*.09, my+d.y*cell*.34+perp.y*cell*.09);
      ctx.moveTo(mx+d.x*cell*.22, my+d.y*cell*.22); ctx.lineTo(mx+d.x*cell*.34-perp.x*cell*.09, my+d.y*cell*.34-perp.y*cell*.09);
      ctx.stroke();
    }
    const blink = (now % 3200) < 110 && !isOver;
    for(const side of [-1,1]){
      const ex = hx + d.x*cell*.13 + perp.x*cell*.19*side;
      const ey = hy + d.y*cell*.13 + perp.y*cell*.19*side;
      if(isOver){
        ctx.strokeStyle='#052e16'; ctx.lineWidth=Math.max(2,cell*.05); ctx.lineCap='round';
        const s=cell*.09;
        ctx.beginPath(); ctx.moveTo(ex-s,ey-s); ctx.lineTo(ex+s,ey+s); ctx.moveTo(ex+s,ey-s); ctx.lineTo(ex-s,ey+s); ctx.stroke();
      }else if(blink){
        ctx.strokeStyle='#052e16'; ctx.lineWidth=Math.max(2,cell*.05); ctx.lineCap='round';
        ctx.beginPath(); ctx.moveTo(ex-cell*.1,ey); ctx.lineTo(ex+cell*.1,ey); ctx.stroke();
      }else{
        ctx.fillStyle='#ffffff'; ctx.beginPath(); ctx.arc(ex,ey,cell*.135,0,Math.PI*2); ctx.fill();
        ctx.fillStyle='#052e16'; ctx.beginPath(); ctx.arc(ex+d.x*cell*.045, ey+d.y*cell*.045, cell*.068, 0, Math.PI*2); ctx.fill();
        ctx.fillStyle='rgba(255,255,255,.95)'; ctx.beginPath(); ctx.arc(ex+d.x*cell*.02-cell*.02, ey+d.y*cell*.02-cell*.025, cell*.022, 0, Math.PI*2); ctx.fill();
      }
    }
    ctx.restore();
  }
  // vignette
  const vg = ctx.createRadialGradient(W/2,H/2,W*.38,W/2,H/2,W*.75);
  vg.addColorStop(0,'rgba(0,0,0,0)'); vg.addColorStop(1,'rgba(0,0,0,.22)');
  ctx.fillStyle=vg; ctx.fillRect(0,0,W,H);
}

function frame(now){
  const dt = Math.min(.05, (now-lastFrame)/1000); lastFrame = now;
  if(!isPaused && !isOver){
    if(now - lastTick >= tickMs()){ stepGame(); lastTick = now; }
  }
  if(!isPaused) updateFx(dt);
  const interp = (!isPaused && !isOver) ? Math.min(1,(now-lastTick)/tickMs()) : 1;
  draw(now, interp);
  requestAnimationFrame(frame);
}

resizeCanvas();
resetGame(true);
requestAnimationFrame(frame);
</script>
</body>
</html>
Reasoning trace (10,765 tokens)
Implementing a single-file vanilla JS canvas Snake game with grid, input handling, scoring, polish, and restart logic.Adding responsive canvas sizing, neon UI overlays, touch controls, and demo-mode auto-play with manual takeover.Extending rendering with interpolated movement, particle effects, and UI overlays plus input handling.Adding demo-to-manual takeover, BFS AI, responsive canvas, audio, and overlay state handling.Defining the color palette, layout constraints, and game loop with interpolation and state transitions.Adding BFS pathfinding with a safe-move fallback and canvas rendering with interpolation and particles.Adding dt-based movement, overlay UI with backdrop blur, input queueing, and keyboard controls.Defining input handling, state transitions, audio cues, and responsive canvas sizing.Adding responsive layout rules and glassmorphism styling with overlay state handling.Implementing AI pathfinding with BFS fallback and flood-fill scoring plus particle and rendering effects.