Run details
- Model
- meta/muse-wonder
- Route
- OpenRouter API, one user message
- Served by
- Meta
- Reasoning
- provider default, 1,398 reasoning tokens
- Limits
- 60 min · 30,000 output tokens ($0.60 at $20/M)
- Date
- 22 Oct 2026
- Time
- 74 s
- Size
- 35 kB
- Tokens
- 85 in, 13,161 out
- List price
- $0.26 ($4 in / $20 out per M)
- Paid
- $0.26
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
35 kB 864 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; margin: 0; padding: 0; }
:root{
--bg:#070812; --panel:#12142a; --cyan:#38f5ff; --pink:#ff4fd8;
--yellow:#ffe75a; --text:#f4f6ff; --muted:#9aa3c7;
}
html,body{
min-height:100%;
background:
radial-gradient(900px 500px at 20% -10%, rgba(83,48,255,.35), transparent 60%),
radial-gradient(800px 500px at 90% 0%, rgba(255,55,180,.22), transparent 55%),
radial-gradient(700px 700px at 50% 110%, rgba(0,220,255,.18), transparent 60%),
var(--bg);
color:var(--text);
font-family: system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
overflow-x:hidden;
}
body{ display:flex; align-items:center; justify-content:center; padding:18px 12px 24px; }
.wrap{ width:min(940px,100%); }
header{
display:flex; align-items:flex-end; justify-content:space-between; gap:14px; flex-wrap:wrap;
margin-bottom:12px;
}
h1{
font-size:clamp(28px,5vw,50px); line-height:.95; letter-spacing:.06em; font-weight:900;
background:linear-gradient(90deg,#fff,var(--cyan) 35%,var(--pink) 70%,var(--yellow));
-webkit-background-clip:text; background-clip:text; color:transparent;
text-shadow:0 0 28px rgba(56,245,255,.12);
}
.sub{ color:var(--muted); font-size:14px; margin-top:6px; }
.buttons{ display:flex; gap:8px; flex-wrap:wrap; }
button{
appearance:none; border:1px solid rgba(255,255,255,.18); color:var(--text);
background:linear-gradient(180deg,#23264a,#151733); padding:10px 14px; border-radius:999px;
font-weight:800; letter-spacing:.04em; cursor:pointer; box-shadow:0 6px 18px rgba(0,0,0,.35);
}
button:hover{ filter:brightness(1.18); transform:translateY(-1px); }
button:active{ transform:translateY(1px); }
.primary{ background:linear-gradient(90deg,#00d6ff,#ff4fd8); border:none; color:white; }
.hud{
display:grid; grid-template-columns:repeat(5,1fr); gap:8px; margin-bottom:10px;
}
.card{
background:linear-gradient(180deg,rgba(255,255,255,.08),rgba(255,255,255,.03));
border:1px solid rgba(255,255,255,.12); border-radius:14px; padding:9px 12px;
min-height:58px; box-shadow: inset 0 1px 0 rgba(255,255,255,.08);
}
.label{ font-size:11px; letter-spacing:.16em; color:var(--muted); font-weight:800; }
.value{ font-size:clamp(19px,3vw,28px); font-weight:900; margin-top:2px; white-space:nowrap; }
#lives{ color:#ff6b81; letter-spacing:.08em; }
#powerStatus{ font-size:13px; color:var(--yellow); font-weight:800; white-space:normal; line-height:1.25; }
.stage{
position:relative; border-radius:20px; overflow:hidden;
border:1px solid rgba(255,255,255,.16);
background:#090a1c;
box-shadow:0 24px 70px rgba(0,0,0,.55), 0 0 0 6px rgba(255,255,255,.03), 0 0 55px rgba(64,80,255,.18);
}
canvas{ display:block; width:100%; height:auto; aspect-ratio: 900 / 620; touch-action:none; cursor:none; }
.overlay{
position:absolute; inset:0; display:flex; align-items:center; justify-content:center; text-align:center;
background:radial-gradient(circle at center, rgba(10,10,30,.28), rgba(4,4,14,.78));
backdrop-filter: blur(2px); padding:24px; z-index:5;
}
.overlay.hidden{ display:none; }
.panel{
max-width:620px; background:rgba(14,16,38,.82); border:1px solid rgba(255,255,255,.16);
border-radius:22px; padding:26px 28px; box-shadow:0 20px 60px rgba(0,0,0,.5);
}
.panel h2{ font-size:clamp(30px,6vw,54px); letter-spacing:.05em; line-height:1; margin-bottom:10px; }
.panel p{ color:#d9def7; line-height:1.55; margin:8px 0; }
.panel .small{ color:var(--muted); font-size:13px; }
.keys{ display:flex; gap:7px; justify-content:center; flex-wrap:wrap; margin:14px 0; }
kbd{
background:#22254a; border:1px solid rgba(255,255,255,.22); border-bottom-width:3px;
padding:4px 8px; border-radius:7px; font-family:inherit; font-weight:800; font-size:12px;
}
.legend{ display:flex; justify-content:center; gap:8px; flex-wrap:wrap; margin-top:12px; }
.pill{ padding:6px 9px; border-radius:999px; font-size:12px; font-weight:900; color:#07101c; }
.w{background:#5aff8a}.m{background:#ffdf5a}.s{background:#62c9ff}.l{background:#ff7d9c}
footer{ display:flex; justify-content:space-between; gap:12px; flex-wrap:wrap; color:var(--muted); font-size:13px; margin-top:10px; line-height:1.5; }
.toast{
position:absolute; left:50%; top:16px; transform:translateX(-50%) translateY(-8px);
background:rgba(10,12,30,.86); border:1px solid rgba(255,255,255,.2); color:#fff;
padding:8px 13px; border-radius:999px; font-weight:900; letter-spacing:.05em; opacity:0;
transition:.25s; pointer-events:none; z-index:6; white-space:nowrap;
}
.toast.show{ opacity:1; transform:translateX(-50%) translateY(0); }
@media(max-width:650px){
.hud{ grid-template-columns:repeat(2,1fr); }
.hud .card:last-child{ grid-column:span 2; }
canvas{ cursor:auto; }
footer{ justify-content:center; text-align:center; }
}
</style>
</head>
<body>
<main class="wrap">
<header>
<div>
<h1>NEON BREAKOUT</h1>
<div class="sub">Smash bricks, catch power-ups, chain combos. 5 handcrafted levels — then the speed keeps rising.</div>
</div>
<div class="buttons">
<button id="startBtn" class="primary">▶ START</button>
<button id="pauseBtn">⏸ PAUSE</button>
<button id="soundBtn">🔊 SOUND</button>
<button id="restartBtn">↻ RESTART</button>
</div>
</header>
<section class="hud" aria-label="Game stats">
<div class="card"><div class="label">SCORE</div><div class="value" id="score">0</div></div>
<div class="card"><div class="label">BEST</div><div class="value" id="best">0</div></div>
<div class="card"><div class="label">LEVEL</div><div class="value" id="level">1 / 5</div></div>
<div class="card"><div class="label">LIVES</div><div class="value" id="lives">♥♥♥</div></div>
<div class="card"><div class="label">ACTIVE POWER</div><div class="value" id="powerStatus">—</div></div>
</section>
<section class="stage">
<canvas id="game" width="900" height="620"></canvas>
<div class="toast" id="toast"></div>
<div class="overlay" id="overlay">
<div class="panel">
<h2 id="ovTitle">READY?</h2>
<p id="ovText">Move with your mouse or arrow keys. Launch the ball, clear every colored brick, and don't let the ball fall.</p>
<div class="keys">
<span><kbd>←</kbd> <kbd>→</kbd> / <kbd>A</kbd> <kbd>D</kbd> Move</span>
<span><kbd>SPACE</kbd> Launch</span>
<span><kbd>P</kbd> Pause</span>
<span><kbd>M</kbd> Mute</span>
</div>
<button class="primary" id="bigBtn" style="font-size:17px;padding:13px 22px;">START GAME</button>
<div class="legend">
<span class="pill w">W = WIDE</span>
<span class="pill m">M = MULTI-BALL</span>
<span class="pill s">S = SLOW-MO</span>
<span class="pill l">L = EXTRA LIFE</span>
</div>
<p class="small">Orange bricks explode. Gray steel bricks can't be broken. Cracked bricks need extra hits.</p>
</div>
</div>
</section>
<footer>
<div>🖱️ Mouse / touch: move paddle • Click or Space: launch</div>
<div>Tip: hit with the edge of the paddle for sharp angles. Build combos for up to ×5 points!</div>
</footer>
</main>
<script>
(() => {
"use strict";
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const el = id => document.getElementById(id);
const scoreEl = el('score'), bestEl = el('best'), levelEl = el('level'),
livesEl = el('lives'), powerEl = el('powerStatus'),
overlay = el('overlay'), ovTitle = el('ovTitle'), ovText = el('ovText'),
bigBtn = el('bigBtn'), toast = el('toast');
const clamp = (v,a,b)=>Math.max(a,Math.min(b,v));
const rand = (a,b)=>a+Math.random()*(b-a);
const TAU = Math.PI*2;
let best = 0;
try { best = +localStorage.getItem('neonBreakoutBest') || 0; } catch(e) {}
bestEl.textContent = best.toLocaleString();
// ---------- Audio (generated, no files) ----------
let audioCtx = null, muted = false;
function ensureAudio(){
if(!audioCtx){
try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } catch(e) {}
}
if(audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
}
function tone(freq=440, dur=.07, type='square', vol=.045, slide=0){
if(muted || !audioCtx) return;
const t = audioCtx.currentTime;
const o = audioCtx.createOscillator(), 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(.0001, t+dur);
o.connect(g).connect(audioCtx.destination);
o.start(t); o.stop(t+dur+.02);
}
const sfx = {
paddle(){ tone(210,.055,'square',.05,120); },
wall(){ tone(170,.04,'square',.032,40); },
brick(combo=0){ tone(330+Math.min(combo,20)*18,.07,'square',.055,260); },
hard(){ tone(125,.06,'sawtooth',.05,-30); },
boom(){ tone(95,.22,'sawtooth',.09,-55); tone(55,.28,'triangle',.08,-20); },
power(){ [523,659,784,1046].forEach((f,i)=>setTimeout(()=>tone(f,.09,'triangle',.055),i*45)); },
lose(){ tone(320,.28,'sawtooth',.06,-220); },
launch(){ tone(420,.09,'triangle',.05,240); },
clear(){ [392,523,659,784,1046,1318].forEach((f,i)=>setTimeout(()=>tone(f,.11,'triangle',.055),i*55)); },
over(){ [440,349,293,220].forEach((f,i)=>setTimeout(()=>tone(f,.16,'sawtooth',.05),i*130)); }
};
// ---------- Levels ----------
// 1,2,3 = HP | E = explosive | X = steel | P = power brick | . = empty
const LEVELS = [
{
name:"First Contact", speed:390,
map:[
"1111111111",
"1111111111",
"2222222222",
"1111111111",
"1P111111P1",
"1111111111"
]
},
{
name:"Checker Storm", speed:425,
map:[
"1.1.11.1.1",
".2.2..2.2.",
"1.1.EE.1.1",
".2.2..2.2.",
"1.1.11.1.1",
"..P....P..",
"1111111111"
]
},
{
name:"The Fortress", speed:455,
map:[
"..111111..",
".12222221.",
"12X3333X21",
"123E22E321",
".12222221.",
"..1P11P1..",
"1111111111"
]
},
{
name:"Diamond Mine", speed:490,
map:[
"....11....",
"...1221...",
"..123321..",
".12E33E21.",
"112X33X211",
".12P22P21.",
"..111111..",
"1111111111"
]
},
{
name:"Neon Core", speed:530,
map:[
"3X111111X3",
"122E22E221",
"1123333211",
"111X22X111",
"12P1331P21",
"1E111111E1",
"2222222222",
"1111111111"
]
}
];
const COLORS = {
1:['#37f5ff','#087f9e'], 2:['#ffe75a','#d68a00'], 3:['#ff4fd8','#8d1590'],
E:['#ff9b3d','#c43d00'], X:['#aeb7cc','#4c5268'], P:['#63ff8f','#109447']
};
// ---------- State ----------
let state = 'menu'; // menu, ready, playing, paused, between, gameover, win
let score=0, lives=3, levelIndex=0, loop=0;
let combo=0, maxCombo=0, shake=0, flash=0, time=0;
let bricks=[], balls=[], particles=[], powerups=[], floaters=[];
let wideTimer=0, slowTimer=0, paddleSquash=0, levelIntroTimer=0;
let keys = {left:false,right:false};
let mouseX = W/2, usingMouse=false;
const paddle = { x:W/2, y:H-42, baseW:118, w:118, h:16, speed:760 };
const BRICK = { cols:10, gap:7, h:25, x:34, y:78 };
BRICK.w = (W - BRICK.x*2 - BRICK.gap*(BRICK.cols-1)) / BRICK.cols;
function currentSpeed(){
const base = LEVELS[levelIndex].speed + loop*35;
return base * (slowTimer>0 ? .68 : 1);
}
function breakablesLeft(){ return bricks.filter(b=>b.hp>0).length; }
function buildLevel(idx){
bricks=[]; powerups=[]; particles=[]; floaters=[];
combo=0; wideTimer=0; slowTimer=0;
paddle.w=paddle.baseW; paddle.x=W/2;
const lvl=LEVELS[idx];
lvl.map.forEach((row,r)=>{
[...row].forEach((ch,c)=>{
if(ch==='.'||ch===' ') return;
let hp=1, type=ch;
if(ch==='2') hp=2; if(ch==='3') hp=3; if(ch==='X') hp=Infinity;
bricks.push({
x:BRICK.x+c*(BRICK.w+BRICK.gap), y:BRICK.y+r*(BRICK.h+BRICK.gap),
w:BRICK.w, h:BRICK.h, hp, maxHp:hp, type, dead:false, pop:0
});
});
});
resetBall();
}
function makeBall(x,y,vx,vy){
return { x,y,vx,vy,r:9, trail:[], stuck:false };
}
function resetBall(){
balls=[makeBall(paddle.x, paddle.y-14, 0,0)];
balls[0].stuck=true;
}
function launchBalls(){
if(state!=='ready' && state!=='playing') return;
let launched=false;
balls.forEach(b=>{
if(b.stuck){
const a = rand(-.5,-.22) * (Math.random()<.5?-1:1) - Math.PI/2;
// angle around upward direction
const ang = -Math.PI/2 + rand(-.45,.45);
const sp=currentSpeed();
b.vx=Math.cos(ang)*sp; b.vy=Math.sin(ang)*sp; b.stuck=false; launched=true;
}
});
if(launched){ state='playing'; hideOverlay(); sfx.launch(); showToast('GO!'); }
}
function resetGame(){
score=0; lives=3; levelIndex=0; loop=0; maxCombo=0;
buildLevel(0);
state='ready';
updateHUD();
showOverlay('LEVEL 1 — '+LEVELS[0].name.toUpperCase(),
'Clear all colored bricks to advance. Steel bricks are indestructible — use them to your advantage!',
'LAUNCH BALL');
}
// ---------- Juice ----------
function burst(x,y,color,n=18,speed=280){
for(let i=0;i<n;i++){
const a=Math.random()*TAU, s=rand(40,speed);
particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s-50,life:rand(.35,.85),max:.85,
size:rand(2,6),color,shape:Math.random()<.25?'rect':'circle',rot:rand(0,TAU),vr:rand(-8,8)});
}
if(particles.length>550) particles.splice(0, particles.length-550);
}
function floater(x,y,text,color='#fff',size=18){
floaters.push({x,y,text,color,size,life:1,vy:-48});
}
function showToast(txt,ms=950){
toast.textContent=txt; toast.classList.add('show');
clearTimeout(showToast._t);
showToast._t=setTimeout(()=>toast.classList.remove('show'),ms);
}
// ---------- Power-ups ----------
const POWER_TYPES=[
{k:'W',label:'WIDE',color:'#5aff8a'},
{k:'M',label:'MULTI',color:'#ffdf5a'},
{k:'S',label:'SLOW',color:'#62c9ff'},
{k:'L',label:'LIFE',color:'#ff7d9c'}
];
function maybeDrop(x,y,guaranteed){
if(!guaranteed && Math.random()>.17) return;
const p=POWER_TYPES[Math.floor(Math.random()*POWER_TYPES.length)];
powerups.push({x,y,w:36,h:20,vy:150,k:p.k,label:p.label,color:p.color,rot:0});
}
function applyPower(p){
sfx.power(); showToast(p.label+'!');
burst(p.x,p.y,p.color,24,240);
if(p.k==='W'){ wideTimer=12; paddle.w=paddle.baseW*1.55; floater(p.x,p.y-12,'WIDE PADDLE','#5aff8a',20); }
if(p.k==='S'){ slowTimer=9; normalizeBalls(); floater(p.x,p.y-12,'SLOW-MO','#62c9ff',20); }
if(p.k==='L'){ lives=Math.min(9,lives+1); floater(p.x,p.y-12,'+1 LIFE','#ff7d9c',22); toneLife(); }
if(p.k==='M'){
const src=[...balls].filter(b=>!b.stuck);
src.forEach(b=>{
if(balls.length>=6) return;
const sp=Math.hypot(b.vx,b.vy)||currentSpeed(), a=Math.atan2(b.vy,b.vx)+rand(.45,.75)*(Math.random()<.5?-1:1);
balls.push(makeBall(b.x,b.y,Math.cos(a)*sp,Math.sin(a)*sp));
});
floater(p.x,p.y-12,'MULTI-BALL','#ffdf5a',20);
}
updateHUD();
}
function toneLife(){ setTimeout(()=>tone(880,.12,'triangle',.06,220),60); }
function normalizeBalls(){
const sp=currentSpeed();
balls.forEach(b=>{
if(b.stuck) return;
const m=Math.hypot(b.vx,b.vy)||1;
b.vx=b.vx/m*sp; b.vy=b.vy/m*sp;
if(Math.abs(b.vy)<sp*.22){ b.vy=(b.vy<0?-1:1)*sp*.22; const s2=Math.hypot(b.vx,b.vy); b.vx*=sp/s2; b.vy*=sp/s2; }
});
}
// ---------- Brick damage / explosions ----------
function damageBrick(br, ball){
if(br.dead) return;
if(br.hp===Infinity){ sfx.hard(); shake=Math.max(shake,2); burst(br.x+br.w/2,br.y+br.h/2,'#aeb7cc',5,130); return; }
if(br.type==='E'){ explodeBrick(br); return; }
br.hp--; br.pop=1;
if(br.hp>0){ sfx.hard(); burst(br.x+br.w/2,br.y+br.h/2,COLORS[br.type][0],7,160); return; }
destroyBrick(br, true);
}
function destroyBrick(br, drop=true){
if(br.dead || br.hp===Infinity) return;
br.dead=true; combo++; maxCombo=Math.max(maxCombo,combo);
const mult=1+Math.min(4,Math.floor(combo/8));
const pts=(br.type==='P'?100:50)*mult;
score+=pts;
const col=COLORS[br.type][0];
burst(br.x+br.w/2,br.y+br.h/2,col, br.type==='P'?30:18, 300);
floater(br.x+br.w/2,br.y, '+'+pts, col, mult>1?20:16);
if(mult>1 && combo%8===0) floater(W/2,150,'COMBO ×'+mult,'#ffe75a',30);
shake=Math.max(shake, br.type==='P'?5:2.5);
sfx.brick(combo);
if(drop) maybeDrop(br.x+br.w/2,br.y+br.h/2, br.type==='P');
updateHUD();
checkLevelClear();
}
function explodeBrick(br){
if(br.dead) return;
br.dead=true; score+=75; combo++;
shake=Math.max(shake,11); flash=.22;
sfx.boom();
burst(br.x+br.w/2,br.y+br.h/2,'#ff9b3d',46,430);
burst(br.x+br.w/2,br.y+br.h/2,'#ffe75a',22,300);
floater(br.x+br.w/2,br.y,'BOOM +75','#ff9b3d',21);
const cx=br.x+br.w/2, cy=br.y+br.h/2;
bricks.forEach(o=>{
if(o===br||o.dead) return;
const ox=o.x+o.w/2, oy=o.y+o.h/2;
if(Math.hypot(ox-cx,oy-cy) < 105){
if(o.hp===Infinity) return;
if(o.type==='E' && !o.dead){ setTimeout(()=>explodeBrick(o),45); }
else { o.hp=0; destroyBrick(o,true); }
}
});
updateHUD(); checkLevelClear();
}
function checkLevelClear(){
if(state!=='playing') return;
if(breakablesLeft()<=0){
state='between'; levelIntroTimer=1.35;
score += 250 + lives*25;
sfx.clear();
for(let i=0;i<5;i++) setTimeout(()=>burst(rand(100,W-100),rand(100,300),
['#38f5ff','#ff4fd8','#ffe75a','#63ff8f'][i%4],35,380), i*110);
showToast('LEVEL CLEAR! BONUS +'+(250+lives*25),1400);
updateHUD();
}
}
function nextLevel(){
levelIndex++;
if(levelIndex>=LEVELS.length){
if(loop===0){
state='win';
saveBest();
showOverlay('🏆 YOU WIN!','You smashed the Neon Core! Final score: <b>'+score.toLocaleString()+'</b><br>Max combo: '+maxCombo+'<br><br>Press play again for Endless Mode — faster balls, same 5 arenas.','PLAY ENDLESS MODE');
return;
}
levelIndex=0;
}
loop += (levelIndex===0 && state!=='win') ? 0 : 0; // loop incremented on endless start below
buildLevel(levelIndex);
state='ready';
showOverlay('LEVEL '+(levelIndex+1)+' — '+LEVELS[levelIndex].name.toUpperCase(),
loop>0 ? 'Endless round '+(loop+1)+'. The ball is faster now. Good luck!' : 'Get ready. Click or press Space to launch.',
'LAUNCH BALL');
updateHUD();
}
// ---------- HUD / overlay ----------
function updateHUD(){
scoreEl.textContent=score.toLocaleString();
bestEl.textContent=Math.max(best,score).toLocaleString();
levelEl.textContent=(levelIndex+1)+' / '+LEVELS.length+(loop>0?' ∞':'');
livesEl.textContent='♥'.repeat(clamp(lives,0,9))||'—';
let p=[];
if(wideTimer>0) p.push('WIDE '+Math.ceil(wideTimer)+'s');
if(slowTimer>0) p.push('SLOW '+Math.ceil(slowTimer)+'s');
if(combo>=3) p.push('COMBO ×'+(1+Math.min(4,Math.floor(combo/8)))+' ('+combo+')');
powerEl.textContent=p.length?p.join(' • '):'—';
}
function showOverlay(title,html,btn){
ovTitle.innerHTML=title; ovText.innerHTML=html; bigBtn.textContent=btn;
overlay.classList.remove('hidden');
}
function hideOverlay(){ overlay.classList.add('hidden'); }
function saveBest(){
if(score>best){ best=score; try{ localStorage.setItem('neonBreakoutBest',String(best)); }catch(e){} }
bestEl.textContent=best.toLocaleString();
}
function gameOver(){
state='gameover'; saveBest(); sfx.over(); shake=14;
showOverlay('GAME OVER','Score: <b>'+score.toLocaleString()+'</b> • Best: <b>'+best.toLocaleString()+'</b><br>Level reached: '+(levelIndex+1)+' • Max combo: '+maxCombo,'TRY AGAIN');
}
// ---------- Update ----------
function update(dt){
time+=dt;
shake=Math.max(0,shake-dt*28);
flash=Math.max(0,flash-dt);
paddleSquash=Math.max(0,paddleSquash-dt*7);
if(wideTimer>0){ wideTimer-=dt; if(wideTimer<=0) paddle.w=paddle.baseW; }
if(slowTimer>0){ const was=slowTimer; slowTimer-=dt; if(was>0&&slowTimer<=0) normalizeBalls(); }
if(state==='between'){
levelIntroTimer-=dt;
updateParticles(dt);
if(levelIntroTimer<=0) nextLevel();
updateHUD(); return;
}
if(state!=='playing' && state!=='ready' && state!=='paused') { updateParticles(dt); return; }
if(state==='paused') return;
// Paddle keyboard
const dir=(keys.right?1:0)-(keys.left?1:0);
if(dir!==0){ paddle.x += dir*paddle.speed*dt; usingMouse=false; }
else if(usingMouse){ paddle.x += (mouseX-paddle.x)*Math.min(1,dt*22); }
paddle.x=clamp(paddle.x,paddle.w/2+8,W-paddle.w/2-8);
// Stuck balls follow paddle
balls.forEach(b=>{ if(b.stuck){ b.x=paddle.x; b.y=paddle.y-b.r-3; b.trail.length=0; } });
if(state==='playing'){
// Move balls in sub-steps to avoid tunneling
balls.forEach(b=>{
if(b.stuck) return;
// Maintain intended speed (keeps slow-mo consistent)
const target=currentSpeed(), m=Math.hypot(b.vx,b.vy);
if(m>0 && Math.abs(m-target)>2){ b.vx*=target/m; b.vy*=target/m; }
const dist=Math.hypot(b.vx,b.vy)*dt, steps=Math.max(1,Math.ceil(dist/(b.r*.75)));
const sdt=dt/steps;
for(let s=0;s<steps && !b.dead;s++){
b.x+=b.vx*sdt; b.y+=b.vy*sdt;
collideWalls(b); collidePaddle(b); collideBricks(b);
}
b.trail.unshift({x:b.x,y:b.y});
if(b.trail.length>13) b.trail.pop();
if(b.y-b.r>H+30) b.dead=true;
});
const before=balls.length;
balls=balls.filter(b=>!b.dead);
if(before>0 && balls.length===0){
lives--; combo=0; sfx.lose(); shake=12; flash=.18;
burst(paddle.x,H-20,'#ff6b81',35,320);
updateHUD();
if(lives<=0){ gameOver(); return; }
resetBall(); state='ready';
showToast('BALL LOST — '+lives+(lives===1?' LIFE':' LIVES')+' LEFT');
setTimeout(()=>{ if(state==='ready') showOverlay('BALL READY','Lives left: '+'♥'.repeat(lives)+'<br>Click or press Space to launch.','LAUNCH'); },350);
}
// Power-ups
powerups.forEach(p=>{
p.y+=p.vy*dt; p.rot+=dt*3;
if(p.y+p.h>=paddle.y && p.y<=paddle.y+paddle.h+8 &&
p.x+p.w/2>=paddle.x-paddle.w/2 && p.x-p.w/2<=paddle.x+paddle.w/2){
p.caught=true; applyPower(p);
}
});
powerups=powerups.filter(p=>!p.caught && p.y<H+40);
}
bricks.forEach(b=>b.pop=Math.max(0,b.pop-dt*6));
updateParticles(dt);
updateHUD();
}
function updateParticles(dt){
particles.forEach(p=>{ p.life-=dt; p.x+=p.vx*dt; p.y+=p.vy*dt; p.vy+=420*dt; p.vx*=.99; p.rot+=p.vr*dt; });
particles=particles.filter(p=>p.life>0);
floaters.forEach(f=>{ f.life-=dt*.75; f.y+=f.vy*dt; });
floaters=floaters.filter(f=>f.life>0);
}
function collideWalls(b){
if(b.x-b.r<8){ b.x=8+b.r; b.vx=Math.abs(b.vx); sfx.wall(); }
if(b.x+b.r>W-8){ b.x=W-8-b.r; b.vx=-Math.abs(b.vx); sfx.wall(); }
if(b.y-b.r<8){ b.y=8+b.r; b.vy=Math.abs(b.vy); sfx.wall(); }
}
function collidePaddle(b){
if(b.vy<=0) return;
const top=paddle.y, left=paddle.x-paddle.w/2, right=paddle.x+paddle.w/2;
if(b.y+b.r>=top && b.y-b.r<=top+paddle.h+10 && b.x>=left-b.r && b.x<=right+b.r){
const rel=clamp((b.x-paddle.x)/(paddle.w/2),-1,1);
const maxAngle=1.12; // ~64°
const ang=-Math.PI/2 + rel*maxAngle;
const sp=currentSpeed();
b.vx=Math.cos(ang)*sp; b.vy=Math.sin(ang)*sp;
b.y=top-b.r-1;
combo=0; paddleSquash=1;
shake=Math.max(shake, Math.abs(rel)*2);
burst(b.x,top, '#38f5ff', 8, 150);
sfx.paddle(); updateHUD();
}
}
function collideBricks(b){
for(const br of bricks){
if(br.dead) continue;
const cx=clamp(b.x,br.x,br.x+br.w), cy=clamp(b.y,br.y,br.y+br.h);
const dx=b.x-cx, dy=b.y-cy;
if(dx*dx+dy*dy <= b.r*b.r){
// Resolve by smallest penetration axis
const overlapL=Math.abs((b.x+b.r)-br.x), overlapR=Math.abs((br.x+br.w)-(b.x-b.r));
const overlapT=Math.abs((b.y+b.r)-br.y), overlapB=Math.abs((br.y+br.h)-(b.y-b.r));
const min=Math.min(overlapL,overlapR,overlapT,overlapB);
if(min===overlapT){ b.y=br.y-b.r; b.vy=-Math.abs(b.vy); }
else if(min===overlapB){ b.y=br.y+br.h+b.r; b.vy=Math.abs(b.vy); }
else if(min===overlapL){ b.x=br.x-b.r; b.vx=-Math.abs(b.vx); }
else { b.x=br.x+br.w+b.r; b.vx=Math.abs(b.vx); }
// Prevent near-horizontal stalls
const sp=currentSpeed(), m=Math.hypot(b.vx,b.vy)||sp;
b.vx*=sp/m; b.vy*=sp/m;
if(Math.abs(b.vy)<sp*.18){ b.vy=(b.vy<0?-1:1)*sp*.18; const m2=Math.hypot(b.vx,b.vy); b.vx*=sp/m2; b.vy*=sp/m2; }
damageBrick(br,b);
break;
}
}
}
// ---------- Draw ----------
function roundRect(x,y,w,h,r){
r=Math.min(r,w/2,h/2);
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 draw(){
ctx.save();
ctx.clearRect(0,0,W,H);
// Background
const g=ctx.createLinearGradient(0,0,0,H);
g.addColorStop(0,'#0b0d26'); g.addColorStop(.55,'#0a1230'); g.addColorStop(1,'#12081f');
ctx.fillStyle=g; ctx.fillRect(0,0,W,H);
// Grid + stars
ctx.strokeStyle='rgba(90,120,255,.07)'; ctx.lineWidth=1;
for(let x=0;x<=W;x+=45){ ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x,H); ctx.stroke(); }
for(let y=0;y<=H;y+=45){ ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(W,y); ctx.stroke(); }
ctx.fillStyle='rgba(255,255,255,.35)';
for(let i=0;i<45;i++){
const sx=(i*167.3)%W, sy=(i*89.7)%H, tw=.35+.65*Math.abs(Math.sin(time*1.4+i));
ctx.globalAlpha=.12*tw; ctx.fillRect(sx,sy,2,2);
}
ctx.globalAlpha=1;
if(shake>0) ctx.translate(rand(-shake,shake),rand(-shake,shake));
// Arena border
ctx.strokeStyle='rgba(56,245,255,.35)'; ctx.lineWidth=2;
ctx.strokeRect(8.5,8.5,W-17,H-17);
ctx.shadowBlur=18; ctx.shadowColor='rgba(56,245,255,.45)';
ctx.strokeRect(8.5,8.5,W-17,H-17); ctx.shadowBlur=0;
drawBricks(); drawPowerups(); drawPaddle(); drawBalls(); drawParticles(); drawFloaters();
// Ready hint arrow
if(state==='ready'){
ctx.fillStyle='rgba(255,255,255,.9)';
ctx.font='800 15px system-ui,sans-serif'; ctx.textAlign='center';
const bob=Math.sin(time*5)*5;
ctx.fillText('CLICK OR PRESS SPACE TO LAUNCH', W/2, H-92+bob);
ctx.beginPath();
ctx.moveTo(W/2-9,H-78+bob); ctx.lineTo(W/2+9,H-78+bob); ctx.lineTo(W/2,H-66+bob); ctx.closePath();
ctx.fillStyle='#ffe75a'; ctx.fill();
}
if(state==='paused'){
ctx.fillStyle='rgba(4,5,18,.62)'; ctx.fillRect(0,0,W,H);
ctx.fillStyle='#fff'; ctx.font='900 52px system-ui,sans-serif'; ctx.textAlign='center';
ctx.fillText('PAUSED',W/2,H/2-8);
ctx.font='700 17px system-ui,sans-serif'; ctx.fillStyle='#bdc6ee';
ctx.fillText('Press P or click Pause to resume',W/2,H/2+26);
}
if(flash>0){ ctx.fillStyle=`rgba(255,120,60,${flash*.55})`; ctx.fillRect(0,0,W,H); }
ctx.restore();
// Vignette
const v=ctx.createRadialGradient(W/2,H/2,H*.35,W/2,H/2,H*.85);
v.addColorStop(0,'rgba(0,0,0,0)'); v.addColorStop(1,'rgba(0,0,0,.38)');
ctx.fillStyle=v; ctx.fillRect(0,0,W,H);
}
function drawBricks(){
bricks.forEach(br=>{
if(br.dead) return;
const [main,dark]=COLORS[br.type];
const scale=1+br.pop*.12, w=br.w*scale, h=br.h*scale;
const x=br.x+(br.w-w)/2, y=br.y+(br.h-h)/2;
const g=ctx.createLinearGradient(x,y,x,y+h);
g.addColorStop(0,main); g.addColorStop(1,dark);
ctx.save();
ctx.shadowBlur=br.type==='X'?0:13; ctx.shadowColor=main;
ctx.fillStyle=g; roundRect(x,y,w,h,7); ctx.fill();
ctx.shadowBlur=0;
ctx.fillStyle='rgba(255,255,255,.28)'; roundRect(x+4,y+3,w-8,h*.36,5); ctx.fill();
ctx.strokeStyle='rgba(255,255,255,.35)'; ctx.lineWidth=1; roundRect(x+.5,y+.5,w-1,h-1,7); ctx.stroke();
if(br.type==='E'){
ctx.fillStyle='#fff'; ctx.font='900 15px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle';
ctx.fillText('✹', br.x+br.w/2, br.y+br.h/2+1);
} else if(br.type==='P'){
ctx.fillStyle='#062b13'; ctx.font='900 13px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle';
ctx.fillText('★', br.x+br.w/2, br.y+br.h/2+1);
} else if(br.hp!==Infinity && br.hp<br.maxHp){
// cracks
ctx.strokeStyle='rgba(0,0,0,.45)'; ctx.lineWidth=1.4;
ctx.beginPath();
ctx.moveTo(br.x+10,br.y+4); ctx.lineTo(br.x+br.w*.42,br.y+br.h/2); ctx.lineTo(br.x+br.w*.3,br.y+br.h-4);
ctx.moveTo(br.x+br.w-12,br.y+5); ctx.lineTo(br.x+br.w*.62,br.y+br.h*.55); ctx.lineTo(br.x+br.w-22,br.y+br.h-3);
ctx.stroke();
ctx.fillStyle='rgba(0,0,0,.55)'; ctx.font='900 12px system-ui'; ctx.textAlign='right'; ctx.textBaseline='top';
ctx.fillText(br.hp, br.x+br.w-5, br.y+3);
}
ctx.restore();
});
}
function drawPaddle(){
const squash=1+paddleSquash*.12, w=paddle.w*squash, h=paddle.h*(2-squash);
const x=paddle.x-w/2, y=paddle.y+(paddle.h-h);
ctx.save();
ctx.shadowBlur=24; ctx.shadowColor='#38f5ff';
const g=ctx.createLinearGradient(x,y,x,y+h);
g.addColorStop(0,'#ffffff'); g.addColorStop(.35,'#5ff7ff'); g.addColorStop(1,'#7b3dff');
ctx.fillStyle=g; roundRect(x,y,w,h,9); ctx.fill();
ctx.shadowBlur=0;
ctx.fillStyle='rgba(255,255,255,.75)'; roundRect(x+8,y+3,w-16,4,3); ctx.fill();
// end caps
ctx.fillStyle='#ff4fd8';
roundRect(x+2,y+2,10,h-4,5); ctx.fill();
roundRect(x+w-12,y+2,10,h-4,5); ctx.fill();
ctx.restore();
}
function drawBalls(){
balls.forEach(b=>{
// trail
b.trail.forEach((t,i)=>{
ctx.globalAlpha=.16*(1-i/b.trail.length);
ctx.fillStyle='#38f5ff';
ctx.beginPath(); ctx.arc(t.x,t.y,b.r*(1-i/(b.trail.length*1.4)),0,TAU); ctx.fill();
});
ctx.globalAlpha=1;
ctx.save();
ctx.shadowBlur=22; ctx.shadowColor='#fff';
const g=ctx.createRadialGradient(b.x-3,b.y-4,1,b.x,b.y,b.r+2);
g.addColorStop(0,'#ffffff'); g.addColorStop(.55,'#aefcff'); g.addColorStop(1,'#1599ff');
ctx.fillStyle=g; ctx.beginPath(); ctx.arc(b.x,b.y,b.r,0,TAU); ctx.fill();
ctx.restore();
});
}
function drawPowerups(){
powerups.forEach(p=>{
ctx.save();
ctx.translate(p.x,p.y);
ctx.shadowBlur=16; ctx.shadowColor=p.color;
ctx.fillStyle=p.color; roundRect(-p.w/2,-p.h/2,p.w,p.h,10); ctx.fill();
ctx.shadowBlur=0;
ctx.fillStyle='rgba(255,255,255,.45)'; roundRect(-p.w/2+5,-p.h/2+3,p.w-10,5,3); ctx.fill();
ctx.fillStyle='#101426'; ctx.font='900 14px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle';
ctx.fillText(p.k,0,1);
ctx.restore();
});
}
function drawParticles(){
particles.forEach(p=>{
ctx.save(); ctx.globalAlpha=clamp(p.life/p.max,0,1);
ctx.translate(p.x,p.y); ctx.rotate(p.rot); ctx.fillStyle=p.color;
if(p.shape==='rect') ctx.fillRect(-p.size/2,-p.size/2,p.size,p.size*.65);
else { ctx.beginPath(); ctx.arc(0,0,p.size/2,0,TAU); ctx.fill(); }
ctx.restore();
});
ctx.globalAlpha=1;
}
function drawFloaters(){
floaters.forEach(f=>{
ctx.save(); ctx.globalAlpha=clamp(f.life,0,1);
ctx.font='900 '+f.size+'px system-ui,sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
ctx.shadowBlur=12; ctx.shadowColor=f.color; ctx.fillStyle=f.color;
ctx.fillText(f.text,f.x,f.y); ctx.restore();
});
}
// ---------- Loop ----------
let last=performance.now();
function frame(now){
const dt=clamp((now-last)/1000,0,.033);
last=now;
update(dt); draw();
requestAnimationFrame(frame);
}
// ---------- Input ----------
function canvasPos(e){
const r=canvas.getBoundingClientRect();
const cx=(e.touches?e.touches[0].clientX:e.clientX);
return (cx-r.left)/r.width*W;
}
canvas.addEventListener('mousemove',e=>{ mouseX=canvasPos(e); usingMouse=true; ensureAudio(); });
canvas.addEventListener('mousedown',e=>{ ensureAudio(); mouseX=canvasPos(e); usingMouse=true; handlePrimaryAction(); });
canvas.addEventListener('touchstart',e=>{ e.preventDefault(); ensureAudio(); mouseX=canvasPos(e); usingMouse=true; },{passive:false});
canvas.addEventListener('touchmove',e=>{ e.preventDefault(); mouseX=canvasPos(e); usingMouse=true; },{passive:false});
canvas.addEventListener('touchend',e=>{ e.preventDefault(); if(state==='ready') launchBalls(); },{passive:false});
window.addEventListener('keydown',e=>{
if(['ArrowLeft','ArrowRight','Space','ArrowUp','ArrowDown'].includes(e.code)) e.preventDefault();
ensureAudio();
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.code==='Enter'){
if(state==='menu'||state==='gameover') resetGame();
else if(state==='win'){ loop++; levelIndex=0; buildLevel(0); state='ready'; hideOverlay(); showToast('ENDLESS MODE — ROUND '+(loop+1)); }
else if(state==='paused') togglePause();
else launchBalls();
}
if(e.code==='KeyP') togglePause();
if(e.code==='KeyM') toggleSound();
if(e.code==='KeyR') resetGame();
});
window.addEventListener('keyup',e=>{
if(e.code==='ArrowLeft'||e.code==='KeyA') keys.left=false;
if(e.code==='ArrowRight'||e.code==='KeyD') keys.right=false;
});
window.addEventListener('blur',()=>{ if(state==='playing'){ state='paused'; syncPauseBtn(); } });
function handlePrimaryAction(){
if(state==='menu'||state==='gameover') resetGame();
else if(state==='win'){ loop++; levelIndex=0; buildLevel(0); state='ready'; hideOverlay(); showToast('ENDLESS MODE'); }
else if(state==='paused') togglePause();
else launchBalls();
}
function togglePause(){
if(state==='playing'){ state='paused'; }
else if(state==='paused'){ state='playing'; hideOverlay(); }
syncPauseBtn();
}
function syncPauseBtn(){ el('pauseBtn').textContent = state==='paused' ? '▶ RESUME' : '⏸ PAUSE'; }
function toggleSound(){
muted=!muted;
el('soundBtn').textContent=muted?'🔇 MUTED':'🔊 SOUND';
if(!muted){ ensureAudio(); tone(660,.08,'triangle',.05); }
}
el('startBtn').addEventListener('click',()=>{ ensureAudio(); if(state==='menu'||state==='gameover'||state==='win') handlePrimaryAction(); else launchBalls(); });
el('restartBtn').addEventListener('click',()=>{ ensureAudio(); resetGame(); });
el('pauseBtn').addEventListener('click',()=>{ ensureAudio(); togglePause(); syncPauseBtn(); });
el('soundBtn').addEventListener('click',()=>{ ensureAudio(); toggleSound(); });
bigBtn.addEventListener('click',()=>{ ensureAudio(); handlePrimaryAction(); });
// ---------- Init ----------
buildLevel(0);
updateHUD();
requestAnimationFrame(frame);
})();
</script>
</body>
</html>Compare with
Muse Wonder: other attempts
- Muse Wonder, Pelican: Queued · #1 of 11 paid · not before 23 Oct
- Muse Wonder, Watch: Queued · #1 of 11 paid · not before 23 Oct
- Muse Wonder, Landing: Queued · #1 of 11 paid · not before 23 Oct

- Muse Wonder, Logo: Queued · #1 of 11 paid · not before 23 Oct
- Muse Wonder, About: Queued · #1 of 11 paid · not before 23 Oct