Run details
- Model
- opencode/longcat-2.5-preview-free
- Route
- opencode CLI
- Reasoning
- provider default
- Limits
- 60 min · 131,072 output tokens
- Date
- 2 Oct 2026
- Time
- 5 min 44 s
- Size
- 24 kB
- Tokens
- 31,090 in, 15,994 out
- 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
24 kB 828 lines Raw
<!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>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
height: 100%;
background: #05060f;
display: flex;
align-items: center;
justify-content: center;
font-family: 'Courier New', monospace;
overflow: hidden;
}
#wrap {
position: relative;
box-shadow: 0 0 60px rgba(77, 150, 255, 0.25), 0 0 120px rgba(199, 125, 255, 0.12);
border-radius: 8px;
overflow: hidden;
}
canvas {
display: block;
background: #05060f;
cursor: none;
}
</style>
</head>
<body>
<div id="wrap">
<canvas id="game" width="800" height="600"></canvas>
</div>
<script>
'use strict';
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
// ---------------- Audio ----------------
let audioCtx = null;
let muted = false;
function ensureAudio() {
if (!audioCtx) {
try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } catch (e) { audioCtx = null; }
}
if (audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
}
function beep(freq, dur, type, vol, slide) {
if (!audioCtx || muted) return;
type = type || 'square'; vol = vol || 0.15; slide = slide || 0;
const t = audioCtx.currentTime;
const o = audioCtx.createOscillator();
const g = audioCtx.createGain();
o.type = type;
o.frequency.setValueAtTime(freq, t);
if (slide) o.frequency.exponentialRampToValueAtTime(Math.max(30, freq + slide), t + dur);
g.gain.setValueAtTime(vol, t);
g.gain.exponentialRampToValueAtTime(0.001, t + dur);
o.connect(g); g.connect(audioCtx.destination);
o.start(t); o.stop(t + dur);
}
function sfx(name, opt) {
opt = opt || {};
switch (name) {
case 'paddle': beep(200, 0.08, 'square', 0.18, 120); break;
case 'wall': beep(150, 0.05, 'square', 0.08, 40); break;
case 'brick': beep(320 + (opt.combo || 0) * 45, 0.07, 'square', 0.14, 220); break;
case 'silver': beep(520, 0.06, 'triangle', 0.14, 100); break;
case 'launch': beep(440, 0.1, 'square', 0.12, 220); break;
case 'lose':
beep(320, 0.35, 'sawtooth', 0.18, -240);
setTimeout(function(){ beep(180, 0.4, 'sawtooth', 0.15, -120); }, 120);
break;
case 'power':
beep(523, 0.09, 'square', 0.14);
setTimeout(function(){ beep(659, 0.09, 'square', 0.14); }, 80);
setTimeout(function(){ beep(784, 0.14, 'square', 0.14); }, 160);
break;
case 'level':
beep(392, 0.12, 'square', 0.15);
setTimeout(function(){ beep(523, 0.12, 'square', 0.15); }, 110);
setTimeout(function(){ beep(659, 0.12, 'square', 0.15); }, 220);
setTimeout(function(){ beep(784, 0.25, 'square', 0.16); }, 330);
break;
case 'over':
beep(392, 0.25, 'sawtooth', 0.16, -100);
setTimeout(function(){ beep(311, 0.25, 'sawtooth', 0.16, -80); }, 240);
setTimeout(function(){ beep(233, 0.5, 'sawtooth', 0.16, -100); }, 480);
break;
}
}
// ---------------- Constants ----------------
const STATE = { MENU: 0, PLAYING: 1, LEVELCLEAR: 2, GAMEOVER: 3, WIN: 4, PAUSED: 5 };
const PADDLE_W = 110, PADDLE_H = 14, PADDLE_Y = 556;
const BALL_R = 7;
const BRICK_W = 74, BRICK_H = 22, BRICK_GAP = 4, BRICK_TOP = 80, BRICK_LEFT = 30;
const COLS = 10;
const BRICK_TYPES = {
'R': { color: '#ff4d6d', hp: 1, score: 50 },
'O': { color: '#ff9f1c', hp: 1, score: 60 },
'Y': { color: '#ffe74c', hp: 1, score: 70 },
'G': { color: '#2ec4b6', hp: 1, score: 80 },
'B': { color: '#4d96ff', hp: 1, score: 90 },
'P': { color: '#c77dff', hp: 1, score: 100 },
'S': { color: '#e8eef5', hp: 2, score: 150 }
};
const LEVELS = [
[
'RRRRRRRRRR',
'OOOOOOOOOO',
'YYYYYYYYYY',
'GGGGGGGGGG',
'BBBBBBBBBB'
],
[
'R.R.R.R.R.',
'.O.O.O.O.O',
'Y.Y.Y.Y.Y.',
'.G.G.G.G.G',
'B.B.B.B.B.',
'.P.P.P.P.P'
],
[
'....YY....',
'...YRRY...',
'..YRRRRY..',
'.YRRRRRRY.',
'YRRRRRRRRY',
'.YRRRRRRY.',
'..YRRRRY..',
'...YRRY...',
'....YY....'
],
[
'SSSSSSSSSS',
'S........S',
'S.RRRRRR.S',
'S.R....R.S',
'S.R.YY.R.S',
'S.R.YY.R.S',
'S.R....R.S',
'S.RRRRRR.S',
'..SS..SS..'
],
[
'P..P..P..P',
'PP..PP..PP',
'..PP..PP..',
'B..B..B..B',
'BB..BB..BB',
'..BB..BB..',
'G..G..G..G',
'GG..GG..GG'
],
[
'SSSSSSSSSS',
'SYRYRYRYRS',
'SSSSSSSSSS',
'..........',
'PPP..PPPPP',
'P........P',
'P.BBBBBB.P',
'P.B.SS.B.P',
'P.B.SS.B.P',
'P.BBBBBB.P',
'P........P',
'PPPPPPPPPP'
]
];
const POWER_TYPES = {
'E': { color: '#2ec4b6', label: 'E', name: 'EXPAND' },
'M': { color: '#c77dff', label: 'M', name: 'MULTI' },
'S': { color: '#ffe74c', label: 'S', name: 'SLOW' },
'L': { color: '#ff4d6d', label: '+', name: '1UP' }
};
// ---------------- State ----------------
let state = STATE.MENU;
let score = 0, lives = 3, level = 0, combo = 0, bestCombo = 0;
let best = 0;
try { best = parseInt(localStorage.getItem('neonBreakoutBest') || '0', 10) || 0; } catch (e) {}
let shake = 0, flash = 0, hitstop = 0, levelClearTimer = 0, time = 0;
let slowTimer = 0, expandTimer = 0;
let paddle, balls, bricks, particles, powerups, popups, stars;
const keys = { left: false, right: false };
let mouseX = W / 2;
function newBall(stuck) {
return {
x: paddle.x, y: PADDLE_Y - BALL_R - 1,
vx: 0, vy: 0, r: BALL_R,
speed: Math.min(340 + level * 25, 520),
stuck: stuck, trail: []
};
}
function buildBricks(map) {
const arr = [];
for (let r = 0; r < map.length; r++) {
for (let c = 0; c < COLS; c++) {
const ch = map[r][c];
if (ch === '.' || ch === ' ') continue;
const t = BRICK_TYPES[ch];
if (!t) continue;
arr.push({
x: BRICK_LEFT + c * (BRICK_W + BRICK_GAP),
y: BRICK_TOP + r * (BRICK_H + BRICK_GAP),
w: BRICK_W, h: BRICK_H,
hp: t.hp, maxHp: t.hp,
color: t.color, score: t.score,
alive: true, wobble: 0
});
}
}
return arr;
}
function resetGame() {
score = 0; lives = 3; level = 0; combo = 0; bestCombo = 0;
slowTimer = 0; expandTimer = 0;
startLevel(0);
}
function startLevel(i) {
level = i;
paddle = { x: W / 2, w: PADDLE_W, h: PADDLE_H, squash: 0 };
bricks = buildBricks(LEVELS[i]);
balls = [newBall(true)];
powerups = []; particles = []; popups = [];
state = STATE.PLAYING;
}
function initStars() {
stars = [];
for (let i = 0; i < 70; i++) {
stars.push({ x: Math.random() * W, y: Math.random() * H, r: Math.random() * 1.6 + 0.4, p: Math.random() * Math.PI * 2 });
}
}
initStars();
// ---------------- Effects ----------------
function spawnParticles(x, y, color, n) {
for (let i = 0; i < n; i++) {
const a = Math.random() * Math.PI * 2;
const sp = 60 + Math.random() * 200;
particles.push({
x: x, y: y,
vx: Math.cos(a) * sp, vy: Math.sin(a) * sp - 60,
life: 0.5 + Math.random() * 0.5, maxLife: 1,
size: 2 + Math.random() * 3, color: color
});
}
}
function spawnPopup(x, y, txt, color) {
popups.push({ x: x, y: y, txt: txt, life: 0.9, color: color });
}
function maybeDropPowerup(x, y) {
if (Math.random() > 0.16) return;
const roll = Math.random();
let type = 'E';
if (roll < 0.30) type = 'E';
else if (roll < 0.55) type = 'M';
else if (roll < 0.80) type = 'S';
else type = (lives < 5) ? 'L' : 'E';
powerups.push({ x: x, y: y, vy: 130, type: type, life: 8 });
}
// ---------------- Update ----------------
function update(dt) {
time += dt;
if (shake > 0) shake = Math.max(0, shake - dt * 30);
if (flash > 0) flash = Math.max(0, flash - dt * 2.5);
if (state === STATE.LEVELCLEAR) {
levelClearTimer -= dt;
updateParticles(dt);
if (levelClearTimer <= 0) {
if (level + 1 >= LEVELS.length) {
state = STATE.WIN;
if (score > best) { best = score; try { localStorage.setItem('neonBreakoutBest', String(best)); } catch (e) {} }
} else {
startLevel(level + 1);
}
}
return;
}
if (state !== STATE.PLAYING) { updateParticles(dt); return; }
if (hitstop > 0) { hitstop -= dt; return; }
if (slowTimer > 0) slowTimer -= dt;
if (expandTimer > 0) expandTimer -= dt;
paddle.w = PADDLE_W * (expandTimer > 0 ? 1.5 : 1);
paddle.squash = Math.max(0, paddle.squash - dt * 6);
// Paddle movement
const kb = 560;
if (keys.left) mouseX -= kb * dt;
if (keys.right) mouseX += kb * dt;
paddle.x += (mouseX - paddle.x) * Math.min(1, dt * 18);
paddle.x = Math.max(paddle.w / 2 + 4, Math.min(W - paddle.w / 2 - 4, paddle.x));
// Balls
for (let i = balls.length - 1; i >= 0; i--) {
const b = balls[i];
if (b.stuck) {
b.x = paddle.x;
b.y = PADDLE_Y - b.r - 1;
continue;
}
b.x += b.vx * dt;
b.y += b.vy * dt;
b.trail.push({ x: b.x, y: b.y });
if (b.trail.length > 12) b.trail.shift();
// Walls
if (b.x - b.r < 0) { b.x = b.r; b.vx = Math.abs(b.vx); sfx('wall'); }
if (b.x + b.r > W) { b.x = W - b.r; b.vx = -Math.abs(b.vx); sfx('wall'); }
if (b.y - b.r < 0) { b.y = b.r; b.vy = Math.abs(b.vy); sfx('wall'); }
// Paddle
if (b.vy > 0 &&
b.y + b.r >= PADDLE_Y && b.y - b.r <= PADDLE_Y + PADDLE_H &&
b.x >= paddle.x - paddle.w / 2 - b.r && b.x <= paddle.x + paddle.w / 2 + b.r) {
const rel = Math.max(-1, Math.min(1, (b.x - paddle.x) / (paddle.w / 2)));
const ang = -Math.PI / 2 + rel * (Math.PI / 3);
b.vx = b.speed * Math.cos(ang);
b.vy = b.speed * Math.sin(ang);
b.y = PADDLE_Y - b.r - 0.5;
combo = 0;
paddle.squash = 1;
spawnParticles(b.x, PADDLE_Y, '#4d96ff', 5);
sfx('paddle');
}
// Bricks
for (let j = 0; j < bricks.length; j++) {
const k = bricks[j];
if (!k.alive) continue;
const nx = Math.max(k.x, Math.min(b.x, k.x + k.w));
const ny = Math.max(k.y, Math.min(b.y, k.y + k.h));
const dx = b.x - nx, dy = b.y - ny;
if (dx * dx + dy * dy > b.r * b.r) continue;
// Resolve bounce by smallest penetration
const overlapL = (b.x + b.r) - k.x;
const overlapR = (k.x + k.w) - (b.x - b.r);
const overlapT = (b.y + b.r) - k.y;
const overlapB = (k.y + k.h) - (b.y - b.r);
const minX = Math.min(overlapL, overlapR);
const minY = Math.min(overlapT, overlapB);
if (minX < minY) {
b.vx = (overlapL < overlapR) ? -Math.abs(b.vx) : Math.abs(b.vx);
} else {
b.vy = (overlapT < overlapB) ? -Math.abs(b.vy) : Math.abs(b.vy);
}
k.hp--;
k.wobble = 1;
if (k.hp <= 0) {
k.alive = false;
combo++;
bestCombo = Math.max(bestCombo, combo);
const mult = Math.min(1 + (combo - 1) * 0.25, 5);
const pts = Math.round(k.score * mult);
score += pts;
spawnParticles(k.x + k.w / 2, k.y + k.h / 2, k.color, 16);
spawnPopup(k.x + k.w / 2, k.y, '+' + pts, k.color);
shake = Math.min(shake + 3.5, 9);
hitstop = 0.03;
maybeDropPowerup(k.x + k.w / 2, k.y + k.h / 2);
sfx('brick', { combo: combo });
b.speed = Math.min(b.speed + 2, 560);
const sp = Math.hypot(b.vx, b.vy) || 1;
b.vx = b.vx / sp * b.speed; b.vy = b.vy / sp * b.speed;
} else {
spawnParticles(nx, ny, '#e8eef5', 6);
sfx('silver');
}
break;
}
// Lost ball
if (b.y - b.r > H) {
balls.splice(i, 1);
}
}
if (balls.length === 0) {
lives--;
combo = 0;
shake = 14;
flash = 1;
sfx('lose');
if (lives <= 0) {
state = STATE.GAMEOVER;
if (score > best) { best = score; try { localStorage.setItem('neonBreakoutBest', String(best)); } catch (e) {} }
sfx('over');
} else {
balls = [newBall(true)];
}
}
// Level clear?
let remaining = 0;
for (let i = 0; i < bricks.length; i++) if (bricks[i].alive) remaining++;
if (remaining === 0 && balls.length > 0) {
score += 500;
state = STATE.LEVELCLEAR;
levelClearTimer = 1.8;
sfx('level');
}
// Powerups
for (let i = powerups.length - 1; i >= 0; i--) {
const p = powerups[i];
p.y += p.vy * dt;
p.life -= dt;
if (p.y > PADDLE_Y - 6 && p.y < PADDLE_Y + PADDLE_H + 6 &&
p.x > paddle.x - paddle.w / 2 - 12 && p.x < paddle.x + paddle.w / 2 + 12) {
applyPowerup(p.type, p.x, p.y);
powerups.splice(i, 1);
} else if (p.y > H + 20 || p.life <= 0) {
powerups.splice(i, 1);
}
}
updateParticles(dt);
}
function applyPowerup(type, x, y) {
const t = POWER_TYPES[type];
spawnPopup(x, y - 10, t.name, t.color);
spawnParticles(x, y, t.color, 14);
sfx('power');
if (type === 'E') {
expandTimer = 12;
} else if (type === 'S') {
slowTimer = 8;
for (const b of balls) { b.vx *= 0.7; b.vy *= 0.7; }
} else if (type === 'M') {
const additions = [];
const src = balls.filter(function(b) { return !b.stuck; });
for (const b of src) {
if (balls.length + additions.length >= 6) break;
const base = Math.atan2(b.vy, b.vx);
const offs = src.length === 1 ? [-0.5, 0.5] : [0.45];
for (const da of offs) {
if (balls.length + additions.length >= 6) break;
additions.push({
x: b.x, y: b.y, r: b.r, speed: b.speed, stuck: false, trail: [],
vx: b.speed * Math.cos(base + da), vy: b.speed * Math.sin(base + da)
});
}
}
balls = balls.concat(additions);
} else if (type === 'L') {
lives = Math.min(lives + 1, 5);
}
}
function updateParticles(dt) {
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx * dt;
p.y += p.vy * dt;
p.vy += 320 * dt;
p.life -= dt;
if (p.life <= 0) particles.splice(i, 1);
}
for (let i = popups.length - 1; i >= 0; i--) {
const p = popups[i];
p.y -= 45 * dt;
p.life -= dt;
if (p.life <= 0) popups.splice(i, 1);
}
for (const k of bricks || []) {
if (k.wobble > 0) k.wobble = Math.max(0, k.wobble - dt * 5);
}
}
// ---------------- Render ----------------
function roundRect(x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.arcTo(x + w, y, x + w, y + h, r);
ctx.arcTo(x + w, y + h, x, y + h, r);
ctx.arcTo(x, y + h, x, y, r);
ctx.arcTo(x, y, x + w, y, r);
ctx.closePath();
}
function drawBackground() {
const g = ctx.createLinearGradient(0, 0, 0, H);
g.addColorStop(0, '#0a0d1f');
g.addColorStop(1, '#05060f');
ctx.fillStyle = g;
ctx.fillRect(0, 0, W, H);
for (const s of stars) {
const a = 0.25 + 0.35 * (0.5 + 0.5 * Math.sin(time * 1.5 + s.p));
ctx.globalAlpha = a;
ctx.fillStyle = '#9db4ff';
ctx.beginPath();
ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
}
function drawBricks() {
for (const k of bricks) {
if (!k.alive) continue;
const wob = k.wobble > 0 ? Math.sin(k.wobble * 20) * k.wobble * 2 : 0;
ctx.save();
ctx.translate(k.x + k.w / 2 + wob, k.y + k.h / 2);
ctx.shadowColor = k.color;
ctx.shadowBlur = 10;
ctx.fillStyle = k.hp < k.maxHp ? '#8d99ae' : k.color;
roundRect(-k.w / 2, -k.h / 2, k.w, k.h, 5);
ctx.fill();
ctx.shadowBlur = 0;
ctx.globalAlpha = 0.35;
ctx.fillStyle = '#ffffff';
roundRect(-k.w / 2 + 3, -k.h / 2 + 2, k.w - 6, 6, 3);
ctx.fill();
ctx.globalAlpha = 1;
ctx.restore();
}
}
function drawPaddle() {
const sq = 1 - paddle.squash * 0.3;
ctx.save();
ctx.translate(paddle.x, PADDLE_Y + PADDLE_H / 2);
ctx.scale(1 + paddle.squash * 0.15, sq);
ctx.shadowColor = '#4d96ff';
ctx.shadowBlur = 18;
const g = ctx.createLinearGradient(0, -PADDLE_H / 2, 0, PADDLE_H / 2);
g.addColorStop(0, '#7db4ff');
g.addColorStop(1, '#2b6cb0');
ctx.fillStyle = g;
roundRect(-paddle.w / 2, -PADDLE_H / 2, paddle.w, PADDLE_H, 7);
ctx.fill();
ctx.restore();
}
function drawBalls() {
for (const b of balls) {
for (let i = 0; i < b.trail.length; i++) {
const t = b.trail[i];
const a = (i / b.trail.length) * 0.3;
ctx.globalAlpha = a;
ctx.fillStyle = '#7df9ff';
ctx.beginPath();
ctx.arc(t.x, t.y, b.r * (i / b.trail.length), 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
ctx.shadowColor = '#7df9ff';
ctx.shadowBlur = 16;
ctx.fillStyle = '#eafcff';
ctx.beginPath();
ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
}
}
function drawPowerups() {
for (const p of powerups) {
const t = POWER_TYPES[p.type];
const pulse = 1 + 0.12 * Math.sin(time * 8);
ctx.save();
ctx.translate(p.x, p.y);
ctx.scale(pulse, pulse);
ctx.shadowColor = t.color;
ctx.shadowBlur = 12;
ctx.fillStyle = t.color;
roundRect(-13, -8, 26, 16, 8);
ctx.fill();
ctx.shadowBlur = 0;
ctx.fillStyle = '#05060f';
ctx.font = 'bold 11px Courier New';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(t.label, 0, 1);
ctx.restore();
}
}
function drawParticles() {
for (const p of particles) {
ctx.globalAlpha = Math.max(0, p.life / 0.9);
ctx.fillStyle = p.color;
ctx.fillRect(p.x - p.size / 2, p.y - p.size / 2, p.size, p.size);
}
ctx.globalAlpha = 1;
}
function drawPopups() {
ctx.font = 'bold 14px Courier New';
ctx.textAlign = 'center';
for (const p of popups) {
ctx.globalAlpha = Math.min(1, p.life * 2);
ctx.fillStyle = p.color;
ctx.fillText(p.txt, p.x, p.y);
}
ctx.globalAlpha = 1;
}
function drawHUD() {
ctx.font = 'bold 16px Courier New';
ctx.textAlign = 'left';
ctx.fillStyle = '#eaf2ff';
ctx.fillText('SCORE ' + String(score).padStart(6, '0'), 16, 28);
ctx.textAlign = 'center';
ctx.fillStyle = '#9db4ff';
ctx.fillText('LEVEL ' + (level + 1) + ' / ' + LEVELS.length, W / 2, 28);
ctx.textAlign = 'right';
ctx.fillText('BEST ' + String(best).padStart(6, '0'), W - 16, 28);
for (let i = 0; i < lives; i++) {
ctx.fillStyle = '#ff4d6d';
ctx.shadowColor = '#ff4d6d';
ctx.shadowBlur = 8;
ctx.beginPath();
ctx.arc(W - 20 - i * 20, 44, 6, 0, Math.PI * 2);
ctx.fill();
ctx.shadowBlur = 0;
}
if (combo >= 2 && state === STATE.PLAYING) {
const pulse = 1 + 0.15 * Math.sin(time * 10);
ctx.save();
ctx.translate(W / 2, 52);
ctx.scale(pulse, pulse);
ctx.font = 'bold 18px Courier New';
ctx.textAlign = 'center';
ctx.fillStyle = '#ffe74c';
ctx.shadowColor = '#ffe74c';
ctx.shadowBlur = 10;
ctx.fillText('COMBO x' + combo, 0, 0);
ctx.restore();
}
if (slowTimer > 0) {
ctx.font = '12px Courier New';
ctx.textAlign = 'left';
ctx.fillStyle = '#ffe74c';
ctx.fillText('SLOW ' + slowTimer.toFixed(1), 16, 48);
}
if (expandTimer > 0) {
ctx.font = '12px Courier New';
ctx.textAlign = 'left';
ctx.fillStyle = '#2ec4b6';
ctx.fillText('WIDE ' + expandTimer.toFixed(1), 16, 64);
}
}
function drawCenterText(lines) {
ctx.textAlign = 'center';
let y = H / 2 - (lines.length - 1) * 26;
for (const ln of lines) {
ctx.font = ln.font;
if (ln.glow) { ctx.shadowColor = ln.color; ctx.shadowBlur = ln.glow; }
ctx.fillStyle = ln.color;
ctx.fillText(ln.text, W / 2, y);
ctx.shadowBlur = 0;
y += ln.gap || 44;
}
}
function render() {
drawBackground();
ctx.save();
if (shake > 0) {
ctx.translate((Math.random() - 0.5) * shake * 2, (Math.random() - 0.5) * shake * 2);
}
if (state !== STATE.MENU) {
drawBricks();
drawPowerups();
drawPaddle();
drawBalls();
}
drawParticles();
drawPopups();
ctx.restore();
if (flash > 0) {
ctx.globalAlpha = flash * 0.25;
ctx.fillStyle = '#ff4d6d';
ctx.fillRect(0, 0, W, H);
ctx.globalAlpha = 1;
}
if (state !== STATE.MENU) drawHUD();
if (state === STATE.MENU) {
drawCenterText([
{ text: 'NEON BREAKOUT', font: 'bold 52px Courier New', color: '#4d96ff', glow: 24, gap: 60 },
{ text: 'Mouse or Arrow Keys to move', font: '16px Courier New', color: '#9db4ff', gap: 30 },
{ text: 'Click or Space to launch', font: '16px Courier New', color: '#9db4ff', gap: 30 },
{ text: 'P pause M mute', font: '14px Courier New', color: '#5a6b8c', gap: 44 }
]);
if (Math.sin(time * 4) > -0.2) {
ctx.font = 'bold 20px Courier New';
ctx.textAlign = 'center';
ctx.fillStyle = '#ffe74c';
ctx.shadowColor = '#ffe74c';
ctx.shadowBlur = 12;
ctx.fillText('CLICK OR PRESS SPACE TO START', W / 2, H / 2 + 118);
ctx.shadowBlur = 0;
}
if (best > 0) {
ctx.font = '14px Courier New';
ctx.textAlign = 'center';
ctx.fillStyle = '#5a6b8c';
ctx.fillText('BEST ' + best, W / 2, H - 30);
}
} else if (state === STATE.LEVELCLEAR) {
drawCenterText([
{ text: 'LEVEL CLEAR!', font: 'bold 44px Courier New', color: '#2ec4b6', glow: 20, gap: 56 },
{ text: 'BONUS +500', font: 'bold 20px Courier New', color: '#ffe74c', glow: 10, gap: 40 },
{ text: 'SCORE ' + score, font: '18px Courier New', color: '#eaf2ff', gap: 0 }
]);
} else if (state === STATE.GAMEOVER) {
drawCenterText([
{ text: 'GAME OVER', font: 'bold 52px Courier New', color: '#ff4d6d', glow: 22, gap: 60 },
{ text: 'SCORE ' + score, font: 'bold 22px Courier New', color: '#eaf2ff', gap: 36 },
{ text: 'BEST COMBO x' + bestCombo + ' BEST ' + best, font: '15px Courier New', color: '#9db4ff', gap: 48 },
{ text: 'CLICK OR PRESS SPACE TO RETRY', font: 'bold 17px Courier New', color: '#ffe74c', glow: 10, gap: 0 }
]);
} else if (state === STATE.WIN) {
drawCenterText([
{ text: 'YOU WIN!', font: 'bold 56px Courier New', color: '#ffe74c', glow: 26, gap: 62 },
{ text: 'ALL ' + LEVELS.length + ' LEVELS CLEARED', font: '18px Courier New', color: '#2ec4b6', gap: 38 },
{ text: 'FINAL SCORE ' + score, font: 'bold 22px Courier New', color: '#eaf2ff', gap: 38 },
{ text: 'BEST COMBO x' + bestCombo, font: '15px Courier New', color: '#9db4ff', gap: 48 },
{ text: 'CLICK OR PRESS SPACE TO PLAY AGAIN', font: 'bold 17px Courier New', color: '#c77dff', glow: 10, gap: 0 }
]);
} else if (state === STATE.PAUSED) {
drawCenterText([
{ text: 'PAUSED', font: 'bold 44px Courier New', color: '#9db4ff', glow: 16, gap: 52 },
{ text: 'PRESS P TO RESUME', font: '16px Courier New', color: '#eaf2ff', gap: 0 }
]);
}
}
// ---------------- Input ----------------
function canvasMouseX(clientX) {
const rect = canvas.getBoundingClientRect();
return (clientX - rect.left) * (W / rect.width);
}
canvas.addEventListener('mousemove', function(e) {
mouseX = canvasMouseX(e.clientX);
});
canvas.addEventListener('mousedown', function(e) {
e.preventDefault();
ensureAudio();
handleAction();
});
canvas.addEventListener('touchstart', function(e) {
e.preventDefault();
ensureAudio();
if (e.touches.length > 0) mouseX = canvasMouseX(e.touches[0].clientX);
handleAction();
}, { passive: false });
canvas.addEventListener('touchmove', function(e) {
e.preventDefault();
if (e.touches.length > 0) mouseX = canvasMouseX(e.touches[0].clientX);
}, { passive: false });
function handleAction() {
if (state === STATE.MENU) { resetGame(); return; }
if (state === STATE.GAMEOVER || state === STATE.WIN) { state = STATE.MENU; return; }
if (state === STATE.LEVELCLEAR) { levelClearTimer = 0; return; }
if (state !== STATE.PLAYING) return;
let launched = false;
for (const b of balls) {
if (b.stuck) {
b.stuck = false;
const ang = -Math.PI / 2 + (Math.random() * 0.3 - 0.15);
b.vx = b.speed * Math.cos(ang);
b.vy = b.speed * Math.sin(ang);
launched = true;
}
}
if (launched) sfx('launch');
}
window.addEventListener('keydown', function(e) {
if (e.code === 'ArrowLeft' || e.code === 'KeyA') { keys.left = true; e.preventDefault(); }
if (e.code === 'ArrowRight' || e.code === 'KeyD') { keys.right = true; e.preventDefault(); }
if (e.code === 'Space') { ensureAudio(); handleAction(); e.preventDefault(); }
if (e.code === 'KeyP') {
if (state === STATE.PLAYING) state = STATE.PAUSED;
else if (state === STATE.PAUSED) state = STATE.PLAYING;
}
if (e.code === 'KeyM') muted = !muted;
});
window.addEventListener('keyup', function(e) {
if (e.code === 'ArrowLeft' || e.code === 'KeyA') keys.left = false;
if (e.code === 'ArrowRight' || e.code === 'KeyD') keys.right = false;
});
// ---------------- Main loop ----------------
let last = performance.now();
function loop(now) {
let dt = (now - last) / 1000;
last = now;
if (dt > 0.033) dt = 0.033;
update(dt);
render();
requestAnimationFrame(loop);
}
paddle = { x: W / 2, w: PADDLE_W, h: PADDLE_H, squash: 0 };
balls = [newBall(true)];
bricks = buildBricks(LEVELS[0]);
particles = []; powerups = []; popups = [];
requestAnimationFrame(loop);
</script>
</body>
</html>LongCat 2.5 Preview: other attempts
- LongCat 2.5 Preview, Pelican: Not run: no OpenRouter route



- About · 144 words