Models 44

GPT-6 Luna

by OpenAIReasoningClean
Open in new window

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

How it went

Total time
5 min 31 s
Cost
$0.013
Output tokens
25,875
Spent thinking
14,572 tokens
Speed
80.7 tok/s
Console errors
0
First token after
10.1 s
First visible text after
3 min 40 s
Prompt tokens
493
Code size
37.9 KB
Finish reason
stop
Served by
OpenAI
Model id
openai/gpt-6-luna
Reasoning sent
{"effort":"xhigh"}
Max output
64,000 tokens
Generated
Nov 10, 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 · 1354 lines

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Snake — Arcade</title>
  <style>
    :root {
      color-scheme: dark;
      --bg: #071116;
      --panel: #0d1b20;
      --line: rgba(181, 222, 207, .11);
      --muted: #809992;
      --text: #e5f2ec;
      --mint: #68e6b1;
      --mint-dark: #123e32;
      --coral: #ff796f;
    }

    * { box-sizing: border-box; }

    html { min-width: 320px; }

    body {
      margin: 0;
      min-height: 100vh;
      color: var(--text);
      background:
        radial-gradient(ellipse at 51% 8%, rgba(31, 89, 73, .18), transparent 40%),
        radial-gradient(ellipse at 90% 80%, rgba(28, 66, 72, .12), transparent 35%),
        var(--bg);
      font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      -webkit-font-smoothing: antialiased;
    }

    body::before {
      position: fixed;
      inset: 0;
      z-index: -1;
      content: "";
      pointer-events: none;
      opacity: .24;
      background-image: radial-gradient(rgba(201, 235, 220, .22) .55px, transparent .75px);
      background-size: 7px 7px;
      mask-image: linear-gradient(to bottom, black, transparent 80%);
    }

    button { font: inherit; }

    .app {
      width: min(100%, 1060px);
      margin: 0 auto;
      padding: 21px 24px 26px;
    }

    .content { width: min(100%, 940px); margin: 0 auto; }

    .topbar {
      display: flex;
      align-items: center;
      justify-content: space-between;
      min-height: 34px;
    }

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

    .brand-mark {
      display: grid;
      width: 32px;
      height: 32px;
      place-items: center;
      border: 1px solid rgba(104, 230, 177, .23);
      border-radius: 10px;
      background: linear-gradient(145deg, rgba(104, 230, 177, .15), rgba(104, 230, 177, .04));
      box-shadow: inset 0 1px rgba(255,255,255,.05);
    }

    .brand-name {
      color: #d9e9e2;
      font-size: 11px;
      font-weight: 800;
      letter-spacing: .19em;
    }

    .session-chip, .grid-chip {
      display: inline-flex;
      align-items: center;
      gap: 8px;
      color: #9eb5ab;
      font-size: 10px;
      font-weight: 750;
      letter-spacing: .13em;
      text-transform: uppercase;
    }

    .session-chip {
      padding: 8px 11px;
      border: 1px solid var(--line);
      border-radius: 999px;
      background: rgba(13, 28, 32, .66);
    }

    .session-chip .tiny-dot {
      width: 6px;
      height: 6px;
      border-radius: 50%;
      background: var(--mint);
      box-shadow: 0 0 10px rgba(104, 230, 177, .8);
    }

    .hero {
      display: flex;
      align-items: flex-end;
      justify-content: space-between;
      margin: 16px 0 15px;
    }

    .eyebrow, .panel-kicker {
      color: #82a096;
      font-size: 9px;
      font-weight: 800;
      letter-spacing: .2em;
      text-transform: uppercase;
    }

    h1 {
      margin: 4px 0 1px;
      color: #eff8f3;
      font-size: clamp(36px, 4.2vw, 45px);
      font-weight: 760;
      letter-spacing: -.065em;
      line-height: 1;
    }

    h1 span { color: var(--mint); }

    .hero p {
      margin: 7px 0 0;
      color: #8da49b;
      font-size: 12px;
      letter-spacing: .005em;
    }

    .hero-aside {
      margin-bottom: 7px;
      color: #607a71;
      font-size: 10px;
      font-weight: 750;
      letter-spacing: .15em;
      text-align: right;
      text-transform: uppercase;
    }

    .hero-aside strong {
      display: block;
      margin-top: 5px;
      color: #b2c7bd;
      font-size: 11px;
      letter-spacing: .12em;
    }

    .game-layout {
      display: grid;
      grid-template-columns: minmax(0, 1fr) 260px;
      gap: 20px;
      align-items: start;
    }

    .board-panel { min-width: 0; }

    .panel-heading {
      display: flex;
      align-items: center;
      justify-content: space-between;
      min-height: 39px;
      margin-bottom: 8px;
      padding: 0 2px;
    }

    .heading-copy {
      display: flex;
      flex-direction: column;
      gap: 3px;
    }

    .heading-copy strong {
      color: #cfdfd7;
      font-size: 12px;
      font-weight: 650;
      letter-spacing: -.01em;
    }

    .grid-chip {
      color: #8aa198;
      font-size: 9px;
      letter-spacing: .12em;
    }

    .grid-chip b {
      color: var(--mint);
      font-size: 10px;
    }

    .board-frame {
      padding: 9px;
      overflow: hidden;
      border: 1px solid rgba(160, 205, 184, .16);
      border-radius: 23px;
      background:
        linear-gradient(145deg, rgba(21, 42, 44, .86), rgba(10, 24, 29, .95));
      box-shadow:
        0 22px 55px rgba(0, 0, 0, .24),
        inset 0 1px rgba(255,255,255,.035);
    }

    .canvas-stage {
      position: relative;
      width: min(100%, 568px);
      margin: 0 auto;
      overflow: hidden;
      aspect-ratio: 1;
      border-radius: 16px;
      background: #091a20;
      isolation: isolate;
    }

    canvas {
      display: block;
      width: 100%;
      height: 100%;
    }

    .stage-overlay {
      position: absolute;
      z-index: 2;
      inset: 0;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 16px;
      border-radius: inherit;
      background: rgba(3, 11, 15, .26);
      backdrop-filter: blur(1.3px);
      -webkit-backdrop-filter: blur(1.3px);
      cursor: pointer;
    }

    .stage-overlay[hidden] { display: none; }

    .overlay-card {
      width: min(100%, 352px);
      padding: 21px 22px 18px;
      border: 1px solid rgba(164, 224, 199, .2);
      border-radius: 18px;
      background:
        linear-gradient(145deg, rgba(15, 35, 38, .96), rgba(8, 22, 28, .96));
      box-shadow: 0 18px 55px rgba(0, 0, 0, .4), inset 0 1px rgba(255,255,255,.045);
      text-align: center;
      cursor: default;
    }

    .overlay-chip {
      display: inline-flex;
      align-items: center;
      gap: 7px;
      padding: 6px 9px;
      border: 1px solid rgba(104, 230, 177, .17);
      border-radius: 999px;
      color: #a2d8bf;
      background: rgba(104, 230, 177, .07);
      font-size: 8px;
      font-weight: 800;
      letter-spacing: .15em;
      text-transform: uppercase;
    }

    .overlay-chip .tiny-dot {
      width: 6px;
      height: 6px;
      border-radius: 50%;
      background: var(--mint);
      box-shadow: 0 0 9px rgba(104,230,177,.8);
      animation: blink 1.5s ease-in-out infinite;
    }

    @keyframes blink {
      50% { opacity: .45; box-shadow: 0 0 3px rgba(104,230,177,.3); }
    }

    .overlay-card h2 {
      margin: 12px 0 5px;
      color: #eff8f3;
      font-size: 23px;
      font-weight: 720;
      letter-spacing: -.045em;
    }

    .overlay-card p {
      max-width: 275px;
      margin: 0 auto 15px;
      color: #9db2a9;
      font-size: 11px;
      line-height: 1.55;
    }

    .overlay-action {
      display: flex;
      width: 100%;
      min-height: 42px;
      align-items: center;
      justify-content: space-between;
      gap: 8px;
      padding: 0 12px 0 15px;
      border: 0;
      border-radius: 11px;
      color: #082219;
      background: linear-gradient(135deg, #86efbf, #58dba6);
      box-shadow: 0 5px 19px rgba(53, 194, 135, .18), inset 0 1px rgba(255,255,255,.45);
      font-size: 11px;
      font-weight: 800;
      cursor: pointer;
      transition: transform .15s ease, filter .15s ease;
    }

    .overlay-action:hover { filter: brightness(1.06); transform: translateY(-1px); }
    .overlay-action:active { transform: translateY(0); }

    .enter-icon {
      display: grid;
      width: 23px;
      height: 23px;
      place-items: center;
      border: 1px solid rgba(8, 34, 25, .23);
      border-radius: 7px;
      font-size: 14px;
      line-height: 1;
    }

    .overlay-hint {
      margin-top: 12px;
      color: #718a80;
      font-size: 8px;
      font-weight: 750;
      letter-spacing: .15em;
      text-transform: uppercase;
    }

    .overlay-score {
      display: flex;
      align-items: baseline;
      justify-content: center;
      gap: 7px;
      margin: 9px 0 14px;
    }

    .overlay-score strong {
      color: var(--mint);
      font-size: 35px;
      letter-spacing: -.05em;
      line-height: 1;
    }

    .overlay-score span {
      color: #87a197;
      font-size: 9px;
      font-weight: 800;
      letter-spacing: .14em;
    }

    .board-foot {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 8px;
      padding: 9px 3px 0;
      color: #728980;
      font-size: 9px;
      letter-spacing: .025em;
    }

    .board-foot .foot-left, .board-foot .foot-right {
      display: inline-flex;
      align-items: center;
      gap: 7px;
    }

    .food-dot {
      display: inline-block;
      width: 7px;
      height: 7px;
      border-radius: 50%;
      background: var(--coral);
      box-shadow: 0 0 8px rgba(255, 121, 111, .55);
    }

    .side-stack {
      display: flex;
      flex-direction: column;
      gap: 12px;
      padding-top: 1px;
    }

    .score-card, .info-card, .tip-card {
      border: 1px solid var(--line);
      border-radius: 17px;
      background: linear-gradient(145deg, rgba(16, 34, 37, .94), rgba(10, 24, 29, .94));
      box-shadow: inset 0 1px rgba(255,255,255,.025);
    }

    .score-card {
      position: relative;
      padding: 16px 17px 13px;
      overflow: hidden;
    }

    .score-card::after {
      position: absolute;
      top: -44px;
      right: -43px;
      width: 118px;
      height: 118px;
      border-radius: 50%;
      background: rgba(104, 230, 177, .055);
      filter: blur(1px);
      content: "";
      pointer-events: none;
    }

    .score-top {
      display: flex;
      align-items: center;
      justify-content: space-between;
      color: #91a69d;
      font-size: 9px;
      font-weight: 800;
      letter-spacing: .16em;
      text-transform: uppercase;
    }

    .run-state {
      display: inline-flex;
      align-items: center;
      gap: 6px;
      color: #a2dfbf;
      font-size: 8px;
      letter-spacing: .1em;
    }

    .run-state i {
      width: 6px;
      height: 6px;
      border-radius: 50%;
      background: var(--mint);
      box-shadow: 0 0 8px rgba(104,230,177,.62);
    }

    .run-state.paused { color: #e5c88a; }
    .run-state.paused i { background: #e6bd67; box-shadow: 0 0 8px rgba(230,189,103,.52); }
    .run-state.over { color: #f29a91; }
    .run-state.over i { background: var(--coral); box-shadow: 0 0 8px rgba(255,121,111,.5); }

    .score-main {
      display: flex;
      align-items: baseline;
      gap: 8px;
      margin: 11px 0 12px;
    }

    .score-main strong {
      color: #eff8f3;
      font-size: 48px;
      font-weight: 720;
      letter-spacing: -.075em;
      line-height: .95;
      font-variant-numeric: tabular-nums;
    }

    .score-main span {
      color: #79958a;
      font-size: 9px;
      font-weight: 800;
      letter-spacing: .17em;
    }

    .score-divider { height: 1px; background: var(--line); }

    .best-line {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding-top: 10px;
      color: #83998f;
      font-size: 9px;
      font-weight: 750;
      letter-spacing: .12em;
      text-transform: uppercase;
    }

    .best-line strong {
      color: #bfe4cf;
      font-size: 12px;
      font-variant-numeric: tabular-nums;
      letter-spacing: .06em;
    }

    .info-card { padding: 15px 16px 13px; }

    .card-title {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 13px;
      color: #d8e7df;
      font-size: 12px;
      font-weight: 700;
    }

    .card-title span {
      color: #6e887d;
      font-size: 8px;
      font-weight: 800;
      letter-spacing: .16em;
      text-transform: uppercase;
    }

    .control-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 10px;
      min-height: 31px;
      border-top: 1px solid rgba(181, 222, 207, .07);
      color: #9aada5;
      font-size: 10px;
    }

    .control-row:first-of-type { border-top: 0; }
    .control-name { color: #9aada5; }

    .key-cluster {
      display: inline-flex;
      align-items: center;
      gap: 3px;
    }

    .key {
      display: inline-grid;
      min-width: 21px;
      height: 21px;
      place-items: center;
      padding: 0 5px;
      border: 1px solid rgba(181, 222, 207, .15);
      border-bottom-color: rgba(181, 222, 207, .25);
      border-radius: 6px;
      color: #d4e4dc;
      background: rgba(31, 52, 54, .78);
      box-shadow: inset 0 -1px rgba(0,0,0,.15);
      font-size: 10px;
      font-weight: 750;
      line-height: 1;
    }

    .key.wide { min-width: 42px; font-size: 8px; letter-spacing: .06em; }

    .wasd-note {
      margin: 7px 0 0;
      color: #70877e;
      font-size: 9px;
      text-align: right;
    }

    .tip-card {
      display: flex;
      align-items: center;
      gap: 12px;
      padding: 13px 15px;
      border-color: rgba(255, 121, 111, .14);
      background: linear-gradient(145deg, rgba(41, 29, 31, .5), rgba(14, 27, 31, .92));
    }

    .tip-orb {
      position: relative;
      flex: 0 0 auto;
      width: 26px;
      height: 26px;
      border: 1px solid rgba(255, 156, 139, .35);
      border-radius: 9px;
      background: rgba(255, 121, 111, .1);
    }

    .tip-orb::before {
      position: absolute;
      top: 7px;
      left: 7px;
      width: 10px;
      height: 10px;
      border-radius: 50%;
      background: var(--coral);
      box-shadow: 0 0 10px rgba(255,121,111,.55);
      content: "";
    }

    .tip-copy strong {
      display: block;
      margin-bottom: 3px;
      color: #e2d4ce;
      font-size: 10px;
      font-weight: 750;
    }

    .tip-copy span {
      display: block;
      color: #9b8984;
      font-size: 9px;
      line-height: 1.4;
    }

    @media (max-width: 760px) {
      .app { padding: 15px 14px 24px; }
      .content { width: 100%; }
      .topbar { min-height: 32px; }
      .hero { margin: 19px 2px 16px; }
      h1 { font-size: 39px; }
      .hero-aside { font-size: 8px; }
      .hero-aside strong { font-size: 9px; }
      .game-layout { grid-template-columns: minmax(0, 1fr); gap: 13px; }
      .panel-heading { min-height: 36px; }
      .board-frame { padding: 7px; border-radius: 19px; }
      .canvas-stage { width: 100%; border-radius: 13px; }
      .side-stack { display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px; }
      .score-card { padding: 14px 16px 12px; }
      .score-main { margin: 8px 0 10px; }
      .score-main strong { font-size: 42px; }
      .info-card { padding: 14px 16px 12px; }
    }

    @media (max-width: 410px) {
      .app { padding-right: 12px; padding-left: 12px; }
      .brand-name { font-size: 10px; }
      .session-chip { padding: 7px 9px; font-size: 8px; }
      .hero-aside { display: none; }
      .heading-copy strong { font-size: 11px; }
      .grid-chip { font-size: 8px; }
      .overlay-card { padding: 17px 15px 15px; }
      .overlay-card h2 { font-size: 21px; }
      .overlay-card p { font-size: 10px; }
      .overlay-action { font-size: 10px; }
      .board-foot { font-size: 8px; }
    }

    @media (prefers-reduced-motion: reduce) {
      *, *::before, *::after { scroll-behavior: auto !important; }
    }
  </style>
</head>
<body>
  <div class="app">
    <header class="topbar content">
      <div class="brand">
        <div class="brand-mark" aria-hidden="true">
          <svg width="19" height="19" viewBox="0 0 24 24" fill="none">
            <path d="M5 8.3h7.3a3.4 3.4 0 1 1-3.4 3.4V9.4a3.4 3.4 0 1 0-3.4 3.4H19" stroke="#68e6b1" stroke-width="2.2" stroke-linecap="round"/>
            <circle cx="16.3" cy="12.8" r="1" fill="#d7fff0"/>
          </svg>
        </div>
        <span class="brand-name">NIGHTSHIFT ARCADE</span>
      </div>
      <div class="session-chip"><span class="tiny-dot"></span> SESSION PLAY</div>
    </header>

    <div class="content">
      <section class="hero">
        <div>
          <div class="eyebrow">ARCADE CLASSICS &nbsp; / &nbsp; 01</div>
          <h1>Snake<span>.</span></h1>
          <p>One more bite. One more block. Don’t hit the edge.</p>
        </div>
        <div class="hero-aside">A little room to roam<strong>CLASSIC MODE &nbsp; / &nbsp; NO WRAP</strong></div>
      </section>

      <main class="game-layout">
        <section class="board-panel" aria-label="Snake game">
          <div class="panel-heading">
            <div class="heading-copy">
              <span class="panel-kicker">PLAYFIELD</span>
              <strong>Stay sharp. Keep growing.</strong>
            </div>
            <div class="grid-chip"><b>24 × 24</b><span>•</span> GRID</div>
          </div>

          <div class="board-frame">
            <div class="canvas-stage">
              <canvas id="gameCanvas" aria-label="Snake game board"></canvas>
              <div class="stage-overlay" id="overlay"></div>
            </div>
          </div>

          <div class="board-foot">
            <span class="foot-left"><i class="food-dot"></i> Food adds 10 points</span>
            <span class="foot-right">SMOOTH MOTION <span>·</span> SESSION BEST</span>
          </div>
        </section>

        <aside class="side-stack" aria-label="Score and controls">
          <section class="score-card">
            <div class="score-top">
              <span>RUN SCORE</span>
              <span id="runState" class="run-state"><i></i><span id="runStateText">DEMO RUN</span></span>
            </div>
            <div class="score-main"><strong id="score">000</strong><span>PTS</span></div>
            <div class="score-divider"></div>
            <div class="best-line"><span>Session best</span><strong id="bestScore">000</strong></div>
          </section>

          <section class="info-card">
            <div class="card-title">Controls <span>Quick guide</span></div>
            <div class="control-row">
              <span class="control-name">Steer</span>
              <span class="key-cluster" aria-label="Arrow keys">
                <span class="key">↑</span><span class="key">←</span><span class="key">↓</span><span class="key">→</span>
              </span>
            </div>
            <div class="control-row">
              <span class="control-name">Pause / resume</span>
              <span class="key">P</span>
            </div>
            <div class="control-row">
              <span class="control-name">Restart after a crash</span>
              <span class="key-cluster"><span class="key wide">SPACE</span><span class="key wide">ENTER</span></span>
            </div>
            <p class="wasd-note">W A S D works too</p>
          </section>

          <section class="tip-card">
            <span class="tip-orb" aria-hidden="true"></span>
            <div class="tip-copy">
              <strong>Chase the glow.</strong>
              <span>Every fruit is +10 points and one extra segment.</span>
            </div>
          </section>
        </aside>
      </main>
    </div>
  </div>

  <script>
    (() => {
      "use strict";

      const GRID = 24;
      const STEP_MS = 128;
      const canvas = document.getElementById("gameCanvas");
      const ctx = canvas.getContext("2d");
      const stage = canvas.parentElement;
      const overlay = document.getElementById("overlay");
      const scoreEl = document.getElementById("score");
      const bestEl = document.getElementById("bestScore");
      const runState = document.getElementById("runState");
      const runStateText = document.getElementById("runStateText");

      let boardSize = 0;
      let cellSize = 0;
      let state = "demo";
      let snake = [];
      let previousSnake = [];
      let direction = { x: 1, y: 0 };
      let turnQueue = [];
      let food = null;
      let score = 0;
      let bestScore = 0;
      let accumulator = 0;
      let lastFrame = 0;
      let particles = [];
      let scoreTexts = [];
      let overlayState = "";
      let cycle = [];
      let cycleIndex = new Map();

      const keyOf = (p) => `${p.x},${p.y}`;
      const cloneSnake = (list) => list.map(p => ({ x: p.x, y: p.y }));
      const opposite = (a, b) => a.x === -b.x && a.y === -b.y;
      const formatScore = (n) => String(n).padStart(3, "0");

      function makeCycle() {
        const path = [];
        for (let x = 0; x < GRID; x++) path.push({ x, y: 0 });

        for (let y = 1; y < GRID; y++) {
          if (y % 2 === 1) {
            for (let x = GRID - 1; x >= 1; x--) path.push({ x, y });
          } else {
            for (let x = 1; x < GRID; x++) path.push({ x, y });
          }
        }

        path.push({ x: 0, y: GRID - 1 });
        for (let y = GRID - 2; y >= 1; y--) path.push({ x: 0, y });

        cycle = path;
        cycleIndex = new Map(path.map((p, i) => [keyOf(p), i]));
      }

      function setDirectionBetween(a, b) {
        return { x: Math.sign(b.x - a.x), y: Math.sign(b.y - a.y) };
      }

      function spawnFood() {
        const occupied = new Set(snake.map(keyOf));
        const free = [];
        for (let y = 0; y < GRID; y++) {
          for (let x = 0; x < GRID; x++) {
            const p = { x, y };
            if (!occupied.has(keyOf(p))) free.push(p);
          }
        }
        if (!free.length) {
          food = null;
          return false;
        }
        food = free[Math.floor(Math.random() * free.length)];
        return true;
      }

      function initializeDemo() {
        const n = cycle.length;
        snake = [
          cycle[0],
          cycle[(n - 1) % n],
          cycle[(n - 2) % n],
          cycle[(n - 3) % n]
        ].map(p => ({ x: p.x, y: p.y }));
        previousSnake = cloneSnake(snake);
        direction = setDirectionBetween(cycle[0], cycle[1]);
        turnQueue = [];
        score = 0;
        food = { ...cycle[7] };
        particles = [];
        scoreTexts = [];
        accumulator = 0;
      }

      function initializeLive() {
        const cx = Math.floor(GRID / 2);
        const cy = Math.floor(GRID / 2);
        snake = [
          { x: cx, y: cy },
          { x: cx - 1, y: cy },
          { x: cx - 2, y: cy },
          { x: cx - 3, y: cy }
        ];
        previousSnake = cloneSnake(snake);
        direction = { x: 1, y: 0 };
        turnQueue = [];
        score = 0;
        particles = [];
        scoreTexts = [];
        accumulator = 0;
        spawnFood();
      }

      function setGameState(next) {
        state = next;
        syncUI();
      }

      function startLive() {
        initializeLive();
        setGameState("playing");
      }

      function restartLive() {
        initializeLive();
        setGameState("playing");
      }

      function syncUI() {
        scoreEl.textContent = formatScore(score);
        bestEl.textContent = formatScore(bestScore);

        runState.className = "run-state";
        if (state === "demo") {
          runStateText.textContent = "DEMO RUN";
        } else if (state === "playing") {
          runStateText.textContent = "LIVE";
        } else if (state === "paused") {
          runStateText.textContent = "PAUSED";
          runState.classList.add("paused");
        } else {
          runStateText.textContent = "GAME OVER";
          runState.classList.add("over");
        }

        if (overlayState !== state) {
          overlayState = state;
          renderOverlay();
        }
      }

      function renderOverlay() {
        if (state === "playing") {
          overlay.hidden = true;
          overlay.innerHTML = "";
          return;
        }

        overlay.hidden = false;
        if (state === "demo") {
          overlay.innerHTML = `
            <div class="overlay-card">
              <div class="overlay-chip"><span class="tiny-dot"></span> AI PRACTICE RUN</div>
              <h2>Your run starts here.</h2>
              <p>The snake is already on the move. Jump in whenever you’re ready.</p>
              <button class="overlay-action" type="button">
                <span>Click or press a key to start</span><span class="enter-icon">↵</span>
              </button>
              <div class="overlay-hint">ARROWS / WASD &nbsp;·&nbsp; P TO PAUSE</div>
            </div>`;
        } else if (state === "paused") {
          overlay.innerHTML = `
            <div class="overlay-card">
              <div class="overlay-chip"><span class="tiny-dot"></span> RUN PAUSED</div>
              <h2>Take a breath.</h2>
              <p>Your run is on hold. Pick up right where you left off.</p>
              <button class="overlay-action" type="button">
                <span>Click or press P to resume</span><span class="enter-icon">↵</span>
              </button>
              <div class="overlay-hint">P TO RESUME</div>
            </div>`;
        } else {
          overlay.innerHTML = `
            <div class="overlay-card">
              <div class="overlay-chip"><span class="tiny-dot"></span> RUN ENDED</div>
              <h2>That’s a wrap.</h2>
              <p>Good run. The next one is already waiting.</p>
              <div class="overlay-score"><strong>${score}</strong><span>POINTS</span></div>
              <button class="overlay-action" type="button">
                <span>Space or Enter to restart</span><span class="enter-icon">↵</span>
              </button>
              <div class="overlay-hint">SESSION BEST &nbsp; ${formatScore(bestScore)}</div>
            </div>`;
        }
      }

      overlay.addEventListener("click", () => {
        if (state === "demo" || state === "over") startLive();
        else if (state === "paused") {
          previousSnake = cloneSnake(snake);
          accumulator = 0;
          setGameState("playing");
        }
      });

      function enqueueDirection(next) {
        const planned = turnQueue.length ? turnQueue[turnQueue.length - 1] : direction;
        if (next.x === planned.x && next.y === planned.y) return;
        if (opposite(next, planned)) return;
        if (turnQueue.length < 8) turnQueue.push(next);
      }

      const directions = {
        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 }
      };

      document.addEventListener("keydown", (event) => {
        const key = event.key.toLowerCase();
        const move = directions[key];
        const isSpace = key === " " || key === "space";
        const isEnter = key === "enter";
        const isPause = key === "p";

        if (move || isSpace || isEnter) event.preventDefault();

        if (state === "demo") {
          startLive();
          if (move) enqueueDirection(move);
          return;
        }

        if (state === "over") {
          if (isSpace || isEnter) restartLive();
          return;
        }

        if (state === "paused") {
          if (isPause) {
            previousSnake = cloneSnake(snake);
            accumulator = 0;
            setGameState("playing");
          }
          return;
        }

        if (state === "playing") {
          if (isPause) {
            previousSnake = cloneSnake(snake);
            accumulator = 0;
            setGameState("paused");
          } else if (move) {
            enqueueDirection(move);
          }
        }
      });

      function addEatEffects(position) {
        if (!boardSize) return;
        const x = (position.x + .5) * cellSize;
        const y = (position.y + .5) * cellSize;
        for (let i = 0; i < 17; i++) {
          const angle = Math.random() * Math.PI * 2;
          const speed = 28 + Math.random() * 95;
          particles.push({
            x, y,
            vx: Math.cos(angle) * speed,
            vy: Math.sin(angle) * speed,
            radius: 1.4 + Math.random() * 2.3,
            life: .45 + Math.random() * .4,
            maxLife: .85,
            color: Math.random() > .25 ? "#ff9a83" : "#ffe0a1"
          });
        }
        scoreTexts.push({ x, y: y - 4, life: .8, maxLife: .8 });
      }

      function finishRun() {
        bestScore = Math.max(bestScore, score);
        setGameState("over");
      }

      function stepGame() {
        if (state === "demo") {
          const index = cycleIndex.get(keyOf(snake[0]));
          const nextCell = cycle[(index + 1) % cycle.length];
          direction = setDirectionBetween(snake[0], nextCell);
        } else if (turnQueue.length) {
          direction = turnQueue.shift();
        }

        const old = cloneSnake(snake);
        const next = { x: old[0].x + direction.x, y: old[0].y + direction.y };
        const willEat = food && next.x === food.x && next.y === food.y;

        const outside = next.x < 0 || next.y < 0 || next.x >= GRID || next.y >= GRID;
        const bodyToCheck = willEat ? old : old.slice(0, -1);
        const hitSelf = bodyToCheck.some(p => p.x === next.x && p.y === next.y);

        if (outside || hitSelf) {
          if (state === "demo") {
            initializeDemo();
            syncUI();
          } else {
            finishRun();
          }
          return;
        }

        previousSnake = old;
        snake = [next, ...old];
        if (!willEat) snake.pop();

        if (willEat) {
          const eaten = { ...food };
          score += 10;
          if (state === "playing") bestScore = Math.max(bestScore, score);
          addEatEffects(eaten);
          const hasRoom = spawnFood();

          if (!hasRoom && state === "playing") {
            finishRun();
            return;
          } else if (!hasRoom && state === "demo") {
            initializeDemo();
          }

          syncUI();
        }
      }

      function resizeCanvas() {
        const rect = stage.getBoundingClientRect();
        if (!rect.width) return;
        boardSize = rect.width;
        cellSize = boardSize / GRID;
        const dpr = Math.min(window.devicePixelRatio || 1, 2.5);
        const pixelSize = Math.round(boardSize * dpr);
        if (canvas.width !== pixelSize || canvas.height !== pixelSize) {
          canvas.width = pixelSize;
          canvas.height = pixelSize;
        }
        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
      }

      function drawBackground() {
        const s = boardSize;
        const bg = ctx.createLinearGradient(0, 0, s, s);
        bg.addColorStop(0, "#0b2025");
        bg.addColorStop(.52, "#0a1b21");
        bg.addColorStop(1, "#09181e");
        ctx.fillStyle = bg;
        ctx.beginPath();
        ctx.roundRect(0, 0, s, s, Math.max(8, cellSize * .45));
        ctx.fill();

        const wash = ctx.createRadialGradient(s * .34, s * .22, 0, s * .45, s * .48, s * .78);
        wash.addColorStop(0, "rgba(54, 116, 91, .12)");
        wash.addColorStop(1, "rgba(5, 17, 22, 0)");
        ctx.fillStyle = wash;
        ctx.fillRect(0, 0, s, s);

        ctx.lineWidth = 1;
        ctx.strokeStyle = "rgba(160, 207, 188, .075)";
        for (let i = 1; i < GRID; i++) {
          const p = i * cellSize;
          ctx.beginPath();
          ctx.moveTo(p, 0);
          ctx.lineTo(p, s);
          ctx.stroke();
          ctx.beginPath();
          ctx.moveTo(0, p);
          ctx.lineTo(s, p);
          ctx.stroke();
        }

        ctx.strokeStyle = "rgba(163, 217, 194, .13)";
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.roundRect(.5, .5, s - 1, s - 1, Math.max(8, cellSize * .45));
        ctx.stroke();

        const tick = Math.max(6, cellSize * .25);
        ctx.strokeStyle = "rgba(104, 230, 177, .32)";
        ctx.lineWidth = 1.5;
        const corners = [
          [1, 1, 1, 1], [s - 1, 1, -1, 1],
          [1, s - 1, 1, -1], [s - 1, s - 1, -1, -1]
        ];
        for (const [x, y, sx, sy] of corners) {
          ctx.beginPath();
          ctx.moveTo(x, y + sy * tick);
          ctx.lineTo(x, y);
          ctx.lineTo(x + sx * tick, y);
          ctx.stroke();
        }
      }

      function drawFood(now) {
        if (!food) return;
        const c = cellSize;
        const x = (food.x + .5) * c;
        const y = (food.y + .5) * c;
        const pulse = .92 + Math.sin(now * .006) * .08;
        const radius = c * .255 * pulse;

        ctx.save();
        const glow = ctx.createRadialGradient(x, y, radius * .15, x, y, c * .78);
        glow.addColorStop(0, "rgba(255, 123, 108, .34)");
        glow.addColorStop(.45, "rgba(255, 110, 100, .11)");
        glow.addColorStop(1, "rgba(255, 110, 100, 0)");
        ctx.fillStyle = glow;
        ctx.beginPath();
        ctx.arc(x, y, c * .78, 0, Math.PI * 2);
        ctx.fill();

        ctx.shadowColor = "rgba(255, 113, 102, .72)";
        ctx.shadowBlur = c * .52;
        ctx.fillStyle = "#ff796f";
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, Math.PI * 2);
        ctx.fill();

        ctx.shadowBlur = 0;
        ctx.strokeStyle = "rgba(255, 214, 182, .64)";
        ctx.lineWidth = Math.max(1, c * .045);
        ctx.beginPath();
        ctx.arc(x, y, radius * .72, 0, Math.PI * 2);
        ctx.stroke();

        ctx.fillStyle = "rgba(255, 240, 216, .94)";
        ctx.beginPath();
        ctx.arc(x - radius * .28, y - radius * .3, radius * .19, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();
      }

      function drawSnake(alpha) {
        if (!snake.length) return;
        const c = cellSize;
        const nodes = snake.map((p, i) => {
          const from = previousSnake[i] || previousSnake[previousSnake.length - 1] || p;
          return {
            x: (from.x + (p.x - from.x) * alpha + .5) * c,
            y: (from.y + (p.y - from.y) * alpha + .5) * c
          };
        });

        ctx.save();
        ctx.beginPath();
        ctx.moveTo(nodes[nodes.length - 1].x, nodes[nodes.length - 1].y);
        for (let i = nodes.length - 2; i >= 0; i--) ctx.lineTo(nodes[i].x, nodes[i].y);

        ctx.lineCap = "round";
        ctx.lineJoin = "round";
        ctx.strokeStyle = "rgba(4, 14, 17, .82)";
        ctx.lineWidth = c * .88;
        ctx.stroke();

        const tail = nodes[nodes.length - 1];
        const head = nodes[0];
        const bodyGradient = ctx.createLinearGradient(head.x, head.y, tail.x, tail.y);
        bodyGradient.addColorStop(0, "#8df3bd");
        bodyGradient.addColorStop(.38, "#55d99f");
        bodyGradient.addColorStop(1, "#278d6a");
        ctx.shadowColor = "rgba(76, 224, 157, .2)";
        ctx.shadowBlur = c * .34;
        ctx.strokeStyle = bodyGradient;
        ctx.lineWidth = c * .69;
        ctx.stroke();

        ctx.shadowBlur = 0;
        ctx.beginPath();
        ctx.moveTo(nodes[nodes.length - 1].x, nodes[nodes.length - 1].y);
        for (let i = nodes.length - 2; i >= 0; i--) ctx.lineTo(nodes[i].x, nodes[i].y);
        ctx.strokeStyle = "rgba(225, 255, 240, .17)";
        ctx.lineWidth = c * .12;
        ctx.stroke();

        const headPoint = nodes[0];
        const angle = Math.atan2(direction.y, direction.x);
        ctx.translate(headPoint.x, headPoint.y);
        ctx.rotate(angle);

        const headGradient = ctx.createLinearGradient(-c * .35, -c * .32, c * .42, c * .34);
        headGradient.addColorStop(0, "#a2f8c8");
        headGradient.addColorStop(1, "#54daa0");
        ctx.shadowColor = "rgba(88, 227, 161, .38)";
        ctx.shadowBlur = c * .48;
        ctx.fillStyle = headGradient;
        ctx.beginPath();
        ctx.roundRect(-c * .42, -c * .39, c * .84, c * .78, c * .27);
        ctx.fill();
        ctx.shadowBlur = 0;
        ctx.strokeStyle = "rgba(209, 255, 229, .55)";
        ctx.lineWidth = Math.max(1, c * .035);
        ctx.stroke();

        for (const ey of [-1, 1]) {
          const ex = c * .14;
          const y = ey * c * .195;
          ctx.fillStyle = "#effff5";
          ctx.beginPath();
          ctx.arc(ex, y, c * .093, 0, Math.PI * 2);
          ctx.fill();
          ctx.fillStyle = "#12352b";
          ctx.beginPath();
          ctx.arc(ex + c * .025, y + c * .006, c * .043, 0, Math.PI * 2);
          ctx.fill();
        }
        ctx.restore();
      }

      function drawEffects(dt) {
        const seconds = dt / 1000;
        for (const p of particles) {
          p.x += p.vx * seconds;
          p.y += p.vy * seconds;
          p.vx *= Math.pow(.12, seconds);
          p.vy *= Math.pow(.12, seconds);
          p.vy += 18 * seconds;
          p.life -= seconds;
        }
        particles = particles.filter(p => p.life > 0);

        for (const p of particles) {
          const a = Math.min(1, p.life / .22);
          ctx.globalAlpha = a;
          ctx.fillStyle = p.color;
          ctx.shadowColor = p.color;
          ctx.shadowBlur = 8;
          ctx.beginPath();
          ctx.arc(p.x, p.y, p.radius * (.55 + a * .45), 0, Math.PI * 2);
          ctx.fill();
        }
        ctx.globalAlpha = 1;
        ctx.shadowBlur = 0;

        for (const fx of scoreTexts) {
          fx.life -= seconds;
          fx.y -= 23 * seconds;
          const alpha = Math.min(1, fx.life / .22);
          ctx.globalAlpha = alpha;
          ctx.fillStyle = "#ffe1a7";
          ctx.font = `800 ${Math.max(11, cellSize * .52)}px system-ui, sans-serif`;
          ctx.textAlign = "center";
          ctx.textBaseline = "middle";
          ctx.shadowColor = "rgba(255, 179, 100, .65)";
          ctx.shadowBlur = 9;
          ctx.fillText("+10", fx.x, fx.y);
        }
        scoreTexts = scoreTexts.filter(p => p.life > 0);
        ctx.globalAlpha = 1;
        ctx.shadowBlur = 0;
      }

      function draw(alpha, now, dt) {
        if (!boardSize) return;
        ctx.clearRect(0, 0, boardSize, boardSize);
        drawBackground();
        drawFood(now);
        drawSnake(Math.max(0, Math.min(1, alpha)));
        drawEffects(dt);
      }

      function frame(now) {
        if (!lastFrame) lastFrame = now;
        const dt = Math.min(80, now - lastFrame);
        lastFrame = now;

        if (state === "demo" || state === "playing") {
          accumulator += dt;
          while (accumulator >= STEP_MS) {
            stepGame();
            accumulator -= STEP_MS;
            if (state !== "demo" && state !== "playing") break;
          }
        } else {
          accumulator = 0;
        }

        draw(accumulator / STEP_MS, now, dt);
        requestAnimationFrame(frame);
      }

      makeCycle();
      initializeDemo();
      resizeCanvas();
      syncUI();

      if ("ResizeObserver" in window) {
        new ResizeObserver(resizeCanvas).observe(stage);
      } else {
        window.addEventListener("resize", resizeCanvas);
      }

      requestAnimationFrame(frame);
    })();
  </script>
</body>
</html>
Reasoning trace (14,572 tokens)
**Planning snake movement**

I'm considering a simple BFS approach for navigating the snake from its head to the food while avoiding occupied cells, except for the tail if it's not growing. Each step requires calculating the BFS since the snake's body changes as it moves. A better solution might be using a Hamiltonian cycle for autopilot navigation, ensuring the snake can safely traverse while eventually reaching food. I need to define the cycle path for a 24x24 grid effectively.**Planning game features**

I’m considering how to implement a live score demo that clearly shows "DEMO MODE" and a button for users to click to start. The scoreboard will remain visible, and I want to avoid issues with canvas controls, so I'll need to manage key events efficiently. 

Once the game starts with any key, I'll ensure it’s easy to restart or pause with specific keys. I also want to keep track of the best score in JavaScript memory, updating without needing persistence.**Designing game layout**

I'm thinking about creating a hero row with a heading or title and including some stats. For the layout, I want a game-shell with a rounded border and a nice background gradient. I should set the board frame padding and consider the canvas width. Maybe I can use CSS to clamp the canvas width, targeting a maximum size of 600 pixels. Also, I need to ensure that the canvas’s aspect ratio is maintained, possibly using devicePixelRatio for resolution. Keeping the grid at 24x24 sounds good!**Considering snake movement update**

I'm thinking about how to handle the snake's body segments during movement. Each tick should capture the old positions and then update the new ones for each segment. For example, when rendering position, I need to interpolate between the old and new segments. When moving normally, the current array of the snake should reflect the new head and then the old segments. I also need to ensure each segment moves towards the positions of the preceding ones.**Considering game overlay design**

The developer wants a clear prompt like "Click or press a key to start." I'm thinking the initial screenshot should last 3 seconds, enough time for players to see the snake move behind the overlay. It might work better to position the overlay card at the center-top of the board. The snake will be around the boundary, moving towards food placed randomly, possibly around the index. As the game progresses, I'll make sure to display the score.**Planning overlay functionality**

The overlay prevents the game from starting automatically, allowing users to see the AI. I’m thinking of an initial demo state with the snake and food positioned at cycle index 8. It might work to place the food at index 6 (x=6, y=0) near the top row, while the snake starts at x0. It takes around 720ms to reach it. The overlay will mask the top center of the board, so I'll need to use canvas along with CSS for proper resizing and coordination.**Designing game-over overlay**

I'm working on the game-over overlay. It should be centered and feature the title "RUN OVER," along with a score display and best score. I’ll include restart instructions, and for the paused overlay, there should be a message indicating that as well. The overlay button needs to be clickable, and I want to ensure no confirmation prompts pop up. I'll set the HTML overlay pointer events to allow clicks easily. Let’s get this in place!**Considering game mechanics**

I'm focusing on creating a game interface, which includes adding a start button and managing the game's functions. I've defined the grid size and game speed; for example, the demo snake moves at about 8 cells per second. I’m thinking about initializing the game state for demo and human modes, which will cover different scenarios like playing, paused, or over.

I’ll set up the snake’s initial position and direction based on its previous moves, ensuring smooth movements and alignment.**Configuring game setup**

I'm focusing on establishing the game's starting conditions for both human and demo modes. For the human start, I think it's best to initialize the snake centered in the grid, facing right, with food placed randomly. Rather than reusing the demo state, I'll reset it entirely.

For the demo mode, the snake begins at cycle positions based on previous indices, ensuring no body overlap. I’ll spawn food randomly to keep things interesting and ensure the initial position avoids the snake's body. For food placement, I'll use `Math.random` to ensure variety.**Managing game state and collisions**

I’m structuring the game logic, focusing on different states: if it's not demo or playing, I want to return early. I need to copy the current snake's position and determine the next direction. 

If it's demo mode, I'll calculate the next step; otherwise, I’ll check the turn queue while validating the current direction. After that, I’ll set the new head's position and check for collisions with walls or the snake's own body. For collisions, I should consider all segments except the tail when it’s not eating, ensuring accurate body occupancy tracking.**Exploring animation principles**

I’m examining how to manage movement and animation in my processing. If a candidate direction is opposite to one currently disallowed, that’s correct. I need to think about how the input queue behaves before movement, as sometimes a direction will be accepted right after the current. Keeping the accumulator reset helps avoid catch-up issues. I can use `requestAnimationFrame` for smooth animations, focusing on frame time and clamping the accumulator appropriately for interpolation.**Figuring out key events**

I'm thinking about the event keys and their interactions. If the state is over, I guess space or enter should restart the process, and I should call `preventDefault` to stop the usual action. If it’s paused, maybe 'p' toggles the pause state, and space or enter could resume it. But then again, does the move key resume, or is that not how it works? It's a bit complex, but I’ll keep digging!**Considering collision mechanics**

I'm thinking about how the collision body includes the tail if the snake is eating. If the next cell matches the tail and it’s not eating, the tail should leave, which is fine.  

I’ll set `willEat = food && matches` and determine `collisionBody` accordingly. If there's a wall, I need to configure the game over state. Maybe the snake can show where it failed, like a flash effect or a red border at the crash point, although that's optional.**Designing control layout**

I'm working on a control layout design and thinking of using the "Steer" commands with keycaps like `↑`, `←`, `↓`, `→`, and also considering a WASD alternative. For pause, I can use "P," and for restart, I might suggest using "SPACE" or "ENTER" after a crash. I’ll be using a grid layout with CSS keycaps, and I think I want to have a coral accent color for the rule card.**Adjusting canvas dimensions**

I'm figuring out the dimensions for my canvas. I can use a max-width of 568, even though 648 is also available. Looks like 24 cells will mean about 23.67. That's good! For mobile content, I see a width of 343, with the stage at 321, which gives cell size around 13.4. It all seems to work well together. I also want to ensure the canvas is responsive and that the borders have a nice radius effect.**Designing a game indicator**

I'm working on a large score display with a live pulse indicator on the panel heading. Now I need to add a food card, using a `.tip-card` style with a red orb icon and the text "Eat a glowfruit. +10 points and a longer tail." Hmm, it feels a bit too game-like, but I think it's nice! I also want to include a status legend cell for the board foot without any screenshot cropping. Let’s get this output ready!