• Updated 31 Oct, 14:35 UTC
  • Next run 1 Nov, 05:17 UTCNext 1 Nov 05:17
  • Last 7 days: 4 new models · 448 runs7 d: 4 models · 448 runs
Playable Breakout

GLM 5.3 FlashZ.ai

Network offOpen alone (new tab)

Run details

Model
z-ai/glm-5.3-flash
Route
OpenRouter API, one user message
Served by
OpenInference
Effort
max, OpenRouter's default for this model
Limits
60 min · 131,072 output tokens (the model's limit)
Date
31 Oct 2026
Time
13 min 57 s
Size
29 kB
Tokens
103 in, 64,984 out (53,387 of them thinking)
List price
$0.010 at today's price ($0.016 in / $0.148 out per M)
Paid
$0.002

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

29 kB 882 lines Raw

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>BRICK RIOT</title>
<style>
  html,body{margin:0;height:100%;background:#0a0b10;display:flex;align-items:center;justify-content:center;overflow:hidden;user-select:none;-webkit-user-select:none}
  canvas{display:block;border:1px solid #262b38;border-radius:10px;box-shadow:0 24px 70px rgba(0,0,0,.6);cursor:none;touch-action:none}
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
'use strict';
/* =========================================================
   BRICK RIOT — a self-contained arcade breakout
   ========================================================= */

/* ---------- constants & palette ---------- */
const W=560, H=760, WALL=12, HUD_H=64, TOP=HUD_H+WALL;
const BRICK_X0=18, BRICK_Y0=150, BW=44, BH=20, BSTEP=48, BSTEPY=24;
const COL={
  bg:'#12141b', wall:'#1b1e28',
  bone:'#efe9db', dim:'#8b8676',
  b1:'#ffd44d', b2:'#ff9d3b', b3:'#ff5a4e',
  steel:'#454b59', steelEdge:'#626a7c',
  coral:'#ff5a4e', amber:'#ffb03a', mint:'#3fd6a4', ice:'#7fd7e4',
};
const POWCOL={W:'#3fd6a4',M:'#ffb03a',S:'#7fd7e4',L:'#ff5a4e'};
const POWLET={W:'W',M:'M',S:'S',L:'+'};

/* ---------- canvas & scaling ---------- */
const cv=document.getElementById('game');
const ctx=cv.getContext('2d');
const DPR=Math.min(2, window.devicePixelRatio||1);
cv.width=W*DPR; cv.height=H*DPR;
function resize(){
  const s=Math.min((window.innerWidth-28)/W,(window.innerHeight-28)/H);
  cv.style.width=(W*s)+'px'; cv.style.height=(H*s)+'px';
}
window.addEventListener('resize',resize); resize();

/* ---------- helpers ---------- */
const clamp=(v,a,b)=>v<a?a:v>b?b:v;
const easeOut=t=>1-Math.pow(1-t,3);
const pad2=n=>String(n).padStart(2,'0');
const pad6=n=>String(clamp(Math.floor(n),0,999999)).padStart(6,'0');
function rr(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();
}

/* ---------- hand-built 5x7 dot-matrix font ---------- */
const FONT={
'0':[14,17,19,21,25,17,14],'1':[4,12,4,4,4,4,14],'2':[14,17,1,2,4,8,31],
'3':[31,2,4,2,1,17,14],'4':[2,6,10,18,31,2,2],'5':[31,16,30,1,1,17,14],
'6':[6,8,16,30,17,17,14],'7':[31,1,2,4,8,8,8],'8':[14,17,17,14,17,17,14],
'9':[14,17,17,15,1,2,12],
'A':[14,17,17,31,17,17,17],'B':[30,17,17,30,17,17,30],'C':[14,17,16,16,16,17,14],
'D':[28,18,17,17,17,18,28],'E':[31,16,16,30,16,16,31],'F':[31,16,16,30,16,16,16],
'G':[14,17,16,23,17,17,15],'H':[17,17,17,31,17,17,17],'I':[14,4,4,4,4,4,14],
'J':[7,2,2,2,2,18,12],'K':[17,18,20,24,20,18,17],'L':[16,16,16,16,16,16,31],
'M':[17,27,21,21,17,17,17],'N':[17,17,25,21,19,17,17],'O':[14,17,17,17,17,17,14],
'P':[30,17,17,30,16,16,16],'Q':[14,17,17,17,21,18,13],'R':[30,17,17,30,20,18,17],
'S':[15,16,16,14,1,1,30],'T':[31,4,4,4,4,4,4],'U':[17,17,17,17,17,17,14],
'V':[17,17,17,17,17,10,4],'W':[17,17,17,21,21,27,17],'X':[17,17,10,4,10,17,17],
'Y':[17,17,10,4,4,4,4],'Z':[31,1,2,4,8,16,31],
' ':[0,0,0,0,0,0,0],'.':[0,0,0,0,0,12,12],',':[0,0,0,0,12,4,8],
'!':[4,4,4,4,4,0,4],'?':[14,17,1,2,4,0,4],':':[0,12,12,0,12,12,0],
'-':[0,0,0,14,0,0,0],'+':[0,4,4,31,4,4,0],'×':[0,17,10,4,10,17,0],
'·':[0,0,0,12,12,0,0],'>':[16,24,28,30,28,24,16],
};
function textW(str,p){ return str.length*6*p-p; }
function drawText(str,x,y,p,color,align){
  str=String(str).toUpperCase();
  const w=textW(str,p);
  if(align==='center') x-=w/2; else if(align==='right') x-=w;
  ctx.fillStyle=color;
  const g=p>=3?Math.max(0.6,p*0.18):0;
  for(let i=0;i<str.length;i++){
    const gl=FONT[str[i]]; if(!gl) continue;
    const cx=x+i*6*p;
    for(let r=0;r<7;r++){
      const row=gl[r]; if(!row) continue;
      for(let c=0;c<5;c++){
        if(row&(16>>c)) ctx.fillRect(cx+c*p,y+r*p,p-g,p-g);
      }
    }
  }
}

/* ---------- procedural audio (Web Audio, no assets) ---------- */
let actx=null, master=null, noiseBuf=null, muted=false;
function initAudio(){
  if(actx){ if(actx.state==='suspended') actx.resume(); return; }
  try{
    actx=new (window.AudioContext||window.webkitAudioContext)();
    master=actx.createGain(); master.gain.value=0.42; master.connect(actx.destination);
    noiseBuf=actx.createBuffer(1,actx.sampleRate*0.5,actx.sampleRate);
    const d=noiseBuf.getChannelData(0);
    for(let i=0;i<d.length;i++) d[i]=Math.random()*2-1;
  }catch(e){}
}
function tone(f,dur,type,vol,f2,delay){
  if(!actx||muted) return;
  const t=actx.currentTime+(delay||0);
  const o=actx.createOscillator(), g=actx.createGain();
  o.type=type||'square';
  o.frequency.setValueAtTime(f,t);
  if(f2) o.frequency.exponentialRampToValueAtTime(Math.max(20,f2),t+dur);
  g.gain.setValueAtTime(vol,t);
  g.gain.exponentialRampToValueAtTime(0.0001,t+dur);
  o.connect(g); g.connect(master);
  o.start(t); o.stop(t+dur+0.03);
}
function noise(dur,vol,freq,q,delay){
  if(!actx||muted||!noiseBuf) return;
  const t=actx.currentTime+(delay||0);
  const src=actx.createBufferSource(); src.buffer=noiseBuf;
  const bp=actx.createBiquadFilter(); bp.type='bandpass'; bp.frequency.value=freq; bp.Q.value=q||1;
  const g=actx.createGain(); g.gain.setValueAtTime(vol,t);
  g.gain.exponentialRampToValueAtTime(0.0001,t+dur);
  src.connect(bp); bp.connect(g); g.connect(master);
  src.start(t); src.stop(t+dur+0.03);
}
const sfx={
  brick(hp,row,combo){
    const f=(300+row*36)*(1+combo*0.02);
    tone(f,0.09,'square',0.16,f*0.7);
    noise(0.07,0.12,2200+combo*60,1.2);
  },
  chip(hp){ tone(180+hp*60,0.05,'square',0.09); },
  steel(){ tone(1700,0.05,'triangle',0.10,1400); tone(2400,0.04,'triangle',0.06,2000,0.012); },
  paddle(){ tone(200,0.09,'triangle',0.26,130); noise(0.04,0.05,700,1); },
  wall(){ tone(660,0.035,'square',0.06); },
  launch(){ noise(0.12,0.10,900,0.8); tone(480,0.10,'triangle',0.14,760); },
  combo(){ tone(880+Math.min(8,combo)*60,0.06,'square',0.10); },
  power(){ tone(523,0.07,'triangle',0.16); tone(659,0.07,'triangle',0.16,null,0.07); tone(880,0.10,'triangle',0.16,null,0.14); },
  oneUp(){ tone(660,0.08,'triangle',0.16); tone(880,0.08,'triangle',0.16,null,0.09); tone(1046,0.14,'triangle',0.18,null,0.18); },
  death(){ tone(300,0.5,'sawtooth',0.24,60); noise(0.3,0.12,300,0.7); },
  clear(){ [523,659,784,1046].forEach((f,i)=>tone(f,0.12,'triangle',0.17,null,i*0.09)); },
  over(){ [392,311,233].forEach((f,i)=>tone(f,0.3,'sawtooth',0.16,null,i*0.22)); },
};

/* ---------- levels ---------- */
const LEVELS=[
 {name:'WARM-UP',rows:[
  '...........',
  '.111111111.',
  '.111111111.',
  '.222222222.',
  '.111111111.',
  '.111111111.',
 ]},
 {name:'THE DIAMOND',rows:[
  '.....2.....',
  '....222....',
  '...23332...',
  '..2333332..',
  '...23332...',
  '....222....',
  '.....2.....',
 ]},
 {name:'PILLARS',rows:[
  'S.1.1.1.1.S',
  'S.2.2.2.2.S',
  'S.3.3.3.3.S',
  'S.2.2.2.2.S',
  'S.1.1.1.1.S',
 ]},
 {name:'CHECKMATE',rows:[
  '2.2.2.2.2.2',
  '.3.3.3.3.3.',
  '2.2.2.2.2.2',
  '.3.3.3.3.3.',
  '2.2.2.2.2.2',
  '.3.3.3.3.3.',
 ]},
 {name:'THE VAULT',rows:[
  '33333333333',
  '32222222223',
  '32S11111S23',
  '32222222223',
  '33333333333',
 ]},
];

/* ---------- state ---------- */
let state='menu', paused=false;
let score=0, lives=3, levelIdx=0, loop=0, totalLevel=1, combo=0;
let hi=0; try{ hi=parseInt(localStorage.getItem('brick-riot-hi'))||0; }catch(e){}
let newBest=false;
let shake=0, shakeX=0, shakeY=0, freezeT=0, redFlash=0;
let slowT=0, slowCur=1, wideT=0;
let balls=[], bricks=[], breakable=0, powerups=[], particles=[], popups=[], toasts=[];
let brickAnimT=0, introDur=1.5;
let serveT=0, deadT=0, clearT=0, clearBonus=0, overT=0;
let demoT=0.9, demoLaunched=false, demoRebuildT=1.4;
let tGlobal=0;
const paddle={x:W/2, y:H-60, w:92, h:14, vx:0, squash:0};
const mouse={x:W/2};
const keys={};

/* ---------- level building ---------- */
function buildBricks(idx){
  bricks=[]; breakable=0;
  const rows=LEVELS[idx].rows;
  let maxDelay=0;
  for(let r=0;r<rows.length;r++){
    for(let c=0;c<11;c++){
      const ch=rows[r][c];
      if(ch==='.') continue;
      const steel=ch==='S';
      const hp=steel?0:Math.min(3,parseInt(ch,10)+loop);
      const b={col:c,row:r,x:BRICK_X0+c*BSTEP,y:BRICK_Y0+r*BSTEPY,w:BW,h:BH,
               hp,maxHp:hp,steel,alive:true,flash:0,delay:r*0.05+c*0.014,cracks:[]};
      maxDelay=Math.max(maxDelay,b.delay);
      if(!steel){
        breakable++;
        let s=(r*73+c*137)%97+1;
        const rnd=()=>{s=(s*16807)%2147483647;return s/2147483647;};
        for(let i=0;i<3;i++){
          const x1=4+rnd()*(b.w-8), y1=2+rnd()*(b.h-4);
          const x2=clamp(x1+(rnd()-0.5)*22,3,b.w-3), y2=clamp(y1+(rnd()-0.5)*10,1,b.h-1);
          b.cracks.push([x1,y1,x2,y2]);
        }
      }
      bricks.push(b);
    }
  }
  introDur=maxDelay+0.75;
  brickAnimT=0;
}

/* ---------- game flow ---------- */
function baseSpeed(){ return Math.min(480,330+(totalLevel-1)*13); }
function newBall(){ return {x:paddle.x,y:paddle.y-8,dx:0,dy:-1,speed:baseSpeed(),r:6,trail:[]}; }
function serve(){ balls=[newBall()]; state='serve'; serveT=0; }
function launch(){
  const b=balls[0]; if(!b) return;
  const a=0.95*Math.sin(serveT*1.9);
  b.dx=Math.sin(a); b.dy=-Math.cos(a);
  b.speed=baseSpeed();
  state='play'; serveT=0;
  sfx.launch(); ring(b.x,b.y,COL.bone);
}
function startLevel(i){
  levelIdx=i; totalLevel=loop*LEVELS.length+i+1;
  buildBricks(i);
  balls=[]; powerups=[]; combo=0;
  freezeT=0; slowT=0; wideT=0; slowCur=1;
  state='intro';
  if(loop>0&&i===0) toast('LOOP '+(loop+1)+' SPEED UP');
}
function nextLevel(){
  levelIdx++;
  if(levelIdx>=LEVELS.length){ levelIdx=0; loop++; }
  startLevel(levelIdx);
}
function startGame(){
  score=0; lives=3; loop=0; newBest=false;
  startLevel(0);
}
function levelCleared(){
  state='clear'; clearT=0;
  clearBonus=400+lives*200;
  score+=clearBonus;
  if(score>hi){ hi=score; newBest=true; }
  for(const b of balls){ ring(b.x,b.y,COL.bone); sparks(b.x,b.y,COL.bone,6); }
  balls.length=0; powerups.length=0;
  sfx.clear(); shake=10;
}
function loseLife(){
  lives--; redFlash=1; shake=13; combo=0;
  sfx.death();
  if(lives<0) gameOver();
  else { deadT=0.7; state='dead'; }
}
function gameOver(){
  state='over'; overT=0;
  try{ localStorage.setItem('brick-riot-hi',String(hi)); }catch(e){}
  sfx.over();
}

/* ---------- effects ---------- */
function trimParticles(){ if(particles.length>320) particles.splice(0,particles.length-320); }
function burst(br){
  const col=COL['b'+br.maxHp]||COL.bone;
  const cx=br.x+br.w/2, cy=br.y+br.h/2;
  for(let i=0;i<12;i++){
    const a=Math.random()*Math.PI*2, sp=60+Math.random()*260;
    particles.push({type:'shard',x:cx,y:cy,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp-90,g:1000,
      rot:Math.random()*Math.PI,vr:(Math.random()-0.5)*12,size:3+Math.random()*5,
      color:col,life:0.45+Math.random()*0.45,max:0.9});
  }
  for(let i=0;i<5;i++){
    const a=Math.random()*Math.PI*2, sp=120+Math.random()*260;
    particles.push({type:'spark',x:cx,y:cy,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp,g:300,
      color:COL.bone,life:0.18+Math.random()*0.14,max:0.32});
  }
  trimParticles();
}
function sparks(x,y,color,n){
  for(let i=0;i<n;i++){
    const a=Math.random()*Math.PI*2, sp=70+Math.random()*220;
    particles.push({type:'spark',x,y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp,g:500,
      color,life:0.15+Math.random()*0.12,max:0.27});
  }
  trimParticles();
}
function ring(x,y,color){ particles.push({type:'ring',x,y,r:5,life:0.28,max:0.28,color}); }
function popup(x,y,text,color){ popups.push({x,y,text,color,life:0.85,max:0.85}); }
function toast(text){ toasts.push({text,life:1.5,max:1.5}); }

/* ---------- power-ups ---------- */
function spawnPowerup(br){
  const r=Math.random();
  let t='W';
  if(r<0.35) t='W'; else if(r<0.65) t='M'; else if(r<0.88) t='S'; else t='L';
  powerups.push({x:br.x+br.w/2,y:br.y+br.h/2,type:t,seed:Math.random()*6.28});
}
function multiball(){
  const cur=balls.slice();
  for(const b of cur){
    for(const da of [-0.45,0.45]){
      if(balls.length>=6) return;
      const a=Math.atan2(b.dy,b.dx)+da;
      balls.push({x:b.x,y:b.y,dx:Math.cos(a),dy:Math.sin(a),speed:b.speed,r:6,trail:[]});
    }
  }
}
function applyPower(t){
  if(t==='W'){ wideT=12; popup(paddle.x,paddle.y-30,'WIDE',COL.mint); sfx.power(); }
  else if(t==='M'){ multiball(); popup(paddle.x,paddle.y-30,'MULTI ×3',COL.amber); sfx.power(); }
  else if(t==='S'){ slowT=8; popup(paddle.x,paddle.y-30,'SLOW-MO',COL.ice); sfx.power(); }
  else if(t==='L'){ lives=Math.min(6,lives+1); popup(paddle.x,paddle.y-30,'1UP',COL.coral); sfx.oneUp(); }
}
function updatePowerups(dt){
  for(let i=powerups.length-1;i>=0;i--){
    const p=powerups[i];
    p.y+=150*dt;
    if(p.y>H+24){ powerups.splice(i,1); continue; }
    if(p.y+9>=paddle.y && p.y-9<=paddle.y+paddle.h+8 && Math.abs(p.x-paddle.x)<paddle.w/2+16){
      applyPower(p.type);
      ring(p.x,paddle.y-6,POWCOL[p.type]);
      powerups.splice(i,1);
    }
  }
}

/* ---------- physics ---------- */
function hitBrick(br,demo){
  if(br.steel){
    if(!demo) sfx.steel();
    sparks(br.x+br.w/2,br.y+br.h/2,'#a9b0bd',5);
    shake=Math.min(16,shake+0.8);
    return;
  }
  br.hp--; br.flash=1;
  if(br.hp<=0){
    br.alive=false; breakable--;
    if(!demo){
      combo++;
      const pts=(br.maxHp===1?50:br.maxHp===2?80:120)*(1+loop)+combo*10;
      score+=pts;
      if(score>hi){ hi=score; newBest=true; }
      popups.push({x:br.x+br.w/2,y:br.y+br.h/2,text:'+'+pts,color:COL.bone,life:0.7,max:0.7});
      if(combo>=3) popups.push({x:br.x+br.w/2,y:br.y+br.h/2-18,text:'×'+combo,color:COL.coral,life:0.9,max:0.9});
      if(combo%5===0) sfx.combo();
      if(Math.random()<0.14) spawnPowerup(br);
    }
    burst(br);
    shake=Math.min(16,shake+2.4+combo*0.25);
    freezeT=Math.min(0.06,0.035+(combo>=5?0.015:0));
    if(!demo){
      sfx.brick(br.maxHp,br.row,combo);
      if(breakable===0) levelCleared();
    }
  } else {
    if(!demo) sfx.chip(br.maxHp);
    sparks(br.x+br.w/2,br.y+br.h/2,COL['b'+br.hp]||COL.bone,3);
    shake=Math.min(16,shake+0.6);
  }
}
function collideBricks(b,demo){
  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) continue;
    if(dx===0&&dy===0){ b.dy=-b.dy; }
    else if(Math.abs(dx)>Math.abs(dy)){
      b.dx=Math.abs(b.dx)*(dx<0?-1:1);
      b.x=nx+(dx<0?-b.r:b.r);
    } else {
      b.dy=Math.abs(b.dy)*(dy<0?-1:1);
      b.y=ny+(dy<0?-b.r:b.r);
    }
    b.dx+=(Math.random()-0.5)*0.02; b.dy+=(Math.random()-0.5)*0.008;
    const m=Math.hypot(b.dx,b.dy); b.dx/=m; b.dy/=m;
    hitBrick(br,demo);
    return;
  }
}
function collidePaddle(b,demo){
  if(b.dy<=0) return;
  if(b.y+b.r<paddle.y||b.y-b.r>paddle.y+paddle.h+8) return;
  if(Math.abs(b.x-paddle.x)>paddle.w/2+b.r*0.85) return;
  b.y=paddle.y-b.r;
  const rel=clamp((b.x-paddle.x)/(paddle.w/2),-1,1);
  const ang=rel*1.05;
  let vx=Math.sin(ang)*b.speed+clamp(paddle.vx,-700,700)*0.22;
  let vy=-Math.cos(ang)*b.speed;
  let m=Math.hypot(vx,vy);
  b.dx=vx/m; b.dy=vy/m;
  if(Math.abs(b.dy)<0.32){
    b.dy=-0.32; const s=b.dx<0?-1:1; b.dx=s*Math.sqrt(1-0.1024);
    m=Math.hypot(b.dx,b.dy); b.dx/=m; b.dy/=m;
  }
  b.speed=Math.min(560,b.speed*1.015+1.5);
  paddle.squash=1; combo=0;
  sparks(b.x,paddle.y,COL.bone,5);
  if(!demo){ sfx.paddle(); shake=Math.min(14,shake+1.1); }
}
function physicsStep(dt,demo){
  if(freezeT>0){ freezeT-=dt; return; }
  const spdMul=slowCur;
  for(let i=balls.length-1;i>=0;i--){
    const b=balls[i];
    const spd=b.speed*spdMul;
    const steps=Math.max(1,Math.ceil(spd*dt/5));
    const sdt=dt/steps;
    for(let s=0;s<steps;s++){
      b.x+=b.dx*spd*sdt; b.y+=b.dy*spd*sdt;
      if(b.x-b.r<WALL){ b.x=WALL+b.r; b.dx=Math.abs(b.dx); if(!demo) sfx.wall(); }
      if(b.x+b.r>W-WALL){ b.x=W-WALL-b.r; b.dx=-Math.abs(b.dx); if(!demo) sfx.wall(); }
      if(b.y-b.r<TOP){ b.y=TOP+b.r; b.dy=Math.abs(b.dy); if(!demo) sfx.wall(); }
      if(Math.abs(b.dy)<0.06){
        b.dy=0.06*(b.dy<0?-1:1);
        const m=Math.hypot(b.dx,b.dy); b.dx/=m; b.dy/=m;
      }
      collidePaddle(b,demo);
      collideBricks(b,demo);
      if(state==='clear'||state==='dead'||state==='over') return;
      if(b.y-b.r>H+10){
        balls.splice(i,1);
        if(demo) demoT=0.6;
        else if(balls.length===0) loseLife();
        return;
      }
    }
    b.trail.push({x:b.x,y:b.y});
    if(b.trail.length>11) b.trail.shift();
  }
}

/* ---------- paddle ---------- */
function movePaddle(dt){
  const kb=(keys['ArrowLeft']||keys['KeyA']?-1:0)+(keys['ArrowRight']||keys['KeyD']?1:0);
  const px=paddle.x;
  if(kb!==0){ paddle.x+=kb*640*dt; mouse.x=paddle.x; }
  else { paddle.x+=(mouse.x-paddle.x)*Math.min(1,dt*16); }
  paddle.x=clamp(paddle.x,WALL+paddle.w/2+2,W-WALL-paddle.w/2-2);
  paddle.vx=clamp((paddle.x-px)/Math.max(dt,1e-4),-700,700);
}

/* ---------- update ---------- */
function updateParticles(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; }
    if(p.type==='ring'){ p.r+=140*dt; continue; }
    p.x+=p.vx*dt; p.y+=p.vy*dt; p.vy+=p.g*dt; p.rot+=p.vr*dt;
  }
}
function updatePopups(dt){
  for(let i=popups.length-1;i>=0;i--){
    const p=popups[i];
    p.y-=48*dt; p.life-=dt;
    if(p.life<=0) popups.splice(i,1);
  }
}
function updateToasts(dt){
  for(let i=toasts.length-1;i>=0;i--){
    toasts[i].life-=dt;
    if(toasts[i].life<=0) toasts.splice(i,1);
  }
}
function updateMenu(dt){
  if(balls.length===0){
    demoT-=dt;
    if(demoT<=0){ balls=[newBall()]; demoLaunched=false; demoT=0.55; }
  }
  const b0=balls[0];
  if(b0&&!demoLaunched){
    b0.x=paddle.x; b0.y=paddle.y-b0.r-2;
    demoT-=dt;
    if(demoT<=0){
      const a=Math.random()*1.4-0.7;
      b0.dx=Math.sin(a); b0.dy=-Math.cos(a); b0.speed=340;
      demoLaunched=true;
    }
  } else if(b0){
    const target=clamp(b0.x+b0.dx*30, WALL+paddle.w/2+2, W-WALL-paddle.w/2-2);
    const px=paddle.x, mv=540*dt;
    paddle.x+=clamp(target-paddle.x,-mv,mv);
    paddle.vx=clamp((paddle.x-px)/Math.max(dt,1e-4),-700,700);
  }
  physicsStep(dt,true);
  if(breakable===0){
    demoRebuildT-=dt;
    if(demoRebuildT<=0){ buildBricks(0); demoRebuildT=1.4; }
  }
}
function update(dt){
  shake*=Math.exp(-8*dt); if(shake<0.04) shake=0;
  shakeX=(Math.random()*2-1)*shake; shakeY=(Math.random()*2-1)*shake;
  redFlash=Math.max(0,redFlash-1.6*dt);
  brickAnimT+=dt;
  for(const br of bricks) if(br.flash>0) br.flash=Math.max(0,br.flash-dt*6);
  updateParticles(dt); updatePopups(dt); updateToasts(dt);
  if(wideT>0) wideT-=dt;
  if(slowT>0) slowT-=dt;
  slowCur+=((slowT>0?0.6:1)-slowCur)*Math.min(1,dt*7);
  paddle.w+=((wideT>0?150:92)-paddle.w)*Math.min(1,dt*9);
  paddle.squash=Math.max(0,paddle.squash-dt*6.5);

  switch(state){
    case 'menu': updateMenu(dt); break;
    case 'intro':
      movePaddle(dt);
      if(brickAnimT>=introDur) serve();
      break;
    case 'serve':
      movePaddle(dt); serveT+=dt;
      if(balls[0]){ balls[0].x=paddle.x; balls[0].y=paddle.y-balls[0].r-2; }
      break;
    case 'play':
      movePaddle(dt); physicsStep(dt,false); updatePowerups(dt);
      break;
    case 'dead': movePaddle(dt); deadT-=dt; if(deadT<=0) serve(); break;
    case 'clear': movePaddle(dt); clearT+=dt; break;
    case 'over': overT+=dt; break;
  }
}

