Run details
- Model
- tencent/hy4
- Route
- OpenRouter API, one user message
- Served by
- Tencent
- Effort
- high, OpenRouter's default for this model
- Limits
- 60 min · 65,536 output tokens (the model's limit)
- Date
- 7 Nov 2026
- Time
- 2 min 17 s
- Size
- 18 kB
- Tokens
- 97 in, 20,371 out (14,347 of them thinking)
- List price
- $0.026 at today's price ($0.417 in / $1.25 out per M)
- Paid
- $0.026
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). Reply with the file in a single ```html code block.
Source
18 kB 546 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>
* { box-sizing: border-box; }
body, html {
margin: 0; padding: 0; width: 100%; height: 100%;
background: #050508;
display: flex; align-items: center; justify-content: center;
font-family: "Courier New", Courier, monospace;
overflow: hidden;
color: #fff;
user-select: none;
}
#wrapper {
position: relative;
width: 100%;
max-width: 800px;
aspect-ratio: 4 / 3;
border: 2px solid #1a1c23;
border-radius: 6px;
box-shadow: 0 0 50px rgba(69, 243, 255, 0.1), 0 0 15px rgba(255, 42, 109, 0.08), inset 0 0 20px rgba(0,0,0,0.8);
background: #0b0c10;
overflow: hidden;
}
/* CRT Scanlines */
#wrapper::after {
content: "";
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
background: repeating-linear-gradient(0deg, rgba(0,0,0,0.12), rgba(0,0,0,0.12) 1px, transparent 1px, transparent 3px);
pointer-events: none;
z-index: 10;
}
canvas {
display: block;
width: 100%;
height: 100%;
cursor: crosshair;
}
</style>
</head>
<body>
<div id="wrapper">
<canvas id="game" width="800" height="600"></canvas>
</div>
<script>
/** @type {HTMLCanvasElement} */
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
// --- Audio Synth (No external assets) ---
let audioCtx = null;
function initAudio() {
if (!audioCtx) audioCtx = new (window.AudioContext || window.webkitAudioContext)();
if (audioCtx.state === 'suspended') audioCtx.resume();
}
function playSound(type, pitchMod = 1) {
if (!audioCtx) return;
const t = audioCtx.currentTime;
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.connect(gain);
gain.connect(audioCtx.destination);
if (type === 'paddle') {
osc.type = 'square';
osc.frequency.setValueAtTime(180 * pitchMod, t);
osc.frequency.exponentialRampToValueAtTime(80 * pitchMod, t + 0.08);
gain.gain.setValueAtTime(0.06, t);
gain.gain.exponentialRampToValueAtTime(0.001, t + 0.08);
osc.start(t); osc.stop(t + 0.08);
} else if (type === 'brick') {
osc.type = 'triangle';
osc.frequency.setValueAtTime(300 * pitchMod, t);
osc.frequency.exponentialRampToValueAtTime(600 * pitchMod, t + 0.1);
gain.gain.setValueAtTime(0.08, t);
gain.gain.exponentialRampToValueAtTime(0.001, t + 0.1);
osc.start(t); osc.stop(t + 0.1);
} else if (type === 'lose') {
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(120, t);
osc.frequency.exponentialRampToValueAtTime(40, t + 0.6);
gain.gain.setValueAtTime(0.12, t);
gain.gain.exponentialRampToValueAtTime(0.001, t + 0.6);
osc.start(t); osc.stop(t + 0.6);
} else if (type === 'clear') {
osc.type = 'sine';
osc.frequency.setValueAtTime(400, t);
osc.frequency.setValueAtTime(500, t + 0.1);
osc.frequency.setValueAtTime(600, t + 0.2);
osc.frequency.setValueAtTime(800, t + 0.3);
gain.gain.setValueAtTime(0.1, t);
gain.gain.linearRampToValueAtTime(0.001, t + 0.5);
osc.start(t); osc.stop(t + 0.5);
}
}
// --- Constants & Levels ---
const COLORS = { 1: '#45f3ff', 2: '#ff2a6d', 3: '#f9cb28' };
const rawLevels = [
[ // Level 1: Classic Wall
"111111111111",
"111111111111",
"111111111111",
"111111111111"
],
[ // Level 2: Pyramid
"000111111000",
"001222222100",
"012233332210",
"123333333321"
],
[ // Level 3: The Fortress
"330000000033",
"322222222223",
"321111111123",
"321033330123",
"321000000123",
"321111111123",
"322222222223",
"330000000033"
]
];
// --- Game State ---
let state = 'MENU'; // MENU, PLAYING, LEVEL_CLEAR, GAME_OVER, VICTORY
let score = 0, lives = 3, level = 0, combo = 0;
let shake = 0, bgGlow = 0, comboPop = 0;
let bricks = [], particles = [];
const paddle = { x: 345, y: 550, w: 110, h: 16, squash: 0 };
const ball = { x: 400, y: 530, r: 7, vx: 0, vy: 0, trail: [] };
let mouseX = 400;
const keys = { left: false, right: false };
function loadLevel(idx) {
bricks = [];
const layout = rawLevels[idx];
const cols = 12, bw = 60, bh = 24, px = 4, py = 6;
const startX = (800 - (cols * (bw + px) - px)) / 2;
const startY = 75;
for (let r = 0; r < layout.length; r++) {
// Auto-pad/center rows just in case
const str = layout[r].padEnd(12, '0').substring(0, 12);
for (let c = 0; c < cols; c++) {
const hp = parseInt(str[c]);
if (hp > 0) {
bricks.push({
x: startX + c * (bw + px),
y: startY + r * (bh + py),
w: bw, h: bh,
hp: hp, flash: 0
});
}
}
}
}
function resetBall() {
ball.x = paddle.x + paddle.w / 2;
ball.y = paddle.y - ball.r - 2;
ball.vx = 0; ball.vy = 0;
ball.trail = [];
combo = 0;
}
function launchBall() {
const angle = (Math.random() - 0.5) * (Math.PI / 3.5);
const speed = 6 + level * 0.6;
ball.vx = Math.sin(angle) * speed;
ball.vy = -Math.cos(angle) * speed;
}
function resetGame() {
score = 0; lives = 3; level = 0; combo = 0;
particles = []; shake = 0; bgGlow = 0;
loadLevel(level);
resetBall();
state = 'MENU';
}
function action() {
initAudio();
if (state === 'MENU') {
launchBall();
state = 'PLAYING';
} else if (state === 'GAME_OVER' || state === 'VICTORY') {
resetGame();
}
}
// --- Input ---
window.addEventListener('keydown', e => {
if (['Space', 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.code)) e.preventDefault();
if (e.code === 'ArrowLeft' || e.code === 'KeyA') keys.left = true;
if (e.code === 'ArrowRight' || e.code === 'KeyD') keys.right = true;
if (e.code === 'Space' && !e.repeat) action();
});
window.addEventListener('keyup', e => {
if (e.code === 'ArrowLeft' || e.code === 'KeyA') keys.left = false;
if (e.code === 'ArrowRight' || e.code === 'KeyD') keys.right = false;
});
canvas.addEventListener('mousemove', e => {
const rect = canvas.getBoundingClientRect();
mouseX = (e.clientX - rect.left) * (canvas.width / rect.width);
});
canvas.addEventListener('mousedown', action);
// Touch support for mobile bonus
canvas.addEventListener('touchmove', e => {
e.preventDefault();
const rect = canvas.getBoundingClientRect();
mouseX = (e.touches[0].clientX - rect.left) * (canvas.width / rect.width);
}, { passive: false });
canvas.addEventListener('touchstart', e => { e.preventDefault(); action(); }, { passive: false });
// --- Logic ---
function spawnParticles(x, y, color, count = 8) {
for (let i = 0; i < count; i++) {
particles.push({
x: x + (Math.random() - 0.5) * 30,
y: y + (Math.random() - 0.5) * 15,
vx: (Math.random() - 0.5) * 12,
vy: (Math.random() - 0.5) * 12 - 2,
life: 1, color: color,
size: Math.random() * 4 + 2
});
}
}
function update(f) {
// Decay juice
if (shake > 0) { shake *= Math.pow(0.88, f); if (shake < 0.5) shake = 0; }
if (bgGlow > 0) { bgGlow *= Math.pow(0.94, f); if (bgGlow < 0.01) bgGlow = 0; }
if (comboPop > 0) { comboPop *= Math.pow(0.85, f); if (comboPop < 0.01) comboPop = 0; }
if (paddle.squash > 0) { paddle.squash *= Math.pow(0.85, f); if (paddle.squash < 0.01) paddle.squash = 0; }
// Paddle Move
if (keys.left) paddle.x -= 9 * f;
if (keys.right) paddle.x += 9 * f;
if (!keys.left && !keys.right) {
paddle.x += (mouseX - paddle.w / 2 - paddle.x) * 0.25 * f;
}
paddle.x = Math.max(0, Math.min(800 - paddle.w, paddle.x));
if (state === 'MENU') {
ball.x = paddle.x + paddle.w / 2;
ball.y = paddle.y - ball.r - 2;
}
else if (state === 'PLAYING') {
ball.trail.unshift({ x: ball.x, y: ball.y });
if (ball.trail.length > 14) ball.trail.pop();
// Sub-stepping for perfect collision
const steps = 4;
const sx = (ball.vx * f) / steps;
const sy = (ball.vy * f) / steps;
for (let s = 0; s < steps; s++) {
ball.x += sx; ball.y += sy;
// Walls (Math.abs prevents sticky vibrations)
if (ball.x - ball.r < 0) { ball.x = ball.r; ball.vx = Math.abs(ball.vx); playSound('paddle', 0.6); }
if (ball.x + ball.r > 800) { ball.x = 800 - ball.r; ball.vx = -Math.abs(ball.vx); playSound('paddle', 0.6); }
if (ball.y - ball.r < 0) { ball.y = ball.r; ball.vy = Math.abs(ball.vy); playSound('paddle', 0.6); }
// Paddle
if (ball.vy > 0 && ball.y + ball.r > paddle.y && ball.y - ball.r < paddle.y + paddle.h &&
ball.x > paddle.x && ball.x < paddle.x + paddle.w) {
const hit = (ball.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2);
const angle = hit * (Math.PI / 3); // Max 60deg
const speed = Math.min(Math.hypot(ball.vx, ball.vy) * 1.03, 11.5);
ball.vx = Math.sin(angle) * speed;
ball.vy = -Math.cos(angle) * speed;
ball.y = paddle.y - ball.r - 1; // Hard eject
combo = 0;
paddle.squash = 1;
shake = 3;
playSound('paddle');
break;
}
// Bricks
let hitBrick = false;
for (let i = 0; i < bricks.length; i++) {
const b = bricks[i];
if (b.hp <= 0) continue;
const cx = Math.max(b.x, Math.min(ball.x, b.x + b.w));
const cy = Math.max(b.y, Math.min(ball.y, b.y + b.h));
const dx = ball.x - cx, dy = ball.y - cy;
if (dx * dx + dy * dy < ball.r * ball.r) {
const ox = (ball.r + b.w / 2) - Math.abs(ball.x - (b.x + b.w / 2));
const oy = (ball.r + b.h / 2) - Math.abs(ball.y - (b.y + b.h / 2));
if (ox < oy) { ball.vx *= -1; ball.x += ball.vx > 0 ? ox : -ox; }
else { ball.vy *= -1; ball.y += ball.vy > 0 ? oy : -oy; }
const oldHp = b.hp;
b.hp--;
b.flash = 3;
combo++;
comboPop = 0.4;
score += 10 * combo;
shake = Math.min(shake + 2 + combo * 0.5, 16);
bgGlow = Math.min(bgGlow + 0.3, 1);
playSound('brick', 1 + combo * 0.07);
spawnParticles(b.x + b.w/2, b.y + b.h/2, COLORS[oldHp]);
if (b.hp <= 0) {
score += 50 * combo;
spawnParticles(b.x + b.w/2, b.y + b.h/2, '#ffffff', 16);
}
hitBrick = true;
break;
}
}
if (hitBrick) break;
// Fall out
if (ball.y - ball.r > 600) {
lives--;
playSound('lose');
shake = 25;
bgGlow = 0;
if (lives > 0) { state = 'MENU'; resetBall(); }
else { state = 'GAME_OVER'; }
break;
}
}
// Win check
if (state === 'PLAYING' && bricks.every(b => b.hp <= 0)) {
state = 'LEVEL_CLEAR';
playSound('clear');
bgGlow = 1;
setTimeout(() => {
level++;
if (level >= rawLevels.length) state = 'VICTORY';
else { loadLevel(level); resetBall(); state = 'MENU'; }
}, 1800);
}
}
// Particles
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx * f; p.y += p.vy * f;
p.vx *= Math.pow(0.94, f);
p.vy *= Math.pow(0.94, f);
p.vy += 0.25 * f; // Gravity
p.life -= 0.025 * f;
if (p.life <= 0) particles.splice(i, 1);
}
}
// --- Render ---
function draw() {
ctx.fillStyle = '#0b0c10';
ctx.fillRect(0, 0, 800, 600);
// Background Pulse
if (bgGlow > 0.01) {
const grad = ctx.createRadialGradient(400, 300, 0, 400, 300, 450);
grad.addColorStop(0, `rgba(69, 243, 255, ${bgGlow * 0.12})`);
grad.addColorStop(1, 'rgba(11,12,16,0)');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, 800, 600);
}
// Grid
ctx.strokeStyle = 'rgba(255,255,255,0.025)';
ctx.lineWidth = 1;
ctx.beginPath();
for (let i = 0; i <= 800; i += 40) { ctx.moveTo(i, 0); ctx.lineTo(i, 600); }
for (let i = 0; i <= 600; i += 40) { ctx.moveTo(0, i); ctx.lineTo(800, i); }
ctx.stroke();
ctx.save();
if (shake > 0) ctx.translate((Math.random() - 0.5) * shake, (Math.random() - 0.5) * shake);
// Bricks
bricks.forEach(b => {
if (b.hp <= 0) return;
const c = COLORS[b.hp];
ctx.fillStyle = b.flash > 0 ? '#ffffff' : c;
ctx.shadowColor = c;
ctx.shadowBlur = b.flash > 0 ? 25 : 8;
ctx.fillRect(b.x, b.y, b.w, b.h);
ctx.shadowBlur = 0;
// Armored Inset
ctx.fillStyle = 'rgba(11, 12, 16, 0.65)';
ctx.fillRect(b.x + 3, b.y + 3, b.w - 6, b.h - 6);
if (b.hp > 1) {
ctx.fillStyle = b.flash > 0 ? '#fff' : c;
ctx.fillRect(b.x + b.w/2 - 5, b.y + b.h/2 - 2, 10, 4);
}
if (b.flash > 0) b.flash--;
});
// Additive Juice (Particles + Trail)
ctx.globalCompositeOperation = 'lighter';
particles.forEach(p => {
ctx.globalAlpha = p.life;
ctx.fillStyle = p.color;
ctx.fillRect(p.x, p.y, p.size, p.size);
});
for (let i = 0; i < ball.trail.length; i++) {
const t = ball.trail[i];
const a = 1 - (i / ball.trail.length);
ctx.beginPath();
ctx.arc(t.x, t.y, ball.r * a, 0, Math.PI * 2);
ctx.fillStyle = `rgba(69, 243, 255, ${a * 0.4})`;
ctx.fill();
}
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = 'source-over';
// Ball
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
ctx.fillStyle = '#fff';
ctx.shadowColor = '#45f3ff';
ctx.shadowBlur = 18;
ctx.fill();
ctx.shadowBlur = 0;
// Paddle (with squash)
const sq = paddle.squash;
const pw = paddle.w * (1 + sq * 0.25);
const ph = paddle.h * (1 - sq * 0.45);
const px = paddle.x - (pw - paddle.w) / 2;
const py = paddle.y + (paddle.h - ph);
ctx.fillStyle = '#fff';
ctx.shadowColor = '#ff2a6d';
ctx.shadowBlur = 15 + sq * 15;
ctx.fillRect(px, py, pw, ph);
ctx.shadowBlur = 0;
ctx.restore(); // End shake
// --- UI (Unshaken) ---
ctx.font = 'bold 20px "Courier New", monospace';
ctx.fillStyle = '#fff';
ctx.textAlign = 'left';
ctx.fillText(`SCORE ${String(score).padStart(6, '0')}`, 20, 32);
ctx.textAlign = 'center';
ctx.fillStyle = '#888';
ctx.fillText(`STAGE ${level + 1} / ${rawLevels.length}`, 400, 32);
ctx.textAlign = 'right';
ctx.fillStyle = '#ff2a6d';
ctx.fillText('●'.repeat(Math.max(0, lives)), 780, 32);
// Combo Pop
if (combo > 1 && state === 'PLAYING') {
ctx.save();
ctx.translate(400, 110);
const scale = 1 + Math.min(combo * 0.08, 1.2) + comboPop;
ctx.scale(scale, scale);
ctx.fillStyle = `rgba(249, 203, 40, ${Math.min(1, 0.4 + combo / 4)})`;
ctx.font = 'bold 34px "Courier New", monospace';
ctx.shadowColor = '#f9cb28';
ctx.shadowBlur = 15;
ctx.fillText(`COMBO ×${combo}`, 0, 0);
ctx.restore();
}
// Overlays
if (state !== 'PLAYING') {
ctx.fillStyle = 'rgba(11, 12, 16, 0.75)';
ctx.fillRect(0, 190, 800, 220);
ctx.strokeStyle = 'rgba(69, 243, 255, 0.3)';
ctx.lineWidth = 2;
ctx.strokeRect(0, 190, 800, 220);
ctx.textAlign = 'center';
ctx.shadowBlur = 20;
if (state === 'MENU') {
ctx.shadowColor = '#45f3ff';
ctx.fillStyle = '#fff';
ctx.font = 'bold 44px "Courier New", monospace';
ctx.fillText(lives === 3 ? 'NEON BREAKOUT' : 'READY?', 400, 265);
ctx.shadowBlur = 0;
ctx.font = '18px "Courier New", monospace';
ctx.fillStyle = '#aaa';
ctx.fillText('CLICK / TAP / SPACE TO LAUNCH', 400, 320);
ctx.fillStyle = '#666';
ctx.fillText('MOVE: MOUSE • ← → • A D', 400, 355);
}
else if (state === 'LEVEL_CLEAR') {
ctx.shadowColor = '#45f3ff';
ctx.fillStyle = '#fff';
ctx.font = 'bold 44px "Courier New", monospace';
ctx.fillText('STAGE CLEAR!', 400, 300);
}
else if (state === 'GAME_OVER' || state === 'VICTORY') {
const win = state === 'VICTORY';
ctx.shadowColor = win ? '#f9cb28' : '#ff2a6d';
ctx.fillStyle = '#fff';
ctx.font = 'bold 48px "Courier New", monospace';
ctx.fillText(win ? 'SYSTEM CLEARED' : 'GAME OVER', 400, 270);
ctx.shadowBlur = 0;
ctx.font = 'bold 20px "Courier New", monospace';
ctx.fillStyle = '#aaa';
ctx.fillText(`FINAL SCORE: ${score}`, 400, 320);
ctx.fillStyle = '#fff';
// Blinking prompt
if (Math.floor(performance.now() / 500) % 2 === 0) {
ctx.fillText('PRESS SPACE TO RESTART', 400, 365);
}
}
ctx.shadowBlur = 0;
}
}
// --- Boot ---
loadLevel(0);
resetBall();
let lastTime = performance.now();
function loop(time) {
let dt = (time - lastTime) / 1000;
lastTime = time;
if (dt > 0.05) dt = 0.05; // Cap lag spikes
update(dt * 60); // Normalize to 60fps units
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>
</body>
</html>

