• Updated 11 Nov, 12:43 UTC
  • Next run 12 Nov, 05:17 UTCNext 12 Nov 05:17
  • Last 7 days: 10 new models · 61 runs7 d: 10 models · 61 runs
Playable Breakout

Nex-N2.6Nex AGI

Network offOpen alone (new tab)

Run details

Model
nex-agi/nex-n2.6
Route
OpenRouter API, one user message
Served by
Nex AGI
Effort
high, OpenRouter's default for this model
Limits
60 min · 235,929 output tokens (the model's limit)
Date
10 Nov 2026
Time
6 min 41 s
Size
33 kB
Tokens
88 in, 44,821 out (33,085 of them thinking)
List price
$0.018 at today's price ($0.12 in / $0.40 out per M)
Paid
$0.018

Raw files: output · meta.json

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

33 kB 860 lines Raw

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="color-scheme" content="dark">
<title>BREAKPOINT — Brick Breaker</title>
<style>
  *{margin:0;padding:0;box-sizing:border-box}
  html,body{height:100%}
  body{
    background:#07090d;
    display:flex;align-items:center;justify-content:center;
    font-family:ui-monospace,"SF Mono","Cascadia Mono",Menlo,Consolas,"Liberation Mono",monospace;
    color:#e8edf4;overflow:hidden;user-select:none;-webkit-user-select:none;
  }
  #stage{position:relative;border:1px solid rgba(255,255,255,.09);border-radius:10px;overflow:hidden;box-shadow:0 30px 80px rgba(0,0,0,.6)}
  #stage::after{content:"";position:absolute;inset:0;pointer-events:none;background:repeating-linear-gradient(0deg,rgba(255,255,255,.018) 0 1px,transparent 1px 3px)}
  canvas{display:block;background:#0b0e13;touch-action:none}
  #overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;text-align:center;background:rgba(9,11,16,.84);backdrop-filter:blur(3px);opacity:0;pointer-events:none;transition:opacity .25s ease}
  #overlay.show{opacity:1}
  #ov-inner{max-width:680px;padding:24px}
  .ov-eyebrow{font-size:11px;letter-spacing:.35em;color:#7f8ca0;margin-bottom:16px}
  .ov-logo{font-size:clamp(44px,9vw,74px);letter-spacing:.1em;color:#f4f7fb;font-weight:800;line-height:1;margin-bottom:12px}
  .ov-logo span{color:#ffc247}
  .ov-title{font-size:clamp(30px,6vw,52px);letter-spacing:.14em;color:#ffc247;font-weight:800;margin-bottom:12px}
  .ov-score{font-size:clamp(40px,8vw,64px);color:#f4f7fb;font-weight:800;letter-spacing:.06em;margin:4px 0 10px}
  .ov-sub{font-size:13px;color:#aab6c4;margin-bottom:18px;letter-spacing:.08em}
  .ov-controls{display:inline-flex;flex-direction:column;gap:6px;margin:6px 0 18px;font-size:12px;color:#8b98a8;letter-spacing:.08em;text-align:left}
  .ov-controls span{display:inline-block;min-width:170px;color:#ffc247;text-align:right;margin-right:16px;font-weight:700}
  .ov-best{font-size:12px;color:#5fd68b;letter-spacing:.2em;margin-bottom:16px}
  .ov-hint{font-size:13px;color:#f4f7fb;letter-spacing:.25em;animation:blink 1.1s steps(2,end) infinite;margin-top:4px}
  @keyframes blink{0%,100%{opacity:1}50%{opacity:.25}}
</style>
</head>
<body>
<div id="stage">
  <canvas id="game"></canvas>
  <div id="overlay"><div id="ov-inner"></div></div>
</div>

<script>
'use strict';
/* ================= setup ================= */
const canvas=document.getElementById('game');
const ctx=canvas.getContext('2d');
const W=900,H=640;
const DPR=Math.min(window.devicePixelRatio||1,2);
canvas.width=W*DPR;canvas.height=H*DPR;
const MONO='ui-monospace,"SF Mono","Cascadia Mono",Menlo,Consolas,monospace';

function resize(){
  const pad=28;
  let w=Math.min(window.innerWidth-pad*2,(window.innerHeight-pad*2)*(W/H));
  w=Math.max(320,w);
  canvas.style.width=w+'px';
  canvas.style.height=(w*H/W)+'px';
}
window.addEventListener('resize',resize);resize();

const overlay=document.getElementById('overlay');
const ovInner=document.getElementById('ov-inner');
function showOverlay(html){ovInner.innerHTML=html;overlay.classList.add('show');}
function hideOverlay(){overlay.classList.remove('show');}

function clamp(v,a,b){return v<a?a:(v>b?b:v);}

/* ================= synthesized audio (no files) ================= */
const Sfx=(()=>{
  let ac=null,master=null,muted=false;
  function ensure(){
    if(!ac){
      const AC=window.AudioContext||window.webkitAudioContext;
      if(!AC)return false;
      ac=new AC();master=ac.createGain();master.gain.value=0.4;master.connect(ac.destination);
    }
    if(ac.state==='suspended')ac.resume();
    return true;
  }
  function env(g,t,vol,dur){g.gain.setValueAtTime(vol,t);g.gain.exponentialRampToValueAtTime(0.0001,t+dur);}
  function blip(freq,dur,type,vol,slide){
    if(!ensure()||muted)return;
    dur=dur||0.08;type=type||'square';vol=vol==null?0.5:vol;
    const t=ac.currentTime,o=ac.createOscillator(),g=ac.createGain();
    o.type=type;o.frequency.setValueAtTime(freq,t);
    if(slide)o.frequency.exponentialRampToValueAtTime(Math.max(30,freq+slide),t+dur);
    env(g,t,vol,dur);o.connect(g);g.connect(master);o.start(t);o.stop(t+dur+0.02);
  }
  function noise(dur,vol,freq,type){
    if(!ensure()||muted)return;
    dur=dur||0.15;vol=vol==null?0.4:vol;type=type||'lowpass';
    const t=ac.currentTime,n=Math.floor(ac.sampleRate*dur);
    const buf=ac.createBuffer(1,n,ac.sampleRate),d=buf.getChannelData(0);
    for(let i=0;i<n;i++)d[i]=Math.random()*2-1;
    const src=ac.createBufferSource();src.buffer=buf;
    const f=ac.createBiquadFilter();f.type=type;f.frequency.value=freq;
    const g=ac.createGain();env(g,t,vol,dur);
    src.connect(f);f.connect(g);g.connect(master);src.start(t);
  }
  function note(freq,when,dur,type,vol){
    if(!ensure()||muted)return;
    const t=ac.currentTime+when,o=ac.createOscillator(),g=ac.createGain();
    o.type=type||'square';o.frequency.value=freq;
    g.gain.setValueAtTime(0.0001,t);
    g.gain.exponentialRampToValueAtTime(vol||0.32,t+0.01);
    g.gain.exponentialRampToValueAtTime(0.0001,t+(dur||0.12));
    o.connect(g);g.connect(master);o.start(t);o.stop(t+(dur||0.12)+0.02);
  }
  return{
    unlock(){ensure();},
    toggleMute(){muted=!muted;return muted;},
    get muted(){return muted;},
    paddle(){blip(190,0.06,'square',0.45);noise(0.03,0.12,2400,'highpass');},
    wall(){blip(150,0.045,'triangle',0.3);},
    brickHit(){blip(320,0.05,'square',0.4);},
    brickBreak(c){blip(340*Math.pow(2,Math.min(c,14)/14),0.07,'square',0.45);noise(0.06,0.2,3200,'highpass');},
    steel(){blip(1250,0.04,'sine',0.3);blip(1870,0.03,'sine',0.2);noise(0.03,0.12,5000,'highpass');},
    explode(){noise(0.4,0.6,420,'lowpass');blip(75,0.35,'sine',0.6,-40);},
    power(){[523,659,784,1047].forEach((f,i)=>note(f,i*0.06,0.09,'square',0.3));},
    launch(){blip(420,0.09,'square',0.4,300);},
    lifeLost(){blip(320,0.5,'sawtooth',0.45,-260);noise(0.3,0.2,600,'lowpass');},
    clear(){[523,659,784,1047,1319].forEach((f,i)=>note(f,i*0.09,0.15,'square',0.32));},
    over(){[392,330,262,196].forEach((f,i)=>note(f,i*0.22,0.3,'sawtooth',0.3));},
    win(){[523,659,784,1047,784,1047,1319].forEach((f,i)=>note(f,i*0.12,0.18,'square',0.3));},
    click(){blip(700,0.03,'square',0.25);}
  };
})();

/* ================= levels ================= */
const LEVELS=[
 {name:'FIRST CONTACT',speed:340,rows:[
  '.............',
  'rrrrrrrrrrrrr',
  'ooooooooooooo',
  'yyyyyyyyyyyyy',
  'ggggggggggggg']},
 {name:'THE VAULT',speed:365,rows:[
  '.....rrr.....',
  '...TTTTTTT...',
  '..ooooooooo..',
  'TTTTTTTTTTTTT',
  '..yyyyyyyyy..',
  '...TTTTTTT...',
  '.....ggg.....']},
 {name:'DIAMOND',speed:390,rows:[
  '......b......',
  '.....bTb.....',
  '....bToTb....',
  '...bToEoTb...',
  '....bToTb....',
  '.....bTb.....',
  '......b......']},
 {name:'FORTRESS',speed:415,rows:[
  'S...........S',
  'S.TTTTTTTTT.S',
  'S.rrrrrrrrr.S',
  'S.TTTTTTTTT.S',
  'S.yyyyyyyyy.S',
  'S.TTTTTTTTT.S',
  'S...........S']},
 {name:'CHECKMATE',speed:440,rows:[
  'T.T.T.T.T.T.T',
  '.o.o.o.o.o.o.',
  'T.T.T.T.T.T.T',
  '.o.o.oEo.o.o.',
  'T.T.T.T.T.T.T',
  '.o.o.o.o.o.o.']},
 {name:'FINAL GAUNTLET',speed:470,rows:[
  'T.E.T.T.T.E.T',
  '.ppppppppppp.',
  'STSTSTSTSTSTS',
  '.bbbbbbbbbbb.',
  'T.E.T.T.T.E.T',
  '.ppppppppppp.']}
];

const COLS=13,BRICK_W=60,BRICK_H=24,BRICK_GAP=6,BRICK_TOP=92,SIDE_PAD=24;
const COLORS={r:'#ff5c5c',o:'#ff9f43',y:'#ffd45e',g:'#5fd68b',b:'#5aa9ff',p:'#b78bff'};
const COLOR_LIST=Object.values(COLORS);
const POWERS=[
 {id:'wide', color:'#5fd68b',label:'W',name:'WIDE PADDLE'},
 {id:'slow', color:'#5aa9ff',label:'S',name:'SLOW MOTION'},
 {id:'multi',color:'#ff9f43',label:'M',name:'MULTIBALL'},
 {id:'fire', color:'#ff5c5c',label:'F',name:'FIREBALL'},
 {id:'life', color:'#ff7ad9',label:'+',name:'EXTRA LIFE'}
];

/* ================= state ================= */
let state='menu'; // menu | intro | playing | clearing | dying | paused | gameover | win
let level=0,score=0,lives=3,combo=0;
let best=0;try{best=parseInt(localStorage.getItem('breakpoint_best_v1'))||0;}catch(e){}
function saveBest(){try{localStorage.setItem('breakpoint_best_v1',String(best));}catch(e){}}

let bricks=[],balls=[],powerups=[],particles=[],floaters=[],pendingBlasts=[];
let shakeMag=0,hitStop=0,flashColor=null,flashAlpha=0;
let slowTimer=0,wideTimer=0,fireTimer=0;
let introTimer=0,dyingTimer=0,clearTimer=0,cleared=false;
let time=0;

const paddle={x:W/2,y:H-46,w:112,h:16,baseW:112,targetW:112,squash:0,vx:0,_px:W/2};

/* stars backdrop */
const stars=[];
for(let i=0;i<70;i++)stars.push({x:Math.random()*W,y:Math.random()*H,s:0.5+Math.random()*1.4,v:6+Math.random()*18,a:0.04+Math.random()*0.14});

/* ================= helpers ================= */
function setPaddleX(x){const hw=paddle.w/2;paddle.x=clamp(x,hw+10,W-hw-10);}
function addShake(m){shakeMag=Math.min(shakeMag+m,26);}
function flash(c,a){flashColor=c;flashAlpha=Math.max(flashAlpha,a);}
function addFloater(x,y,text,color,size){floaters.push({x,y,text,color,size,life:1.1,vy:-46});if(floaters.length>24)floaters.shift();}
function capParticles(){while(particles.length>350)particles.shift();}

function rrPath(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();
}

/* ================= level build ================= */
function buildLevel(idx){
  bricks=[];
  LEVELS[idx].rows.forEach((row,r)=>{
    for(let c=0;c<COLS;c++){
      const ch=row[c];
      if(!ch||ch==='.')continue;
      const b={x:SIDE_PAD+c*(BRICK_W+BRICK_GAP),y:BRICK_TOP+r*(BRICK_H+BRICK_GAP),
               w:BRICK_W,h:BRICK_H,type:'normal',hits:1,color:'#fff',alive:true,flash:0,cx:0,cy:0};
      b.cx=b.x+b.w/2;b.cy=b.y+b.h/2;
      if(COLORS[ch])b.color=COLORS[ch];
      else if(ch==='T'){b.type='tough';b.hits=2;b.color='#9db1bd';}
      else if(ch==='S'){b.type='steel';b.hits=Infinity;b.color='#414a57';}
      else if(ch==='E'){b.type='explosive';b.color='#2e3440';}
      bricks.push(b);
    }
  });
}

/* ================= balls ================= */
function spawnBall(x,y,ang,speed){
  speed=speed||LEVELS[level].speed;
  const b={x,y,r:7,speed,vx:Math.sin(ang)*speed,vy:-Math.cos(ang)*speed,stuck:false,fire:0,trail:[]};
  balls.push(b);return b;
}
function spawnStickyBall(){const b=spawnBall(paddle.x,paddle.y-14,0);b.stuck=true;}
function launchBalls(){
  let did=false;
  balls.forEach(b=>{
    if(b.stuck){b.stuck=false;const a=Math.random()*0.6-0.3;b.vx=Math.sin(a)*b.speed;b.vy=-Math.cos(a)*b.speed;did=true;}
  });
  if(did)Sfx.launch();
}
function pushTrail(b){
  b.trail.push({x:b.x,y:b.y});
  const max=b.fire>0?16:10;
  while(b.trail.length>max)b.trail.shift();
}
function onBounce(b){
  const minVy=b.speed*0.12,minVx=b.speed*0.06;
  let vx=b.vx,vy=b.vy;
  if(Math.abs(vy)<minVy)vy=(vy<0?-1:1)*minVy;
  if(Math.abs(vx)<minVx)vx=(vx<0?-1:1)*minVx;
  const m=Math.hypot(vx,vy)||1;
  b.vx=vx/m*b.speed;b.vy=vy/m*b.speed;
}

/* ================= collisions ================= */
function collideWalls(b){
  if(b.x<b.r){b.x=b.r;b.vx=Math.abs(b.vx);onBounce(b);Sfx.wall();addShake(1.2);}
  else if(b.x>W-b.r){b.x=W-b.r;b.vx=-Math.abs(b.vx);onBounce(b);Sfx.wall();addShake(1.2);}
  if(b.y<b.r+66){b.y=b.r+66;b.vy=Math.abs(b.vy);onBounce(b);Sfx.wall();addShake(1.2);}
}
function collidePaddle(b){
  const hw=paddle.w/2,hh=paddle.h/2;
  if(b.vy>0&&b.y+b.r>=paddle.y-hh&&b.y-b.r<=paddle.y+hh&&b.x>=paddle.x-hw-b.r&&b.x<=paddle.x+hw+b.r){
    b.y=paddle.y-hh-b.r;
    b.speed=Math.min(b.speed+3,640);
    const rel=clamp((b.x-paddle.x)/hw,-1,1);
    const ang=rel*1.05;
    b.vx=Math.sin(ang)*b.speed;b.vy=-Math.cos(ang)*b.speed;
    b.vx+=clamp(paddle.vx*0.12,-60,60);
    onBounce(b);
    combo=0;paddle.squash=1;
    Sfx.paddle();addShake(2);
    spawnSparks(b.x,paddle.y-hh,'#ffffff',6);
  }
}
function collideBricks(b){
  for(const br of bricks){
    if(!br.alive)continue;
    const nx=clamp(b.x,br.x,br.x+br.w),ny=clamp(b.y,br.y,br.y+br.h);
    const dx=b.x-nx,dy=b.y-ny;
    if(dx*dx+dy*dy<=b.r*b.r){
      if(b.fire>0&&br.type!=='steel'){destroyBrick(br);continue;} // fireball pierces
      hitBrick(b,br);
      const ox=(b.x<br.x+br.w/2)?(br.x-b.r-b.x):(br.x+br.w+b.r-b.x);
      const oy=(b.y<br.y+br.h/2)?(br.y-b.r-b.y):(br.y+br.h+b.r-b.y);
      if(Math.abs(ox)<Math.abs(oy)){b.x+=ox;b.vx=-b.vx;}else{b.y+=oy;b.vy=-b.vy;}
      onBounce(b);
      break;
    }
  }
}
function hitBrick(b,br){
  br.flash=1;
  if(br.type==='steel'){Sfx.steel();spawnSparks(b.x,b.y,'#9fb0c0',5);addShake(1.5);return;}
  br.hits--;
  if(br.hits<=0)destroyBrick(br);
  else{Sfx.brickHit();spawnSparks(b.x,b.y,br.color,4);addShake(2);addScore(10,br.cx,br.cy,false);}
}
function destroyBrick(br){
  if(!br.alive)return;
  br.alive=false;
  combo++;
  const base=br.type==='explosive'?120:(br.type==='tough'?80:50);
  addScore(base,br.cx,br.cy,true);
  Sfx.brickBreak(combo);
  spawnShards(br);
  addShake(br.type==='explosive'?9:3.5);
  if(br.type==='explosive'){hitStop=Math.max(hitStop,0.08);pendingBlasts.push({x:br.cx,y:br.cy,delay:0.03});}
  else if(br.type==='tough'){hitStop=Math.max(hitStop,0.035);}
  maybeDropPower(br);
  checkClear();
}
function doBlast(x,y){
  spawnExplosion(x,y);Sfx.explode();addShake(12);
  for(const br of bricks){
    if(!br.alive||br.type==='steel')continue;
    if(Math.abs(br.cx-x)<=BRICK_W+BRICK_GAP&&Math.abs(br.cy-y)<=BRICK_H+BRICK_GAP){
      br.hits--;br.flash=1;
      if(br.hits<=0)destroyBrick(br);
      else addScore(10,br.cx,br.cy,false);
    }
  }
}

/* ================= score / clear ================= */
function addScore(base,x,y,popup){
  const pts=base+Math.max(0,combo-1)*10;
  score+=pts;
  if(score>best){best=score;saveBest();}
  if(popup){
    addFloater(x,y,'+'+pts,'#ffffff',13);
    if(combo>=3)addFloater(x,y-18,'COMBO ×'+combo,'#ffc247',11);
  }
}
function checkClear(){
  if(cleared)return;
  if(bricks.some(b=>b.alive&&b.type!=='steel'))return;
  cleared=true;
  state='clearing';clearTimer=1.5;
  const bonus=250*(level+1)+lives*100;
  addScore(bonus,W/2,H/2,false);
  addFloater(W/2,H/2-30,'+'+bonus,'#ffffff',16);
  addFloater(W/2,H/2-64,'LEVEL CLEAR','#ffd45e',26);
  Sfx.clear();fountain();
}

/* ================= power-ups ================= */
function maybeDropPower(br){
  if(Math.random()>0.13||powerups.length>=3)return;
  const table=[['wide',26],['slow',22],['multi',20],['fire',20],['life',12]];
  let r=Math.random()*100,pick='wide';
  for(const[id,w]of table){if(r<w){pick=id;break;}r-=w;}
  const def=POWERS.find(p=>p.id===pick);
  powerups.push({x:br.cx,y:br.cy,vy:135,wobble:Math.random()*6.28,id:def.id,label:def.label,color:def.color,name:def.name});
}
function applyPower(p){
  Sfx.power();
  addFloater(paddle.x,paddle.y-26,p.name,p.color,15);
  spawnSparks(p.x,p.y,p.color,14);
  addShake(3);
  switch(p.id){
    case 'wide':wideTimer=12;paddle.targetW=paddle.baseW*1.55;break;
    case 'slow':slowTimer=8;break;
    case 'fire':fireTimer=8;balls.forEach(b=>b.fire=8);break;
    case 'multi':{
      const snap=balls.filter(b=>!b.stuck).map(b=>({x:b.x,y:b.y,vx:b.vx,vy:b.vy,speed:b.speed,fire:b.fire}));
      for(const s of snap){
        if(balls.length>=9)break;
        for(const da of[-0.5,0.5]){
          if(balls.length>=9)break;
          const a=Math.atan2(s.vx,-s.vy)+da;
          const nb={x:s.x,y:s.y,r:7,speed:s.speed,vx:Math.sin(a)*s.speed,vy:-Math.cos(a)*s.speed,stuck:false,fire:s.fire,trail:[]};
          balls.push(nb);
        }
      }
      break;
    }
    case 'life':lives=Math.min(9,lives+1);break;
  }
}

/* ================= particles / fx ================= */
function spawnSparks(x,y,color,n){
  for(let i=0;i<n;i++){
    const a=Math.random()*Math.PI*2,sp=60+Math.random()*180;
    particles.push({x,y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp-40,life:0.35+Math.random()*0.25,max:0.6,size:1.5+Math.random()*2,color,g:600,shape:0,rot:0,vr:0});
  }
  capParticles();
}
function spawnShards(br){
  for(let i=0;i<14;i++){
    const a=Math.random()*Math.PI*2,sp=80+Math.random()*220;
    particles.push({x:br.cx+(Math.random()-0.5)*br.w*0.7,y:br.cy+(Math.random()-0.5)*br.h*0.7,
      vx:Math.cos(a)*sp,vy:Math.sin(a)*sp-90,life:0.5+Math.random()*0.4,max:0.9,
      size:3+Math.random()*4,color:Math.random()<0.75?br.color:'#ffffff',g:900,
      rot:Math.random()*6.28,vr:(Math.random()-0.5)*12,shape:1});
  }
  capParticles();
}
function spawnExplosion(x,y){
  flash('#ffb340',0.15);
  for(let i=0;i<30;i++){
    const a=Math.random()*Math.PI*2,sp=100+Math.random()*320;
    particles.push({x,y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp,life:0.4+Math.random()*0.5,max:0.9,
      size:2+Math.random()*4,color:['#ffb340','#ff5c5c','#ffd45e','#ffffff'][i%4],g:500,rot:0,vr:0,shape:1});
  }
  particles.push({x,y,vx:0,vy:0,life:0.18,max:0.18,size:46,color:'#ffffff',g:0,rot:0,vr:0,shape:2});
  capParticles();
}
function spawnFireSpark(b){
  particles.push({x:b.x+(Math.random()-0.5)*6,y:b.y+(Math.random()-0.5)*6,
    vx:(Math.random()-0.5)*40,vy:30+Math.random()*40,life:0.3,max:0.3,
    size:2+Math.random()*2.5,color:Math.random()<0.5?'#ff8c3b':'#ffd45e',g:-60,rot:0,vr:0,shape:0});
  capParticles();
}
function fountain(){
  for(let i=0;i<60;i++){
    particles.push({x:W/2+(Math.random()-0.5)*W*0.7,y:H*0.35,
      vx:(Math.random()-0.5)*250,vy:-Math.random()*320-100,life:0.8+Math.random()*0.8,max:1.6,
      size:2+Math.random()*3,color:COLOR_LIST[i%6],g:500,rot:0,vr:(Math.random()-0.5)*10,shape:1});
  }
  capParticles();
}

/* ================= updates ================= */
function updateStars(dt){for(const s of stars){s.y+=s.v*dt;if(s.y>H){s.y=-2;s.x=Math.random()*W;}}}

function updateFx(dt){
  for(let i=particles.length-1;i>=0;i--){
    const p=particles[i];p.life-=dt;
    if(p.life<=0){particles.splice(i,1);continue;}
    p.x+=p.vx*dt;p.y+=p.vy*dt;p.vy+=(p.g||0)*dt;
    if(p.vr)p.rot+=p.vr*dt;
  }
  for(let i=floaters.length-1;i>=0;i--){
    const f=floaters[i];f.life-=dt;f.y+=f.vy*dt;
    if(f.life<=0)floaters.splice(i,1);
  }
  shakeMag*=Math.exp(-7*dt);if(shakeMag<0.05)shakeMag=0;
  if(flashAlpha>0)flashAlpha=Math.max(0,flashAlpha-dt*1.2);
  for(const b of bricks)if(b.flash>0)b.flash=Math.max(0,b.flash-dt*5);
}

function updatePaddle(dt){
  const dir=(keys.right?1:0)-(keys.left?1:0);
  if(dir!==0)setPaddleX(paddle.x+dir*640*dt);
  paddle.w+=(paddle.targetW-paddle.w)*Math.min(1,dt*10);
  paddle.squash=Math.max(0,paddle.squash-dt*5);
  paddle.vx=(paddle.x-paddle._px)/Math.max(dt,0.001);paddle._px=paddle.x;
}

function updateBalls(dt){
  const sdt=dt*(slowTimer>0?0.72:1);
  for(let i=balls.length-1;i>=0;i--){
    const b=balls[i];
    if(b.fire>0)b.fire=Math.max(0,b.fire-sdt);
    if(b.stuck){b.x=paddle.x;b.y=paddle.y-paddle.h/2-b.r-1;continue;}
    const dist=b.speed*sdt;
    const steps=Math.max(1,Math.ceil(dist/6)),step=dist/steps;
    let dead=false;
    for(let s=0;s<steps;s++){
      b.x+=b.vx/b.speed*step;b.y+=b.vy/b.speed*step;
      collideWalls(b);collidePaddle(b);collideBricks(b);
      if(b.y>H+20){dead=true;break;}
    }
    pushTrail(b);
    if(b.fire>0&&Math.random()<0.5)spawnFireSpark(b);
    if(dead){
      balls.splice(i,1);
      if(balls.length===0&&state==='playing')loseLife();
    }
  }
}

function updatePowerups(dt){
  for(let i=powerups.length-1;i>=0;i--){
    const p=powerups[i];
    p.y+=p.vy*dt;p.x+=Math.sin(time*4+p.wobble)*12*dt;
    if(p.y>paddle.y-14&&p.y<paddle.y+16&&Math.abs(p.x-paddle.x)<paddle.w/2+16){
      applyPower(p);powerups.splice(i,1);continue;
    }
    if(p.y>H+24)powerups.splice(i,1);
  }
}

function updateEffects(dt){
  if(wideTimer>0){wideTimer-=dt;if(wideTimer<=0){wideTimer=0;paddle.targetW=paddle.baseW;}}
  if(slowTimer>0)slowTimer=Math.max(0,slowTimer-dt);
  if(fireTimer>0){fireTimer=Math.max(0,fireTimer-dt);if(fireTimer===0)balls.forEach(b=>b.fire=0);}
}

function updateBlasts(dt){
  for(let i=pendingBlasts.length-1;i>=0;i--){
    const bl=pendingBlasts[i];bl.delay-=dt;
    if(bl.delay<=0){pendingBlasts.splice(i,1);doBlast(bl.x,bl.y);}
  }
}

function loseLife(){
  lives--;combo=0;
  Sfx.lifeLost();addShake(14);flash('#ff3b3b',0.22);
  slowTimer=0;fireTimer=0;balls.forEach(b=>b.fire=0);
  state='dying';dyingTimer=lives>0?0.9:1.0;
}
function afterDeath(){
  if(lives<=0){gameOver();return;}
  paddle.targetW=paddle.baseW;wideTimer=0;
  spawnStickyBall();state='playing';
}

function update(dt){
  updateStars(dt);updateFx(dt);updateBlasts(dt);
  if(state==='playing'||state==='clearing'){
    updatePaddle(dt);updateBalls(dt);updatePowerups(dt);updateEffects(dt);
    if(state==='clearing'){
      clearTimer-=dt;
      if(clearTimer<=0){level++;if(level>=LEVELS.length)winGame();else startLevel();}
    }
  }else if(state==='dying'){
    updatePaddle(dt);dyingTimer-=dt;
    if(dyingTimer<=0)afterDeath();
  }
}

/* ================= rendering ================= */
let vignette=null,lastCursor='';
function setCursor(){
  const want=(state==='playing'||state==='intro'||state==='clearing'||state==='dying')?'none':'default';
  if(want!==lastCursor){canvas.style.cursor=want;lastCursor=want;}
}

function drawBricks(){
  for(const b of bricks){
    if(!b.alive)continue;
    if(b.type==='steel'){
      ctx.fillStyle='#414a57';rrPath(b.x,b.y,b.w,b.h,3);ctx.fill();
      ctx.strokeStyle='#5b6678';ctx.lineWidth=1.5;rrPath(b.x,b.y,b.w,b.h,3);ctx.stroke();
      ctx.fillStyle='#78849b';
      ctx.beginPath();ctx.arc(b.x+7,b.y+7,1.8,0,7);ctx.fill();
      ctx.beginPath();ctx.arc(b.x+b.w-7,b.y+7,1.8,0,7);ctx.fill();
      ctx.beginPath();ctx.arc(b.x+7,b.y+b.h-7,1.8,0,7);ctx.fill();
      ctx.beginPath();ctx.arc(b.x+b.w-7,b.y+b.h-7,1.8,0,7);ctx.fill();
    }else{
      ctx.fillStyle=b.color;rrPath(b.x,b.y,b.w,b.h,4);ctx.fill();
      ctx.fillStyle='rgba(255,255,255,0.28)';ctx.fillRect(b.x+3,b.y+2,b.w-6,2);
      ctx.fillStyle='rgba(0,0,0,0.25)';ctx.fillRect(b.x+3,b.y+b.h-4,b.w-6,2);
      if(b.type==='explosive'){
        const p=0.55+0.45*Math.sin(time*6+b.cx);
        ctx.strokeStyle='rgba(255,179,64,'+(0.45+0.3*Math.sin(time*6+b.cx)).toFixed(3)+')';
        ctx.lineWidth=1.5;rrPath(b.x+1,b.y+1,b.w-2,b.h-2,4);ctx.stroke();
        ctx.fillStyle='rgba(255,179,64,'+p.toFixed(3)+')';
        ctx.beginPath();ctx.arc(b.cx,b.cy,5,0,7);ctx.fill();
      }
      if(b.type==='tough'&&b.hits<2){
        ctx.strokeStyle='rgba(15,20,26,0.65)';ctx.lineWidth=1.5;ctx.beginPath();
        ctx.moveTo(b.x+b.w*0.3,b.y+2);ctx.lineTo(b.x+b.w*0.45,b.y+b.h*0.5);ctx.lineTo(b.x+b.w*0.3,b.y+b.h-2);
        ctx.moveTo(b.x+b.w*0.45,b.y+b.h*0.5);ctx.lineTo(b.x+b.w*0.7,b.y+b.h-3);
        ctx.stroke();
      }
      if(b.flash>0){
        ctx.globalAlpha=b.flash*0.8;ctx.fillStyle='#ffffff';
        rrPath(b.x,b.y,b.w,b.h,4);ctx.fill();ctx.globalAlpha=1;
      }
    }
  }
}

function drawPaddle(){
  const sq=paddle.squash;
  const w=paddle.w*(1+sq*0.12),h=paddle.h*(1-sq*0.35);
  const x=paddle.x,y=paddle.y;
  ctx.fillStyle='rgba(0,0,0,0.35)';rrPath(x-w/2,y-h/2+3,w,h,h/2);ctx.fill();
  ctx.fillStyle='#e8edf4';rrPath(x-w/2,y-h/2,w,h,h/2);ctx.fill();
  ctx.fillStyle='rgba(255,255,255,0.5)';ctx.fillRect(x-w/2+6,y-h/2+2,w-12,2);
  ctx.fillStyle='#ffb340';
  const sw=Math.min(34,w*0.3);rrPath(x-sw/2,y-h/2+2,sw,h-4,3);ctx.fill();
  if(wideTimer>0){
    ctx.strokeStyle='rgba(95,214,139,0.7)';ctx.lineWidth=2;
    rrPath(x-w/2-3,y-h/2-3,w+6,h+6,h/2+3);ctx.stroke();
  }
}

function drawBalls(){
  for(const b of balls){
    for(let i=0;i<b.trail.length;i++){
      const t=b.trail[i],f=i/b.trail.length;
      ctx.globalAlpha=f*0.25;
      ctx.fillStyle=b.fire>0?'#ff8c3b':'#ffffff';
      ctx.beginPath();ctx.arc(t.x,t.y,b.r*f*0.9,0,7);ctx.fill();
    }
    ctx.globalAlpha=1;
    if(b.stuck){
      ctx.strokeStyle='rgba(255,255,255,0.18)';ctx.setLineDash([3,5]);
      ctx.beginPath();ctx.moveTo(b.x,b.y-12);ctx.lineTo(b.x,b.y-52);ctx.stroke();
      ctx.setLineDash([]);
    }
    if(b.fire>0){ctx.shadowColor='#ff7b2e';ctx.shadowBlur=14;}
    ctx.fillStyle=b.fire>0?'#ffb340':'#fff6e8';
    ctx.beginPath();ctx.arc(b.x,b.y,b.r,0,7);ctx.fill();
    ctx.shadowBlur=0;
    ctx.fillStyle='rgba(255,255,255,0.8)';
    ctx.beginPath();ctx.arc(b.x-2,b.y-2,2.2,0,7);ctx.fill();
  }
}

function drawPowerups(){
  for(const p of powerups){
    const x=p.x+Math.sin(time*5+p.wobble)*3,y=p.y;
    ctx.save();ctx.translate(x,y);
    ctx.fillStyle=p.color;rrPath(-17,-9,34,18,9);ctx.fill();
    ctx.fillStyle='rgba(255,255,255,0.35)';ctx.fillRect(-13,-7,26,2);
    ctx.fillStyle='#0b0e13';ctx.font='bold 12px '+MONO;
    ctx.textAlign='center';ctx.textBaseline='middle';
    ctx.fillText(p.label,0,1);
    ctx.restore();
  }
  ctx.textBaseline='alphabetic';
}

function drawParticles(){
  for(const p of particles){
    const a=Math.max(0,p.life/p.max);
    ctx.globalAlpha=a;
    if(p.shape===2){
      const r=p.size*(1-a)+6;
      ctx.strokeStyle=p.color;ctx.lineWidth=Math.max(0.5,3*a);
      ctx.beginPath();ctx.arc(p.x,p.y,r,0,7);ctx.stroke();
    }else if(p.shape===1){
      ctx.save();ctx.translate(p.x,p.y);ctx.rotate(p.rot||0);
      ctx.fillStyle=p.color;ctx.fillRect(-p.size/2,-p.size/2,p.size,p.size*0.8);
      ctx.restore();
    }else{
      ctx.fillStyle=p.color;
      ctx.beginPath();ctx.arc(p.x,p.y,p.size,0,7);ctx.fill();
    }
  }
  ctx.globalAlpha=1;
}

function drawFloaters(){
  ctx.textAlign='center';
  for(const f of floaters){
    ctx.globalAlpha=Math.min(1,f.life*1.8);
    ctx.font='bold '+f.size+'px '+MONO;
    ctx.strokeStyle='rgba(0,0,0,0.6)';ctx.lineWidth=3;
    ctx.strokeText(f.text,f.x,f.y);
    ctx.fillStyle=f.color;ctx.fillText(f.text,f.x,f.y);
  }
  ctx.globalAlpha=1;
}

function drawEffectBars(){
  const acts=[];
  if(wideTimer>0)acts.push(['WIDE',wideTimer/12,'#5fd68b']);
  if(slowTimer>0)acts.push(['SLOW',slowTimer/8,'#5aa9ff']);
  if(fireTimer>0)acts.push(['FIRE',fireTimer/8,'#ff5c5c']);
  if(!acts.length)return;
  let x=W/2-(acts.length*86)/2+43;
  for(const[label,frac,col]of acts){
    ctx.fillStyle='rgba(255,255,255,0.08)';ctx.fillRect(x-35,56,70,5);
    ctx.fillStyle=col;ctx.fillRect(x-35,56,70*clamp(frac,0,1),5);
    ctx.font='8px '+MONO;ctx.textAlign='center';ctx.fillText(label,x,52);
    x+=86;
  }
}

function drawHUD(){
  ctx.fillStyle='rgba(255,255,255,0.025)';ctx.fillRect(0,0,W,64);
  ctx.strokeStyle='rgba(255,255,255,0.08)';ctx.lineWidth=1;
  ctx.beginPath();ctx.moveTo(0,64.5);ctx.lineTo(W,64.5);ctx.stroke();
  ctx.textBaseline='alphabetic';
  // score
  ctx.textAlign='left';ctx.letterSpacing='3px';
  ctx.fillStyle='#7f8ca0';ctx.font='10px '+MONO;ctx.fillText('SCORE',24,24);
  ctx.letterSpacing='0px';
  ctx.fillStyle='#f4f7fb';ctx.font='bold 25px '+MONO;ctx.fillText(String(score).padStart(6,'0'),24,50);
  if(combo>=2&&(state==='playing'||state==='clearing')){
    ctx.fillStyle='#ffc247';ctx.globalAlpha=0.65+0.35*Math.sin(time*10);
    ctx.font='bold 13px '+MONO;ctx.fillText('×'+combo,168,50);ctx.globalAlpha=1;
  }
  // level
  ctx.textAlign='center';ctx.letterSpacing='3px';
  ctx.fillStyle='#7f8ca0';ctx.font='10px '+MONO;ctx.fillText('LEVEL '+(level+1)+' / '+LEVELS.length,W/2,24);
  ctx.letterSpacing='2px';
  ctx.fillStyle='#ffc247';ctx.font='bold 15px '+MONO;ctx.fillText(LEVELS[level].name,W/2,45);
  ctx.letterSpacing='0px';
  drawEffectBars();
  // best + lives
  ctx.textAlign='right';ctx.letterSpacing='3px';
  ctx.fillStyle='#7f8ca0';ctx.font='10px '+MONO;ctx.fillText('BEST',W-24,24);
  ctx.letterSpacing='0px';
  ctx.fillStyle='#f4f7fb';ctx.font='bold 16px '+MONO;ctx.fillText(String(best).padStart(6,'0'),W-24,44);
  for(let i=0;i<lives;i++){
    ctx.fillStyle='#f4f7fb';
    ctx.beginPath();ctx.arc(W-28-i*17,54,4.5,0,7);ctx.fill();
  }
  if(Sfx.muted){
    ctx.fillStyle='#ff5c5c';ctx.font='bold 9px '+MONO;ctx.letterSpacing='2px';
    ctx.fillText('MUTED',W-110,57);ctx.letterSpacing='0px';
  }
}

function render(){
  ctx.setTransform(DPR,0,0,DPR,0,0);
  ctx.fillStyle='#0b0e13';ctx.fillRect(0,0,W,H);
  for(const s of stars){
    ctx.globalAlpha=s.a;ctx.fillStyle='#cfe3ff';ctx.fillRect(s.x,s.y,s.s,s.s);
  }
  ctx.globalAlpha=1;
  const sx=(Math.random()*2-1)*shakeMag,sy=(Math.random()*2-1)*shakeMag;
  ctx.save();ctx.translate(sx,sy);
  drawBricks();drawPowerups();drawParticles();drawPaddle();drawBalls();drawFloaters();
  ctx.restore();
  if(flashAlpha>0&&flashColor){
    ctx.globalAlpha=flashAlpha;ctx.fillStyle=flashColor;
    ctx.fillRect(0,0,W,H);ctx.globalAlpha=1;
  }
  if(!vignette){
    vignette=ctx.createRadialGradient(W/2,H/2,H*0.35,W/2,H/2,H*0.78);
    vignette.addColorStop(0,'rgba(0,0,0,0)');
    vignette.addColorStop(1,'rgba(0,0,0,0.42)');
  }
  ctx.fillStyle=vignette;ctx.fillRect(0,0,W,H);
  drawHUD();
}

/* ================= overlays / flow ================= */
function menuHTML(){
  return '<div class="ov-eyebrow">ARCADE // BRICK-BREAKER</div>'+
    '<h1 class="ov-logo">BREAK<span>POINT</span></h1>'+
    '<div class="ov-sub">SIX LEVELS. ONE PADDLE. ZERO MERCY.</div>'+
    '<div class="ov-controls">'+
      '<div><span>&larr; &rarr; / A D</span>move</div>'+
      '<div><span>MOUSE</span>move</div>'+
      '<div><span>SPACE / CLICK</span>launch ball</div>'+
      '<div><span>P / ESC</span>pause</div>'+
      '<div><span>M</span>mute</div>'+
    '</div>'+
    (best>0?'<div class="ov-best">BEST · '+String(best).padStart(6,'0')+'</div>':'')+
    '<div class="ov-hint">PRESS SPACE OR CLICK TO START</div>';
}
function introHTML(){
  return '<div class="ov-eyebrow">LEVEL '+(level+1)+' OF '+LEVELS.length+'</div>'+
    '<div class="ov-title">'+LEVELS[level].name+'</div>'+
    '<div class="ov-sub">'+(level===0?'Clear every breakable brick. Steel is forever.':'It only gets faster from here.')+'</div>'+
    '<div class="ov-hint">LAUNCH WITH SPACE OR CLICK</div>';
}
function gameOver(){
  state='gameover';saveBest();Sfx.over();
  showOverlay('<div class="ov-eyebrow">GAME OVER</div>'+
    '<div class="ov-score">'+score+'</div>'+
    '<div class="ov-sub">REACHED LEVEL '+(level+1)+' — '+LEVELS[level].name+((score>=best&&score>0)?' · NEW BEST':'')+'</div>'+
    '<div class="ov-best">BEST · '+String(best).padStart(6,'0')+'</div>'+
    '<div class="ov-hint">SPACE OR CLICK TO RETRY</div>');
}
function winGame(){
  state='win';saveBest();Sfx.win();fountain();
  showOverlay('<div class="ov-eyebrow">ALL SIX LEVELS CLEARED</div>'+
    '<div class="ov-title" style="color:#5fd68b">YOU WIN</div>'+
    '<div class="ov-score">'+score+'</div>'+
    '<div class="ov-best">BEST · '+String(best).padStart(6,'0')+'</div>'+
    '<div class="ov-hint">SPACE OR CLICK TO PLAY AGAIN</div>');
}
function startGame(){
  score=0;lives=3;level=0;combo=0;shakeMag=0;flashAlpha=0;
  particles=[];floaters=[];
  hideOverlay();startLevel();
}
function startLevel(){
  buildLevel(level);
  balls=[];powerups=[];pendingBlasts=[];
  slowTimer=0;wideTimer=0;fireTimer=0;
  paddle.w=paddle.baseW;paddle.targetW=paddle.baseW;paddle.x=W/2;
  combo=0;cleared=false;flashAlpha=0;
  spawnStickyBall();
  state='intro';introTimer=1.7;
  showOverlay(introHTML());
  Sfx.click();
}
function togglePause(){
  if(state==='playing'){state='paused';showOverlay('<div class="ov-title">PAUSED</div><div class="ov-hint">P / ESC / CLICK TO RESUME</div>');Sfx.click();}
  else if(state==='paused'){state='playing';hideOverlay();Sfx.click();}
}
function action(){
  switch(state){
    case 'menu':case 'gameover':case 'win':startGame();break;
    case 'playing':launchBalls();break;
    case 'paused':togglePause();break;
  }
}

/* ================= input ================= */
const keys={left:false,right:false};
window.addEventListener('keydown',e=>{
  const k=e.key;
  if(k==='ArrowLeft'||k==='a'||k==='A'){keys.left=true;e.preventDefault();}
  if(k==='ArrowRight'||k==='d'||k==='D'){keys.right=true;e.preventDefault();}
  if(k===' '||k==='Enter'){e.preventDefault();Sfx.unlock();action();}
  if(k==='p'||k==='P'||k==='Escape')togglePause();
  if(k==='m'||k==='M'){const m=Sfx.toggleMute();addFloater(W/2,140,m?'MUTED':'SOUND ON','#9fb0c0',14);}
});
window.addEventListener('keyup',e=>{
  const k=e.key;
  if(k==='ArrowLeft'||k==='a'||k==='A')keys.left=false;
  if(k==='ArrowRight'||k==='d'||k==='D')keys.right=false;
});
window.addEventListener('pointermove',e=>{
  const r=canvas.getBoundingClientRect();
  if(r.width===0)return;
  if(state==='playing'||state==='intro'||state==='clearing'||state==='menu'){
    setPaddleX((e.clientX-r.left)/r.width*W);
  }
});
window.addEventListener('pointerdown',()=>{Sfx.unlock();action();});
window.addEventListener('blur',()=>{if(state==='playing')togglePause();});

/* ================= main loop ================= */
let last=performance.now();
function loop(now){
  requestAnimationFrame(loop);
  const dt=Math.min((now-last)/1000,0.033);
  last=now;time+=dt;
  setCursor();
  if(hitStop>0){hitStop-=dt;render();return;} // hit-stop freeze for punch
  if(state==='paused'||state==='menu'||state==='gameover'||state==='win'){
    updateStars(dt);updateFx(dt);render();return;
  }
  if(state==='intro'){
    updateStars(dt);updateFx(dt);
    introTimer-=dt;
    if(introTimer<=0){hideOverlay();state='playing';}
    render();return;
  }
  update(dt);render();
}

/* ================= boot ================= */
buildLevel(0);       // backdrop behind the menu
spawnStickyBall();
showOverlay(menuHTML());
requestAnimationFrame(loop);
</script>
</body>
</html>

Compare with

Nex-N2.6: other attempts