Gemini 3.8 Flash
by GoogleReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 59.1 s
- Cost
- $0.037
- Output tokens
- 9,694
- Spent thinking
- 730 tokens
- Speed
- 170.7 tok/s
- Console errors
- 0
- First token after
- 2.3 s
- First visible text after
- 9.0 s
- Prompt tokens
- 524
- Code size
- 23.4 KB
- Finish reason
- stop
- Served by
- Model id
google/gemini-3.8-flash- Reasoning sent
model default- Max output
- 65,536 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 · 858 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NEON BREAKOUT</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
user-select: none;
-webkit-user-select: none;
}
body {
background: radial-gradient(circle at 50% 30%, #15102a, #07050f 85%);
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
color: #fff;
overflow: hidden;
}
#game-container {
position: relative;
width: min(95vw, 840px);
aspect-ratio: 840 / 620;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.7), 0 0 30px rgba(120, 80, 255, 0.2);
border-radius: 12px;
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1);
background: #090814;
}
canvas {
width: 100%;
height: 100%;
display: block;
cursor: none;
}
.hint-bar {
margin-top: 10px;
font-size: 13px;
letter-spacing: 1px;
color: rgba(255, 255, 255, 0.5);
text-transform: uppercase;
text-align: center;
}
</style>
</head>
<body>
<div id="game-container">
<canvas id="canvas" width="840" height="620"></canvas>
</div>
<div class="hint-bar">Mouse or Left/Right Arrow Keys • P to Pause</div>
<script>
(() => {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const W = canvas.width;
const H = canvas.height;
// Web Audio Synth
let audioCtx = null;
function initAudio() {
if (!audioCtx) {
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (AudioContext) audioCtx = new AudioContext();
}
if (audioCtx && audioCtx.state === 'suspended') {
audioCtx.resume();
}
}
function playTone(freq, type, dur, vol = 0.15, slide = 0) {
if (!audioCtx) return;
try {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
if (slide !== 0) {
osc.frequency.exponentialRampToValueAtTime(Math.max(10, freq + slide), audioCtx.currentTime + dur);
}
gain.gain.setValueAtTime(vol, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + dur);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + dur);
} catch(e) {}
}
const sfx = {
paddleHit: () => playTone(320, 'sine', 0.1, 0.12, 100),
brickHit: () => playTone(440, 'triangle', 0.08, 0.15, -80),
brickBreak: () => playTone(620, 'square', 0.12, 0.15, 200),
powerUp: () => {
playTone(523, 'sine', 0.08, 0.15);
setTimeout(() => playTone(659, 'sine', 0.08, 0.15), 60);
setTimeout(() => playTone(784, 'sine', 0.15, 0.18), 120);
},
loseLife: () => playTone(180, 'sawtooth', 0.35, 0.2, -100),
win: () => {
[400, 500, 600, 800].forEach((f, i) => {
setTimeout(() => playTone(f, 'triangle', 0.2, 0.15), i * 120);
});
}
};
// Game States
const STATES = { PLAYING: 0, GAMEOVER: 1, VICTORY: 2 };
let state = STATES.PLAYING;
let isDemo = true;
let paused = false;
let score = 0;
let lives = 3;
let screenShake = 0;
// Paddle
const defaultPaddleWidth = 110;
const paddle = {
x: W / 2,
y: H - 38,
w: defaultPaddleWidth,
h: 14,
speed: 9,
targetX: W / 2,
wideTimer: 0
};
// Balls
const baseBallSpeed = 6.2;
let balls = [];
function createBall(x, y, vx, vy) {
return {
x, y,
r: 6.5,
vx, vy,
trail: []
};
}
function resetBall() {
balls = [createBall(paddle.x, paddle.y - 12, (Math.random() > 0.5 ? 1 : -1) * 3, -baseBallSpeed)];
}
// Bricks
const BRICK_ROWS = 7;
const BRICK_COLS = 10;
const BRICK_PAD = 8;
const BRICK_W = (W - (BRICK_PAD * (BRICK_COLS + 1))) / BRICK_COLS;
const BRICK_H = 22;
const BRICK_TOP = 65;
// Power-up types: 'wide', 'multi'
let bricks = [];
let particles = [];
let powerUps = [];
const ROW_CONFIG = [
{ hp: 2, color: '#ff2d75', score: 30 }, // Tough row
{ hp: 2, color: '#e040fb', score: 30 }, // Tough row
{ hp: 2, color: '#7c4dff', score: 25 }, // Tough row
{ hp: 1, color: '#00e5ff', score: 15 },
{ hp: 1, color: '#1de9b6', score: 15 },
{ hp: 1, color: '#00e676', score: 10 },
{ hp: 1, color: '#ffea00', score: 10 }
];
function initLevel() {
bricks = [];
powerUps = [];
particles = [];
// Plan special bricks (approx 8-10 with powerups)
for (let r = 0; r < BRICK_ROWS; r++) {
for (let c = 0; c < BRICK_COLS; c++) {
const x = BRICK_PAD + c * (BRICK_W + BRICK_PAD);
const y = BRICK_TOP + r * (BRICK_H + BRICK_PAD);
const cfg = ROW_CONFIG[r];
// Powerup assignment
let ptype = null;
const rand = Math.random();
if (rand < 0.11) ptype = 'wide';
else if (rand < 0.22) ptype = 'multi';
bricks.push({
x, y,
w: BRICK_W,
h: BRICK_H,
maxHp: cfg.hp,
hp: cfg.hp,
color: cfg.color,
score: cfg.score,
powerUp: ptype,
shake: 0
});
}
}
}
function spawnParticles(x, y, color, count = 14) {
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = Math.random() * 4.5 + 1.2;
particles.push({
x, y,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed,
size: Math.random() * 3.5 + 1.5,
color,
alpha: 1,
life: 1
});
}
}
// Keyboard & Mouse Controls
const keys = { left: false, right: false };
window.addEventListener('keydown', (e) => {
initAudio();
if (isDemo && (e.key === ' ' || e.key.startsWith('Arrow') || e.key === 'Enter')) {
startRealGame();
return;
}
if (state !== STATES.PLAYING && (e.key === ' ' || e.key === 'Enter')) {
restartGame();
return;
}
if (e.key === 'p' || e.key === 'P') {
if (state === STATES.PLAYING && !isDemo) paused = !paused;
}
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys.left = true;
if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys.right = true;
});
window.addEventListener('keyup', (e) => {
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys.left = false;
if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys.right = false;
});
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const mouseX = (e.clientX - rect.left) * (W / rect.width);
paddle.targetX = Math.max(paddle.w / 2, Math.min(W - paddle.w / 2, mouseX));
if (isDemo && (e.movementX !== 0 || e.movementY !== 0)) {
// Don't leave demo solely on slight mouse hover, but allow click
}
});
canvas.addEventListener('mousedown', () => {
initAudio();
if (isDemo) {
startRealGame();
} else if (state !== STATES.PLAYING) {
restartGame();
}
});
function startRealGame() {
isDemo = false;
score = 0;
lives = 3;
state = STATES.PLAYING;
paddle.w = defaultPaddleWidth;
paddle.wideTimer = 0;
initLevel();
resetBall();
}
function restartGame() {
startRealGame();
}
// Multi-ball trigger
function triggerMultiBall() {
sfx.powerUp();
const refBall = balls[0] || createBall(paddle.x, paddle.y - 12, 0, -baseBallSpeed);
const speed = Math.hypot(refBall.vx, refBall.vy) || baseBallSpeed;
// Create 2 additional balls with divergent angles
const angles = [-0.45, 0.45];
angles.forEach(angOffset => {
const curAng = Math.atan2(refBall.vy, refBall.vx);
const newAng = curAng + angOffset;
balls.push(createBall(
refBall.x,
refBall.y,
Math.cos(newAng) * speed,
Math.sin(newAng) * speed
));
});
}
// Wide paddle trigger
function triggerWidePaddle() {
sfx.powerUp();
paddle.wideTimer = 600; // 10 seconds at 60fps
paddle.w = defaultPaddleWidth * 1.6;
}
// Ball vs Box Collision with precise edge reflection
function checkBallBoxCollision(ball, box) {
// Find closest point on box
const closestX = Math.max(box.x, Math.min(ball.x, box.x + box.w));
const closestY = Math.max(box.y, Math.min(ball.y, box.y + box.h));
const distX = ball.x - closestX;
const distY = ball.y - closestY;
const distanceSq = distX * distX + distY * distY;
if (distanceSq <= ball.r * ball.r) {
// Collision detected; determine collision normal
const overlapLeft = (ball.x + ball.r) - box.x;
const overlapRight = (box.x + box.w) - (ball.x - ball.r);
const overlapTop = (ball.y + ball.r) - box.y;
const overlapBottom = (box.y + box.h) - (ball.y - ball.r);
const minOverlapX = Math.min(overlapLeft, overlapRight);
const minOverlapY = Math.min(overlapTop, overlapBottom);
if (minOverlapX < minOverlapY) {
ball.vx = (ball.x < box.x + box.w / 2) ? -Math.abs(ball.vx) : Math.abs(ball.vx);
ball.x = (ball.vx < 0) ? box.x - ball.r : box.x + box.w + ball.r;
} else {
ball.vy = (ball.y < box.y + box.h / 2) ? -Math.abs(ball.vy) : Math.abs(ball.vy);
ball.y = (ball.vy < 0) ? box.y - ball.r : box.y + box.h + ball.r;
}
return true;
}
return false;
}
// Update Game
function update() {
if (paused) return;
if (screenShake > 0) screenShake *= 0.88;
if (screenShake < 0.2) screenShake = 0;
// Paddle Power-up timer
if (paddle.wideTimer > 0) {
paddle.wideTimer--;
if (paddle.wideTimer <= 0) {
paddle.w = defaultPaddleWidth;
}
}
// Paddle AI in Demo mode, else Player controls
if (isDemo) {
// Find lowest ball or center
let targetBall = null;
let maxY = -1;
for (const b of balls) {
if (b.y > maxY) {
maxY = b.y;
targetBall = b;
}
}
if (targetBall) {
// Predictive lead
const lead = targetBall.vx * 1.5;
paddle.targetX = targetBall.x + lead;
} else {
paddle.targetX = W / 2;
}
} else {
if (keys.left) paddle.targetX -= paddle.speed;
if (keys.right) paddle.targetX += paddle.speed;
}
paddle.targetX = Math.max(paddle.w / 2, Math.min(W - paddle.w / 2, paddle.targetX));
paddle.x += (paddle.targetX - paddle.x) * 0.35; // smooth chase
if (state !== STATES.PLAYING) return;
// Sub-stepping for ultra-reliable collision detection (anti-tunneling)
const SUBSTEPS = 4;
for (let step = 0; step < SUBSTEPS; step++) {
for (let bi = balls.length - 1; bi >= 0; bi--) {
const b = balls[bi];
b.x += b.vx / SUBSTEPS;
b.y += b.vy / SUBSTEPS;
// Wall collisions
if (b.x - b.r <= 0) {
b.x = b.r;
b.vx = Math.abs(b.vx);
sfx.paddleHit();
} else if (b.x + b.r >= W) {
b.x = W - b.r;
b.vx = -Math.abs(b.vx);
sfx.paddleHit();
}
if (b.y - b.r <= 0) {
b.y = b.r;
b.vy = Math.abs(b.vy);
sfx.paddleHit();
}
// Paddle Collision
const pBox = { x: paddle.x - paddle.w / 2, y: paddle.y, w: paddle.w, h: paddle.h };
if (
b.y + b.r >= pBox.y &&
b.y - b.r <= pBox.y + pBox.h &&
b.x >= pBox.x - b.r &&
b.x <= pBox.x + pBox.w + b.r &&
b.vy > 0
) {
b.y = pBox.y - b.r;
// Calculate hit offset: -1 (left edge) to +1 (right edge)
const hit = (b.x - paddle.x) / (paddle.w / 2);
const clampedHit = Math.max(-0.92, Math.min(0.92, hit));
// Max bounce angle: 72 degrees
const maxAngle = (72 * Math.PI) / 180;
const bounceAngle = clampedHit * maxAngle;
// Ball speed slightly increases with score
const currentSpeed = Math.min(9.5, baseBallSpeed + (score / 400) * 0.4);
b.vx = currentSpeed * Math.sin(bounceAngle);
b.vy = -currentSpeed * Math.cos(bounceAngle);
sfx.paddleHit();
screenShake = 2.5;
// Splash particles from paddle
for (let p = 0; p < 4; p++) {
particles.push({
x: b.x,
y: paddle.y,
vx: (Math.random() - 0.5) * 4,
vy: -Math.random() * 3 - 1,
size: Math.random() * 2.5 + 1,
color: '#00e5ff',
alpha: 0.8,
life: 0.8
});
}
}
// Brick Collisions
for (let i = bricks.length - 1; i >= 0; i--) {
const brk = bricks[i];
if (checkBallBoxCollision(b, brk)) {
brk.hp--;
brk.shake = 5;
screenShake = Math.max(screenShake, 3.5);
if (brk.hp <= 0) {
sfx.brickBreak();
score += brk.score;
spawnParticles(brk.x + brk.w / 2, brk.y + brk.h / 2, brk.color, 16);
// Spawn power-up if attached
if (brk.powerUp) {
powerUps.push({
x: brk.x + brk.w / 2,
y: brk.y + brk.h / 2,
type: brk.powerUp,
vy: 2.2,
rot: 0
});
}
bricks.splice(i, 1);
// Win condition check
if (bricks.length === 0) {
state = STATES.VICTORY;
sfx.win();
}
} else {
sfx.brickHit();
spawnParticles(b.x, b.y, brk.color, 5);
}
break; // One brick collision per substep
}
}
// Floor death check (only on last substep)
if (step === SUBSTEPS - 1 && b.y - b.r > H) {
balls.splice(bi, 1);
}
}
}
// Ball Trails & check death
balls.forEach(b => {
b.trail.push({ x: b.x, y: b.y });
if (b.trail.length > 7) b.trail.shift();
});
if (balls.length === 0) {
lives--;
sfx.loseLife();
screenShake = 8;
if (lives <= 0) {
state = STATES.GAMEOVER;
} else {
resetBall();
paddle.w = defaultPaddleWidth;
paddle.wideTimer = 0;
}
}
// Update PowerUps
for (let i = powerUps.length - 1; i >= 0; i--) {
const p = powerUps[i];
p.y += p.vy;
p.rot += 0.04;
// Catch by paddle
if (
p.y + 12 >= paddle.y &&
p.y - 12 <= paddle.y + paddle.h &&
p.x >= paddle.x - paddle.w / 2 - 8 &&
p.x <= paddle.x + paddle.w / 2 + 8
) {
if (p.type === 'wide') triggerWidePaddle();
if (p.type === 'multi') triggerMultiBall();
spawnParticles(p.x, p.y, p.type === 'wide' ? '#00e5ff' : '#ffea00', 20);
powerUps.splice(i, 1);
continue;
}
// Out of bounds
if (p.y > H + 20) {
powerUps.splice(i, 1);
}
}
// Update particles
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx;
p.y += p.vy;
p.vy += 0.08; // gravity
p.alpha -= 0.024;
if (p.alpha <= 0) particles.splice(i, 1);
}
// Update brick shakes
bricks.forEach(b => {
if (b.shake > 0) b.shake *= 0.8;
if (b.shake < 0.1) b.shake = 0;
});
}
// Draw Functions
function draw() {
ctx.save();
// Screen Shake Offset
if (screenShake > 0) {
const sx = (Math.random() - 0.5) * screenShake * 2;
const sy = (Math.random() - 0.5) * screenShake * 2;
ctx.translate(sx, sy);
}
// Background gradient and subtle grid
ctx.fillStyle = '#0a0818';
ctx.fillRect(0, 0, W, H);
ctx.strokeStyle = 'rgba(255, 255, 255, 0.025)';
ctx.lineWidth = 1;
for (let x = 0; x < W; x += 40) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, H);
ctx.stroke();
}
for (let y = 0; y < H; y += 40) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(W, y);
ctx.stroke();
}
// Bricks
bricks.forEach(b => {
ctx.save();
const ox = (Math.random() - 0.5) * b.shake;
const oy = (Math.random() - 0.5) * b.shake;
ctx.translate(b.x + ox, b.y + oy);
// Brick body
ctx.fillStyle = b.color;
ctx.shadowColor = b.color;
ctx.shadowBlur = b.hp === b.maxHp ? 8 : 2;
// Rounded brick
ctx.beginPath();
ctx.roundRect(0, 0, b.w, b.h, 4);
ctx.fill();
// Top glossy highlight
ctx.shadowBlur = 0;
ctx.fillStyle = 'rgba(255, 255, 255, 0.22)';
ctx.fillRect(2, 2, b.w - 4, 3);
// Cracks for 2-HP brick damaged down to 1
if (b.maxHp === 2 && b.hp === 1) {
ctx.strokeStyle = 'rgba(255, 255, 255, 0.9)';
ctx.lineWidth = 1.8;
ctx.beginPath();
ctx.moveTo(b.w * 0.25, 2);
ctx.lineTo(b.w * 0.4, b.h * 0.55);
ctx.lineTo(b.w * 0.35, b.h - 2);
ctx.moveTo(b.w * 0.4, b.h * 0.55);
ctx.lineTo(b.w * 0.65, b.h * 0.4);
ctx.lineTo(b.w * 0.8, b.h - 2);
ctx.stroke();
}
// Power-up indicator badge inside brick
if (b.powerUp) {
ctx.fillStyle = b.powerUp === 'wide' ? '#00e5ff' : '#ffea00';
ctx.shadowColor = ctx.fillStyle;
ctx.shadowBlur = 6;
ctx.beginPath();
if (b.powerUp === 'wide') {
// Double arrow symbol <->
const cx = b.w / 2, cy = b.h / 2;
ctx.arc(cx, cy, 3.5, 0, Math.PI * 2);
ctx.fill();
} else {
// Multi-dot triangle symbol
const cx = b.w / 2, cy = b.h / 2;
ctx.arc(cx - 5, cy, 2, 0, Math.PI * 2);
ctx.arc(cx, cy - 2, 2, 0, Math.PI * 2);
ctx.arc(cx + 5, cy, 2, 0, Math.PI * 2);
ctx.fill();
}
}
ctx.restore();
});
// Falling Power-ups
powerUps.forEach(p => {
ctx.save();
ctx.translate(p.x, p.y);
ctx.rotate(p.rot);
const color = p.type === 'wide' ? '#00e5ff' : '#ffea00';
ctx.shadowColor = color;
ctx.shadowBlur = 12;
ctx.fillStyle = color;
// Hexagonal capsule
ctx.beginPath();
for (let i = 0; i < 6; i++) {
const ang = (i * Math.PI) / 3;
const px = Math.cos(ang) * 11;
const py = Math.sin(ang) * 11;
if (i === 0) ctx.moveTo(px, py);
else ctx.lineTo(px, py);
}
ctx.closePath();
ctx.fill();
// Icon overlay
ctx.fillStyle = '#0a0818';
ctx.font = 'bold 11px system-ui';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(p.type === 'wide' ? 'W' : '3x', 0, 1);
ctx.restore();
});
// Paddle
ctx.save();
ctx.translate(paddle.x - paddle.w / 2, paddle.y);
const paddleColor = paddle.wideTimer > 0 ? '#00e5ff' : '#a855f7';
ctx.shadowColor = paddleColor;
ctx.shadowBlur = 14;
// Body
const padGrad = ctx.createLinearGradient(0, 0, 0, paddle.h);
padGrad.addColorStop(0, '#ffffff');
padGrad.addColorStop(0.3, paddleColor);
padGrad.addColorStop(1, '#3b0764');
ctx.fillStyle = padGrad;
ctx.beginPath();
ctx.roundRect(0, 0, paddle.w, paddle.h, paddle.h / 2);
ctx.fill();
// Neon Core Line
ctx.fillStyle = '#ffffff';
ctx.shadowBlur = 6;
ctx.fillRect(8, paddle.h / 2 - 1.5, paddle.w - 16, 3);
ctx.restore();
// Balls with neon glowing trails
balls.forEach(b => {
// Trail
for (let i = 0; i < b.trail.length; i++) {
const t = b.trail[i];
const alpha = (i + 1) / (b.trail.length * 2.8);
ctx.fillStyle = `rgba(0, 229, 255, ${alpha})`;
ctx.beginPath();
ctx.arc(t.x, t.y, b.r * (i / b.trail.length), 0, Math.PI * 2);
ctx.fill();
}
// Main Ball
ctx.save();
ctx.shadowColor = '#00e5ff';
ctx.shadowBlur = 12;
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2);
ctx.fill();
// Glowing outer rim
ctx.strokeStyle = '#00e5ff';
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.restore();
});
// Particles
particles.forEach(p => {
ctx.save();
ctx.globalAlpha = Math.max(0, p.alpha);
ctx.fillStyle = p.color;
ctx.shadowColor = p.color;
ctx.shadowBlur = 6;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
});
// HUD Header
ctx.save();
ctx.font = 'bold 16px "Segoe UI", system-ui, sans-serif';
ctx.fillStyle = '#ffffff';
ctx.shadowColor = 'rgba(255, 255, 255, 0.4)';
ctx.shadowBlur = 8;
// Score
ctx.textAlign = 'left';
ctx.fillText(`SCORE: ${score}`, 25, 34);
// Wide timer active badge
if (paddle.wideTimer > 0) {
ctx.fillStyle = '#00e5ff';
ctx.fillText(`WIDE: ${(paddle.wideTimer / 60).toFixed(1)}s`, 180, 34);
}
// Lives as neon hearts
ctx.textAlign = 'right';
ctx.fillStyle = '#ff2d75';
ctx.shadowColor = '#ff2d75';
let lifeStr = '';
for (let i = 0; i < lives; i++) lifeStr += '♥ ';
ctx.fillText(lifeStr, W - 25, 34);
ctx.restore();
// Overlay: Demo Mode Banner
if (isDemo) {
ctx.save();
ctx.fillStyle = 'rgba(9, 8, 20, 0.65)';
ctx.fillRect(0, H - 110, W, 85);
ctx.fillStyle = '#00e5ff';
ctx.shadowColor = '#00e5ff';
ctx.shadowBlur = 14;
ctx.font = '900 24px "Segoe UI", system-ui, sans-serif';
ctx.textAlign = 'center';
ctx.fillText('CLICK OR PRESS ANY KEY TO PLAY', W / 2, H - 65);
ctx.font = '500 13px system-ui';
ctx.fillStyle = 'rgba(255, 255, 255, 0.75)';
ctx.shadowBlur = 0;
ctx.fillText('AUTO-DEMO ACTIVE • CATCH GLOWING POWER-UPS FOR WIDE PADDLE & MULTI-BALL', W / 2, H - 40);
ctx.restore();
}
// Overlay: Pause
if (paused) {
ctx.save();
ctx.fillStyle = 'rgba(7, 5, 15, 0.75)';
ctx.fillRect(0, 0, W, H);
ctx.font = '900 38px "Segoe UI", system-ui';
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
ctx.shadowColor = '#00e5ff';
ctx.shadowBlur = 20;
ctx.fillText('PAUSED', W / 2, H / 2);
ctx.restore();
}
// Overlay: Game Over & Victory
if (state === STATES.GAMEOVER || state === STATES.VICTORY) {
ctx.save();
ctx.fillStyle = 'rgba(5, 4, 12, 0.84)';
ctx.fillRect(0, 0, W, H);
const isWin = state === STATES.VICTORY;
const title = isWin ? 'VICTORY!' : 'GAME OVER';
const color = isWin ? '#00e5ff' : '#ff2d75';
ctx.font = '900 46px "Segoe UI", system-ui';
ctx.fillStyle = color;
ctx.shadowColor = color;
ctx.shadowBlur = 25;
ctx.textAlign = 'center';
ctx.fillText(title, W / 2, H / 2 - 35);
ctx.font = '600 22px system-ui';
ctx.fillStyle = '#ffffff';
ctx.shadowColor = 'rgba(255,255,255,0.4)';
ctx.shadowBlur = 8;
ctx.fillText(`FINAL SCORE: ${score}`, W / 2, H / 2 + 15);
ctx.font = '500 15px system-ui';
ctx.fillStyle = 'rgba(255, 255, 255, 0.75)';
ctx.shadowBlur = 0;
ctx.fillText('CLICK OR PRESS SPACE TO RESTART', W / 2, H / 2 + 65);
ctx.restore();
}
ctx.restore();
}
// Main Loop
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
// Bootstrap game in Demo Mode
initLevel();
resetBall();
loop();
})();
</script>
</body>
</html>Reasoning trace (730 tokens)
**Building Game** I'm focusing on the core mechanics for the Breakout game, aiming for a single HTML file with vanilla JS and canvas. Current efforts are centered on implementing responsive paddle controls, including both mouse and arrow key support, and refining the physics for ball-paddle collision to ensure varied bounce angles based on impact point. **Developing Brick Logic** I'm refining the brick structure, incorporating different hit point values and visually indicating damage. The next step is integrating power-up drops upon brick destruction, focusing on the wider paddle and multi-ball mechanics, alongside ensuring robust collision detection to prevent tunneling and adding particle effects for visual flair. **Refining Game Elements** I'm working on the visual and auditory polish. This involves implementing distinct brick types with visual damage indicators, adding subtle synth sound effects for key game events, and ensuring the game scales responsively to various resolutions. The demo mode AI is also being tuned for smoother paddle movement.