Claude Haiku 5.5
by AnthropicReasoningCleanTruncated
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 8 min 44 s
- Cost
- $0.064
- Output tokens
- 128,000
- Spent thinking
- 114,922 tokens
- Speed
- 245.1 tok/s
- Console errors
- 0
- First token after
- 1.8 s
- First visible text after
- 8 min 10 s
- Prompt tokens
- 793
- Code size
- 25.6 KB
- Finish reason
- length
- Served by
- Claude Platform on AWS
- Model id
anthropic/claude-haiku-5.5- Reasoning sent
{"effort":"xhigh"}- Max output
- 128,000 tokens
- Generated
- Nov 10, 2026
What it was asked
The prompt
Build a polished Breakout (brick breaker) game in a single HTML file using vanilla JavaScript and a <canvas>. Requirements: - A paddle controlled with the mouse and with the left/right arrow keys. - The bounce angle off the paddle depends on where the ball hits it (center = straight up, edges = sharp angle). - One level with at least 6 rows of bricks, mixing normal bricks and tougher bricks that need 2 hits and visibly crack after the first one. - Some bricks visibly carry a power-up icon; when broken they drop it, and catching it with the paddle gives either a wider paddle (10 seconds) or multi-ball (3 balls). - The ball must never pass through bricks or the paddle. - Particles when a brick breaks. - 3 lives, score displayed. Game over and victory screens with a restart option.
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 · 746 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Breakout</title>
<style>
:root{
--ink:#e9edff;
--muted:#8f99c9;
--panel:rgba(14,18,44,.74);
--line:rgba(150,170,255,.16);
--teal:#5eead4;
--gold:#fbbf24;
--rose:#ff5d7a;
--sw:min(calc(100vw - 24px), calc((100vh - 190px) * 4 / 3), 960px);
}
*{box-sizing:border-box}
[hidden]{display:none !important}
html,body{height:100%;margin:0}
body{
font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
color:var(--ink);
background:
radial-gradient(900px 500px at 50% -10%, rgba(94,234,212,.14), transparent 70%),
radial-gradient(900px 600px at 50% 115%, rgba(139,92,246,.20), transparent 70%),
linear-gradient(180deg,#0b1026,#070a1a);
overflow-x:hidden;
-webkit-tap-highlight-color:transparent;
}
.app{
min-height:100vh;min-height:100dvh;
display:flex;flex-direction:column;align-items:center;justify-content:center;
gap:12px;padding:12px;
}
.hud{
width:var(--sw);display:flex;flex-wrap:wrap;align-items:center;
gap:8px 10px;justify-content:space-between;
}
.brand{
margin-right:auto;font-weight:900;font-size:15px;letter-spacing:.2em;
background:linear-gradient(90deg,#fff,var(--teal) 60%,#a78bfa);
-webkit-background-clip:text;background-clip:text;color:transparent;
}
.stat{
display:flex;flex-direction:column;gap:4px;min-width:66px;padding:6px 10px;
border-radius:12px;background:var(--panel);border:1px solid var(--line);
}
.stat small{font-size:10px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted)}
.stat b{font-size:17px;line-height:1;font-variant-numeric:tabular-nums}
.hearts{font-size:16px;line-height:1;letter-spacing:3px;color:var(--rose);text-shadow:0 0 10px rgba(255,93,122,.5)}
.hearts .off{color:#3a4070;text-shadow:none}
.powers{display:flex;gap:6px;flex-wrap:wrap}
.chip{
position:relative;display:inline-flex;align-items:center;overflow:hidden;
padding:7px 11px;border-radius:999px;border:1px solid;
font-size:12px;font-weight:700;white-space:nowrap;font-variant-numeric:tabular-nums;
}
.chip.wide{color:var(--teal);border-color:rgba(94,234,212,.5);background:rgba(94,234,212,.12)}
.chip.multi{color:var(--gold);border-color:rgba(251,191,36,.55);background:rgba(251,191,36,.12)}
.chip .bar{position:absolute;left:0;right:0;bottom:0;height:2px;background:currentColor;transform-origin:0 50%}
.btns{display:flex;gap:6px;align-items:center}
.badge{
font-size:10px;font-weight:800;letter-spacing:.14em;padding:6px 9px;border-radius:999px;
color:#fff;background:linear-gradient(90deg,#f472b6,#a78bfa);
}
.btn{
font:inherit;font-size:12px;font-weight:700;color:var(--ink);
background:var(--panel);border:1px solid var(--line);border-radius:10px;
padding:8px 11px;min-height:36px;cursor:pointer;
}
.btn:hover:not(:disabled){border-color:rgba(94,234,212,.6)}
.btn:disabled{opacity:.4;cursor:default}
.btn:focus-visible,.cta:focus-visible{outline:2px solid var(--teal);outline-offset:2px}
.stage{
position:relative;width:var(--sw);aspect-ratio:4/3;border-radius:16px;overflow:hidden;
background:#050815;
box-shadow:0 0 0 1px var(--line),0 24px 70px rgba(0,0,0,.55),0 0 90px rgba(94,234,212,.07);
touch-action:none;user-select:none;-webkit-user-select:none;
}
#cv{position:absolute;inset:0;width:100%;height:100%;display:block}
.overlay{
position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
padding:16px;background:rgba(3,5,16,.36);transition:opacity .25s ease;
}
.overlay.hidden{opacity:0;pointer-events:none}
.card{
text-align:center;max-width:min(92%,460px);
padding:clamp(16px,3vw,26px) clamp(18px,4vw,34px);
border-radius:20px;background:rgba(10,14,38,.82);
border:1px solid rgba(160,180,255,.22);
box-shadow:0 20px 60px rgba(0,0,0,.45);
}
.kicker{font-size:11px;letter-spacing:.24em;text-transform:uppercase;color:var(--muted);font-weight:700}
.title{
margin:6px 0 4px;font-size:clamp(30px,7vw,54px);font-weight:900;letter-spacing:.08em;line-height:1.05;
background:linear-gradient(180deg,#ffffff,#a5f3fc 55%,#818cf8);
-webkit-background-clip:text;background-clip:text;color:transparent;
}
.title.bad{background:linear-gradient(180deg,#ffffff,#ffb3c1 50%,#ff5d7a);-webkit-background-clip:text;background-clip:text}
.title.win{background:linear-gradient(180deg,#ffffff,#fde68a 50%,#f59e0b);-webkit-background-clip:text;background-clip:text}
.title.sm{font-size:clamp(24px,5vw,36px)}
.prompt{margin:12px 0 0;font-size:clamp(14px,2.4vw,17px);font-weight:700;color:#fff;animation:pulse 1.6s ease-in-out infinite}
.score{margin:10px 0 0;font-size:16px;color:#cfd6ff}
.score b{color:#fff;font-size:22px;font-variant-numeric:tabular-nums}
.keys{list-style:none;margin:14px 0 0;padding:0;display:grid;gap:6px;font-size:13px;color:var(--muted)}
kbd{
font:600 11px ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:#fff;
background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.2);border-bottom-width:2px;
border-radius:6px;padding:2px 6px;margin:0 2px;
}
.cta{
margin-top:16px;font:inherit;font-weight:800;font-size:15px;letter-spacing:.04em;color:#06121f;
background:linear-gradient(180deg,#a7f3d0,#5eead4);border:0;border-radius:999px;
padding:11px 26px;cursor:pointer;box-shadow:0 8px 24px rgba(94,234,212,.35);
}
.cta:active{transform:translateY(1px)}
.hint{margin:10px 0 0;font-size:12px;color:var(--muted)}
.footer{width:var(--sw);text-align:center;font-size:12px;color:var(--muted);margin:0}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.45}}
@media (max-width:560px){
.stat{min-width:0;padding:5px 8px}
.brand{font-size:13px}
.footer{font-size:11px}
.keys{font-size:12px}
.chip{font-size:11px;padding:6px 9px}
}
</style>
</head>
<body>
<div class="app">
<header class="hud" id="hud">
<div class="brand">BREAKOUT</div>
<div class="stat"><small>Score</small><b id="score">0</b></div>
<div class="stat"><small>Lives</small><span class="hearts" id="hearts" aria-label="3 lives">♥♥♥</span></div>
<div class="stat"><small>Bricks</small><b id="left">84</b></div>
<div class="powers">
<span class="chip wide" id="chipWide" hidden><span id="wideTxt">Wide</span><i class="bar" id="wideBar"></i></span>
<span class="chip multi" id="chipMulti" hidden><span id="multiTxt">3 balls</span></span>
</div>
<div class="btns">
<span class="badge" id="demoBadge">DEMO</span>
<button class="btn" id="pauseBtn" type="button">Pause</button>
<button class="btn" id="soundBtn" type="button">Sound: on</button>
</div>
</header>
<main class="stage" id="stage">
<canvas id="cv" role="img" aria-label="Breakout playfield"></canvas>
<div class="overlay" id="overlay"><div class="card" id="card"></div></div>
</main>
<p class="footer">← → or mouse to move · Space or click to launch · P pause · M mute</p>
</div>
<script>
(() => {
'use strict';
/* ================= CONFIG ================= */
const W = 800, H = 600, TAU = Math.PI * 2;
const ROWS = 7, COLS = 12, TOTAL = ROWS * COLS;
const MARGIN_X = 28, GAP = 6, BRICK_TOP = 60;
const BW = (W - MARGIN_X * 2 - GAP * (COLS - 1)) / COLS;
const BH = 26;
const R = 7.5; // ball radius
const PADDLE_Y = 556, PADDLE_H = 14;
const PADDLE_W = 118, PADDLE_WIDE = 184;
const BASE_SPEED = 320, MAX_SPEED = 500, SPEED_PER_BRICK = 2;
const KEY_SPEED = 620, AI_SPEED = 720;
const WIDE_TIME = 10;
const CAP_SPEED = 150, CAP_W = 34, CAP_H = 16;
const MAX_ANGLE = 1.1; // ~63 degrees: strongest bounce at the paddle edge
const GRAVITY = 380;
const ROW_COLORS = ['#ff5d7a', '#ff9f43', '#ffd166', '#6ee7a8', '#38d9f5', '#6b8cff', '#b07cff'];
const POWER_COLORS = { wide: '#5eead4', multi: '#fbbf24' };
// n normal · t tough (2 hits) · w / m normal carrying WIDE / MULTI · T / M tough carrying WIDE / MULTI
const CODES = {
n: { tough: false, power: null },
t: { tough: true, power: null },
w: { tough: false, power: 'wide' },
m: { tough: false, power: 'multi' },
T: { tough: true, power: 'wide' },
M: { tough: true, power: 'multi' }
};
const LAYOUT = [
'tntntnntntnt',
'nnnwnnnnmnnn',
'ntnnnnnnntnn',
'nnnnTnnnnnnn',
'nnmnnnnnnwtn',
'tnnntnnMnnnt',
'nnnnnnnnnnnn'
];
/* ================= DOM ================= */
const $ = (id) => document.getElementById(id);
const stage = $('stage');
const cv = $('cv');
const ctx = cv.getContext('2d');
const overlay = $('overlay');
const card = $('card');
const ui = {
score: $('score'), left: $('left'), hearts: $('hearts'),
chipWide: $('chipWide'), wideTxt: $('wideTxt'), wideBar: $('wideBar'),
chipMulti: $('chipMulti'), multiTxt: $('multiTxt'),
demo: $('demoBadge'), pause: $('pauseBtn'), sound: $('soundBtn')
};
/* ================= HELPERS ================= */
const clamp = (v, a, b) => (v < a ? a : (v > b ? b : v));
const rnd = (a, b) => a + Math.random() * (b - a);
function shade(hex, amt) {
const n = parseInt(hex.slice(1), 16);
const r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255;
const t = amt < 0 ? 0 : 255, p = Math.abs(amt);
return 'rgb(' + Math.round(r + (t - r) * p) + ',' +
Math.round(g + (t - g) * p) + ',' + Math.round(b + (t - b) * p) + ')';
}
function rr(x, y, w, h, r) {
r = Math.max(0, 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 resize() {
const cssW = stage.clientWidth;
if (!cssW) return;
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const pw = Math.round(cssW * dpr);
const ph = Math.round(pw * H / W);
if (cv.width !== pw || cv.height !== ph) { cv.width = pw; cv.height = ph; }
ctx.setTransform(pw / W, 0, 0, ph / H, 0, 0);
}
/* ================= AUDIO (WebAudio beeps, created on user gesture) ================= */
let actx = null;
let muted = false;
function ensureAudio() {
try {
if (!actx) {
const AC = window.AudioContext || window.webkitAudioContext;
if (AC) actx = new AC();
}
if (actx && actx.state === 'suspended') actx.resume().catch(() => {});
} catch (e) { actx = null; }
}
function tone(freq, dur, type, vol, slide, delay) {
if (!actx || muted || actx.state !== 'running') return;
try {
const t0 = actx.currentTime + (delay || 0);
const o = actx.createOscillator();
const g = actx.createGain();
o.type = type || 'square';
o.frequency.setValueAtTime(freq, t0);
if (slide && slide !== 1) o.frequency.exponentialRampToValueAtTime(Math.max(30, freq * slide), t0 + dur);
g.gain.setValueAtTime(vol || 0.05, t0);
g.gain.exponentialRampToValueAtTime(0.0001, t0 + dur);
o.connect(g);
g.connect(actx.destination);
o.start(t0);
o.stop(t0 + dur + 0.05);
} catch (e) { /* audio is optional */ }
}
function setMuted(m) {
muted = m;
ui.sound.textContent = muted ? 'Sound: off' : 'Sound: on';
}
/* ================= STATE ================= */
const G = {
mode: 'demo', // 'demo' (AI plays) | 'play' (human)
phase: 'serve', // 'serve' | 'play' | 'paused' | 'clear' | 'over' | 'won'
prePause: 'play',
score: 0, lives: 3, broken: 0,
speed: BASE_SPEED,
bricks: [], balls: [], caps: [], parts: [], texts: [],
paddle: { x: W / 2, tx: W / 2, w: PADDLE_W },
wideT: 0,
keyL: false, keyR: false,
shake: 0, flashPaddle: 0,
banner: '', bannerCol: '#fff', bannerT: 0,
serveT: 0, endT: 0, fwT: 0,
aiErr: 0, time: 0
};
const stars = Array.from({ length: 70 }, () => ({
x: Math.random() * W, y: Math.random() * H,
r: 0.4 + Math.random() * 1.1,
s: 0.5 + Math.random() * 2,
p: Math.random() * TAU
}));
/* ================= LEVEL ================= */
function makeCracks() {
const out = [];
const n = 3 + Math.floor(Math.random() * 2);
for (let i = 0; i < n; i++) {
let x = rnd(BW * 0.3, BW * 0.7), y = rnd(BH * 0.3, BH * 0.7);
let a = rnd(0, TAU);
const pts = [[x, y]];
const segs = 3 + Math.floor(Math.random() * 2);
for (let s = 0; s < segs; s++) {
a += rnd(-0.8, 0.8);
const len = rnd(6, 11);
x = clamp(x + Math.cos(a) * len, 1, BW - 1);
y = clamp(y + Math.sin(a) * len, 1, BH - 1);
pts.push([x, y]);
}
out.push(pts);
}
return out;
}
function buildLevel() {
G.bricks = [];
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
const code = CODES[LAYOUT[r][c]] || CODES.n;
G.bricks.push({
r, c,
x: MARGIN_X + c * (BW + GAP),
y: BRICK_TOP + r * (BH + GAP),
tough: code.tough,
power: code.power,
hp: code.tough ? 2 : 1,
maxHp: code.tough ? 2 : 1,
alive: true,
color: ROW_COLORS[r],
flash: 0,
crack: 1,
cracks: code.tough ? makeCracks() : null,
phase: rnd(0, TAU)
});
}
}
}
/* ================= BALLS & PADDLE ================= */
function makeBall(x, y, vx, vy, stuck) {
return { x, y, px: x, py: y, vx, vy, stuck: !!stuck, dead: false, trail: [] };
}
function serveBall() {
G.balls = [makeBall(G.paddle.x, PADDLE_Y - R - 0.5, 0, 0, true)];
G.phase = 'serve';
G.serveT = 0;
G.caps = [];
}
function setSpeed(sp) {
G.speed = sp;
for (const b of G.balls) {
if (b.stuck) continue;
const m = Math.hypot(b.vx, b.vy) || 1;
b.vx = b.vx / m * sp;
b.vy = b.vy / m * sp;
}
}
function launch() {
if (G.phase !== 'serve') return;
const b = G.balls.find(q => q.stuck);
if (!b) return;
const a = rnd(-0.3, 0.3);
b.stuck = false;
b.vx = G.speed * Math.sin(a);
b.vy = -G.speed * Math.cos(a);
G.phase = 'play';
tone(520, 0.08, 'triangle', 0.05);
tone(780, 0.1, 'triangle', 0.04, 1, 0.06);
updateOverlay();
}
function predictX(b, t) {
const L = W - 2 * R, span = 2 * L;
let u = ((b.x - R) + b.vx * t) % span;
if (u < 0) u += span;
if (u > L) u = span - u;
return R + u;
}
function spawnExtraBalls() {
const src = G.balls.find(b => !b.stuck);
if (!src) return;
const angles = [0.5, -0.5, 0.9, -0.9];
let k = 0;
while (G.balls.length < 3 && k < angles.length) {
const a = angles[k++];
const c = Math.cos(a), s = Math.sin(a);
G.balls.push(makeBall(src.x, src.y, src.vx * c - src.vy * s, src.vx * s + src.vy * c, false));
}
}
/* ================= PARTICLES / TEXT ================= */
function burst(x, y, col, n, power, lift) {
power = power || 1;
lift = lift || 0;
for (let i = 0; i < n; i++) {
const a = rnd(0, TAU), s = rnd(50, 240) * power;
G.parts.push({
x, y,
vx: Math.cos(a) * s,
vy: Math.sin(a) * s - lift,
life: 0, max: rnd(0.45, 0.95),
size: rnd(1.6, 3.8), col
});
}
if (G.parts.length > 420) G.parts.splice(0, G.parts.length - 420);
}
function updateParts(dt) {
for (const p of G.parts) {
p.life += dt;
p.vy += GRAVITY * dt;
p.vx *= 1 - Math.min(1, dt * 1.2);
p.x += p.vx * dt;
p.y += p.vy * dt;
}
G.parts = G.parts.filter(p => p.life < p.max);
}
function addText(txt, x, y, col) {
G.texts.push({ txt, x, y, col, t: 0, max: 0.9 });
if (G.texts.length > 24) G.texts.shift();
}
function updateTexts(dt) {
for (const tx of G.texts) { tx.t += dt; tx.y -= 26 * dt; }
G.texts = G.texts.filter(tx => tx.t < tx.max);
}
function addScore(pts, x, y, col) {
G.score += pts;
addText('+' + pts, x, y, col);
}
function showBanner(txt, col) {
G.banner = txt;
G.bannerCol = col;
G.bannerT = 1.8;
}
function firework() {
const col = ROW_COLORS[(Math.random() * ROW_COLORS.length) | 0];
burst(rnd(80, W - 80), rnd(60, 300), col, 36, 1.15);
tone(rnd(500, 900), 0.2, 'triangle', 0.03);
}
/* ================= PHYSICS ================= */
function tick() { tone(150, 0.03, 'sine', 0.02); }
function stepBall(b, dt) {
const dist = Math.hypot(b.vx, b.vy) * dt;
const n = Math.max(1, Math.ceil(dist / 2)); // sub-steps of <= 2 units: no tunnelling
const h = dt / n;
for (let i = 0; i < n; i++) {
b.px = b.x; b.py = b.y;
b.x += b.vx * h;
b.y += b.vy * h;
if (b.x < R) { b.x = R; b.vx = Math.abs(b.vx); tick(); }
else if (b.x > W - R) { b.x = W - R; b.vx = -Math.abs(b.vx); tick(); }
if (b.y < R) { b.y = R; b.vy = Math.abs(b.vy); tick(); }
collidePaddle(b);
collideBricks(b);
if (b.y > H + R) { b.dead = true; break; }
}
if (!b.dead) {
const sp = Math.hypot(b.vx, b.vy);
const minVy = sp * 0.22;
if (Math.abs(b.vy) < minVy) {
b.vy = (b.vy < 0 ? -1 : 1) * minVy;
b.vx = Math.sign(b.vx || 1) * Math.sqrt(Math.max(0, sp * sp - minVy * minVy));
}
}
}
function collidePaddle(b) {
const P = G.paddle;
const left = P.x - P.w / 2, right = P.x + P.w / 2;
const top = PADDLE_Y, bottom = PADDLE_Y + PADDLE_H;
const cx = clamp(b.x, left, right), cy = clamp(b.y, top, bottom);
const dx = b.x - cx, dy = b.y - cy;
if (dx * dx + dy * dy >= R * R) return;
if (b.vy > 0 && b.y <= bottom) {
// Angle depends on where the ball struck: centre = straight up, edges = sharp angle
const off = clamp((b.x - P.x) / (P.w / 2), -1, 1);
const ang = off * MAX_ANGLE;
b.vx = G.speed * Math.sin(ang);
b.vy = -G.speed * Math.cos(ang);
b.y = top - R;
G.flashPaddle = 1;
G.aiErr = rnd(-12, 12);
burst(b.x, top, '#e0f2fe', 6, 0.6, 40);
tone(330, 0.06, 'square', 0.05);
return;
}
if (b.y < top) return; // above the paddle and moving away
if (b.y <= bottom) { // side contact while travelling upward: push out sideways
const side = b.x < P.x ? -1 : 1;
b.x = side < 0 ? left - R : right + R;
b.vx = side * Math.max(Math.abs(b.vx), 60);
} else { // underside contact: bounce back down
b.y = bottom + R;
b.vy = Math.abs(b.vy);
}
}
function collideBricks(b) {
for (const br of G.bricks) {
if (!br.alive) continue;
const x0 = br.x, x1 = br.x + BW, y0 = br.y, y1 = br.y + BH;
if (b.x < x0 - R || b.x > x1 + R || b.y < y0 - R || b.y > y1 + R) continue;
const cx = clamp(b.x, x0, x1), cy = clamp(b.y, y0, y1);
const dx = b.x - cx, dy = b.y - cy;
let d = Math.sqrt(dx * dx + dy * dy);
if (d >= R) continue;
let nx, ny;
if (d > 1e-6) {
nx = dx / d; ny = dy / d;
} else { // degenerate: centre inside the brick
const ex = Math.min(b.x - x0, x1 - b.x), ey = Math.min(b.y - y0, y1 - b.y);
if (ex < ey) { nx = (b.x - x0 < x1 - b.x) ? -1 : 1; ny = 0; }
else { nx = 0; ny = (b.y - y0 < y1 - b.y) ? -1 : 1; }
d = 0;
}
const pen = R - d;
b.x += nx * pen;
b.y += ny * pen;
const vn = b.vx * nx + b.vy * ny;
if (vn < 0) { b.vx -= 2 * vn * nx; b.vy -= 2 * vn * ny; }
hitBrick(br, b);
return true;
}
return false;
}
function hitBrick(br, b) {
const cxm = br.x + BW / 2, cym = br.y + BH / 2;
if (br.tough && br.hp > 1) {
// first hit on a tough brick: it cracks and chips, but survives
br.hp--;
br.flash = 1;
br.crack = 0;
burst(b.x, b.y, '#e2e8f0', 10, 0.7);
addScore(5, cxm, br.y - 4, '#e2e8f0');
tone(240, 0.07, 'square', 0.05, 1.4);
return;
}
br.alive = false;
br.flash = 0;
G.broken++;
burst(cxm, cym, br.color, br.tough ? 26 : 16, 1);
if (br.tough) burst(cxm, cym, '#e2e8f0', 8, 0.9);
addScore(br.tough ? 20 : 10, cxm, cym - 8, br.color);
if (br.power) G.caps.push({ x: cxm, y: cym, type: br.power, dead: false });
tone(330 + (ROWS - br.r) * 70, 0.09, 'triangle', 0.06);
setSpeed(Math.min(MAX_SPEED, BASE_SPEED + G.broken * SPEED_PER_BRICK));
if (G.broken >= TOTAL) levelClear();
}
function updateCaps(dt) {
const P = G.paddle;
const left = P.x - P.w / 2, right = P.x + P.w / 2;
for (const c of G.caps) {
c.y += CAP_SPEED * dt;
if (c.y > H + 20) { c.dead = true; continue; }
if (c.y + CAP_H / 2 > PADDLE_Y && c.y - CAP_H / 2 < PADDLE_Y + PADDLE_H &&
c.x + CAP_W / 2 > left && c.x - CAP_W / 2 < right) {
collect(c);
}
}
G.caps = G.caps.filter(c => !c.dead);
}
function collect(c) {
c.dead = true;
if (c.type === 'wide') {
G.wideT = WIDE_TIME;
showBanner('WIDE PADDLE · 10 s', POWER_COLORS.wide);
burst(c.x, c.y, POWER_COLORS.wide, 22, 0.9);
addText('WIDE!', c.x, c.y - 12, POWER_COLORS.wide);
tone(523, 0.12, 'triangle', 0.05);
tone(784, 0.16, 'triangle', 0.05, 1, 0.1);
} else {
spawnExtraBalls();
showBanner('MULTI-BALL · 3 BALLS', POWER_COLORS.multi);
burst(c.x, c.y, POWER_COLORS.multi, 22, 0.9);
addScore(25, c.x, c.y - 14, POWER_COLORS.multi);
tone(660, 0.12, 'triangle', 0.05);
tone(990, 0.16, 'triangle', 0.05, 1, 0.1);
}
}
function loseLife() {
G.lives--;
G.shake = 1;
G.wideT = 0;
G.caps = [];
tone(220, 0.35, 'sawtooth', 0.05, 0.4);
if (G.lives <= 0) {
G.balls = [];
G.phase = 'over';
G.endT = 1.2;
updateOverlay();
return;
}
showBanner('BALL LOST', '#ff8fa3');
serveBall();
}
function levelClear() {
G.phase = 'clear';
G.endT = G.mode === 'demo' ? 2.6 : 2.0;
G.fwT = 0;
G.caps = [];
showBanner('LEVEL CLEARED!', '#fde68a');
[523, 659, 784, 1046].forEach((f, i) => tone(f, 0.25, 'triangle', 0.05, 1, i * 0.12));
}
/* ================= AI (demo mode) ================= */
function aiThink(dt) {
const P = G.paddle;
let target = W / 2;
if (G.phase === 'play') {
let best = null, bt = Infinity;
for (const b of G.balls) {
if (b.stuck || b.vy <= 0) continue;
const t = (PADDLE_Y - R - b.y) / b.vy;
if (t >= 0 && t < bt) { bt = t; best = b; }
}
if (best) target = predictX(best, bt) + G.aiErr;
let cap = null, ct = Infinity;
for (const c of G.caps) {
const t = (PADDLE_Y - c.y) / CAP_SPEED;
if (t >= 0 && t < ct) { ct = t; cap = c; }
}
if (cap) {
const reach = Math.abs(cap.x - P.x) / AI_SPEED;
if (!best || (Math.abs(cap.x - target) < 110 && reach < bt)) target = cap.x;
}
}
const step = AI_SPEED * dt;
P.tx += clamp(target - P.tx, -step, step);
}
/* ================= GAME FLOW ================= */
function newGame() {
G.score = 0; G.lives = 3; G.broken = 0;
G.speed = BASE_SPEED; G.wideT = 0;
G.caps = []; G.parts = []; G.texts = [];
G.banner = ''; G.bannerT = 0;
G.keyL = false; G.keyR = false;
G.shake = 0; G.flashPaddle = 0;
G.paddle.w = PADDLE_W; G.paddle.x = W / 2; G.paddle.tx = W / 2;
G.endT = 0; G.fwT = 0; G.aiErr = 0;
buildLevel();
serveBall();
updateOverlay();
}
function startGame() {
if (G.mode === 'play') return;
ensureAudio();
G.mode = 'play';
newGame();
launch();
}
function restartGame() {
if (G.mode !== 'play') return;
if (G.phase !== 'over' && G.phase !== 'won') return;
if (G.endT > 0) return;
newGame();
}
function togglePause() {
if (G.mode !== 'play') return;
if (G.phase === 'paused') { resume(); return; }
if (G.phase === 'play' || G.phase === 'serve') {
G.prePause = G.phase;
G.phase = 'paused';
updateOverlay();
}
}
function resume() {
if (G.phase !== 'paused') return;
G.phase = G.prePause;
updateOverlay();
}
/* ================= OVERLAY ================= */
const overlayTpl = {
start: () => `
<div class="kicker">Brick breaker · arcade</div>
<h1 class="title">BREAKOUT</h1>
<p class="prompt">Click or press a key to start</p>
<ul class="keys">
<li><kbd>←</kbd> <kbd>→</kbd> or mouse to move the paddle</li>
<li><kbd>Space</kbd> or click to launch the ball</li>
<li><kbd>P</kbd> pause · <kbd>M</kbd> mute</li>
</ul>`,
paused: () => `
<div class="kicker">Paused</div>
<h1 class="title sm">PAUSED</h1>
<p class="prompt">Press P, Space or click to resume</p>
<button class="cta" type="button" data-act="resume">Resume</button>`,
over: () => `
<div class="kicker">Out of balls</div>
<h1 class="title bad">GAME OVER</h1>
<p class="score">Final score <b>${G.score}</b></p>
<button class="cta" type="button" data-act="restart">Play again</button>
<p class="hint">or press Enter</p>`,
won: () => `
<div class="kicker">Level complete</div>
<h1 class="title win">VICTORY!</h1>
<p class="score">All ${TOTAL} bricks smashed · Score <b>${G.score}</b></p>
<button class="cta" type="button" data-act="restart">Play again</button>
<p class="hint">or press Enter</p>`
};
let overlayKey = null;
function updateOverlay() {
let key = '';
if (G.mode === 'demo') key = 'start';
else if (G.phase === 'paused') key = 'paused';
else if (G.phase === 'over' && G.endT <= 0) key = 'over';
else if (G.phase === 'won') key = 'won';
if (key === overlayKey) return;
overlayKey = key;
overlay.classList.toggle('hidden', key === '');
card.innerHTML = key ? overlayTpl[key]() : '';Reasoning trace (114,922 tokens)
The user wants a polished single-file Breakout game, delivered as one code block with nothing around it. It has to open in a self-playing demo that waits behind a "Click or press a key to start" prompt, show a clear keyboard message, run without console errors, and look right from 1280x800 down to 375px wide. I'm also keeping track of the requirement that the paddle responds to both the mouse and the left and right arrow keys. Now I'm working out how the ball should collide with bricks and the paddle so it can never tunnel through them. I'm planning to break each frame into small substeps, capped at about half the ball's radius per step, and check for collisions at every step. I'm working out how to tell which side of a brick the ball hit, using the previous position: a vertical hit if the ball was clear above or below the brick, otherwise a horizontal one. For the paddle, I only bounce the ball when it's moving downward, and the exit angle comes from where it struck, up to about 65 degrees from vertical. The ball's bounce off the paddle depends on where it lands: the exit angle is set from the hit position, with the speed kept constant. I'm also weighing a substep check for when the ball's bottom edge crosses the paddle's top from above while moving down. If the ball ends up inside the paddle anyway, I'd push it back up. Now I'm settling how the paddle handles collisions: it bounces the ball only when it's moving down, and if the ball ever ends up overlapping the paddle, I'll push it back above the paddle's top edge. I'm also planning power-up icons drawn on the bricks, like a "W" for a wider paddle or an "M" for multi-ball. Now I'm laying out the power-up effects: a wide paddle that lasts ten seconds with a timer bar in the HUD, and a multi-ball mode that spawns two extra balls from the main ball's position so there are three in play, with any lost balls only subtracted once that count drops. Now I'm settling the timing rules: balls that are still in play keep going after a loss, and a life is only lost once none remain. The wide paddle lasts ten seconds, a second catch resets that timer, the paddle width eases toward its new size, and the countdown appears in the HUD. The timers only tick while the game is actually running, including during the demo. I'm settling the layout now: a fixed 800×640 logical world that scales to fit the viewport, so the whole game shrinks proportionally on narrow screens. I'm sizing the layout now: a fixed 800×600 logical world that scales to fit the viewport, with the score, lives, level, and power-up timers in an HTML header above the canvas. I'm settling the HUD and overlay design now: the score, lives, level, and power-up timers go in an HTML bar above the canvas, with the start, game over, and victory screens as HTML overlays on top. For sizing, the canvas gets a CSS width capped by the viewport and a fixed 4:3 aspect ratio, and I'll keep it updated with a resize observer. Now I'm settling the canvas sizing: the internal resolution stays fixed at 800×600 logical units, and the canvas is DPR-aware so it stays crisp on high-density screens, with the CSS size scaling to the viewport. I'm also planning a start overlay that carries the "Click or press a key to start" message in HTML rather than drawing it on the canvas. Now I'm weighing a semi-transparent start overlay over the running demo, with the title and the "Click or press a key to start" message in a small centered panel so the game stays visible behind it. I'm planning the demo AI so the paddle follows the lowest descending ball with a bit of lag and noise, so it sometimes misses and the demo plays out like a real game. When the demo ends, I'll restart it automatically after a few seconds. I'm working through how the demo hands over to real play: a click or keypress starts a fresh game with three lives and a zeroed score, and from then on the player controls the paddle with the mouse or the arrow keys. I'm settling the end-of-game flow: a game over or victory overlay with a restart button, with Enter or Space also restarting. For input, I'm listening for arrow keys at the window level and preventing their default scrolling, while the mouse on the canvas sets the paddle target through pointer events. Now I'm settling how the paddle follows input: the mouse sets its position directly, while the arrow keys move a target at a steady speed, and whichever was used last takes over. I'm also considering a pause toggle on P or Escape. I'm looking at the pause option now: a P or Escape toggle that shows a "Paused" overlay, maybe with a pause button in the HUD. I'm also weighing WebAudio blips for polish, created only after the first user gesture so it doesn't throw or warn. I'm weighing whether to create the audio context only after the first click so it doesn't trigger a browser warning, and adding an M key to mute sound. Then I'm sketching the level layout: 7 rows by 12 columns, with the top rows as tougher bricks that take two hits. Now I'm sketching the brick layout as a grid of codes: normal bricks, tough ones that take two hits, and markers for the bricks that carry power-ups. I'm settling the brick codes now: plain tiles for normal bricks, tougher ones that need two hits, and separate markers for the bricks that drop a wide-paddle or multi-ball power-up. Now I'm sketching the level as a 12-by-7 grid: the top row is all tough bricks, and the rows below mix normal bricks with a few that carry the wide-paddle or multi-ball power-ups. Now I'm sketching the level as a grid of codes: plain tiles for normal bricks, tougher ones that take two hits, and separate codes for the bricks that drop a wide-paddle or multi-ball power-up. Still sketching the level grid, now with the power-up bricks split into four kinds: normal with wide, normal with multi, tough with wide, and tough with multi. Still working out the level grid: I'm sketching the rows as 12-character strings, with a mix of normal and tough bricks and a few that carry power-ups. I'm laying out the brick grid now, mixing plain and tough bricks in the top row and scattering the power-up bricks through the rows below, and I'm still checking that the counts come out to twelve columns per row. I'm settling the brick layout as a 12-column grid across seven rows, alternating plain and tough bricks so the top area takes more hits to clear. I'll draw cracks on tough bricks after the first hit and mark the power-up carriers with a small icon so they stand out. I'm settling how the bricks look and score: tough bricks get a metallic, lighter finish that darkens and shows cracks after the first hit, and I'm working out point values, with normal bricks at 10 and tough ones at 25. I'm working out the falling power-up capsules, which will use the same icon as the brick badge, and I'm checking the ball speed ramp: a base of 420 units per second that goes up by 15 every twelve bricks, capped at 620. I'm settling on ball launch: the ball stays stuck to the paddle until the player clicks the canvas or presses Space, while the demo AI launches it on its own after a short delay. A small on-canvas hint will say "Click or press Space to launch," and I'm still thinking through how the demo AI handles its launch. I'm defining the lives and loss rules: a life goes when every ball falls past the bottom, the paddle recenters, and falling capsules and wide-paddle effects clear. Game over comes at zero lives with a restart button, and clearing every brick shows a "Level cleared!" overlay with the score and a play-again option. I'm also planning a brief particle burst on each break, with extra chips when a tough brick cracks, and a screen shake. Still working on the paddle's look and the ball's trail, and I'm checking how mouse input and the overlay's start click interact so the click doesn't leak through to the canvas. Now I'm wiring the mouse input to the whole game wrapper, so the paddle keeps following the pointer anywhere inside it, and I'm mapping those positions relative to the canvas rect. Now I'm wiring the pointer input so the paddle follows the mouse anywhere over the game area, mapping its position to logical coordinates through the canvas rect. Moving the mouse during the demo won't take over; only a click does. I'm settling how pointer and keyboard input share the paddle: the arrow keys nudge the paddle target at a steady speed, the mouse sets the target directly, and whichever moved most recently wins. I'm laying out the demo AI's pacing and the pause behavior next. The demo AI will steer the paddle toward the ball's x position with a bit of noise and ignore the keyboard, and P or Escape will toggle a pause overlay that says to click or press P to resume. I'm sorting out the state machine next: a demo state, a playing state, and paused, life-lost, game-over, and victory states. The game opens in demo mode with the start prompt, and the first click or key press starts a fresh run. I'm working out the state flow for the lives and victory cases: a lost life moves to a serve state where the ball sticks to the paddle, and in demo mode the game restarts itself after a short pause when lives run out or all bricks are cleared, with the start prompt staying on screen. I'm working out how the overlay switches between its start, game over, victory, and paused states, and how keyboard input maps to each one. The Breakout game would need an extra check so a click on the restart button doesn't also reach the wrapper's pointerdown handler and launch or restart the game twice. I'm settling the double-start problem: the wrapper's pointerdown handler would fire when the Start button is clicked, so I'll let the wrapper handle starting in demo mode and have the button only restart during game over or victory, with the wrapper ignoring pointers in those states. I'm deciding that the demo overlay will be just the styled start message and a list of controls, with no separate Start button, since the whole wrapper is already clickable. For the pause screen I'm weighing a Resume button, where a click on it reaches the wrapper's pointerdown handler and resumes the game without needing any extra click logic. I'm checking the restart button's focus handling: pressing Enter or Space on it would fire a click, so a global keydown restart could collide with that click and restart the game twice. Once the overlay hides, the focused button loses focus, which should keep the second restart from landing. I'm settling the overlay buttons: keyboard users can reach a restart button by focusing it when the game-over overlay appears, so Enter or Space on it does the restart through the button's own click, with no separate global key handler to collide with. I'm weighing whether a global R or Enter handler on the game-over screen could collide with the restart button's own activation, since a focused button may fire a click on the same keypress. A double restart would be harmless, so I'm checking whether the hidden overlay drops focus in time to prevent it. Now I'm settling the brick layout: the grid will have 12 columns with 4px gaps and a 24px side margin, so each brick ends up roughly 56px wide. Sizing the brick grid now: 59px-wide bricks across 12 columns, 22px tall in seven rows. Then I'll put the paddle at the bottom and set the ball's starting speed around 360. Still going through the collision math: each substep moves the ball no more than about 3 pixels, so a 26-pixel brick can't be skipped over, and I'm finding the closest point on each brick's rectangle to the ball's center to decide whether they overlap. Next I'm working through the collision normal for the circle hitting a rectangle: the offset from the closest point to the ball's center gives the direction, and reflecting the velocity off that normal handles sides and corners the same way. Now I'm checking the paddle collision: if the ball overlaps the paddle while moving downward, I'll compute the hit offset from the paddle's center to set the bounce angle, and push the ball just above the paddle so it can't pass through. The open question is a ball coming in sideways and catching the paddle's edge. I'm checking the paddle collision next: a ball descending onto the paddle's edge hits the corner, so I'll treat any overlap while moving down as a bounce and clamp the angle. Now I'm working through the edge case where the ball hits the paddle from the side at its top edge: if it overlaps while moving downward or sideways, I'll bounce it upward at an angle, and if it's coming up from below the paddle's top, I'll push it out sideways. I'm settling how paddle hits get resolved: if the ball is above the paddle's top surface and moving down, I'll bounce it upward at an angle, and if it overlaps from the side while below the top edge, I'll push it out horizontally and reflect its sideways speed. I'm also checking that the substep size of about 3 pixels is small enough that the ball can't skip past the paddle. I'm checking the paddle hit angle next: the offset from the paddle's center sets the bounce, clamped to about 70 degrees so the ball never goes flat. I'm also making sure the vertical speed never drops too low, so the ball can't get stuck bouncing sideways between the walls. I'm working out how the ball behaves at the walls and when it gets lost: it bounces off the left, right, and top edges by flipping its velocity, and once it drops past the bottom it's removed, with a lost life only if no balls remain. Next I'm working out multi-ball and wide-paddle timing: the paddle widens for about 10 seconds, and when the multi-ball power-up is collected, I'll top up to three balls by spawning extras angled roughly 0.35 radians off the current direction. Next I'm checking the case where a capsule is caught while no ball is in play: the new balls would have to be spawned at the paddle rather than from an existing ball, so I need a fallback for that. Next I'm settling how balls behave while stuck to the paddle: the first ball rides on the paddle top in a serve state and launches at a small random angle when released. Bricks take damage on each hit, so a tough brick cracks on its first strike and only drops its power-up capsule when its hit points reach zero. Next I'm laying out the particle system for brick debris and a floating "+10" score popup, plus a brief screen shake and a "Life lost" banner when a life is lost. I'm also planning crack lines on tougher bricks, drawn once they've taken their first hit. Now I'm picking the brick colors: seven rows, each with its own shade from pink-red at the top down to purple, while the tough bricks get a steel gradient so they don't read as a regular row. Now I'm choosing the icons for the power-up badges: a teal double arrow for the wide paddle and an amber triangle of dots for multi-ball, with the carrying bricks pulsing so they stand out. The falling capsules get the same colors, and the HUD above the canvas will show the score on the left and the level with the remaining bricks in the middle. Still laying out the HUD and the page structure, with the score, level, lives, and power-up timers in the header above the canvas. Still writing the page layout: the HUD header holds the score, lives, and power-up timers, with the canvas stage and a controls footer below. The stage sizes itself to a 4:3 ratio so it fits the viewport height. Still writing the stage sizing, so the canvas fits both the viewport width and height at a 4:3 ratio. Still working out the stage sizing: I'm capping the width against both the viewport width and a height-based value so the 4:3 canvas fits on screen. On a narrow phone the width will be the limiting factor, so the stage will shrink while the HUD may wrap onto more rows. Still writing the canvas sizing: the internal resolution will follow the stage's on-screen width times the device pixel ratio, so it stays sharp on high-density screens. I'll redo the transform from there so the 800×600 game coordinates still map onto the canvas. I'm settling the overlay styling: a dark translucent backdrop with blur, and a centered card carrying the title and the pulsing "Click or press a key to start" prompt. The stage's pointer events will bubble up from the overlay, so a click anywhere still starts the game, and the canvas will draw its own serve hint. Now I'm settling the tough bricks as a steel gradient with a darker bevel, cracks drawn on the first hit, and power-up badges in teal and amber. Still writing the page's base CSS: the body gets a layered dark background, and I'm setting up the shared color variables. Still writing the page's base CSS, now sizing the HUD and stage to one shared width variable so they line up. Still sizing the stage: I'm settling on a width that comes from the viewport height minus about 190px for the header and footer, so the 4:3 canvas stays fully on screen at 1280×800 and shrinks on shorter windows. Still sizing the stage and HUD: both will share one width tied to the viewport, and on a phone the HUD will wrap onto two rows while the footer stays small. Still writing the game's constants and level layouts: a 12-by-7 brick grid with tough bricks and power-up carriers encoded by letter. Still laying out the level grid: I'm settling the brick rows, with a few tough bricks in each and about five power-up carriers spread across the layout, two for wide and two for multi-ball. Still laying out the level grid, now the brick rows: a tough brick in the first row, and a couple of wide and multi-ball carriers spread through the middle rows. Still laying out the brick grid, now the third and fourth rows: a tough brick at the start of row two, and a tough brick carrying a wide-paddle power-up in row four. Still laying out the brick grid, now the lower rows: a few tough bricks sit in the fourth and sixth rows, and the wide and multi-ball carriers are placed to match the earlier ones. Now I'm checking the brick layout: 14 tough bricks in total, the rest normal, and six power-up carriers split evenly between wide and multi-ball, including a tough brick on each type. Still setting up the brick geometry and paddle dimensions for the game field. Still writing the game state: a single object tracking the mode, score, lives, level, bricks, balls, and effects. Still writing the game state: the ball and power-up timers are being set up so the stuck ball follows the paddle until launch. Still writing the game loop's ball logic: when multi-ball is active, I'm spawning extra balls off the lead ball and angling each one a bit to the side of it. Still working out the multi-ball spawn: each new ball starts at the lead ball's position, with its velocity turned half a radian to one side or the other. Still working out the multi-ball spawn: the new balls start at the source ball's position and are angled slightly off its path so they separate over time. Still wiring up the multi-ball spawn, now the speed and the physics step: each new ball is rotated half a radian off the lead ball's direction, and velocities get rescaled whenever the speed climbs with bricks broken. Still writing the physics step: I'm sub-stepping each ball's motion so fast balls can't tunnel through walls, paddle, or bricks, and running the wall, paddle, and brick checks on every sub-step. Still writing the paddle collision: I'm clamping the ball to the paddle's top edge and checking whether the distance to that point falls inside the ball radius. Now I'm settling the paddle's bounce: if the ball is moving down, I'll send it back up whenever it meets the paddle, including at the edges, so corner hits still return cleanly. I'm checking the paddle bounce for the case where the ball is moving down and overlaps the paddle from the side. Pushing it back to the top edge should work even for corner hits, so I'll send it up whenever it's descending into the paddle. Still working out the paddle bounce: if the ball is descending, it gets pushed back to the top edge and sent upward, which also covers corner hits. If the ball is rising into the paddle from below, I'll push it out to the nearest side and send it downward. Still writing the paddle bounce: the top hit sets the ball's horizontal speed from where it lands on the paddle, and the vertical speed follows from the same angle. Still writing the brick collision: I'm clamping the ball to each brick's rectangle to find the nearest point, and skipping any brick that's out of reach. Still writing the brick collision: I'm finding the nearest point on each brick's rectangle and pushing the ball out by its overlap, with a fallback direction when the center is already inside. Still writing the brick collision: once the ball's center is found to be inside a brick, I'm pushing it out along the surface normal and reflecting its velocity if it's moving into the brick, then stopping after one brick per sub-step. Still writing the brick hit handler: a tough brick loses one hit point and gets a crack, while any other brick is removed, scores points, and may drop a power-up capsule. Still writing the brick hit handler: a tough brick gets one hit point knocked off, and anything else is removed and scores. Still working on the game's floating text and particle effects: particles fly out from broken bricks in their color, with the count depending on brick type. Power-up collection will show popup labels and update the HUD timer. Still writing the brick draw code: a rounded gradient fill for each brick, with a highlight along the top edge, a drop shadow, crack lines on tough bricks, and a power-up badge where one is carried. Still writing the brick draw code: a tough brick gets a gradient fill, and a damaged one switches to a darker palette. Still writing the brick draw code: the cracks on damaged bricks are being generated as short random polylines, and I'm drawing them as dark strokes. Still writing the brick draw code: the cracks grow in over about 0.2 seconds, and I'm adding a few small chip particles on the first hit. Still writing the paddle and capsule drawing: the power-up capsules get a glowing gradient with a wide or multi-ball icon inside, and the paddle gets a rounded gradient with a glow and highlight. Still writing the overlays: the menu, pause, and game-over screens are built as static HTML, with the score filled in afterward as plain text. Still writing the overlays: the start, game-over, victory, and paused cards each get a title, a short line of text, and a button, and the start card shows a pulsing "Click or press a key to start" prompt. The overlay hides once play or serve begins, and the serve hint is only drawn outside the demo, where the AI launches the ball on its own after a short delay. Now I'm setting the overlay to a light, see-through backdrop so the demo stays visible behind the start card. For the demo AI, I'm treating the state like normal play, and steering the paddle toward where the most urgent ball will land, with a little random error added so it doesn't look perfect. Still writing the AI paddle: I'm setting a target position and moving the paddle toward it at a capped speed, so it never teleports across the screen. Now I'm predicting where the most urgent ball will cross the paddle line, folding its horizontal position back inside the walls to account for bounces. Still writing the AI paddle: if a falling power-up capsule is low on the screen, I'm aiming for its x position so the demo catches it, otherwise I'm aiming for the predicted landing point of the most urgent descending ball, and centering when nothing is coming down. Now I'm settling the AI's aim error: rather than jittering every frame, it gets a random offset of about ±22 units that re-rolls each time the paddle hits the ball. Then, in demo mode, if the last life is lost, the game restarts after a two-second pause, with the HUD reading "DEMO". Still writing the game's audio: I'm setting up a lazily created audio context for simple beeps, with a mute toggle on the M key and a button. Still writing the audio helper: the tone function sets up an oscillator and gain envelope that fades out over its duration. Still writing the audio helper: the tone function fires a short oscillator with a gain envelope that ramps down to near silence, and sounds are only played when the audio context exists and sound isn't muted. Still writing the input handling: a pointer move maps the mouse position onto the canvas and sets the paddle target, but only while play or serve is active, since the AI owns the paddle in demo. A pointer down starts the game from demo, launches from serve, and otherwise acts during play. Still wiring up the input handling: pointer-down with nothing to act on falls through, a paused game resumes, and over or won states are left to the buttons. The Enter, Space, and P keys will also resume from the pause screen. Still writing the keyboard handler: it ignores modifier combos, prevents default scrolling for the arrow keys and Space, and routes each key by the current game state. From the demo any key starts the game, and on the over or won screens Enter, Space, or R restarts it. Still writing the keyboard handler: the rest of the key routing covers launching from serve and steering with the arrow keys during play, and I'm checking how the Space key on a focused restart button gets handled. Now I'm weighing the restart on the demo's game-over screen: the restart button should only fire when the game is actually over or won, so a second click from the same keypress can't reset a game that just began. Now I'm handling the window blur and visibility changes: if the tab is hidden while playing, the game pauses, and held keys get cleared. Then I'm sketching the HUD buttons for pause and sound, keeping them as simple text pills rather than icons. Still writing the start flow: the demo keeps the start overlay up with its "Click or press a key to start" line, and I'm working out how the game moves from the demo into a fresh serve state. Still settling the state model: the demo runs as its own mode, and the phase tracks serve, play, paused, over, or won, with the overlay showing whenever the demo is on or the game is paused or finished. Still writing the game-over flow: if the last life is lost in demo mode, the game simply restarts into a fresh demo after a short pause, with the start overlay staying up. Now I'm settling the end-of-game flow: losing the last life in demo mode resets the demo after about 1.6 seconds, and clearing the bricks means a win, which shows after a one-second pause so the last explosion can be seen, then restarts the demo after three seconds. For the overlay, the demo keeps the start card up, paused shows the paused card, and over or won show their own cards in regular play. Still writing the frame loop: I'm setting up a timestamp-based delta so physics runs at a steady rate, and the loop skips the update while the game is paused. Still writing the frame loop: the update step advances the game clock, decays the flash and shake effects, and sets the paddle target from the AI in demo mode or from the held arrow keys otherwise. Keyboard input should feel tighter than the mouse, so I'm weighing whether the paddle should follow the key-driven target with smoothing or move directly. Smoothing would leave it trailing by roughly 32 units at full keyboard speed, which is probably acceptable, but I'm leaning toward moving the paddle itself with the keys. Still writing the AI: the paddle's smoothing stays around 30, giving a lag of roughly 23 units, and it's kept inside the walls. The AI loops over the balls heading downward, picks the one that reaches the paddle line soonest, and aims at its predicted landing point. Still working out the AI's paddle target: it aims at the predicted landing point of the closest falling ball, with a small error offset, and stays put during serve. Now I'm checking whether the capsule should win over the ball: if the capsule reaches the paddle line sooner than the ball, the AI goes for the capsule first, then heads back to the ball afterward. Still writing the AI's capsule handling: it picks the capsule over the ball if the capsule reaches the paddle line within about 0.4 seconds of the ball, accepting that it may sometimes miss the ball. Now I'm working out how to predict where a ball will land: I'm projecting its horizontal position to the paddle line, folding it back off the side walls. Still writing the AI's aim: the paddle error gets re-randomized on each hit, within about thirty percent of the paddle width, and the demo keeps the paddle inside its range so it doesn't glitch. Still writing the brick setup: each brick gets its position, toughness, hit points, color, and any power-up it carries, and tough bricks get a set of random cracks generated for them. Still writing the brick rendering: the color ramp for each row runs through a shade helper, and the bricks get a gradient body with a lighter top edge. Still writing the brick rendering: I'm rounding the corners with a helper and drawing a steel gradient for the tough bricks, switching to a damaged look once they've taken a hit. Still writing the brick colors: damaged bricks switch to a warmer, rustier gradient, and the cracks get drawn as dark lines with a lighter highlight. Still writing the HUD: the score, lives, and bricks-left counters only get touched when their values actually change, and the power-up timers show up as their own badges. Still writing the game's HUD and canvas text: the power-up chips and demo badge get their own states, and the serve and ball-lost messages are drawn directly on the canvas. Still writing the page styles: the body gets a dark gradient backdrop with teal and violet glows, and it's set to fill the full viewport height. Still writing the page styles: the header bar, stat boxes, and brand text are getting their layout and colors. Still writing the page styles: the stage gets a fixed aspect ratio with rounded corners and a soft glow, and the buttons and power-up chips are being styled next. Still writing the page styles: the stage gets a fixed aspect ratio with rounded corners, and the overlay and cards are being set up to show the start, pause, and end screens. Still writing the page styles: the prompt gets a pill look with a gentle pulse, the controls are laid out as a small list, and the footer is set to match the stage width. The HUD and fonts will shrink on narrow screens. Still writing the HTML layout: the header holds the title, stat boxes, power-up chips, and buttons, and the stage holds the canvas with the start overlay on top. Still writing the HTML: the pause button and the sound toggle sit in the header, and the footer lists the controls. Now I'm writing the start overlay: a title, a subtitle, and the "Click or press a key to start" prompt, with a list of the controls below. Still writing the start and end overlays: the start card gets a title, a short subtitle, the "Click or press a key to start" prompt, and a small list of controls, and the game-over and victory cards get their score line and restart button. Still writing the start and end screens: the game-over and victory cards get their final score and a play-again button, and the pause card gets a resume prompt. Still writing the event handling: clicks on the restart button trigger a restart, and the pointer handler on the stage ignores input on the game-over screen. A click on the start card also starts the game through the stage. Still working out the click handling: a pointerdown on the paused overlay resumes the game, and the click that follows is harmless since resuming is a no-op once the phase is already playing. Still writing the pointer handling: the stage tracks pointer position and maps it onto the canvas width, so the paddle follows the mouse or touch. Still writing the game constants and brick grid geometry. Still writing the game constants: paddle and ball sizes, speeds, and the angle limit, along with the row colors and the brick layout string. Still writing the brick layout: the level string is a 12-column grid, with each row checked for the right number of cells. Still writing the brick definitions: the level grid is checked to make sure every row has twelve cells, and the power-up carriers are counted up. Still writing the game state: the mode is set to demo, the phase starts at serve, and the score, lives, and broken-brick count are initialized along with the empty lists for balls, power-up capsules, particles, and floating text. Still writing the canvas setup: I'm sizing the backing store to the stage width and device pixel ratio, keeping the aspect ratio fixed. Still setting up the canvas: I'm making the backing store match the stage size and pixel ratio, and resizing it whenever the stage changes. Still setting up the game logic: I'm defining the random and clamp helpers, then the crack patterns for the tougher bricks. Still writing the crack pattern: each crack starts at a random point inside the brick and wanders in short, angled segments, with the segment length measured in pixels. Still writing the crack pattern: each crack starts near the middle of the brick and wanders in short angled segments, with the next segment being generated now. Still writing the crack pattern: each crack is a short polyline wandering from near the brick's middle, with a few angled segments clamped inside the brick bounds. Still writing the brick grid: each brick gets its position, row and column, whether it's tough, any power-up it carries, and its own crack pattern if it's tough. Still writing the game logic: I'm adding the ball factory and the launch function, which picks a random angle near straight up and sets the ball's velocity from the current speed. Still writing the game loop: the launch function sets the ball's velocity and flips the phase to play, and serving resets the ball onto the paddle. Still working out the ball's lost threshold: the paddle's top edge sits at PADDLE_Y, so the ball's center rests just above it, and a ball counts as lost once its bottom edge drops past the field's bottom. Still writing the game loop: the update function now ticks the timers, the screen shake, and the paddle flash, and it routes paddle movement through the AI in demo mode or the player's input otherwise. Still writing the paddle update: the target position is clamped to the field, and the paddle eases toward it while its width eases toward the wide or normal size. Still writing the game loop: the update step ticks the timers and the screen shake, and in demo mode the AI steers the paddle while the player's input drives it otherwise. Still writing the game loop: ball physics only runs in play, stuck balls ride along with the paddle, and any ball that drops past the bottom is removed. Still writing the collision code: the capsule is caught when its box overlaps the paddle's top edge and horizontal extent. Still writing the spawn logic for extra balls: it grabs the live ball as the source and sets up two side angles to add up to three balls. Still writing the spawn logic for extra balls: each new ball copies the source ball's position and gets its velocity rotated by a small angle, until there are three in play. Still writing the game loop: the life-loss path and the game-over handling, with a second multi-ball pickup giving a 50-point bonus and a "3 BALLS" message. Still working out the end-of-level flow: when the last brick falls, the game enters a clearing phase with a short countdown so the explosion can play out before the overlay appears. Continuing the clear-phase logic: the last-brick path should leave the balls moving, so physics still runs while the explosion plays out. Still wiring up the loss path: a ball falling during the clear phase just gets removed, and only a play-phase empty field triggers loseLife, so the victory screen isn't overridden. Still writing the overlay: the key is picked from demo, paused, over, or won, and the overlay only rebuilds its content when that key changes. I'm weighing whether the start overlay should stay quite dark or turn lighter so the demo shows through, and I'm considering a card placed at the bottom instead of the center so it doesn't cover the bricks. Still working on the start overlay: I'm weighing a compact centered card so the bricks stay visible, then I'll move on to the HUD update each frame. Still writing the HUD: I'm refreshing the score, lives, and remaining-bricks readouts each frame, but only touching the DOM when a value has actually changed. Still writing the HTML for the power-up chips and the pause button label. Still writing the background: a vertical gradient fills the canvas, and I'm starting a faint grid overlay on top of it. Still writing the background: I'm drawing a faint danger-zone glow along the bottom of the field. Still writing the paddle draw: I'm setting up a glowing teal shadow and starting a vertical gradient across the paddle's height. Still writing the paddle highlight and the flash on hit, then moving on to the balls: each ball gets a fading trail drawn from its recent positions. Still drawing the balls: each one gets a fading teal trail from its recent positions, then a radial-gradient body with a glow. Still drawing the balls: I'm rendering each capsule as a glowing rounded box. Still writing the capsule icons: I'm drawing the wide and multi symbols as strokes in the dark color over each bright capsule. Still writing the icon: I'm drawing the three-ball symbol by setting up each small circle's radius. Still writing the icons: I'm renaming the local radius to avoid clashing with the rounded-rect helper, and I'm working out how the small badge circles on the bricks will look. Still writing the brick badge: a pulsing colored circle with a white ring, and the icon drawn on top in dark ink. Still writing the particle system: each burst spawns a batch of pieces with random angles and speeds, and they fall under gravity while their life runs out. I'm capping the list at 600 entries and drawing each piece with its remaining alpha. Still drawing the text popups: each one rises slowly while it fades out over its lifetime. Still writing the AI: in serve mode the demo paddle just sits near the center, and otherwise I'm picking out the ball that will reach the paddle line soonest and predicting where it will land. Still working out the AI's aim: the paddle goes for the predicted landing spot of the ball closest to reaching it, with a small error offset, and if a capsule would arrive first or nearly first, it targets the capsule instead. Still writing the AI's paddle movement: I'm limiting how fast the demo paddle can chase its target so it moves at a steady speed rather than snapping across the field. Still tuning the AI's aim: the demo paddle moves toward its target at a capped speed, and after each paddle hit it picks a small random error, so it misses now and then instead of playing flawlessly. Still working through the demo's AI: after a ball bounces off a brick its path changes, so the paddle recalculates its intercept every frame, and I'm also planning for pointer input to start the game from the demo. Still writing the new-game setup: the start function switches to play mode and resets the score, lives, and paddle, then builds the level and serves the first ball. Still writing the restart and pause logic: restarting from the game-over screen switches back to play and serves a fresh ball, and pausing toggles between the paused phase and the phase before it. Still working through the pause logic: pausing records the current phase, switches to paused, and refreshes the overlay, and resuming restores that saved phase. Still writing the pointer handling: moves update the paddle target only during play or serve, and a click starts the game from the demo, resumes from pause, or launches the ball when it's in serve. Still writing the pointer handling: I'm setting up the click path so a press on the field starts a demo, resumes a pause, or launches a served ball, and moves the paddle toward the pointer. Still writing the keyboard handler: I'm skipping modifier combinations and blocking default scrolling for the arrows and space, then the first key press in demo mode starts the game. Still writing the keyboard handler: after the demo starts on a key press, the key falls through to normal handling, so Space can launch a served ball straight away. Mute is handled first, then the game-over and paused phases get their own key checks. Still writing the keyboard handler: the arrow keys and A/D set the left and right flags, and Space or Enter launches a served ball. In demo mode the first key press starts the game before it's handled. Still writing the keyboard handler: I'm wiring the key-up side so the arrow keys and A/D clear their flags when released. Still writing the keyboard handler: I'm wiring the blur event to clear the arrow flags and auto-pause the game if it's playing. Still writing the blur and visibility handling: I'm switching the auto-pause to trigger only when the page is hidden, and clearing the arrow flags on blur so keys don't get stuck. Then I'm wiring the mute toggle and the pause button, which pauses during play and does nothing else. Still writing the pause button: I'm disabling it during the demo, and the sound toggle will also call ensureAudio before muting. Then I'm wiring the card's buttons through one delegated click handler so restart and resume go to their own actions. I'm wiring the click path so a pointer press on the overlay's buttons doesn't double up with the stage handler, since the overlay goes pointer-events none once it's hidden. I need to make the click handling safe: resume only acts when the game is actually paused, so a pointerdown that already resumed won't get toggled back into pause by the follow-up click. Restart from the click handler should likewise only fire from the game-over or win states. The HUD pause button sits outside the stage, so its press has to be wired separately. Still working through the restart logic: I'm guarding it so it only acts from the game-over or win screens, then resetting to a fresh run. Still writing the demo's restart path: in demo mode, a game-over or cleared level starts a fresh run automatically after a short timer. Still writing the brick hit logic, so the handler resolves each collision against its brick. Still writing the brick hit logic: tough bricks lose a hit point and flash with a small score bump, while a destroyed brick adds points, may drop a power-up capsule, and speeds the ball up. Still writing the brick hit logic: the speed cap gets applied after each break, and the crack animation ticks forward each frame. Still writing the brick hit logic: I'm rescaling the balls to the new speed and counting the bricks left after each break. Still writing the collision stepping: each ball moves in small substeps so it doesn't tunnel through bricks or the paddle. Still writing the wall and ball-step logic: each substep clamps the ball against the walls, checks the paddle and bricks, and drops the ball once it falls past the bottom. Still working out the safeguard for stepBall: if the vertical speed drops too low after a collision, I'll reset it to a minimum and renormalize the horizontal component so the ball can't bounce forever sideways. Still writing the paddle collision: I'm checking whether the ball is in contact with the paddle's top edge or corner, and when it's moving downward I'm working out the bounce angle from where it hit. Still writing the paddle collision: the ball is bouncing off the top edge, so I'm setting its angle from where it hit the paddle and placing it just above the paddle. Still writing the paddle collision: when the ball is moving upward and overlaps the paddle from the side, I'm pushing it out sideways rather than downward. Still writing the paddle collision: if the ball is moving up and its center sits below the paddle's top edge, I'm checking whether it's already separating before I push it out sideways. Still writing the lateral push: I'm working out which side the ball is on relative to the paddle, moving it just outside that edge, and keeping its sideways speed pointed away from the paddle. Still writing the brick collision: I'm handling the case where a ball could end up inside a brick after a substep, and planning to check bricks one at a time with the step size kept small enough to prevent that. I'm working out the brick collision, with a fallback for the rare case where the ball's center ends up inside a brick: I'd pick the normal from the previous position so the push-out still points the right way. Still writing the brick collision fallback: if the ball's center is somehow inside a brick, I'm picking the push direction from whichever axis it's deeper along, then pushing it out and reflecting its velocity off that face. Still writing the brick collision: after the reflection, I'm calling hitBrick on that brick and returning true, and I want to make sure a destroyed brick can't keep catching the ball on later substeps. Still writing the burst helper: each particle gets a random direction and speed, nudged upward a bit, and it's pushed into the particle list. Still writing the particle burst: I'm updating each particle's gravity, velocity, and lifetime, then culling the ones that have expired. Now I'm writing the victory fireworks, spawning bursts at random spots near the top every quarter second during the clear phase. Still writing the HUD overlays: the serve prompt sits near the middle of the screen with a pulsing alpha, and there's a banner that shows for a short time with its own text and color. Still writing the HUD update: I'm caching the element references and tracking last-shown values so the score and lives only touch the DOM when they actually change. Still writing the HUD update: I'm working out the hearts display, filling the lives with hearts and dimming the lost ones, and the wide-paddle chip shows a countdown when its timer is running. Still writing the HUD styling: the wide-paddle and multi-ball chips get their own pills with countdown and ball-count text, the demo badge only shows in demo mode, and the pause and sound buttons update their labels to match the current state. Now I'm working out the template overlays: a start screen with the title, the "Click or press a key to start" prompt, and a short key legend. Still writing the template overlays: the game-over and paused screens get their own titles, score line, and buttons. Next I'm settling the overlay behavior: the start card has the keys list and the demo runs underneath it. For the paused card I'm keeping a resume button, and I'll move focus onto the button whenever a game-over, victory, or paused card appears so keyboard players can act on it right away. Still writing the CSS details: the title gets a bright gradient fill, the prompt pulses in opacity, and the key legend uses styled keycaps. Checking the audio setup next: the AudioContext gets created only inside the pointer and key handlers, so the browser won't complain about it starting before a gesture. I also want to catch the promise from resume, and use a tiny nonzero value instead of zero for the exponential ramps. Now I'm setting the start overlay lighter, with a fainter backdrop and a semi-transparent card so the demo stays visible behind it. Then I'm checking the init order: build the level, put the game in demo mode, refresh the overlay, and start the frame loop so the bricks are already drawn on the first paint. Still writing the update loop: I'm ticking the timers for shake and paddle flash, and I'm planning the AI to aim at center during the clear phase. Still writing the update loop: I'm ticking down the shake, paddle flash, banner, and wide-paddle timers, then letting the AI steer in demo mode while the keyboard moves the paddle in play. Still writing the paddle update: I'm easing its width toward the target, then smoothing its position toward the requested spot and keeping it inside the play area. Still writing the paddle update: after the serve and play branches, I'm stepping each live ball and recording its trail, with the paddle still tracking the target along the way. Still writing the update loop: after the paddle is placed, I'm stepping each live ball and recording its trail, and if the ball count hits zero during play I'm taking a life. Still writing the capsule handling: each falling capsule drops a bit each frame, gets removed once it leaves the bottom, and is collected when it overlaps the paddle. Still writing the timer logic: in demo mode a finished game restarts itself, and during the clear phase I'm counting down the end timer while firing fireworks every 0.3 seconds, then either restarting or moving to the won state. Still writing the game-over logic: when a life is lost or the game ends, I'm calling updateOverlay so the right card appears, and the same goes for the paused and resume transitions. Next I'll set up how a ball gets served, placing it on the paddle with an empty trail. Still writing loseLife: it drops a life, shakes the screen, clears the falling capsules, and plays a low buzz with a burst at the paddle. If that was the last life, the game goes to over and the overlay updates, otherwise a new ball gets served. Still writing the over-state timing: I'm making the game-over card wait about 0.9 seconds before it appears in a human game, while demo mode just restarts once the timer runs out. Still writing the update function: I'm working out the game-over and victory delays, so the overlay waits about 0.9 seconds before it appears for a human player, and in demo mode it restarts the game once the timer runs out. Still settling the restart rules: I'm guarding the over and won phases so a keypress only restarts once the overlay is actually showing, and I'm adding a canRestart flag that gets set when the overlay appears. Still writing the end-of-round timing: the game-over and win delays count down, and once they run out the overlay appears for a human player or a new game starts in demo mode. Still writing the end-of-round timers: when a game-over delay runs out, a demo restarts with a new game and a human player gets the overlay, and in the clear phase the win timer ticks down until it either starts a new demo game or moves to the won state. Writing the fireworks and victory jingle code... Still writing the sound effects: the brick hit pitch rises with the row, tough bricks get a low tone, and breaking a brick plays a triangle chime. Writing the brick drawing code... Still writing the brick colors: damaged tough bricks get a warmer, darker gradient, and the normal ones get a shaded version of their row color. Still writing the brick shading: a lighter top, a darker bottom, and a flash overlay when the brick is hit. Still writing the brick drawing: I'm adding a shaded glow ring and a badge for bricks that carry a power-up. Still writing the crack drawing: I'm lining up the crack paths on each damaged brick and making them grow with the damage level. Still writing the brick badge: I'm drawing a pulsing colored dot at the brick's center. Still writing the brick badge: I'm sizing the icon so it stays readable on the badge. Still writing the ball speed control: I'm adding a function that rescales every live ball's velocity to a new speed, and it gets called on each brick break so the ball ramps up gradually. Still writing the ball speed logic: I'm capping the speed at a set maximum and checking that the substep count stays reasonable at the fastest speed. Still writing the power-up handling: a wide paddle gets its timer set and a banner, and a multi-ball spawns extra balls from the source ball, with a small score bonus. Still writing the banner helper: it sets the banner text, color, and a 1.6-second timer, and the banner draws centered near the top of the play area with a fade-out as the timer runs down. During the serve phase in human play I'm adding a hint at the middle of the screen telling the player to click, tap, or press Space to launch. Still writing the page markup and styles: I'm setting up the color variables and a responsive board width that fits the window, with a hidden-attribute rule so overlays stay out of the way until needed. Still writing the page styles: I'm setting up the color variables and a responsive stage width that fits the viewport, and a hidden-attribute rule so overlays stay out of the way until needed. Still writing the page layout: I'm centering the app with flex, setting a dark gradient background, and keeping the content from overflowing sideways. Still writing the stat chips: I'm giving them a panel background, a thin border, and a minimum width so they stay readable. Still writing the page styles: I'm styling the stats, hearts, and power-up chips with panels, a small caps label, and a tabular-number readout. Still writing the badge and chip styles: the power-up chips get a teal or gold tint, and a small caps badge label sits beside the controls. Still writing the stage styles: I'm sizing the board to the computed stage width and keeping its layout centered within the page. Still writing the stage styles: the canvas fills the stage, and the overlay dims the board with a fade when it hides. Still writing the page styles: I'm finishing the card's padding, rounded corners, and blurred backdrop, and styling the kicker and title text. Still writing the page styles: I'm adding the title color variants, the prompt pulse, and the subtitle text. Still writing the page styles: I'm finishing the overlay's score readout and the key-hint list, with the keyboard keys drawn as small bordered caps. Still writing the stylesheet: I'm adding the CTA button's gradient, shadow, and focus outline, then the hint and footer text. Still writing the stylesheet: the brand, footer, and small-screen rules are being tightened so the layout holds at narrow widths. Still writing the constants: the canvas is 800 by 600, with 12 columns of 7 rows of bricks, and the paddle and ball sizes are set. Still writing the game constants: I'm setting the ball speed range and the paddle and brick sizing values, plus the color palette for the rows and the power-up chips. Still writing the level layout: I'm setting up the brick grid as strings of codes, with tough bricks and power-up carriers placed across the rows. Still writing the game loop and input handling: the constants, canvas, and UI references are set up, and I'm wiring up the DOM elements the rest of the script will use. Still writing the audio setup: I'm creating the audio context lazily on first input, guarded in a try block, with a mute toggle. Still writing the audio tone helper: it plays a short oscillator blip with a pitch slide and volume envelope, scheduled a little ahead, and skips entirely when the sound is muted or the audio context isn't running. Still writing the audio tone: I'm creating the oscillator and gain nodes, setting the wave type, and shaping the pitch and volume over the blip's duration. Now I'm setting up the game state, with the mode set to demo, the phase set to serve, and a note on the pause handling. Still writing the game state setup: I'm resetting the score, lives, and broken-brick count, along with the speed, the entity lists, and the paddle position. Still writing the game state reset: I'm clearing the score, lives, and broken-brick count, along with the particles, banners, and paddle, then building the level and serving the ball. Next I'm wiring up the start and restart actions: starting a game switches the mode to play, initializes audio, and rebuilds the state, and restarting only works once a run has ended. Still wiring up the pause toggle: it does nothing outside play mode, resumes when paused, and pauses from the play or serve phase. Still writing the pause and overlay logic: the overlay is built from a template keyed by the current state, and the key decides which message gets shown. Now I'm checking how the overlay decides what to show, looking at the mode and phase, and skipping the rebuild when the key hasn't changed. Still reworking the overlay: when the timer runs out, updateOverlay has to be called again so the game-over card appears. Still working out the overlay templates: the start card has its kicker, title, the "Click or press a key to start" prompt, and the key-hint list, and I'm building out the rest of the markup. Now I'm writing the overlay templates for the start, paused, game-over, and won screens, with the paused card carrying a resume button. Still writing the overlay templates: the victory card shows the level-complete kicker, the VICTORY title, the total brick count with the score, and a play-again button with an Enter hint. Still writing the click handler for the overlay card: the restart and resume buttons are routed through data-act, and a click only does anything when the overlay is showing a valid end state. Still writing the stage pointer handling: a pointerdown starts the demo game, resumes from pause, or launches the ball when serving, and it steers the paddle during play. Still working out how a click should start play: I'm leaning toward having the first click both start the game and launch the ball, with the keyboard launching on Space. Next I'm wiring up the keydown handler, skipping any key combos with Ctrl, Meta, or Alt, so that the start action also launches the ball right away. Still wiring up the keydown handler: arrow keys, Space, and the A/D keys are flagged to stop the page from scrolling, and M toggles mute. In demo mode, any key other than the modifier keys starts the game. Still writing the keydown handler: the modifier-free keys are checked, and in the paused, over, and won states Enter or Space restart or resume the game. Still writing the keydown handler: I'm working out the demo-start path so that pressing P to begin doesn't also immediately pause the game. Still wiring up the keyup handler: I'm clearing the held-key flags for left and right when the arrow keys or A/D are released. Still wiring up the keyup and blur handlers, clearing the held-key flags and pausing when the tab is hidden during play. Next I'm adding the HUD button clicks and the pointer-move steering, which only applies during play. Still writing the click and keyboard handlers: the overlay card is only interactive when it's showing an end state, and I'm checking the pointer-move steering too. Still writing the launch function: it only runs during the serve phase, gives the stuck ball a random angle, and switches the phase to play with a short sound. Still writing the helpers: the ball factory gets a position, velocity, and stuck flag, and the level is built next. Still writing the collision code: for each brick that's still alive, I'm checking whether the ball's position falls within its bounds, padded by the ball radius. Still writing the collision code: for each live brick, I'm checking the ball against its bounds and working out the normal to the nearest edge when they overlap. Still writing the collision code: when the ball is closer to a brick's edge than its radius, I'm working out which side it hit and pushing it back out before bouncing it off that face. Still writing the hit handler for the brick: I'm working out how the collision updates the ball's bounce and which brick gets broken. Still writing the brick hit handler: a tough brick with hp left takes a hit, flashes, and sends a small burst and a score pop, while a destroyed brick counts toward the total. Still writing the brick hit handler: a tough brick that's already been struck once gets a chip, a flash, and a small white burst, while a destroyed one adds to the broken count and drops a power-up if it carries one. Once the last brick falls, the level gets cleared. Still writing the brick hit handler: the score update for a tough brick and the crack and flash timers that decay each frame, with the sound pitched by row. Now I'm finishing the level-clear path: the phase switches to clear, the end timer is set to 2.4 seconds in demo mode and 1.8 otherwise, the capsules are emptied, and the victory tones and banner fire. Still writing the collect function: a wide power-up sets the wide timer and shows a banner, while a multi-ball power-up spawns extra balls and adds points. Still writing the spawn helper: it takes a live ball as the source and adds extra balls from it, and I'm checking which balls are still in play. Still working through the spawn helper's logic: I'm checking how many extra balls are needed to reach three, and the count is taken from the balls still in play. Still writing the HUD update: the score, bricks left, and lives are only written to the page when their values have actually changed. Still writing the HUD update: I'm checking whether the score and bricks-left values have changed before writing them, then rebuilding the hearts row and the wide-paddle timer chip. Still writing the HUD update: I'm rebuilding the wide-paddle timer and the multi-ball chip only when their values change, and the pause button label and disabled state follow the current phase. Still writing the HUD update: the pause button label and disabled state follow the current phase, and the wide-paddle bar only shrinks when it's active. Still writing the frame loop: the delta time is clamped to 33 milliseconds, then update, render, and the HUD refresh run once per frame. Still working on the render order: background, stars, bricks, capsules, paddle, balls, particles, and the floating text and banner on top. Now I'm checking the paddle's flash on hit and the space left below it, and the ball's exit line sits past the canvas edge. Now I'm checking the paddle collision, which uses the same approach as the bricks. A fast sideways paddle could catch the ball from the side, so the lateral push-out handles that case. Still writing the AI: the paddle's target is the middle of the field, and when a ball is coming down, I'm predicting where it will cross the paddle line so the paddle can move toward that spot. Still working through the AI's target choice: I'm checking whether the paddle should chase the nearest falling capsule instead of the ball, based on how soon each one reaches the paddle line. Still working out the AI's capsule choice: I'm checking whether the capsule sits close enough to where the ball will land that the paddle can reach both, and only then steering toward it. Still working out the AI's capsule choice: if no ball is coming down, the paddle goes after the nearest capsule, and otherwise it only takes a capsule when it sits close to where the ball will cross the paddle line. Still working out the AI's capsule choice: I'm weighing whether the paddle can reach the capsule before the ball arrives, with some margin, or whether the capsule sits close enough to the ball's landing spot to be worth going for. Still working out the AI's capsule choice: if there's no ball coming down, or the capsule sits close to where the ball will cross the paddle line and the paddle can reach it in time, the paddle goes for the capsule, and otherwise it stays on the ball's predicted spot. Still writing the AI's target: the paddle steers toward where the ball will cross, and each frame it moves by at most its speed times the time step. Still writing the burst function: it spawns a set of particles at the given point, with the color, count, power and upward lift each one uses. Still writing the particle update: each one ages, picks up gravity, and loses speed a little every frame. Still writing the particle and text effects: the texts drift upward and fade over their lifetime, and I'm capping how many are kept on screen. Still writing the render function: the background gets a vertical gradient, and the whole frame is offset with a small random shake while it is active. Still writing the render function: the background is drawn first, then the stars twinkle on their own timing, and the danger zone glow comes next. Still writing the hints: the start prompt pulses softly on screen while the game waits in serve, with the text fading in and out over time. Still writing the draw functions: the banner text is drawn on top with a soft glow, and the paddle flashes on hit. Drawing the capsules next: each one gets its power-up color, a glow, and a gradient fill along its rounded shape. Still writing the capsule draw: the fill is applied, the shadow is cleared before the outline stroke, and the power-up icon goes on top. Still writing the paddle draw: the glow color switches when the wide paddle is active. Still writing the paddle draw: the glow shifts to a teal tint when the wide paddle is active, and the fill is a gradient from light to deeper color down the bar. Still writing the ball draw: each ball's trail is drawn as fading circles that shrink toward the tail. Still writing the draw functions: the particles fade out as their life runs down. Still writing the brick draw: each brick gets a gradient fill, and the tough ones show cracks once they've taken damage. Still writing the brick draw: the fill is a gradient that goes from light to dark, with the tough bricks shifting to a cracked, darker tone once they've taken damage. Still writing the brick draw: the shading goes through a helper that adjusts each color's brightness. Still working on the brick draw: the shading for each color goes through a helper that adjusts its brightness, and the cracks get drawn as lines across the damaged tough bricks. Still writing the crack lines: each one gets a dark stroke with a thin light stroke offset beside it, and I'm working out how to offset that second pass without disturbing the coordinates. Next I'm writing a helper, `tracePts`, to rebuild the crack path. The trail is set by each point at the moment it's added, so shifting the transform afterward won't move it. For the light highlight I'll simply restroke the same path at a thinner width in white, skipping any offset. Still writing the power-up badge: it gets a glowing outline in the power color, plus a pulsing center dot with a white ring. Still writing the brick draw: each brick's power badge gets a glowing outline in its power color, then a pulsing center dot with a white ring and the icon on top. Next I'm checking the remaining requirements against the finished Breakout build: the pointer-driven paddle, the particle effects on brick breaks, the three-life HUD, the restart option on the game over and victory screens, the seven-row level, and the 10-second wide paddle and triple-ball power-ups. I'm confirming they all still hold at 1280×800. Still checking the mobile layout at 375px: the HUD wraps onto a few rows, and the brick badges shrink to roughly 3px, which is small but still visible. Still working on the mobile layout: I'm weighing whether to make the play field taller on portrait phones, but that would move the bricks and paddle around too much, so I'm keeping the 4:3 field and accepting the small ball and badges at 375px. I'm checking the stage's aspect ratio and how the canvas sizes itself on a phone at 375px, which will look small but still playable. Still checking for undefined references and console errors across the game loop, the overlay handlers, and the visibility handler. Writing out the full Breakout file now, then checking it over for undefined references and console errors. Still writing the HTML markup: the stats row, the power-up chips, and the pause and sound buttons sit in the header, with the canvas inside the stage and an overlay for the start and end cards. Still writing the HTML markup: the header holds the score, lives, and brick count, plus the power-up chips and the pause and sound buttons, with the canvas inside the stage. Still writing the config section: setting the logical field size, the brick grid of seven rows by twelve columns, the paddle dimensions, and the base ball speed. Still writing the config section: the logical field is 800 by 600, with a grid of seven rows by twelve columns, and the paddle and ball sizes are set in that same space. Still tuning the layout: with the bricks now sized at 56.5 by 26, I'm fixing the brick block to end around y=282, which leaves a clear band for the ball above the paddle. Next I'm checking that the crack coordinates still fit inside those new dimensions. Still writing the brick helpers: each color gets shaded through a brightness helper, and the crack lines are placed inside the brick's bounds. Still writing the helpers: the brick sizing is set and the resize routine keeps the canvas matched to the stage width at the device pixel ratio. Still checking the resize path: the canvas backing store follows the stage width at up to 2x pixel density, and the transform is set to match, so the logical 800-by-600 coordinates still line up. Still writing the brick helpers: the crack lines get placed inside each brick's bounds, and the shading runs through the brightness helper. Still writing the crack generator: each brick gets a few jagged line segments that start near its center and wander outward, kept inside the brick's bounds. Still writing the brick setup: each brick gets its position, its hit points, and, for the tough ones, a set of crack lines generated up front. Now I'm wrapping up the physics section, where the ball and paddle update each frame. Still writing the update loop: the paddle eases toward its target and its width eases toward the wide or normal size. In serve phase the first ball is pinned to the paddle. Still writing the update loop: the ball steps forward, dead balls are dropped, and if the field is empty during play, a life is lost. Still writing the update loop: the bricks' flash timers count down each frame. Still writing the update loop: the brick flash and crack timers tick down, the particle and text effects advance, and during the clear phase fireworks keep launching. When the end-screen timer runs out, the demo starts a fresh game, while a real player gets the game-over or victory overlay. Still writing the update loop: after the bricks and effects tick, I'm checking the end-of-level timers, and the demo starts a fresh game when its end screen runs out. Still checking the physics helpers: the ball step splits its movement into small sub-steps so it can't skip through bricks or the paddle. Still writing the physics step: the ball moves in small sub-steps, bouncing off the walls, the paddle, and the bricks, and if it falls past the bottom it's marked dead. After that I'm adding a check so the ball can't get stuck bouncing almost flat along the top of the field. Still writing the wall and paddle checks for the ball's sub-steps, with a small nudge to keep its vertical speed from flattening out. Still writing the paddle collision: I'm clamping the ball's position to the paddle's edges to find the closest point, and bailing out if the ball isn't touching it. I'm checking the top-bounce case in the paddle collision: a ball descending from above that overlaps the paddle's top edge should bounce upward. I'm also asking whether a ball whose center has dropped below the paddle's bottom while still within radius can be treated as passing underneath. Still writing the paddle collision: a ball coming down onto the paddle's top edge gets its angle set by where it hit, so it bounces back upward. Still writing the paddle collision: the ball's angle is set by where it strikes the paddle, and a ball that clips the paddle from the side or underneath gets pushed out sideways instead. Still checking the paddle collision for the case where the ball sits level with the paddle's edge, to make sure the side push is applied on the correct side. Still checking the ball's collision with the paddle, making sure a hit from the top, the side, or below is handled and that the ball can't pass through. Next I'll move on to the brick collisions. Still writing the capsule update: each falling capsule drops down the field, and if it lands on the paddle it gets collected. Still writing the firework helper: it bursts sparks at a random spot near the top with a random row color and plays a short triangle tone. Still writing the level-clear handler: it switches the phase to clear, sets a shorter timer in demo mode, resets the capsules, and plays a rising fanfare of four tones. Still writing the level-clear and life-loss handlers: a cleared level switches the phase, shortens the wait in demo mode, and plays a rising fanfare, while losing a ball shakes the screen and either ends the game or serves a new ball. Still checking the no-pass-through rules in the overlay and banner timers. Still writing the Breakout demo's collision and capsule handlers, checking that the banners and overlay don't block the start prompt in human mode. Still writing the spawn helper: it adds up to two extra balls off the first live ball, each at a slightly different angle. Still writing the physics and collision helpers: the ball's speed is normalized to the current target speed while keeping its direction, and the rest of the render code comes next. Now I'm checking the first-paint details: the start overlay should be visible straight away, and the stage's pointer handler covers mobile taps too. I'm also confirming that the initial paddle width is set from the normal paddle constant. Still writing the start logic: the start function skips if already in play, sets up audio, switches to play mode, resets the game, and launches the ball. Still writing the keydown handler: in demo mode, an ignored key returns early, otherwise the game starts and the key that triggered it is still processed so the arrow keys can steer the paddle right away. Now I'm handling the keydown case during demo mode: any key that isn't ignored starts the game, and the arrow keys still steer the paddle straight away. Still writing the keyboard handling for the demo start: any key that isn't ignored starts the game, and the arrow keys still steer the paddle right away. Still wrapping up the game code: I'm finishing the overlay styling and the keyboard handling, so the first keypress in demo mode starts a game and the arrow keys still steer the paddle right away. Still writing the spawn and speed helpers: the spawn adds up to two extra balls from the first live ball at slightly different angles, and the speed setter rescales every live ball's velocity to the new speed while keeping its direction. Still writing the constants: brick grid sizing, paddle and ball dimensions, the speed limits, and the falling capsule settings. Still checking the remaining details: the sound helper's slide parameter, the particle counts for brick breaks, and the stars that get generated once at load. Still writing the draw helper for the power-up icons: the wide icon is a horizontal bar with arrowheads at both ends, drawn in the given color. Still writing the icon drawing: the bounce icon gets two arcs and a bar, and I'm finishing the lower-left arc. Still writing the icon drawing for the multi-ball power-up: three dots, in a triangle shape. Still checking the remaining wiring: the stage needs touch-action set to none so dragging on mobile doesn't scroll the page, and I'm making sure the HUD pause button is disabled during the demo. Still checking the opening moments of the demo: the ball launches after about a second and reaches the bricks soon after, so the first few seconds should already show bricks breaking, particles, and capsules being caught by the AI paddle. Checking the AI's aiThink function next. It works out the intercept point only in play phase, and it doesn't run in clear or over phases, so the paddle stays put there. Still writing the AI's aim logic: it looks for the ball that's coming down soonest and predicts where it will cross the paddle line. Still writing the AI's aim logic: if the ball is out of reach, the paddle chases the nearest falling capsule instead. Checking the AI's aim: it confirms the ball's predicted x is taken from its current velocity, and the capsule-chase only kicks in when the capsule is close to the intercept point. Still writing the final game code: I'm checking the resize handling and the first-paint setup, and I'm about to draw the background once. Still writing the background: a vertical gradient fill, then a faint grid of lines spaced 40 pixels apart. Still writing the background: the danger glow along the bottom, a gradient from transparent up near the paddle line to a faint red at the edge. Still adding the finishing touches to the Breakout canvas: a faint red floor line along the bottom edge, and checking the brick rounding and the HUD labels. Still writing the text helpers: drawTexts sets up the bold label font and centers each floating label, fading its alpha as its timer runs down. Still writing the update helpers: the floating texts drift upward and fade out, and I'm dropping any that have expired. Still writing the HUD update and the remaining game code, including the initial state and the particle filtering. Still writing the final code: the overlay fades out when its hidden class toggles, and the demo badge is a span that the hidden attribute handles. Next I'm wiring up the pause and sound buttons, then calling the overlay update once after the new game is set up before starting the frame loop. Still writing the keydown handler for the game: Ctrl, Meta, and Alt combos are ignored, and the arrow keys and space get their default behavior suppressed. M toggles mute, and modifier-only keys are skipped. Any other key in demo mode starts the game and sets the matching left or right key flag. Still writing the keydown handler: the Escape and P keys pause, and any arrow key or Enter launches the ball. Still writing the keyup handler: when the arrow keys or A and D are released, the matching paddle flags get cleared. Still wiring the keyup handler and the helper order, checking that the overlay template is defined before the first newGame call runs. Finishing the keyup handler and checking the init order, so the overlay template is defined before the first newGame call runs. Still writing the CSS: I'm setting up the color variables and a stage size that fits the viewport at a 4:3 ratio. Checking the layout math now: at 1280×800 the stage comes to about 630px tall, so the whole page sits well under the viewport height. Checking whether the header still fits on one line at this width: the brand, score, lives, and brick counter plus the buttons add up to roughly 680px, which is under the 706px stage width, so it should hold when no power-up chips are showing. Once the wide and multi-ball chips appear, the row will likely wrap to a second line, which is acceptable but means the layout shifts a little. Still working out where the power-up chips should sit: I'm considering placing them as an overlay inside the stage, near the top, so they don't push the header onto a second line. Still working out the CSS for the power-up chips. I'm weighing a fixed minimum height for the header row so the layout doesn't jump when the chips appear, and I'm leaning toward keeping them inline in the header since that's simpler and more reliable than overlaying them on the stage. Trying to keep the header from wrapping when the power-up chips appear: the brand, stats, and buttons already nearly fill the row at this width, so I'm weighing whether to slim it down, like dropping the bricks counter, or move the chips elsewhere, possibly into the footer. Still working out the CSS for the header: I'm keeping the power-up chips inline there, and I'm weighing a flex setting so the row can wrap cleanly instead of forcing everything into one line. Still writing the final code: I'm wrapping up the header layout and the power-up chip styling, and I'm accepting that the header may wrap onto a second line when the chips appear. Still writing the CSS: I'm setting the color variables and a stage width that scales with the viewport at a 4:3 ratio. Still writing the CSS: I'm laying out the body background, the app container, and the header row with its brand styling. Still writing the CSS: the stat boxes, heart icons, and power-up chips are styled next, with the chips set to inline-flex so the hidden attribute can hide them. Still writing the CSS: the badge and button styles are next, along with the rest of the chip and control layout. Still writing the stage CSS: it's a 4:3 box with rounded corners and a dark background, and I'm adding the shadow next. Still writing the CSS: the stage gets a layered shadow and a glow, then the canvas fills it and the pause overlay sits on top with a dimmed backdrop and a fading transition. Still writing the CSS: I'm finishing the overlay and card styles, and checking that the viewport height units have a fallback. Still writing the JavaScript: I'm setting up the game constants and the fixed canvas size before the rest of the logic. Still writing the JavaScript: I'm defining the game constants, including the canvas size, brick grid, paddle widths, and speed limits. Still writing the game constants: the canvas is 800 by 600, the brick grid is 7 rows by 12 columns, and the paddle, ball, and speed values are set. Still wiring up the layout constants and the brick grid: the layout strings are checked for their twelve columns, and I'm making sure the tough and normal bricks mix across the top row. Still checking the brick-cracking behavior: the damaged state with lower hit points should show a rust-colored crack along with the particle burst and flash. I'm also making sure the tough brick's power-up still behaves correctly in demo mode. Still checking the brick logic: a tough brick should return early without being marked dead on its first hit, and the collision pass stops after the first brick it touches while later substeps catch any others. I'm also planning the particle burst for tough-brick hits, and the break effect should spawn at the brick's center. Now I'm writing the HUD update logic, tracking the last shown values so the DOM only changes when something actually differs. Still writing the JavaScript: I'm setting up the DOM references, including the canvas context, overlay, and the HUD elements for score, lives, and the power-up chips. Still writing the JavaScript: I'm wiring up the DOM references and a small shade helper for the brick colors. Still writing the JavaScript: I'm setting up the audio context helper, which creates the audio context lazily and falls back gracefully if it isn't supported. Still writing the audio helper: I'm adding a tone function that plays a short oscillator note with a gain envelope, skipping playback when audio is muted or not running. Still writing the audio helper: the tone function is nearly done, with a short oscillator note and a gain envelope that fades out, and it only plays when audio is unmuted and the context is running. Still writing the JavaScript: I'm setting up the audio helpers, the overlay text, and the game state, so everything is in place before the game starts. Still writing the JavaScript: I'm setting up the DOM references and the helpers for shading colors and drawing rounded rectangles. Still writing the game logic: I'm finishing the ball and paddle updates, and making sure a life lost during the demo resets the game cleanly after a short pause. Now I'm checking the game-over path: if the player loses in human mode, the paddle keeps moving but nothing else does, which is fine. I'm also making sure Enter on the focused restart button doesn't fire twice, since the keydown handler rebuilds the card and removes that button first. Now I'm checking the keyboard handling: Enter on the focused restart button is already covered by the preventDefault on keydown, so the click won't fire twice, and the arrow and Space keys get their default suppressed too.