Models 44

Muse Ember

Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
31.1 s
Cost
$0.0071
Output tokens
14,131
Spent thinking
4,286 tokens
Speed
487.7 tok/s
Console errors
0
First token after
2.1 s
First visible text after
14.9 s
Prompt tokens
526
Code size
28.1 KB
Finish reason
stop
Served by
Meta
Model id
meta/muse-ember
Reasoning sent
{"effort":"high"}
Max output
943,718 tokens
Generated
Nov 12, 2026

What it was asked

The prompt
Build a polished Breakout (brick breaker) game in a single HTML file using vanilla JavaScript and a <canvas>. Requirements: - A paddle controlled with the mouse and with the left/right arrow keys. - The bounce angle off the paddle depends on where the ball hits it (center = straight up, edges = sharp angle). - One level with at least 6 rows of bricks, mixing normal bricks and tougher bricks that need 2 hits and visibly crack after the first one. - Some bricks visibly carry a power-up icon; when broken they drop it, and catching it with the paddle gives either a wider paddle (10 seconds) or multi-ball (3 balls). - The ball must never pass through bricks or the paddle. - Particles when a brick breaks. - 3 lives, score displayed. Game over and victory screens with a restart option.
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task.

Output rules:
- Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block.
- Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task.
- Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw.
- The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar.
- Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction.
- Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session.
- Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing.
- The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message.
- The code must run with zero errors in the console of the latest Chrome.

Code run-1.html · 660 lines

