GPT-6.1 Terra
by OpenAIReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 1 min 51 s
- Cost
- $0.051
- Output tokens
- 10,010
- Spent thinking
- 1,244 tokens
- Speed
- 102.1 tok/s
- Console errors
- 0
- First token after
- 13.2 s
- First visible text after
- 29.9 s
- Prompt tokens
- 508
- Code size
- 25.4 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6.1-terra- Reasoning sent
{"effort":"medium"}- Max output
- 128,000 tokens
- Generated
- Nov 12, 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 · 507 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Afterglow — Breakout</title>
<style>
:root {
color-scheme: dark;
--bg: #090b13;
--panel: #111522;
--line: #252b3c;
--muted: #858da2;
--white: #f2f5ff;
--cyan: #72f2e1;
--purple: #bc9bff;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
color: var(--white);
background: radial-gradient(ellipse at 50% -30%, #24213c 0%, transparent 50%), var(--bg);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
body::before {
content: "";
position: fixed;
inset: 0;
pointer-events: none;
opacity: .2;
background-image: linear-gradient(#ffffff08 1px, transparent 1px), linear-gradient(90deg, #ffffff08 1px, transparent 1px);
background-size: 48px 48px;
mask-image: linear-gradient(to bottom, #000, transparent 85%);
}
.shell { position: relative; max-width: 1190px; margin: 0 auto; padding: 0 20px 22px; }
header {
height: 76px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #242838;
}
.brand { display: flex; align-items: center; gap: 11px; }
.brand-mark {
width: 35px; height: 35px; border: 1px solid #7062a0; border-radius: 10px;
display: grid; place-items: center; background: #1b192a;
color: #d5c3ff; font-size: 19px; font-weight: 800;
box-shadow: inset 0 0 15px #ae8eff18;
}
.brand-name { font-size: 14px; font-weight: 850; letter-spacing: .19em; }
.brand-sub { margin-top: 3px; color: #737b90; font-size: 9px; font-weight: 700; letter-spacing: .23em; }
.header-right { display: flex; align-items: center; gap: 13px; color: #8e96a9; font-size: 10px; font-weight: 700; letter-spacing: .14em; }
.online { width: 7px; height: 7px; border-radius: 50%; background: #77ecd5; box-shadow: 0 0 10px #72f2e1; }
.header-tag { border: 1px solid #303448; border-radius: 5px; padding: 7px 9px; color: #b5b6c8; }
.heading {
min-height: 91px; display: flex; align-items: center; justify-content: space-between; gap: 20px;
}
.eyebrow { color: #a393d4; font-size: 10px; font-weight: 800; letter-spacing: .2em; margin-bottom: 7px; }
h1 { font-size: 25px; letter-spacing: -.035em; margin: 0; line-height: 1; }
h1 span { color: #767e94; font-size: 13px; font-weight: 500; letter-spacing: 0; margin-left: 10px; }
.stats { display: flex; align-items: center; gap: 0; }
.stat { min-width: 113px; padding: 0 22px; border-left: 1px solid #2a2e3b; }
.stat:last-child { padding-right: 0; }
.stat-label { display: block; color: #7f879a; font-size: 9px; letter-spacing: .16em; font-weight: 800; margin-bottom: 6px; }
.stat-value { font-size: 19px; font-weight: 750; letter-spacing: .04em; font-variant-numeric: tabular-nums; line-height: 1; }
.hearts { color: #ff8aab; font-size: 17px; letter-spacing: 3px; }
.layout { display: grid; grid-template-columns: minmax(0, 1fr) 230px; gap: 16px; align-items: stretch; }
.game-wrap {
position: relative; overflow: hidden; border: 1px solid #36394e; border-radius: 12px;
background: #101321; box-shadow: 0 24px 70px #0007, 0 0 38px #a88bff0c;
aspect-ratio: 960 / 620;
}
canvas { display: block; width: 100%; height: 100%; touch-action: none; cursor: crosshair; }
.demo-banner {
position: absolute; left: 50%; bottom: 13%; transform: translateX(-50%);
display: flex; align-items: center; gap: 12px; padding: 10px 16px;
border: 1px solid #8c80b65e; border-radius: 8px; background: #111321e8;
box-shadow: 0 8px 30px #0007; white-space: nowrap; pointer-events: none;
}
.play-icon {
display: grid; place-items: center; width: 28px; height: 28px; border-radius: 6px;
background: #bfa8ff; color: #171323; font-size: 11px; padding-left: 2px;
}
.demo-banner strong { display: block; font-size: 11px; letter-spacing: .02em; }
.demo-banner small { display: block; margin-top: 3px; color: #a59dbb; font-size: 9px; letter-spacing: .08em; text-transform: uppercase; }
.end-screen {
position: absolute; inset: 0; display: none; place-items: center;
background: #090b13c9; backdrop-filter: blur(5px);
}
.end-screen.show { display: grid; }
.end-card { text-align: center; padding: 28px; }
.end-kicker { color: var(--cyan); font-size: 10px; font-weight: 800; letter-spacing: .22em; }
.end-card h2 { margin: 10px 0; font-size: clamp(28px, 5vw, 48px); letter-spacing: -.05em; }
.end-card p { color: #a5adbf; font-size: 13px; }
button {
border: 0; border-radius: 7px; padding: 12px 20px; margin-top: 10px;
background: #c4afff; color: #171323; font: inherit; font-size: 11px;
font-weight: 850; letter-spacing: .08em; cursor: pointer;
}
button:hover { background: #ddd1ff; }
button:focus-visible { outline: 2px solid white; outline-offset: 3px; }
aside {
display: flex; flex-direction: column; border: 1px solid #282d3d; border-radius: 12px;
background: linear-gradient(160deg, #151927, #10131e); padding: 20px;
}
.side-title { display: flex; justify-content: space-between; align-items: center; font-size: 10px; font-weight: 800; letter-spacing: .16em; }
.side-number { color: #656e83; font-size: 10px; }
.side-intro { color: #8f98aa; font-size: 12px; line-height: 1.6; margin: 13px 0 19px; }
.rule { height: 1px; background: #2b3040; }
.section-label { color: #747e92; font-size: 9px; font-weight: 800; letter-spacing: .17em; margin: 20px 0 14px; }
.control { display: flex; align-items: center; gap: 11px; margin-bottom: 12px; font-size: 11px; color: #c8cfdd; }
.keys { display: flex; gap: 3px; min-width: 57px; }
.key { display: grid; place-items: center; min-width: 26px; height: 25px; border: 1px solid #3b4254; border-bottom-width: 2px; border-radius: 5px; color: #c6cada; font-size: 12px; background: #202536; }
.key.mouse { min-width: 57px; font-size: 9px; letter-spacing: .03em; }
.power-row { display: flex; gap: 11px; align-items: center; margin-bottom: 15px; }
.power-icon { flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: 8px; font-size: 15px; font-weight: 900; }
.power-icon.wide { color: #78f3df; border: 1px solid #3a776e; background: #1a3938; }
.power-icon.multi { color: #dab7ff; border: 1px solid #755891; background: #332543; }
.power-copy b { display: block; font-size: 11px; }
.power-copy span { display: block; margin-top: 3px; color: #8790a3; font-size: 10px; }
.side-bottom { margin-top: auto; padding-top: 15px; }
.tip { border: 1px solid #36364b; border-radius: 8px; padding: 12px; background: #1a1a2a; color: #9fa5b8; font-size: 10px; line-height: 1.55; }
.tip b { display: block; color: #d3c3f5; font-size: 9px; letter-spacing: .12em; margin-bottom: 4px; }
footer { display: flex; justify-content: space-between; gap: 10px; padding-top: 15px; color: #646d80; font-size: 9px; font-weight: 700; letter-spacing: .12em; }
footer span:last-child { color: #8a829d; }
@media (max-width: 760px) {
.shell { padding: 0 14px 24px; }
header { height: 62px; }
.heading { min-height: 100px; align-items: flex-start; flex-direction: column; justify-content: center; gap: 14px; padding: 13px 0; }
h1 { font-size: 22px; }
.stats { width: 100%; }
.stat { min-width: 0; flex: 1; padding: 0 12px; }
.stat:first-child { border-left: 0; padding-left: 0; }
.stat-value { font-size: 16px; }
.layout { grid-template-columns: 1fr; }
aside { display: grid; grid-template-columns: 1fr 1fr; gap: 0 20px; padding: 17px; }
.side-top { grid-column: 1 / -1; }
.side-intro { margin-bottom: 14px; }
.side-bottom { display: none; }
.demo-banner { padding: 7px 10px; gap: 8px; bottom: 11%; }
.demo-banner strong { font-size: 9px; }
.demo-banner small { font-size: 8px; }
.play-icon { width: 23px; height: 23px; }
footer { font-size: 8px; }
}
@media (max-width: 420px) {
.header-right .online, .header-right .live-text { display: none; }
h1 span { font-size: 11px; }
}
</style>
</head>
<body>
<div class="shell">
<header>
<div class="brand">
<div class="brand-mark">✳</div>
<div><div class="brand-name">AFTERGLOW</div><div class="brand-sub">THE ARCADE SERIES</div></div>
</div>
<div class="header-right"><span class="online"></span><span class="live-text">SYSTEM ONLINE</span><span class="header-tag">ARCADE / 01</span></div>
</header>
<div class="heading">
<div>
<div class="eyebrow">✦ CLASSIC REIMAGINED</div>
<h1>Breakout <span>Clear the field. Keep the glow.</span></h1>
</div>
<div class="stats">
<div class="stat"><span class="stat-label">SCORE</span><div class="stat-value" id="score">000000</div></div>
<div class="stat"><span class="stat-label">LIVES</span><div class="stat-value hearts" id="lives">♥ ♥ ♥</div></div>
<div class="stat"><span class="stat-label">LEVEL</span><div class="stat-value">01 <span style="color:#646c80;font-size:12px">/ 01</span></div></div>
</div>
</div>
<main class="layout">
<div class="game-wrap" id="gameWrap">
<canvas id="game" width="960" height="620" aria-label="Breakout game board"></canvas>
<div class="demo-banner" id="demoBanner">
<div class="play-icon">▶</div>
<div><strong>Click or press a key to start</strong><small>Demo mode is playing</small></div>
</div>
<div class="end-screen" id="endScreen">
<div class="end-card">
<div class="end-kicker" id="endKicker">GAME OVER</div>
<h2 id="endTitle">Until next time.</h2>
<p id="endText"></p>
<button id="restart">PLAY AGAIN ↗</button>
</div>
</div>
</div>
<aside>
<div class="side-top">
<div class="side-title"><span>MISSION CONTROL</span><span class="side-number">01 / 01</span></div>
<p class="side-intro">Break every brick and keep the ball in play. You've got this.</p>
<div class="rule"></div>
</div>
<div>
<div class="section-label">CONTROLS</div>
<div class="control"><div class="keys"><span class="key">←</span><span class="key">→</span></div><span>Move paddle</span></div>
<div class="control"><div class="keys"><span class="key mouse">MOUSE</span></div><span>Move paddle</span></div>
</div>
<div>
<div class="section-label">POWER-UPS</div>
<div class="power-row"><div class="power-icon wide">↔</div><div class="power-copy"><b>Wide paddle</b><span>Extra width · 10 sec</span></div></div>
<div class="power-row"><div class="power-icon multi">✣</div><div class="power-copy"><b>Multi-ball</b><span>Three balls in play</span></div></div>
</div>
<div class="side-bottom">
<div class="tip"><b>PRO TIP</b>Hit the ball with the edge of your paddle to change its angle.</div>
</div>
</aside>
</main>
<footer><span>BUILT FOR A LITTLE ESCAPE</span><span>GOOD LUCK, PLAYER ✳</span></footer>
</div>
<script>
(() => {
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const livesEl = document.getElementById('lives');
const banner = document.getElementById('demoBanner');
const endScreen = document.getElementById('endScreen');
const W = 960, H = 620, R = 7;
const paddle = { x: 480, y: 566, w: 130, h: 13, target: 480 };
const colors = ['#bd9aff', '#ad9cff', '#8fb8ff', '#77d3e8', '#73e3cb', '#a4e8bb'];
let bricks = [], balls = [], particles = [], drops = [];
let score = 0, lives = 3, demo = true, ended = false, wideTimer = 0, time = 0;
let keys = { left: false, right: false };
function makeLevel() {
bricks = [];
const cols = 12, gap = 7, margin = 42;
const bw = (W - margin * 2 - gap * (cols - 1)) / cols;
for (let row = 0; row < 6; row++) {
for (let col = 0; col < cols; col++) {
const tough = (row + col * 2) % 5 === 0 || (row === 1 && col % 4 === 0);
let power = null;
if ((row === 2 && col === 2) || (row === 4 && col === 8) || (row === 0 && col === 10)) power = 'wide';
if ((row === 3 && col === 5) || (row === 5 && col === 10) || (row === 1 && col === 3)) power = 'multi';
bricks.push({ x: margin + col * (bw + gap), y: 89 + row * 40, w: bw, h: 30,
hp: tough ? 2 : 1, maxHp: tough ? 2 : 1, color: colors[row], power, flash: 0 });
}
}
}
function newBall() {
return { x: 480, y: 414, vx: 155, vy: -360, trail: [] };
}
function reset(asDemo) {
demo = asDemo; ended = false; score = 0; lives = 3; wideTimer = 0;
paddle.x = paddle.target = 480; paddle.w = 130;
balls = [newBall()]; particles = []; drops = [];
keys.left = keys.right = false;
makeLevel(); updateHUD();
banner.style.display = demo ? 'flex' : 'none';
endScreen.classList.remove('show');
}
function updateHUD() {
scoreEl.textContent = String(score).padStart(6, '0');
livesEl.textContent = Array.from({length: 3}, (_, i) => i < lives ? '♥' : '♡').join(' ');
}
function start() { if (demo) reset(false); }
function finish(win) {
if (demo) { reset(true); return; }
ended = true;
document.getElementById('endKicker').textContent = win ? 'LEVEL COMPLETE' : 'GAME OVER';
document.getElementById('endTitle').textContent = win ? 'Brilliantly done.' : 'Until next time.';
document.getElementById('endText').textContent = (win ? 'You cleared the field. ' : 'The glow lives on. ') + 'Final score: ' + score.toLocaleString();
endScreen.classList.add('show');
}
document.getElementById('restart').addEventListener('click', e => { e.stopPropagation(); reset(false); });
canvas.addEventListener('pointerdown', e => {
start();
const rect = canvas.getBoundingClientRect();
paddle.target = (e.clientX - rect.left) / rect.width * W;
if (e.pointerType === 'touch') canvas.setPointerCapture(e.pointerId);
});
canvas.addEventListener('pointermove', e => {
if (demo || ended) return;
const rect = canvas.getBoundingClientRect();
paddle.target = (e.clientX - rect.left) / rect.width * W;
});
window.addEventListener('keydown', e => {
if (['ArrowLeft', 'ArrowRight', ' ', 'Enter'].includes(e.key)) e.preventDefault();
if (demo) start();
if (ended && (e.key === 'Enter' || e.key === ' ')) { reset(false); return; }
if (e.key === 'ArrowLeft') keys.left = true;
if (e.key === 'ArrowRight') keys.right = true;
});
window.addEventListener('keyup', e => {
if (e.key === 'ArrowLeft') keys.left = false;
if (e.key === 'ArrowRight') keys.right = false;
});
window.addEventListener('blur', () => { keys.left = keys.right = false; });
function burst(x, y, color, count) {
for (let i = 0; i < count; i++) {
const a = Math.random() * Math.PI * 2, speed = 35 + Math.random() * 160;
particles.push({ x, y, vx: Math.cos(a) * speed, vy: Math.sin(a) * speed,
life: .35 + Math.random() * .45, max: .8, size: 2 + Math.random() * 3, color });
}
}
// Swept point against a rectangle expanded by the ball radius.
// This checks the entire travel segment, rather than just its end position.
function sweep(ball, rect, maxT) {
const left = rect.x - R, right = rect.x + rect.w + R;
const top = rect.y - R, bottom = rect.y + rect.h + R;
let tx1 = -Infinity, tx2 = Infinity, ty1 = -Infinity, ty2 = Infinity;
if (Math.abs(ball.vx) < .0001) { if (ball.x < left || ball.x > right) return null; }
else { tx1 = (left - ball.x) / ball.vx; tx2 = (right - ball.x) / ball.vx; if (tx1 > tx2) [tx1, tx2] = [tx2, tx1]; }
if (Math.abs(ball.vy) < .0001) { if (ball.y < top || ball.y > bottom) return null; }
else { ty1 = (top - ball.y) / ball.vy; ty2 = (bottom - ball.y) / ball.vy; if (ty1 > ty2) [ty1, ty2] = [ty2, ty1]; }
const entry = Math.max(tx1, ty1), exit = Math.min(tx2, ty2);
if (entry > exit || exit < 0 || entry < -0.000001 || entry > maxT) return null;
if (tx1 > ty1) return { t: Math.max(0, entry), nx: ball.vx > 0 ? -1 : 1, ny: 0 };
return { t: Math.max(0, entry), nx: 0, ny: ball.vy > 0 ? -1 : 1 };
}
function hitBrick(brick) {
brick.hp--; brick.flash = .14;
score += brick.hp ? 20 : 100;
if (brick.hp <= 0) {
burst(brick.x + brick.w / 2, brick.y + brick.h / 2, brick.color, 13);
if (brick.power) drops.push({ x: brick.x + brick.w / 2, y: brick.y + brick.h / 2, type: brick.power, phase: Math.random() * 6 });
} else burst(brick.x + brick.w / 2, brick.y + brick.h / 2, brick.color, 5);
updateHUD();
}
function moveBall(ball, dt) {
let remaining = dt, iterations = 0;
while (remaining > .00001 && iterations++ < 12) {
let hit = null;
const consider = (candidate, type, obj) => {
if (candidate && candidate.t >= 0 && candidate.t <= remaining && (!hit || candidate.t < hit.t)) hit = { ...candidate, type, obj };
};
if (ball.vx < 0) consider({ t: (R - ball.x) / ball.vx, nx: 1, ny: 0 }, 'wall');
if (ball.vx > 0) consider({ t: (W - R - ball.x) / ball.vx, nx: -1, ny: 0 }, 'wall');
if (ball.vy < 0) consider({ t: (R - ball.y) / ball.vy, nx: 0, ny: 1 }, 'wall');
for (const brick of bricks) if (brick.hp > 0) consider(sweep(ball, brick, remaining), 'brick', brick);
if (ball.vy > 0 && ball.y <= paddle.y + 1) {
consider(sweep(ball, { x: paddle.x - paddle.w / 2, y: paddle.y, w: paddle.w, h: paddle.h }, remaining), 'paddle');
}
if (!hit) { ball.x += ball.vx * remaining; ball.y += ball.vy * remaining; break; }
ball.x += ball.vx * hit.t; ball.y += ball.vy * hit.t;
remaining -= hit.t;
if (hit.type === 'brick') hitBrick(hit.obj);
if (hit.type === 'paddle') {
const offset = Math.max(-1, Math.min(1, (ball.x - paddle.x) / (paddle.w / 2)));
const angle = offset * 1.12;
const speed = Math.max(390, Math.min(500, Math.hypot(ball.vx, ball.vy) + 2));
ball.vx = Math.sin(angle) * speed;
ball.vy = -Math.cos(angle) * speed;
ball.y = paddle.y - R - .1;
burst(ball.x, paddle.y, '#a2fff0', 4);
} else {
if (hit.nx) ball.vx = -ball.vx;
if (hit.ny) ball.vy = -ball.vy;
ball.x += hit.nx * .03; ball.y += hit.ny * .03;
}
remaining = Math.max(0, remaining - .00001);
}
}
function applyPower(type) {
if (type === 'wide') { wideTimer = 10; }
else if (balls.length) {
const source = balls[0];
while (balls.length < 3) {
const a = balls.length === 1 ? .42 : -.42;
const vx = source.vx * Math.cos(a) - source.vy * Math.sin(a);
const vy = source.vx * Math.sin(a) + source.vy * Math.cos(a);
balls.push({ x: source.x, y: source.y, vx, vy, trail: [] });
}
}
burst(paddle.x, paddle.y, type === 'wide' ? '#72f2e1' : '#d8aaff', 16);
}
function update(dt) {
if (ended) return;
time += dt;
if (demo && balls.length) {
const b = balls.reduce((a, v) => v.y > a.y ? v : a, balls[0]);
let target = b.x;
if (b.vy > 0) {
const travel = Math.max(0, (paddle.y - b.y) / b.vy);
const span = W - 2 * R;
let projected = (b.x - R + b.vx * travel) % (2 * span);
if (projected < 0) projected += 2 * span;
target = R + (projected > span ? 2 * span - projected : projected);
target += Math.sin(time * .8) * 19;
}
paddle.target = target;
} else if (keys.left || keys.right) {
paddle.target = paddle.x + ((keys.right ? 1 : 0) - (keys.left ? 1 : 0)) * 700 * dt;
}
wideTimer = Math.max(0, wideTimer - dt);
const desiredWidth = wideTimer > 0 ? 205 : 130;
paddle.w += (desiredWidth - paddle.w) * Math.min(1, dt * 8);
paddle.target = Math.max(paddle.w / 2 + 8, Math.min(W - paddle.w / 2 - 8, paddle.target));
const move = (demo ? 680 : 1100) * dt;
paddle.x += Math.max(-move, Math.min(move, paddle.target - paddle.x));
paddle.x = Math.max(paddle.w / 2 + 8, Math.min(W - paddle.w / 2 - 8, paddle.x));
for (const b of balls) {
b.trail.unshift({ x: b.x, y: b.y });
if (b.trail.length > 7) b.trail.pop();
moveBall(b, dt);
}
balls = balls.filter(b => b.y < H + 25);
if (!balls.length) {
lives--; updateHUD();
if (lives <= 0) { finish(false); return; }
balls.push(newBall()); balls[0].x = paddle.x;
}
for (const brick of bricks) brick.flash = Math.max(0, brick.flash - dt);
for (const d of drops) {
d.y += 125 * dt; d.phase += dt * 4;
if (d.y + 12 >= paddle.y && d.y - 12 <= paddle.y + paddle.h && Math.abs(d.x - paddle.x) < paddle.w / 2 + 11) {
d.caught = true; applyPower(d.type);
}
}
drops = drops.filter(d => !d.caught && d.y < H + 20);
for (const p of particles) { p.x += p.vx * dt; p.y += p.vy * dt; p.vy += 100 * dt; p.life -= dt; }
particles = particles.filter(p => p.life > 0);
if (bricks.every(b => b.hp <= 0)) finish(true);
}
function rounded(x, y, w, h, r) { ctx.beginPath(); ctx.roundRect(x, y, w, h, r); }
function draw() {
ctx.clearRect(0, 0, W, H);
const bg = ctx.createLinearGradient(0, 0, 0, H);
bg.addColorStop(0, '#16182a'); bg.addColorStop(.6, '#101524'); bg.addColorStop(1, '#101725');
ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H);
const glow = ctx.createRadialGradient(W / 2, 240, 5, W / 2, 240, 500);
glow.addColorStop(0, '#9671d017'); glow.addColorStop(1, '#9671d000');
ctx.fillStyle = glow; ctx.fillRect(0, 0, W, H);
ctx.strokeStyle = '#ffffff08'; ctx.lineWidth = 1;
for (let x = 40; x < W; x += 40) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke(); }
for (let y = 40; y < H; y += 40) { ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke(); }
ctx.fillStyle = '#8b8ca8'; ctx.font = 'bold 10px system-ui'; ctx.letterSpacing = '2px';
ctx.fillText('SECTOR 01', 42, 40);
ctx.textAlign = 'right'; ctx.fillText('BREAK ALL BRICKS', W - 42, 40); ctx.textAlign = 'left'; ctx.letterSpacing = '0px';
ctx.strokeStyle = '#ffffff12'; ctx.beginPath(); ctx.moveTo(42, 57); ctx.lineTo(W - 42, 57); ctx.stroke();
for (const b of bricks) {
if (b.hp <= 0) continue;
ctx.save();
ctx.shadowColor = b.color; ctx.shadowBlur = 9;
ctx.fillStyle = b.color; rounded(b.x, b.y, b.w, b.h, 4); ctx.fill();
ctx.shadowBlur = 0;
ctx.fillStyle = '#ffffff38'; rounded(b.x + 2, b.y + 2, b.w - 4, 3, 2); ctx.fill();
ctx.fillStyle = '#14172822'; rounded(b.x + 2, b.y + b.h - 5, b.w - 4, 3, 1); ctx.fill();
if (b.flash > 0) { ctx.fillStyle = '#ffffff99'; rounded(b.x, b.y, b.w, b.h, 4); ctx.fill(); }
if (b.maxHp === 2 && b.hp === 2) {
ctx.fillStyle = '#20234055';
ctx.beginPath(); ctx.arc(b.x + 7, b.y + 8, 2, 0, Math.PI * 2); ctx.fill();
ctx.beginPath(); ctx.arc(b.x + b.w - 7, b.y + 8, 2, 0, Math.PI * 2); ctx.fill();
}
if (b.hp === 1 && b.maxHp === 2) {
ctx.strokeStyle = '#302b4d99'; ctx.lineWidth = 1.7; ctx.beginPath();
ctx.moveTo(b.x + b.w * .54, b.y + 3); ctx.lineTo(b.x + b.w * .47, b.y + 11);
ctx.lineTo(b.x + b.w * .56, b.y + 15); ctx.lineTo(b.x + b.w * .43, b.y + 27);
ctx.moveTo(b.x + b.w * .52, b.y + 15); ctx.lineTo(b.x + b.w * .34, b.y + 19); ctx.stroke();
}
if (b.power) {
ctx.fillStyle = '#22253c99'; ctx.font = 'bold 17px system-ui'; ctx.textAlign = 'center';
ctx.fillText(b.power === 'wide' ? '↔' : '✣', b.x + b.w / 2, b.y + 21); ctx.textAlign = 'left';
}
ctx.restore();
}
for (const d of drops) {
ctx.save(); ctx.shadowBlur = 15; ctx.shadowColor = d.type === 'wide' ? '#72f2e1' : '#c8a4ff';
ctx.fillStyle = '#22293a'; ctx.strokeStyle = d.type === 'wide' ? '#72f2e1' : '#d0a8ff'; ctx.lineWidth = 2;
rounded(d.x - 12, d.y - 12, 24, 24, 6); ctx.fill(); ctx.stroke();
ctx.shadowBlur = 0; ctx.fillStyle = ctx.strokeStyle; ctx.textAlign = 'center'; ctx.font = 'bold 16px system-ui';
ctx.fillText(d.type === 'wide' ? '↔' : '✣', d.x, d.y + 5); ctx.restore();
}
for (const p of particles) {
ctx.globalAlpha = Math.min(1, p.life / .4); ctx.fillStyle = p.color;
ctx.fillRect(p.x, p.y, p.size, p.size);
}
ctx.globalAlpha = 1;
for (const b of balls) {
b.trail.forEach((t, i) => {
ctx.globalAlpha = (1 - i / b.trail.length) * .19;
ctx.fillStyle = '#b3fff1'; ctx.beginPath(); ctx.arc(t.x, t.y, R * (1 - i / 10), 0, Math.PI * 2); ctx.fill();
});
ctx.globalAlpha = 1; ctx.save(); ctx.shadowColor = '#9affed'; ctx.shadowBlur = 20;
ctx.fillStyle = '#f3fffb'; ctx.beginPath(); ctx.arc(b.x, b.y, R, 0, Math.PI * 2); ctx.fill(); ctx.restore();
}
ctx.save(); ctx.shadowColor = '#75f6e3'; ctx.shadowBlur = 20;
const pg = ctx.createLinearGradient(0, paddle.y, 0, paddle.y + paddle.h);
pg.addColorStop(0, '#d2fff7'); pg.addColorStop(.45, '#81f5e4'); pg.addColorStop(1, '#45bcaf');
ctx.fillStyle = pg; rounded(paddle.x - paddle.w / 2, paddle.y, paddle.w, paddle.h, 7); ctx.fill(); ctx.restore();
ctx.fillStyle = '#eafffc'; rounded(paddle.x - 15, paddle.y + 3, 30, 2, 1); ctx.fill();
if (wideTimer > 0) {
ctx.fillStyle = '#82e9dc'; ctx.font = 'bold 10px system-ui'; ctx.textAlign = 'center';
ctx.fillText('WIDE ' + Math.ceil(wideTimer) + 's', paddle.x, paddle.y + 32); ctx.textAlign = 'left';
}
ctx.fillStyle = '#ffffff25'; ctx.font = '10px system-ui'; ctx.fillText('✳', 16, H - 17);
ctx.textAlign = 'right'; ctx.fillText('AFTERGLOW ARCADE', W - 18, H - 17); ctx.textAlign = 'left';
}
reset(true);
let last = performance.now();
function frame(now) {
const dt = Math.min((now - last) / 1000, .035); last = now;
update(dt); draw(); requestAnimationFrame(frame);
}
draw(); requestAnimationFrame(frame);
})();
</script>
</body>
</html>Reasoning trace (1,244 tokens)
**Designing layout and visuals** I'm outlining a layout with a 1280 width including a header and main section. The design features dark navy, glowing cyan, purple, and peach colors. I want the header to display "VECTOR / ARCADE" along with a LIVE status and stats like lives, score, and power. The main content grid will have a width of 1160, while the canvas is sized 900 x 610 with a side width of 230. I'll ensure to take a screenshot of the side too!**Designing game overlays** I’m working on implementing an overlay that doesn’t block screenshots or interactions. There’s a need to adjust UI scores and to draw the game’s background and floor on the canvas. I need to set the canvas dimensions to 960x620, create a rounded frame, and adjust for shadows and callouts. I’ll also implement a time-limited feature using requestAnimationFrame. The starting position for the ball and paddle, along with dimensions for the bricks, is crucial for gameplay. Finally, I need to ensure the overlay accommodates everything without obscuring essential elements.