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
9 min 5 s
Cost
$0.031
Output tokens
60,995
Spent thinking
47,374 tokens
Speed
112.7 tok/s
Console errors
0
First token after
4.0 s
First visible text after
7 min 34 s
Prompt tokens
357
Code size
39.9 KB
Finish reason
stop
Served by
OpenAI
Model id
openai/gpt-6-luna
Reasoning sent
{"effort":"max"}
Max output
64,000 tokens
Generated
Nov 10, 2026

What it was asked

The prompt
“i want a breakout game where the paddle is a hamburger and every brick you break explodes into tons of little hearts”
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 · 1421 lines

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Burger Breakout — Pixel Diner Arcade</title>
<style>
  :root {
    color-scheme: dark;
    --ink: #130f19;
    --panel: #1e1926;
    --cream: #fff1d6;
    --muted: #a99cac;
    --pink: #ff718c;
    --orange: #ffbb67;
    --line: rgba(255, 241, 224, .12);
  }

  * { box-sizing: border-box; }

  html, body {
    margin: 0;
    min-height: 100%;
  }

  body {
    min-height: 100vh;
    overflow-x: hidden;
    color: var(--cream);
    font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
    background:
      radial-gradient(ellipse at 48% -20%, rgba(111, 49, 91, .35), transparent 48%),
      radial-gradient(ellipse at 3% 85%, rgba(57, 48, 106, .16), transparent 39%),
      #120f18;
  }

  body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    opacity: .2;
    background-image:
      linear-gradient(rgba(255,255,255,.025) 1px, transparent 1px),
      linear-gradient(90deg, rgba(255,255,255,.025) 1px, transparent 1px);
    background-size: 42px 42px;
    mask-image: linear-gradient(to bottom, black, transparent 90%);
  }

  .page {
    position: relative;
    z-index: 1;
    width: min(1160px, calc(100% - 48px));
    margin: 0 auto;
    padding: 22px 0 15px;
  }

  .hero {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 18px;
    min-height: 56px;
    margin-bottom: 17px;
  }

  .brand {
    display: flex;
    align-items: center;
    gap: 11px;
    min-width: 0;
  }

  .brand-mark {
    flex: none;
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    overflow: hidden;
    border: 1px solid rgba(255, 231, 195, .26);
    border-radius: 15px;
    background: linear-gradient(145deg, #fb8b7e, #e95578 65%, #a93e72);
    box-shadow: 0 7px 24px rgba(245, 94, 130, .22), inset 0 1px rgba(255,255,255,.35);
  }

  .brand-mark svg {
    display: block;
    width: 36px;
    height: 36px;
  }

  .brand-copy { min-width: 0; }

  .brand-kicker {
    display: flex;
    align-items: center;
    gap: 7px;
    color: #b7a6b7;
    font-size: 9px;
    font-weight: 800;
    letter-spacing: 1.75px;
    line-height: 1;
    text-transform: uppercase;
  }

  .kicker-star {
    color: var(--orange);
    font-size: 10px;
  }

  .brand-title {
    margin: 4px 0 0;
    color: #fff4df;
    font-size: clamp(21px, 2.65vw, 31px);
    font-weight: 950;
    letter-spacing: -1.45px;
    line-height: .98;
    white-space: nowrap;
  }

  .brand-title span { color: #ff8397; }

  .hero-right {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 6px;
    flex: none;
  }

  .hero-note {
    color: #928499;
    font-size: 9px;
    font-weight: 800;
    letter-spacing: 1.65px;
    text-transform: uppercase;
  }

  .live-pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 8px 12px;
    border: 1px solid rgba(255, 255, 255, .13);
    border-radius: 999px;
    background: rgba(255, 255, 255, .045);
    color: #f6e8da;
    font-size: 9px;
    font-weight: 850;
    letter-spacing: 1.1px;
    white-space: nowrap;
  }

  .live-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #88edb7;
    box-shadow: 0 0 10px rgba(136, 237, 183, .75);
    animation: pulse 1.6s ease-in-out infinite;
  }

  .live-pill.is-player .live-dot {
    background: #ffb766;
    box-shadow: 0 0 10px rgba(255, 183, 102, .75);
  }

  @keyframes pulse {
    50% { opacity: .45; transform: scale(.78); }
  }

  .game-shell {
    overflow: hidden;
    border: 1px solid rgba(255, 239, 222, .14);
    border-radius: 21px;
    background: #1c1723;
    box-shadow:
      0 28px 90px rgba(0, 0, 0, .42),
      0 2px 0 rgba(255,255,255,.025) inset;
  }

  .game-hud {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    align-items: center;
    height: 58px;
    padding: 0 22px;
    background: linear-gradient(180deg, #241e2b, #201a27);
    border-bottom: 1px solid rgba(255,255,255,.085);
  }

  .hud-stage {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
  }

  .stage-number {
    flex: none;
    display: grid;
    place-items: center;
    width: 34px;
    height: 34px;
    border: 1px solid rgba(255, 199, 116, .32);
    border-radius: 11px;
    background: linear-gradient(145deg, rgba(255,190,103,.2), rgba(255,116,142,.12));
    color: #ffd28b;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 13px;
    font-weight: 900;
    box-shadow: inset 0 1px rgba(255,255,255,.1);
  }

  .hud-copy, .hud-score, .hud-lives {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
  }

  .hud-copy span, .hud-score span, .hud-lives > span {
    color: #96899c;
    font-size: 8px;
    font-weight: 850;
    letter-spacing: 1.45px;
    line-height: 1;
    text-transform: uppercase;
  }

  .hud-copy strong {
    overflow: hidden;
    color: #f5e9dc;
    font-size: 11px;
    font-weight: 850;
    letter-spacing: .55px;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .hud-score {
    align-items: center;
    justify-content: center;
    height: 34px;
    border-right: 1px solid var(--line);
    border-left: 1px solid var(--line);
  }

  .hud-score strong {
    color: #ffd088;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 18px;
    font-weight: 900;
    letter-spacing: 1.15px;
    line-height: 1;
    text-shadow: 0 0 16px rgba(255, 183, 100, .2);
  }

  .hud-lives {
    align-items: flex-end;
    justify-content: center;
    gap: 5px;
  }

  .life-icons {
    display: flex;
    align-items: center;
    gap: 4px;
    height: 17px;
  }

  .life-heart {
    width: 15px;
    height: 15px;
    fill: #514451;
    transition: fill .2s ease, filter .2s ease;
  }

  .life-heart.on {
    fill: #ff7e96;
    filter: drop-shadow(0 0 4px rgba(255, 107, 143, .46));
  }

  .game-stage {
    position: relative;
    width: 100%;
    aspect-ratio: 2 / 1;
    overflow: hidden;
    background: #211a2b;
    touch-action: none;
  }

  canvas {
    display: block;
    width: 100%;
    height: 100%;
    touch-action: none;
  }

  .start-prompt {
    position: absolute;
    top: 72%;
    left: 50%;
    z-index: 2;
    display: flex;
    width: 330px;
    max-width: calc(100% - 32px);
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 11px 16px 12px;
    transform: translate(-50%, -50%);
    border: 1px solid rgba(255, 232, 217, .25);
    border-radius: 15px;
    background: linear-gradient(145deg, rgba(39, 25, 42, .86), rgba(26, 21, 32, .84));
    box-shadow: 0 13px 34px rgba(0,0,0,.27), inset 0 1px rgba(255,255,255,.08);
    color: #fff3df;
    cursor: pointer;
    font: inherit;
    text-align: center;
    backdrop-filter: blur(6px);
    transition: border-color .18s ease, background .18s ease, transform .18s ease;
  }

  .start-prompt:hover {
    border-color: rgba(255, 202, 132, .62);
    background: linear-gradient(145deg, rgba(48, 29, 47, .92), rgba(31, 23, 37, .9));
    transform: translate(-50%, -52%);
  }

  .start-prompt:focus-visible {
    outline: 2px solid #ffd08a;
    outline-offset: 4px;
  }

  .start-prompt[hidden] { display: none; }

  .overlay-kicker {
    display: flex;
    align-items: center;
    gap: 6px;
    color: #c6a9bb;
    font-size: 8px;
    font-weight: 850;
    letter-spacing: 1.45px;
    line-height: 1;
    text-transform: uppercase;
  }

  .overlay-kicker i {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #87edb8;
    box-shadow: 0 0 8px rgba(135,237,184,.7);
  }

  .overlay-title {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    color: #fff1d8;
    font-size: 11px;
    font-weight: 950;
    letter-spacing: .8px;
    white-space: nowrap;
  }

  .play-icon {
    display: grid;
    place-items: center;
    width: 21px;
    height: 21px;
    padding-left: 2px;
    border: 1px solid rgba(255, 211, 151, .4);
    border-radius: 50%;
    background: rgba(255, 185, 102, .16);
    color: #ffd18a;
    font-size: 8px;
  }

  .overlay-sub {
    color: #a999a9;
    font-size: 9px;
    font-weight: 650;
  }

  .game-footer {
    display: grid;
    grid-template-columns: 1fr 1.2fr 1fr;
    align-items: center;
    height: 38px;
    padding: 0 19px;
    border-top: 1px solid rgba(255,255,255,.075);
    background: linear-gradient(180deg, #201a27, #1c1723);
  }

  .footer-controls, .footer-hearts, .footer-takeover {
    display: flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    color: #b4a5b4;
    font-size: 8px;
    font-weight: 850;
    letter-spacing: 1px;
    text-transform: uppercase;
    white-space: nowrap;
  }

  .footer-controls kbd {
    display: grid;
    place-items: center;
    width: 19px;
    height: 19px;
    border: 1px solid rgba(255,255,255,.15);
    border-bottom-color: rgba(255,255,255,.25);
    border-radius: 5px;
    background: rgba(255,255,255,.055);
    color: #f3dfd5;
    font-family: inherit;
    font-size: 11px;
    line-height: 1;
  }

  .footer-controls span { margin-left: 3px; }

  .footer-hearts { justify-content: center; color: #d6b9c8; }

  .footer-hearts svg {
    flex: none;
    width: 13px;
    height: 13px;
    fill: #ff7992;
    filter: drop-shadow(0 0 4px rgba(255, 111, 145, .38));
  }

  .footer-takeover { justify-content: flex-end; color: #cdb6a4; }
  .takeover-spark { color: #ffbd69; font-size: 12px; }

  @media (max-width: 680px) {
    .page {
      width: calc(100% - 24px);
      padding-top: 14px;
      padding-bottom: 14px;
    }

    .hero {
      gap: 7px;
      min-height: 42px;
      margin-bottom: 12px;
    }

    .brand { gap: 8px; }
    .brand-mark { width: 38px; height: 38px; border-radius: 12px; }
    .brand-mark svg { width: 30px; height: 30px; }
    .brand-kicker { gap: 5px; font-size: 7px; letter-spacing: 1.15px; }
    .brand-title { margin-top: 4px; font-size: clamp(19px, 5.4vw, 26px); letter-spacing: -1.2px; }
    .hero-right { gap: 0; }
    .hero-note { display: none; }
    .live-pill { gap: 6px; padding: 7px 9px; font-size: 8px; letter-spacing: .8px; }
    .live-dot { width: 6px; height: 6px; }

    .game-shell { border-radius: 16px; }
    .game-hud {
      grid-template-columns: 1.18fr .78fr .95fr;
      height: 53px;
      padding: 0 10px;
    }
    .hud-stage { gap: 6px; }
    .stage-number { width: 29px; height: 29px; border-radius: 9px; font-size: 11px; }
    .hud-copy { gap: 3px; }
    .hud-copy span, .hud-score span, .hud-lives > span {
      font-size: 7px;
      letter-spacing: .85px;
    }
    .hud-copy strong { font-size: 9px; letter-spacing: .2px; }
    .hud-score { height: 31px; }
    .hud-score strong { font-size: 14px; letter-spacing: .45px; }
    .hud-lives { gap: 4px; }
    .life-icons { gap: 2px; height: 14px; }
    .life-heart { width: 12px; height: 12px; }

    .game-stage { aspect-ratio: 1000 / 650; }

    .start-prompt {
      top: 72%;
      gap: 5px;
      padding: 9px 12px 10px;
      border-radius: 12px;
    }
    .overlay-title { gap: 6px; font-size: 9.5px; letter-spacing: .55px; }
    .play-icon { width: 19px; height: 19px; }
    .overlay-kicker { font-size: 7px; letter-spacing: 1.1px; }
    .overlay-sub { font-size: 8px; }

    .game-footer {
      grid-template-columns: 1fr 1.35fr;
      height: 35px;
      padding: 0 9px;
    }
    .footer-controls, .footer-hearts, .footer-takeover {
      gap: 4px;
      font-size: 7px;
      letter-spacing: .55px;
    }
    .footer-controls kbd { width: 16px; height: 16px; font-size: 9px; border-radius: 4px; }
    .footer-controls span { margin-left: 2px; }
    .footer-hearts svg { width: 11px; height: 11px; }
    .footer-takeover { display: none; }
  }

  @media (max-width: 390px) {
    .brand-title { font-size: 20px; }
    .brand-kicker { font-size: 6.7px; }
    .live-pill { padding-right: 7px; padding-left: 7px; }
    .hud-copy strong { font-size: 8.5px; }
    .footer-controls, .footer-hearts { font-size: 6.6px; }
  }
</style>
</head>
<body>
<main class="page">
  <header class="hero">
    <div class="brand">
      <div class="brand-mark" aria-hidden="true">
        <svg viewBox="0 0 48 48" fill="none">
          <path d="M10 22c.6-7.7 6.2-12.3 14-12.3S37.4 14.3 38 22H10Z" fill="#FFD891"/>
          <path d="M10 22h28v3H10z" fill="#F6B958"/>
          <ellipse cx="18" cy="16" rx="1.2" ry="2" transform="rotate(30 18 16)" fill="#FFF1CF"/>
          <ellipse cx="24" cy="13.8" rx="1.2" ry="2" transform="rotate(-14 24 13.8)" fill="#FFF1CF"/>
          <ellipse cx="30" cy="16" rx="1.2" ry="2" transform="rotate(24 30 16)" fill="#FFF1CF"/>
          <path d="M8.5 25h31v5H8.5z" rx="2.5" fill="#6D3D31"/>
          <path d="M9 27.8h30l-3.3 6.5-6.5-2.8-5.4 4-5.5-3.8-6.3 2.5L9 27.8Z" fill="#FFCE56"/>
          <path d="M8 32c4.4-2.1 6.1 2 9.2 0 2.8-1.8 4.5 1.9 7.5.4 3-1.5 4.8-1.8 7.4.1 2.9 2.2 5.4-.4 7.9-.5l-2.3 5H10.3L8 32Z" fill="#53C995"/>
          <path d="M11 37.5h26l-2.1 3.8H13.2L11 37.5Z" fill="#EFB666"/>
        </svg>
      </div>
      <div class="brand-copy">
        <div class="brand-kicker"><span class="kicker-star">✦</span> Pixel Diner · Arcade 01</div>
        <h1 class="brand-title">BURGER <span>BREAKOUT</span></h1>
      </div>
    </div>
    <div class="hero-right">
      <div class="hero-note">Fresh buns. Big feelings.</div>
      <div class="live-pill" id="modePill">
        <span class="live-dot" aria-hidden="true"></span>
        <span id="modeLabel">DEMO LIVE</span>
      </div>
    </div>
  </header>

  <section class="game-shell" aria-label="Burger Breakout arcade game">
    <div class="game-hud">
      <div class="hud-stage">
        <div class="stage-number" id="levelValue">01</div>
        <div class="hud-copy">
          <span>Now serving</span>
          <strong>The Heart Stack</strong>
        </div>
      </div>
      <div class="hud-score">
        <span>Burger bounty</span>
        <strong id="scoreValue">000000</strong>
      </div>
      <div class="hud-lives">
        <span>Extra lives</span>
        <div class="life-icons" id="lifeIcons" aria-label="3 lives"></div>
      </div>
    </div>

    <div class="game-stage" id="gameStage">
      <canvas id="gameCanvas" aria-label="Breakout game with a hamburger paddle and heart-exploding bricks">
        Burger Breakout. Use the left and right arrow keys or your mouse to move the hamburger paddle.
      </canvas>
      <button class="start-prompt" id="startPrompt" type="button" aria-label="Click or press a key to start playing">
        <span class="overlay-kicker"><i aria-hidden="true"></i> The demo is playing itself</span>
        <span class="overlay-title">
          <span class="play-icon" aria-hidden="true">▶</span>
          <span>CLICK OR PRESS A KEY TO START</span>
        </span>
        <span class="overlay-sub">Take over with ← → or your mouse</span>
      </button>
    </div>

    <div class="game-footer">
      <div class="footer-controls">
        <kbd>←</kbd><kbd>→</kbd><span>Move paddle</span>
      </div>
      <div class="footer-hearts">
        <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21s-8.1-4.8-9.8-10C.7 6.7 3.2 3 7 3c2.2 0 3.8 1.2 5 3 1.2-1.8 2.8-3 5-3 3.8 0 6.3 3.7 4.8 8C20.1 16.2 12 21 12 21Z"/></svg>
        <span>Every brick pops into hearts</span>
      </div>
      <div class="footer-takeover"><span class="takeover-spark">✦</span><span id="takeoverText">Click / key to play</span></div>
    </div>
  </section>
</main>

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

  const W = 1000;
  let H = 500;
  let cssScale = 1;
  let stars = [];
  let bricks = [];
  let hearts = [];
  let rings = [];
  let remaining = 0;
  let score = 0;
  let level = 1;
  let lives = 3;
  let mode = "demo";
  let gameReady = false;
  let pointerControl = false;
  let levelToast = 0;
  let previousTime = 0;

  const canvas = document.getElementById("gameCanvas");
  const ctx = canvas.getContext("2d");
  const gameStage = document.getElementById("gameStage");
  const startPrompt = document.getElementById("startPrompt");
  const modePill = document.getElementById("modePill");
  const modeLabel = document.getElementById("modeLabel");
  const scoreValue = document.getElementById("scoreValue");
  const levelValue = document.getElementById("levelValue");
  const lifeIcons = document.getElementById("lifeIcons");
  const takeoverText = document.getElementById("takeoverText");

  const palette = [
    { top: "#ff9eaa", mid: "#ff6d85", bottom: "#d8496d", glow: "rgba(255,94,131,.38)" },
    { top: "#ffc18b", mid: "#ff984e", bottom: "#d8663d", glow: "rgba(255,151,76,.34)" },
    { top: "#ffe48a", mid: "#ffc34d", bottom: "#d99638", glow: "rgba(255,201,80,.32)" },
    { top: "#8ce7b6", mid: "#4dd5a1", bottom: "#299d82", glow: "rgba(68,218,165,.32)" },
    { top: "#8ce2ee", mid: "#54c9e5", bottom: "#3889b3", glow: "rgba(84,201,229,.3)" },
    { top: "#b6a1f6", mid: "#9275e8", bottom: "#654caf", glow: "rgba(151,117,238,.32)" },
    { top: "#ffa7d2", mid: "#f17cbd", bottom: "#bd5797", glow: "rgba(241,124,189,.32)" }
  ];

  const heartColors = ["#ff7898", "#ff9fba", "#ffd0df", "#ff657f", "#ffe0a1", "#f889cc"];
  const paddle = { x: W / 2, y: 444, targetX: W / 2, vx: 0, bop: 0 };
  const ball = { x: W / 2, y: 410, vx: 4.2, vy: -6.2, r: 7, trail: [] };
  const keys = { left: false, right: false };
  const paddleHalf = 58;

  function clamp(value, min, max) {
    return Math.max(min, Math.min(max, value));
  }

  function sy() {
    return H / 500;
  }

  function roundedRectPath(context, x, y, width, height, radius) {
    const r = Math.max(0, Math.min(radius, width / 2, height / 2));
    context.beginPath();
    context.moveTo(x + r, y);
    context.lineTo(x + width - r, y);
    context.arcTo(x + width, y, x + width, y + r, r);
    context.lineTo(x + width, y + height - r);
    context.arcTo(x + width, y + height, x + width - r, y + height, r);
    context.lineTo(x + r, y + height);
    context.arcTo(x, y + height, x, y + height - r, r);
    context.lineTo(x, y + r);
    context.arcTo(x, y, x + r, y, r);
    context.closePath();
  }

  function makeStars() {
    stars = [];
    let seed = 4801 + Math.round(H);
    const seededRandom = () => {
      seed = (seed * 1664525 + 1013904223) >>> 0;
      return seed / 4294967296;
    };
    for (let i = 0; i < 48; i++) {
      stars.push({
        x: 35 + seededRandom() * (W - 70),
        y: 32 * sy() + seededRandom() * Math.max(1, H - 64 * sy()),
        r: .65 + seededRandom() * 1.35,
        phase: seededRandom() * Math.PI * 2
      });
    }
  }

  function fitCanvas() {
    const rect = gameStage.getBoundingClientRect();
    if (!rect.width || !rect.height) return;

    const oldH = H;
    H = W * rect.height / rect.width;
    cssScale = rect.width / W;
    const verticalScale = sy();

    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    canvas.width = Math.max(1, Math.round(rect.width * dpr));
    canvas.height = Math.max(1, Math.round(rect.height * dpr));
    ctx.setTransform(canvas.width / W, 0, 0, canvas.height / H, 0, 0);
    ctx.imageSmoothingEnabled = true;
    ctx.lineCap = "round";
    ctx.lineJoin = "round";

    paddle.y = H - 56 * verticalScale;
    paddle.x = clamp(paddle.x, 34 + paddleHalf, W - 34 - paddleHalf);
    paddle.targetX = clamp(paddle.targetX, 34 + paddleHalf, W - 34 - paddleHalf);
    ball.r = 7 * verticalScale;

    if (gameReady && oldH > 0) {
      const ratio = H / oldH;
      if (Math.abs(ratio - 1) > .001) {
        ball.y *= ratio;
        ball.vy *= ratio;
        for (const point of ball.trail) point.y *= ratio;
        for (const particle of hearts) {
          particle.y *= ratio;
          particle.vy *= ratio;
        }
        for (const ring of rings) ring.y *= ratio;
      }
      for (const brick of bricks) {
        brick.y = (56 + brick.row * 30) * verticalScale;
        brick.h = 22 * verticalScale;
      }
    }

    makeStars();
  }

  function makeBricks() {
    const rows = 7;
    const cols = 10;
    const margin = 60;
    const gap = 9;
    const brickWidth = (W - margin * 2 - gap * (cols - 1)) / cols;
    const verticalScale = sy();
    const result = [];

    for (let row = 0; row < rows; row++) {
      for (let col = 0; col < cols; col++) {
        result.push({
          x: margin + col * (brickWidth + gap),
          y: (56 + row * 30) * verticalScale,
          w: brickWidth,
          h: 22 * verticalScale,
          row,
          color: palette[(row + (level - 1) % palette.length) % palette.length],
          alive: true
        });
      }
    }
    remaining = result.length;
    return result;
  }

  function resetBall() {
    const verticalScale = sy();
    ball.x = paddle.x;
    ball.y = paddle.y - 34 * verticalScale;
    ball.vx = (Math.random() < .5 ? -1 : 1) * (3.4 + Math.random() * 1.4);
    ball.vy = -6.2 * verticalScale;
    ball.r = 7 * verticalScale;
    ball.trail.length = 0;
  }

  function updateHud() {
    scoreValue.textContent = String(score).padStart(6, "0");
    levelValue.textContent = String(level).padStart(2, "0");
    lifeIcons.setAttribute("aria-label", lives + (lives === 1 ? " life" : " lives"));
    lifeIcons.innerHTML = [0, 1, 2].map(i =>
      `<svg class="life-heart ${i < lives ? "on" : ""}" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21s-8.1-4.8-9.8-10C.7 6.7 3.2 3 7 3c2.2 0 3.8 1.2 5 3 1.2-1.8 2.8-3 5-3 3.8 0 6.3 3.7 4.8 8C20.1 16.2 12 21 12 21Z"/></svg>`
    ).join("");
  }

  function startPlayer() {
    if (mode !== "demo") return;
    mode = "player";
    modeLabel.textContent = "PLAYER LIVE";
    modePill.classList.add("is-player");
    takeoverText.textContent = "You're in control";
    startPrompt.hidden = true;
  }

  function pointerWorldX(event) {
    const rect = canvas.getBoundingClientRect();
    return clamp((event.clientX - rect.left) / rect.width * W, 34 + paddleHalf, W - 34 - paddleHalf);
  }

  function predictedLandingX() {
    if (ball.vy <= 0) return ball.x;
    const left = 34 + ball.r;
    const right = W - 34 - ball.r;
    const span = right - left;
    const time = Math.max(0, (paddle.y - ball.y) / ball.vy);
    const period = span * 2;
    let projected = (ball.x + ball.vx * time - left) % period;
    if (projected < 0) projected += period;
    return left + (projected <= span ? projected : period - projected);
  }

  function emitHeartBurst(x, y) {
    const verticalScale = sy();
    for (let i = 0; i < 44; i++) {
      const angle = Math.random() * Math.PI * 2;
      const speed = 1.1 + Math.random() * 4.8;
      const life = 44 + Math.random() * 34;
      hearts.push({
        x: x + (Math.random() - .5) * 5,
        y: y + (Math.random() - .5) * 5,
        vx: Math.cos(angle) * speed,
        vy: (Math.sin(angle) * speed - 1.2) * verticalScale,
        size: 5.5 + Math.random() * 9,
        rotation: Math.random() * Math.PI * 2,
        spin: (Math.random() - .5) * .19,
        life,
        maxLife: life,
        color: heartColors[Math.floor(Math.random() * heartColors.length)]
      });
    }
    rings.push({ x, y, age: 0, life: 20, color: "#fff0d5" });
    if (hearts.length > 1800) hearts.splice(0, hearts.length - 1400);
  }

  function completeLevel() {
    level++;
    score += 500;
    bricks = makeBricks();
    levelToast = 1.8;
    updateHud();
  }

  function update(dt) {
    const verticalScale = sy();
    paddle.y = H - 56 * verticalScale;

    const oldPaddleX = paddle.x;
    const axis = (keys.right ? 1 : 0) - (keys.left ? 1 : 0);

    if (mode === "demo") {
      const target = predictedLandingX();
      paddle.x += (target - paddle.x) * (1 - Math.pow(.82, dt));
    } else if (axis !== 0) {
      pointerControl = false;
      const speed = 7.7 / Math.max(.25, cssScale);
      paddle.x += axis * speed * dt;
    } else if (pointerControl) {
      paddle.x += (paddle.targetX - paddle.x) * (1 - Math.pow(.62, dt));
    }

    paddle.x = clamp(paddle.x, 34 + paddleHalf, W - 34 - paddleHalf);
    paddle.vx = dt > 0 ? (paddle.x - oldPaddleX) / dt : 0;
    paddle.bop = Math.max(0, paddle.bop - .13 * dt);

    const oldX = ball.x;
    const oldY = ball.y;
    ball.x += ball.vx * dt;
    ball.y += ball.vy * dt;

    const leftWall = 34;
    const rightWall = W - 34;
    const topWall = 27 * verticalScale;

    if (ball.x - ball.r < leftWall) {
      ball.x = leftWall + ball.r;
      ball.vx = Math.abs(ball.vx);
    } else if (ball.x + ball.r > rightWall) {
      ball.x = rightWall - ball.r;
      ball.vx = -Math.abs(ball.vx);
    }

    if (ball.y - ball.r < topWall) {
      ball.y = topWall + ball.r;
      ball.vy = Math.abs(ball.vy);
    }

    for (const brick of bricks) {
      if (!brick.alive) continue;
      const nearestX = clamp(ball.x, brick.x, brick.x + brick.w);
      const nearestY = clamp(ball.y, brick.y, brick.y + brick.h);
      const dx = ball.x - nearestX;
      const dy = ball.y - nearestY;
      if (dx * dx + dy * dy > ball.r * ball.r) continue;

      brick.alive = false;
      remaining--;
      score += 100;
      emitHeartBurst(brick.x + brick.w / 2, brick.y + brick.h / 2);

      const cameFromTop = oldY + ball.r <= brick.y + 1;
      const cameFromBottom = oldY - ball.r >= brick.y + brick.h - 1;
      const cameFromSide = oldX + ball.r <= brick.x || oldX - ball.r >= brick.x + brick.w;

      if (cameFromTop || cameFromBottom) {
        ball.y = oldY;
        ball.vy *= -1;
      } else if (cameFromSide) {
        ball.x = oldX;
        ball.vx *= -1;
      } else if (Math.abs(dx) < Math.abs(dy)) {
        ball.x = oldX;
        ball.vx *= -1;
      } else {
        ball.y = oldY;
        ball.vy *= -1;
      }

      updateHud();
      if (remaining <= 0) completeLevel();
      break;
    }

    const paddleTop = paddle.y - 3 * verticalScale;
    const paddleBottom = paddle.y + 36 * verticalScale;
    if (
      ball.vy > 0 &&
      ball.y + ball.r >= paddleTop &&
      oldY + ball.r <= paddleBottom &&
      Math.abs(ball.x - paddle.x) <= paddleHalf + ball.r
    ) {
      ball.y = paddleTop - ball.r;
      const offset = clamp((ball.x - paddle.x) / (paddleHalf + ball.r), -1, 1);
      const speed = Math.hypot(ball.vx, ball.vy);
      let vx = offset * speed * .79 + paddle.vx * .045;
      vx = clamp(vx, -speed * .86, speed * .86);
      if (Math.abs(vx) < 1.5) vx = (vx < 0 || (vx === 0 && ball.vx < 0) ? -1 : 1) * 1.5;
      ball.vx = vx;
      ball.vy = -Math.sqrt(Math.max(15 * verticalScale * verticalScale, speed * speed - vx * vx));
      paddle.bop = 1;
    }

    if (ball.y - ball.r > H - 12 * verticalScale) {
      lives--;
      if (lives <= 0) {
        lives = 3;
        score = 0;
        level = 1;
        bricks = makeBricks();
      }
      updateHud();
      resetBall();
    }

    ball.trail.unshift({ x: ball.x, y: ball.y });
    if (ball.trail.length > 9) ball.trail.pop();

    for (let i = hearts.length - 1; i >= 0; i--) {
      const particle = hearts[i];
      particle.life -= dt;
      particle.vx *= Math.pow(.986, dt);
      particle.vy += .092 * verticalScale * dt;
      particle.x += particle.vx * dt;
      particle.y += particle.vy * dt;
      particle.rotation += particle.spin * dt;
      if (particle.life <= 0) hearts.splice(i, 1);
    }

    for (let i = rings.length - 1; i >= 0; i--) {
      rings[i].age += dt;
      if (rings[i].age >= rings[i].life) rings.splice(i, 1);
    }

    levelToast = Math.max(0, levelToast - dt / 60);
  }

  function drawArena(now) {
    const verticalScale = sy();
    const background = ctx.createLinearGradient(0, 0, 0, H);
    background.addColorStop(0, "#2b1e33");
    background.addColorStop(.48, "#241c2e");
    background.addColorStop(1, "#191925");
    ctx.fillStyle = background;
    ctx.fillRect(0, 0, W, H);

    const glow = ctx.createRadialGradient(W * .48, H * .28, 8, W * .48, H * .34, H * .88);
    glow.addColorStop(0, "rgba(151, 57, 107, .16)");
    glow.addColorStop(.5, "rgba(94, 48, 99, .07)");
    glow.addColorStop(1, "rgba(24, 20, 36, 0)");
    ctx.fillStyle = glow;
    ctx.fillRect(0, 0, W, H);

    ctx.save();
    ctx.strokeStyle = "rgba(224, 205, 255, .028)";
    ctx.lineWidth = 1;
    for (let x = 42; x < W; x += 42) {
      ctx.beginPath();
      ctx.moveTo(x, 0);
      ctx.lineTo(x, H);
      ctx.stroke();
    }
    for (let y = 40 * verticalScale; y < H; y += 40 * verticalScale) {
      ctx.beginPath();
      ctx.moveTo(0, y);
      ctx.lineTo(W, y);
      ctx.stroke();
    }

    for (const star of stars) {
      const alpha = .12 + (Math.sin(now * .0015 + star.phase) + 1) * .08;
      ctx.globalAlpha = alpha;
      ctx.fillStyle = "#ffe4dc";
      ctx.beginPath();
      ctx.arc(star.x, star.y, star.r, 0, Math.PI * 2);
      ctx.fill();
    }
    ctx.globalAlpha = 1;
    ctx.restore();

    ctx.save();
    ctx.shadowColor = "rgba(255, 121, 163, .34)";
    ctx.shadowBlur = 7;
    ctx.strokeStyle = "rgba(255, 142, 173, .33)";
    ctx.lineWidth = 1.3;
    ctx.beginPath();
    ctx.moveTo(27, 27 * verticalScale);
    ctx.lineTo(27, H - 20 * verticalScale);
    ctx.moveTo(W - 27, 27 * verticalScale);
    ctx.lineTo(W - 27, H - 20 * verticalScale);
    ctx.stroke();
    ctx.restore();

    const floorY = H - 20 * verticalScale;
    const floor = ctx.createLinearGradient(0, floorY, 0, H);
    floor.addColorStop(0, "rgba(255, 132, 171, .08)");
    floor.addColorStop(1, "rgba(15, 14, 24, .48)");
    ctx.fillStyle = floor;
    ctx.fillRect(0, floorY, W, H - floorY);
    ctx.strokeStyle = "rgba(255, 144, 174, .27)";
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(28, floorY);
    ctx.lineTo(W - 28, floorY);
    ctx.stroke();

    if (cssScale > .65) {
      ctx.save();
      ctx.font = "800 9px ui-sans-serif, system-ui, sans-serif";
      ctx.fillStyle = "rgba(231, 205, 220, .55)";
      ctx.textAlign = "left";
      ctx.fillText("STACK ATTACK", 48, 34 * verticalScale);
      ctx.textAlign = "right";
      ctx.fillStyle = "rgba(255, 199, 139, .62)";
      ctx.fillText("100 PTS / CRUNCH", W - 48, 34 * verticalScale);
      ctx.restore();
    }
  }

  function drawBricks() {
    for (const brick of bricks) {
      if (!brick.alive) continue;
      const color = brick.color;
      const gradient = ctx.createLinearGradient(0, brick.y, 0, brick.y + brick.h);
      gradient.addColorStop(0, color.top);
      gradient.addColorStop(.46, color.mid);
      gradient.addColorStop(1, color.bottom);

      ctx.save();
      ctx.shadowColor = color.glow;
      ctx.shadowBlur = 6;
      ctx.shadowOffsetY = 1;
      roundedRectPath(ctx, brick.x, brick.y, brick.w, brick.h, 5);
      ctx.fillStyle = gradient;
      ctx.fill();
      ctx.shadowBlur = 0;
      ctx.shadowOffsetY = 0;
      ctx.strokeStyle = "rgba(255, 247, 240, .28)";
      ctx.lineWidth = 1;
      ctx.stroke();

      roundedRectPath(ctx, brick.x + 4, brick.y + 3, brick.w - 8, Math.max(2, 2.4 * sy()), 1.2);
      ctx.fillStyle = "rgba(255,255,255,.26)";
      ctx.fill();

      ctx.beginPath();
      ctx.moveTo(brick.x + 7, brick.y + brick.h - 3);
      ctx.lineTo(brick.x + brick.w - 7, brick.y + brick.h - 3);
      ctx.strokeStyle = "rgba(70, 24, 50, .18)";
      ctx.lineWidth = 1;
      ctx.stroke();
      ctx.restore();
    }
  }

  function drawRings() {
    ctx.save();
    for (const ring of rings) {
      const progress = ring.age / ring.life;
      const alpha = (1 - progress) * .68;
      const radius = 4 + ring.age * 2.25;
      ctx.globalAlpha = alpha;
      ctx.strokeStyle = ring.color;
      ctx.lineWidth = 1.35;
      ctx.beginPath();
      ctx.arc(ring.x, ring.y, radius, 0, Math.PI * 2);
      ctx.stroke();
      ctx.globalAlpha = alpha * .48;
      ctx.beginPath();
      ctx.arc(ring.x, ring.y, radius * .62, 0, Math.PI * 2);
      ctx.stroke();
    }
    ctx.restore();
  }

  function drawHeart(x, y, size, rotation, color, alpha) {
    ctx.save();
    ctx.translate(x, y);
    ctx.rotate(rotation);
    ctx.globalAlpha = alpha;
    ctx.shadowColor = color;
    ctx.shadowBlur = size > 9 ? 4 : 2;
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.moveTo(0, size * .46);
    ctx.bezierCurveTo(-size * .11, size * .34, -size * .5, size * .06, -size * .5, -size * .16);
    ctx.bezierCurveTo(-size * .5, -size * .43, -size * .13, -size * .49, 0, -size * .24);
    ctx.bezierCurveTo(size * .13, -size * .49, size * .5, -size * .43, size * .5, -size * .16);
    ctx.bezierCurveTo(size * .5, size * .06, size * .11, size * .34, 0, size * .46);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
  }

  function drawHearts() {
    for (const particle of hearts) {
      const alpha = Math.min(1, particle.life / 12);
      drawHeart(
        particle.x,
        particle.y,
        particle.size,
        particle.rotation,
        particle.color,
        alpha
      );
    }
  }

  function drawBall() {
    ctx.save();
    for (let i = ball.trail.length - 1; i >= 0; i--) {
      const point = ball.trail[i];
      const amount = 1 - i / (ball.trail.length + 1);
      ctx.globalAlpha = amount * .19;
      ctx.fillStyle = "#ff789e";
      ctx.beginPath();
      ctx.arc(point.x, point.y, ball.r * (.28 + amount * .48), 0, Math.PI * 2);
      ctx.fill();
    }

    ctx.globalAlpha = 1;
    ctx.shadowColor = "rgba(255, 126, 168, .9)";
    ctx.shadowBlur = 17;
    const orb = ctx.createRadialGradient(
      ball.x - ball.r * .35, ball.y - ball.r * .42, 1,
      ball.x, ball.y, ball.r * 1.35
    );
    orb.addColorStop(0, "#fffdf1");
    orb.addColorStop(.42, "#fff0dc");
    orb.addColorStop(1, "#ff8ca7");
    ctx.fillStyle = orb;
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
    ctx.fill();

    ctx.shadowBlur = 0;
    ctx.globalAlpha = .85;
    ctx.fillStyle = "#ffffff";
    ctx.beginPath();
    ctx.arc(ball.x - ball.r * .25, ball.y - ball.r * .32, Math.max(1, ball.r * .2), 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
  }

  function drawBurger(now) {
    const verticalScale = sy();
    ctx.save();
    ctx.translate(paddle.x, paddle.y + Math.sin(now * .004) * .35);
    ctx.scale(1, verticalScale);

    const squash = paddle.bop;
    ctx.translate(0, 17);
    ctx.scale(1 + squash * .025, 1 - squash * .075);
    ctx.translate(0, -17);

    ctx.save();
    ctx.globalAlpha = .68;
    ctx.shadowColor = "rgba(255, 171, 90, .56)";
    ctx.shadowBlur = 13;
    ctx.fillStyle = "rgba(4, 3, 10, .66)";
    ctx.beginPath();
    ctx.ellipse(0, 37, 52, 5, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();

    const bottomGradient = ctx.createLinearGradient(0, 23, 0, 37);
    bottomGradient.addColorStop(0, "#ffd782");
    bottomGradient.addColorStop(.55, "#e8a84e");
    bottomGradient.addColorStop(1, "#b9693d");
    roundedRectPath(ctx, -49, 23, 98, 12, 6);
    ctx.fillStyle = bottomGradient;
    ctx.fill();
    ctx.strokeStyle = "rgba(255, 232, 171, .48)";
    ctx.lineWidth = 1;
    ctx.stroke();

    const pattyGradient = ctx.createLinearGradient(0, 15, 0, 27);
    pattyGradient.addColorStop(0, "#87503b");
    pattyGradient.addColorStop(.5, "#633b32");
    pattyGradient.addColorStop(1, "#3c2930");
    roundedRectPath(ctx, -55, 15, 110, 12, 5);
    ctx.fillStyle = pattyGradient;
    ctx.fill();
    ctx.strokeStyle = "rgba(255, 186, 121, .33)";
    ctx.lineWidth = 1;
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(-52, 14);
    ctx.lineTo(52, 14);
    ctx.lineTo(48, 22);
    ctx.lineTo(37, 20);
    ctx.lineTo(30, 26);
    ctx.lineTo(18, 22);
    ctx.lineTo(6, 25);
    ctx.lineTo(-6, 21);
    ctx.lineTo(-20, 24);
    ctx.lineTo(-31, 20);
    ctx.lineTo(-43, 23);
    ctx.closePath();
    const cheese = ctx.createLinearGradient(0, 13, 0, 26);
    cheese.addColorStop(0, "#ffe36b");
    cheese.addColorStop(1, "#f5ad31");
    ctx.fillStyle = cheese;
    ctx.fill();

    ctx.beginPath();
    ctx.moveTo(-54, 14);
    ctx.lineTo(-44, 10);
    ctx.lineTo(-34, 13);
    ctx.lineTo(-24, 9);
    ctx.lineTo(-13, 13);
    ctx.lineTo(-2, 9);
    ctx.lineTo(10, 13);
    ctx.lineTo(21, 9);
    ctx.lineTo(32, 13);
    ctx.lineTo(43, 9);
    ctx.lineTo(54, 14);
    ctx.lineTo(50, 20);
    ctx.lineTo(41, 17);
    ctx.lineTo(33, 21);
    ctx.lineTo(23, 17);
    ctx.lineTo(12, 21);
    ctx.lineTo(2, 17);
    ctx.lineTo(-10, 21);
    ctx.lineTo(-20, 17);
    ctx.lineTo(-31, 21);
    ctx.lineTo(-41, 17);
    ctx.lineTo(-51, 20);
    ctx.closePath();
    const lettuce = ctx.createLinearGradient(0, 9, 0, 22);
    lettuce.addColorStop(0, "#a9ee76");
    lettuce.addColorStop(.55, "#53d68a");
    lettuce.addColorStop(1, "#229e72");
    ctx.fillStyle = lettuce;
    ctx.fill();
    ctx.strokeStyle = "rgba(207, 255, 153, .45)";
    ctx.lineWidth = .8;
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(-52, 16);
    ctx.bezierCurveTo(-57, 8, -53, 1, -43, -1);
    ctx.bezierCurveTo(-32, -5, -17, -3, 0, -3);
    ctx.bezierCurveTo(18, -3, 34, -5, 45, 0);
    ctx.bezierCurveTo(55, 4, 56, 11, 51, 16);
    ctx.quadraticCurveTo(0, 20, -52, 16);
    ctx.closePath();
    const bun = ctx.createLinearGradient(0, -4, 0, 19);
    bun.addColorStop(0, "#ffe4a0");
    bun.addColorStop(.47, "#f5bd67");
    bun.addColorStop(1, "#d88648");
    ctx.fillStyle = bun;
    ctx.shadowColor = "rgba(255, 190, 106, .42)";
    ctx.shadowBlur = 6;
    ctx.fill();
    ctx.shadowBlur = 0;
    ctx.strokeStyle = "rgba(255, 238, 193, .66)";
    ctx.lineWidth = 1;
    ctx.stroke();

    ctx.beginPath();
    ctx.moveTo(-38, 7);
    ctx.quadraticCurveTo(0, -1, 38, 7);
    ctx.strokeStyle = "rgba(255, 247, 210, .42)";
    ctx.lineWidth = 1;
    ctx.stroke();

    const seeds = [
      [-28, 7, -.45], [-15, 4, .2], [-2, 5, -.1],
      [12, 4, .32], [27, 7, -.28]
    ];
    for (const [x, y, angle] of seeds) {
      ctx.save();
      ctx.translate(x, y);
      ctx.rotate(angle);
      ctx.fillStyle = "rgba(255, 248, 215, .9)";
      ctx.beginPath();
      ctx.ellipse(0, 0, 2.15, .9, 0, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();
    }

    ctx.restore();
  }

  function drawLevelToast() {
    if (levelToast <= 0) return;
    const alpha = Math.min(1, levelToast * 1.5);
    ctx.save();
    ctx.globalAlpha = alpha;
    roundedRectPath(ctx, W / 2 - 108, H * .49 - 17, 216, 34, 12);
    ctx.fillStyle = "rgba(30, 21, 37, .86)";
    ctx.fill();
    ctx.strokeStyle = "rgba(255, 205, 136, .48)";
    ctx.lineWidth = 1;
    ctx.stroke();
    ctx.fillStyle = "#ffe0a1";
    ctx.font = "900 12px ui-sans-serif, system-ui, sans-serif";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.fillText("STACK CLEARED  ·  +500", W / 2, H * .49);
    ctx.restore();
  }

  function render(now) {
    ctx.clearRect(0, 0, W, H);
    drawArena(now);
    drawBricks();
    drawRings();
    drawHearts();
    drawBall();
    drawBurger(now);
    drawLevelToast();
  }

  function frame(now) {
    const dt = previousTime ? Math.min((now - previousTime) / 16.6667, 2) : 1;
    previousTime = now;
    update(dt);
    render(now);
    requestAnimationFrame(frame);
  }

  fitCanvas();
  bricks = makeBricks();
  paddle.x = W / 2;
  paddle.targetX = W / 2;
  resetBall();
  gameReady = true;
  updateHud();

  if ("ResizeObserver" in window) {
    const observer = new ResizeObserver(fitCanvas);
    observer.observe(gameStage);
  }
  window.addEventListener("resize", fitCanvas);

  gameStage.addEventListener("pointerdown", event => {
    if (mode === "demo") startPlayer();
    if (mode === "player") {
      pointerControl = true;
      paddle.targetX = pointerWorldX(event);
    }
  });

  gameStage.addEventListener("pointermove", event => {
    if (mode === "player") {
      pointerControl = true;
      paddle.targetX = pointerWorldX(event);
    }
  });

  gameStage.addEventListener("pointerleave", () => {
    pointerControl = false;
  });

  startPrompt.addEventListener("click", startPlayer);

  window.addEventListener("keydown", event => {
    if (mode === "demo") startPlayer();
    const key = event.key.toLowerCase();
    if (key === "arrowleft" || key === "arrowright" || key === " " || key === "a" || key === "d") {
      event.preventDefault();
    }
    if (key === "arrowleft" || key === "a") {
      keys.left = true;
      pointerControl = false;
    } else if (key === "arrowright" || key === "d") {
      keys.right = true;
      pointerControl = false;
    }
  });

  window.addEventListener("keyup", event => {
    const key = event.key.toLowerCase();
    if (key === "arrowleft" || key === "a") keys.left = false;
    if (key === "arrowright" || key === "d") keys.right = false;
  });

  window.addEventListener("blur", () => {
    keys.left = false;
    keys.right = false;
  });

  requestAnimationFrame(frame);
})();
</script>
</body>
</html>
Reasoning trace (47,374 tokens)
**Designing a breakout game**

I need to create an HTML breakout game that includes a hamburger paddle and exploding bricks, with lots of hearts. It should have a self-playing demo that runs behind a "Click or press a key to start" overlay. I’ll aim for a visually polished canvas game, possibly at 1280x800, but also adaptable to 375. The demo should start dynamically after 3 seconds, and the user should be able to transition to the actual game with a click or keypress.**Creating a breakout game demo**

I want to set up a demo mode behind an overlay, where a user can click or press a key to enter interactive mode. In the demo, an AI will autonomously control the paddle. Once the user starts the game, they can take over with keyboard or mouse controls. The game should immediately display action with a fun visual style called "BURGER BREAKOUT." I'd like bright, pastel-colored bricks that explode into heart particles when hit. The canvas might be around 1024x560 within a larger page, with a playful HUD and a 3-second demo that showcases the ball breaking the bricks.**Defining game mechanics**

I need to arrange the bricks about 220 pixels from the top of the game board. The ball will start near the paddle at around y 450 and travel diagonally to the bricks in about 4 seconds. For the demo, the ball should launch right away, moving up from the paddle. I aim to break some blocks within roughly 1 second. I’ll design the board to be 980x555 with specific paddle and ball positions, generating lots of heart particles during gameplay.**Designing a game layout**

I’m working on the design for a game called "A HEART-PUNNING ARCADE." I need to arrange the score and lives stats in the top row. For a resolution of 1280x800, the game board should be around 1100x620, keeping the height in mind. There's a structure I want to maintain, such as a header with a tagline and a main board panel with a HUD. The panel design needs to follow a certain style to ensure everything looks cohesive.**Considering game instructions**

I'm thinking about how to handle the subtitle description for the game. It might be best to keep it simple, without any extra details. Additionally, I could include small instructions on the right side, indicating the controls like "← → / MOUSE." This way, players will know how to navigate without feeling overwhelmed by too much information. Balancing clarity and simplicity is key here, so I'm excited to make this work effectively!**Designing Brick Mechanics**

I'm thinking about a glowing track highlight and a decorative score line for a HUD. I'll create an array of bricks from x positions 72 to 928, likely with 10 columns and 6 rows, totaling around 60 blocks. Each brick will explode into heart particles when broken, with colors like red, pink, and turquoise for a vibrant look. I want about 25 to 34 heart particles per brick, ensuring they look great without overwhelming the scene, aiming for smooth performance at 60fps.**Creating Heart Trails**

I'm working on a canvas that’s 1000x560, and I want tiny hearts sized between 3.5 and 7. I need to ensure they leave a nice trail, with some small hearts floating. I’ll use the `drawHeart(ctx, x, y, size, rot, alpha, color)` function to draw the hearts. For this, I'll employ `Path2D` for better support in modern browsers. I have a normalized heart path that I’ll define using bezier curves, ensuring the shape is centered and visually appealing.**Considering demo controls**

I'm thinking about how to reset everything when entering a demo. I want to ensure the score is preserved and provide an overlay that says "CLICK OR PRESS ANY KEY TO PLAY." This overlay should stay visible while the demo is active. 

I need to use the pointer to move the paddle once the demo starts, and set it up so keys like the arrow keys or mouse clicks work properly. I'll consider making the overlay translucent to keep the game visible underneath.**Evaluating overlay placement**

I'm considering whether the overlay should be at the bottom or top of the screen. The requirement is to have it "behind a 'Click or press a key to start' message." A floating toast at the center top could be nice, maybe with a play icon to draw attention. However, I need to make sure it doesn't obscure too many bricks. It's important that any screenshot still captures the blocks and action clearly.**Designing a game interface**

I’m considering an embedded play chip that says “DEMO RUNNING / CLICK OR PRESS A KEY TO START” at the bottom center. I think it’s clear. Maybe I should also include a header with instructions like "The burger is your bat. Every smash showers the board with hearts" along with a hero image. I wonder about the aesthetics, considering a premium arcade design with a nearly black background, warm gradients, and a centered score panel. The header layout might focus on the brand mark and game title.**Refining the game layout**

I’m thinking about creating a large hero section, possibly without a big header to keep the game as the focal point. At the top center, I could place the main title "BURGER BREAKOUT" along with a subtitle. I'd like to include a score HUD row for clarity. It seems polished and distinctive would fit well. For the layout, the app's maximum width is 1136 at a 1280x800 resolution, allowing for a margin of 72 pixels.**Considering canvas resize dynamics**

I’m thinking about how if I change the X and Y scales differently, it could distort elements in the canvas. I could set logical dimensions like width 1000 and height 650 for mobile, keeping the game scales in sync with CSS. I’ll need to recalculate coordinate mapping and adapt to the canvas's CSS ratio of 1000/650. So, there are a lot of factors to consider with the resizing, especially based on viewport/media queries.**Adjusting logical dimensions**

I'm considering a way to set the logical height dynamically when resizing the canvas. It seems like using the formula `canvas.clientHeight / canvas.clientWidth * W` could help ensure that the height adjusts in proportion to the width. This method should keep everything looking consistent, especially when the canvas size changes. I want to make sure the visual elements remain properly scaled, so this approach feels promising!**Adjusting particle sizes**

I'm considering how to adjust the size of hearts displayed on mobile versus desktop. For mobile, I need tiny hearts to be visible, so I’m thinking of using about 1-3 pixels, while desktop hearts might be larger, around 10-17 pixels. It seems 4-10 pixels could be a better size range, but I wonder if I can scale the particle sizes based on the screen width. I could calculate this by using the canvas width, considering various scaling options to ensure a good visual experience!**Designing Game UI Elements**

I'm working on a dynamic score and a premium brand style. I’m thinking of a dark violet and hot pink-orange palette, maybe with a warm beige game overlay. A dark arcade environment could work well. For the header, I want an SVG icon with a coral/orange gradient and a burger graphic. The text will read "ARCADE · 01" and "BURGER BREAKOUT." I’ll include a "DEMO RUNNING" badge, score display, and some fun footer text with a heart icon.**Ensuring Responsive Design**

I'm thinking of making the footer a bit darker and ensuring everything is responsive. For screens under 700px, I’ll adjust padding and margins, set the brand icon size, and use a clamp for the h1 font size. The status info should be smaller, and I might hide the header caption. For narrower screens, like 430px, I’ll consider hiding some elements and adjusting text sizes and widths, all while keeping the viewport at 100% to prevent horizontal scrolling.**Refining visual design**

I'm focusing on how to create gradients dynamically with 70 blocks. I plan to use `ctx.fillRect` for shine and add a small highlight with rounded corners along with some text labels like "THE HEART STACK." I'll be adjusting the font settings and colors to polish the overall look, including a score display. I also want to make sure to draw structures like the game field walls and add background stars that will regenerate if the height changes.**Creating the background design**

I'm focusing on drawing the background with a gradient using `ctx.fillStyle` and `ctx.fillRect`. I'll create a radial gradient for a glow effect and set vertical grid lines spaced at x=50 with low alpha, plus horizontal lines every 40 units for added texture. I want to ensure the background dots are subtle to avoid clashing with the bricks. Incorporating a heart shape into the design with the `drawHeartPath` function will help add a unique touch, focusing on the lobes and tip.**Refining heart graphics**

I'm working on centering the coordinate system at (0,0) with specific dimensions for a heart shape. I've got the bezier curves defined to shape the lobes, and I need to ensure the heart's width and height are correct. In the drawing function, I’ll apply translations and rotations, and add shadows conditionally based on particle size. Each particle will be updated based on physics equations, and I’ll handle the game loop to manage render timing efficiently.**Updating game mechanics**

I'm setting up the game loop to track the current time and run an update function for the delta time, `dt`. I need to update the paddle's vertical position, ensuring it stays within the game boundaries using a half-width constant. The paddle can respond to either the AI or player input, adjusting its horizontal movement. For the ball, it should be active, starting immediately, and I’ll need to check for wall collisions to handle reflections properly.**Considering overlay placement**

I’m contemplating the overlay area around the card; I want to ensure the board's still visible while the card appears dark. At 3 seconds, the ball could be near the paddle, intersecting the overlay. I think I need to position the overlay carefully so it doesn’t obscure the ball. Maybe placing the overlay near the top edges will help, and using translucency can allow the ball to be somewhat visible through the blur. I want the CTA to be readable too!**Evaluating animation and canvas setup**

I'm analyzing the animation for the ball, setting its initial Y position and ensuring calculations for speed and distance work out correctly. I've determined that the ball will hit the bricks in about 0.37 seconds, followed by a couple of bounces. It’s essential that my CSS for the game stage maintains an appropriate aspect ratio and handles responsiveness. Also, I'll ensure that the canvas dimensions and pixel ratios are properly set up for best performance! Using `ResizeObserver` is a good step to manage resizing.**Optimizing breakout game visuals**

I'm working on optimizing the visuals and gameplay for a breakout game. The canvas board is going to be a dark starry background with colorful bricks and a hamburger paddle. I want little heart particles to explode outward when bricks are hit, contributing to the game's overall aesthetics. I'll implement effects like burst rings and flashes upon brick destruction, and everything should happen instantly for a more engaging experience. The paddle AI will track the ball, and I'll ensure the game looks visually appealing with an SVG for lives.**Managing dynamic gameplay updates**

I need to prevent stale data during resizing events in the game. I’ll set the color palette globally, and also establish arrays for heart particles, rings, and sparks. The background stars will recalibrate dynamically, and I’ll create a trail for the ball that updates every frame by adding the current position to the trail array. I want to draw trailing circles around the ball, managing the effect's size and alpha to enhance the visuals. Plus, I’ll handle particle rotation and ensure hearts are bright and highlighted correctly.**Calculating ball dynamics**

I’m considering the ball's speed, which starts at 7.5. After a hit, there are changes — `ball.vy` might be -6.7 due to collision. It seems that the ball collides with bricks repeatedly, causing it to bounce around. I also need to check collisions with the brick frame at the new position. This could get complex, but I’ll make sure to keep track of those movements and interactions as they happen!**Thinking through game mechanics**

I'm working through how to implement ball and brick interactions correctly. When the ball hits a brick, it should bounce, and I need to check if there's any overlap with subsequent bricks. If the ball hits the top wall, I have to adjust for lives. I’m considering adding effects like mini hearts when lives decrease, but I'm not sure if that’s too complex. I’ll also tweak the paddle’s movement and how the burger animation works. Everything seems to be coming together!**Adjusting canvas text for scaling**

I'm diving into how to properly scale the text on the canvas for mobile. When I set my font to 9 logical pixels, it turned out to be just 3 pixels on the screen, which is way too small. The invisible text isn't very helpful! With mobile scaling, I think I might need to skip canvas labels altogether and go for something like 12 logical pixels instead. For desktops, 11 logical pixels work well. I want to make sure everything is readable, especially for the bricks!**Adjusting visual heights**

I'm thinking about how to set the brick visual height based on logical dimensions, considering different sizes for mobile and desktop. For mobile, I want to increase the height to about 32 with a gap of 9, while for desktop, it should be around 22. It's important to keep the same object collision heights. The vertical size of board bricks will increase on mobile, and I can increase the ball radius to 10 logical units. The paddle height might be around 38 logical units.**Calculating scale and dimensions**

I’m noticing that while the paddle width hasn't changed, the mobile speed remains proportional. I could set `uiScale = 1 / screenScale` at about 2.85, but it won’t apply to everything. For mobile, identifying `unitY = H/500` gives me approximately 1.29, considering height adjustments. Height increases by 29%, meaning brick sizes need to scale appropriately. The new calculations show increases in brick height and gaps, which looks promising for the overall design. I'll also adjust coordinates based on our logical height.**Adjusting brick and paddle dimensions**

I’m calculating the brick height at `22*scaleY`, with vertical spacing and a top position defined at `30*scaleY` and `56*scaleY`, respectively. This brings the final bottom position to around 335. The paddle now sits at a vertical position that updates to 574, while the ball starts at 519. I need to refine how the burger scales; drawing uses `ctx.scale(1, scaleY)` for vertical adjustments. I'm ensuring the paddle dimensions remain visually proportional while keeping a manageable gap between elements.**Assessing bounce calculations**

I need to ensure my y velocities match, while keeping the x velocity the same. I've figured out that the ball's bounce depends on speed, with initial vx being 4.2 and vy at -6.2 scaled by sy, which looks good. When calculating the paddle bounce, I consider the speed using the hypotenuse formula, ensuring proper scaling. Even though the y layout distances use sy and x doesn't, I've confirmed that the movements correspond correctly across the coordinate scales, so it all seems fine!**Considering visual elements**

I'm thinking about using a glowing line for the top, possibly setting it at y=32sy. I want to apply a gradient for the floor using `createLinearGradient(0, H-24sy, ...)`. Then, I’ll fill a rectangle for the floor at `ctx.fillRect(0, H-17sy, W, 17sy)`. It could be nice to add some little dots at x=26 to enhance the visual appearance. Let’s see how that comes together!**Updating game elements**

I need to reset the score to zero and keep the lives set at three. I'm thinking about adding some optional random screen sparkle for ambiance. The drawing function for the board involves using the canvas context to fill colors and manage stars. I want to compute star alpha via a sine function, so stars twinkle nicely. I'll use a radial gradient for the background colors, blending from dark to lighter tones at the center.**Implementing player mode**

I'm diving into how `startPlayer` updates its mode. I'll set the mode label to 'PLAYER LIVE' and adjust the status class. I think it makes sense to hide the `startPrompt` and possibly add the stage class as `is-player`. 

Initially, the footer text can say "CLICK / KEY TO PLAY," and once we start, I'll update it to "PLAYER IN CONTROL." Oh, and I want to incorporate the takeover text creatively. Overall, I'm refining the mode status.**Creating final code**

I think of starting the line board game at the top of the board, placing the bricks at y=56 right after the top margin. I've decided HUD will be above that. It's clear enough for me. Now, I want to focus on creating the final code in fenced HTML format, making sure to follow strict syntax rules without any commentary. I'll ensure everything is accurate and ready for use!