/* ---------- rendering ---------- */
function drawGrid(){
  ctx.fillStyle='rgba(240,235,220,0.05)';
  for(let x=WALL+17;x<W-WALL;x+=34)
    for(let y=TOP+17;y<H;y+=34)
      ctx.fillRect(x-1,y-1,2,2);
}
function drawWalls(){
  ctx.fillStyle=COL.wall;
  ctx.fillRect(0,HUD_H,WALL,H-HUD_H);
  ctx.fillRect(W-WALL,HUD_H,WALL,H-HUD_H);
  ctx.fillRect(0,HUD_H,W,WALL);
  ctx.fillStyle='rgba(240,235,220,0.10)';
  ctx.fillRect(WALL,TOP,1.5,H-TOP);
  ctx.fillRect(W-WALL-1.5,TOP,1.5,H-TOP);
  ctx.fillRect(WALL,TOP,W-2*WALL,1.5);
}
function drawBricks(){
  for(const b of bricks){
    if(!b.alive) continue;
    let y=b.y, e=1;
    const ap=clamp((brickAnimT-b.delay)/0.5,0,1);
    if(ap<1){ e=easeOut(ap); y=b.y-(1-e)*240; }
    ctx.globalAlpha=e;
    const color=b.steel?COL.steel:COL['b'+Math.min(3,b.hp)];
    rr(b.x,y,b.w,b.h,4); ctx.fillStyle=color; ctx.fill();
    if(b.steel){
      ctx.strokeStyle=COL.steelEdge; ctx.lineWidth=1.5;
      rr(b.x+1.5,y+1.5,b.w-3,b.h-3,3); ctx.stroke();
      ctx.fillStyle='rgba(240,235,220,0.28)';
      ctx.fillRect(b.x+4,y+b.h/2-1,2,2);
      ctx.fillRect(b.x+b.w-6,y+b.h/2-1,2,2);
    } else {
      ctx.fillStyle='rgba(255,255,255,0.22)';
      rr(b.x+3,y+2.5,b.w-6,2.5,1.2); ctx.fill();
      ctx.fillStyle='rgba(0,0,0,0.18)';
      rr(b.x+3,y+b.h-4.5,b.w-6,2.5,1.2); ctx.fill();
      if(b.hp<b.maxHp){
        ctx.strokeStyle='rgba(10,11,15,0.5)'; ctx.lineWidth=1.4;
        ctx.beginPath();
        for(const seg of b.cracks){
          ctx.moveTo(b.x+seg[0],y+seg[1]);
          ctx.lineTo(b.x+seg[2],y+seg[3]);
        }
        ctx.stroke();
      }
    }
    if(b.flash>0){
      ctx.fillStyle='rgba(255,255,255,'+(b.flash*0.85)+')';
      rr(b.x-1,y-1,b.w+2,b.h+2,5); ctx.fill();
    }
    ctx.globalAlpha=1;
  }
}
function drawPowerups(){
  for(const p of powerups){
    ctx.save();
    ctx.translate(p.x,p.y);
    ctx.rotate(Math.sin(tGlobal*3+p.seed)*0.12);
    rr(-18,-9,36,18,9); ctx.fillStyle=POWCOL[p.type]; ctx.fill();
    rr(-15,-6,30,3,1.5); ctx.fillStyle='rgba(255,255,255,0.3)'; ctx.fill();
    drawText(POWLET[p.type],0,-6.6,1.9,'#15161c','center');
    ctx.restore();
  }
}
function drawParticles(){
  for(const p of particles){
    const a=clamp(p.life/p.max,0,1);
    if(p.type==='ring'){
      ctx.globalAlpha=a*0.7; ctx.strokeStyle=p.color; ctx.lineWidth=2.5;
      ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,7); ctx.stroke();
    } else if(p.type==='spark'){
      ctx.globalAlpha=a; ctx.strokeStyle=p.color; ctx.lineWidth=1.8;
      ctx.beginPath();
      ctx.moveTo(p.x,p.y);
      ctx.lineTo(p.x-p.vx*0.035,p.y-p.vy*0.035);
      ctx.stroke();
    } else {
      ctx.globalAlpha=a;
      ctx.save();
      ctx.translate(p.x,p.y); ctx.rotate(p.rot);
      const s=p.size*(0.4+0.6*a);
      ctx.fillStyle=p.color;
      ctx.fillRect(-s/2,-s/2,s,s*0.7);
      ctx.restore();
    }
  }
  ctx.globalAlpha=1;
}
function drawBall(b,moving){
  for(let i=0;i<b.trail.length;i++){
    const t=b.trail[i], k=(i+1)/b.trail.length;
    ctx.globalAlpha=k*0.14;
    ctx.fillStyle=slowCur<0.95?'#78e1c3':'#f0ebdc';
    ctx.beginPath(); ctx.arc(t.x,t.y,b.r*k*0.85,0,7); ctx.fill();
  }
  ctx.globalAlpha=1;
  ctx.save();
  ctx.translate(b.x,b.y);
  if(moving){
    ctx.rotate(Math.atan2(b.dy,b.dx));
    const st=1+Math.min(0.3,b.speed/2400);
    ctx.scale(st,1/st);
  } else {
    const pu=1+0.06*Math.sin(tGlobal*7);
    ctx.scale(pu,pu);
  }
  ctx.fillStyle=COL.bone;
  ctx.beginPath(); ctx.arc(0,0,b.r,0,7); ctx.fill();
  ctx.fillStyle='rgba(10,11,15,0.14)';
  ctx.beginPath(); ctx.arc(b.r*0.32,b.r*0.32,b.r*0.5,0,7); ctx.fill();
  ctx.restore();
}
function drawAim(b){
  const a=0.95*Math.sin(serveT*1.9);
  const dx=Math.sin(a), dy=-Math.cos(a);
  ctx.fillStyle=COL.bone;
  for(let i=1;i<=8;i++){
    const d=16+i*13;
    const s=Math.max(1.2,3.4-i*0.28);
    ctx.globalAlpha=clamp(0.55-i*0.05+0.15*Math.sin(tGlobal*6+i),0,1);
    ctx.fillRect(b.x+dx*d-s/2,b.y+dy*d-s/2,s,s);
  }
  ctx.globalAlpha=1;
}
function drawBalls(){
  const moving=state==='play'||state==='menu';
  for(const b of balls) drawBall(b,moving);
  if(state==='serve'&&balls[0]) drawAim(balls[0]);
}
function drawPaddle(){
  const k=paddle.squash;
  const w=paddle.w*(1+0.16*k), h=paddle.h*(1-0.28*k);
  const x=paddle.x-w/2, y=paddle.y+(paddle.h-h);
  rr(x,y,w,h,7); ctx.fillStyle=COL.bone; ctx.fill();
  ctx.fillStyle='rgba(0,0,0,0.22)';
  rr(x+2,y+h-3,w-4,2.2,1.1); ctx.fill();
  const tipCol=wideT>0?COL.mint:COL.amber;
  const tipA=(wideT>0&&wideT<1.8)?0.45+0.55*Math.abs(Math.sin(tGlobal*9)):1;
  ctx.globalAlpha=tipA; ctx.fillStyle=tipCol;
  rr(x+3,y+2.5,7,h-5,2.5); ctx.fill();
  rr(x+w-10,y+2.5,7,h-5,2.5); ctx.fill();
  ctx.globalAlpha=1;
  ctx.fillStyle=COL.coral;
  rr(paddle.x-2,y+2,4,4,1.5); ctx.fill();
}
function drawPopups(){
  for(const p of popups){
    ctx.globalAlpha=clamp(p.life/p.max,0,1);
    drawText(p.text,p.x,p.y,1.7,p.color,'center');
  }
  ctx.globalAlpha=1;
}
function drawHUD(){
  ctx.fillStyle='rgba(240,235,220,0.06)';
  ctx.fillRect(0,HUD_H-1,W,1);
  drawText('SCORE',18,12,1.4,COL.dim,'left');
  drawText(pad6(score),18,26,2.6,COL.bone,'left');
  drawText('LEVEL',W/2,12,1.4,COL.dim,'center');
  drawText(pad2(totalLevel),W/2,26,2.6,COL.bone,'center');
  drawText('HI',W-18,12,1.4,COL.dim,'right');
  drawText(pad6(hi),W-18,26,2.6,COL.bone,'right');
  const n=Math.max(0,lives);
  if(n>0){
    const iw=16,g=6,tw=n*iw+(n-1)*g;
    let lx=W/2-tw/2;
    ctx.fillStyle=COL.bone;
    for(let i=0;i<n;i++){ rr(lx+i*(iw+g),54,iw,4,2); ctx.fill(); }
  }
  if(combo>=3){
    ctx.globalAlpha=0.7+0.3*Math.sin(tGlobal*8);
    drawText('COMBO ×'+combo,18,50,1.6,COL.coral,'left');
    ctx.globalAlpha=1;
  }
  if(muted) drawText('MUTED',W-18,52,1.4,COL.dim,'right');
  if(state==='serve'||state==='play'){
    ctx.globalAlpha=0.4;
    drawText('MOUSE MOVE · SPACE LAUNCH · P PAUSE · M SOUND',W/2,H-16,1.35,COL.dim,'center');
    ctx.globalAlpha=1;
  }
}
function drawToasts(){
  for(const t of toasts){
    ctx.globalAlpha=Math.min(1,t.life/0.35);
    drawText(t.text,W/2,112-(t.max-t.life)*14,1.8,COL.amber,'center');
  }
  ctx.globalAlpha=1;
}
function drawOverlays(){
  const blink=0.4+0.6*Math.max(0,Math.sin(tGlobal*4));
  if(paused){
    ctx.fillStyle='rgba(10,11,15,0.66)'; ctx.fillRect(0,0,W,H);
    drawText('PAUSED',W/2,320,4,COL.bone,'center');
    ctx.globalAlpha=blink;
    drawText('P OR ESC TO RESUME',W/2,392,1.7,COL.dim,'center');
    ctx.globalAlpha=1;
    return;
  }
  if(state==='menu'){
    ctx.fillStyle='rgba(11,12,17,0.62)'; ctx.fillRect(0,0,W,H);
    if(hi>0) drawText('HI SCORE '+pad6(hi),W/2,116,1.6,COL.dim,'center');
    drawText('BRICK',W/2,170,12.5,COL.bone,'center');
    drawText('RIOT',W/2,272,12.5,COL.coral,'center');
    ctx.fillStyle=COL.coral;
    ctx.fillRect(W/2-64,370,128,2.5);
    drawText('MOUSE OR ARROW KEYS TO MOVE',W/2,428,1.7,COL.dim,'center');
    drawText('SPACE OR CLICK TO LAUNCH',W/2,456,1.7,COL.dim,'center');
    drawText('P PAUSE · M SOUND',W/2,484,1.7,COL.dim,'center');
    ctx.globalAlpha=blink;
    drawText('PRESS SPACE TO START',W/2,546,2.4,COL.coral,'center');
    ctx.globalAlpha=1;
    return;
  }
  if(state==='intro'){
    const aIn=clamp(brickAnimT/0.25,0,1);
    const aOut=clamp((introDur-brickAnimT)/0.35,0,1);
    ctx.globalAlpha=aIn*aOut;
    drawText('LEVEL '+pad2(totalLevel),W/2,296,3.2,COL.bone,'center');
    drawText(LEVELS[levelIdx].name,W/2,346,2.2,COL.coral,'center');
    ctx.globalAlpha=1;
  }
  if(state==='serve'){
    ctx.globalAlpha=blink;
    drawText('SPACE OR CLICK TO LAUNCH',W/2,636,1.7,COL.dim,'center');
    ctx.globalAlpha=1;
  }
  if(state==='clear'){
    ctx.fillStyle='rgba(10,11,15,0.55)'; ctx.fillRect(0,0,W,H);
    drawText('LEVEL '+pad2(totalLevel)+' CLEAR',W/2,286,3,COL.bone,'center');
    drawText('BONUS +'+clearBonus,W/2,344,2.2,COL.amber,'center');
    drawText('SCORE '+pad6(score),W/2,388,1.8,COL.dim,'center');
    if(clearT>0.55){
      ctx.globalAlpha=blink;
      drawText('SPACE TO CONTINUE',W/2,452,2,COL.bone,'center');
      ctx.globalAlpha=1;
    }
  }
  if(state==='over'){
    ctx.fillStyle='rgba(10,11,15,0.7)'; ctx.fillRect(0,0,W,H);
    drawText('GAME OVER',W/2,270,4.2,COL.coral,'center');
    drawText('SCORE '+pad6(score),W/2,348,2.2,COL.bone,'center');
    if(newBest){
      ctx.globalAlpha=blink;
      drawText('NEW BEST!',W/2,392,2,COL.amber,'center');
      ctx.globalAlpha=1;
    } else {
      drawText('BEST '+pad6(hi),W/2,392,1.8,COL.dim,'center');
    }
    drawText('REACHED LEVEL '+pad2(totalLevel),W/2,428,1.6,COL.dim,'center');
    if(overT>0.7){
      ctx.globalAlpha=blink;
      drawText('SPACE TO RETRY',W/2,492,2,COL.bone,'center');
      ctx.globalAlpha=1;
    }
  }
}
function render(){
  ctx.setTransform(DPR,0,0,DPR,0,0);
  ctx.fillStyle=COL.bg; ctx.fillRect(0,0,W,H);
  ctx.save();
  ctx.translate(shakeX,shakeY);
  drawGrid();
  drawWalls();
  drawBricks();
  drawPowerups();
  drawParticles();
  drawBalls();
  drawPaddle();
  drawPopups();
  if(state!=='menu') drawHUD();
  ctx.restore();
  drawOverlays();
  drawToasts();
  if(redFlash>0){
    ctx.fillStyle='rgba(255,70,60,'+(redFlash*0.22)+')';
    ctx.fillRect(0,H-70,W,70);
    ctx.fillStyle='rgba(255,70,60,'+(redFlash*0.55)+')';
    ctx.fillRect(0,H-3,W,3);
  }
}

