Run details
- Model
- deepseek/deepseek-v4.1-flash
- Route
- Command Code CLI
- Reasoning
- provider default
- Limits
- 60 min · 943,718 output tokens
- Date
- 2 Oct 2026
- Time
- 4 min 13 s
- Size
- 39 kB
- Tokens
- 711,776 in, 42,806 out
- List price
- $0.051 ($0.049 in / $0.375 out per M)
- Paid
- Not billed per run (CLI plan)
Exact prompt
Make a Breakout game I can play right now with the keyboard or the mouse. Give it several levels, a score and some juice. It must be one complete, self-contained HTML file: inline all CSS and JavaScript, and make no network requests of any kind (no CDNs, web fonts or remote images). Save it as index.html in the current directory, then stop. Do not ask questions.
Source
39 kB 1,238 lines Raw
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<title>NEON BREAKOUT</title>
<style>
:root { color-scheme: dark; }
* { box-sizing: border-box; }
html, body {
margin: 0; padding: 0; height: 100%; width: 100%;
overflow: hidden; background: #05060f;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "DejaVu Sans Mono", monospace;
}
body {
display: flex; align-items: center; justify-content: center;
background: radial-gradient(circle at 50% 0%, #131a3a 0%, #05060f 60%);
-webkit-tap-highlight-color: transparent;
user-select: none; -webkit-user-select: none;
}
#stage {
position: relative;
width: min(100vw, calc(100vh * 900 / 680));
height: min(100vh, calc(100vw * 680 / 900));
box-shadow: 0 0 90px rgba(56,189,248,.16), 0 0 200px rgba(168,85,247,.08);
}
canvas { display: block; width: 100%; height: 100%; touch-action: none; cursor: none; }
</style>
</head>
<body>
<div id="stage"><canvas id="game"></canvas></div>
<script>
(function () {
'use strict';
var TAU = Math.PI * 2;
var W = 900, H = 680, TOP = 64;
var STEP = 1 / 120;
var BASE_SPEED = 400;
var MAX_SPEED = 830;
var COLS = 11;
var SIDE = 40, GAP = 6, BH = 26, ROWGAP = 8, BRICK_TOP = 104;
var canvas = document.getElementById('game');
var ctx = canvas.getContext('2d', { alpha: false });
var DPR = 1;
var bg = document.createElement('canvas');
var bgx = bg.getContext('2d');
function clamp(v, a, b) { return v < a ? a : (v > b ? b : v); }
function rand(a, b) { return a + Math.random() * (b - a); }
function rr(c, x, y, w, h, r) {
r = Math.min(r, w / 2, h / 2);
c.beginPath();
c.moveTo(x + r, y);
c.arcTo(x + w, y, x + w, y + h, r);
c.arcTo(x + w, y + h, x, y + h, r);
c.arcTo(x, y + h, x, y, r);
c.arcTo(x, y, x + w, y, r);
c.closePath();
}
/* ------------------------------------------------------------------ audio */
var actx = null, master = null, muted = false;
function audioOn() {
if (actx) { if (actx.state === 'suspended') actx.resume(); return; }
try {
var AC = window.AudioContext || window.webkitAudioContext;
if (!AC) return;
actx = new AC();
master = actx.createGain();
master.gain.value = 0.3;
master.connect(actx.destination);
} catch (e) { actx = null; }
}
function tone(f1, f2, dur, type, vol) {
if (!actx || muted) return;
var t = actx.currentTime;
var o = actx.createOscillator();
var g = actx.createGain();
o.type = type || 'square';
o.frequency.setValueAtTime(f1, t);
if (f2 && f2 !== f1) o.frequency.exponentialRampToValueAtTime(Math.max(20, f2), t + dur);
g.gain.setValueAtTime(0.0001, t);
g.gain.exponentialRampToValueAtTime(vol || 0.25, t + 0.008);
g.gain.exponentialRampToValueAtTime(0.0001, t + dur);
o.connect(g); g.connect(master);
o.start(t); o.stop(t + dur + 0.03);
}
var SFX = {
hit: function (row) { tone(320 + row * 55, 480 + row * 55, 0.06, 'square', 0.16); },
brk: function (row) { tone(520 + row * 60, 180, 0.13, 'triangle', 0.24); },
paddle: function () { tone(180, 300, 0.07, 'sine', 0.24); },
wall: function () { tone(140, 110, 0.05, 'sine', 0.13); },
launch: function () { tone(300, 720, 0.12, 'triangle', 0.2); },
power: function () { tone(520, 1040, 0.16, 'square', 0.18); setTimeout(function () { tone(780, 1560, 0.14, 'square', 0.14); }, 90); },
laser: function () { tone(900, 220, 0.06, 'sawtooth', 0.1); },
lose: function () { tone(320, 60, 0.5, 'sawtooth', 0.24); },
level: function () {
var n = [523, 659, 784, 1047];
for (var i = 0; i < n.length; i++) (function (f, i) { setTimeout(function () { tone(f, f, 0.18, 'triangle', 0.2); }, i * 100); })(n[i], i);
},
over: function () {
var n = [440, 349, 262, 175];
for (var i = 0; i < n.length; i++) (function (f, i) { setTimeout(function () { tone(f, f * 0.9, 0.28, 'sawtooth', 0.18); }, i * 150); })(n[i], i);
}
};
/* ------------------------------------------------------------------ levels */
var LEVELS = [
{ name: 'WARM UP', rows: [
'11111111111',
'11111111111',
'1.1.1.1.1.1',
'11111111111'
] },
{ name: 'CHECKERED', rows: [
'2.2.2.2.2.2',
'.1.1.1.1.1.',
'2.2.2.2.2.2',
'.1.1.1.1.1.',
'2.2.2.2.2.2'
] },
{ name: 'PYRAMID', rows: [
'.....3.....',
'....232....',
'...12221...',
'..1122211..',
'.111222111.',
'11112222111'
] },
{ name: 'FORTRESS', rows: [
'33333333333',
'3.........3',
'3.2222222.3',
'3.2.....2.3',
'3.2222222.3',
'3.........3',
'33333333333'
] },
{ name: 'DIAMOND', rows: [
'.....2.....',
'....121....',
'...12121...',
'..1212121..',
'.121212121.',
'..1212121..',
'...12121...',
'....121....',
'.....1.....'
] },
{ name: 'OVERDRIVE', rows: [
'3.3.3.3.3.3',
'23232323232',
'3.3.3.3.3.3',
'23232323232',
'3.3.3.3.3.3',
'23232323232'
] }
];
var BRICK = {
1: { a: '#22d3ee', b: '#0e7490', glow: '#22d3ee', pts: 60 },
2: { a: '#a855f7', b: '#5b21b6', glow: '#a855f7', pts: 130 },
3: { a: '#fb7185', b: '#9f1239', glow: '#fb7185', pts: 220 }
};
var PU = {
W: { color: '#38bdf8', label: 'W', name: 'WIDE PADDLE' },
M: { color: '#34d399', label: 'M', name: 'MULTI BALL' },
S: { color: '#a78bfa', label: 'S', name: 'SLOW MOTION' },
L: { color: '#fbbf24', label: 'L', name: 'LASER' },
P: { color: '#f472b6', label: '+', name: 'EXTRA LIFE' }
};
/* ------------------------------------------------------------------ state */
var state = {
mode: 'menu', prevMode: 'play',
score: 0, best: 0, lives: 3, level: 0,
combo: 0, time: 0,
shake: 0, laserTime: 0, fireCd: 0,
msg: '', msgTimer: 0, levelTimer: 0
};
var paddle = {
x: W / 2, y: H - 54, w: 122, baseW: 122, wTarget: 122, wTime: 0,
h: 16, vx: 0, squash: 0, glow: 0
};
var balls = [], bricks = [], particles = [], powerups = [], bullets = [], floaters = [];
var input = { left: false, right: false, pointerX: null, usePointer: false, fire: false };
/* ------------------------------------------------------------------ setup */
function buildBG() {
var g = bgx.createLinearGradient(0, 0, 0, H);
g.addColorStop(0, '#0b1026');
g.addColorStop(0.45, '#070a1a');
g.addColorStop(1, '#03040c');
bgx.fillStyle = g;
bgx.fillRect(0, 0, W, H);
bgx.strokeStyle = 'rgba(96,150,255,0.055)';
bgx.lineWidth = 1;
var x, y;
for (x = 0; x <= W; x += 45) { bgx.beginPath(); bgx.moveTo(x + 0.5, 0); bgx.lineTo(x + 0.5, H); bgx.stroke(); }
for (y = 0; y <= H; y += 45) { bgx.beginPath(); bgx.moveTo(0, y + 0.5); bgx.lineTo(W, y + 0.5); bgx.stroke(); }
var v = bgx.createRadialGradient(W / 2, H / 2, H * 0.22, W / 2, H / 2, H * 0.8);
v.addColorStop(0, 'rgba(0,0,0,0)');
v.addColorStop(1, 'rgba(0,0,0,0.7)');
bgx.fillStyle = v;
bgx.fillRect(0, 0, W, H);
}
function resize() {
DPR = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = Math.round(W * DPR);
canvas.height = Math.round(H * DPR);
bg.width = Math.round(W * DPR);
bg.height = Math.round(H * DPR);
bgx.setTransform(DPR, 0, 0, DPR, 0, 0);
buildBG();
}
var glowGrad = null;
function makeGlowGrad() {
var g = ctx.createRadialGradient(0, 0, 0, 0, 0, 360);
g.addColorStop(0, 'rgba(56,189,248,0.20)');
g.addColorStop(0.45, 'rgba(168,85,247,0.10)');
g.addColorStop(1, 'rgba(0,0,0,0)');
return g;
}
/* ------------------------------------------------------------------ level */
function buildLevel(idx) {
bricks.length = 0;
var lvl = LEVELS[Math.min(idx, LEVELS.length - 1)];
var bw = (W - SIDE * 2 - GAP * (COLS - 1)) / COLS;
for (var r = 0; r < lvl.rows.length; r++) {
var s = (lvl.rows[r] + '...........').slice(0, COLS);
for (var c = 0; c < COLS; c++) {
var ch = s.charAt(c);
if (ch === '.' || ch === ' ') continue;
var hp = parseInt(ch, 10) || 1;
bricks.push({
x: SIDE + c * (bw + GAP),
y: BRICK_TOP + r * (BH + ROWGAP),
w: bw, h: BH,
hp: hp, maxHp: hp,
alive: true, row: r, col: c,
flash: 0, pop: 0
});
}
}
}
function makeBall(stuck) {
return { x: paddle.x, y: paddle.y - 9, r: 8, vx: 0, vy: 0, stuck: !!stuck, offset: 0, trail: [] };
}
function resetBall() {
balls.length = 0;
balls.push(makeBall(true));
bullets.length = 0;
}
function startLevel(i) {
state.level = i;
buildLevel(i);
paddle.w = paddle.baseW;
paddle.wTarget = paddle.baseW;
paddle.wTime = 0;
paddle.x = W / 2;
state.laserTime = 0;
state.combo = 0;
resetBall();
state.mode = 'serve';
state.msg = 'LEVEL ' + (i + 1) + ' \u2014 ' + LEVELS[i].name;
state.msgTimer = 2.4;
}
function startGame() {
state.score = 0;
state.lives = 3;
state.combo = 0;
particles.length = 0;
powerups.length = 0;
floaters.length = 0;
startLevel(0);
}
function victory() {
state.mode = 'victory';
state.score += 2500 + state.lives * 500;
saveBest();
SFX.level();
confetti();
}
function gameOver() {
state.mode = 'gameover';
saveBest();
SFX.over();
}
function saveBest() {
if (state.score > state.best) {
state.best = state.score;
try { localStorage.setItem('neon-breakout-best', String(state.best)); } catch (e) {}
}
}
function comboMult() { return 1 + Math.min(state.combo, 25) * 0.12; }
/* ------------------------------------------------------------------ fx */
function addShake(a) { state.shake = Math.min(26, state.shake + a); }
function spawnParticles(x, y, n, color, opts) {
opts = opts || {};
var spd = opts.speed || 200;
var life = opts.life || 0.6;
var size = opts.size || 4;
for (var i = 0; i < n; i++) {
var a = opts.dir !== undefined ? opts.dir + rand(-0.9, 0.9) : rand(0, TAU);
var s = rand(spd * 0.25, spd);
particles.push({
x: x, y: y,
vx: Math.cos(a) * s,
vy: Math.sin(a) * s,
life: rand(life * 0.55, life),
max: life,
size: rand(size * 0.55, size * 1.4),
color: color,
grav: opts.grav !== undefined ? opts.grav : 420,
rot: rand(0, TAU),
spin: rand(-9, 9)
});
}
}
function addFloater(x, y, text, color, size) {
floaters.push({ x: x, y: y, vy: -60, life: 0.9, max: 0.9, text: text, color: color, size: size || 18 });
}
function confetti() {
var cols = ['#38bdf8', '#a855f7', '#fb7185', '#fbbf24', '#34d399'];
for (var i = 0; i < 160; i++) {
particles.push({
x: rand(0, W), y: rand(-120, 120),
vx: rand(-90, 90), vy: rand(60, 260),
life: rand(1.6, 3), max: 3,
size: rand(3, 7),
color: cols[(Math.random() * cols.length) | 0],
grav: 60, rot: rand(0, TAU), spin: rand(-12, 12)
});
}
}
/* ------------------------------------------------------------------ input */
function pointerToX(clientX) {
var rect = canvas.getBoundingClientRect();
return clamp((clientX - rect.left) / rect.width * W, 0, W);
}
function primaryAction() {
audioOn();
if (state.mode === 'menu') startGame();
else if (state.mode === 'serve') launch();
else if (state.mode === 'paused') resume();
else if (state.mode === 'gameover' || state.mode === 'victory') startGame();
else if (state.mode === 'levelclear') { state.levelTimer = 0.01; }
}
function launch() {
var sp = BASE_SPEED + state.level * 22;
for (var i = 0; i < balls.length; i++) {
var b = balls[i];
if (b.stuck) {
b.stuck = false;
var a = -Math.PI / 2 + rand(-0.4, 0.4);
b.vx = Math.cos(a) * sp;
b.vy = Math.sin(a) * sp;
}
}
state.mode = 'play';
state.msg = '';
SFX.launch();
}
function pause() {
if (state.mode === 'play' || state.mode === 'serve') {
state.prevMode = state.mode;
state.mode = 'paused';
input.fire = false;
}
}
function resume() {
if (state.mode === 'paused') state.mode = state.prevMode;
}
window.addEventListener('keydown', function (e) {
var k = e.code;
if (k === 'ArrowLeft' || k === 'ArrowRight' || k === 'Space' || k === 'ArrowUp' || k === 'ArrowDown') e.preventDefault();
audioOn();
if (k === 'ArrowLeft' || k === 'KeyA') { input.left = true; input.usePointer = false; }
if (k === 'ArrowRight' || k === 'KeyD') { input.right = true; input.usePointer = false; }
if (k === 'Space') {
input.fire = true;
if (state.mode === 'paused') resume();
else if (state.mode === 'play') { /* fire handled in step */ }
else primaryAction();
}
if (k === 'KeyP' || k === 'Escape') { if (state.mode === 'paused') resume(); else pause(); }
if (k === 'KeyM') { muted = !muted; }
if (k === 'KeyR') { if (state.mode === 'gameover' || state.mode === 'victory' || state.mode === 'paused') startGame(); }
});
window.addEventListener('keyup', function (e) {
var k = e.code;
if (k === 'ArrowLeft' || k === 'KeyA') input.left = false;
if (k === 'ArrowRight' || k === 'KeyD') input.right = false;
if (k === 'Space') input.fire = false;
});
canvas.addEventListener('mousemove', function (e) {
input.pointerX = pointerToX(e.clientX);
input.usePointer = true;
});
canvas.addEventListener('mousedown', function (e) {
e.preventDefault();
input.pointerX = pointerToX(e.clientX);
input.usePointer = true;
audioOn();
if (state.mode === 'play') input.fire = true;
else primaryAction();
});
window.addEventListener('mouseup', function () { input.fire = false; });
canvas.addEventListener('touchstart', function (e) {
e.preventDefault();
var t = e.changedTouches[0];
input.pointerX = pointerToX(t.clientX);
input.usePointer = true;
audioOn();
if (state.mode === 'play') input.fire = true;
else primaryAction();
}, { passive: false });
canvas.addEventListener('touchmove', function (e) {
e.preventDefault();
var t = e.changedTouches[0];
input.pointerX = pointerToX(t.clientX);
input.usePointer = true;
}, { passive: false });
canvas.addEventListener('touchend', function () { input.fire = false; });
window.addEventListener('blur', function () {
input.left = input.right = input.fire = false;
if (state.mode === 'play' || state.mode === 'serve') pause();
});
window.addEventListener('resize', resize);
/* ------------------------------------------------------------------ brick hit */
function damageBrick(br, dmg) {
br.hp -= dmg;
br.flash = 1;
br.pop = 1;
if (br.hp > 0) {
spawnParticles(br.x + br.w / 2, br.y + br.h / 2, 5, BRICK[br.maxHp].a, { speed: 170, life: 0.4, size: 3 });
addShake(1.6);
SFX.hit(br.row);
state.score += 10;
return;
}
br.alive = false;
state.combo++;
var mult = comboMult();
var pts = Math.round(BRICK[br.maxHp].pts * mult);
state.score += pts;
addFloater(br.x + br.w / 2, br.y + br.h / 2, '+' + pts, BRICK[br.maxHp].a, 16);
spawnParticles(br.x + br.w / 2, br.y + br.h / 2, 18, BRICK[br.maxHp].a, { speed: 300, life: 0.75, size: 5 });
spawnParticles(br.x + br.w / 2, br.y + br.h / 2, 6, '#ffffff', { speed: 220, life: 0.4, size: 3 });
addShake(3.6);
SFX.brk(br.row);
maybeDrop(br);
checkLevelClear();
}
function checkLevelClear() {
for (var i = 0; i < bricks.length; i++) if (bricks[i].alive) return;
if (state.mode === 'levelclear' || state.mode === 'gameover' || state.mode === 'victory') return;
state.mode = 'levelclear';
state.levelTimer = 2.4;
input.fire = false;
var bonus = 500 + state.level * 250 + state.lives * 100;
state.score += bonus;
addFloater(W / 2, H / 2 + 40, '+' + bonus + ' CLEAR BONUS', '#fde047', 24);
addShake(10);
SFX.level();
for (var j = 0; j < bricks.length; j++) {
spawnParticles(bricks[j].x + bricks[j].w / 2, bricks[j].y + bricks[j].h / 2, 3, '#fbbf24', { speed: 260, life: 0.9, size: 4 });
}
}
function maybeDrop(br) {
if (Math.random() > 0.17) return;
var pool = ['W', 'W', 'M', 'M', 'S', 'S', 'L', 'P'];
var t = pool[(Math.random() * pool.length) | 0];
powerups.push({
x: br.x + br.w / 2, y: br.y + br.h / 2,
vy: 130, rot: 0, t: t, size: 34, life: 14
});
}
function applyPowerup(t) {
var info = PU[t];
addFloater(paddle.x, paddle.y - 30, info.name, info.color, 20);
SFX.power();
spawnParticles(paddle.x, paddle.y, 22, info.color, { speed: 280, life: 0.7, size: 4 });
addShake(5);
if (t === 'W') {
paddle.wTarget = Math.min(230, paddle.wTarget + 48);
paddle.wTime = 16;
} else if (t === 'M') {
multiball();
} else if (t === 'S') {
for (var i = 0; i < balls.length; i++) {
var b = balls[i];
if (b.stuck) continue;
var sp = Math.hypot(b.vx, b.vy);
var ns = Math.max(250, sp * 0.76);
var k = ns / (sp || 1);
b.vx *= k; b.vy *= k;
}
} else if (t === 'L') {
state.laserTime = 12;
} else if (t === 'P') {
state.lives = Math.min(6, state.lives + 1);
}
}
function multiball() {
var src = balls.slice(0, 3);
for (var i = 0; i < src.length; i++) {
var b = src[i];
var baseA = b.stuck ? -Math.PI / 2 : Math.atan2(b.vy, b.vx);
var sp = b.stuck ? (BASE_SPEED + state.level * 22) : Math.hypot(b.vx, b.vy);
for (var k = 0; k < 2; k++) {
if (balls.length >= 6) break;
var a = baseA + (k === 0 ? 0.45 : -0.45);
balls.push({
x: b.x, y: b.y, r: b.r,
vx: Math.cos(a) * sp, vy: Math.sin(a) * sp,
stuck: false, offset: 0, trail: []
});
}
}
if (balls.length > 1) { /* keep serving if still stuck elsewhere */ }
}
function loseLife() {
state.lives--;
state.combo = 0;
state.shake = 22;
state.laserTime = 0;
SFX.lose();
spawnParticles(clamp(paddle.x, 20, W - 20), H - 30, 30, '#fb7185', { speed: 340, life: 0.9, size: 5, grav: 260 });
if (state.lives <= 0) { gameOver(); return; }
resetBall();
state.mode = 'serve';
state.msg = '';
}
/* ------------------------------------------------------------------ physics */
function updatePaddle(h) {
var move = (input.right ? 1 : 0) - (input.left ? 1 : 0);
var prevX = paddle.x;
if (move !== 0) {
paddle.x += move * 760 * h;
} else if (input.usePointer && input.pointerX != null) {
var d = input.pointerX - paddle.x;
paddle.x += d * Math.min(1, 17 * h);
}
var lim = W - paddle.w / 2 - 2;
paddle.x = clamp(paddle.x, paddle.w / 2 + 2, lim);
paddle.vx = (paddle.x - prevX) / h;
if (paddle.wTime > 0) {
paddle.wTime -= h;
if (paddle.wTime <= 0) { paddle.wTime = 0; paddle.wTarget = paddle.baseW; }
}
paddle.w += (paddle.wTarget - paddle.w) * Math.min(1, 14 * h);
if (state.laserTime > 0) {
state.laserTime = Math.max(0, state.laserTime - h);
state.fireCd -= h;
if (input.fire && state.fireCd <= 0) {
state.fireCd = 0.17;
bullets.push({ x: paddle.x - paddle.w / 2 + 9, y: paddle.y - 4, vy: -980 });
bullets.push({ x: paddle.x + paddle.w / 2 - 9, y: paddle.y - 4, vy: -980 });
SFX.laser();
}
} else {
state.fireCd = 0;
}
}
function hitWall() { SFX.wall(); addShake(0.8); }
function collidePaddle(b) {
if (b.vy <= 0) return;
var px = paddle.x - paddle.w / 2, pw = paddle.w;
if (b.y + b.r < paddle.y || b.y - b.r > paddle.y + paddle.h) return;
if (b.x + b.r < px || b.x - b.r > px + pw) return;
var rel = clamp((b.x - paddle.x) / (pw / 2), -1, 1);
var ang = rel * (Math.PI / 3);
var sp = clamp(Math.hypot(b.vx, b.vy), 240, MAX_SPEED);
var vx = Math.sin(ang) * sp + paddle.vx * 0.16;
var minVx = sp * 0.11;
if (vx > -minVx && vx < minVx) {
var sgn = b.vx !== 0 ? (b.vx > 0 ? 1 : -1) : (Math.random() < 0.5 ? -1 : 1);
vx = sgn * minVx;
}
vx = clamp(vx, -0.92 * sp, 0.92 * sp);
b.vx = vx;
b.vy = -Math.sqrt(Math.max(1, sp * sp - vx * vx));
b.y = paddle.y - b.r - 0.5;
paddle.squash = 1;
paddle.glow = 1;
if (state.combo > 2) addFloater(paddle.x, paddle.y - 34, 'COMBO LOST', 'rgba(255,255,255,0.5)', 14);
state.combo = 0;
spawnParticles(b.x, paddle.y, 9, '#67e8f9', { speed: 240, life: 0.4, size: 4, grav: 300, dir: -Math.PI / 2 });
addShake(2.4);
SFX.paddle();
}
function collideBricks(b) {
for (var i = 0; i < bricks.length; i++) {
var br = bricks[i];
if (!br.alive) continue;
var nx = clamp(b.x, br.x, br.x + br.w);
var ny = clamp(b.y, br.y, br.y + br.h);
var dx = b.x - nx, dy = b.y - ny;
if (dx * dx + dy * dy > b.r * b.r) continue;
var bcx = br.x + br.w / 2, bcy = br.y + br.h / 2;
var ox = (br.w / 2 + b.r) - Math.abs(b.x - bcx);
var oy = (br.h / 2 + b.r) - Math.abs(b.y - bcy);
if (ox < oy) {
b.x += (b.x < bcx ? -ox : ox);
b.vx = (b.x < bcx ? -Math.abs(b.vx) : Math.abs(b.vx));
} else {
b.y += (b.y < bcy ? -oy : oy);
b.vy = (b.y < bcy ? -Math.abs(b.vy) : Math.abs(b.vy));
}
damageBrick(br, 1);
return;
}
}
function stepPhysics(h) {
updatePaddle(h);
for (var i = balls.length - 1; i >= 0; i--) {
var b = balls[i];
if (b.stuck) {
b.x = paddle.x + b.offset;
b.y = paddle.y - b.r - 1;
continue;
}
b.x += b.vx * h;
b.y += b.vy * h;
b.trail.push({ x: b.x, y: b.y });
if (b.trail.length > 14) b.trail.shift();
if (b.x - b.r < 0) { b.x = b.r; b.vx = Math.abs(b.vx); hitWall(); }
else if (b.x + b.r > W) { b.x = W - b.r; b.vx = -Math.abs(b.vx); hitWall(); }
if (b.y - b.r < TOP) { b.y = TOP + b.r; b.vy = Math.abs(b.vy); hitWall(); }
collidePaddle(b);
collideBricks(b);
if (b.y - b.r > H) { balls.splice(i, 1); }
}
// powerups
for (var p = powerups.length - 1; p >= 0; p--) {
var pu = powerups[p];
pu.y += pu.vy * h;
pu.rot += h * 2.2;
pu.life -= h;
var px = paddle.x - paddle.w / 2, pw = paddle.w;
if (pu.y + pu.size / 2 > paddle.y && pu.y - pu.size / 2 < paddle.y + paddle.h &&
pu.x + pu.size / 2 > px && pu.x - pu.size / 2 < px + pw) {
applyPowerup(pu.t);
powerups.splice(p, 1);
continue;
}
if (pu.y - pu.size > H || pu.life <= 0) powerups.splice(p, 1);
}
// bullets
for (var k = bullets.length - 1; k >= 0; k--) {
var bl = bullets[k];
bl.y += bl.vy * h;
if (bl.y < TOP) { bullets.splice(k, 1); continue; }
var hit = false;
for (var m = 0; m < bricks.length; m++) {
var bb = bricks[m];
if (!bb.alive) continue;
if (bl.x > bb.x && bl.x < bb.x + bb.w && bl.y < bb.y + bb.h && bl.y > bb.y) {
damageBrick(bb, 1);
hit = true;
break;
}
}
if (hit) bullets.splice(k, 1);
}
if (state.mode === 'play' && balls.length === 0) loseLife();
}
/* ------------------------------------------------------------------ update */
function update(dt) {
state.time += dt;
paddle.squash = Math.max(0, paddle.squash - dt * 4.2);
paddle.glow = Math.max(0, paddle.glow - dt * 3);
state.shake = Math.max(0, state.shake - dt * 46);
if (state.msgTimer > 0) state.msgTimer -= dt;
var i, k;
for (i = particles.length - 1; i >= 0; i--) {
var pt = particles[i];
pt.life -= dt;
if (pt.life <= 0) { particles.splice(i, 1); continue; }
var damp = Math.max(0, 1 - 2.0 * dt);
pt.vx *= damp; pt.vy *= damp;
pt.vy += pt.grav * dt;
pt.x += pt.vx * dt;
pt.y += pt.vy * dt;
pt.rot += pt.spin * dt;
}
for (i = floaters.length - 1; i >= 0; i--) {
var f = floaters[i];
f.life -= dt;
if (f.life <= 0) { floaters.splice(i, 1); continue; }
f.y += f.vy * dt;
f.vy *= Math.max(0, 1 - 1.6 * dt);
}
for (i = 0; i < bricks.length; i++) {
var br = bricks[i];
if (br.flash > 0) br.flash = Math.max(0, br.flash - dt * 6);
if (br.pop > 0) br.pop = Math.max(0, br.pop - dt * 5);
}
if (state.mode === 'menu') {
if (Math.random() < dt * 8) {
spawnParticles(rand(0, W), TOP + rand(0, 300), 1, ['#38bdf8', '#a855f7', '#fb7185'][(Math.random() * 3) | 0],
{ speed: 40, life: 2.2, size: 3, grav: -14 });
}
return;
}
if (state.mode === 'serve' || state.mode === 'play' || state.mode === 'levelclear') {
var acc = state.acc || 0;
acc += dt;
var guard = 0;
while (acc >= STEP && guard < 10) { stepPhysics(STEP); acc -= STEP; guard++; }
if (acc > STEP) acc = 0;
state.acc = acc;
}
if (state.mode === 'levelclear') {
state.levelTimer -= dt;
// keep balls alive while transitioning
if (state.levelTimer <= 0) {
var next = state.level + 1;
if (next >= LEVELS.length) victory();
else startLevel(next);
}
}
}
/* ------------------------------------------------------------------ render */
function drawGlow() {
if (!glowGrad) glowGrad = makeGlowGrad();
var t = state.time;
ctx.save();
ctx.globalCompositeOperation = 'lighter';
ctx.globalAlpha = 0.9;
ctx.translate(W / 2 + Math.sin(t * 0.34) * 150, TOP + 170 + Math.cos(t * 0.26) * 70);
ctx.fillStyle = glowGrad;
ctx.beginPath(); ctx.arc(0, 0, 360, 0, TAU); ctx.fill();
ctx.restore();
}
function drawWalls() {
var pulse = 0.55 + Math.sin(state.time * 3) * 0.1 + Math.min(state.combo, 12) * 0.03;
ctx.save();
ctx.shadowColor = '#38bdf8';
ctx.shadowBlur = 16;
ctx.fillStyle = 'rgba(56,189,248,' + pulse.toFixed(3) + ')';
ctx.fillRect(0, TOP - 3, W, 3);
ctx.fillRect(0, TOP, 3, H - TOP);
ctx.fillRect(W - 3, TOP, 3, H - TOP);
ctx.restore();
var off = (state.time * 60) % 24;
ctx.save();
ctx.globalAlpha = 0.22;
ctx.fillStyle = '#67e8f9';
for (var x = -off; x < W; x += 24) ctx.fillRect(x, TOP - 6, 10, 2);
ctx.restore();
}
function drawBricks() {
for (var i = 0; i < bricks.length; i++) {
var b = bricks[i];
if (!b.alive) continue;
var c = BRICK[b.hp] || BRICK[1];
var e = b.pop * 3;
var x = b.x - e, y = b.y - e, w = b.w + e * 2, h = b.h + e * 2;
ctx.save();
ctx.shadowColor = c.glow;
ctx.shadowBlur = 10 + b.flash * 18;
rr(ctx, x, y, w, h, 6);
ctx.fillStyle = c.b;
ctx.fill();
ctx.restore();
ctx.save();
rr(ctx, x + 2, y + 2, w - 4, h - 6, 4);
ctx.fillStyle = c.a;
ctx.fill();
ctx.restore();
ctx.save();
rr(ctx, x + 4, y + 3, w - 8, (h - 6) * 0.35, 3);
ctx.fillStyle = 'rgba(255,255,255,0.25)';
ctx.fill();
ctx.restore();
if (b.hp < b.maxHp) {
ctx.save();
ctx.globalAlpha = 0.4;
ctx.strokeStyle = '#04060f';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(x + w * 0.25, y + 2);
ctx.lineTo(x + w * 0.42, y + h * 0.5);
ctx.lineTo(x + w * 0.3, y + h - 2);
ctx.moveTo(x + w * 0.68, y + 2);
ctx.lineTo(x + w * 0.55, y + h * 0.45);
ctx.lineTo(x + w * 0.75, y + h - 2);
ctx.stroke();
ctx.restore();
}
if (b.flash > 0) {
ctx.save();
ctx.globalAlpha = b.flash * 0.7;
rr(ctx, x, y, w, h, 6);
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.restore();
}
}
}
function drawPaddle() {
var sq = paddle.squash;
var w = paddle.w * (1 + sq * 0.08);
var h = paddle.h * (1 - sq * 0.3);
var x = paddle.x - w / 2;
var y = paddle.y + (paddle.h - h) / 2;
ctx.save();
ctx.shadowColor = '#22d3ee';
ctx.shadowBlur = 18 + paddle.glow * 26;
rr(ctx, x, y, w, h, h / 2);
var g = ctx.createLinearGradient(x, y, x, y + h);
g.addColorStop(0, '#a5f3fc');
g.addColorStop(0.5, '#22d3ee');
g.addColorStop(1, '#0e7490');
ctx.fillStyle = g;
ctx.fill();
ctx.restore();
ctx.save();
ctx.globalAlpha = 0.55;
rr(ctx, x + 5, y + 2, w - 10, h * 0.32, h * 0.2);
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.restore();
if (state.laserTime > 0) {
var blink = state.laserTime < 3 && Math.floor(state.time * 8) % 2 === 0;
ctx.save();
ctx.shadowColor = '#fbbf24';
ctx.shadowBlur = 14;
ctx.fillStyle = blink ? 'rgba(251,191,36,0.4)' : '#fbbf24';
rr(ctx, x + 1, y - 9, 6, 10, 2); ctx.fill();
rr(ctx, x + w - 7, y - 9, 6, 10, 2); ctx.fill();
ctx.restore();
}
}
function drawBalls() {
ctx.save();
ctx.globalCompositeOperation = 'lighter';
for (var i = 0; i < balls.length; i++) {
var b = balls[i];
for (var j = 0; j < b.trail.length; j++) {
var p = b.trail[j];
var f = (j + 1) / b.trail.length;
ctx.globalAlpha = f * f * 0.4;
ctx.fillStyle = '#38bdf8';
ctx.beginPath();
ctx.arc(p.x, p.y, b.r * f * 0.95, 0, TAU);
ctx.fill();
}
}
ctx.restore();
for (var k = 0; k < balls.length; k++) {
var ball = balls[k];
ctx.save();
ctx.shadowColor = '#67e8f9';
ctx.shadowBlur = 24;
ctx.fillStyle = '#e0fbff';
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, TAU);
ctx.fill();
ctx.restore();
ctx.beginPath();
ctx.arc(ball.x - ball.r * 0.2, ball.y - ball.r * 0.2, ball.r * 0.45, 0, TAU);
ctx.fillStyle = '#ffffff';
ctx.fill();
}
}
function drawPowerups() {
for (var i = 0; i < powerups.length; i++) {
var pu = powerups[i];
var info = PU[pu.t];
var s = pu.size * (1 + Math.sin(state.time * 7 + i) * 0.06);
var blink = pu.life < 3 && Math.floor(state.time * 8) % 2 === 0;
ctx.save();
ctx.globalAlpha = blink ? 0.35 : 1;
ctx.translate(pu.x, pu.y);
ctx.rotate(Math.sin(state.time * 2.4 + i) * 0.22);
ctx.shadowColor = info.color;
ctx.shadowBlur = 20;
rr(ctx, -s / 2, -s / 2, s, s, 8);
ctx.fillStyle = '#05070f';
ctx.fill();
ctx.strokeStyle = info.color;
ctx.lineWidth = 3;
ctx.stroke();
ctx.shadowBlur = 0;
ctx.fillStyle = info.color;
ctx.font = '900 ' + Math.round(s * 0.6) + 'px ui-monospace, Menlo, monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(info.label, 0, 1);
ctx.restore();
}
}
function drawBullets() {
ctx.save();
ctx.shadowColor = '#fbbf24';
ctx.shadowBlur = 14;
ctx.fillStyle = '#fde68a';
for (var i = 0; i < bullets.length; i++) {
var bl = bullets[i];
rr(ctx, bl.x - 2, bl.y - 14, 4, 18, 2);
ctx.fill();
}
ctx.restore();
}
function drawParticles() {
ctx.save();
ctx.globalCompositeOperation = 'lighter';
for (var i = 0; i < particles.length; i++) {
var p = particles[i];
var a = Math.max(0, p.life / p.max);
ctx.globalAlpha = a;
ctx.fillStyle = p.color;
ctx.save();
ctx.translate(p.x, p.y);
ctx.rotate(p.rot);
ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size * 0.7);
ctx.restore();
}
ctx.restore();
}
function drawFloaters() {
ctx.save();
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
for (var i = 0; i < floaters.length; i++) {
var f = floaters[i];
var a = Math.max(0, f.life / f.max);
ctx.globalAlpha = a;
ctx.font = '900 ' + f.size + 'px ui-monospace, Menlo, monospace';
ctx.shadowColor = f.color;
ctx.shadowBlur = 12;
ctx.fillStyle = f.color;
ctx.fillText(f.text, f.x, f.y);
}
ctx.restore();
}
function drawHUD() {
ctx.save();
ctx.textBaseline = 'middle';
ctx.textAlign = 'left';
ctx.font = '700 11px ui-monospace, Menlo, monospace';
ctx.fillStyle = 'rgba(148,197,255,0.6)';
ctx.fillText('S C O R E', 26, 20);
ctx.font = '900 28px ui-monospace, Menlo, monospace';
ctx.fillStyle = '#e0f2ff';
ctx.shadowColor = '#38bdf8';
ctx.shadowBlur = 14;
ctx.fillText(String(state.score), 26, 44);
ctx.shadowBlur = 0;
ctx.textAlign = 'center';
ctx.font = '700 11px ui-monospace, Menlo, monospace';
ctx.fillStyle = 'rgba(148,197,255,0.6)';
ctx.fillText('LEVEL', W / 2, 18);
ctx.font = '900 20px ui-monospace, Menlo, monospace';
ctx.fillStyle = '#c4b5fd';
ctx.shadowColor = '#a855f7';
ctx.shadowBlur = 12;
ctx.fillText((state.level + 1) + ' ' + LEVELS[Math.min(state.level, LEVELS.length - 1)].name, W / 2, 42);
ctx.shadowBlur = 0;
var lx = W - 26;
ctx.textAlign = 'right';
ctx.font = '700 11px ui-monospace, Menlo, monospace';
ctx.fillStyle = 'rgba(148,197,255,0.6)';
ctx.fillText('L I V E S', lx, 20);
for (var i = 0; i < state.lives; i++) {
ctx.save();
ctx.shadowColor = '#22d3ee';
ctx.shadowBlur = 10;
rr(ctx, lx - 18 - i * 24, 36, 18, 8, 4);
ctx.fillStyle = '#22d3ee';
ctx.fill();
ctx.restore();
}
if (state.combo >= 3) {
var m = comboMult();
ctx.textAlign = 'center';
ctx.font = '900 18px ui-monospace, Menlo, monospace';
ctx.globalAlpha = 0.85;
ctx.fillStyle = '#fde047';
ctx.shadowColor = '#fbbf24';
ctx.shadowBlur = 14;
ctx.fillText('COMBO x' + m.toFixed(1), W / 2, H - 22);
ctx.shadowBlur = 0;
}
if (state.laserTime > 0) {
ctx.globalAlpha = 1;
ctx.textAlign = 'left';
ctx.font = '700 12px ui-monospace, Menlo, monospace';
ctx.fillStyle = '#fbbf24';
ctx.fillText('LASER ' + state.laserTime.toFixed(1) + 's', 26, H - 22);
}
if (muted) {
ctx.textAlign = 'right';
ctx.font = '700 12px ui-monospace, Menlo, monospace';
ctx.fillStyle = 'rgba(148,197,255,0.5)';
ctx.fillText('MUTED', W - 26, H - 22);
}
ctx.restore();
}
function drawBanner() {
if (state.msgTimer <= 0 || !state.msg) return;
var a = Math.min(1, state.msgTimer / 0.6);
ctx.save();
ctx.globalAlpha = a * 0.95;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '900 34px ui-monospace, Menlo, monospace';
ctx.shadowColor = '#38bdf8';
ctx.shadowBlur = 24;
ctx.fillStyle = '#e0f2ff';
ctx.fillText(state.msg, W / 2, H / 2 - 40);
ctx.restore();
}
function panel(title, titleColor, lines) {
ctx.save();
ctx.fillStyle = 'rgba(4,6,15,0.72)';
ctx.fillRect(0, TOP, W, H - TOP);
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '900 62px ui-monospace, Menlo, monospace';
ctx.shadowColor = titleColor;
ctx.shadowBlur = 30;
ctx.fillStyle = titleColor;
ctx.fillText(title, W / 2, H / 2 - 110);
ctx.shadowBlur = 0;
for (var i = 0; i < lines.length; i++) {
var ln = lines[i];
ctx.font = (ln.big ? '900 ' : '600 ') + (ln.size || 18) + 'px ui-monospace, Menlo, monospace';
ctx.fillStyle = ln.color || 'rgba(200,225,255,0.8)';
if (ln.glow) { ctx.shadowColor = ln.color; ctx.shadowBlur = 14; }
ctx.globalAlpha = ln.alpha != null ? ln.alpha : 1;
ctx.fillText(ln.text, W / 2, H / 2 - 40 + i * 34);
ctx.shadowBlur = 0;
ctx.globalAlpha = 1;
}
ctx.restore();
}
function drawOverlay() {
var pulse = 0.65 + Math.sin(state.time * 4) * 0.35;
if (state.mode === 'menu') {
panel('NEON BREAKOUT', '#38bdf8', [
{ text: 'PRESS SPACE / CLICK TO PLAY', color: 'rgba(224,242,255,' + pulse.toFixed(2) + ')', size: 20 },
{ text: '', size: 10 },
{ text: '\u2190 \u2192 or A D or MOUSE / TOUCH \u2014 MOVE', size: 15, color: 'rgba(148,197,255,0.7)' },
{ text: 'SPACE \u2014 LAUNCH & FIRE LASERS', size: 15, color: 'rgba(148,197,255,0.7)' },
{ text: 'P \u2014 PAUSE M \u2014 MUTE', size: 15, color: 'rgba(148,197,255,0.7)' },
{ text: 'CATCH DROPS: W WIDE M MULTI S SLOW L LASER + LIFE', size: 14, color: 'rgba(251,191,36,0.85)' },
{ text: 'BEST ' + state.best, size: 16, color: 'rgba(253,224,71,0.9)' }
]);
} else if (state.mode === 'paused') {
panel('PAUSED', '#a855f7', [
{ text: 'PRESS P OR SPACE TO RESUME', color: 'rgba(224,242,255,' + pulse.toFixed(2) + ')', size: 18 },
{ text: 'R \u2014 RESTART', size: 15, color: 'rgba(148,197,255,0.7)' }
]);
} else if (state.mode === 'levelclear') {
ctx.save();
ctx.globalAlpha = 0.35;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = '900 44px ui-monospace, Menlo, monospace';
ctx.fillStyle = '#fde047';
ctx.shadowColor = '#fbbf24';
ctx.shadowBlur = 26;
ctx.fillText('LEVEL CLEAR', W / 2, H / 2 + 80);
ctx.restore();
} else if (state.mode === 'gameover') {
panel('GAME OVER', '#fb7185', [
{ text: 'SCORE ' + state.score, color: '#e0f2ff', size: 24 },
{ text: 'BEST ' + state.best, color: '#fde047', size: 18 },
{ text: 'REACHED LEVEL ' + (state.level + 1), size: 15, color: 'rgba(148,197,255,0.7)' },
{ text: 'PRESS SPACE / R / CLICK TO PLAY AGAIN', color: 'rgba(224,242,255,' + pulse.toFixed(2) + ')', size: 18 }
]);
} else if (state.mode === 'victory') {
panel('YOU WIN!', '#34d399', [
{ text: 'ALL 6 LEVELS CLEARED', color: '#a7f3d0', size: 20 },
{ text: 'FINAL SCORE ' + state.score, color: '#fde047', size: 26 },
{ text: 'PRESS SPACE / R / CLICK TO PLAY AGAIN', color: 'rgba(224,242,255,' + pulse.toFixed(2) + ')', size: 18 }
]);
}
}
function render() {
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
ctx.drawImage(bg, 0, 0, W, H);
drawGlow();
ctx.save();
if (state.shake > 0.15) {
ctx.translate((Math.random() - 0.5) * state.shake, (Math.random() - 0.5) * state.shake);
}
drawBricks();
drawPowerups();
drawBullets();
drawParticles();
drawBalls();
drawPaddle();
drawFloaters();
ctx.restore();
drawWalls();
drawHUD();
drawBanner();
drawOverlay();
}
/* ------------------------------------------------------------------ loop */
var last = 0;
function loop(now) {
if (!last) last = now;
var dt = (now - last) / 1000;
last = now;
if (dt > 0.25) dt = 0.25;
if (dt < 0) dt = 0;
update(dt);
render();
requestAnimationFrame(loop);
}
/* ------------------------------------------------------------------ boot */
try {
var saved = localStorage.getItem('neon-breakout-best');
if (saved) state.best = parseInt(saved, 10) || 0;
} catch (e) {}
resize();
buildLevel(0);
requestAnimationFrame(loop);
})();
</script>
</body>
</html>