<!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}
  html,body{height:100%}
  body{
    background:radial-gradient(1000px 600px at 50% -10%, #1b2350 0%, #0b0f26 45%, #05060f 100%);
    color:#eef2ff;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
    min-height:100vh;display:flex;flex-direction:column;align-items:center;
    padding:14px 12px 18px;overflow-x:hidden;
  }
  .wrap{width:100%;max-width:920px}
  header{text-align:center;margin-bottom:10px}
  h1{
    font-size:clamp(28px,5vw,42px);letter-spacing:.14em;font-weight:900;line-height:1;
    background:linear-gradient(90deg,#6ef3ff,#7c8cff,#ff6ec7,#ffd166);
    -webkit-background-clip:text;background-clip:text;color:transparent;
    text-shadow:0 0 28px rgba(124,140,255,.25);
  }
  .sub{margin-top:5px;font-size:12px;letter-spacing:.28em;color:#8fa0d8;text-transform:uppercase}
  .hud{
    display:flex;flex-wrap:wrap;gap:8px;justify-content:space-between;align-items:stretch;
    margin:10px 0 10px;
  }
  .pill{
    flex:1 1 auto;min-width:92px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);
    border-radius:12px;padding:7px 10px;text-align:center;backdrop-filter:blur(6px);
  }
  .pill .label{font-size:10px;letter-spacing:.18em;color:#8fa0d8;text-transform:uppercase}
  .pill .value{font-size:19px;font-weight:800;line-height:1.15;color:#fff;font-variant-numeric:tabular-nums}
  #widePill{display:none}
  #widePill.on{display:block}
  #wideBarOuter{height:5px;background:rgba(255,255,255,.15);border-radius:99px;margin-top:4px;overflow:hidden}
  #wideBar{height:100%;width:100%;background:linear-gradient(90deg,#6ef3ff,#7c8cff);border-radius:99px}
  .btns{display:flex;gap:6px}
  .iconBtn{
    cursor:pointer;border:1px solid rgba(255,255,255,.18);background:rgba(255,255,255,.07);color:#fff;
    border-radius:12px;padding:0 12px;font-size:16px;min-height:44px;
  }
  .iconBtn:hover{background:rgba(255,255,255,.14)}
  .stage{
    position:relative;border-radius:18px;overflow:hidden;
    border:1px solid rgba(140,160,255,.35);
    box-shadow:0 0 0 4px rgba(80,100,255,.08),0 18px 50px rgba(0,0,0,.55),0 0 36px rgba(110,243,255,.14);
    background:#070a18;line-height:0;
  }
  canvas{width:100%;height:auto;display:block;touch-action:none}
  #demoBanner{
    position:absolute;top:12px;left:50%;transform:translateX(-50%);
    line-height:1.2;white-space:nowrap;max-width:94%;
    background:rgba(8,10,28,.78);border:1px solid rgba(110,243,255,.55);color:#dffcff;
    padding:8px 14px;border-radius:999px;font-size:13px;font-weight:700;letter-spacing:.02em;
    box-shadow:0 0 18px rgba(110,243,255,.35);backdrop-filter:blur(6px);
    animation:pulse 1.6s ease-in-out infinite;z-index:5;text-align:center;
  }
  @keyframes pulse{0%,100%{transform:translateX(-50%) scale(1)}50%{transform:translateX(-50%) scale(1.04)}}
  #overlay{
    position:absolute;inset:0;display:none;align-items:center;justify-content:center;
    background:rgba(4,6,18,.72);backdrop-filter:blur(3px);line-height:1.4;z-index:6;padding:16px;
  }
  #overlay.show{display:flex}
  .card{
    text-align:center;background:rgba(12,16,38,.92);border:1px solid rgba(255,255,255,.16);
    border-radius:18px;padding:22px 20px;max-width:420px;width:100%;
    box-shadow:0 20px 50px rgba(0,0,0,.5);
  }
  .card h2{font-size:34px;letter-spacing:.08em;margin-bottom:6px}
  .card p{color:#aeb9e6;font-size:14px;margin-bottom:14px}
  .card .final{font-size:18px;color:#fff;font-weight:800;margin-bottom:16px}
  .playBtn{
    cursor:pointer;border:none;border-radius:999px;padding:13px 26px;font-size:16px;font-weight:900;letter-spacing:.04em;
    color:#07101f;background:linear-gradient(90deg,#6ef3ff,#7c8cff);box-shadow:0 8px 24px rgba(110,243,255,.35);
  }
  .playBtn:hover{filter:brightness(1.08)} .playBtn:active{transform:scale(.98)}
  .controls{
    display:flex;flex-wrap:wrap;gap:6px 14px;justify-content:center;margin-top:10px;
    color:#8fa0d8;font-size:12.5px;text-align:center;line-height:1.5;
  }
  .controls b{color:#e8edff;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.13);border-radius:6px;padding:1px 6px;font-size:11px}
  .legend{margin-top:8px;display:flex;flex-wrap:wrap;gap:8px;justify-content:center;font-size:12px;color:#aeb9e6}
  .legend span{background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.1);padding:4px 9px;border-radius:999px}
  @media(max-width:560px){ .pill{min-width:76px;padding:6px 6px} .pill .value{font-size:16px} #demoBanner{font-size:11px;padding:7px 10px} }
</style>
</head>
<body>
<div class="wrap">
  <header>
    <h1>NEON BREAKOUT</h1>
    <div class="sub">Brick Breaker &bull; 3 Lives &bull; Power-Ups</div>
  </header>

  <div class="hud">
    <div class="pill"><div class="label">Score</div><div class="value" id="scoreVal">0</div></div>
    <div class="pill"><div class="label">Best</div><div class="value" id="bestVal">0</div></div>
    <div class="pill"><div class="label">Lives</div><div class="value" id="livesVal">♥♥♥</div></div>
    <div class="pill"><div class="label">Bricks Left</div><div class="value" id="bricksVal">70</div></div>
    <div class="pill"><div class="label">Balls</div><div class="value" id="ballsVal">1</div></div>
    <div class="pill" id="widePill"><div class="label">Wide Paddle</div><div class="value" id="wideVal">10s</div><div id="wideBarOuter"><div id="wideBar"></div></div></div>
    <div class="btns">
      <button class="iconBtn" id="pauseBtn" title="Pause (P)">⏸</button>
      <button class="iconBtn" id="soundBtn" title="Sound (M)">🔊</button>
      <button class="iconBtn" id="restartBtn" title="Restart (R)">↻</button>
    </div>
  </div>

  <div class="stage" id="stage">
    <canvas id="game" width="900" height="600"></canvas>
    <div id="demoBanner">▶ DEMO PLAYING &nbsp;•&nbsp; Click or press a key to take control</div>
    <div id="overlay">
      <div class="card">
        <h2 id="ovTitle">GAME OVER</h2>
        <p id="ovText">The bricks win this time.</p>
        <div class="final" id="ovScore">Score: 0</div>
        <button class="playBtn" id="playAgainBtn">▶ &nbsp;PLAY AGAIN</button>
      </div>
    </div>
  </div>

  <div class="controls">
    <span><b>Mouse</b> move paddle</span>
    <span><b>← →</b> / <b>A D</b> move</span>
    <span><b>Space</b> launch / pause</span>
    <span><b>P</b> pause</span>
    <span><b>R</b> restart</span>
    <span><b>M</b> mute</span>
  </div>
  <div class="legend">
    <span>⬜ Steel brick = 2 hits &amp; cracks</span>
    <span>🟦 <b>W</b> = Wide paddle 10s</span>
    <span>🟪 <b>×3 ✦</b> = Multi-ball</span>
  </div>
</div>

<script>
(() => {
"use strict";
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;

const scoreEl=document.getElementById('scoreVal'), bestEl=document.getElementById('bestVal'),
      livesEl=document.getElementById('livesVal'), bricksEl=document.getElementById('bricksVal'),
      ballsEl=document.getElementById('ballsVal'), widePill=document.getElementById('widePill'),
      wideVal=document.getElementById('wideVal'), wideBar=document.getElementById('wideBar'),
      demoBanner=document.getElementById('demoBanner'), overlay=document.getElementById('overlay'),
      ovTitle=document.getElementById('ovTitle'), ovText=document.getElementById('ovText'),
      ovScore=document.getElementById('ovScore');

// ---------- constants ----------
const COLS=10, ROWS=7, GAP=7, TOP=64, LEFT=30;
const BRICK_W=(W-LEFT*2-(COLS-1)*GAP)/COLS, BRICK_H=22;
const BASE_PADDLE_W=132, WIDE_PADDLE_W=208, PADDLE_H=14;
const ROW_COLORS=['#ff5a6e','#ff8c42','#ffd166','#4ade80','#6ef3ff','#7c8cff','#ff6ec7'];
const POWER_MAP={'1,2':'wide','1,7':'multi','3,4':'wide','3,8':'multi','5,1':'multi','5,6':'wide'};

// stars
const stars=[]; for(let i=0;i<90;i++) stars.push({x:Math.random()*W,y:Math.random()*H,r:Math.random()*1.6+0.4,a:Math.random()*0.5+0.2});

// ---------- state ----------
let mode='demo'; // demo | manual
let gameState='playing'; // playing | waiting | paused | over | win
let score=0, best=0, lives=3;
let bricks=[], balls=[], particles=[], powerups=[], floaters=[];
let wideTimer=0, announce=null, autoLaunchTimer=0, elapsed=0, muted=false;
let mouseX=W/2, mouseActive=false;
const keys={};

const paddle={x:W/2-BASE_PADDLE_W/2, y:H-42, w:BASE_PADDLE_W, h:PADDLE_H, speed:560, targetX:null};

let audioCtx=null;
function initAudio(){ if(!audioCtx){ try{ audioCtx=new (window.AudioContext||window.webkitAudioContext)(); }catch(e){} } if(audioCtx&&audioCtx.state==='suspended') audioCtx.resume(); }
function beep(f,d=0.07,type='square',vol=0.12){
  if(muted||!audioCtx) return;
  try{
    const o=audioCtx.createOscillator(), g=audioCtx.createGain();
    o.type=type; o.frequency.value=f;
    g.gain.setValueAtTime(vol,audioCtx.currentTime);
    g.gain.exponentialRampToValueAtTime(0.001,audioCtx.currentTime+d);
    o.connect(g); g.connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime+d);
  }catch(e){}
}

// ---------- helpers ----------
function clamp(v,a,b){return Math.max(a,Math.min(b,v));}
function rrect(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 isTough(r,c){
  if(r===2 && c>=2 && c<=7) return true;
  if(r===4 && (c===1||c===4||c===5||c===8)) return true;
  if(r===1 && (c===0||c===9)) return true;
  if(r===5 && (c===4||c===5)) return true;
  if(r===6 && (c===2||c===7)) return true;
  return false;
}
function buildBricks(){
  bricks=[];
  for(let r=0;r<ROWS;r++) for(let c=0;c<COLS;c++){
    const tough=isTough(r,c);
    bricks.push({
      x:LEFT+c*(BRICK_W+GAP), y:TOP+r*(BRICK_H+GAP), w:BRICK_W, h:BRICK_H,
      color:tough?'#8ea2c9':ROW_COLORS[r], row:r, col:c,
      hp:tough?2:1, maxHp:tough?2:1, tough, alive:true,
      power:POWER_MAP[r+','+c]||null
    });
  }
}
function makeBall(x,y,angleDeg,speed){
  const a=angleDeg*Math.PI/180;
  return {x,y,r:8,vx:Math.sin(a)*speed,vy:-Math.cos(a)*speed,speed,trail:[],stuck:false};
}
function spawnLaunchBall(){
  balls=[makeBall(paddle.x+paddle.w/2, paddle.y-12, (Math.random()*24-12), 380)];
  balls[0].stuck=true; gameState='waiting';
}
function launchBalls(){
  balls.forEach(b=>{ if(b.stuck){ b.stuck=false; const a=(Math.random()*24-12)*Math.PI/180; b.vy=-Math.cos(a)*b.speed; b.vx=Math.sin(a)*b.speed; }});
  if(balls.length) gameState='playing';
}
function resetGame(autoLaunch=true){
  score=0; lives=3; wideTimer=0; particles=[]; powerups=[]; floaters=[]; announce=null; autoLaunchTimer=0;
  paddle.w=BASE_PADDLE_W; paddle.x=W/2-paddle.w/2;
  buildBricks();
  balls=[makeBall(paddle.x+paddle.w/2, paddle.y-12, -8, 380)];
  balls[0].stuck=false; gameState='playing';
  overlay.classList.remove('show');
  updateHUD();
}
function bricksLeft(){ return bricks.filter(b=>b.alive).length; }
function updateHUD(){
  scoreEl.textContent=score;
  bestEl.textContent=best;
  livesEl.textContent='♥'.repeat(Math.max(0,lives))||'—';
  bricksEl.textContent=bricksLeft();
  ballsEl.textContent=balls.length;
  if(wideTimer>0){ widePill.classList.add('on'); wideVal.textContent=Math.ceil(wideTimer)+'s'; wideBar.style.width=(wideTimer/10*100)+'%'; }
  else widePill.classList.remove('on');
}
function showAnnounce(text,color){ announce={text,color,t:2.2}; }
function addFloater(x,y,text,color){ floaters.push({x,y,text,color,t:1}); }

// ---------- particles ----------
function burst(x,y,color,n=16){
  for(let i=0;i<n;i++){
    const a=Math.random()*Math.PI*2, s=60+Math.random()*260;
    particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s-60,life:0.5+Math.random()*0.5,t:0,color,size:2+Math.random()*4});
  }
}

// ---------- brick damage ----------
function damageBrick(b, ball){
  b.hp--;
  if(b.hp>0){
    // cracked tough brick
    burst(ball.x,ball.y,'#cbd5ff',8);
    beep(180,0.08,'square',0.12);
    addFloater(b.x+b.w/2,b.y+b.h/2,'CRACK!','#cbd5ff');
    score+=25;
  } else {
    b.alive=false;
    const pts=b.tough?100:50;
    score+=pts; best=Math.max(best,score);
    burst(b.x+b.w/2,b.y+b.h/2,b.color,b.tough?22:16);
    addFloater(b.x+b.w/2,b.y+b.h/2,'+'+pts,'#ffffff');
    beep(b.tough?520:660,0.08,'triangle',0.14);
    if(b.power){
      powerups.push({x:b.x+b.w/2,y:b.y+b.h/2,vy:150,w:38,h:20,type:b.power});
    }
    if(bricksLeft()===0){
      gameState='win';
      setTimeout(()=>{ if(gameState==='win') showEnd(true); },500);
    }
  }
  updateHUD();
}

// ---------- powerups ----------
function applyPowerup(p){
  if(p.type==='wide'){
    wideTimer=10; paddle.w=WIDE_PADDLE_W;
    paddle.x=clamp(paddle.x,6,W-6-paddle.w);
    showAnnounce('WIDE PADDLE! 10s','#6ef3ff'); beep(880,0.15,'sine',0.16);
  } else {
    const src=balls[0];
    if(src){
      if(balls.length<3){
        const baseA=Math.atan2(src.vx,-src.vy)*180/Math.PI;
        [-26,26].forEach(off=>{
          if(balls.length>=3) return;
          const a=(baseA+off)*Math.PI/180;
          balls.push({x:src.x,y:src.y,r:8,vx:Math.sin(a)*src.speed,vy:-Math.cos(a)*src.speed,speed:src.speed,trail:[],stuck:false});
        });
      } else { score+=200; best=Math.max(best,score); addFloater(p.x,p.y-14,'+200','#ff6ec7'); }
    }
    showAnnounce('MULTI-BALL! ×3','#ff6ec7'); beep(990,0.15,'sine',0.16);
  }
  updateHUD();
}

// ---------- collisions ----------
function collidePaddle(b, prevY){
  if(b.vy<=0) return;
  if(b.y+b.r>=paddle.y && b.y-b.r<=paddle.y+paddle.h && b.x+b.r>=paddle.x && b.x-b.r<=paddle.x+paddle.w){
    // ensure it was above before (prevents side tunneling weirdness)
    if(prevY+b.r>paddle.y+6) return;
    const center=paddle.x+paddle.w/2;
    const rel=clamp((b.x-center)/(paddle.w/2),-1,1);
    const ang=rel*(Math.PI*0.38); // up to ~68°
    b.speed=Math.min(580,b.speed+4);
    b.vx=Math.sin(ang)*b.speed;
    b.vy=-Math.cos(ang)*b.speed;
    b.y=paddle.y-b.r-0.5;
    beep(440+Math.abs(rel)*220,0.06,'triangle',0.11);
    burst(b.x,paddle.y,'#6ef3ff',5);
  }
}
function collideBricks(b, prevX, prevY){
  for(const br of bricks){
    if(!br.alive) 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){
      // decide axis from previous position
      if(prevY+b.r<=br.y+2){ b.vy=-Math.abs(b.vy); b.y=br.y-b.r-0.5; }
      else if(prevY-b.r>=br.y+br.h-2){ b.vy=Math.abs(b.vy); b.y=br.y+br.h+b.r+0.5; }
      else if(prevX+b.r<=br.x+2){ b.vx=-Math.abs(b.vx); b.x=br.x-b.r-0.5; }
      else if(prevX-b.r>=br.x+br.w-2){ b.vx=Math.abs(b.vx); b.x=br.x+br.w+b.r+0.5; }
      else {
        // fallback: push along smallest penetration
        const overlapL=(b.x+b.r)-br.x, overlapR=(br.x+br.w)-(b.x-b.r);
        const overlapT=(b.y+b.r)-br.y, overlapB=(br.y+br.h)-(b.y-b.r);
        const m=Math.min(overlapL,overlapR,overlapT,overlapB);
        if(m===overlapT){b.vy=-Math.abs(b.vy);b.y=br.y-b.r-0.5;}
        else if(m===overlapB){b.vy=Math.abs(b.vy);b.y=br.y+br.h+b.r+0.5;}
        else if(m===overlapL){b.vx=-Math.abs(b.vx);b.x=br.x-b.r-0.5;}
        else {b.vx=Math.abs(b.vx);b.x=br.x+br.w+b.r+0.5;}
      }
      // renormalise to preserved speed (slight speedup)
      b.speed=Math.min(580,b.speed+2);
      const len=Math.hypot(b.vx,b.vy)||1;
      b.vx=b.vx/len*b.speed; b.vy=b.vy/len*b.speed;
      // avoid near-horizontal loops
      if(Math.abs(b.vy)<b.speed*0.22){ b.vy=(b.vy<0?-1:1)*b.speed*0.22; const l=Math.hypot(b.vx,b.vy); b.vx=b.vx/l*b.speed; b.vy=b.vy/l*b.speed; }
      damageBrick(br,b);
      break; // one brick per substep
    }
  }
}
function stepBall(b, dt){
  const dist=Math.hypot(b.vx,b.vy)*dt;
  const steps=Math.max(1,Math.min(24,Math.ceil(dist/(b.r*0.5))));
  const sdt=dt/steps;
  for(let i=0;i<steps;i++){
    const prevX=b.x, prevY=b.y;
    b.x+=b.vx*sdt; b.y+=b.vy*sdt;
    // walls
    if(b.x-b.r<6){ b.x=6+b.r; b.vx=Math.abs(b.vx); beep(300,0.04,'square',0.06); }
    if(b.x+b.r>W-6){ b.x=W-6-b.r; b.vx=-Math.abs(b.vx); beep(300,0.04,'square',0.06); }
    if(b.y-b.r<6){ b.y=6+b.r; b.vy=Math.abs(b.vy); beep(300,0.04,'square',0.06); }
    collidePaddle(b,prevY);
    collideBricks(b,prevX,prevY);
  }
  b.trail.push({x:b.x,y:b.y}); if(b.trail.length>10) b.trail.shift();
}

// ---------- update ----------
function update(dt){
  elapsed+=dt;
  // paddle control
  if(gameState==='playing'||gameState==='waiting'){
    if(mode==='demo'){
      // AI tracks most threatening ball
      let target=paddle.x+paddle.w/2;
      const falling=balls.filter(b=>b.vy>0);
      if(falling.length){ falling.sort((a,b2)=>b2.y-a.y); target=falling[0].x + falling[0].vx*0.12; }
      else if(balls.length) target=balls[0].x;
      const diff=target-(paddle.x+paddle.w/2);
      const maxMove=paddle.speed*1.15*dt;
      paddle.x+=clamp(diff,-maxMove,maxMove);
    } else {
      let move=0;
      if(keys['ArrowLeft']||keys['a']||keys['A']) move-=1;
      if(keys['ArrowRight']||keys['d']||keys['D']) move+=1;
      if(move!==0){ paddle.x+=move*paddle.speed*dt; mouseActive=false; }
      else if(mouseActive && paddle.targetX!==null){
        // smooth follow mouse
        const diff=paddle.targetX-(paddle.x+paddle.w/2);
        paddle.x+=diff*Math.min(1,dt*22);
      }
    }
    paddle.x=clamp(paddle.x,6,W-6-paddle.w);
  }

  if(wideTimer>0){ wideTimer-=dt; if(wideTimer<=0){ wideTimer=0; paddle.w=BASE_PADDLE_W; paddle.x=clamp(paddle.x,6,W-6-paddle.w); showAnnounce('WIDE ENDS','#8fa0d8'); } }

  if(gameState==='waiting'){
    balls.forEach(b=>{ if(b.stuck){ b.x=paddle.x+paddle.w/2; b.y=paddle.y-b.r-1; }});
    if(mode==='demo'){ autoLaunchTimer-=dt; if(autoLaunchTimer<=0) launchBalls(); }
  }

  if(gameState==='playing'){
    // balls
    for(let i=balls.length-1;i>=0;i--){
      const b=balls[i];
      if(b.stuck) continue;
      stepBall(b,dt);
      if(b.y-b.r>H+10){
        balls.splice(i,1);
        burst(b.x,H-10,'#ff5a6e',8);
        if(balls.length===0){
          lives--; beep(140,0.3,'sawtooth',0.16);
          updateHUD();
          if(lives<=0){ gameState='over'; showEnd(false); }
          else { spawnLaunchBall(); autoLaunchTimer=1.0; showAnnounce('BALL LOST — '+lives+(lives===1?' LIFE':' LIVES')+' LEFT','#ff8c42'); }
        } else {
          showAnnounce('BALL LOST','#ff8c42');
        }
      }
    }
    // powerups
    for(let i=powerups.length-1;i>=0;i--){
      const p=powerups[i]; p.y+=p.vy*dt;
      // catch
      if(p.y+p.h/2>=paddle.y && p.y-p.h/2<=paddle.y+paddle.h && p.x+p.w/2>=paddle.x && p.x-p.w/2<=paddle.x+paddle.w){
        applyPowerup(p); powerups.splice(i,1); continue;
      }
      if(p.y>H+20) powerups.splice(i,1);
    }
  }

  // particles & floaters always animate (even on end screens)
  for(let i=particles.length-1;i>=0;i--){ const p=particles[i]; p.t+=dt; p.x+=p.vx*dt; p.y+=p.vy*dt; p.vy+=420*dt; if(p.t>=p.life) particles.splice(i,1); }
  for(let i=floaters.length-1;i>=0;i--){ const f=floaters[i]; f.t-=dt; f.y-=34*dt; if(f.t<=0) floaters.splice(i,1); }
  if(announce){ announce.t-=dt; if(announce.t<=0) announce=null; }

  updateHUD();
}

// ---------- draw ----------
function draw(){
  // bg
  const g=ctx.createLinearGradient(0,0,0,H);
  g.addColorStop(0,'#0d1333'); g.addColorStop(0.55,'#0a0f2a'); g.addColorStop(1,'#070918');
  ctx.fillStyle=g; ctx.fillRect(0,0,W,H);
  // stars
  stars.forEach(s=>{ ctx.globalAlpha=s.a*(0.7+0.3*Math.sin(elapsed*2+s.r*10)); ctx.fillStyle='#cfe6ff'; ctx.beginPath(); ctx.arc(s.x,s.y,s.r,0,7); ctx.fill(); });
  ctx.globalAlpha=1;
  // subtle grid
  ctx.strokeStyle='rgba(120,140,255,0.06)'; 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(); }
  // walls glow
  ctx.strokeStyle='rgba(110,243,255,0.5)'; ctx.lineWidth=3; ctx.shadowBlur=10; ctx.shadowColor='#6ef3ff';
  ctx.beginPath(); ctx.moveTo(6,6); ctx.lineTo(6,H); ctx.moveTo(6,6); ctx.lineTo(W-6,6); ctx.moveTo(W-6,6); ctx.lineTo(W-6,H); ctx.stroke();
  ctx.shadowBlur=0;
  // bottom danger line
  ctx.strokeStyle='rgba(255,90,110,0.35)'; ctx.setLineDash([8,8]); ctx.beginPath(); ctx.moveTo(6,H-4); ctx.lineTo(W-6,H-4); ctx.stroke(); ctx.setLineDash([]);

  // bricks
  for(const b of bricks){
    if(!b.alive) continue;
    ctx.save();
    if(b.tough){
      const gg=ctx.createLinearGradient(b.x,b.y,b.x,b.y+b.h);
      gg.addColorStop(0,'#c7d4f2'); gg.addColorStop(0.5,'#8ea2c9'); gg.addColorStop(1,'#5f729e');
      ctx.fillStyle=gg; rrect(b.x,b.y,b.w,b.h,5); ctx.fill();
      ctx.strokeStyle=b.hp===1?'#ffd166':'#e6ecff'; ctx.lineWidth=b.hp===1?2:1.5; rrect(b.x+1,b.y+1,b.w-2,b.h-2,4); ctx.stroke();
      // rivets
      ctx.fillStyle='rgba(20,30,60,0.55)';
      [[b.x+6,b.y+5],[b.x+b.w-6,b.y+5],[b.x+6,b.y+b.h-5],[b.x+b.w-6,b.y+b.h-5]].forEach(([rx,ry])=>{ctx.beginPath();ctx.arc(rx,ry,1.8,0,7);ctx.fill();});
      if(b.hp===1){ // cracks
        ctx.strokeStyle='#1a2440'; ctx.lineWidth=1.6; ctx.beginPath();
        ctx.moveTo(b.x+10,b.y+3); ctx.lineTo(b.x+18,b.y+10); ctx.lineTo(b.x+14,b.y+18);
        ctx.moveTo(b.x+b.w-12,b.y+4); ctx.lineTo(b.x+b.w-20,b.y+11); ctx.lineTo(b.x+b.w-16,b.y+19);
        ctx.moveTo(b.x+b.w/2-4,b.y+2); ctx.lineTo(b.x+b.w/2+3,b.y+10); ctx.lineTo(b.x+b.w/2-2,b.y+b.h-3);
        ctx.stroke();
      }
    } else {
      const gg=ctx.createLinearGradient(b.x,b.y,b.x,b.y+b.h);
      gg.addColorStop(0,b.color); gg.addColorStop(1,'rgba(0,0,0,0.35)');
      ctx.fillStyle=b.color; rrect(b.x,b.y,b.w,b.h,5); ctx.globalAlpha=0.25; ctx.fillStyle=gg; rrect(b.x,b.y,b.w,b.h,5); ctx.fill(); ctx.globalAlpha=1;
      ctx.fillStyle=b.color; rrect(b.x,b.y,b.w,b.h,5); ctx.fill();
      ctx.fillStyle='rgba(255,255,255,0.35)'; rrect(b.x+3,b.y+2,b.w-6,6,3); ctx.fill();
      ctx.strokeStyle='rgba(255,255,255,0.22)'; ctx.lineWidth=1; rrect(b.x+0.5,b.y+0.5,b.w-1,b.h-1,5); ctx.stroke();
    }
    // powerup icon badge
    if(b.power){
      const cx=b.x+b.w/2, cy=b.y+b.h/2, r=9;
      ctx.save(); ctx.shadowBlur=8; ctx.shadowColor=b.power==='wide'?'#6ef3ff':'#ff6ec7';
      ctx.fillStyle='#ffffff'; ctx.beginPath(); ctx.arc(cx,cy,r,0,7); ctx.fill();
      ctx.shadowBlur=0; ctx.fillStyle=b.power==='wide'?'#0077ff':'#c026d3';
      ctx.font='900 9px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle';
      ctx.fillText(b.power==='wide'?'W':'×3',cx,cy+0.5);
      ctx.restore();
    }
    ctx.restore();
  }

  // powerups falling
  for(const p of powerups){
    ctx.save();
    ctx.shadowBlur=14; ctx.shadowColor=p.type==='wide'?'#6ef3ff':'#ff6ec7';
    ctx.fillStyle=p.type==='wide'?'#0891ff':'#d946ef';
    rrect(p.x-p.w/2,p.y-p.h/2,p.w,p.h,10); ctx.fill();
    ctx.shadowBlur=0; ctx.strokeStyle='rgba(255,255,255,0.85)'; ctx.lineWidth=1.5; rrect(p.x-p.w/2,p.y-p.h/2,p.w,p.h,10); ctx.stroke();
    ctx.fillStyle='#fff'; ctx.font='900 11px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle';
    ctx.fillText(p.type==='wide'?'↔ W':'✦ ×3',p.x,p.y+0.5);
    ctx.restore();
  }

  // paddle
  ctx.save();
  ctx.shadowBlur=16; ctx.shadowColor='#6ef3ff';
  const pg=ctx.createLinearGradient(paddle.x,0,paddle.x+paddle.w,0);
  pg.addColorStop(0,'#22d3ee'); pg.addColorStop(0.5,'#e0f7ff'); pg.addColorStop(1,'#818cf8');
  ctx.fillStyle=pg; rrect(paddle.x,paddle.y,paddle.w,paddle.h,8); ctx.fill();
  ctx.shadowBlur=0;
  ctx.fillStyle='rgba(255,255,255,0.85)'; rrect(paddle.x+4,paddle.y+2,paddle.w-8,4,2); ctx.fill();
  ctx.fillStyle='rgba(10,20,60,0.35)'; rrect(paddle.x+paddle.w/2-18,paddle.y+6,36,3,2); ctx.fill();
  ctx.restore();

  // balls + trails
  for(const b of balls){
    b.trail.forEach((t,i)=>{
      ctx.globalAlpha=(i/b.trail.length)*0.28; ctx.fillStyle='#9beaff';
      ctx.beginPath(); ctx.arc(t.x,t.y,b.r*(i/b.trail.length)*0.8,0,7); ctx.fill();
    });
    ctx.globalAlpha=1;
    ctx.save(); ctx.shadowBlur=16; ctx.shadowColor='#ffffff';
    const bg=ctx.createRadialGradient(b.x-2,b.y-2,1,b.x,b.y,b.r+2);
    bg.addColorStop(0,'#ffffff'); bg.addColorStop(1,'#a5f3fc');
    ctx.fillStyle=bg; ctx.beginPath(); ctx.arc(b.x,b.y,b.r,0,7); ctx.fill(); ctx.restore();
  }

  // particles
  for(const p of particles){
    ctx.globalAlpha=1-(p.t/p.life); ctx.fillStyle=p.color;
    ctx.beginPath(); ctx.arc(p.x,p.y,p.size,0,7); ctx.fill();
  }
  ctx.globalAlpha=1;

  // floaters
  ctx.textAlign='center'; ctx.textBaseline='middle';
  for(const f of floaters){
    ctx.globalAlpha=Math.min(1,f.t*1.6); ctx.fillStyle=f.color; ctx.font='800 14px system-ui';
    ctx.fillText(f.text,f.x,f.y);
  }
  ctx.globalAlpha=1;

  // announce
  if(announce){
    ctx.globalAlpha=Math.min(1,announce.t);
    ctx.save(); ctx.shadowBlur=16; ctx.shadowColor=announce.color;
    ctx.fillStyle=announce.color; ctx.font='900 30px system-ui';
    ctx.fillText(announce.text,W/2,210);
    ctx.restore(); ctx.globalAlpha=1;
  }

  // waiting / paused hints
  if(gameState==='waiting'){
    ctx.fillStyle='rgba(238,242,255,0.95)'; ctx.font='800 15px system-ui';
    ctx.fillText(mode==='demo'?'Launching…':'SPACE / CLICK TO LAUNCH',W/2,H-90);
  }
  if(gameState==='paused'){
    ctx.fillStyle='rgba(4,6,18,0.55)'; ctx.fillRect(0,0,W,H);
    ctx.fillStyle='#fff'; ctx.font='900 34px system-ui'; ctx.fillText('PAUSED',W/2,H/2-8);
    ctx.font='600 14px system-ui'; ctx.fillStyle='#aeb9e6'; ctx.fillText('Press P / Space to resume',W/2,H/2+22);
  }
  if(gameState==='win'||gameState==='over'){
    // dim handled by overlay; keep scene visible behind
  }
}

// ---------- loop ----------
let last=performance.now();
function loop(now){
  const dt=Math.min(0.033,(now-last)/1000); last=now;
  if(gameState!=='paused') update(dt);
  draw();
  requestAnimationFrame(loop);
}

// ---------- end screens ----------
function showEnd(won){
  if(won){ best=Math.max(best,score); ovTitle.textContent='🏆 VICTORY!'; ovTitle.style.color='#ffd166'; ovText.textContent='You cleared every brick. Brilliant!'; }
  else { ovTitle.textContent='GAME OVER'; ovTitle.style.color='#ff5a6e'; ovText.textContent='Out of lives — the bricks win this time.'; }
  ovScore.textContent='Score: '+score+'   •   Best: '+best;
  overlay.classList.add('show');
  beep(won?880:160,0.35,'sine',0.16);
}

// ---------- input ----------
function takeControl(){
  initAudio();
  if(mode==='demo'){ mode='manual'; demoBanner.style.display='none'; }
}
function canvasPos(e){
  const r=canvas.getBoundingClientRect();
  return (e.clientX-r.left)*(W/r.width);
}
canvas.addEventListener('pointermove',e=>{
  mouseX=canvasPos(e); mouseActive=true; paddle.targetX=mouseX;
});
canvas.addEventListener('pointerdown',e=>{
  takeControl(); mouseX=canvasPos(e); mouseActive=true; paddle.targetX=mouseX;
  if(gameState==='waiting') launchBalls();
});
window.addEventListener('pointerdown',e=>{
  // clicks outside canvas also take control (except buttons handled separately)
  if(e.target.closest('button')) return;
  takeControl();
  if(gameState==='waiting' && !overlay.classList.contains('show')) launchBalls();
});
window.addEventListener('keydown',e=>{
  const k=e.key;
  if(['ArrowLeft','ArrowRight',' ','ArrowDown','ArrowUp'].includes(k)) e.preventDefault();
  keys[k]=true;
  takeControl();
  if(overlay.classList.contains('show')){
    if(k==='Enter'||k===' '||k==='r'||k==='R'){ resetGame(); mode='manual'; demoBanner.style.display='none'; }
    return;
  }
  if(k===' '||k==='Spacebar'){
    if(gameState==='waiting') launchBalls();
    else if(gameState==='playing'||gameState==='paused') togglePause();
  }
  if(k==='p'||k==='P'||k==='Escape') togglePause();
  if(k==='r'||k==='R'){ resetGame(); }
  if(k==='m'||k==='M') toggleMute();
});
window.addEventListener('keyup',e=>{ keys[e.key]=false; });

function togglePause(){
  if(gameState==='playing') gameState='paused';
  else if(gameState==='paused') gameState='playing';
}
function toggleMute(){
  muted=!muted; document.getElementById('soundBtn').textContent=muted?'🔇':'🔊';
}
document.getElementById('pauseBtn').addEventListener('click',()=>{ initAudio(); takeControl(); togglePause(); });
document.getElementById('soundBtn').addEventListener('click',()=>{ initAudio(); toggleMute(); });
document.getElementById('restartBtn').addEventListener('click',()=>{ initAudio(); takeControl(); demoBanner.style.display='none'; resetGame(); });
document.getElementById('playAgainBtn').addEventListener('click',()=>{ initAudio(); mode='manual'; demoBanner.style.display='none'; resetGame(); });

// ---------- start (demo auto-play) ----------
resetGame();
mode='demo';
demoBanner.style.display='block';
requestAnimationFrame(loop);
})();
</script>
</body>
</html>
Reasoning trace (4,286 tokens)
Implementing Breakout in vanilla JS canvas with paddle controls, collision handling, power-ups, particles, lives, and self-playing demo mode.Adding attract-mode AI, power-up capsules, multi-ball logic, and substep collision handling.Extending collision with substep AABB resolution, paddle angle control, and HUD plus demo overlay.Implementing brick layout, toughness, power-ups, ball physics, and game state with substep collision and rendering.Defining per-row brick configs, power-up placement, HUD updates, overlays, input handling, and demo mode transitions.Adding audio beep, roundRect fallback, ball trail, brick patterns, powerup mapping, and overlay reset logic.