Models 44

Gemini 4 Argon

by Googlereasoning defaultClean
Open in new window

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

How it went

Total time
2 min 17 s
Cost
$0.113
Output tokens
11,156
Spent thinking
0 tokens
Speed
85 tok/s
Console errors
0
First token after
6.2 s
First visible text after
6.2 s
Prompt tokens
546
Code size
32.8 KB
Finish reason
stop
Served by
Google
Model id
google/gemini-4-argon
Reasoning sent
model default
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 · 1109 lines

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Polished Neon Snake</title>
  <style>
    :root {
      --bg-dark: #0b0f19;
      --bg-panel: #131b2e;
      --accent-green: #10b981;
      --accent-lime: #34d399;
      --accent-food: #f43f5e;
      --text-main: #f8fafc;
      --text-muted: #94a3b8;
      --border-glow: rgba(52, 211, 153, 0.25);
    }

    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      user-select: none;
      -webkit-user-select: none;
    }

    body {
      background: radial-gradient(circle at 50% 20%, #17233b 0%, var(--bg-dark) 80%);
      color: var(--text-main);
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      padding: 16px;
      overflow-x: hidden;
    }

    .game-wrapper {
      display: flex;
      flex-direction: column;
      align-items: center;
      width: 100%;
      max-width: 560px;
      gap: 14px;
    }

    /* Header & Scoreboard */
    .hud {
      display: flex;
      justify-content: space-between;
      align-items: center;
      width: 100%;
      background: rgba(19, 27, 46, 0.85);
      border: 1px solid rgba(255, 255, 255, 0.08);
      box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.5);
      border-radius: 14px;
      padding: 12px 20px;
      backdrop-filter: blur(8px);
    }

    .brand {
      display: flex;
      align-items: center;
      gap: 10px;
    }

    .brand-icon {
      width: 14px;
      height: 14px;
      border-radius: 4px;
      background: var(--accent-lime);
      box-shadow: 0 0 12px var(--accent-lime);
      position: relative;
    }

    .brand-title {
      font-weight: 800;
      font-size: 1.25rem;
      letter-spacing: 0.5px;
      background: linear-gradient(90deg, #fff, #a7f3d0);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
    }

    .scores {
      display: flex;
      gap: 20px;
    }

    .score-box {
      display: flex;
      flex-direction: column;
      align-items: flex-end;
    }

    .score-label {
      font-size: 0.7rem;
      text-transform: uppercase;
      letter-spacing: 1px;
      color: var(--text-muted);
      font-weight: 600;
    }

    .score-value {
      font-size: 1.35rem;
      font-weight: 800;
      font-variant-numeric: tabular-nums;
      color: #fff;
      text-shadow: 0 0 10px rgba(52, 211, 153, 0.3);
      transition: transform 0.15s ease;
    }

    .score-value.bump {
      transform: scale(1.25);
      color: var(--accent-lime);
    }

    /* Canvas Container & Overlays */
    .board-container {
      position: relative;
      width: 100%;
      aspect-ratio: 1 / 1;
      border-radius: 16px;
      padding: 6px;
      background: linear-gradient(135deg, rgba(52, 211, 153, 0.25), rgba(15, 23, 42, 0.9));
      box-shadow: 0 20px 50px rgba(0, 0, 0, 0.6), 0 0 25px var(--border-glow);
    }

    canvas {
      width: 100%;
      height: 100%;
      display: block;
      border-radius: 11px;
      background: #0d1322;
      cursor: pointer;
    }

    .overlay {
      position: absolute;
      inset: 6px;
      border-radius: 11px;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      text-align: center;
      padding: 24px;
      transition: opacity 0.25s ease, visibility 0.25s ease;
      pointer-events: none;
    }

    /* Demo Mode Banner */
    .overlay.demo-mode {
      justify-content: flex-end;
      padding-bottom: 28px;
      background: radial-gradient(circle at center, transparent 40%, rgba(11, 15, 25, 0.55) 100%);
    }

    .demo-banner {
      background: rgba(15, 23, 42, 0.88);
      border: 1px solid rgba(52, 211, 153, 0.45);
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6), 0 0 20px rgba(16, 185, 129, 0.25);
      padding: 14px 26px;
      border-radius: 999px;
      backdrop-filter: blur(8px);
      pointer-events: auto;
      cursor: pointer;
      animation: pulseBanner 2s infinite ease-in-out;
    }

    @keyframes pulseBanner {
      0%, 100% { transform: translateY(0) scale(1); border-color: rgba(52, 211, 153, 0.45); }
      50% { transform: translateY(-3px) scale(1.02); border-color: rgba(52, 211, 153, 0.9); }
    }

    .demo-badge {
      display: inline-block;
      font-size: 0.68rem;
      font-weight: 800;
      text-transform: uppercase;
      letter-spacing: 1.5px;
      color: var(--accent-lime);
      margin-bottom: 3px;
    }

    .demo-cta {
      font-size: 1.05rem;
      font-weight: 700;
      color: #fff;
    }

    /* Fullscreen Modal Overlays (Pause / Game Over) */
    .overlay.modal {
      background: rgba(11, 15, 25, 0.78);
      backdrop-filter: blur(6px);
      pointer-events: auto;
    }

    .overlay.hidden {
      opacity: 0;
      visibility: hidden;
      pointer-events: none;
    }

    .modal-card {
      background: rgba(19, 27, 46, 0.95);
      border: 1px solid rgba(255, 255, 255, 0.12);
      border-radius: 16px;
      padding: 28px 32px;
      max-width: 340px;
      width: 100%;
      box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6);
      transform: translateY(0);
      transition: transform 0.25s ease;
    }

    .overlay.hidden .modal-card {
      transform: translateY(12px);
    }

    .modal-title {
      font-size: 1.75rem;
      font-weight: 800;
      margin-bottom: 6px;
      color: #fff;
    }

    .modal-subtitle {
      font-size: 0.95rem;
      color: var(--text-muted);
      margin-bottom: 20px;
    }

    .modal-score-summary {
      display: flex;
      justify-content: space-around;
      background: rgba(11, 15, 25, 0.6);
      border-radius: 10px;
      padding: 12px;
      margin-bottom: 22px;
      border: 1px solid rgba(255, 255, 255, 0.06);
    }

    .btn-primary {
      background: linear-gradient(135deg, var(--accent-lime), var(--accent-green));
      color: #06281e;
      font-weight: 800;
      font-size: 0.95rem;
      border: none;
      border-radius: 10px;
      padding: 12px 24px;
      width: 100%;
      cursor: pointer;
      box-shadow: 0 6px 20px rgba(16, 185, 129, 0.35);
      transition: transform 0.12s ease, filter 0.12s ease;
    }

    .btn-primary:hover {
      filter: brightness(1.08);
      transform: translateY(-1px);
    }

    .btn-primary:active {
      transform: translateY(1px);
    }

    /* Footer Controls */
    .controls-footer {
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
      gap: 14px;
      font-size: 0.8rem;
      color: var(--text-muted);
    }

    .key-hint {
      display: flex;
      align-items: center;
      gap: 6px;
    }

    kbd {
      background: rgba(255, 255, 255, 0.08);
      border: 1px solid rgba(255, 255, 255, 0.15);
      border-bottom-width: 2px;
      border-radius: 5px;
      padding: 2px 6px;
      font-family: inherit;
      font-size: 0.72rem;
      font-weight: 700;
      color: #e2e8f0;
    }

    /* Touch D-Pad for smaller viewports */
    .dpad {
      display: none;
      grid-template-columns: repeat(3, 52px);
      grid-template-rows: repeat(2, 46px);
      gap: 8px;
      margin-top: 4px;
    }

    .dpad-btn {
      background: rgba(19, 27, 46, 0.9);
      border: 1px solid rgba(255, 255, 255, 0.12);
      border-radius: 10px;
      color: var(--text-main);
      font-size: 1.1rem;
      display: flex;
      align-items: center;
      justify-content: center;
      cursor: pointer;
      active-background: var(--accent-green);
    }

    .dpad-btn:active {
      background: rgba(52, 211, 153, 0.25);
      border-color: var(--accent-lime);
    }

    @media (max-width: 600px) {
      .game-wrapper {
        max-width: 420px;
        gap: 10px;
      }
      .hud {
        padding: 10px 14px;
      }
      .brand-title {
        font-size: 1.05rem;
      }
      .score-value {
        font-size: 1.15rem;
      }
      .dpad {
        display: grid;
      }
    }
  </style>
