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
42.6 s
Cost
$0.012
Output tokens
22,923
Spent thinking
14,162 tokens
Speed
560.5 tok/s
Console errors
0
First token after
1.7 s
First visible text after
32.1 s
Prompt tokens
510
Code size
25.7 KB
Finish reason
stop
Served by
Meta
Model id
meta/muse-ember
Reasoning sent
{"effort":"max"}
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 · 578 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>Snake — Classic Arcade</title>
<style>
  *{box-sizing:border-box}
  html,body{overflow-x:hidden}
  body{
    margin:0;min-height:100dvh;display:flex;justify-content:center;color:#e2e8f0;
    font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
    background:
      radial-gradient(700px 420px at 15% -5%, rgba(34,197,94,.20), transparent 60%),
      radial-gradient(700px 500px at 95% 105%, rgba(34,211,238,.16), transparent 60%),
      #060a13;
  }
  .app{width:100%;max-width:640px;padding:16px 14px 22px;display:flex;flex-direction:column;align-items:center;gap:13px}
  header{text-align:center}
  .pill{display:inline-flex;align-items:center;gap:7px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);
    padding:5px 12px;border-radius:999px;font-size:11px;font-weight:800;letter-spacing:.16em;text-transform:uppercase;color:#a7f3d0;margin-bottom:6px}
  .pill span{width:7px;height:7px;border-radius:50%;background:#4ade80;box-shadow:0 0 10px #4ade80;animation:pulse 1.4s infinite}
  @keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.8)}}
  h1{margin:0;font-size:clamp(36px,8vw,54px);line-height:.95;letter-spacing:.14em;font-weight:900;
    background:linear-gradient(90deg,#4ade80,#22d3ee);-webkit-background-clip:text;background-clip:text;color:transparent}
  .sub{margin:6px 0 0;color:#94a3b8;font-size:13px;letter-spacing:.04em}
  .hud{width:100%;display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px}
  .card{background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.09);border-radius:14px;padding:9px 10px 10px;
    display:flex;flex-direction:column;align-items:center;backdrop-filter:blur(8px)}
  .card .label{font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:#94a3b8;font-weight:800}
  .card .value{font-size:27px;font-weight:900;line-height:1.15;color:#fff;font-variant-numeric:tabular-nums}
  #scoreCard{border-color:rgba(74,222,128,.28);box-shadow:0 0 18px rgba(34,197,94,.10)}
  @keyframes pop{0%{transform:scale(1)}40%{transform:scale(1.12)}100%{transform:scale(1)}}
  .pop{animation:pop .25s ease}
  .board-wrap{position:relative;width:min(94vw,560px,calc(100dvh - 220px));min-width:260px;
    background:linear-gradient(160deg,rgba(255,255,255,.10),rgba(255,255,255,.03));
    border:1px solid rgba(255,255,255,.13);padding:10px;border-radius:20px;
    box-shadow:0 22px 55px rgba(0,0,0,.6),0 0 42px rgba(34,197,94,.13)}
  @keyframes shake{0%,100%{transform:translateX(0)}20%{transform:translateX(-7px)}40%{transform:translateX(7px)}60%{transform:translateX(-5px)}80%{transform:translateX(5px)}}
  .shake{animation:shake .4s ease}
  canvas{display:block;width:100%;aspect-ratio:1/1;border-radius:12px;background:#0b1526;touch-action:none}
  #demoBadge{position:absolute;top:18px;left:50%;transform:translateX(-50%);z-index:3;white-space:nowrap;
    background:rgba(3,7,18,.82);border:1px solid rgba(255,255,255,.16);padding:6px 13px;border-radius:999px;
    font-size:12px;font-weight:800;letter-spacing:.03em;backdrop-filter:blur(6px);display:flex;gap:8px;align-items:center;cursor:pointer}
  #demoBadge i{width:8px;height:8px;border-radius:50%;background:#facc15;box-shadow:0 0 10px #facc15;animation:pulse 1.2s infinite}
  .overlay{position:absolute;inset:10px;border-radius:12px;display:flex;align-items:center;justify-content:center;
    background:rgba(4,8,18,.76);backdrop-filter:blur(7px);z-index:4;padding:16px}
  .hidden{display:none !important}
  .panel{text-align:center;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.13);border-radius:18px;
    padding:22px 24px;max-width:360px;width:100%;animation:panelIn .35s cubic-bezier(.2,.9,.3,1.15)}
  @keyframes panelIn{from{opacity:0;transform:scale(.88) translateY(10px)}to{opacity:1;transform:scale(1) translateY(0)}}
  .panel h2{margin:0 0 5px;font-size:31px;letter-spacing:.09em;font-weight:900}
  #gameOverOverlay h2{color:#fda4af}
  #pauseOverlay h2{color:#fde68a}
  .panel p{margin:4px 0 14px;color:#cbd5e1;font-size:14px;line-height:1.5}
  .big-score{font-size:52px;font-weight:900;line-height:1;color:#fff;margin:6px 0 2px}
  .stats{display:flex;gap:10px;justify-content:center;margin:12px 0 16px;flex-wrap:wrap}
  .stat{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.10);border-radius:12px;padding:7px 13px;min-width:82px}
  .stat b{display:block;font-size:20px;color:#fff}
  .stat span{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:#94a3b8;font-weight:800}
  .new-best{display:inline-block;background:linear-gradient(90deg,#facc15,#f59e0b);color:#422006;font-weight:900;
    font-size:11px;letter-spacing:.1em;padding:5px 12px;border-radius:999px;margin-bottom:6px;animation:pop .5s ease infinite alternate}
  .btn{background:linear-gradient(90deg,#22c55e,#10b981);color:#fff;border:none;padding:11px 24px;border-radius:999px;
    font-weight:900;font-size:15px;cursor:pointer;box-shadow:0 8px 22px rgba(34,197,94,.38);transition:transform .15s,box-shadow .15s;font-family:inherit}
  .btn:hover{transform:translateY(-1px) scale(1.04);box-shadow:0 12px 26px rgba(34,197,94,.45)}
  .btn:active{transform:scale(.97)}
  .btn.ghost{background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.18);box-shadow:none;margin-left:8px}
  kbd{background:rgba(255,255,255,.11);border:1px solid rgba(255,255,255,.20);border-bottom-width:2px;padding:2px 7px;border-radius:6px;
    font-size:12px;font-weight:800;font-family:inherit;color:#fff}
  .hint{margin:0;color:#94a3b8;font-size:13px;text-align:center;line-height:1.7}
  .dpad{display:none;gap:6px;justify-content:center}
  .dpad button{width:58px;height:58px;border-radius:14px;border:1px solid rgba(255,255,255,.16);background:rgba(255,255,255,.07);
    color:#fff;font-size:22px;cursor:pointer;backdrop-filter:blur(6px)}
  .dpad button:active{background:rgba(74,222,128,.25)}
  @media(max-width:640px){
    .dpad{display:grid;grid-template-columns:repeat(3,58px);grid-template-rows:repeat(2,58px)}
    .card .value{font-size:23px}
    .app{padding-top:12px;gap:11px}
  }
</style>
</head>
<body>
<div class="app">
  <header>
    <div class="pill"><span></span> Classic Arcade</div>
    <h1>SNAKE</h1>
    <p class="sub">Eat the apples. Grow long. Don't hit the walls.</p>
  </header>

  <div class="hud">
    <div class="card" id="scoreCard"><span class="label">Score</span><span class="value" id="scoreVal">0</span></div>
    <div class="card"><span class="label">Best</span><span class="value" id="bestVal">0</span></div>
    <div class="card"><span class="label">Length</span><span class="value" id="lenVal">4</span></div>
  </div>

  <div class="board-wrap" id="boardWrap">
    <canvas id="game" width="540" height="540" aria-label="Snake game board"></canvas>

    <div id="demoBadge"><i></i> DEMO PLAYING — Click or press a key to start</div>

    <div id="pauseOverlay" class="overlay hidden">
      <div class="panel">
        <h2>⏸ PAUSED</h2>
        <p>Take a breath. The snake is waiting.</p>
        <button class="btn" id="resumeBtn">Resume</button>
        <button class="btn ghost" id="pauseRestartBtn">Restart</button>
        <p style="margin:12px 0 0;font-size:12px;color:#94a3b8">Press <kbd>P</kbd> to resume</p>
      </div>
    </div>

    <div id="gameOverOverlay" class="overlay hidden">
      <div class="panel">
        <h2>GAME OVER</h2>
        <div id="newBestBadge" class="new-best hidden">★ NEW BEST! ★</div>
        <div class="big-score" id="finalScore">0</div>
        <p style="margin:0;color:#94a3b8;font-size:12px;letter-spacing:.14em;text-transform:uppercase;font-weight:800">points</p>
        <div class="stats">
          <div class="stat"><b id="finalBest">0</b><span>Best</span></div>
          <div class="stat"><b id="finalLength">4</b><span>Length</span></div>
          <div class="stat"><b id="finalFood">0</b><span>Apples</span></div>
        </div>
        <button class="btn" id="restartBtn">Play Again</button>
        <p style="margin:12px 0 0;font-size:12px;color:#94a3b8">Press <kbd>Space</kbd> or <kbd>Enter</kbd> to restart</p>
      </div>
    </div>
  </div>

  <p class="hint"><kbd>↑</kbd> <kbd>↓</kbd> <kbd>←</kbd> <kbd>→</kbd> or <kbd>W A S D</kbd> to steer &nbsp;•&nbsp; <kbd>P</kbd> pause &nbsp;•&nbsp; <kbd>Space</kbd> restart<br><span style="font-size:12px">Swipe on mobile • Eat an apple = +10 pts &amp; +1 length</span></p>

  <div class="dpad" aria-label="On-screen controls">
    <span></span><button data-dir="0,-1" aria-label="Up">↑</button><span></span>
    <button data-dir="-1,0" aria-label="Left">←</button><button data-dir="0,1" aria-label="Down">↓</button><button data-dir="1,0" aria-label="Right">→</button>
  </div>
</div>

<script>
(()=>{ 'use strict';
const GRID = 24;
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const boardWrap = document.getElementById('boardWrap');
const scoreVal = document.getElementById('scoreVal');
const bestVal = document.getElementById('bestVal');
const lenVal = document.getElementById('lenVal');
const scoreCard = document.getElementById('scoreCard');
const demoBadge = document.getElementById('demoBadge');
const pauseOverlay = document.getElementById('pauseOverlay');
const gameOverOverlay = document.getElementById('gameOverOverlay');
const finalScore = document.getElementById('finalScore');
const finalBest = document.getElementById('finalBest');
const finalLength = document.getElementById('finalLength');
const finalFood = document.getElementById('finalFood');
const newBestBadge = document.getElementById('newBestBadge');

let boardSize = 540, cell = boardSize / GRID;
let snake = [], prevSnake = [], direction = {x:1,y:0}, dirQueue = [], food = {x:18,y:12};
let score = 0, bestScore = 0, foodsEaten = 0, isNewBest = false;
let particles = [], floaters = [];
let mode = 'demo'; // demo | playing | gameover
let isPaused = false, pausedAlpha = 0;
let tickInterval = 150, lastTick = performance.now(), lastFrame = performance.now();
let audioCtx = null;

function resize(){
  const r = canvas.getBoundingClientRect();
  if(r.width < 10) return;
  boardSize = r.width;
  const dpr = Math.min(window.devicePixelRatio || 1, 2);
  canvas.width = Math.round(boardSize * dpr);
  canvas.height = Math.round(boardSize * dpr);
  ctx.setTransform(dpr,0,0,dpr,0,0);
  cell = boardSize / GRID;
}
window.addEventListener('resize', resize);
window.addEventListener('orientationchange', ()=> setTimeout(resize, 100));

function baseInterval(){ return Math.max(68, 150 - foodsEaten * 5); }

function updateHUD(pop){
  scoreVal.textContent = score;
  bestVal.textContent = bestScore;
  lenVal.textContent = snake.length;
  if(pop){ scoreCard.classList.remove('pop'); void scoreCard.offsetWidth; scoreCard.classList.add('pop'); }
}

function spawnFood(){
  const occ = new Set(snake.map(s=>s.y*GRID+s.x));
  const empty = [];
  for(let y=0;y<GRID;y++) for(let x=0;x<GRID;x++) if(!occ.has(y*GRID+x)) empty.push({x,y});
  if(!empty.length) return;
  food = empty[Math.floor(Math.random()*empty.length)];
}

function resetGame(demo){
  mode = demo ? 'demo' : 'playing';
  isPaused = false;
  snake = [{x:12,y:12},{x:11,y:12},{x:10,y:12},{x:9,y:12}];
  prevSnake = snake.map(s=>({x:s.x,y:s.y}));
  direction = {x:1,y:0};
  dirQueue = [];
  score = 0; foodsEaten = 0; isNewBest = false;
  particles = []; floaters = [];
  tickInterval = 150;
  spawnFood();
  lastTick = performance.now(); lastFrame = performance.now();
  demoBadge.classList.toggle('hidden', !demo);
  pauseOverlay.classList.add('hidden');
  gameOverOverlay.classList.add('hidden');
  updateHUD(false);
}

function enqueueDir(d){
  const last = dirQueue.length ? dirQueue[dirQueue.length-1] : direction;
  if(d.x === -last.x && d.y === -last.y) return; // never reverse
  if(d.x === last.x && d.y === last.y) return;
  if(dirQueue.length < 3) dirQueue.push({x:d.x,y:d.y});
}

/* ---------- Demo AI (BFS to food) ---------- */
function aiDirection(){
  if(!snake.length) return null;
  const head = snake[0];
  const dirs = [{x:1,y:0},{x:-1,y:0},{x:0,y:1},{x:0,y:-1}];
  const blocked = new Set();
  for(let i=0;i<snake.length-1;i++) blocked.add(snake[i].y*GRID+snake[i].x); // tail moves away
  const startKey = head.y*GRID+head.x, foodKey = food.y*GRID+food.x;
  const visited = new Set([startKey]); const prev = new Map(); const q = [head]; let qi = 0;
  while(qi < q.length){
    const cur = q[qi++]; const curKey = cur.y*GRID+cur.x;
    if(curKey === foodKey) break;
    for(const d of dirs){
      const nx = cur.x+d.x, ny = cur.y+d.y;
      if(nx<0||nx>=GRID||ny<0||ny>=GRID) continue;
      const nk = ny*GRID+nx;
      if(visited.has(nk) || blocked.has(nk)) continue;
      visited.add(nk); prev.set(nk, curKey); q.push({x:nx,y:ny});
    }
  }
  if(visited.has(foodKey)){
    let curKey = foodKey;
    while(prev.get(curKey) !== startKey){
      curKey = prev.get(curKey);
      if(curKey === undefined) break;
    }
    if(curKey !== undefined){
      return {x:(curKey%GRID)-head.x, y:Math.floor(curKey/GRID)-head.y};
    }
  }
  let bestDir = null, bestSpace = -1;
  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||nx>=GRID||ny<0||ny>=GRID) continue;
    if(blocked.has(ny*GRID+nx)) continue;
    let count = 0; const seen = new Set([ny*GRID+nx]); const qq = [{x:nx,y:ny}]; let qqi = 0;
    while(qqi < qq.length && count < 140){
      const c = qq[qqi++]; count++;
      for(const dd of dirs){
        const ax = c.x+dd.x, ay = c.y+dd.y;
        if(ax<0||ax>=GRID||ay<0||ay>=GRID) continue;
        const ak = ay*GRID+ax;
        if(seen.has(ak) || blocked.has(ak)) continue;
        seen.add(ak); qq.push({x:ax,y:ay});
      }
    }
    if(count > bestSpace){ bestSpace = count; bestDir = d; }
  }
  return bestDir;
}

/* ---------- Effects ---------- */
function burst(gx, gy){
  const cx = (gx+0.5)*cell, cy = (gy+0.5)*cell;
  const colors = ['#fda4af','#fb7185','#4ade80','#facc15','#ffffff','#22d3ee'];
  for(let i=0;i<20;i++){
    const a = Math.random()*Math.PI*2, sp = 60 + Math.random()*220;
    const max = 420 + Math.random()*380;
    particles.push({x:cx,y:cy,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp-50,life:max,maxLife:max,
      size:2+Math.random()*3.6,color:colors[Math.floor(Math.random()*colors.length)]});
  }
}
function updateFx(dt){
  for(let i=particles.length-1;i>=0;i--){
    const p = particles[i]; p.life -= dt*1000;
    if(p.life<=0){ particles.splice(i,1); continue; }
    p.x += p.vx*dt; p.y += p.vy*dt; p.vy += 380*dt; p.vx *= 0.985; p.vy *= 0.99;
  }
  for(let i=floaters.length-1;i>=0;i--){
    const f = floaters[i]; f.life -= dt*1.15;
    if(f.life<=0) floaters.splice(i,1);
  }
}

/* ---------- Audio ---------- */
function ensureAudio(){
  if(audioCtx){ if(audioCtx.state==='suspended') audioCtx.resume(); return; }
  try{ audioCtx = new (window.AudioContext||window.webkitAudioContext)(); }catch(e){}
}
function tone(f1,f2,dur,type,vol){
  if(!audioCtx) return;
  try{
    const t = audioCtx.currentTime, o = audioCtx.createOscillator(), g = audioCtx.createGain();
    o.type = type; o.frequency.setValueAtTime(f1,t);
    o.frequency.exponentialRampToValueAtTime(Math.max(30,f2), t+dur);
    g.gain.setValueAtTime(vol,t); g.gain.exponentialRampToValueAtTime(0.001, t+dur);
    o.connect(g); g.connect(audioCtx.destination); o.start(t); o.stop(t+dur+0.03);
  }catch(e){}
}
const playEat = ()=> tone(620, 940, 0.13, 'sine', 0.07);
const playDie = ()=> tone(320, 55, 0.5, 'sawtooth', 0.09);

/* ---------- Core step ---------- */
function doGameOver(){
  if(mode === 'demo'){ resetGame(true); return; }
  mode = 'gameover';
  playDie();
  boardWrap.classList.remove('shake'); void boardWrap.offsetWidth; boardWrap.classList.add('shake');
  finalScore.textContent = score;
  finalBest.textContent = bestScore;
  finalLength.textContent = snake.length;
  finalFood.textContent = foodsEaten;
  newBestBadge.classList.toggle('hidden', !isNewBest);
  gameOverOverlay.classList.remove('hidden');
}

function step(){
  prevSnake = snake.map(s=>({x:s.x,y:s.y}));
  if(mode === 'demo'){
    const ai = aiDirection();
    if(ai) direction = ai;
  } else if(dirQueue.length){
    direction = dirQueue.shift();
  }
  const head = {x: snake[0].x + direction.x, y: snake[0].y + direction.y};
  if(head.x < 0 || head.x >= GRID || head.y < 0 || head.y >= GRID){ doGameOver(); return; }
  const willEat = head.x === food.x && head.y === food.y;
  const body = willEat ? snake : snake.slice(0,-1);
  if(body.some(s=>s.x===head.x && s.y===head.y)){ doGameOver(); return; }

  snake.unshift(head);
  if(willEat){
    score += 10; foodsEaten++; tickInterval = baseInterval();
    if(score > bestScore){ bestScore = score; isNewBest = true; }
    burst(food.x, food.y);
    floaters.push({x:food.x, y:food.y, text:'+10', life:1});
    playEat();
    updateHUD(true);
    spawnFood();
  } else {
    snake.pop();
  }
}

/* ---------- Drawing ---------- */
function drawFood(now){
  const cx = (food.x+0.5)*cell, cy = (food.y+0.5)*cell;
  const pulse = 1 + Math.sin(now/180)*0.11;
  const r = cell*0.34*pulse;
  // glow
  const g = ctx.createRadialGradient(cx,cy,r*0.4,cx,cy,r*2.4);
  g.addColorStop(0,'rgba(244,63,94,.40)'); g.addColorStop(1,'rgba(244,63,94,0)');
  ctx.fillStyle = g; ctx.beginPath(); ctx.arc(cx,cy,r*2.4,0,Math.PI*2); ctx.fill();
  // shadow
  ctx.fillStyle = 'rgba(0,0,0,.30)';
  ctx.beginPath(); ctx.ellipse(cx, cy + r*0.95, r*0.85, r*0.28, 0, 0, Math.PI*2); ctx.fill();
  // body
  const bg = ctx.createRadialGradient(cx-r*0.35,cy-r*0.4,r*0.2,cx,cy,r);
  bg.addColorStop(0,'#fda4af'); bg.addColorStop(0.45,'#f43f5e'); bg.addColorStop(1,'#be123c');
  ctx.fillStyle = bg; ctx.beginPath(); ctx.arc(cx,cy,r,0,Math.PI*2); ctx.fill();
  // leaf
  ctx.fillStyle = '#4ade80';
  ctx.beginPath(); ctx.ellipse(cx + r*0.28, cy - r*0.95, r*0.42, r*0.20, -0.5, 0, Math.PI*2); ctx.fill();
  ctx.strokeStyle = '#166534'; ctx.lineWidth = Math.max(1, cell*0.05);
  ctx.beginPath(); ctx.moveTo(cx, cy - r*0.75); ctx.quadraticCurveTo(cx + r*0.15, cy - r*1.25, cx + r*0.35, cy - r*1.3); ctx.stroke();
  // shine
  ctx.fillStyle = 'rgba(255,255,255,.85)';
  ctx.beginPath(); ctx.arc(cx - r*0.32, cy - r*0.36, r*0.20, 0, Math.PI*2); ctx.fill();
}

function draw(now){
  if(boardSize < 10) return;
  ctx.clearRect(0,0,boardSize,boardSize);
  // checkerboard
  for(let y=0;y<GRID;y++) for(let x=0;x<GRID;x++){
    ctx.fillStyle = ((x+y)%2===0) ? '#0e1a30' : '#0a1424';
    ctx.fillRect(x*cell, y*cell, cell+0.5, cell+0.5);
  }
  // subtle inner vignette
  const vg = ctx.createRadialGradient(boardSize/2,boardSize/2,boardSize*0.35,boardSize/2,boardSize/2,boardSize*0.75);
  vg.addColorStop(0,'rgba(0,0,0,0)'); vg.addColorStop(1,'rgba(0,0,0,.28)');
  ctx.fillStyle = vg; ctx.fillRect(0,0,boardSize,boardSize);

  drawFood(now);

  // smooth interpolation
  let alpha = 1;
  if(isPaused) alpha = pausedAlpha;
  else if(mode !== 'gameover') alpha = Math.min(1, Math.max(0, (now - lastTick)/tickInterval));

  const pts = snake.map((seg,i)=>{
    const p = prevSnake[i] || seg;
    const ix = p.x + (seg.x - p.x)*alpha;
    const iy = p.y + (seg.y - p.y)*alpha;
    return {x:(ix+0.5)*cell, y:(iy+0.5)*cell};
  });
  if(pts.length < 1) return;

  // body as thick smooth path tail -> head
  if(pts.length >= 2){
    const headPt = pts[0], tailPt = pts[pts.length-1];
    const grad = ctx.createLinearGradient(headPt.x,headPt.y,tailPt.x,tailPt.y);
    grad.addColorStop(0,'#4ade80'); grad.addColorStop(0.5,'#22c55e'); grad.addColorStop(1,'#14b8a6');
    ctx.lineCap = 'round'; ctx.lineJoin = 'round';
    ctx.shadowColor = 'rgba(74,222,128,.50)'; ctx.shadowBlur = 14;
    ctx.strokeStyle = grad; ctx.lineWidth = cell*0.66;
    ctx.beginPath();
    ctx.moveTo(tailPt.x, tailPt.y);
    for(let i=pts.length-2;i>=0;i--) ctx.lineTo(pts[i].x, pts[i].y);
    ctx.stroke();
    ctx.shadowBlur = 0;
    // glossy centre stripe
    ctx.strokeStyle = 'rgba(255,255,255,.16)'; ctx.lineWidth = cell*0.18;
    ctx.beginPath();
    ctx.moveTo(tailPt.x, tailPt.y);
    for(let i=pts.length-2;i>=0;i--) ctx.lineTo(pts[i].x, pts[i].y);
    ctx.stroke();
  }

  // head
  const hp = pts[0];
  const hr = cell*0.50;
  const hg = ctx.createRadialGradient(hp.x-hr*0.3,hp.y-hr*0.35,hr*0.2,hp.x,hp.y,hr);
  hg.addColorStop(0,'#bbf7d0'); hg.addColorStop(0.5,'#4ade80'); hg.addColorStop(1,'#16a34a');
  ctx.shadowColor = 'rgba(74,222,128,.55)'; ctx.shadowBlur = 16;
  ctx.fillStyle = hg; ctx.beginPath(); ctx.arc(hp.x,hp.y,hr,0,Math.PI*2); ctx.fill();
  ctx.shadowBlur = 0;
  ctx.strokeStyle = 'rgba(6,78,59,.35)'; ctx.lineWidth = Math.max(1, cell*0.05); ctx.stroke();

  // tongue flick
  if(now % 2200 < 260){
    const tx = hp.x + direction.x*hr, ty = hp.y + direction.y*hr;
    ctx.strokeStyle = '#f43f5e'; ctx.lineWidth = Math.max(1.5, cell*0.07); ctx.lineCap = 'round';
    ctx.beginPath(); ctx.moveTo(tx,ty);
    const ex = tx + direction.x*cell*0.32, ey = ty + direction.y*cell*0.32;
    ctx.lineTo(ex,ey);
    ctx.moveTo(ex,ey); ctx.lineTo(ex + (direction.x*0.6 - direction.y*0.4)*cell*0.12, ey + (direction.y*0.6 + direction.x*0.4)*cell*0.12);
    ctx.moveTo(ex,ey); ctx.lineTo(ex + (direction.x*0.6 + direction.y*0.4)*cell*0.12, ey + (direction.y*0.6 - direction.x*0.4)*cell*0.12);
    ctx.stroke();
  }

  // eyes facing direction
  const perp = {x:-direction.y, y:direction.x};
  for(const side of [-1,1]){
    const ex = hp.x + direction.x*cell*0.15 + perp.x*cell*0.20*side;
    const ey = hp.y + direction.y*cell*0.15 + perp.y*cell*0.20*side;
    ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(ex,ey,cell*0.145,0,Math.PI*2); ctx.fill();
    ctx.strokeStyle = 'rgba(15,23,42,.25)'; ctx.lineWidth = 1; ctx.stroke();
    const px = ex + direction.x*cell*0.045, py = ey + direction.y*cell*0.045;
    ctx.fillStyle = '#0f172a'; ctx.beginPath(); ctx.arc(px,py,cell*0.078,0,Math.PI*2); ctx.fill();
    ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(px-cell*0.025,py-cell*0.025,cell*0.028,0,Math.PI*2); ctx.fill();
  }

  // particles
  for(const p of particles){
    ctx.globalAlpha = Math.max(0, p.life/p.maxLife);
    ctx.fillStyle = p.color;
    ctx.beginPath(); ctx.arc(p.x,p.y,p.size,0,Math.PI*2); ctx.fill();
  }
  ctx.globalAlpha = 1;

  // floaters
  ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
  ctx.font = `900 ${Math.round(cell*0.62)}px system-ui, sans-serif`;
  for(const f of floaters){
    const fx = (f.x+0.5)*cell, fy = (f.y+0.5)*cell - (1-f.life)*cell*1.1;
    ctx.globalAlpha = Math.max(0, f.life);
    ctx.strokeStyle = 'rgba(6,20,12,.85)'; ctx.lineWidth = cell*0.14;
    ctx.strokeText(f.text, fx, fy);
    ctx.fillStyle = '#a7f3d0'; ctx.fillText(f.text, fx, fy);
  }
  ctx.globalAlpha = 1;
}

/* ---------- Loop ---------- */
function loop(now){
  requestAnimationFrame(loop);
  const dt = Math.min(50, now - lastFrame); lastFrame = now;
  if(!isPaused && mode !== 'gameover'){
    let guard = 0;
    while(now - lastTick >= tickInterval && guard < 4){
      step(); lastTick += tickInterval; guard++;
      if(mode === 'gameover') break;
    }
    if(now - lastTick >= tickInterval) lastTick = now;
    updateFx(dt/1000);
  }
  draw(now);
}

/* ---------- Control ---------- */
function startPlaying(){ ensureAudio(); resetGame(false); }
function togglePause(){
  if(mode === 'gameover') return;
  if(!isPaused){
    pausedAlpha = Math.min(1, Math.max(0, (performance.now()-lastTick)/tickInterval));
    isPaused = true; pauseOverlay.classList.remove('hidden');
  } else {
    isPaused = false; lastTick = performance.now() - pausedAlpha*tickInterval;
    pauseOverlay.classList.add('hidden');
  }
}

document.addEventListener('keydown', (e)=>{
  const k = e.key, lower = k.toLowerCase();
  if(['arrowup','arrowdown','arrowleft','arrowright',' '].includes(lower) || k === ' ') e.preventDefault();
  ensureAudio();

  if(lower === 'p'){ if(mode !== 'gameover') togglePause(); return; }
  if(k === ' ' || lower === 'enter'){
    if(isPaused){ togglePause(); return; }
    startPlaying(); return;
  }
  if(lower === 'r'){ startPlaying(); return; }

  const map = {arrowup:{x:0,y:-1}, w:{x:0,y:-1}, arrowdown:{x:0,y:1}, s:{x:0,y:1},
               arrowleft:{x:-1,y:0}, a:{x:-1,y:0}, arrowright:{x:1,y:0}, d:{x:1,y:0}};
  const d = map[lower];
  if(!d) return;
  if(mode === 'demo'){ startPlaying(); enqueueDir(d); return; }
  if(mode === 'gameover'){ startPlaying(); enqueueDir(d); return; }
  if(isPaused) return;
  enqueueDir(d);
});

boardWrap.addEventListener('click', ()=>{ ensureAudio(); if(mode==='demo') startPlaying(); });
document.getElementById('restartBtn').addEventListener('click', startPlaying);
document.getElementById('resumeBtn').addEventListener('click', ()=>{ ensureAudio(); togglePause(); });
document.getElementById('pauseRestartBtn').addEventListener('click', startPlaying);

/* Touch swipe */
let touchStart = null;
boardWrap.addEventListener('touchstart', e=>{
  ensureAudio();
  if(e.touches.length) touchStart = {x:e.touches[0].clientX, y:e.touches[0].clientY};
},{passive:true});
boardWrap.addEventListener('touchend', e=>{
  if(!touchStart) return;
  const t = e.changedTouches[0];
  if(!t){ touchStart = null; return; }
  const dx = t.clientX - touchStart.x, dy = t.clientY - touchStart.y;
  touchStart = null;
  if(Math.max(Math.abs(dx),Math.abs(dy)) < 24) return;
  const d = Math.abs(dx) > Math.abs(dy) ? {x:Math.sign(dx),y:0} : {x:0,y:Math.sign(dy)};
  if(mode === 'demo'){ startPlaying(); enqueueDir(d); }
  else if(mode === 'playing' && !isPaused) enqueueDir(d);
},{passive:true});

/* D-pad */
document.querySelectorAll('[data-dir]').forEach(btn=>{
  btn.addEventListener('click', ()=>{
    ensureAudio();
    const [x,y] = btn.dataset.dir.split(',').map(Number);
    const d = {x,y};
    if(mode === 'demo'){ startPlaying(); enqueueDir(d); }
    else if(mode === 'gameover'){ startPlaying(); enqueueDir(d); }
    else if(mode === 'playing' && !isPaused) enqueueDir(d);
  });
});

/* ---------- Init ---------- */
resize();
resetGame(true);
requestAnimationFrame(loop);
setTimeout(resize, 50);
})();
</script>
</body>
</html>
Reasoning trace (14,162 tokens)
Implementing a single-file vanilla JS canvas Snake game with grid, input queue, scoring, collision, and visual polish.Adding responsive canvas sizing, demo mode with manual takeover, and interpolated movement with input queuing.Adding BFS pathfinding for demo AI, growth handling via prev state, and UI overlays with touch controls.Defining state machine transitions, input handling, and responsive canvas scaling for gameplay.Implementing input queue filtering, collision logic with tail exclusion, and interpolated snake rendering with tapered stroke.Adding canvas eye and food rendering, particle effects, overlays, and responsive UI controls.Implementing input handling, BFS AI, timing loop, and state transitions for gameplay.Extending rendering with touch swipe, HUD updates, CSS styling, and particle effects.Adding WebAudio sound effects and input queue validation with pause and reset logic.Extending input handling with resume, restart, and overlay button logic plus responsive layout constraints.