GPT-6 Luna
by OpenAIReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 7 min 41 s
- Cost
- $0.031
- Output tokens
- 62,660
- Spent thinking
- 44,277 tokens
- Speed
- 139.3 tok/s
- Console errors
- 0
- First token after
- 11.2 s
- First visible text after
- 5 min 59 s
- Prompt tokens
- 508
- Code size
- 60.3 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6-luna- Reasoning sent
{"effort":"max"}- Max output
- 64,000 tokens
- Generated
- Nov 10, 2026
What it was asked
The prompt
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 · 1945 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#080c17">
<title>Breakout — Orbital Arcade</title>
<style>
:root {
color-scheme: dark;
--ink: #f2f6ff;
--muted: #8593ad;
--line: rgba(137, 165, 211, .17);
--panel: rgba(17, 25, 45, .88);
--cyan: #61e7ed;
}
* { box-sizing: border-box; }
html, body {
min-width: 320px;
min-height: 100%;
margin: 0;
}
body {
position: relative;
min-height: 100vh;
overflow-x: hidden;
background:
radial-gradient(ellipse at 50% -18%, rgba(49, 76, 135, .29), transparent 57%),
radial-gradient(ellipse at 100% 70%, rgba(37, 54, 103, .13), transparent 42%),
#080c17;
color: var(--ink);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
-webkit-font-smoothing: antialiased;
}
body::before {
position: fixed;
z-index: 0;
inset: 0;
pointer-events: none;
content: "";
opacity: .2;
background-image:
linear-gradient(rgba(131, 157, 197, .075) 1px, transparent 1px),
linear-gradient(90deg, rgba(131, 157, 197, .075) 1px, transparent 1px);
background-size: 48px 48px;
mask-image: radial-gradient(ellipse at center, #000 0%, transparent 82%);
}
.game-shell {
position: relative;
z-index: 1;
width: min(1120px, calc(100% - 48px));
margin: 18px auto 14px;
}
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 53px;
margin-bottom: 13px;
}
.brand {
display: flex;
align-items: center;
gap: 11px;
min-width: 0;
}
.brand-mark {
display: grid;
flex: 0 0 42px;
width: 42px;
height: 42px;
place-items: center;
overflow: hidden;
border: 1px solid rgba(129, 206, 239, .28);
border-radius: 13px;
background: linear-gradient(145deg, rgba(40, 74, 118, .88), rgba(15, 26, 48, .92));
box-shadow: inset 0 1px rgba(255, 255, 255, .1), 0 7px 24px rgba(34, 163, 193, .12);
}
.brand-mark svg { width: 27px; height: 27px; }
.brand-copy { display: flex; flex-direction: column; gap: 2px; }
.brand-eyebrow {
color: #8d9cb8;
font-size: 9px;
font-weight: 700;
letter-spacing: .18em;
line-height: 1.2;
text-transform: uppercase;
}
.brand-title {
margin: 0;
color: #f4f7ff;
font-size: 21px;
font-weight: 800;
letter-spacing: .19em;
line-height: 1;
}
.topbar-right {
display: flex;
align-items: center;
gap: 8px;
}
.level-tag, .mode-pill {
display: inline-flex;
align-items: center;
min-height: 32px;
border: 1px solid var(--line);
border-radius: 10px;
background: rgba(18, 27, 47, .72);
}
.level-tag {
gap: 6px;
padding: 0 10px;
color: #f0f4ff;
font-size: 11px;
font-weight: 800;
letter-spacing: .07em;
}
.level-word {
color: #7e8ca8;
font-size: 8px;
font-weight: 700;
letter-spacing: .14em;
}
.level-total { color: #74819b; font-size: 9px; }
.mode-pill {
gap: 7px;
padding: 0 11px;
color: #b6c4dd;
font-size: 9px;
font-weight: 800;
letter-spacing: .1em;
text-transform: uppercase;
white-space: nowrap;
}
.mode-dot, .status-dot {
position: relative;
flex: 0 0 7px;
width: 7px;
height: 7px;
border-radius: 50%;
background: #76e2b7;
box-shadow: 0 0 11px rgba(118, 226, 183, .76);
}
.mode-dot::after {
position: absolute;
inset: -4px;
border: 1px solid rgba(118, 226, 183, .38);
border-radius: 50%;
content: "";
animation: pulse-ring 1.8s ease-out infinite;
}
.mode-pill[data-mode="live"] .mode-dot,
.mode-pill[data-mode="clear"] .mode-dot { background: #67e9ef; box-shadow: 0 0 11px rgba(103, 233, 239, .76); }
.mode-pill[data-mode="over"] .mode-dot { background: #ff7b94; box-shadow: 0 0 11px rgba(255, 123, 148, .7); }
.mode-pill[data-mode="live"] .mode-dot::after,
.mode-pill[data-mode="clear"] .mode-dot::after { border-color: rgba(103, 233, 239, .4); }
@keyframes pulse-ring {
0% { transform: scale(.65); opacity: .85; }
100% { transform: scale(1.75); opacity: 0; }
}
.stats {
display: grid;
grid-template-columns: 1.05fr .9fr 1.1fr;
gap: 11px;
margin-bottom: 12px;
}
.stat-card {
position: relative;
display: flex;
min-width: 0;
min-height: 64px;
flex-direction: column;
justify-content: center;
overflow: hidden;
padding: 9px 15px;
border: 1px solid rgba(129, 154, 198, .17);
border-radius: 13px;
background:
radial-gradient(ellipse at 12% 0%, rgba(77, 111, 171, .1), transparent 55%),
linear-gradient(125deg, rgba(19, 28, 49, .94), rgba(13, 20, 37, .91));
box-shadow: inset 0 1px rgba(255, 255, 255, .035), 0 8px 22px rgba(0, 0, 0, .14);
}
.stat-card::after {
position: absolute;
right: 15px;
bottom: 0;
left: 15px;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(100, 222, 230, .17), transparent);
content: "";
}
.stat-label {
display: flex;
align-items: center;
justify-content: space-between;
gap: 5px;
margin-bottom: 3px;
color: #8290aa;
font-size: 8px;
font-weight: 800;
letter-spacing: .16em;
line-height: 1.1;
text-transform: uppercase;
}
.stat-label .micro {
color: #5c6b86;
font-size: 7px;
letter-spacing: .12em;
}
.stat-value-row {
display: flex;
align-items: baseline;
gap: 8px;
min-width: 0;
}
.score-value {
color: #f3f6ff;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 20px;
font-weight: 750;
letter-spacing: .06em;
line-height: 1.05;
font-variant-numeric: tabular-nums;
}
.stat-unit {
color: #65738f;
font-size: 8px;
font-weight: 800;
letter-spacing: .13em;
}
.lives-row {
display: flex;
align-items: center;
gap: 6px;
min-height: 23px;
}
.heart {
width: 18px;
height: 18px;
overflow: visible;
transition: opacity .2s, transform .2s;
}
.heart.on {
fill: #ff738f;
filter: drop-shadow(0 0 4px rgba(255, 96, 135, .38));
}
.heart.off { fill: #39445a; opacity: .55; }
.bricks-row {
display: flex;
align-items: baseline;
gap: 5px;
margin-bottom: 5px;
}
.bricks-value {
color: #eaf4ff;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 18px;
font-weight: 800;
line-height: 1;
font-variant-numeric: tabular-nums;
}
.bricks-total {
color: #73819b;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 10px;
font-weight: 700;
}
.progress-track {
width: 100%;
height: 3px;
overflow: hidden;
border-radius: 9px;
background: rgba(94, 111, 143, .2);
}
.progress-fill {
width: 0%;
height: 100%;
border-radius: inherit;
background: linear-gradient(90deg, #50cde2, #9d89ff);
box-shadow: 0 0 9px rgba(92, 214, 229, .55);
transition: width .25s ease;
}
.stage-frame {
position: relative;
overflow: hidden;
border: 1px solid rgba(105, 143, 196, .25);
border-radius: 17px;
background: #090f1d;
box-shadow:
0 20px 56px rgba(0, 0, 0, .32),
inset 0 1px rgba(255, 255, 255, .055),
0 0 0 1px rgba(11, 18, 34, .8);
}
#gameCanvas {
display: block;
width: 100%;
height: clamp(455px, calc(100vh - 280px), 540px);
outline: none;
cursor: crosshair;
touch-action: none;
}
.overlay {
position: absolute;
z-index: 3;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
}
.overlay[hidden] { display: none; }
.overlay.intro {
pointer-events: none;
background: transparent;
}
.intro-card {
display: flex;
width: min(366px, calc(100% - 32px));
align-items: center;
gap: 15px;
padding: 16px 19px;
transform: translateY(8px);
border: 1px solid rgba(133, 184, 219, .28);
border-radius: 16px;
background: rgba(8, 15, 30, .82);
box-shadow: 0 18px 50px rgba(0, 0, 0, .3), inset 0 1px rgba(255, 255, 255, .07);
backdrop-filter: blur(9px);
-webkit-backdrop-filter: blur(9px);
}
.play-glyph {
display: grid;
flex: 0 0 39px;
width: 39px;
height: 39px;
place-items: center;
border: 1px solid rgba(107, 227, 235, .32);
border-radius: 12px;
background: linear-gradient(145deg, rgba(64, 187, 206, .2), rgba(83, 105, 190, .15));
box-shadow: 0 0 22px rgba(63, 210, 223, .12);
}
.play-glyph svg { width: 17px; height: 17px; margin-left: 2px; }
.intro-copy { min-width: 0; }
.card-kicker {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 4px;
color: #73dfe7;
font-size: 8px;
font-weight: 850;
letter-spacing: .16em;
text-transform: uppercase;
}
.card-kicker .status-dot {
width: 5px;
height: 5px;
flex-basis: 5px;
}
.intro-card h2 {
margin: 0 0 4px;
color: #f2f6ff;
font-size: 19px;
font-weight: 760;
letter-spacing: -.025em;
line-height: 1.1;
}
.intro-card p {
margin: 0;
color: #c4d0e3;
font-size: 12px;
font-weight: 550;
line-height: 1.35;
}
.intro-sub {
margin-top: 5px;
color: #72819e;
font-size: 9px;
font-weight: 650;
letter-spacing: .025em;
}
.overlay.result {
pointer-events: auto;
padding: 18px;
background: rgba(5, 9, 19, .7);
backdrop-filter: blur(3px);
-webkit-backdrop-filter: blur(3px);
}
.result-card {
width: min(370px, 100%);
padding: 27px 24px 23px;
border: 1px solid rgba(131, 172, 220, .3);
border-radius: 18px;
background:
radial-gradient(ellipse at 50% 0%, rgba(77, 121, 191, .2), transparent 60%),
linear-gradient(145deg, rgba(16, 25, 45, .98), rgba(8, 14, 27, .98));
box-shadow: 0 24px 70px rgba(0, 0, 0, .48), inset 0 1px rgba(255, 255, 255, .07);
text-align: center;
}
.result-kicker {
color: #70dfe6;
font-size: 9px;
font-weight: 850;
letter-spacing: .2em;
text-transform: uppercase;
}
.result-card h2 {
margin: 9px 0 6px;
color: #f6f8ff;
font-size: 28px;
font-weight: 800;
letter-spacing: -.045em;
}
.result-card p {
margin: 0 0 18px;
color: #9eacc4;
font-size: 12px;
}
.result-score {
color: #f0f5ff;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-weight: 800;
letter-spacing: .08em;
}
.restart-btn {
display: inline-flex;
min-height: 42px;
align-items: center;
justify-content: center;
gap: 9px;
padding: 0 20px;
border: 1px solid rgba(171, 239, 242, .45);
border-radius: 11px;
background: linear-gradient(105deg, #51cbd8, #7b9ef1);
box-shadow: 0 8px 24px rgba(64, 176, 209, .2), inset 0 1px rgba(255, 255, 255, .35);
color: #07121d;
cursor: pointer;
font: inherit;
font-size: 11px;
font-weight: 900;
letter-spacing: .14em;
text-transform: uppercase;
transition: filter .15s, transform .15s;
pointer-events: auto;
}
.restart-btn:hover { filter: brightness(1.1); transform: translateY(-1px); }
.restart-btn:active { transform: translateY(0); }
.restart-btn span { font-size: 16px; line-height: 1; }
.result-help {
margin-top: 13px;
color: #6e7d98;
font-size: 8px;
font-weight: 700;
letter-spacing: .12em;
text-transform: uppercase;
}
.control-strip {
display: flex;
min-height: 41px;
align-items: center;
gap: 17px;
margin-top: 10px;
padding: 0 4px;
color: #8190aa;
}
.control-group {
display: flex;
align-items: center;
gap: 7px;
white-space: nowrap;
}
.control-symbol {
display: grid;
width: 22px;
height: 22px;
place-items: center;
border: 1px solid rgba(108, 147, 197, .19);
border-radius: 7px;
background: rgba(21, 31, 52, .65);
color: #74dce7;
font-size: 13px;
}
.control-copy {
color: #9aa8c0;
font-size: 9px;
font-weight: 700;
letter-spacing: .08em;
}
.control-copy b { color: #d8e1f1; font-weight: 800; }
.control-divider {
width: 1px;
height: 19px;
background: rgba(132, 155, 190, .16);
}
.keycaps { display: flex; align-items: center; gap: 4px; }
kbd {
display: inline-grid;
width: 21px;
height: 20px;
place-items: center;
border: 1px solid rgba(130, 156, 198, .25);
border-bottom-color: rgba(130, 156, 198, .42);
border-radius: 5px;
background: linear-gradient(#17223a, #111a2d);
box-shadow: 0 2px 0 rgba(0, 0, 0, .26);
color: #c6d4e9;
font-family: inherit;
font-size: 11px;
font-weight: 700;
line-height: 1;
}
.power-legend {
display: flex;
align-items: center;
gap: 7px;
margin-left: auto;
}
.legend-title {
margin-right: 2px;
color: #65738e;
font-size: 7px;
font-weight: 850;
letter-spacing: .15em;
text-transform: uppercase;
}
.power-chip {
display: inline-flex;
min-height: 24px;
align-items: center;
gap: 5px;
padding: 0 8px;
border: 1px solid rgba(112, 145, 188, .18);
border-radius: 8px;
background: rgba(16, 25, 43, .62);
color: #7888a3;
font-size: 8px;
font-weight: 800;
letter-spacing: .06em;
white-space: nowrap;
transition: color .2s, border-color .2s, background .2s, box-shadow .2s;
}
.power-chip .chip-icon {
color: #5ba9c1;
font-size: 12px;
line-height: 1;
}
.power-chip.active {
border-color: rgba(103, 223, 232, .5);
background: rgba(39, 106, 121, .2);
color: #d2f8fa;
box-shadow: 0 0 14px rgba(63, 207, 218, .11);
}
.power-chip.active .chip-icon { color: #76eff1; }
@media (max-width: 560px) {
.game-shell { width: calc(100% - 24px); margin-top: 11px; }
.topbar { min-height: 43px; margin-bottom: 9px; }
.brand { gap: 8px; }
.brand-mark { flex-basis: 36px; width: 36px; height: 36px; border-radius: 11px; }
.brand-mark svg { width: 23px; height: 23px; }
.brand-eyebrow { font-size: 7px; letter-spacing: .13em; }
.brand-title { font-size: 17px; letter-spacing: .15em; }
.topbar-right { gap: 6px; }
.level-tag, .mode-pill { min-height: 29px; border-radius: 9px; }
.level-tag { gap: 4px; padding: 0 7px; font-size: 10px; }
.level-word { font-size: 7px; }
.mode-pill { gap: 6px; padding: 0 8px; font-size: 7px; letter-spacing: .08em; }
.mode-dot { width: 6px; height: 6px; flex-basis: 6px; }
.stats { gap: 7px; margin-bottom: 9px; }
.stat-card { min-height: 59px; padding: 8px 9px; border-radius: 11px; }
.stat-card::after { right: 9px; left: 9px; }
.stat-label { margin-bottom: 3px; font-size: 7px; letter-spacing: .11em; }
.stat-label .micro { font-size: 6px; }
.score-value { font-size: 16px; letter-spacing: .025em; }
.stat-unit { font-size: 7px; }
.lives-row { gap: 4px; min-height: 20px; }
.heart { width: 15px; height: 15px; }
.bricks-row { margin-bottom: 5px; }
.bricks-value { font-size: 16px; }
.bricks-total { font-size: 9px; }
.stage-frame { border-radius: 14px; }
#gameCanvas { height: clamp(500px, 65vh, 570px); }
.intro-card { width: min(366px, calc(100% - 24px)); gap: 11px; padding: 13px 13px; border-radius: 14px; }
.play-glyph { flex-basis: 34px; width: 34px; height: 34px; border-radius: 10px; }
.play-glyph svg { width: 15px; height: 15px; }
.card-kicker { font-size: 7px; letter-spacing: .13em; }
.intro-card h2 { font-size: 17px; }
.intro-card p { font-size: 11px; }
.intro-sub { font-size: 8px; }
.control-strip {
display: grid;
grid-template-columns: 1fr auto 1fr;
gap: 7px;
margin-top: 8px;
padding: 0 2px 4px;
}
.control-group { gap: 5px; }
.control-symbol { width: 20px; height: 20px; font-size: 11px; }
.control-copy { font-size: 8px; letter-spacing: .045em; }
.control-divider { height: 17px; }
.keycaps { gap: 3px; }
kbd { width: 19px; height: 19px; font-size: 10px; }
.power-legend {
grid-column: 1 / -1;
justify-content: center;
gap: 6px;
margin: 2px 0 0;
padding-top: 7px;
border-top: 1px solid rgba(132, 155, 190, .12);
}
.legend-title { font-size: 6px; }
.power-chip { min-height: 22px; padding: 0 7px; font-size: 7px; }
}
@media (max-width: 390px) {
.level-word { display: none; }
.brand-eyebrow { font-size: 6.5px; }
.brand-title { font-size: 16px; }
.mode-pill { padding: 0 7px; font-size: 6.5px; }
.stat-card { padding-right: 7px; padding-left: 7px; }
.stat-label { letter-spacing: .08em; }
.heart { width: 14px; height: 14px; }
.control-copy { font-size: 7px; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
</style>
</head>
<body>
<main class="game-shell">
<header class="topbar">
<div class="brand">
<div class="brand-mark" aria-hidden="true">
<svg viewBox="0 0 32 32" fill="none">
<path d="M5 8.5h6.5v5H5zM13 8.5h6.5v5H13zM21 8.5h6v5h-6z" fill="#6ce8eb"/>
<path d="M5 15h6.5v5H5zM13 15h6.5v5H13zM21 15h6v5h-6z" fill="#6f9bfa" opacity=".82"/>
<path d="M8 24h16" stroke="#c6f7fb" stroke-width="2.5" stroke-linecap="round"/>
<circle cx="23.5" cy="5" r="2" fill="#fff" opacity=".92"/>
</svg>
</div>
<div class="brand-copy">
<div class="brand-eyebrow">Orbital Arcade <span style="color:#566581">/</span> 01</div>
<h1 class="brand-title">BREAKOUT</h1>
</div>
</div>
<div class="topbar-right">
<div class="level-tag"><span class="level-word">LEVEL</span><span>01</span><span class="level-total">/ 01</span></div>
<div class="mode-pill" id="modePill" data-mode="demo">
<span class="mode-dot"></span><span id="modeText">AI DEMO</span>
</div>
</div>
</header>
<section class="stats" aria-label="Game statistics">
<div class="stat-card">
<div class="stat-label"><span>SCORE</span><span class="micro">RUN 01</span></div>
<div class="stat-value-row">
<strong class="score-value" id="scoreValue">000000</strong>
<span class="stat-unit">PTS</span>
</div>
</div>
<div class="stat-card">
<div class="stat-label"><span>SHIELDS</span><span class="micro">LIVES</span></div>
<div class="lives-row" id="livesRow" aria-label="3 lives"></div>
</div>
<div class="stat-card">
<div class="stat-label"><span>BRICKS</span><span class="micro">FIELD CLEAR</span></div>
<div class="bricks-row">
<strong class="bricks-value" id="bricksValue">60</strong>
<span class="bricks-total">/ <span id="bricksTotal">60</span></span>
</div>
<div class="progress-track" aria-hidden="true"><div class="progress-fill" id="progressFill"></div></div>
</div>
</section>
<section class="stage-frame" aria-label="Breakout playfield">
<canvas id="gameCanvas" tabindex="0" role="application" aria-label="Breakout game. Move the paddle with your mouse or the left and right arrow keys.">
Your browser does not support the canvas element.
</canvas>
<div class="overlay intro" id="overlay" aria-live="polite"></div>
</section>
<footer class="control-strip">
<div class="control-group">
<span class="control-symbol" aria-hidden="true">⌖</span>
<span class="control-copy"><b>MOUSE</b> TO STEER</span>
</div>
<span class="control-divider" aria-hidden="true"></span>
<div class="control-group">
<span class="keycaps" aria-hidden="true"><kbd>←</kbd><kbd>→</kbd></span>
<span class="control-copy">MOVE PADDLE</span>
</div>
<div class="power-legend" aria-label="Power-ups">
<span class="legend-title">POWER-UPS</span>
<span class="power-chip" id="wideChip"><span class="chip-icon">↔</span><span>WIDE <b id="wideTime">10s</b></span></span>
<span class="power-chip" id="multiChip"><span class="chip-icon">•••</span><span id="multiLabel">3 BALLS</span></span>
</div>
</footer>
</main>
<script>
(() => {
"use strict";
const canvas = document.getElementById("gameCanvas");
const ctx = canvas.getContext("2d", { alpha: false });
const overlay = document.getElementById("overlay");
const scoreValue = document.getElementById("scoreValue");
const livesRow = document.getElementById("livesRow");
const bricksValue = document.getElementById("bricksValue");
const bricksTotal = document.getElementById("bricksTotal");
const progressFill = document.getElementById("progressFill");
const modePill = document.getElementById("modePill");
const modeText = document.getElementById("modeText");
const wideChip = document.getElementById("wideChip");
const wideTime = document.getElementById("wideTime");
const multiChip = document.getElementById("multiChip");
const multiLabel = document.getElementById("multiLabel");
const WALL = 19;
const TOP_WALL = 48;
const ROWS = 6;
const BALL_RADIUS = 6.3;
const PALETTE = [
{ top: "#a2fff2", mid: "#31d8ce", bottom: "#138b9c", glow: "#54f1e2", spark: "#d1fff8" },
{ top: "#a8e6ff", mid: "#55baf7", bottom: "#2679ba", glow: "#55c5ff", spark: "#e0f5ff" },
{ top: "#c9c0ff", mid: "#9383fb", bottom: "#5c54c4", glow: "#a391ff", spark: "#eeeaff" },
{ top: "#f4c3ff", mid: "#d47ae9", bottom: "#984fb8", glow: "#e27cff", spark: "#ffecff" },
{ top: "#ffc4df", mid: "#f27eaf", bottom: "#b94b83", glow: "#ff79b6", spark: "#ffe6f2" },
{ top: "#ffe1ad", mid: "#f4aa61", bottom: "#b86639", glow: "#ffb365", spark: "#fff0d8" }
];
let W = 0;
let H = 0;
let pixelRatio = 1;
let stars = [];
let bricks = [];
let balls = [];
let drops = [];
let particles = [];
let floatTexts = [];
let columns = 10;
let levelReady = false;
let remaining = 0;
let total = 0;
let score = 0;
let lives = 3;
let state = "demo";
let mouseTarget = 0;
let mouseActive = false;
let lastTime = performance.now();
let lastWideSecond = -1;
let feedback = null;
const keys = new Set();
const paddle = {
x: 0,
y: 0,
width: 104,
baseWidth: 104,
height: 12,
wideTimer: 0
};
const clamp = (value, low, high) => Math.max(low, Math.min(high, value));
function roundedPath(context, x, y, width, height, radius) {
const r = Math.max(0, Math.min(radius, width / 2, height / 2));
context.beginPath();
context.moveTo(x + r, y);
context.lineTo(x + width - r, y);
context.quadraticCurveTo(x + width, y, x + width, y + r);
context.lineTo(x + width, y + height - r);
context.quadraticCurveTo(x + width, y + height, x + width - r, y + height);
context.lineTo(x + r, y + height);
context.quadraticCurveTo(x, y + height, x, y + height - r);
context.lineTo(x, y + r);
context.quadraticCurveTo(x, y, x + r, y);
context.closePath();
}
function setStars() {
let seed = 739391 + Math.round(W * 19 + H * 7);
const random = () => {
seed = (seed * 16807) % 2147483647;
return (seed - 1) / 2147483646;
};
const count = Math.max(25, Math.floor((W * H) / 9000));
stars = [];
for (let i = 0; i < count; i++) {
stars.push({
x: random() * W,
y: random() * H,
r: .45 + random() * 1.05,
phase: random() * Math.PI * 2,
speed: .5 + random() * 1.2
});
}
}
function layoutBricks() {
if (!bricks.length || !W) return;
const gapX = W < 500 ? 4 : 8;
const gapY = W < 500 ? 7 : 9;
const brickH = W < 500 ? 18 : 23;
const side = Math.max(18, Math.min(58, W * .052));
const usable = W - side * 2;
const brickW = (usable - gapX * (columns - 1)) / columns;
const startX = (W - (columns * brickW + (columns - 1) * gapX)) / 2;
const startY = 77;
for (const brick of bricks) {
brick.x = startX + brick.col * (brickW + gapX);
brick.y = startY + brick.row * (brickH + gapY);
brick.w = brickW;
brick.h = brickH;
}
}
function makeLevel() {
bricks = [];
const powerSpots = [
{ row: 1, col: Math.floor(columns * .2), type: "wide" },
{ row: 2, col: columns - 2, type: "multi" },
{ row: 4, col: Math.floor(columns * .42), type: "wide" },
{ row: 5, col: Math.floor(columns * .72), type: "multi" }
];
for (let row = 0; row < ROWS; row++) {
for (let col = 0; col < columns; col++) {
const powerSpot = powerSpots.find(p => p.row === row && p.col === col);
const tough = !powerSpot && (
(row === 0 && col % 4 === 1) ||
(row === 1 && col % 4 === 3) ||
(row === 2 && col % 3 === 0) ||
(row === 3 && col % 4 === 2) ||
(row === 4 && col % 3 === 1) ||
(row === 5 && col % 4 === 0)
);
bricks.push({
row,
col,
x: 0,
y: 0,
w: 0,
h: 0,
hp: tough ? 2 : 1,
maxHp: tough ? 2 : 1,
tough,
alive: true,
power: powerSpot ? powerSpot.type : null,
flash: 0
});
}
}
remaining = bricks.length;
total = bricks.length;
layoutBricks();
}
function resizeCanvas() {
const rect = canvas.getBoundingClientRect();
if (!rect.width || !rect.height) return;
const nextW = rect.width;
const nextH = rect.height;
const nextRatio = Math.min(2, window.devicePixelRatio || 1);
if (Math.abs(nextW - W) < .05 && Math.abs(nextH - H) < .05 && nextRatio === pixelRatio &&
canvas.width === Math.round(nextW * nextRatio)) return;
const oldW = W;
const oldH = H;
W = nextW;
H = nextH;
pixelRatio = nextRatio;
canvas.width = Math.round(W * pixelRatio);
canvas.height = Math.round(H * pixelRatio);
ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
if (oldW && oldH) {
const sx = W / oldW;
const sy = H / oldH;
paddle.x *= sx;
for (const ball of balls) {
ball.x *= sx;
ball.y *= sy;
}
for (const drop of drops) {
drop.x *= sx;
drop.y *= sy;
}
for (const particle of particles) {
particle.x *= sx;
particle.y *= sy;
}
for (const text of floatTexts) {
text.x *= sx;
text.y *= sy;
}
} else {
paddle.x = W / 2;
}
paddle.baseWidth = W < 480 ? 82 : 104;
paddle.width = paddle.wideTimer > 0 ? paddle.baseWidth * 1.55 : paddle.baseWidth;
paddle.y = H - 43;
paddle.x = clampPaddleX(paddle.x);
if (!levelReady) {
columns = W < 520 ? 8 : 10;
levelReady = true;
makeLevel();
} else {
layoutBricks();
}
setStars();
updateHud();
}
function clampPaddleX(x) {
const half = paddle.width / 2;
return clamp(x, WALL + half + 2, W - WALL - half - 2);
}
function ballSpeed() {
return clamp(W * .37, 315, 395);
}
function createBall(x, y, vx, vy, attached = false, serveTime = 0) {
return {
x, y, vx, vy,
r: BALL_RADIUS,
attached,
serveTime,
trail: [],
trailClock: 0
};
}
function launchBall(angle = .24) {
const speed = ballSpeed();
return createBall(
paddle.x,
paddle.y - BALL_RADIUS - 3,
Math.sin(angle) * speed,
-Math.cos(angle) * speed
);
}
function createWaitingBall(delay) {
const ball = createBall(paddle.x, paddle.y - BALL_RADIUS - 3, 0, 0, true, delay);
return ball;
}
function showIntro() {
overlay.hidden = false;
overlay.className = "overlay intro";
overlay.innerHTML = `
<div class="intro-card">
<div class="play-glyph" aria-hidden="true">
<svg viewBox="0 0 20 20" fill="none"><path d="M6 3.8c0-.7.8-1.1 1.4-.7l8 5.6a1.5 1.5 0 0 1 0 2.5l-8 5.6c-.6.4-1.4 0-1.4-.7V3.8Z" fill="#86f0f1"/></svg>
</div>
<div class="intro-copy">
<div class="card-kicker"><span class="status-dot"></span> AUTOPILOT DEMO</div>
<h2>Break the grid.</h2>
<p>Click or press a key to start</p>
<div class="intro-sub">Mouse or ← → to steer</div>
</div>
</div>`;
}
function showResult() {
overlay.hidden = false;
overlay.className = "overlay result";
const won = state === "victory";
overlay.innerHTML = `
<div class="result-card">
<div class="result-kicker">${won ? "SECTOR 01 · SECURED" : "SIGNAL LOST · 3 LIVES"}</div>
<h2>${won ? "Grid cleared!" : "Game over"}</h2>
<p>Final score <strong class="result-score">${String(score).padStart(6, "0")}</strong></p>
<button class="restart-btn" type="button"><span aria-hidden="true">↻</span> Play again</button>
<div class="result-help">Click play again or press Enter</div>
</div>`;
}
function renderOverlay() {
if (state === "demo") {
showIntro();
} else if (state === "playing") {
overlay.hidden = true;
overlay.className = "overlay";
overlay.innerHTML = "";
} else {
showResult();
}
}
overlay.addEventListener("click", event => {
if (event.target.closest(".restart-btn")) startGame();
});
function startGame(pointerX = null) {
score = 0;
lives = 3;
state = "playing";
drops = [];
particles = [];
floatTexts = [];
feedback = null;
paddle.wideTimer = 0;
paddle.width = paddle.baseWidth;
mouseActive = pointerX !== null;
mouseTarget = pointerX === null ? W / 2 : pointerX;
paddle.x = clampPaddleX(mouseTarget);
keys.clear();
makeLevel();
balls = [launchBall(.22)];
updateHud();
renderOverlay();
}
function startDemo() {
score = 0;
lives = 3;
state = "demo";
drops = [];
particles = [];
floatTexts = [];
feedback = null;
paddle.wideTimer = 0;
paddle.width = paddle.baseWidth;
paddle.x = clampPaddleX(W / 2);
mouseActive = false;
keys.clear();
makeLevel();
balls = [launchBall(.25)];
updateHud();
renderOverlay();
}
function updateHud() {
scoreValue.textContent = String(score).padStart(6, "0");
bricksValue.textContent = String(remaining).padStart(2, "0");
bricksTotal.textContent = String(total).padStart(2, "0");
const cleared = total ? ((total - remaining) / total) * 100 : 0;
progressFill.style.width = `${clamp(cleared, 0, 100)}%`;
let hearts = "";
for (let i = 0; i < 3; i++) {
hearts += `<svg class="heart ${i < lives ? "on" : "off"}" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 21s-8.5-5.1-9.8-10.2C1 6.1 3.6 3 7.1 3c2 0 3.8 1.1 4.9 2.8C13.1 4.1 14.9 3 16.9 3c3.5 0 6.1 3.1 4.9 7.8C20.5 15.9 12 21 12 21Z"/></svg>`;
}
livesRow.innerHTML = hearts;
livesRow.setAttribute("aria-label", `${lives} ${lives === 1 ? "life" : "lives"} remaining`);
const mode = state === "demo" ? "demo" : state === "playing" ? "live" : state === "victory" ? "clear" : "over";
modePill.dataset.mode = mode;
modeText.textContent = state === "demo" ? "AI DEMO" :
state === "playing" ? "LIVE RUN" :
state === "victory" ? "FIELD CLEAR" : "RUN ENDED";
wideChip.classList.toggle("active", paddle.wideTimer > 0);
wideTime.textContent = paddle.wideTimer > 0 ? `${Math.ceil(paddle.wideTimer)}s` : "10s";
const movingBalls = balls.filter(ball => !ball.attached).length;
multiChip.classList.toggle("active", movingBalls > 1);
multiLabel.textContent = movingBalls > 1 ? `MULTI ×${Math.min(movingBalls, 3)}` : "3 BALLS";
lastWideSecond = Math.ceil(paddle.wideTimer);
}
function addFloatText(x, y, text, color) {
floatTexts.push({ x, y, text, color, life: .8, maxLife: .8 });
}
function setFeedback(text, color) {
feedback = { text, color, life: 1.25, maxLife: 1.25 };
}
function spawnBrickParticles(brick, count) {
const palette = PALETTE[brick.row];
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 65 + Math.random() * 210;
const life = .32 + Math.random() * .48;
particles.push({
x: brick.x + brick.w * (.2 + Math.random() * .6),
y: brick.y + brick.h * (.2 + Math.random() * .6),
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed - 30,
size: 1.4 + Math.random() * 3,
angle: Math.random() * Math.PI,
spin: (Math.random() - .5) * 8,
life,
maxLife: life,
color: i % 3 === 0 ? palette.spark : palette.glow
});
}
}
function hitBrick(brick, ball) {
if (!brick.alive) return;
const points = brick.tough ? (brick.hp === 2 ? 25 : 75) : 50;
brick.hp -= 1;
brick.flash = .15;
score += points;
addFloatText(brick.x + brick.w / 2, brick.y + brick.h / 2, `+${points}`, PALETTE[brick.row].spark);
if (brick.hp <= 0) {
brick.alive = false;
remaining = Math.max(0, remaining - 1);
spawnBrickParticles(brick, 19);
if (brick.power) {
drops.push({
x: brick.x + brick.w / 2,
y: brick.y + brick.h / 2,
r: 10,
vy: W < 500 ? 128 : 142,
type: brick.power,
age: 0
});
}
} else {
spawnBrickParticles(brick, 7);
}
updateHud();
if (remaining === 0) {
if (state === "playing") {
state = "victory";
renderOverlay();
updateHud();
} else if (state === "demo") {
makeLevel();
}
}
}
function collideBallWithBrick(ball, oldX, oldY, brick) {
const left = brick.x - ball.r;
const right = brick.x + brick.w + ball.r;
const top = brick.y - ball.r;
const bottom = brick.y + brick.h + ball.r;
if (ball.x < left || ball.x > right || ball.y < top || ball.y > bottom) return false;
const dx = ball.x - oldX;
const dy = ball.y - oldY;
let tx = -Infinity;
let ty = -Infinity;
if (dx > 0 && oldX <= left) tx = (left - oldX) / dx;
else if (dx < 0 && oldX >= right) tx = (right - oldX) / dx;
if (dy > 0 && oldY <= top) ty = (top - oldY) / dy;
else if (dy < 0 && oldY >= bottom) ty = (bottom - oldY) / dy;
let hitX = false;
let hitY = false;
if (Number.isFinite(tx) && Number.isFinite(ty) && Math.abs(tx - ty) < .12) {
hitX = true;
hitY = true;
} else if (tx > ty) {
hitX = true;
} else if (Number.isFinite(ty)) {
hitY = true;
} else if (Number.isFinite(tx)) {
hitX = true;
} else {
const penX = Math.min(ball.x - left, right - ball.x);
const penY = Math.min(ball.y - top, bottom - ball.y);
hitX = penX < penY;
hitY = !hitX;
}
if (hitX) {
ball.x = ball.vx >= 0 ? left - .02 : right + .02;
ball.vx = -ball.vx;
}
if (hitY) {
ball.y = ball.vy >= 0 ? top - .02 : bottom + .02;
ball.vy = -ball.vy;
}
return true;
}
function bounceFromPaddle(ball) {
const offset = clamp((ball.x - paddle.x) / (paddle.width / 2), -1, 1);
const angle = offset * 1.16;
const speed = Math.max(ballSpeed(), Math.hypot(ball.vx, ball.vy));
ball.vx = Math.sin(angle) * speed;
ball.vy = -Math.cos(angle) * speed;
ball.y = paddle.y - ball.r - .02;
}
function stepBall(ball, dt) {
if (ball.attached) {
ball.x = paddle.x;
ball.y = paddle.y - ball.r - 3;
ball.serveTime -= dt;
if (ball.serveTime <= 0) {
ball.attached = false;
const angle = .18;
const speed = ballSpeed();
ball.vx = Math.sin(angle) * speed;
ball.vy = -Math.cos(angle) * speed;
}
return true;
}
const distance = Math.max(Math.abs(ball.vx), Math.abs(ball.vy)) * dt;
const steps = Math.max(1, Math.min(18, Math.ceil(distance / 3.5)));
const subDt = dt / steps;
for (let step = 0; step < steps; step++) {
const oldX = ball.x;
const oldY = ball.y;
ball.x += ball.vx * subDt;
ball.y += ball.vy * subDt;
const minX = WALL + ball.r;
const maxX = W - WALL - ball.r;
if (ball.x < minX) {
ball.x = minX;
ball.vx = Math.abs(ball.vx);
} else if (ball.x > maxX) {
ball.x = maxX;
ball.vx = -Math.abs(ball.vx);
}
if (ball.y < TOP_WALL + ball.r) {
ball.y = TOP_WALL + ball.r;
ball.vy = Math.abs(ball.vy);
}
for (const brick of bricks) {
if (!brick.alive) continue;
if (collideBallWithBrick(ball, oldX, oldY, brick)) {
hitBrick(brick, ball);
break;
}
}
if (state === "victory") return true;
if (
ball.vy > 0 &&
oldY + ball.r <= paddle.y &&
ball.y + ball.r >= paddle.y &&
ball.x + ball.r >= paddle.x - paddle.width / 2 &&
ball.x - ball.r <= paddle.x + paddle.width / 2
) {
bounceFromPaddle(ball);
}
if (ball.y - ball.r > H + 8) return false;
}
ball.trailClock += dt;
while (ball.trailClock >= .022) {
ball.trailClock -= .022;
ball.trail.push({ x: ball.x, y: ball.y });
if (ball.trail.length > 7) ball.trail.shift();
}
return true;
}
function projectLandingX(ball) {
const landingY = paddle.y - ball.r;
const time = ball.vy > 0 ? Math.max(0, (landingY - ball.y) / ball.vy) : 0;
const low = WALL + ball.r;
const high = W - WALL - ball.r;
const span = high - low;
if (span <= 0) return W / 2;
const period = span * 2;
const raw = ball.x - low + ball.vx * time;
let folded = ((raw % period) + period) % period;
if (folded > span) folded = period - folded;
return low + folded;
}
function updatePaddle(dt) {
paddle.y = H - 43;
if (state === "demo") {
let target = paddle.x;
const descending = balls.filter(ball => !ball.attached && ball.vy > 0);
if (descending.length) {
descending.sort((a, b) => {
const ta = (paddle.y - a.r - a.y) / a.vy;
const tb = (paddle.y - b.r - b.y) / b.vy;
return ta - tb;
});
const ball = descending[0];
target = projectLandingX(ball) + clamp(ball.vx / Math.max(1, Math.abs(ball.vy)) * 8, -10, 10);
} else {
const moving = balls.filter(ball => !ball.attached).sort((a, b) => b.y - a.y)[0];
if (moving) target = moving.x;
}
const closeDrop = drops
.filter(drop => drop.y > paddle.y - 145 && drop.y < paddle.y + 10)
.sort((a, b) => b.y - a.y)[0];
if (closeDrop) target = closeDrop.x;
const delta = clamp(target, WALL + paddle.width / 2 + 2, W - WALL - paddle.width / 2 - 2) - paddle.x;
paddle.x += clamp(delta, -1500 * dt, 1500 * dt);
} else if (state === "playing") {
if (mouseActive) {
paddle.x = mouseTarget;
} else {
let direction = 0;
if (keys.has("ArrowLeft")) direction -= 1;
if (keys.has("ArrowRight")) direction += 1;
paddle.x += direction * 560 * dt;
}
}
paddle.x = clampPaddleX(paddle.x);
}
function applyPower(type, x, y) {
if (type === "wide") {
paddle.wideTimer = 10;
paddle.width = paddle.baseWidth * 1.55;
paddle.x = clampPaddleX(paddle.x);
setFeedback("WIDE PADDLE · 10 SEC", "#85f4f1");
} else {
const source = balls.find(ball => !ball.attached) || balls[0];
if (source) {
const baseVx = source.vx;
const baseVy = source.vy;
const speed = Math.max(ballSpeed(), Math.hypot(baseVx, baseVy));
const baseAngle = Math.atan2(baseVy, baseVx);
const offsets = [-.28, .28];
for (const offset of offsets) {
if (balls.length >= 3) break;
const angle = baseAngle + offset;
const side = balls.length % 2 === 0 ? -1 : 1;
balls.push(createBall(
source.x + side * 4,
source.y,
Math.cos(angle) * speed,
Math.sin(angle) * speed
));
}
}
setFeedback("MULTI-BALL · 3 ACTIVE", "#d6a8ff");
}
updateHud();
}
function updateDrops(dt) {
for (let i = drops.length - 1; i >= 0; i--) {
const drop = drops[i];
drop.y += drop.vy * dt;
drop.age += dt;
const overlapsPaddle =
drop.y + drop.r >= paddle.y &&
drop.y - drop.r <= paddle.y + paddle.height &&
drop.x + drop.r >= paddle.x - paddle.width / 2 &&
drop.x - drop.r <= paddle.x + paddle.width / 2;
if (overlapsPaddle) {
applyPower(drop.type, drop.x, drop.y);
drops.splice(i, 1);
} else if (drop.y - drop.r > H + 5) {
drops.splice(i, 1);
}
}
}
function updateBalls(dt) {
for (let i = balls.length - 1; i >= 0; i--) {
if (state !== "playing" && state !== "demo") break;
if (!stepBall(balls[i], dt)) balls.splice(i, 1);
}
if (state === "playing" && balls.length === 0) {
lives = Math.max(0, lives - 1);
if (lives <= 0) {
state = "gameover";
renderOverlay();
updateHud();
} else {
balls.push(createWaitingBall(.72));
updateHud();
}
} else if (state === "demo" && balls.length === 0) {
balls.push(launchBall(.22));
updateHud();
}
}
function update(dt) {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.life -= dt;
p.x += p.vx * dt;
p.y += p.vy * dt;
p.vx *= Math.pow(.985, dt * 60);
p.vy += 350 * dt;
p.angle += p.spin * dt;
if (p.life <= 0) particles.splice(i, 1);
}
for (let i = floatTexts.length - 1; i >= 0; i--) {
const text = floatTexts[i];
text.life -= dt;
text.y -= 28 * dt;
if (text.life <= 0) floatTexts.splice(i, 1);
}
if (feedback) {
feedback.life -= dt;
if (feedback.life <= 0) feedback = null;
}
if (paddle.wideTimer > 0) {
const before = Math.ceil(paddle.wideTimer);
paddle.wideTimer = Math.max(0, paddle.wideTimer - dt);
if (paddle.wideTimer === 0) {
paddle.width = paddle.baseWidth;
paddle.x = clampPaddleX(paddle.x);
updateHud();
} else {
const after = Math.ceil(paddle.wideTimer);
if (after !== before) updateHud();
}
}
if (state === "playing" || state === "demo") {
updatePaddle(dt);
updateDrops(dt);
updateBalls(dt);
if (state === "demo" && remaining === 0) makeLevel();
}
}
function drawBackground(time) {
const bg = ctx.createLinearGradient(0, 0, 0, H);
bg.addColorStop(0, "#0c1527");
bg.addColorStop(.48, "#0b1324");
bg.addColorStop(1, "#080f1e");
ctx.fillStyle = bg;
ctx.fillRect(0, 0, W, H);
const glow = ctx.createRadialGradient(W * .5, H * .48, 3, W * .5, H * .48, Math.max(W, H) * .72);
glow.addColorStop(0, "rgba(44, 91, 132, .13)");
glow.addColorStop(.5, "rgba(30, 50, 92, .055)");
glow.addColorStop(1, "rgba(3, 7, 16, .04)");
ctx.fillStyle = glow;
ctx.fillRect(0, 0, W, H);
ctx.lineWidth = 1;
ctx.strokeStyle = "rgba(112, 146, 194, .045)";
for (let x = WALL + 20; x < W; x += 40) {
ctx.beginPath();
ctx.moveTo(x, 45);
ctx.lineTo(x, H - 16);
ctx.stroke();
}
for (let y = 80; y < H - 14; y += 40) {
ctx.beginPath();
ctx.moveTo(WALL, y);
ctx.lineTo(W - WALL, y);
ctx.stroke();
}
for (const star of stars) {
const alpha = .17 + (.31 * (Math.sin(time * star.speed + star.phase) + 1) / 2);
ctx.globalAlpha = alpha;
ctx.fillStyle = "#c8e8ff";
ctx.beginPath();
ctx.arc(star.x, star.y, star.r, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
ctx.strokeStyle = "rgba(108, 154, 205, .2)";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(WALL, 48.5);
ctx.lineTo(W - WALL, 48.5);
ctx.stroke();
ctx.fillStyle = "rgba(132, 162, 203, .63)";
ctx.font = "700 8px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
ctx.textBaseline = "middle";
ctx.textAlign = "left";
ctx.fillText("ORBITAL DEFENSE ARRAY", WALL + 5, 26);
ctx.textAlign = "right";
ctx.fillText(`NODES ${String(remaining).padStart(2, "0")} / ${String(total).padStart(2, "0")}`, W - WALL - 5, 26);
ctx.setLineDash([3, 7]);
ctx.strokeStyle = "rgba(255, 107, 137, .19)";
ctx.beginPath();
ctx.moveTo(WALL + 4, H - 13.5);
ctx.lineTo(W - WALL - 4, H - 13.5);
ctx.stroke();
ctx.setLineDash([]);
ctx.textAlign = "left";
ctx.font = "700 7px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
ctx.fillStyle = "rgba(255, 127, 151, .5)";
ctx.fillText("DANGER LINE", WALL + 7, H - 23);
}
function drawPowerIcon(type, x, y, size, color) {
ctx.save();
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = Math.max(1.2, size * .23);
ctx.lineCap = "round";
ctx.lineJoin = "round";
if (type === "wide") {
const wing = size * .72;
ctx.beginPath();
ctx.moveTo(x - wing, y);
ctx.lineTo(x + wing, y);
ctx.moveTo(x - wing, y);
ctx.lineTo(x - wing * .38, y - size * .48);
ctx.moveTo(x - wing, y);
ctx.lineTo(x - wing * .38, y + size * .48);
ctx.moveTo(x + wing, y);
ctx.lineTo(x + wing * .38, y - size * .48);
ctx.moveTo(x + wing, y);
ctx.lineTo(x + wing * .38, y + size * .48);
ctx.stroke();
} else {
const r = Math.max(1.15, size * .2);
ctx.beginPath();
ctx.arc(x - size * .52, y + size * .17, r, 0, Math.PI * 2);
ctx.arc(x + size * .52, y + size * .17, r, 0, Math.PI * 2);
ctx.arc(x, y - size * .42, r, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
}
function drawBrick(brick) {
if (!brick.alive) return;
const palette = PALETTE[brick.row];
const x = brick.x;
const y = brick.y;
const w = brick.w;
const h = brick.h;
ctx.save();
ctx.shadowColor = palette.glow;
ctx.shadowBlur = brick.flash > 0 ? 17 : (brick.tough ? 7 : 5);
roundedPath(ctx, x, y, w, h, Math.min(6, h * .27));
const gradient = ctx.createLinearGradient(0, y, 0, y + h);
gradient.addColorStop(0, palette.top);
gradient.addColorStop(.38, palette.mid);
gradient.addColorStop(1, palette.bottom);
ctx.fillStyle = gradient;
ctx.fill();
ctx.shadowBlur = 0;
ctx.strokeStyle = brick.tough ? "rgba(243, 248, 255, .42)" : "rgba(245, 255, 255, .3)";
ctx.lineWidth = brick.tough ? 1.1 : .8;
roundedPath(ctx, x + .4, y + .4, w - .8, h - .8, Math.min(6, h * .27));
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x + 5, y + 2);
ctx.lineTo(x + Math.max(6, w - 5), y + 2);
ctx.strokeStyle = "rgba(255,255,255,.35)";
ctx.lineWidth = 1;
ctx.stroke();
if (brick.tough) {
ctx.strokeStyle = "rgba(255,255,255,.19)";
ctx.lineWidth = 1;
roundedPath(ctx, x + 2.2, y + 2.2, w - 4.4, h - 4.4, Math.min(4, h * .2));
ctx.stroke();
for (let i = 0; i < 2; i++) {
ctx.beginPath();
ctx.arc(x + w - 5 - i * 4, y + 5, 1.15, 0, Math.PI * 2);
ctx.fillStyle = brick.hp === 2 || i === 0 ? "rgba(255,255,255,.8)" : "rgba(255,255,255,.23)";
ctx.fill();
}
}
if (brick.power) {
const r = Math.min(7.1, h * .37, w * .21);
ctx.beginPath();
ctx.arc(x + w / 2, y + h / 2, r + 1.4, 0, Math.PI * 2);
ctx.fillStyle = "rgba(6, 17, 32, .46)";
ctx.fill();
ctx.strokeStyle = "rgba(244, 255, 255, .66)";
ctx.lineWidth = .8;
ctx.stroke();
drawPowerIcon(brick.power, x + w / 2, y + h / 2, r * .82, "#f3ffff");
}
if (brick.tough && brick.hp === 1) {
ctx.fillStyle = "rgba(7, 11, 25, .17)";
roundedPath(ctx, x + 1, y + 1, w - 2, h - 2, Math.min(5, h * .22));
ctx.fill();
ctx.save();
ctx.strokeStyle = "rgba(255, 251, 240, .96)";
ctx.lineWidth = 1.45;
ctx.lineCap = "round";
ctx.lineJoin = "round";
ctx.shadowColor = "#fff4dc";
ctx.shadowBlur = 5;
ctx.beginPath();
ctx.moveTo(x + w * .52, y + 1.5);
ctx.lineTo(x + w * .44, y + h * .3);
ctx.lineTo(x + w * .57, y + h * .49);
ctx.lineTo(x + w * .43, y + h * .72);
ctx.lineTo(x + w * .51, y + h - 1.5);
ctx.moveTo(x + w * .45, y + h * .38);
ctx.lineTo(x + w * .29, y + h * .3);
ctx.moveTo(x + w * .53, y + h * .55);
ctx.lineTo(x + w * .72, y + h * .47);
ctx.stroke();
ctx.restore();
}
ctx.restore();
}
function drawDrop(drop, time) {
const palette = drop.type === "wide" ? "#6ef0ee" : "#d2a2ff";
const pulse = 1 + Math.sin(time * 5 + drop.age * 2) * .08;
ctx.save();
ctx.globalAlpha = .32;
ctx.fillStyle = palette;
ctx.beginPath();
ctx.arc(drop.x, drop.y - 11, 3.2, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = .56;
ctx.beginPath();
ctx.arc(drop.x, drop.y - 6, 2.1, 0, Math.PI * 2);
ctx.fill();
ctx.globalAlpha = 1;
ctx.shadowColor = palette;
ctx.shadowBlur = 18;
ctx.beginPath();
ctx.arc(drop.x, drop.y, drop.r * pulse, 0, Math.PI * 2);
const g = ctx.createRadialGradient(drop.x - 3, drop.y - 4, 1, drop.x, drop.y, drop.r * 1.25);
g.addColorStop(0, "#ffffff");
g.addColorStop(.25, palette);
g.addColorStop(1, drop.type === "wide" ? "#1b8296" : "#714ca8");
ctx.fillStyle = g;
ctx.fill();
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.arc(drop.x, drop.y, drop.r + 2, 0, Math.PI * 2);
ctx.strokeStyle = drop.type === "wide" ? "rgba(128,255,244,.56)" : "rgba(222,183,255,.55)";
ctx.lineWidth = 1;
ctx.stroke();
drawPowerIcon(drop.type, drop.x, drop.y, 6.2, "#ffffff");
ctx.restore();
}
function drawParticles() {
for (const p of particles) {
const alpha = clamp(p.life / p.maxLife, 0, 1);
ctx.save();
ctx.globalAlpha = alpha;
ctx.translate(p.x, p.y);
ctx.rotate(p.angle);
ctx.fillStyle = p.color;
ctx.shadowColor = p.color;
ctx.shadowBlur = 7;
ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size);
ctx.restore();
}
}
function drawPaddle() {
const x = paddle.x - paddle.width / 2;
const y = paddle.y;
ctx.save();
ctx.shadowColor = paddle.wideTimer > 0 ? "rgba(120, 242, 239, .8)" : "rgba(80, 198, 232, .68)";
ctx.shadowBlur = paddle.wideTimer > 0 ? 24 : 18;
roundedPath(ctx, x, y, paddle.width, paddle.height, 6);
const gradient = ctx.createLinearGradient(x, y, x, y + paddle.height);
gradient.addColorStop(0, "#d4ffff");
gradient.addColorStop(.28, "#79e8ee");
gradient.addColorStop(1, "#3888cb");
ctx.fillStyle = gradient;
ctx.fill();
ctx.shadowBlur = 0;
roundedPath(ctx, x + 7, y + 2, paddle.width - 14, 2, 1);
ctx.fillStyle = "rgba(255,255,255,.56)";
ctx.fill();
roundedPath(ctx, x + 4, y + paddle.height - 3, paddle.width - 8, 1, .5);
ctx.fillStyle = "rgba(15, 49, 90, .55)";
ctx.fill();
ctx.fillStyle = "rgba(238,255,255,.78)";
ctx.beginPath();
ctx.arc(x + 7, y + paddle.height / 2, 1.3, 0, Math.PI * 2);
ctx.arc(x + paddle.width - 7, y + paddle.height / 2, 1.3, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function drawBall(ball) {
ctx.save();
for (let i = 0; i < ball.trail.length; i++) {
const point = ball.trail[i];
const t = (i + 1) / (ball.trail.length + 1);
ctx.globalAlpha = t * .22;
ctx.fillStyle = "#74eaf2";
ctx.beginPath();
ctx.arc(point.x, point.y, ball.r * (.35 + t * .42), 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
ctx.shadowColor = "#78f2f5";
ctx.shadowBlur = 18;
const gradient = ctx.createRadialGradient(ball.x - 2.3, ball.y - 2.7, .5, ball.x, ball.y, ball.r * 1.5);
gradient.addColorStop(0, "#ffffff");
gradient.addColorStop(.45, "#e5ffff");
gradient.addColorStop(1, "#76dce9");
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.arc(ball.x - 2, ball.y - 2.2, 1.1, 0, Math.PI * 2);
ctx.fillStyle = "rgba(255,255,255,.96)";
ctx.fill();
ctx.restore();
}
function drawFloatingText() {
for (const item of floatTexts) {
ctx.save();
ctx.globalAlpha = clamp(item.life / item.maxLife, 0, 1);
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "800 10px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
ctx.shadowColor = item.color;
ctx.shadowBlur = 7;
ctx.fillStyle = item.color;
ctx.fillText(item.text, item.x, item.y);
ctx.restore();
}
}
function drawFeedback() {
if (!feedback) return;
const alpha = clamp(feedback.life / feedback.maxLife, 0, 1);
ctx.save();
ctx.globalAlpha = Math.min(1, alpha * 1.5);
ctx.font = "800 9px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
const width = ctx.measureText(feedback.text).width + 26;
const x = (W - width) / 2;
const y = H * .43;
roundedPath(ctx, x, y, width, 25, 9);
ctx.fillStyle = "rgba(7, 13, 27, .82)";
ctx.fill();
ctx.strokeStyle = `${feedback.color}88`;
ctx.lineWidth = 1;
ctx.stroke();
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = feedback.color;
ctx.fillText(feedback.text, W / 2, y + 12.5);
ctx.restore();
}
function render(timeMs) {
if (!W || !H) return;
const time = timeMs / 1000;
drawBackground(time);
for (const brick of bricks) drawBrick(brick);
for (const drop of drops) drawDrop(drop, time);
drawParticles();
drawPaddle();
for (const ball of balls) drawBall(ball);
drawFloatingText();
drawFeedback();
}
canvas.addEventListener("pointermove", event => {
if (state !== "playing") return;
const rect = canvas.getBoundingClientRect();
mouseTarget = (event.clientX - rect.left) * (W / rect.width);
mouseActive = true;
});
canvas.addEventListener("pointerdown", event => {
const rect = canvas.getBoundingClientRect();
const x = (event.clientX - rect.left) * (W / rect.width);
if (state === "demo") {
startGame(x);
} else if (state === "playing") {
mouseTarget = x;
mouseActive = true;
}
});
window.addEventListener("keydown", event => {
if (state === "demo") {
startGame();
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
keys.add(event.key);
event.preventDefault();
} else if (event.key === " " || event.code === "Space") {
event.preventDefault();
}
return;
}
if (state === "gameover" || state === "victory") {
if (event.key === "Enter" || event.key === " " || event.code === "Space") {
event.preventDefault();
startGame();
}
return;
}
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
keys.add(event.key);
mouseActive = false;
event.preventDefault();
}
});
window.addEventListener("keyup", event => {
if (event.key === "ArrowLeft" || event.key === "ArrowRight") keys.delete(event.key);
});
window.addEventListener("blur", () => keys.clear());
window.addEventListener("resize", resizeCanvas);
if ("ResizeObserver" in window) {
const observer = new ResizeObserver(resizeCanvas);
observer.observe(canvas);
}
resizeCanvas();
startDemo();
function animate(now) {
const dt = Math.min(.034, Math.max(0, (now - lastTime) / 1000));
lastTime = now;
update(dt);
render(now);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
})();
</script>
</body>
</html>Reasoning trace (44,277 tokens)
**Designing a game overlay** I’m thinking about how to design a large, tasteful badge, ideally placed in the lower or middle section of the screen. I want to use a semi-transparent overlay card in the center, making sure the game remains visible. The text will say "CLICK OR PRESS A KEY TO START." I also have an idea for a demo robot to track the ball and start automatically. I need to keep the start message top-centered to avoid covering the game action while ensuring a smooth user experience.**Refining the overlay card design** I’m considering positioning the overlay card at the center with a height of 300 in the lower half of the screen so it won't obstruct the brick array or the ball too much. Placing it at the top with a height of 48 seems like an option too. The overlay should have a polished look, with a message centered, about 380 in width, featuring a dark translucent surface and a subtle border, allowing ball trajectories to be visible behind it.**Evaluating game mechanics** I've been thinking about making the canvas dynamic and responsive with dimensions like 1100 wide and 540 high. I wonder if I can normalize coordinates for physics in a fixed world of 1000 by 560. For mobiles, I could try a width of 351 with a height of around 580. I need to be cautious about maintaining the aspect ratio; too tight, and it might feel cramped. The layout should adapt to avoid horizontal scrolling and keep gameplay comfortable.**Adjusting canvas for adaptation** I’m thinking about setting the mobile canvas height to 66vh or around 560 pixels, with a width of 351, giving an aspect ratio of 0.63. Should the world use actual dimensions for scaling axes and building physics based on canvas dimensions? Maybe I can set 9 columns on desktop and 7 on mobile. I wonder if I can fix the height relative to width using breakpoints, like setting canvas CSS width to 100% and height to clamp between 440px, 65vh, and 560px.**Designing game layout** I'm working on the internal logic for a game canvas with a width of 1136 and a height of 530. The background will have a navy gradient with radial glows and subtle grid lines or stars. I need to create an outer game frame with proper padding for gameplay. For the bricks, I'm considering 8 columns with a total width of 1100, which will fit nicely on mobile screens as well. It's all about balancing responsiveness while maintaining a solid design!**Designing brick mechanics** I'm thinking about how tougher bricks need two hits to break and visibly crack after the first hit. I’ll assign hit points (hp) to the bricks, reducing it with each hit. I want to ensure the cracks are visible, perhaps with glowing lines that show damage. Then, I need to introduce power-up icons on certain bricks. To indicate their special effects, I could use small symbols like blue diamonds for wide and three circles for multi. It's important to ensure these drop when the bricks are broken!**Developing icon placement** I'm considering the placement of icons on the right side of the screen. With 10 columns, I think 95 pixels should be enough, and for mobile, I could use 40 pixels. If the icons are small at 11 pixels, I could center them at the top of the bricks, which are 39 pixels wide. I might draw icons as symbols using vectors. For power-up pickups, I’ll have them drop at a speed of around 95 pixels per second and set the collision with the paddle rectangle at its top.**Planning ball movement physics** I'm thinking about how the ball moves upward at the start and needs to maintain speed between 290 and 340. I’ll animate on the canvas with requestAnimationFrame while considering step timing, possibly clamping dt to a max of 0.033 seconds. I want to prevent the ball from passing through bricks or the paddle. At higher speeds, like 360, I need to ensure robust collision detection, perhaps through substeps for continuous collision handling. Let’s figure out what's best for a smooth experience!**Refining collision detection** I'm considering the collision algorithm that uses substeps to move the ball in small increments with dt. First, I need to check for wall collisions: if the ball's x-position minus its radius is less than the bounds, I should set its velocity to the absolute value. If it crosses the right wall or top boundary, I’ll adjust the velocity accordingly. Also, I need to handle the bottom edge to ensure the ball is properly managed if it goes out of bounds. Let's make this robust!**Implementing game controls** I’m considering how to set up event listeners for a canvas and overlay. The overlay should have `pointer-events: none`, allowing clicks on the canvas to be caught. I need to attach a `pointerdown` event to the canvas, starting the demo when clicked. Also, I'll need keyboard listeners for arrow controls during active gameplay, and I should ensure keyboard events only trigger after iframe clicks. The demo should simulate a game state with a loop to draw bricks and the ball when starting.**Configuring ball tracking** I’m setting up the initial run with the ball starting at the paddle, and I need a reliable tracking system for the ball’s future movements. The easiest way is to track the current ball's x position as it descends, adjusting accordingly to avoid misses. The AI paddle should follow the nearest descending ball. I’m also considering adding multiple balls for demo power-ups to make things more engaging. Additionally, I should start the initial ball's movement diagonally towards the bricks to ensure it hits one soon.**Designing AI Demo Interaction** I need to ensure the AI clearly shows an overlay message during the demo. It should involve one ball that occasionally interacts with power-up drops. The AI will track the ball but not the drops directly; catching a power-up near the paddle is nice but not essential. For a more impactful demo, maybe the power-ups should be initial active. I’m considering game states like 'playing,' 'demo,' 'gameover,' and 'victory' and how to manage lives effectively.**Implementing Game Mechanics** I think creating a new ball that moves after 0.8 seconds using a serve timer is simpler, while keeping the paddle in place. I might show a serve flash and maintain the controls. The game should end when lives are less than or equal to zero and declare victory when no bricks remain—power-ups should not count towards bricks. I’m considering a scoring system where normal hits yield 50 points, and tough hits grant an extra 25. An overlay for restarting would be helpful, and it should be clickable.**Designing breakout interface** I'm starting to outline the design for the breakout game interface. I need a polished look, focusing on elements like headers, branding, and a classy logo. Using CSS to manage layout and styling is key. I want to consider aspects like box-sizing, radial gradients for the background, and a responsive design with mobile adjustments. I’ll ensure to include custom icons with SVG for a neat, modern appearance. The title and subtitle will also be aligned properly for a sleek presentation.**Considering rounded rectangle implementation** I’m weighing the options for creating a rounded rectangle. There’s the native `CanvasRenderingContext2D.roundRect`, which seems the latest approach. But I might also consider using a custom function like `roundedRectPath` for broader usage. I guess both have their merits, but I want to be careful to choose the safest and most effective method. Let's see how I can implement this without any hiccups!**Addressing grid resizing** I’m considering how to manage the grid width during resizing. For a width of 1120, I should set 10 columns, or 7 for mobile. If I resize mid-game, the grid might change, potentially affecting gameplay and scores. I could recalculate the brick layout only before the game becomes active. Also, it’s worth using a responsive grid from the outset. Changing the number of columns while crossing breakpoints could reset the board, but resetting user progress seems risky. Preserving some stats might help!**Designing responsive grid layout** I’m thinking of setting a fixed column count, perhaps 10, but for mobile, the bricks will be around 31px wide with a field width of 300. Alternatively, using 9 columns at a desktop width of 375 gives me a 33px width per brick, which might work! Having 7 columns for mobile could be more legible. I'm also considering how to maintain the brick layout on resize while updating positions. I could set a fixed 8 columns for all sizes, giving about 39px for mobile and creating a total of 48 bricks with 6 rows, which sounds good!**Optimizing dynamic brick width** I need to set the brick width dynamically based on the field width. For a width of 1120, subtracting gaps makes it 1000, leading to a brick width of 119, which feels too wide. Breakout games usually have broader bricks but with 8 columns, it looks low density. Maybe using a fixed 10 columns could bring it down to 94px each. For mobile, setting the bricks to around 30px seems sensible. So, I could use 10 columns on desktop and 8 on mobile for better density!**Adjusting layout calculations** I'm figuring out how to maintain six rows, but it's tricky since resizing changes the count. I need to recompute the layout based on width, which might cause a loss in rows or columns. For mobile, it's eight columns and for desktop, ten. I've calculated some brick widths based on different viewport sizes, and everything seems good so far. I have specifics for margins, gaps, and brick heights for both mobile and desktop setups. Overall, it looks promising!**Calculating paddle and ball positions** I'm working on the layout for the paddle and ball. At a height of 560, I've calculated the top at 74, and I've added to that. The lower open area is 270. The paddle's Y position is roughly at 487 for desktop and 520 for mobile, with a thickness of 11. The ball starts at 438. I need to ensure the ball can be lost when it goes beyond certain boundaries. I’m determining the appropriate coordinates for walls and potential collisions. Everything is coming together!**Scoring criteria in a game** I’m trying to figure out how to score based on brick hits. It looks like the scoring system is: 25 points for a damaged brick, 50 for a destroyed normal one, and then a specific system for tougher bricks, with the first hit being worth 30 points and the second hit 50 points. It's a simple numeric system, but I want to make sure I understand all the details correctly!**Considering collision scenarios** I'm thinking about a situation where the ball might already be inside from a previous step. When we separate after a collision, it seems straightforward, but I’m also considering what happens if the row is adjacent and the speed is high. There could be complications there! It's interesting to explore how these dynamics work and how they might change the outcome. I wonder what specific interactions might occur in that case.**Considering ball physics** I'm thinking about how to handle collisions with balls and the paddle. When the balls hit the paddle, I need to apply an angle formula to determine how they will bounce off. It's crucial that the new velocity retains the ball's speed; otherwise, the motion wouldn't behave as expected if the angle changes. I need to ensure these elements work smoothly together to achieve realistic gameplay dynamics.**Implementing game mechanics** I'm considering how to introduce new balls at the pickup drop position near the paddle, potentially up to three from the current ball's position. I can use a function to rotate the vector for this. Okay, if no ball is present—though that's unlikely—I should create one. I also need to consider power-ups and their status. Perhaps a UI card showing "BOOSTS" with an active timer would be ideal. It could be in the top right header and dynamically displayed. I'll add footer controls and ensure the mobile version hides certain elements to streamline the experience.**Designing UI elements** I'm planning to add a `.statusbar` at the bottom for the desktop display with play controls and a right-side `POWER-UPS` icon. For mobile, I need to make sure it wraps appropriately. There's also a scoreboard card that will show the level as `01 / 01` and possibly the number of bricks. Initially, I want to display a message that says, "Click or press a key to start" exactly as it is to guide players effectively.**Considering button interaction** I’m pondering if the overlay I've applied is set to `pointer-events:none`, which would make the button unclickable. I wonder if that's what's causing the issue. It feels like a simple oversight, but these details can trip me up! I'll need to check the CSS and ensure that the overlay settings aren’t interfering with the button functionality. If they are, I should adjust that property to allow for click events. Let's see how this plays out!**Configuring mouse control** I'm thinking about how to set up smooth interpolation at a speed of around 1600. I want it to be instantly responsive. I’d set the mouse control as `paddle.x = pointerTarget`, making sure the mouse controls are distinct. If a key is pressed, I should set `mouseActive` to false, but then if the pointer moves after that key is released, I need to account for that too. Let's make sure the logic flows smoothly!**Considering game mechanics** I’m thinking about how to handle ball-playing dynamics. If a ball is lost, I shouldn’t decrease lives until all are gone. In a demo, maybe I should show three lives visually, even if the AI doesn’t miss. If the demo loses a ball, I could reset without reducing lives—keeping it fair. Power-ups should fall straight down, and I’ll want to check collision with the paddle to ensure they’re caught properly. That seems to cover the main gameplay mechanics!**Designing the drop mechanic** I’m working on the drop mechanic and thinking that if it passes the bottom, it should definitely be discarded since it’s under the paddle's Y-level. I want to make it visually appealing, so I envision the drop as a glowing orb with an outer ring, kind of like an icon. It could also have a trail effect to enhance its look as it moves. This way, it feels engaging for players!**Designing canvas for a game** I'm considering how to set things up for the canvas in my game. I plan to ensure the canvas is ready by using `setTimeout` to wait for the DOM to be loaded since my JS script is at the bottom. I'll make sure to call `resizeCanvas()` for smooth resizing. I need to store brick details in an array and think about how to handle resizing the level without disrupting the game state too much. It’s tricky balancing all these details!**Adjusting game mechanics** I should check the bricks after the walls. If the ball is outside the wall, I’ll adjust using a loop through the bricks. I’m thinking about handling collisions so that when the ball hits a brick, it reflects properly based on its current position. If the ball bounces back down after hitting hard, it could hit the paddle sometime. I need to manage lives carefully in the `updateGame` function, ensuring I only lose one life if the player is down to their last ball.**Assessing demo requirements** I’m considering whether the state demo needs to restart. Maybe I’ll click the `Start` button, or am I supposed to look for something like “Click or press a key to start”? The card isn't a button; it seems like the canvas is the entire area. I also need to add a key. The victory overlay should show result details like the score, but high scores aren’t allowed due to storage limitations. I need to test all functionalities and focus only on outputting code within `<html>`. Let’s work on coherent CSS too!**Exploring brick design** I’m considering the color for normal bricks, thinking of a tough fill with a darker gradient. I might add an inner stroke and have the top row with two smaller pips at coordinates x+w-8. The power-up icon can be centered, although it might overlap with the tough bricks. Maybe I should avoid placing a power-up on tough bricks by setting the tough property to false. This seems like a good plan moving forward!**Analyzing game mechanics** I'm considering the mechanics of a game where a ball drops from a certain height (y=100) and takes about 3.3 seconds to reach the paddle at y=470. That drop visibility is good for the user, especially if they can catch it around the 2.5-second mark. I'll need to update the drop position using the formula `d.y += d.vy*dt`. Also, I think it would be interesting if an AI can catch pickups if they're near the paddle!**Evaluating ball tracking logic** I’m thinking about how to track the ball better. Maybe I can update the paddle position based on the ball's x-coordinate, but I should be cautious. For drops, I need to calculate the time it takes for the drop to reach the paddle and target the nearest one. I should be careful with how I prioritize drops so that it doesn’t cause missed balls. I want to avoid perfect paddle tracking that could lead to a situation with multiple balls on screen, or unexpected outcomes in the demo.**Optimizing paddle movement** I’m considering how to move the paddle during gameplay. If a key is pressed, I’ll move it at a rate of 560 times the delta time. If the mouse is active, I might set the paddle's x-coordinate directly to the mouse's x-position, although using interpolation could make it smoother. For the `pointermove` event on the canvas, I'll track the mouse position and ensure the paddle updates accordingly while the game is playing. That could improve responsiveness!**Refining input handling** I’m thinking about improving the event handling for the game. It would be simpler if I use key events to start the game and handle arrow keys. I want to ensure that keys are added and the default actions are prevented when needed. I also need to manage pointer events, handling mouse movements and clicks. It's crucial to consider accessibility by providing labels and making sure user interactions are smooth, especially for touch controls. Overall, I want to ensure a seamless experience!**Designing game graphics** I'm thinking about how to draw circles with color and a core radial gradient shadow blur. For the core, I might use `ctx.arc` and fill it with a white radial, adding a small highlight at x-2, y-2. I wonder if I should implement a small concentric pulse. For the paddle, I want to set the shadow blur and color, then create a gradient from light cyan to blue. I’ll include outer shadows and particles to enhance visual effects.**Considering game mechanics** I'm thinking about the game mechanics, specifically how recomputing `paddle.y` affects gameplay. It’s good that `speed x` remains unchanged since that keeps things stable. Now, I also need to consider `balls x` width and how it interacts with the game map and bricks. Finding the right balance will ensure the gameplay feels smooth and engaging, which is my main goal! Let's explore how these elements work together.**Designing game mechanics** I'm working on the trajectory of the ball hitting bricks. If I start from the center, the ball might hit the top brick and return; I estimate it could take about 3 seconds for 2/3 of the hits. I think I need to consider making the power-up brick one of the first hits, but I’m unsure about the specific positions. The timing and visual effects need to be fine-tuned, especially on both desktop and mobile displays. Should I position the overlay at the center or in a corner?**Choosing overlay position** I’m considering how to position the overlay to avoid covering the ball's path when it hits the bricks. It might be better to place the overlay at x=W/2 and y=0, as this could keep it clear of the action. I'm hoping that this decision will enhance the gameplay experience, making sure players can still see where the ball is headed without any obstruction. I really want to get this right!**Considering game overlay design** I’m thinking about placing a message with a height of 50, maybe at y=H-?. An overlay could work as a floating strip above the bricks, but its position is tricky since it overlaps with the first row. Alternatively, I could center a small card below the top. There's also a concern about starting the game in that self-playing demo mode, which requires a visible message saying "Click or press a key to start." The demo bot needs to be active with the balls moving, so I'll keep that in mind.**Planning game interface elements** I’m considering including a style label that says "DEMO ACTIVE" in the header to indicate the game's status. Additionally, I need to add a restart button in HTML. I’ll need to use `setState(state)` to adjust the display accordingly. This way, it’ll be easier for the user to understand when the demo is active and have the option to restart if needed. Keeping the interface clean and intuitive is important, so I’ll focus on that.**Considering powerup icons and interactions** I'm thinking about the colors for power-up icons, and how lives could be represented as small filled SVG hearts. For the HUD card, I want to show the current progress and status of power. I don't think we need an extra button for interaction starts; maybe we can use `keydown e.key==='Escape'` to handle that. The restart can be with the Space or Enter keys. Also, I'm wondering if a subtle pulse for scoring could work, though maybe not with CSS. For text labels, they should be dynamic on the canvas.**Evaluating canvas resizing logic** I'm considering whether the stage might include a `border`. The `canvas` should display a block with a width of 100%, and the width of the stage content needs to exclude the border. I'm thinking about using `getBoundingClientRect()` to get the exact dimensions. When I call `resizeCanvas()`, it initializes my demo state, score, lives, and other elements. However, I realize that using `ResizeObserver` is optional since a window resize is usually enough. Still, I should be cautious about how asynchronous calls might happen with the canvas width.**Analyzing game mechanics** So I'm figuring out that in the game, it seems like there's an issue with rendering when `life0` calls `render`. The overlay results must trigger the click button event listener to set `startGame`. But wait, can `startGame()` even run if the game is live? Yes, since the button only handles results. I want to ensure this logic checks out for a smooth game reset!**Sketching CSS specifics** I'm outlining some CSS specifics. For the desktop view at 1280px, I plan for game shell width at 1120px, top bar height at 56px, and a margin set to 20px auto with 14px at the bottom. The top bar's height should be 52px, with a gap of 13px. For the canvas, I’ll set a maximum height of 520px. On mobile, I'll override the width to calculate at 100% minus 24px and adjust the margin to 12px auto. Overall, I’m aiming for total measurements that add up nicely!**Considering initial conditions** I'm wondering what might happen if the initial ball were to start inside a brick due to resizing or if the brick appears beneath the ball. That seems unlikely, so I should focus on situations closer to the edges and use normal velocity. It's important for me to clarify these assumptions to ensure accurate reasoning about how things would play out in that scenario!**Formulating CSS styles** I’m focusing on creating dynamic and crisp CSS styles now. I think starting with `:root` variables will help establish background colors and surfaces effectively. I want to ensure elements like the `.game-shell` and `.intro-card` have appropriate layouts, including hover effects and media queries for responsiveness at mobile sizes. I’ll balance elements like margins and paddings for clarity, scaling things according to screen size with a `@media` query. It seems like a lot to track, but I’m hopeful it’ll come together nicely!**Refining HUD card design** I’m considering the label for the third HUD card, possibly shortening it to "REMAINING." The card layout will feature items like the score in an inner row, lives represented by heart icons, and a count of bricks. For mobile, widths are set to 115 for the score, 100 for lives, and 110 for bricks. I also want to adjust the footer's margin and padding. I think I’ll use `ctx.createRadialGradient` for gradient effects on the canvas. Balancing these elements will be essential!**Clarifying game state logic** I'm thinking through the game logic regarding ball counts. If the number of balls starts at zero, the game state shouldn't change until lives are also zero. No need for an old count if there’s none to begin with. If I lose a ball when there's only one left, then I should make sure to adjust the attached ball accordingly. My UI must accurately reflect the loss in lives and remaining balls for a clear gaming experience.**Refining demo and update logic** I'm working on the demo state where if `remaining` equals zero, I need to ensure the `score` stays intact. In the `update()` function, I'll handle various updates like particles and paddle movements. If the game reaches a game over state during `updateBalls`, that's fine, but I need to apply power-ups properly when a demo catches them. The HUD should reflect changes in the ball count, and I’ll ensure everything is ready for the rendering cycle, considering the potential for unexpected behavior like resizing.**Calculating badge radius** I'm working on the badge radius and calculating it with `Math.min(7.8, h * .38, w * .21)`. For mobile height 18, that gives me a value of 6.84, and the width calculation gives me 7.35. So, it looks good! I need to draw a circle without leaving any `ctx.globalAlpha` since I want to save it. For the pips, I'm thinking I could use a loop to fill styles based on conditions!**Clarifying trail methodology** I’m considering how to handle the trail for this bouncing ball. It seems I need to draw an attached trail and use the default `createBall` settings. The `stepBall` method should update the trail after its substeps if the ball is alive. This means the trail needs to work in a `while` loop based on delta time. I'll copy the balls after they drop into the update and step, ensuring there’s never a trail before it. The ball's speed needs to be set to top, and I need to think about how the paddle impacts the bounce effect.