</head>
<body>

  <div class="game-wrapper">
    <!-- Top HUD -->
    <header class="hud">
      <div class="brand">
        <div class="brand-icon"></div>
        <div class="brand-title">NEON SNAKE</div>
      </div>
      <div class="scores">
        <div class="score-box">
          <span class="score-label">Score</span>
          <span class="score-value" id="scoreDisplay">0</span>
        </div>
        <div class="score-box">
          <span class="score-label">Best</span>
          <span class="score-value" id="bestDisplay">0</span>
        </div>
      </div>
    </header>

    <!-- Game Board -->
    <div class="board-container">
      <canvas id="gameCanvas" width="660" height="660"></canvas>

      <!-- Demo Mode Overlay -->
      <div id="demoOverlay" class="overlay demo-mode">
        <div class="demo-banner" id="startBanner">
          <div class="demo-badge">Auto-Pilot Demo</div>
          <div class="demo-cta">Click or press a key to start</div>
        </div>
      </div>

      <!-- Pause Overlay -->
      <div id="pauseOverlay" class="overlay modal hidden">
        <div class="modal-card">
          <div class="modal-title">Game Paused</div>
          <div class="modal-subtitle">Press <kbd>P</kbd> or click below to continue</div>
          <button class="btn-primary" id="resumeBtn">Resume Game</button>
        </div>
      </div>

      <!-- Game Over Overlay -->
      <div id="gameOverOverlay" class="overlay modal hidden">
        <div class="modal-card">
          <div class="modal-title">Game Over</div>
          <div class="modal-subtitle" id="deathReason">You crashed!</div>
          <div class="modal-score-summary">
            <div class="score-box" style="align-items: center;">
              <span class="score-label">Final Score</span>
              <span class="score-value" id="finalScore">0</span>
            </div>
            <div class="score-box" style="align-items: center;">
              <span class="score-label">Session Best</span>
              <span class="score-value" id="finalBest">0</span>
            </div>
          </div>
          <button class="btn-primary" id="restartBtn">Play Again (Space / Enter)</button>
        </div>
      </div>
    </div>

    <!-- Mobile / Touch D-Pad -->
    <div class="dpad" aria-label="Directional Controls">
      <div></div>
      <button class="dpad-btn" data-dir="UP" aria-label="Up">▲</button>
      <div></div>
      <button class="dpad-btn" data-dir="LEFT" aria-label="Left">◀</button>
      <button class="dpad-btn" data-dir="DOWN" aria-label="Down">▼</button>
      <button class="dpad-btn" data-dir="RIGHT" aria-label="Right">▶</button>
    </div>

    <!-- Keyboard Hints -->
    <footer class="controls-footer">
      <div class="key-hint"><kbd>▲</kbd><kbd>▼</kbd><kbd>◀</kbd><kbd>▶</kbd> or <kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> Move</div>
      <div class="key-hint"><kbd>P</kbd> Pause</div>
      <div class="key-hint"><kbd>Space</kbd> / <kbd>Enter</kbd> Restart</div>
    </footer>
  </div>

  <script>
    (() => {
      const canvas = document.getElementById('gameCanvas');
      const ctx = canvas.getContext('2d');

      // UI Elements
      const scoreDisplay = document.getElementById('scoreDisplay');
      const bestDisplay = document.getElementById('bestDisplay');
      const finalScore = document.getElementById('finalScore');
      const finalBest = document.getElementById('finalBest');
      const deathReason = document.getElementById('deathReason');

      const demoOverlay = document.getElementById('demoOverlay');
      const pauseOverlay = document.getElementById('pauseOverlay');
      const gameOverOverlay = document.getElementById('gameOverOverlay');
      const resumeBtn = document.getElementById('resumeBtn');
      const restartBtn = document.getElementById('restartBtn');

      // Grid & Speed Constants (22x22 grid -> 30px cells on 660x660 canvas)
      const GRID_SIZE = 22;
      const CELL_SIZE = canvas.width / GRID_SIZE;
      const BASE_STEP_MS = 105;
      const DEMO_STEP_MS = 85;

      // Game State
      let mode = 'DEMO'; // 'DEMO' | 'PLAYING' | 'PAUSED' | 'GAMEOVER'
      let score = 0;
      let bestScore = 0;

      let snake = [];
      let prevSnake = [];
      let direction = { x: 1, y: 0 };
      let inputQueue = [];
      let grewLastTick = false;

      let food = { x: 15, y: 10 };
      let particles = [];
      let floatingTexts = [];

      let lastFrameTime = performance.now();
      let stepAccumulator = 0;
      let shakeTime = 0;

      // Initialize / Reset Game
      function initGame(targetMode) {
        mode = targetMode;
        score = 0;
        updateScoreUI(false);

        const startX = 7;
        const startY = Math.floor(GRID_SIZE / 2);

        snake = [
          { x: startX,     y: startY },
          { x: startX - 1, y: startY },
          { x: startX - 2, y: startY },
          { x: startX - 3, y: startY },
          { x: startX - 4, y: startY }
        ];
        prevSnake = snake.map(seg => ({ ...seg }));
        direction = { x: 1, y: 0 };
        inputQueue = [];
        grewLastTick = false;
        stepAccumulator = 0;

        if (mode === 'DEMO') {
          // Place first food right ahead of the demo snake so action happens within seconds
          food = { x: startX + 4, y: startY };
          // Spawn a cheerful initial burst so first paint is lively
          spawnEatParticles((startX + 4) * CELL_SIZE + CELL_SIZE / 2, startY * CELL_SIZE + CELL_SIZE / 2, 12);
        } else {
          spawnFood();
        }

        updateOverlays();
      }

      function updateOverlays() {
        demoOverlay.classList.toggle('hidden', mode !== 'DEMO');
        pauseOverlay.classList.toggle('hidden', mode !== 'PAUSED');
        gameOverOverlay.classList.toggle('hidden', mode !== 'GAMEOVER');
      }

      function updateScoreUI(animate = true) {
        scoreDisplay.textContent = score;
        bestDisplay.textContent = bestScore;
        if (animate) {
          scoreDisplay.classList.remove('bump');
          void scoreDisplay.offsetWidth; // trigger reflow
          scoreDisplay.classList.add('bump');
          setTimeout(() => scoreDisplay.classList.remove('bump'), 160);
        }
      }

      // Spawn food guaranteed not on the snake
      function spawnFood() {
        const occupied = new Set(snake.map(s => `${s.x},${s.y}`));
        const available = [];
        for (let x = 0; x < GRID_SIZE; x++) {
          for (let y = 0; y < GRID_SIZE; y++) {
            if (!occupied.has(`${x},${y}`)) {
              available.push({ x, y });
            }
          }
        }
        if (available.length > 0) {
          const pick = available[Math.floor(Math.random() * available.length)];
          food = pick;
        }
      }

      // Queue player directional input preventing self-reversal & honoring rapid keys
      function queueDirection(newDir) {
        const lastScheduledDir = inputQueue.length > 0 ? inputQueue[inputQueue.length - 1] : direction;
        // Ignore same direction or 180-degree reversal
        if (newDir.x === lastScheduledDir.x && newDir.y === lastScheduledDir.y) return;
        if (newDir.x === -lastScheduledDir.x && newDir.y === -lastScheduledDir.y) return;

        // Buffer up to 3 rapid inputs
        if (inputQueue.length < 3) {
          inputQueue.push(newDir);
        }
      }

      // Simple smart AI for Demo Mode (BFS shortest path + safe fallback)
      function computeAIDirection() {
        const head = snake[0];
        const dirs = [
          { x: 1, y: 0 },
          { x: -1, y: 0 },
          { x: 0, y: 1 },
          { x: 0, y: -1 }
        ];

        // Treat all segments except the very tip of the tail as blocked
        const blocked = new Set();
        for (let i = 0; i < snake.length - 1; i++) {
          blocked.add(`${snake[i].x},${snake[i].y}`);
        }

        // BFS to find shortest path to food
        const queue = [{ x: head.x, y: head.y, firstMove: null }];
        const visited = new Set([`${head.x},${head.y}`]);

        while (queue.length > 0) {
          const curr = queue.shift();
          if (curr.x === food.x && curr.y === food.y && curr.firstMove) {
            return curr.firstMove;
          }

          for (const d of dirs) {
            // Cannot reverse immediately on first step
            if (!curr.firstMove && d.x === -direction.x && d.y === -direction.y) continue;

            const nx = curr.x + d.x;
            const ny = curr.y + d.y;
            const key = `${nx},${ny}`;

            if (nx >= 0 && nx < GRID_SIZE && ny >= 0 && ny < GRID_SIZE && !blocked.has(key) && !visited.has(key)) {
              visited.add(key);
              queue.push({ x: nx, y: ny, firstMove: curr.firstMove || d });
            }
          }
        }

        // Fallback: pick valid move with the most open neighbors
        let bestDir = direction;
        let maxSpace = -1;

        for (const d of dirs) {
          if (d.x === -direction.x && d.y === -direction.y) continue;
          const nx = head.x + d.x;
          const ny = head.y + d.y;
          if (nx >= 0 && nx < GRID_SIZE && ny >= 0 && ny < GRID_SIZE && !blocked.has(`${nx},${ny}`)) {
            // Count free adjacent cells
            let freeCount = 0;
            for (const sub of dirs) {
              const sx = nx + sub.x;
              const sy = ny + sub.y;
              if (sx >= 0 && sx < GRID_SIZE && sy >= 0 && sy < GRID_SIZE && !blocked.has(`${sx},${sy}`)) {
                freeCount++;
              }
            }
            if (freeCount > maxSpace) {
              maxSpace = freeCount;
              bestDir = d;
            }
          }
        }
        return bestDir;
      }

      // Particle Burst Effect
      function spawnEatParticles(px, py, count = 22) {
        const palette = ['#34d399', '#10b981', '#fb7185', '#fde047', '#ffffff'];
        for (let i = 0; i < count; i++) {
          const angle = (Math.PI * 2 * i) / count + (Math.random() - 0.5) * 0.4;
          const speed = 60 + Math.random() * 150;
          particles.push({
            x: px,
            y: py,
            vx: Math.cos(angle) * speed,
            vy: Math.sin(angle) * speed,
            radius: 2.5 + Math.random() * 3.5,
            color: palette[Math.floor(Math.random() * palette.length)],
            alpha: 1,
            life: 0,
            maxLife: 0.35 + Math.random() * 0.25
          });
        }
      }

      function spawnFloatingText(px, py, text) {
        floatingTexts.push({
          x: px,
          y: py,
          text,
          alpha: 1,
          life: 0,
          maxLife: 0.65
        });
      }

      // Advance one logical grid step
      function stepGame() {
        prevSnake = snake.map(seg => ({ ...seg }));

        if (mode === 'DEMO') {
          direction = computeAIDirection();
        } else if (inputQueue.length > 0) {
          direction = inputQueue.shift();
        }

        const head = snake[0];
        const nextHead = {
          x: head.x + direction.x,
          y: head.y + direction.y
        };

        // Check wall collision
        if (nextHead.x < 0 || nextHead.x >= GRID_SIZE || nextHead.y < 0 || nextHead.y >= GRID_SIZE) {
          handleCrash('You hit the wall!');
          return;
        }

        // Check self collision (note: if not growing, tail tip vacates its cell this tick)
        const willEat = (nextHead.x === food.x && nextHead.y === food.y);
        const checkLimit = willEat ? snake.length : snake.length - 1;
        for (let i = 0; i < checkLimit; i++) {
          if (snake[i].x === nextHead.x && snake[i].y === nextHead.y) {
            handleCrash('You ran into yourself!');
            return;
          }
        }

        // Move snake
        snake.unshift(nextHead);

        if (willEat) {
          grewLastTick = true;
          score += 10;
          if (score > bestScore) {
            bestScore = score;
          }
          updateScoreUI(true);

          const fx = food.x * CELL_SIZE + CELL_SIZE / 2;
          const fy = food.y * CELL_SIZE + CELL_SIZE / 2;
          spawnEatParticles(fx, fy, 24);
          spawnFloatingText(fx, fy, '+10');
          spawnFood();
        } else {
          grewLastTick = false;
          snake.pop();
        }
      }

      function handleCrash(reasonText) {
        const head = snake[0];
        const hx = head.x * CELL_SIZE + CELL_SIZE / 2;
        const hy = head.y * CELL_SIZE + CELL_SIZE / 2;
        spawnEatParticles(hx, hy, 30);
        shakeTime = 0.25;

        if (mode === 'DEMO') {
          // Seamlessly restart demo if AI ever traps itself
          initGame('DEMO');
          return;
        }

        mode = 'GAMEOVER';
        deathReason.textContent = reasonText;
        finalScore.textContent = score;
        finalBest.textContent = bestScore;
        updateOverlays();
      }

      // Smoothly interpolate snake segments between discrete grid steps
      function getInterpolatedSnake(alpha) {
        const pts = [];
        for (let i = 0; i < snake.length; i++) {
          const curr = snake[i];
          let prev;
          if (i === 0) {
            prev = prevSnake[0] || curr;
          } else if (grewLastTick && i === snake.length - 1) {
            // Newly grown tail segment stays anchored at the old tail tip
            prev = curr;
          } else {
            prev = prevSnake[i - 1] || curr;
          }

          pts.push({
            x: (prev.x + (curr.x - prev.x) * alpha) * CELL_SIZE + CELL_SIZE / 2,
            y: (prev.y + (curr.y - prev.y) * alpha) * CELL_SIZE + CELL_SIZE / 2
          });
        }
        return pts;
      }

      // Render Board, Food, Snake, Head/Eyes, and Particles
      function render(alpha, timestamp) {
        ctx.save();

        // Screen shake on collision
        if (shakeTime > 0) {
          const mag = shakeTime * 22;
          ctx.translate((Math.random() - 0.5) * mag, (Math.random() - 0.5) * mag);
        }

        // 1. Checkerboard Grid Background
        for (let y = 0; y < GRID_SIZE; y++) {
          for (let x = 0; x < GRID_SIZE; x++) {
            ctx.fillStyle = (x + y) % 2 === 0 ? '#0f172a' : '#131d35';
            ctx.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
          }
        }

        // Subtle grid intersections
        ctx.fillStyle = 'rgba(255, 255, 255, 0.03)';
        for (let y = 1; y < GRID_SIZE; y++) {
          for (let x = 1; x < GRID_SIZE; x++) {
            ctx.fillRect(x * CELL_SIZE - 1, y * CELL_SIZE - 1, 2, 2);
          }
        }

        // 2. Draw Food (Glowing Pulsing Apple)
        const pulse = 1 + Math.sin(timestamp * 0.008) * 0.1;
        const fx = food.x * CELL_SIZE + CELL_SIZE / 2;
        const fy = food.y * CELL_SIZE + CELL_SIZE / 2;
        const foodRadius = (CELL_SIZE * 0.38) * pulse;

        ctx.save();
        ctx.shadowColor = '#f43f5e';
        ctx.shadowBlur = 18;

        const foodGrad = ctx.createRadialGradient(
          fx - foodRadius * 0.3,
          fy - foodRadius * 0.3,
          foodRadius * 0.1,
          fx,
          fy,
          foodRadius
        );
        foodGrad.addColorStop(0, '#fda4af');
        foodGrad.addColorStop(0.4, '#f43f5e');
        foodGrad.addColorStop(1, '#be123c');

        ctx.fillStyle = foodGrad;
        ctx.beginPath();
        ctx.arc(fx, fy, foodRadius, 0, Math.PI * 2);
        ctx.fill();

        // Little green leaf on top of the apple
        ctx.shadowBlur = 0;
        ctx.fillStyle = '#4ade80';
        ctx.beginPath();
        ctx.ellipse(fx + 2, fy - foodRadius - 1, 4.5, 2.2, -Math.PI / 5, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();

        // 3. Draw Smooth Snake Body & Distinct Head
        const pts = getInterpolatedSnake(alpha);
        if (pts.length > 0) {
          ctx.save();
          ctx.shadowColor = 'rgba(16, 185, 129, 0.35)';
          ctx.shadowBlur = 12;

          // Draw connected body spine for seamless corners
          ctx.strokeStyle = '#10b981';
          ctx.lineCap = 'round';
          ctx.lineJoin = 'round';
          ctx.lineWidth = CELL_SIZE * 0.72;
          ctx.beginPath();
          ctx.moveTo(pts[0].x, pts[0].y);
          for (let i = 1; i < pts.length; i++) {
            ctx.lineTo(pts[i].x, pts[i].y);
          }
          ctx.stroke();

          // Draw individual tapered segments from tail to neck for organic scales feel
          for (let i = pts.length - 1; i >= 1; i--) {
            const p = pts[i];
            const t = i / pts.length; // 0 near head, 1 at tail
            const radius = (CELL_SIZE * 0.41) * (1 - t * 0.38);

            ctx.fillStyle = i % 2 === 0 ? '#10b981' : '#059669';
            ctx.beginPath();
            ctx.arc(p.x, p.y, radius, 0, Math.PI * 2);
            ctx.fill();
          }

          // 4. Draw Distinct Snake Head with Eyes & Tongue
          const headPt = pts[0];
          const neckPt = pts[1] || { x: headPt.x - direction.x, y: headPt.y - direction.y };
          const headAngle = Math.atan2(headPt.y - neckPt.y, headPt.x - neckPt.x);

          ctx.translate(headPt.x, headPt.y);
          ctx.rotate(headAngle);

          // Flickering forked tongue
          const tongueWave = Math.sin(timestamp * 0.025);
          if (tongueWave > -0.2) {
            const tongueLen = CELL_SIZE * 0.55 + tongueWave * 3;
            ctx.strokeStyle = '#fb7185';
            ctx.lineWidth = 2;
            ctx.beginPath();
            ctx.moveTo(CELL_SIZE * 0.3, 0);
            ctx.lineTo(tongueLen, 0);
            ctx.lineTo(tongueLen + 4, -3.5);
            ctx.moveTo(tongueLen, 0);
            ctx.lineTo(tongueLen + 4, 3.5);
            ctx.stroke();
          }

          // Distinct sculpted head shape
          const headGrad = ctx.createRadialGradient(-2, -3, 2, 0, 0, CELL_SIZE * 0.55);
          headGrad.addColorStop(0, '#6ee7b7');
          headGrad.addColorStop(0.55, '#34d399');
          headGrad.addColorStop(1, '#059669');

          ctx.fillStyle = headGrad;
          ctx.beginPath();
          ctx.ellipse(2, 0, CELL_SIZE * 0.48, CELL_SIZE * 0.43, 0, 0, Math.PI * 2);
          ctx.fill();

          // Subtle nostrils near snout
          ctx.fillStyle = '#064e3b';
          ctx.beginPath();
          ctx.arc(CELL_SIZE * 0.3, -3, 1.2, 0, Math.PI * 2);
          ctx.arc(CELL_SIZE * 0.3, 3, 1.2, 0, Math.PI * 2);
          ctx.fill();

          // Eyes facing movement direction
          const eyeForward = CELL_SIZE * 0.12;
          const eyeSpread = CELL_SIZE * 0.24;
          const eyeRadius = CELL_SIZE * 0.145;

          // Sclera (white of the eyes)
          ctx.shadowBlur = 0;
          ctx.fillStyle = '#ffffff';
          ctx.beginPath();
          ctx.arc(eyeForward, -eyeSpread, eyeRadius, 0, Math.PI * 2);
          ctx.arc(eyeForward, eyeSpread, eyeRadius, 0, Math.PI * 2);
          ctx.fill();

          // Pupils looking slightly ahead towards food
          const foodAngleWorld = Math.atan2(fy - headPt.y, fx - headPt.x);
          const relFoodAngle = foodAngleWorld - headAngle;
          const pupilShiftX = Math.cos(relFoodAngle) * 1.4;
          const pupilShiftY = Math.sin(relFoodAngle) * 1.4;

          ctx.fillStyle = '#0f172a';
          ctx.beginPath();
          ctx.arc(eyeForward + 1.2 + pupilShiftX, -eyeSpread + pupilShiftY, eyeRadius * 0.56, 0, Math.PI * 2);
          ctx.arc(eyeForward + 1.2 + pupilShiftX, eyeSpread + pupilShiftY, eyeRadius * 0.56, 0, Math.PI * 2);
          ctx.fill();

          // Specular eye highlight
          ctx.fillStyle = '#ffffff';
          ctx.beginPath();
          ctx.arc(eyeForward + 0.5, -eyeSpread - 1.2, eyeRadius * 0.22, 0, Math.PI * 2);
          ctx.arc(eyeForward + 0.5, eyeSpread - 1.2, eyeRadius * 0.22, 0, Math.PI * 2);
          ctx.fill();

          ctx.restore();
        }

        // 5. Render Particle Bursts
        for (const p of particles) {
          ctx.save();
          ctx.globalAlpha = Math.max(0, p.alpha);
          ctx.fillStyle = p.color;
          ctx.shadowColor = p.color;
          ctx.shadowBlur = 8;
          ctx.beginPath();
          ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
          ctx.fill();
          ctx.restore();
        }

        // 6. Render Floating "+10" Score Popups
        for (const ft of floatingTexts) {
          ctx.save();
          ctx.globalAlpha = Math.max(0, ft.alpha);
          ctx.font = '800 15px -apple-system, BlinkMacSystemFont, sans-serif';
          ctx.fillStyle = '#fde047';
          ctx.textAlign = 'center';
          ctx.shadowColor = 'rgba(0,0,0,0.8)';
          ctx.shadowBlur = 4;
          ctx.fillText(ft.text, ft.x, ft.y);
          ctx.restore();
        }

        ctx.restore();
      }

      // Main Animation Loop
      function loop(timestamp) {
        const dt = Math.min((timestamp - lastFrameTime) / 1000, 0.1);
        lastFrameTime = timestamp;

        if (shakeTime > 0) {
          shakeTime = Math.max(0, shakeTime - dt);
        }

        // Update particles
        for (let i = particles.length - 1; i >= 0; i--) {
          const p = particles[i];
          p.life += dt;
          if (p.life >= p.maxLife) {
            particles.splice(i, 1);
            continue;
          }
          p.x += p.vx * dt;
          p.y += p.vy * dt;
          p.vx *= 0.92;
          p.vy *= 0.92;
          p.alpha = 1 - p.life / p.maxLife;
        }

        // Update floating text
        for (let i = floatingTexts.length - 1; i >= 0; i--) {
          const ft = floatingTexts[i];
          ft.life += dt;
          if (ft.life >= ft.maxLife) {
            floatingTexts.splice(i, 1);
            continue;
          }
          ft.y -= 28 * dt;
          ft.alpha = 1 - ft.life / ft.maxLife;
        }

        // Step logic if playing or in demo
        const stepDuration = mode === 'DEMO' ? DEMO_STEP_MS : BASE_STEP_MS;
        if (mode === 'PLAYING' || mode === 'DEMO') {
          stepAccumulator += dt * 1000;
          while (stepAccumulator >= stepDuration) {
            stepAccumulator -= stepDuration;
            stepGame();
            if (mode === 'GAMEOVER') {
              stepAccumulator = 0;
              break;
            }
          }
        }

        const alpha = (mode === 'PLAYING' || mode === 'DEMO')
          ? Math.min(1, stepAccumulator / stepDuration)
          : 1;

        render(alpha, timestamp);
        requestAnimationFrame(loop);
      }

      // Input Handling
      const KEY_DIR_MAP = {
        ArrowUp:    { x: 0, y: -1 },
        KeyW:       { x: 0, y: -1 },
        ArrowDown:  { x: 0, y: 1 },
        KeyS:       { x: 0, y: 1 },
        ArrowLeft:  { x: -1, y: 0 },
        KeyA:       { x: -1, y: 0 },
        ArrowRight: { x: 1, y: 0 },
        KeyD:       { x: 1, y: 0 }
      };

      function startFromDemo(initialDir = null) {
        initGame('PLAYING');
        if (initialDir) {
          queueDirection(initialDir);
        }
      }

      window.addEventListener('keydown', (e) => {
        const mappedDir = KEY_DIR_MAP[e.code];

        // Prevent page scrolling on arrow keys or space
        if (mappedDir || e.code === 'Space') {
          e.preventDefault();
        }

        // If in Demo Mode, any key starts the player game immediately
        if (mode === 'DEMO') {
          startFromDemo(mappedDir);
          return;
        }

        // Pause toggle (P)
        if (e.code === 'KeyP') {
          if (mode === 'PLAYING') {
            mode = 'PAUSED';
            updateOverlays();
          } else if (mode === 'PAUSED') {
            mode = 'PLAYING';
            lastFrameTime = performance.now();
            updateOverlays();
          }
          return;
        }

        // Restart on Space or Enter when Game Over
        if (mode === 'GAMEOVER' && (e.code === 'Space' || e.code === 'Enter')) {
          initGame('PLAYING');
          return;
        }

        // Directional controls during active play
        if (mode === 'PLAYING' && mappedDir) {
          queueDirection(mappedDir);
        }
      });

      // Mouse / Touch interactions
      canvas.addEventListener('pointerdown', () => {
        if (mode === 'DEMO') {
          startFromDemo();
        }
      });

      demoOverlay.addEventListener('pointerdown', () => {
        if (mode === 'DEMO') {
          startFromDemo();
        }
      });

      resumeBtn.addEventListener('click', () => {
        if (mode === 'PAUSED') {
          mode = 'PLAYING';
          lastFrameTime = performance.now();
          updateOverlays();
        }
      });

      restartBtn.addEventListener('click', () => {
        initGame('PLAYING');
      });

      // On-screen D-Pad buttons
      const DPAD_DIRS = {
        UP:    { x: 0, y: -1 },
        DOWN:  { x: 0, y: 1 },
        LEFT:  { x: -1, y: 0 },
        RIGHT: { x: 1, y: 0 }
      };

      document.querySelectorAll('.dpad-btn').forEach(btn => {
        btn.addEventListener('pointerdown', (e) => {
          e.preventDefault();
          const dir = DPAD_DIRS[btn.dataset.dir];
          if (!dir) return;
          if (mode === 'DEMO') {
            startFromDemo(dir);
          } else if (mode === 'PLAYING') {
            queueDirection(dir);
          }
        });
      });

      // Start immediately in self-playing Demo Mode
      initGame('DEMO');
      requestAnimationFrame(loop);
    })();
  </script>
</body>
</html>