/* ---------- input ---------- */
function press(){
  if(paused){ paused=false; return; }
  switch(state){
    case 'menu': startGame(); break;
    case 'intro': brickAnimT=Math.max(brickAnimT,introDur); serve(); break;
    case 'serve': launch(); break;
    case 'clear': if(clearT>0.55) nextLevel(); break;
    case 'over': if(overT>0.7) startGame(); break;
  }
}
window.addEventListener('keydown',e=>{
  if(['ArrowLeft','ArrowRight','ArrowUp','ArrowDown','Space'].includes(e.code)) e.preventDefault();
  initAudio();
  if(e.repeat) return;
  keys[e.code]=true;
  if(e.code==='KeyM'){ muted=!muted; toast(muted?'SOUND OFF':'SOUND ON'); }
  if(e.code==='KeyP'||e.code==='Escape'){
    if(state==='play'||state==='serve'||state==='dead') paused=!paused;
  }
  if(e.code==='Space'||e.code==='Enter') press();
});
window.addEventListener('keyup',e=>{ keys[e.code]=false; });
window.addEventListener('pointermove',e=>{
  const r=cv.getBoundingClientRect();
  mouse.x=(e.clientX-r.left)*W/r.width;
});
cv.addEventListener('pointerdown',e=>{ e.preventDefault(); initAudio(); press(); });
window.addEventListener('blur',()=>{
  if(state==='play'||state==='serve'||state==='dead') paused=true;
});

/* ---------- boot ---------- */
buildBricks(0);
let last=performance.now();
function frame(now){
  const dt=Math.min(0.033,(now-last)/1000);
  last=now;
  tGlobal+=dt;
  if(!paused) update(dt);
  render();
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script>
</body>
</html>

Compare with

GLM 5.3 Flash: other attempts