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
38.0 s
Cost
$0.010
Output tokens
20,610
Spent thinking
10,512 tokens
Speed
575.9 tok/s
Console errors
0
First token after
2.3 s
First visible text after
26.7 s
Prompt tokens
526
Code size
29.9 KB
Finish reason
stop
Served by
Meta
Model id
meta/muse-ember
Reasoning sent
{"effort":"xhigh"}
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 · 743 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 — Brick Breaker</title>
<style>
  *{box-sizing:border-box}
  html,body{margin:0;padding:0}
  body{
    font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
    background:
      radial-gradient(900px 500px at 15% -10%, rgba(34,211,238,.18), transparent 60%),
      radial-gradient(800px 500px at 90% 0%, rgba(236,72,153,.14), transparent 60%),
      radial-gradient(700px 700px at 50% 110%, rgba(99,102,241,.16), transparent 60%),
      #050816;
    color:#e2e8f0;
    min-height:100vh;
  }
  .page{width:min(1020px,calc(100% - 24px));margin:0 auto;padding:18px 0 22px}
  header{text-align:center;margin-bottom:14px}
  .logo{
    display:inline-block;font-size:clamp(30px,5vw,46px);font-weight:900;letter-spacing:.16em;line-height:1;
    background:linear-gradient(90deg,#22d3ee,#a78bfa,#f472b6,#fbbf24);
    -webkit-background-clip:text;background-clip:text;color:transparent;
    text-shadow:0 0 30px rgba(34,211,238,.15);
  }
  .subtitle{margin:8px 0 0;color:#94a3b8;font-size:13px;letter-spacing:.14em;text-transform:uppercase}
  .subtitle b{color:#67e8f9;font-weight:700}
  .hud{
    display:flex;flex-wrap:wrap;gap:8px;align-items:stretch;justify-content:space-between;
    background:rgba(255,255,255,.045);border:1px solid rgba(148,163,184,.18);
    border-radius:14px;padding:10px 12px;margin-bottom:10px;backdrop-filter:blur(6px);
  }
  .stat{background:rgba(2,6,23,.55);border:1px solid rgba(148,163,184,.16);border-radius:10px;padding:6px 12px;min-width:92px;text-align:center}
  .stat span{display:block;font-size:10px;letter-spacing:.18em;color:#94a3b8;font-weight:700}
  .stat strong{display:block;font-size:20px;line-height:1.15;color:#f8fafc;font-variant-numeric:tabular-nums}
  #lives{color:#fb7185;letter-spacing:.08em}
  .wide-stat{background:rgba(34,211,238,.12);border-color:rgba(34,211,238,.45)}
  .wide-stat strong{color:#67e8f9}
  .hud-actions{display:flex;gap:8px;align-items:center}
  .btn{
    appearance:none;border:1px solid rgba(103,232,249,.45);background:linear-gradient(180deg,rgba(34,211,238,.22),rgba(34,211,238,.08));
    color:#cffafe;border-radius:999px;padding:9px 16px;font-weight:800;font-size:13px;cursor:pointer;letter-spacing:.04em;
  }
  .btn:hover{filter:brightness(1.2)} .btn:active{transform:translateY(1px)}
  .btn.secondary{border-color:rgba(148,163,184,.4);background:rgba(255,255,255,.06);color:#e2e8f0}
  .frame{
    position:relative;border-radius:18px;overflow:hidden;background:#060a1c;
    border:1px solid rgba(34,211,238,.45);
    box-shadow:0 0 0 1px rgba(34,211,238,.12),0 24px 70px rgba(0,0,0,.65),0 0 46px rgba(34,211,238,.16);
  }
  canvas{display:block;width:100%;height:auto;aspect-ratio:3/2;cursor:crosshair;touch-action:none}
  .controls{
    display:flex;flex-wrap:wrap;gap:8px 16px;justify-content:center;align-items:center;
    margin-top:11px;color:#94a3b8;font-size:13px;text-align:center;line-height:1.5;
  }
  .controls strong{color:#e2e8f0}
  .pill{display:inline-flex;align-items:center;gap:6px;background:rgba(255,255,255,.05);border:1px solid rgba(148,163,184,.18);border-radius:999px;padding:4px 10px}
  .dotW{display:inline-grid;place-items:center;width:18px;height:18px;border-radius:50%;background:#fff;color:#0f172a;font-weight:900;font-size:11px}
  .dotM{display:inline-grid;place-items:center;width:22px;height:18px;border-radius:999px;background:#ec4899;color:#fff;font-weight:900;font-size:10px}
  #status{width:100%;text-align:center;color:#67e8f9;font-weight:700;font-size:13px;letter-spacing:.03em}
  kbd{background:#1e293b;border:1px solid #475569;border-bottom-width:2px;border-radius:6px;padding:1px 6px;font-family:inherit;font-size:11px;color:#f1f5f9}
  @media (max-width:560px){
    .page{padding-top:12px}
    .stat{min-width:70px;padding:5px 8px}.stat strong{font-size:16px}.stat span{font-size:9px}
    .hud{padding:8px}.btn{padding:8px 12px;font-size:12px}
    .controls{font-size:12px;gap:6px 8px}
  }
</style>
</head>
<body>
<div class="page">
  <header>
    <div class="logo">NEON BREAKOUT</div>
    <p class="subtitle">Break all <b>60 bricks</b> &nbsp;·&nbsp; Crack the tough ones &nbsp;·&nbsp; Catch the power-ups</p>
  </header>

  <div class="hud" aria-label="Game HUD">
    <div class="stat"><span>SCORE</span><strong id="score">0</strong></div>
    <div class="stat"><span>BEST</span><strong id="best">0</strong></div>
    <div class="stat"><span>LIVES</span><strong id="lives">♥♥♥</strong></div>
    <div class="stat"><span>BRICKS LEFT</span><strong id="bricksLeft">60</strong></div>
    <div class="stat wide-stat" id="wideStat" hidden><span>WIDE PADDLE</span><strong id="wideTime">10.0s</strong></div>
    <div class="hud-actions">
      <button class="btn secondary" id="pauseBtn" type="button">⏸ Pause</button>
      <button class="btn" id="restartBtn" type="button">↻ Restart</button>
    </div>
  </div>

  <div class="frame">
    <canvas id="game" width="1920" height="1280" aria-label="Breakout game canvas"></canvas>
  </div>

  <div class="controls">
    <span class="pill">🖱 <strong>Mouse</strong> move paddle</span>
    <span class="pill"><kbd>←</kbd> <kbd>→</kbd> arrow keys</span>
    <span class="pill"><kbd>Space</kbd> launch &nbsp;·&nbsp; <kbd>P</kbd> pause</span>
    <span class="pill"><span class="dotW">W</span> Wide paddle · 10s</span>
    <span class="pill"><span class="dotM">×3</span> Multi-ball</span>
    <span class="pill">🪨 Cracked brick = needs 2 hits</span>
    <span id="status">Demo mode — AI is playing. Click the canvas or press a key to start.</span>
  </div>
</div>

<script>
(() => {
  'use strict';
  const canvas = document.getElementById('game');
  const ctx = canvas.getContext('2d');
  const W = 960, H = 640, SCALE = 2;
  const WALL = 20, WALL_TOP = 16;

  const scoreEl = document.getElementById('score');
  const bestEl = document.getElementById('best');
  const livesEl = document.getElementById('lives');
  const bricksEl = document.getElementById('bricksLeft');
  const wideStatEl = document.getElementById('wideStat');
  const wideTimeEl = document.getElementById('wideTime');
  const statusEl = document.getElementById('status');
  const pauseBtn = document.getElementById('pauseBtn');
  const restartBtn = document.getElementById('restartBtn');

  const clamp = (v,a,b)=>Math.max(a,Math.min(b,v));
  const rand = (a,b)=>a+Math.random()*(b-a);

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

  // ---------- Audio (only after user interaction) ----------
  let audioCtx = null, userInteracted = false;
  function beep(freq, dur=0.06, type='square', vol=0.035){
    if(!userInteracted) return;
    try{
      if(!audioCtx) audioCtx = new (window.AudioContext||window.webkitAudioContext)();
      if(audioCtx.state==='suspended') audioCtx.resume();
      const o = audioCtx.createOscillator(), g = audioCtx.createGain();
      o.type = type; o.frequency.value = freq;
      g.gain.setValueAtTime(vol, audioCtx.currentTime);
      g.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime+dur);
      o.connect(g); g.connect(audioCtx.destination);
      o.start(); o.stop(audioCtx.currentTime+dur);
    }catch(e){}
  }

  // ---------- State ----------
  let mode = 'playing'; // playing | serve | over | win
  let isDemo = true;
  let paused = false;
  let score = 0, best = 0, lives = 3;
  let bricks = [], balls = [], particles = [], powerups = [], floaters = [];
  let wideTimer = 0, shake = 0, time = 0;
  let centerMsg = '', centerMsgTimer = 0;
  let keys = {}, mouseX = null;
  const paddle = { x: W/2, w: 110, h: 16, y: H-38, baseW: 110, wideW: 178 };

  const stars = Array.from({length:80},()=>({x:rand(0,W),y:rand(0,H),r:rand(0.6,1.8),t:rand(0,Math.PI*2)}));

  function bricksAlive(){ return bricks.filter(b=>b.alive).length; }
  function targetSpeed(){ return Math.min(565, 432 + (60-bricksAlive())*2); }

  function makeBall(x,y){
    return {x,y,vx:0,vy:0,r:8,trail:[]};
  }

  function buildBricks(){
    bricks = [];
    const cols=10, rows=6, marginX=40, top=64, gap=6;
    const bw=(W-marginX*2-gap*(cols-1))/cols, bh=22;
    const palette=['#ff3860','#ff7a18','#ffd23f','#2ee66b','#2db9ff','#9b5cff'];
    const special={'1,7':'wide','2,3':'multi','3,6':'wide','4,2':'multi','5,5':'wide','0,8':'multi'};
    for(let r=0;r<rows;r++){
      for(let c=0;c<cols;c++){
        let hard=false;
        if(r===0 && c%3===0) hard=true;
        if(r===1 && c%2===1) hard=true;
        if(r===2 && (c===0||c===9||c===5)) hard=true;
        if(r===3 && c%4===2) hard=true;
        if(r===4 && (c===1||c===8)) hard=true;
        if(r===5 && c%3===1) hard=true;
        let power = special[r+','+c] || null;
        if(power) hard=false;
        bricks.push({
          x:marginX+c*(bw+gap), y:top+r*(bh+gap), w:bw, h:bh,
          row:r, col:c, color:palette[r],
          hp:hard?2:1, hard, cracked:false, power, alive:true, flash:0
        });
      }
    }
  }

  function setup(demo){
    isDemo = demo; paused = false;
    score = 0; lives = 3; wideTimer = 0; shake = 0;
    particles = []; powerups = []; floaters = [];
    paddle.x = W/2; paddle.w = paddle.baseW;
    buildBricks();
    balls = [makeBall(paddle.x, paddle.y-10)];
    if(demo){
      launchBall(rand(-22,22));
    }else{
      mode = 'serve';
    }
  }

  function startGame(){
    userInteracted = true;
    setup(false);
    beep(660,0.08,'triangle',0.05);
  }

  function launchBall(angleDeg){
    const sp = targetSpeed(), rad = angleDeg*Math.PI/180;
    balls.forEach(b=>{
      b.x = paddle.x; b.y = paddle.y - b.r - 2;
      b.vx = Math.sin(rad)*sp; b.vy = -Math.cos(rad)*sp; b.trail=[];
    });
    mode = 'playing';
    beep(520,0.07,'triangle',0.05);
  }

  function spawnParticles(x,y,color,n,power=1){
    for(let i=0;i<n;i++){
      const a = rand(0,Math.PI*2), sp = rand(60,300)*power;
      particles.push({x,y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp-60,life:rand(0.35,0.8),max:0.8,color,size:rand(2,5),rot:rand(0,Math.PI),vr:rand(-8,8)});
    }
  }
  function addFloater(x,y,text,color='#fff'){
    floaters.push({x,y,text,color,life:1});
  }
  function spawnPowerup(brick){
    powerups.push({x:brick.x+brick.w/2,y:brick.y+brick.h/2,w:40,h:22,type:brick.power,vy:175,t:0});
  }

  function applyPowerup(p){
    if(p.type==='wide'){
      wideTimer = 10;
      addFloater(p.x,p.y-14,'WIDE PADDLE!','#67e8f9');
      beep(880,0.09,'triangle',0.06); setTimeout(()=>beep(1174,0.09,'triangle',0.05),70);
    }else{
      addFloater(p.x,p.y-14,'MULTI-BALL!','#f9a8d4');
      beep(784,0.08,'triangle',0.06); setTimeout(()=>beep(1046,0.08,'triangle',0.05),60);
      const src = balls[0];
      if(src){
        [-26,26].forEach(deg=>{
          if(balls.length>=3) return;
          const rad=deg*Math.PI/180, cos=Math.cos(rad), sin=Math.sin(rad);
          const nb = makeBall(src.x,src.y);
          nb.vx = src.vx*cos - src.vy*sin;
          nb.vy = src.vx*sin + src.vy*cos;
          balls.push(nb);
        });
      }
    }
  }

  function damageBrick(brick){
    brick.hp--; brick.flash = 1;
    if(brick.hp>0){
      brick.cracked = true;
      score += 25;
      spawnParticles(brick.x+brick.w/2,brick.y+brick.h/2,brick.color,7,0.6);
      addFloater(brick.x+brick.w/2,brick.y-4,'+25','#e2e8f0');
      beep(320,0.05,'square',0.04);
    }else{
      brick.alive = false;
      const pts = brick.hard?100:50;
      score += pts;
      spawnParticles(brick.x+brick.w/2,brick.y+brick.h/2,brick.color,brick.hard?24:16,1);
      addFloater(brick.x+brick.w/2,brick.y-4,'+'+pts, brick.hard?'#fbbf24':'#fff');
      shake = Math.max(shake, brick.hard?5:3);
      if(brick.power) spawnPowerup(brick);
      beep(brick.hard?440:540,0.06,'square',0.045);
      if(bricksAlive()===0){
        if(isDemo){
          setup(true);
          centerMsg='LEVEL CLEAR!'; centerMsgTimer=2;
        }else{
          mode='win'; best=Math.max(best,score); beep(880,0.15,'triangle',0.06);
        }
      }
    }
  }

  // ---------- Update ----------
  function update(dt){
    time += dt;
    if(centerMsgTimer>0) centerMsgTimer-=dt;
    shake = Math.max(0, shake - dt*18);
    bricks.forEach(b=>{ if(b.flash>0) b.flash=Math.max(0,b.flash-dt*4); });

    // Particles & floaters always animate (even on serve/over screens)
    particles.forEach(p=>{ p.life-=dt; p.x+=p.vx*dt; p.y+=p.vy*dt; p.vy+=620*dt; p.rot+=p.vr*dt; });
    particles = particles.filter(p=>p.life>0);
    floaters.forEach(f=>{ f.life-=dt*0.9; f.y-=34*dt; });
    floaters = floaters.filter(f=>f.life>0);

    if(paused) return;

    // Wide timer
    if(wideTimer>0) wideTimer = Math.max(0, wideTimer-dt);
    const targetW = wideTimer>0 ? paddle.wideW : paddle.baseW;
    paddle.w += (targetW-paddle.w)*Math.min(1,dt*10);

    // Paddle movement
    if(isDemo && mode==='playing' && balls.length){
      let targetBall = balls[0], bestY=-1;
      balls.forEach(b=>{ if(b.y>bestY){bestY=b.y; targetBall=b;} });
      let tx = targetBall.x;
      if(targetBall.vy>40){
        const t = (paddle.y-targetBall.y)/targetBall.vy;
        tx = targetBall.x + targetBall.vx*clamp(t,0,1.2);
      }
      tx = clamp(tx, WALL+paddle.w/2, W-WALL-paddle.w/2);
      const dx = tx-paddle.x, maxStep = 640*dt;
      paddle.x += clamp(dx,-maxStep,maxStep);
    }else{
      if(keys['ArrowLeft']||keys['KeyA']) paddle.x -= 585*dt;
      if(keys['ArrowRight']||keys['KeyD']) paddle.x += 585*dt;
      paddle.x = clamp(paddle.x, WALL+paddle.w/2, W-WALL-paddle.w/2);
    }

    if(mode==='serve'){
      balls.forEach(b=>{ b.x=paddle.x; b.y=paddle.y-b.r-2; b.trail=[]; });
      return;
    }
    if(mode!=='playing') return;

    // Power-ups
    powerups.forEach(p=>{
      p.t+=dt; p.y+=p.vy*dt;
      const px1=paddle.x-paddle.w/2, px2=paddle.x+paddle.w/2;
      if(p.y+p.h/2>=paddle.y && p.y-p.h/2<=paddle.y+paddle.h && p.x+p.w/2>=px1 && p.x-p.w/2<=px2){
        p.caught=true; applyPowerup(p);
      }
    });
    powerups = powerups.filter(p=>!p.caught && p.y<H+30);

    // Balls with sub-stepping so they can never tunnel through bricks/paddle
    const lost = [];
    balls.forEach(ball=>{
      const speed = Math.hypot(ball.vx,ball.vy) || targetSpeed();
      const dist = speed*dt;
      const steps = Math.max(1, Math.ceil(dist/4));
      for(let s=0;s<steps;s++){
        const prevX=ball.x, prevY=ball.y;
        ball.x += ball.vx*dt/steps;
        ball.y += ball.vy*dt/steps;

        // Walls
        if(ball.x < WALL+ball.r){ ball.x=WALL+ball.r; ball.vx=Math.abs(ball.vx); }
        if(ball.x > W-WALL-ball.r){ ball.x=W-WALL-ball.r; ball.vx=-Math.abs(ball.vx); }
        if(ball.y < WALL_TOP+ball.r){ ball.y=WALL_TOP+ball.r; ball.vy=Math.abs(ball.vy); }

        // Paddle — angle depends on hit position
        if(ball.vy>0){
          const px1=paddle.x-paddle.w/2, px2=paddle.x+paddle.w/2;
          if(ball.x+ball.r>px1 && ball.x-ball.r<px2 && ball.y+ball.r>paddle.y && ball.y-ball.r<paddle.y+paddle.h){
            const rel = clamp((ball.x-paddle.x)/(paddle.w/2),-1,1);
            const ang = rel * (65*Math.PI/180);
            const sp = targetSpeed();
            ball.vx = Math.sin(ang)*sp;
            ball.vy = -Math.cos(ang)*sp;
            ball.y = paddle.y - ball.r;
            spawnParticles(ball.x,paddle.y,'#67e8f9',4,0.35);
            beep(230,0.05,'triangle',0.045);
          }
        }

        // Bricks
        for(const brick of bricks){
          if(!brick.alive) continue;
          const cx = clamp(ball.x,brick.x,brick.x+brick.w);
          const cy = clamp(ball.y,brick.y,brick.y+brick.h);
          const dx = ball.x-cx, dy = ball.y-cy;
          if(dx*dx+dy*dy < ball.r*ball.r){
            let horiz=false, vert=false;
            if(prevX+ball.r<=brick.x || prevX-ball.r>=brick.x+brick.w) horiz=true;
            else if(prevY+ball.r<=brick.y || prevY-ball.r>=brick.y+brick.h) vert=true;
            else{
              const oL=(ball.x+ball.r)-brick.x, oR=(brick.x+brick.w)-(ball.x-ball.r);
              const oT=(ball.y+ball.r)-brick.y, oB=(brick.y+brick.h)-(ball.y-ball.r);
              const m=Math.min(oL,oR,oT,oB);
              if(m===oL||m===oR) horiz=true; else vert=true;
            }
            if(horiz){
              ball.vx=-ball.vx;
              ball.x = prevX<brick.x ? brick.x-ball.r-0.1 : brick.x+brick.w+ball.r+0.1;
            }
            if(vert){
              ball.vy=-ball.vy;
              ball.y = prevY<brick.y ? brick.y-ball.r-0.1 : brick.y+brick.h+ball.r+0.1;
            }
            if(!horiz && !vert) ball.vy=-ball.vy;
            damageBrick(brick);
            break;
          }
        }
        if(ball.y-ball.r>H+10){ lost.push(ball); break; }
      }
      ball.trail.unshift({x:ball.x,y:ball.y});
      if(ball.trail.length>13) ball.trail.pop();
    });
    if(lost.length){
      balls = balls.filter(b=>!lost.includes(b));
      if(balls.length===0){
        if(isDemo){
          balls=[makeBall(paddle.x,paddle.y-10)];
          launchBall(rand(-22,22));
        }else{
          lives--; beep(150,0.25,'sawtooth',0.05);
          wideTimer=0;
          if(lives<=0){
            mode='over'; best=Math.max(best,score);
          }else{
            balls=[makeBall(paddle.x,paddle.y-10)];
            mode='serve';
          }
        }
      }
    }
  }

  // ---------- Drawing ----------
  function drawBackground(){
    const g = ctx.createLinearGradient(0,0,0,H);
    g.addColorStop(0,'#0b1026'); g.addColorStop(0.55,'#070b1d'); g.addColorStop(1,'#05060f');
    ctx.fillStyle=g; ctx.fillRect(0,0,W,H);
    stars.forEach(s=>{
      const a=0.25+0.35*Math.sin(time*1.4+s.t);
      ctx.fillStyle=`rgba(148,197,255,${Math.max(0.08,a)})`;
      ctx.beginPath(); ctx.arc(s.x,s.y,s.r,0,Math.PI*2); ctx.fill();
    });
    ctx.strokeStyle='rgba(148,163,184,0.05)'; ctx.lineWidth=1;
    for(let x=60;x<W;x+=60){ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x,H);ctx.stroke();}
    for(let y=60;y<H;y+=60){ctx.beginPath();ctx.moveTo(0,y);ctx.lineTo(W,y);ctx.stroke();}
    // walls
    ctx.strokeStyle='rgba(34,211,238,0.55)'; ctx.lineWidth=3;
    ctx.shadowColor='#22d3ee'; ctx.shadowBlur=10;
    ctx.beginPath(); ctx.moveTo(WALL,WALL_TOP); ctx.lineTo(WALL,H); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(W-WALL,WALL_TOP); ctx.lineTo(W-WALL,H); ctx.stroke();
    ctx.beginPath(); ctx.moveTo(WALL,WALL_TOP); ctx.lineTo(W-WALL,WALL_TOP); ctx.stroke();
    ctx.shadowBlur=0;
  }

  function drawTopInfo(){
    ctx.textBaseline='middle';
    ctx.font='800 13px system-ui,sans-serif'; ctx.fillStyle='#94a3b8'; ctx.textAlign='left';
    ctx.fillText('SCORE', WALL+8, 34);
    ctx.font='900 19px system-ui,sans-serif'; ctx.fillStyle='#f8fafc';
    ctx.fillText(String(score), WALL+8, 54);
    if(isDemo){
      ctx.font='800 11px system-ui,sans-serif';
      const label='● DEMO — AI PLAYING';
      const tw=ctx.measureText(label).width+22;
      ctx.fillStyle='rgba(251,191,36,0.15)'; rr(W/2-tw/2,26,tw,22,11); ctx.fill();
      ctx.strokeStyle='rgba(251,191,36,0.55)'; ctx.lineWidth=1; rr(W/2-tw/2,26,tw,22,11); ctx.stroke();
      ctx.fillStyle='#fbbf24'; ctx.textAlign='center'; ctx.fillText(label,W/2,37);
    }
    ctx.textAlign='right'; ctx.font='900 19px system-ui,sans-serif'; ctx.fillStyle='#fb7185';
    ctx.fillText('♥'.repeat(Math.max(0,lives))+'♡'.repeat(Math.max(0,3-lives)), W-WALL-8, 46);
    ctx.font='700 11px system-ui,sans-serif'; ctx.fillStyle='#94a3b8';
    ctx.fillText('LIVES', W-WALL-8, 28);
    ctx.textAlign='left';
  }

  function drawBricks(){
    bricks.forEach(b=>{
      if(!b.alive) return;
      const pulse = b.power ? (Math.sin(time*4.5+b.x*0.05)*0.5+0.5) : 0;
      ctx.save();
      if(b.power){ ctx.shadowColor='#fbbf24'; ctx.shadowBlur=10+pulse*14; }
      if(b.flash>0){ ctx.shadowColor='#fff'; ctx.shadowBlur=14*b.flash; }
      rr(b.x,b.y,b.w,b.h,5); ctx.fillStyle=b.color; ctx.fill();
      ctx.shadowBlur=0;
      if(b.hard){
        rr(b.x,b.y,b.w,b.h,5); ctx.fillStyle='rgba(15,23,42,0.38)'; ctx.fill();
        rr(b.x,b.y,b.w,b.h,5); ctx.strokeStyle='#e2e8f0'; ctx.lineWidth=2; ctx.stroke();
        ctx.fillStyle='#e2e8f0';
        [[7,7],[b.w-7,7],[7,b.h-7],[b.w-7,b.h-7]].forEach(([ox,oy])=>{
          ctx.beginPath(); ctx.arc(b.x+ox,b.y+oy,2.2,0,Math.PI*2); ctx.fill();
        });
      }else{
        rr(b.x,b.y,b.w,b.h,5); ctx.strokeStyle='rgba(255,255,255,0.22)'; ctx.lineWidth=1; ctx.stroke();
      }
      rr(b.x+3,b.y+3,b.w-6,b.h*0.42,4); ctx.fillStyle='rgba(255,255,255,0.22)'; ctx.fill();
      if(b.cracked){
        ctx.strokeStyle='rgba(0,0,0,0.75)'; ctx.lineWidth=1.6; ctx.beginPath();
        const cx=b.x+b.w/2, cy=b.y+b.h/2;
        ctx.moveTo(cx-14,cy-6); ctx.lineTo(cx-4,cy+1); ctx.lineTo(cx+3,cy-4); ctx.lineTo(cx+13,cy+5);
        ctx.moveTo(cx-2,cy+1); ctx.lineTo(cx+1,cy+8);
        ctx.moveTo(cx-16,cy+6); ctx.lineTo(cx-8,cy+2); ctx.lineTo(cx-2,cy+9);
        ctx.stroke();
      }
      if(b.power){
        const cx=b.x+b.w/2, cy=b.y+b.h/2;
        ctx.beginPath(); ctx.arc(cx,cy,10+pulse*1.2,0,Math.PI*2);
        ctx.fillStyle='#fff'; ctx.fill();
        ctx.strokeStyle=b.power==='wide'?'#0891b2':'#db2777'; ctx.lineWidth=2; ctx.stroke();
        ctx.fillStyle=b.power==='wide'?'#0e7490':'#be185d';
        ctx.font='900 9px system-ui,sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
        ctx.fillText(b.power==='wide'?'W':'×3',cx,cy+0.5);
      }
      ctx.restore();
    });
  }

  function drawPowerups(){
    powerups.forEach(p=>{
      const bob=Math.sin(p.t*6)*2;
      ctx.save();
      ctx.shadowColor=p.type==='wide'?'#22d3ee':'#ec4899'; ctx.shadowBlur=14;
      rr(p.x-p.w/2,p.y-p.h/2+bob,p.w,p.h,11);
      ctx.fillStyle=p.type==='wide'?'#0891b2':'#db2777'; ctx.fill();
      ctx.strokeStyle='#fff'; ctx.lineWidth=2; ctx.stroke();
      ctx.shadowBlur=0;
      ctx.fillStyle='#fff'; ctx.font='900 11px system-ui,sans-serif';
      ctx.textAlign='center'; ctx.textBaseline='middle';
      ctx.fillText(p.type==='wide'?'W':'×3',p.x,p.y+bob+0.5);
      ctx.restore();
    });
  }

  function drawPaddle(){
    const x1=paddle.x-paddle.w/2;
    ctx.save();
    ctx.shadowColor=wideTimer>0?'#fbbf24':'#22d3ee'; ctx.shadowBlur=16;
    const g=ctx.createLinearGradient(x1,paddle.y,x1,paddle.y+paddle.h);
    g.addColorStop(0,'#a5f3fc'); g.addColorStop(0.45,'#22d3ee'); g.addColorStop(1,'#0369a1');
    if(wideTimer>0){ g.addColorStop(0,'#fde68a'); g.addColorStop(0.45,'#f59e0b'); g.addColorStop(1,'#b45309'); }
    rr(x1,paddle.y,paddle.w,paddle.h,8); ctx.fillStyle=g; ctx.fill();
    ctx.shadowBlur=0;
    ctx.strokeStyle='rgba(255,255,255,0.55)'; ctx.lineWidth=1.5; rr(x1,paddle.y,paddle.w,paddle.h,8); ctx.stroke();
    rr(x1+4,paddle.y+3,paddle.w-8,5,3); ctx.fillStyle='rgba(255,255,255,0.45)'; ctx.fill();
    ctx.fillStyle='rgba(2,6,23,0.25)';
    [-0.25,0,0.25].forEach(f=>{ rr(paddle.x+paddle.w*f-2,paddle.y+9,4,4,2); ctx.fill(); });
    ctx.restore();
    if(wideTimer>0){
      ctx.font='800 11px system-ui,sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
      ctx.fillStyle='#fde68a'; ctx.fillText(`WIDE ${wideTimer.toFixed(1)}s`,paddle.x,paddle.y-12);
    }
  }

  function drawBalls(){
    balls.forEach(b=>{
      b.trail.forEach((t,i)=>{
        const a=(1-i/b.trail.length)*0.22;
        ctx.fillStyle=`rgba(103,232,249,${a})`;
        ctx.beginPath(); ctx.arc(t.x,t.y,b.r*(0.4+0.5*(1-i/b.trail.length)),0,Math.PI*2); ctx.fill();
      });
      ctx.save(); ctx.shadowColor='#67e8f9'; ctx.shadowBlur=16;
      ctx.beginPath(); ctx.arc(b.x,b.y,b.r,0,Math.PI*2); ctx.fillStyle='#fff'; ctx.fill();
      ctx.shadowBlur=0;
      ctx.beginPath(); ctx.arc(b.x-2,b.y-2,3,0,Math.PI*2); ctx.fillStyle='#cffafe'; ctx.fill();
      ctx.restore();
    });
  }

  function drawParticles(){
    particles.forEach(p=>{
      ctx.save(); ctx.globalAlpha=clamp(p.life/p.max,0,1);
      ctx.translate(p.x,p.y); ctx.rotate(p.rot); ctx.fillStyle=p.color;
      ctx.fillRect(-p.size/2,-p.size/2,p.size,p.size); ctx.restore();
    });
  }
  function drawFloaters(){
    ctx.textAlign='center'; ctx.textBaseline='middle';
    floaters.forEach(f=>{
      ctx.globalAlpha=clamp(f.life,0,1);
      ctx.font='900 16px system-ui,sans-serif'; ctx.fillStyle=f.color;
      ctx.fillText(f.text,f.x,f.y);
    });
    ctx.globalAlpha=1;
  }

  function pillBanner(y,text,accent='#22d3ee'){
    ctx.font='800 15px system-ui,sans-serif';
    const tw=ctx.measureText(text).width+34;
    ctx.save();
    ctx.fillStyle='rgba(2,6,23,0.82)'; rr(W/2-tw/2,y,tw,38,19); ctx.fill();
    ctx.strokeStyle=accent; ctx.lineWidth=1.5; ctx.shadowColor=accent; ctx.shadowBlur=12;
    rr(W/2-tw/2,y,tw,38,19); ctx.stroke(); ctx.shadowBlur=0;
    ctx.fillStyle='#f8fafc'; ctx.textAlign='center'; ctx.textBaseline='middle';
    ctx.fillText(text,W/2,y+19);
    ctx.restore();
  }

  function overlayPanel(title,titleColor,lines,accent){
    ctx.fillStyle='rgba(2,4,12,0.68)'; ctx.fillRect(0,0,W,H);
    const pw=440, ph=250, px=W/2-pw/2, py=H/2-ph/2;
    ctx.save(); ctx.shadowColor=accent; ctx.shadowBlur=26;
    rr(px,py,pw,ph,18); ctx.fillStyle='rgba(8,12,32,0.95)'; ctx.fill(); ctx.shadowBlur=0;
    ctx.strokeStyle=accent; ctx.lineWidth=2; rr(px,py,pw,ph,18); ctx.stroke();
    ctx.textAlign='center'; ctx.textBaseline='middle';
    ctx.font='900 42px system-ui,sans-serif'; ctx.fillStyle=titleColor;
    ctx.fillText(title,W/2,py+58);
    ctx.font='700 16px system-ui,sans-serif'; ctx.fillStyle='#cbd5e1';
    lines.forEach((ln,i)=>ctx.fillText(ln,W/2,py+108+i*28));
    ctx.restore();
  }

  function draw(){
    ctx.setTransform(SCALE,0,0,SCALE,0,0);
    ctx.save();
    if(shake>0) ctx.translate(rand(-shake,shake),rand(-shake,shake));
    drawBackground();
    drawBricks();
    drawPowerups();
    drawPaddle();
    drawBalls();
    drawParticles();
    drawFloaters();
    drawTopInfo();

    if(centerMsgTimer>0){
      ctx.font='900 34px system-ui,sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
      ctx.save(); ctx.shadowColor='#fbbf24'; ctx.shadowBlur=20;
      ctx.fillStyle='#fde68a'; ctx.fillText(centerMsg,W/2,220); ctx.restore();
    }

    if(isDemo && mode==='playing'){
      const pulse=Math.sin(time*3)*0.5+0.5;
      ctx.globalAlpha=0.85+0.15*pulse;
      pillBanner(H-112,'▶  Click or press a key to start','#22d3ee');
      ctx.globalAlpha=1;
    }
    if(!isDemo && mode==='serve'){
      pillBanner(H-112,'Click or press SPACE to launch','#fbbf24');
    }
    if(paused && mode==='playing'){
      overlayPanel('PAUSED','#e2e8f0',['Press P or click to resume'],'#94a3b8');
    }
    if(mode==='over' && !isDemo){
      overlayPanel('GAME OVER','#fb7185',[
        `Score: ${score}`,
        `Best: ${best}`,
        `Bricks remaining: ${bricksAlive()}`,
        '',
        'Click or press Enter to restart'
      ],'#fb7185');
    }
    if(mode==='win' && !isDemo){
      overlayPanel('★ VICTORY! ★','#fbbf24',[
        'You cleared every brick!',
        `Final score: ${score}`,
        `Best: ${best}`,
        '',
        'Click or press Enter to play again'
      ],'#fbbf24');
    }
    ctx.restore();
  }

  // ---------- HUD sync ----------
  const last = {};
  function syncHud(statusText){
    if(last.score!==score){scoreEl.textContent=score; last.score=score;}
    if(last.best!==best){bestEl.textContent=best; last.best=best;}
    const lv='♥'.repeat(Math.max(0,lives))+'♡'.repeat(Math.max(0,3-lives));
    if(last.lives!==lv){livesEl.textContent=lv; last.lives=lv;}
    const ba=bricksAlive();
    if(last.ba!==ba){bricksEl.textContent=ba; last.ba=ba;}
    const wideOn=wideTimer>0;
    if(last.wideOn!==wideOn){wideStatEl.hidden=!wideOn; last.wideOn=wideOn;}
    if(wideOn){ const t=wideTimer.toFixed(1)+'s'; if(last.wt!==t){wideTimeEl.textContent=t; last.wt=t;} }
    if(last.st!==statusText){statusEl.textContent=statusText; last.st=statusText;}
  }

  function currentStatus(){
    if(paused) return 'Paused — press P or click to resume.';
    if(isDemo) return 'Demo mode — AI is playing. Click the canvas or press a key to start.';
    if(mode==='serve') return 'Ready — click the canvas or press Space to launch the ball.';
    if(mode==='over') return 'Game over — click the canvas or press Enter to restart.';
    if(mode==='win') return 'Victory! Click the canvas or press Enter to play again.';
    return 'Playing — catch W for a wide paddle and ×3 for multi-ball!';
  }

  // ---------- Input ----------
  function press(){
    userInteracted=true;
    if(isDemo){ startGame(); return; }
    if(mode==='over'||mode==='win'){ startGame(); return; }
    if(paused){ paused=false; return; }
    if(mode==='serve') launchBall(rand(-20,20));
  }
  canvas.addEventListener('pointerdown', e=>{ e.preventDefault(); press(); });
  canvas.addEventListener('pointermove', e=>{
    const rect=canvas.getBoundingClientRect();
    mouseX=(e.clientX-rect.left)*W/rect.width;
    if(!isDemo && (mode==='playing'||mode==='serve') && !paused){
      paddle.x=clamp(mouseX,WALL+paddle.w/2,W-WALL-paddle.w/2);
    }
  });
  canvas.addEventListener('touchmove', e=>{
    const t=e.touches[0]; if(!t) return;
    const rect=canvas.getBoundingClientRect();
    mouseX=(t.clientX-rect.left)*W/rect.width;
    if(!isDemo && (mode==='playing'||mode==='serve')) paddle.x=clamp(mouseX,WALL+paddle.w/2,W-WALL-paddle.w/2);
  },{passive:true});

  window.addEventListener('keydown', e=>{
    if(['ArrowLeft','ArrowRight','ArrowUp','ArrowDown','Space'].includes(e.code)) e.preventDefault();
    keys[e.code]=true;
    userInteracted=true;
    if(isDemo){ startGame(); return; }
    if(mode==='over'||mode==='win'){
      if(e.code==='Enter'||e.code==='Space') startGame();
      return;
    }
    if(e.code==='KeyP'){ paused=!paused; return; }
    if(mode==='serve' && (e.code==='Space'||e.code==='ArrowUp'||e.code==='Enter')) launchBall(rand(-20,20));
  });
  window.addEventListener('keyup', e=>{ keys[e.code]=false; });

  pauseBtn.addEventListener('click', ()=>{
    if(isDemo){ startGame(); paused=true; } else paused=!paused;
  });
  restartBtn.addEventListener('click', ()=>{ startGame(); });

  // ---------- Main loop ----------
  setup(true);
  let lastT = performance.now();
  function loop(now){
    const dt = Math.min(0.033,(now-lastT)/1000 || 0.016);
    lastT = now;
    update(dt);
    draw();
    syncHud(currentStatus());
    requestAnimationFrame(loop);
  }
  requestAnimationFrame(loop);
})();
</script>
</body>
</html>
Reasoning trace (10,512 tokens)
Implementing Breakout in vanilla JS canvas with paddle controls, brick types, power-ups, particles, lives, score, and responsive layout.Adding attract/demo AI mode, substepped collision, and brick layout with hard and power-up types.Extending collision handling with substepped brick and paddle response plus power-up and particle effects.Refining collision stepping and brick state logic with rendering and demo mode handling.Adding DPR scaling, HUD layout, input handling, and demo-mode state transitions.Implementing substep collision handling, brick response, paddle bounce, and powerup effects.Adding demo overlay, restart and pause controls, HUD sync, and canvas rendering details.Defining state variables, audio beep helper, and demo versus play mode initialization.Implementing AI paddle prediction, input handling, and mode transitions for serve, play, and win states.Adding CSS styling, HUD updates, brick layout logic, and collision handling with particle effects.