• Updated 30 Oct, 21:11 UTC
  • Next run 31 Oct, 05:17 UTCNext 31 Oct 05:17
  • Last 7 days: 4 new models · 475 runs7 d: 4 models · 475 runs
Playable Breakout

GPT-6.1 Terra ProOpenAI

Network offOpen alone (new tab)

Run details

Model
openai/gpt-6.1-terra-pro
Route
OpenRouter API, one user message
Served by
OpenAI
Effort
medium, OpenRouter's default for this model
Limits
60 min · 128,000 output tokens (the model's limit)
Date
27 Oct 2026
Time
1 min 42 s
Size
21 kB
Tokens
17,315 in, 16,398 out (2,283 of them thinking)
List price
$0.099 at today's price ($1 in / $5 out per M)
Paid
$0.099

Raw files: output · meta.json

Exact prompt

Make a Breakout game I can play right now with the keyboard or the mouse. Give it several levels, a score and some juice.

It must be one complete, self-contained HTML file: inline all CSS and JavaScript, and make no network requests of any kind (no CDNs, web fonts or remote images). Reply with the file in a single ```html code block.

Source

21 kB 718 lines Raw

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Neon Breakout</title>
<style>
  :root {
    color-scheme: dark;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    background: #080b18;
    color: #f4f6ff;
  }

  * { box-sizing: border-box; }

  body {
    margin: 0;
    min-height: 100vh;
    display: grid;
    place-items: center;
    padding: 20px;
    background:
      radial-gradient(circle at 20% 0%, #202050 0, transparent 35%),
      radial-gradient(circle at 90% 90%, #102b42 0, transparent 35%),
      #080b18;
  }

  .app { width: min(100%, 1020px); }

  header {
    display: flex;
    align-items: end;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 14px;
  }

  h1 {
    margin: 0;
    font-size: clamp(1.35rem, 4vw, 2.1rem);
    letter-spacing: .11em;
    line-height: 1;
  }

  h1 span { color: #71f4ee; }

  .subtitle {
    color: #a0a8c8;
    font-size: .82rem;
    margin-top: 7px;
  }

  .toolbar { display: flex; gap: 8px; }

  button {
    border: 1px solid #465172;
    border-radius: 9px;
    background: #1a2038;
    color: #eef4ff;
    padding: 9px 13px;
    font: inherit;
    font-size: .83rem;
    font-weight: 700;
    cursor: pointer;
  }

  button:hover { background: #2b3555; }
  button:focus-visible { outline: 3px solid #71f4ee; outline-offset: 3px; }

  .hud {
    display: flex;
    align-items: center;
    gap: 24px;
    padding: 12px 17px;
    border: 1px solid #333b5a;
    border-bottom: 0;
    border-radius: 14px 14px 0 0;
    background: #141a30;
  }

  .stat { display: flex; align-items: baseline; gap: 7px; }
  .label {
    color: #8d98bb;
    font-size: .68rem;
    font-weight: 800;
    letter-spacing: .12em;
    text-transform: uppercase;
  }
  .value { font-weight: 800; font-variant-numeric: tabular-nums; }
  #lives { color: #ff799e; }
  #combo { color: #ffd574; margin-left: auto; }

  .game-wrap {
    position: relative;
    overflow: hidden;
    border: 1px solid #333b5a;
    border-radius: 0 0 14px 14px;
    background: #0b1022;
    box-shadow: 0 20px 70px #0008;
  }

  canvas {
    display: block;
    width: 100%;
    aspect-ratio: 8 / 5;
    touch-action: none;
    cursor: crosshair;
  }

  .overlay {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    padding: 20px;
    text-align: center;
    background: #080c1acc;
    backdrop-filter: blur(5px);
  }

  .overlay[hidden] { display: none; }

  .card {
    max-width: 440px;
    padding: 26px;
    border: 1px solid #4b587b;
    border-radius: 18px;
    background: linear-gradient(145deg, #202944, #141a30);
    box-shadow: 0 20px 65px #0008;
  }

  .eyebrow {
    color: #71f4ee;
    font-size: .72rem;
    font-weight: 900;
    letter-spacing: .18em;
    text-transform: uppercase;
  }

  .card h2 { margin: 10px 0; font-size: clamp(1.5rem, 4vw, 2.3rem); }
  .card p { color: #bcc5e1; line-height: 1.55; margin: 0 0 18px; }
  .primary {
    color: #091323;
    background: #71f4ee;
    border-color: #71f4ee;
    padding: 12px 24px;
    font-size: .95rem;
  }
  .primary:hover { background: #adfff9; }

  .help {
    display: flex;
    justify-content: space-between;
    gap: 12px;
    color: #8f9ab9;
    font-size: .78rem;
    line-height: 1.5;
    margin-top: 12px;
  }

  @media (max-width: 560px) {
    body { padding: 10px; }
    header { align-items: center; }
    .subtitle { display: none; }
    .hud { gap: 12px; padding: 10px; }
    .stat { gap: 4px; }
    .label { font-size: .59rem; }
    .value { font-size: .9rem; }
    .help { display: block; }
    .card { padding: 18px; }
    .card p { font-size: .88rem; }
  }
</style>
</head>
<body>
<main class="app">
  <header>
    <div>
      <h1>NEON <span>BREAKOUT</span></h1>
      <div class="subtitle">Five levels. Three lives. One more bounce.</div>
    </div>
    <div class="toolbar">
      <button id="pauseButton" type="button" aria-label="Pause game">Pause</button>
      <button id="soundButton" type="button" aria-pressed="true">Sound: on</button>
    </div>
  </header>

  <div class="hud" aria-label="Game statistics">
    <div class="stat"><span class="label">Score</span><span class="value" id="score">0</span></div>
    <div class="stat"><span class="label">Level</span><span class="value" id="level">1 / 5</span></div>
    <div class="stat"><span class="label">Lives</span><span class="value" id="lives">♥ ♥ ♥</span></div>
    <div class="stat" id="combo"></div>
  </div>

  <div class="game-wrap">
    <canvas id="game" width="960" height="600" aria-label="Breakout game board"></canvas>
    <div class="overlay" id="overlay">
      <div class="card">
        <div class="eyebrow" id="overlayEyebrow">Ready to play?</div>
        <h2 id="overlayTitle">Break the glow.</h2>
        <p id="overlayText">Move with your mouse or the arrow keys. Click or press Space to launch. Clear all five levels without losing your three lives.</p>
        <button class="primary" id="actionButton" type="button">Start game</button>
      </div>
    </div>
  </div>

  <div class="help">
    <span>Move: mouse, touch, ← → or A / D &nbsp;·&nbsp; Launch: click or Space</span>
    <span>Pause: P or Esc</span>
  </div>
</main>

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

  const canvas = document.getElementById("game");
  const ctx = canvas.getContext("2d");
  const W = canvas.width, H = canvas.height;
  const scoreEl = document.getElementById("score");
  const levelEl = document.getElementById("level");
  const livesEl = document.getElementById("lives");
  const comboEl = document.getElementById("combo");
  const overlay = document.getElementById("overlay");
  const overlayEyebrow = document.getElementById("overlayEyebrow");
  const overlayTitle = document.getElementById("overlayTitle");
  const overlayText = document.getElementById("overlayText");
  const actionButton = document.getElementById("actionButton");
  const pauseButton = document.getElementById("pauseButton");
  const soundButton = document.getElementById("soundButton");

  const colors = ["#ff6b9a", "#ff9b71", "#ffd36e", "#75ead0", "#79bcff", "#b895ff", "#f58cff"];
  const stars = Array.from({ length: 65 }, () => ({
    x: Math.random() * W, y: Math.random() * H,
    r: .5 + Math.random() * 1.3, a: .12 + Math.random() * .35
  }));

  let state = "menu";
  let pausedState = "ready";
  let level = 1, score = 0, lives = 3, combo = 0;
  let bricks = [], particles = [], popups = [], trail = [];
  let shake = 0, flash = 0;
  let soundOn = true, audio = null;
  let lastTime = 0, clock = 0;
  const keys = new Set();
  const paddle = { x: W / 2, target: W / 2, y: 548, w: 130, h: 16 };
  const ball = { x: W / 2, y: 0, vx: 0, vy: 0, r: 9 };

  function sound(freq, duration = .08, type = "sine", volume = .045, endFreq = freq) {
    if (!soundOn) return;
    try {
      const AudioClass = window.AudioContext || window.webkitAudioContext;
      if (!AudioClass) return;
      if (!audio) audio = new AudioClass();
      if (audio.state === "suspended") audio.resume().catch(() => {});
      const now = audio.currentTime;
      const osc = audio.createOscillator();
      const gain = audio.createGain();
      osc.type = type;
      osc.frequency.setValueAtTime(freq, now);
      osc.frequency.exponentialRampToValueAtTime(Math.max(1, endFreq), now + duration);
      gain.gain.setValueAtTime(volume, now);
      gain.gain.exponentialRampToValueAtTime(.001, now + duration);
      osc.connect(gain);
      gain.connect(audio.destination);
      osc.start(now);
      osc.stop(now + duration);
    } catch (_) {}
  }

  function updateHUD() {
    scoreEl.textContent = score.toLocaleString();
    levelEl.textContent = level + " / 5";
    livesEl.textContent = "♥ ".repeat(lives).trim() || "—";
    comboEl.textContent = combo >= 2 ? "×" + combo + " COMBO" : "";
  }

  function showOverlay(eyebrow, title, text, buttonText, callback) {
    overlayEyebrow.textContent = eyebrow;
    overlayTitle.textContent = title;
    overlayText.textContent = text;
    actionButton.textContent = buttonText;
    actionButton.onclick = callback;
    overlay.hidden = false;
  }

  function hideOverlay() {
    overlay.hidden = true;
    canvas.focus();
  }

  function makeLevel(n) {
    const rows = [5, 6, 6, 7, 7][n - 1];
    const cols = 12, gap = 7, left = 42;
    const width = (W - left * 2 - gap * (cols - 1)) / cols;
    const result = [];
    for (let row = 0; row < rows; row++) {
      for (let col = 0; col < cols; col++) {
        let include = true;
        if (n === 2) include = !(row === 0 && (col < 2 || col > 9));
        if (n === 3) include = !((row + col) % 5 === 0);
        if (n === 4) include = !(row % 2 === 1 && (col === 0 || col === 11 || col === 5 || col === 6));
        if (n === 5) include = !((row === 0 || row === 6) && (col < 2 || col > 9));
        if (!include) continue;
        let hp = 1;
        if (n >= 2 && row < 2 && (col + row + n) % 3 === 0) hp = 2;
        if (n >= 4 && row === 0 && col % 3 === 1) hp = 3;
        result.push({
          x: left + col * (width + gap),
          y: 65 + row * 29,
          w: width, h: 22, hp, maxHp: hp,
          color: colors[(row + n - 1) % colors.length]
        });
      }
    }
    return result;
  }

  function resetBall() {
    paddle.x = paddle.target = W / 2;
    ball.x = paddle.x;
    ball.y = paddle.y - ball.r - 2;
    ball.vx = ball.vy = 0;
    trail = [];
    state = "ready";
    pauseButton.textContent = "Pause";
  }

  function startLevel() {
    bricks = makeLevel(level);
    particles = [];
    popups = [];
    combo = 0;
    paddle.w = Math.max(100, 130 - (level - 1) * 6);
    resetBall();
    updateHUD();
    hideOverlay();
  }

  function newGame() {
    level = 1;
    score = 0;
    lives = 3;
    startLevel();
  }

  function launch() {
    if (state !== "ready") return;
    const speed = 400 + (level - 1) * 25;
    ball.vx = speed * (Math.random() < .5 ? -.4 : .4);
    ball.vy = -Math.sqrt(speed * speed - ball.vx * ball.vx);
    state = "playing";
    sound(420, .12, "sine", .05, 700);
  }

  function togglePause() {
    if (state === "playing" || state === "ready") {
      pausedState = state;
      state = "paused";
      keys.clear();
      pauseButton.textContent = "Resume";
    } else if (state === "paused") {
      state = pausedState;
      pauseButton.textContent = "Pause";
    }
  }

  function burst(x, y, color, count = 12, power = 1) {
    for (let i = 0; i < count; i++) {
      const angle = Math.random() * Math.PI * 2;
      const speed = (60 + Math.random() * 230) * power;
      const life = .3 + Math.random() * .5;
      particles.push({
        x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed,
        life, maxLife: life, size: 2 + Math.random() * 4, color
      });
    }
    if (particles.length > 450) particles.splice(0, particles.length - 450);
  }

  function loseLife() {
    lives--;
    combo = 0;
    shake = 12;
    flash = .28;
    burst(ball.x, H - 15, "#ff6b9a", 20);
    sound(220, .35, "sawtooth", .035, 65);
    updateHUD();
    if (lives <= 0) {
      state = "gameover";
      showOverlay("Game over", "Nice run.", "Final score: " + score.toLocaleString() + ". Ready for another shot?", "Play again", newGame);
    } else {
      resetBall();
    }
  }

  function clearLevel() {
    state = "levelclear";
    score += level * 100;
    updateHUD();
    sound(520, .18, "sine", .05, 880);
    setTimeout(() => sound(780, .25, "sine", .04, 1100), 120);
    if (level === 5) {
      state = "won";
      showOverlay("All levels cleared", "You broke the glow!", "Brilliant work. Final score: " + score.toLocaleString() + ".", "Play again", newGame);
    } else {
      showOverlay("Level " + level + " complete", "Looking good.", "Level bonus: " + (level * 100) + " points. Get ready for the next round.", "Next level", () => {
        level++;
        startLevel();
      });
    }
  }

  function hitBrick(brick, index) {
    brick.hp--;
    score += 10;
    burst(ball.x, ball.y, brick.color, brick.hp ? 5 : 13, brick.hp ? .6 : 1);
    sound(brick.hp ? 350 : 480 + Math.min(combo, 10) * 30, .07, "triangle", .035, 650);
    if (brick.hp <= 0) {
      bricks.splice(index, 1);
      combo++;
      const bonus = 20 + Math.min(combo - 1, 10) * 5;
      score += bonus;
      popups.push({ x: brick.x + brick.w / 2, y: brick.y, text: "+" + (10 + bonus), life: .8 });
      shake = Math.min(8, shake + 2);
      if (!bricks.length) clearLevel();
    }
    updateHUD();
  }

  function updateBall(dt) {
    const speed = Math.hypot(ball.vx, ball.vy);
    const steps = Math.max(1, Math.ceil(speed * dt / (ball.r * .55)));
    const step = dt / steps;

    for (let s = 0; s < steps && state === "playing"; s++) {
      const oldY = ball.y;
      ball.x += ball.vx * step;
      ball.y += ball.vy * step;

      if (ball.x < ball.r) {
        ball.x = ball.r;
        ball.vx = Math.abs(ball.vx);
        sound(240, .035, "sine", .025);
      } else if (ball.x > W - ball.r) {
        ball.x = W - ball.r;
        ball.vx = -Math.abs(ball.vx);
        sound(240, .035, "sine", .025);
      }
      if (ball.y < ball.r) {
        ball.y = ball.r;
        ball.vy = Math.abs(ball.vy);
        sound(270, .035, "sine", .025);
      }

      if (ball.vy > 0 && oldY + ball.r <= paddle.y + 3 &&
          ball.y + ball.r >= paddle.y &&
          ball.x >= paddle.x - paddle.w / 2 - ball.r &&
          ball.x <= paddle.x + paddle.w / 2 + ball.r) {
        ball.y = paddle.y - ball.r;
        const offset = Math.max(-1, Math.min(1, (ball.x - paddle.x) / (paddle.w / 2)));
        const currentSpeed = Math.min(650, Math.hypot(ball.vx, ball.vy) + 5);
        const angle = offset * 1.08;
        ball.vx = currentSpeed * Math.sin(angle);
        ball.vy = -currentSpeed * Math.cos(angle);
        combo = 0;
        updateHUD();
        burst(ball.x, paddle.y, "#71f4ee", 7, .5);
        sound(320, .07, "triangle", .04, 450);
      }

      for (let i = bricks.length - 1; i >= 0; i--) {
        const b = bricks[i];
        const nearX = Math.max(b.x, Math.min(ball.x, b.x + b.w));
        const nearY = Math.max(b.y, Math.min(ball.y, b.y + b.h));
        const dx = ball.x - nearX, dy = ball.y - nearY;
        if (dx * dx + dy * dy > ball.r * ball.r) continue;

        const overlapLeft = ball.x + ball.r - b.x;
        const overlapRight = b.x + b.w - (ball.x - ball.r);
        const overlapTop = ball.y + ball.r - b.y;
        const overlapBottom = b.y + b.h - (ball.y - ball.r);
        const minOverlap = Math.min(overlapLeft, overlapRight, overlapTop, overlapBottom);

        if (minOverlap === overlapLeft) {
          ball.x = b.x - ball.r;
          ball.vx = -Math.abs(ball.vx);
        } else if (minOverlap === overlapRight) {
          ball.x = b.x + b.w + ball.r;
          ball.vx = Math.abs(ball.vx);
        } else if (minOverlap === overlapTop) {
          ball.y = b.y - ball.r;
          ball.vy = -Math.abs(ball.vy);
        } else {
          ball.y = b.y + b.h + ball.r;
          ball.vy = Math.abs(ball.vy);
        }
        hitBrick(b, i);
        break;
      }

      if (ball.y > H + ball.r) {
        loseLife();
        break;
      }
    }
  }

  function update(dt) {
    clock += dt;
    if (state === "playing" || state === "ready") {
      const direction = (keys.has("arrowright") || keys.has("d") ? 1 : 0) -
                        (keys.has("arrowleft") || keys.has("a") ? 1 : 0);
      if (direction) paddle.target += direction * 760 * dt;
      paddle.target = Math.max(paddle.w / 2, Math.min(W - paddle.w / 2, paddle.target));
      const distance = paddle.target - paddle.x;
      paddle.x += Math.max(-950 * dt, Math.min(950 * dt, distance));

      if (state === "ready") {
        ball.x = paddle.x;
        ball.y = paddle.y - ball.r - 2;
      } else {
        updateBall(dt);
        trail.push({ x: ball.x, y: ball.y, life: .18 });
        if (trail.length > 15) trail.shift();
      }
    }

    if (state !== "paused") {
      for (const t of trail) t.life -= dt;
      trail = trail.filter(t => t.life > 0);
      for (const p of particles) {
        p.life -= dt;
        p.x += p.vx * dt;
        p.y += p.vy * dt;
        p.vy += 180 * dt;
        p.vx *= Math.pow(.18, dt);
      }
      particles = particles.filter(p => p.life > 0);
      for (const p of popups) {
        p.life -= dt;
        p.y -= 45 * dt;
      }
      popups = popups.filter(p => p.life > 0);
      shake = Math.max(0, shake - 25 * dt);
      flash = Math.max(0, flash - dt);
    }
  }

  function roundedRect(x, y, w, h, r) {
    r = Math.min(r, w / 2, h / 2);
    ctx.beginPath();
    ctx.moveTo(x + r, y);
    ctx.arcTo(x + w, y, x + w, y + h, r);
    ctx.arcTo(x + w, y + h, x, y + h, r);
    ctx.arcTo(x, y + h, x, y, r);
    ctx.arcTo(x, y, x + w, y, r);
    ctx.closePath();
  }

  function draw() {
    ctx.clearRect(0, 0, W, H);
    const bg = ctx.createLinearGradient(0, 0, 0, H);
    bg.addColorStop(0, "#151a35");
    bg.addColorStop(1, "#090e1d");
    ctx.fillStyle = bg;
    ctx.fillRect(0, 0, W, H);

    for (const star of stars) {
      ctx.globalAlpha = star.a * (.7 + .3 * Math.sin(clock * 1.5 + star.x));
      ctx.fillStyle = "#a8caff";
      ctx.fillRect(star.x, star.y, star.r, star.r);
    }
    ctx.globalAlpha = 1;

    ctx.save();
    if (shake > 0) ctx.translate((Math.random() - .5) * shake, (Math.random() - .5) * shake);

    ctx.strokeStyle = "#ffffff0a";
    ctx.lineWidth = 1;
    for (let x = 0; x < W; x += 60) {
      ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke();
    }
    for (let y = 0; y < H; y += 60) {
      ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke();
    }

    for (const b of bricks) {
      ctx.shadowColor = b.color;
      ctx.shadowBlur = 10;
      ctx.fillStyle = b.color;
      roundedRect(b.x, b.y, b.w, b.h, 5);
      ctx.fill();
      ctx.shadowBlur = 0;
      ctx.fillStyle = "#ffffff55";
      roundedRect(b.x + 3, b.y + 3, b.w - 6, 3, 2);
      ctx.fill();
      if (b.hp > 1) {
        ctx.fillStyle = "#11172c99";
        for (let i = 0; i < b.hp; i++) {
          ctx.beginPath();
          ctx.arc(b.x + b.w / 2 + (i - (b.hp - 1) / 2) * 8, b.y + 14, 2, 0, Math.PI * 2);
          ctx.fill();
        }
      }
    }

    for (const t of trail) {
      ctx.globalAlpha = Math.max(0, t.life / .18) * .35;
      ctx.fillStyle = "#a7ffff";
      ctx.beginPath(); ctx.arc(t.x, t.y, ball.r * (t.life / .18), 0, Math.PI * 2); ctx.fill();
    }
    ctx.globalAlpha = 1;

    ctx.shadowColor = "#71f4ee";
    ctx.shadowBlur = 22;
    ctx.fillStyle = "#71f4ee";
    roundedRect(paddle.x - paddle.w / 2, paddle.y, paddle.w, paddle.h, 8);
    ctx.fill();
    ctx.shadowBlur = 0;
    ctx.fillStyle = "#e6ffff";
    roundedRect(paddle.x - paddle.w / 2 + 9, paddle.y + 3, paddle.w - 18, 3, 2);
    ctx.fill();

    ctx.shadowColor = "#9affff";
    ctx.shadowBlur = 20;
    ctx.fillStyle = "#f1ffff";
    ctx.beginPath(); ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2); ctx.fill();
    ctx.shadowBlur = 0;

    for (const p of particles) {
      ctx.globalAlpha = Math.max(0, p.life / p.maxLife);
      ctx.fillStyle = p.color;
      ctx.fillRect(p.x, p.y, p.size, p.size);
    }
    ctx.globalAlpha = 1;

    ctx.textAlign = "center";
    ctx.font = "bold 15px system-ui, sans-serif";
    for (const p of popups) {
      ctx.globalAlpha = Math.min(1, p.life * 2);
      ctx.fillStyle = "#ffffff";
      ctx.fillText(p.text, p.x, p.y);
    }
    ctx.globalAlpha = 1;
    ctx.restore();

    if (flash > 0) {
      ctx.fillStyle = "rgba(255, 75, 120, " + (flash * .5) + ")";
      ctx.fillRect(0, 0, W, H);
    }

    if (state === "ready" || state === "paused") {
      ctx.textAlign = "center";
      ctx.font = "700 19px system-ui, sans-serif";
      ctx.fillStyle = "#d8e6ff";
      ctx.fillText(state === "paused" ? "PAUSED" : "CLICK OR PRESS SPACE TO LAUNCH", W / 2, H - 72);
    }
  }

  function frame(time) {
    const dt = lastTime ? Math.min((time - lastTime) / 1000, .033) : 0;
    lastTime = time;
    update(dt);
    draw();
    requestAnimationFrame(frame);
  }

  function movePointer(event) {
    const rect = canvas.getBoundingClientRect();
    paddle.target = (event.clientX - rect.left) * W / rect.width;
  }

  canvas.tabIndex = 0;
  canvas.addEventListener("pointermove", movePointer);
  canvas.addEventListener("pointerdown", event => {
    movePointer(event);
    if (event.pointerType === "touch") canvas.setPointerCapture(event.pointerId);
    if (state === "ready") launch();
  });

  window.addEventListener("keydown", event => {
    const key = event.key.toLowerCase();
    if (["arrowleft", "arrowright", " ", "a", "d"].includes(key)) {
      if (overlay.hidden) event.preventDefault();
      keys.add(key);
    }
    if (event.repeat) return;
    if (key === " " && overlay.hidden) launch();
    if ((key === "p" || key === "escape") && overlay.hidden) {
      event.preventDefault();
      togglePause();
    }
  });
  window.addEventListener("keyup", event => keys.delete(event.key.toLowerCase()));
  window.addEventListener("blur", () => {
    keys.clear();
    if (state === "playing") togglePause();
  });

  pauseButton.addEventListener("click", togglePause);
  soundButton.addEventListener("click", () => {
    soundOn = !soundOn;
    soundButton.textContent = "Sound: " + (soundOn ? "on" : "off");
    soundButton.setAttribute("aria-pressed", String(soundOn));
    if (soundOn) sound(520, .08);
  });

  actionButton.onclick = newGame;
  updateHUD();
  resetBall();
  state = "menu";
  requestAnimationFrame(frame);
})();
</script>
</body>
</html>

Compare with

GPT-6.1 Terra Pro: other attempts