Models 44

Nemotron 3.5 Super VL (free)

by NVIDIAReasoningClean
Open in new window

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

How it went

Total time
2 min 53 s
Cost
$0
Output tokens
20,468
Spent thinking
15,748 tokens
Speed
118.6 tok/s
Console errors
0
First token after
478 ms
First visible text after
2 min 19 s
Prompt tokens
531
Code size
12.8 KB
Finish reason
stop
Served by
Nvidia
Model id
nvidia/nemotron-3.5-super-vl:free
Reasoning sent
model default
Max output
32,000 tokens
Generated
Nov 9, 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 · 420 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Breakout</title>
<style>
  *{box-sizing:border-box;margin:0;padding:0}
  body{background:#0b0b10;color:#fff;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;overflow:hidden;touch-action:manipulation}
  #wrap{aspect-ratio:4/3;width:92vw;max-width:800px;max-height:90vh;position:relative;border:6px solid #1a1a2e;box-shadow:0 0 30px rgba(0,0,0,0.9);border-radius:8px;overflow:hidden}
  canvas{width:100%;height:100%;display:block;background:#000}
  #hud{position:absolute;top:12px;left:12px;right:12px;display:flex;justify-content:space-between;pointer-events:none;font-weight:700;font-size:clamp(1rem,2vw,1.4rem);letter-spacing:1px;text-shadow:0 0 6px #000;z-index:10}
  #score{color:#22d3ee}
  #lives{color:#f87171}
  #ui{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;z-index:20}
  h1{font-size:clamp(1.4rem,5vw,2.6rem);text-transform:uppercase;letter-spacing:6px;margin-bottom:0.4rem;color:#22d3ee;text-shadow:0 0 12px rgba(34,211,238,0.6)}
  .hint{font-size:clamp(0.9rem,3vw,1.2rem);color:#ccc;animation:pulse 1.8s infinite alternate}
  @keyframes pulse{from{opacity:.7}to{opacity:1}}
  #overlay{position:absolute;inset:0;background:rgba(10,10,16,0.88);display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:auto;z-index:30}
  #overlay.hidden{display:none}
  #overlay h1{font-size:clamp(1.6rem,6vw,3rem);margin-bottom:0.5rem}
  #overlaySub{font-size:clamp(1rem,3vw,1.4rem);color:#aaa;margin-bottom:1.2rem}
  button{padding:0.9rem 1.8rem;font-size:1.1rem;background:#22d3ee;color:#000;border:none;border-radius:6px;cursor:pointer;font-weight:700;box-shadow:0 0 15px rgba(34,211,238,0.4)}
  button:hover{background:#67e8f9}
</style>
</head>
<body>
<div id="wrap">
  <canvas id="c" width="800" height="600"></canvas>
  <div id="hud"><div id="score">SCORE 0</div><div id="lives">LIVES 3</div></div>
  <div id="ui">
    <h1>Breakout</h1>
    <div class="hint" id="startHint">Click or press any key to start</div>
  </div>
  <div id="overlay" class="hidden">
    <h1 id="overlayTitle">GAME OVER</h1>
    <div id="overlaySub">Final Score: 0</div>
    <button id="restartBtn">Play Again</button>
  </div>
</div>
<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d', { alpha: false });
const W = 800, H = 600;

let state = 'MENU'; // MENU | PLAYING | GAMEOVER | VICTORY
let score = 0, lives = 3;
let paddle = { x: W/2 - 50, y: H - 34, w: 100, h: 14, speed: 420, dx: 0 };
let balls = [];
let bricks = [];
let particles = [];
let powerups = [];
let paddleWideEnd = 0;
let mouseX = W/2;
let last = 0;

function clampPaddle(){
  if(paddle.x < 0) paddle.x = 0;
  if(paddle.x + paddle.w > W) paddle.x = W - paddle.w;
}

function drawRoundRect(x,y,w,h,r){
  ctx.beginPath();
  ctx.moveTo(x+r, y);
  ctx.lineTo(x+w-r, y);
  ctx.quadraticCurveTo(x+w, y, x+w, y+r);
  ctx.lineTo(x+w, y+h-r);
  ctx.quadraticCurveTo(x+w, y+h, x+w-r, y+h);
  ctx.lineTo(x+r, y+h);
  ctx.quadraticCurveTo(x, y+h, x, y+h-r);
  ctx.lineTo(x, y+r);
  ctx.quadraticCurveTo(x, y, x+r, y);
  ctx.closePath();
}

function initBricks(){
  bricks = [];
  const rows=7, cols=10, bw=66, bh=22, gap=8, ox=55, oy=70;
  for(let r=0;r<rows;r++){
    for(let c=0;c<cols;c++){
      const x = ox + c*(bw+gap);
      const y = oy + r*(bh+gap);
      const tough = Math.random() < 0.28;
      let pu = null;
      if(Math.random() < 0.14) pu = Math.random() < 0.5 ? 'wide' : 'multi';
      bricks.push({
        x,y,w:bw,h:bh,tough,
        hits: tough?2:1,
        pu,
        color: `hsl(${(r/rows)*340 + 180}, 75%, ${tough?40:58}%)`,
        alive:true
      });
    }
  }
}

function spawnBall(){
  const speed = 340;
  const angle = (Math.random()-0.5)*1.1;
  return {
    x: paddle.x + paddle.w/2,
    y: paddle.y - 10,
    r: 8,
    vx: Math.sin(angle)*speed,
    vy: -Math.cos(angle)*speed
  };
}

function updateHUD(){
  document.getElementById('score').textContent = 'SCORE ' + score;
  document.getElementById('lives').textContent = 'LIVES ' + lives;
}

function showOverlay(title, sub){
  document.getElementById('overlayTitle').textContent = title;
  document.getElementById('overlaySub').textContent = sub;
  document.getElementById('overlay').classList.remove('hidden');
}

function startGame(){
  if(state === 'PLAYING') return;
  score = 0; lives = 3;
  paddle.w = 100; paddle.x = W/2 - 50; paddle.dx = 0; paddleWideEnd = 0;
  balls = []; bricks = []; particles = []; powerups = [];
  initBricks();
  balls.push(spawnBall());
  state = 'PLAYING';
  document.getElementById('overlay').classList.add('hidden');
  updateHUD();
}

function loseLife(){
  lives--;
  updateHUD();
  if(lives <= 0){
    state = 'GAMEOVER';
    showOverlay('GAME OVER', 'Final Score: ' + score);
    return;
  }
  balls = [spawnBall()];
  paddle.w = 100;
  paddle.x = W/2 - paddle.w/2;
  paddleWideEnd = 0;
}

function activatePowerup(type){
  if(type === 'wide'){
    paddle.w = 160;
    paddleWideEnd = Date.now() + 10000;
    clampPaddle();
  } else if(type === 'multi'){
    const base = balls[0];
    if(!base) return;
    const sp = Math.hypot(base.vx, base.vy) || 340;
    for(let i=0;i<2;i++){
      const ang = (Math.random()-0.5)*1.2;
      balls.push({x:base.x, y:base.y, r:8, vx: Math.sin(ang)*sp, vy: -Math.cos(ang)*sp});
    }
  }
}

function updateMenu(dt){
  if(balls.length === 0){
    balls.push({x:W/2,y:H/2,r:8,vx:140,vy:-220});
  }
  const b = balls[0];
  b.x += b.vx * dt;
  b.y += b.vy * dt;
  if(b.x - b.r < 0){ b.x = b.r; b.vx *= -1; }
  if(b.x + b.r > W){ b.x = W - b.r; b.vx *= -1; }
  if(b.y - b.r < 0){ b.y = b.r; b.vy *= -1; }
  if(b.y + b.r > H){ b.x = W/2; b.y = H/2; b.vx = (Math.random()-0.5)*220; b.vy = -220; }

  const target = b.x - paddle.w/2;
  const diff = target - paddle.x;
  paddle.x += Math.sign(diff) * Math.min(Math.abs(diff), paddle.speed * dt);
  clampPaddle();

  if(b.vy > 0 && b.y + b.r >= paddle.y && b.y - b.r <= paddle.y + paddle.h && b.x >= paddle.x && b.x <= paddle.x + paddle.w){
    const offset = (b.x - (paddle.x + paddle.w/2)) / (paddle.w/2);
    const ang = offset * (68 * Math.PI / 180);
    const sp = Math.hypot(b.vx, b.vy) || 260;
    b.vx = Math.sin(ang) * sp;
    b.vy = -Math.cos(ang) * sp;
    b.y = paddle.y - b.r;
  }
}

function update(dt){
  if(state === 'MENU'){ updateMenu(dt); return; }
  if(state !== 'PLAYING') return;

  if(paddleWideEnd && Date.now() > paddleWideEnd){
    paddle.w = 100;
    paddleWideEnd = 0;
    clampPaddle();
  }

  const keyMove = paddle.dx !== 0;
  if(!keyMove){
    paddle.x = mouseX - paddle.w/2;
  }
  paddle.x += paddle.dx * dt;
  clampPaddle();

  const steps = 5;
  const step = dt / steps;
  for(let s=0;s<steps;s++){
    if(paddle.dx !== 0){
      paddle.x += paddle.dx * step;
      clampPaddle();
    }
    for(let i=balls.length-1;i>=0;i--){
      const b = balls[i];
      b.x += b.vx * step;
      b.y += b.vy * step;

      if(b.x - b.r < 0){ b.x = b.r; b.vx *= -1; }
      if(b.x + b.r > W){ b.x = W - b.r; b.vx *= -1; }
      if(b.y - b.r < 0){ b.y = b.r; b.vy *= -1; }

      if(b.vy > 0 && b.y + b.r >= paddle.y && b.y - b.r <= paddle.y + paddle.h && b.x >= paddle.x && b.x <= paddle.x + paddle.w){
        const offset = (b.x - (paddle.x + paddle.w/2)) / (paddle.w/2);
        const ang = offset * (68 * Math.PI / 180);
        const sp = Math.hypot(b.vx, b.vy) || 340;
        b.vx = Math.sin(ang) * sp;
        b.vy = -Math.cos(ang) * sp;
        b.y = paddle.y - b.r;
      }

      let hit = false;
      for(let j=0;j<bricks.length && !hit;j++){
        const br = bricks[j];
        if(br.hits <= 0) continue;
        const rx = Math.max(br.x, Math.min(b.x, br.x + br.w));
        const ry = Math.max(br.y, Math.min(b.y, br.y + br.h));
        const dx = b.x - rx, dy = b.y - ry;
        const d2 = dx*dx + dy*dy;
        if(d2 <= b.r*b.r && d2 > 0){
          const d = Math.sqrt(d2);
          const nx = dx/d, ny = dy/d;
          const dot = b.vx*nx + b.vy*ny;
          b.vx = b.vx - 2*dot*nx;
          b.vy = b.vy - 2*dot*ny;
          const overlap = b.r - d;
          b.x += nx*overlap;
          b.y += ny*overlap;
          br.hits--;
          if(br.hits <= 0){
            br.alive = false;
            score += br.tough ? 40 : 10;
            const col = br.color;
            for(let p=0;p<10;p++){
              const pa = Math.random()*Math.PI*2;
              const pv = 40 + Math.random()*160;
              particles.push({
                x: br.x+br.w/2, y: br.y+br.h/2,
                vx: Math.cos(pa)*pv, vy: Math.sin(pa)*pv,
                life: 0.5 + Math.random()*0.4, maxLife: 1,
                color: col, size: 2+Math.random()*3
              });
            }
            if(br.pu){
              powerups.push({x: br.x+br.w/2, y: br.y+br.h/2, type: br.pu, r: 6, vy: 170, active: true});
            }
          }
          hit = true;
          updateHUD();
        }
      }
      if(b.y - b.r > H){
        balls.splice(i,1);
      }
    }
    if(balls.length === 0){
      loseLife();
      break;
    }
  }
  if(state !== 'PLAYING') return;

  for(let i=bricks.length-1;i>=0;i--) if(bricks[i].alive === false) bricks.splice(i,1);

  if(bricks.length === 0){
    state = 'VICTORY';
    showOverlay('VICTORY', 'Score: ' + score);
    return;
  }

  for(let i=particles.length-1;i>=0;i--){
    const p = particles[i];
    p.x += p.vx * dt;
    p.y += p.vy * dt;
    p.vy += 500 * dt;
    p.life -= dt;
    if(p.life <= 0) particles.splice(i,1);
  }

  for(let i=powerups.length-1;i>=0;i--){
    const p = powerups[i];
    if(!p.active) continue;
    p.y += p.vy * dt;
    const rx = Math.max(paddle.x, Math.min(p.x, paddle.x + paddle.w));
    const ry = Math.max(paddle.y, Math.min(p.y, paddle.y + paddle.h));
    const dx = p.x - rx, dy = p.y - ry;
    if(dx*dx + dy*dy <= p.r*p.r){
      activatePowerup(p.type);
      p.active = false;
      powerups.splice(i,1);
      continue;
    }
    if(p.y - p.r > H){
      p.active = false;
      powerups.splice(i,1);
    }
  }
}

function draw(){
  ctx.clearRect(0,0,W,H);

  for(const br of bricks){
    if(br.hits <= 0) continue;
    ctx.fillStyle = br.color;
    ctx.fillRect(br.x, br.y, br.w, br.h);
    ctx.strokeStyle = 'rgba(0,0,0,0.45)';
    ctx.lineWidth = 2;
    ctx.strokeRect(br.x, br.y, br.w, br.h);
    if(br.tough && br.hits === 1){
      ctx.strokeStyle = 'rgba(255,255,255,0.55)';
      ctx.beginPath();
      ctx.moveTo(br.x+8, br.y+8);
      ctx.lineTo(br.x+br.w-8, br.y+br.h-8);
      ctx.moveTo(br.x+br.w-8, br.y+8);
      ctx.lineTo(br.x+8, br.y+br.h-8);
      ctx.stroke();
    }
    if(br.pu){
      ctx.fillStyle = 'rgba(255,255,255,0.9)';
      ctx.font = 'bold 13px sans-serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(br.pu === 'wide' ? 'W' : 'M', br.x + br.w/2, br.y + br.h/2);
    }
  }

  for(const p of powerups){
    if(!p.active) continue;
    ctx.fillStyle = p.type === 'wide' ? '#4ade80' : '#f472b6';
    ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI*2); ctx.fill();
    ctx.fillStyle = '#fff';
    ctx.font = 'bold 11px sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(p.type === 'wide' ? 'W' : 'M', p.x, p.y);
  }

  for(const p of particles){
    ctx.globalAlpha = Math.max(0, p.life / p.maxLife);
    ctx.fillStyle = p.color;
    ctx.fillRect(p.x, p.y, p.size, p.size);
  }
  ctx.globalAlpha = 1;

  ctx.fillStyle = '#22d3ee';
  drawRoundRect(paddle.x, paddle.y, paddle.w, paddle.h, 5);
  ctx.fill();
  ctx.strokeStyle = '#05b8d4';
  ctx.lineWidth = 2;
  ctx.stroke();

  ctx.fillStyle = '#fff';
  ctx.lineWidth = 2;
  ctx.strokeStyle = '#000';
  for(const b of balls){
    ctx.beginPath(); ctx.arc(b.x, b.y, b.r, 0, Math.PI*2); ctx.fill(); ctx.stroke();
  }
}

function loop(now){
  if(!last) last = now;
  const dt = Math.min((now - last) / 1000, 0.05);
  last = now;
  update(dt);
  draw();
  requestAnimationFrame(loop);
}

canvas.addEventListener('mousemove', e=>{
  const rect = canvas.getBoundingClientRect();
  const sx = W / rect.width;
  mouseX = (e.clientX - rect.left) * sx;
  paddle.dx = 0;
});

window.addEventListener('keydown', e=>{
  if(state === 'MENU' || state === 'GAMEOVER' || state === 'VICTORY'){
    startGame();
    return;
  }
  if(e.key === 'ArrowLeft'){ paddle.dx = -paddle.speed; e.preventDefault(); }
  if(e.key === 'ArrowRight'){ paddle.dx = paddle.speed; e.preventDefault(); }
});

window.addEventListener('keyup', e=>{
  if(e.key === 'ArrowLeft' && paddle.dx < 0) paddle.dx = 0;
  if(e.key === 'ArrowRight' && paddle.dx > 0) paddle.dx = 0;
});

canvas.addEventListener('click', ()=>{
  if(state === 'MENU' || state === 'GAMEOVER' || state === 'VICTORY') startGame();
});

document.getElementById('restartBtn').addEventListener('click', startGame);

initBricks();
requestAnimationFrame(loop);
</script>
</body>
</html>
Reasoning trace (15,748 tokens)
The user wants a polished Breakout game in a single HTML file with vanilla JavaScript and canvas. Let me break down the requirements:

1. Paddle controlled by mouse and left/right arrow keys
2. Bounce angle off paddle depends on hit position (center = straight up, edges = sharp angle)
3. One level with at least 6 rows of bricks
4. Mix normal bricks (1 hit) and tougher bricks (2 hits, visibly crack after first hit)
5. Some bricks carry power-up icons; when broken they drop power-ups; catching with paddle gives wider paddle (10s) or multi-ball (3 balls)
6. Ball must never pass through bricks or paddle (continuous collision detection or small time steps)
7. Particles when brick breaks
8. 3 lives, score displayed
9. Game over and victory screens with restart option
10. Self-playing demo mode behind "Click or press a key to start"
11. Must work in 1280x800 and down to 375px wide, no horizontal scrollbar
12. No external assets (images, fonts) - everything inline/CDN allowed but better to use canvas drawing
13. No localStorage etc.

Design decisions:
- Use canvas with fixed logical resolution, CSS to fit container
- Responsive canvas: resize to parent width/height but keep aspect ratio? Or just fill window with max-width. Let's make canvas fill available space with CSS `max-width: 100vw; max-height: 100vh; display: block; margin: auto` and internal resolution fixed at say 800x600 or dynamic based on window. Actually simpler: set canvas width/height to window inner dimensions but clamped? No, aspect ratio matters for gameplay. Let's use a fixed game world of 800x600 and scale CSS to fit container with `object-fit: contain` inside a wrapper. But requirement says no horizontal scrollbar and look right in 1280x800 down to 375px. So wrapper with width 100%, height 100%, canvas inside with width:100%; height:auto? That would distort if height not set. Better: canvas wrapper with aspect ratio 4:3, max-width 100%, max-height 100%, centered. Canvas element width="800" height="600" and CSS width:100%; height:100% inside wrapper that maintains aspect ratio via padding-top trick or just `aspect-ratio: 4/3`. Modern browsers support aspect-ratio. Let's use a container `div#game-wrap` with `aspect-ratio: 4/3; width: 95vw; max-width: 800px; max-height: 600px; margin: auto; border...` and canvas fills it: `width:100%; height:100%; display:block`.

- Game states: MENU (click to start, demo playing), PLAYING, GAMEOVER, VICTORY.
- Demo mode: simple AI moves paddle to follow ball. When in MENU, balls/paddle move automatically.
- Physics: use fixed time step or sub-stepping to prevent tunneling. Since balls are small and speed moderate, we can do continuous-ish collision by moving in small steps or checking swept collision. Simpler: split each frame into sub-steps (e.g., 4 sub-steps) and update positions incrementally, checking collisions each sub-step. This prevents tunneling.

- Paddle bounce: `offset = (ball.x - paddle.centerX) / (paddle.width/2)` ranging -1 to 1. Angle = offset * maxAngle (e.g., 60 degrees? maybe 75). Normalize direction vector. Keep speed constant.

- Bricks: grid of columns x rows. At least 6 rows. Let's do 10 columns x 7 rows = 70 bricks. Types: normal (1 hit, color), tough (2 hits, darker color, crack visual after first hit). Some bricks have power-up icon (small circle/letter inside). When broken, spawn power-up entity falling down. Power-up types: 'W' wider paddle, 'M' multi-ball.

- Particles: array of particles with position, velocity, life, color. Draw as small rectangles/circles.

- Power-ups: fall at constant speed. If paddle intersects, activate. If pass bottom, disappear.
  - Wider paddle: paddle width multiplied by 1.5 for 10 seconds.
  - Multi-ball: spawn 2 additional balls (total 3). Existing balls keep going.

- Lives: 3. Ball drops below paddle -> lose life, reset ball/paddle. If lives=0 -> GAMEOVER.
- Victory: all bricks destroyed.

- Start screen: "CLICK OR PRESS ANY KEY TO START". Behind it, demo AI plays. Once started, normal controls.

- Controls: mouse move updates paddle x. Arrow keys left/right. Touch? Not required but mouse covers.

- No external network. Draw everything with canvas API.

Let's sketch code structure:

HTML:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Breakout</title>
<style>
  * { box-sizing: border-box; margin:0; padding:0; }
  body { background: #111; color: #fff; font-family: system-ui, -apple-system, sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; overflow: hidden; }
  #wrap { aspect-ratio: 4/3; width: 92vw; max-width: 800px; max-height: 600px; position: relative; border: 4px solid #333; box-shadow: 0 0 40px rgba(0,0,0,0.8); overflow: hidden; }
  canvas { width:100%; height:100%; display:block; background:#000; }
  #ui { position: absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; pointer-events:none; }
  h1 { font-size: clamp(1.5rem, 5vw, 2.5rem); margin-bottom: 0.5rem; text-transform: uppercase; letter-spacing: 4px; color:#0ff; text-shadow: 0 0 10px #0ff; }
  .hint { font-size: clamp(1rem, 3vw, 1.25rem); color:#ccc; margin-top:1rem; }
  #overlay { position:absolute; inset:0; background: rgba(0,0,0,0.75); display:flex; flex-direction:column; align-items:center; justify-content:center; pointer-events:auto; }
  #overlay.hidden { display:none; }
  button { padding: 0.8rem 1.6rem; font-size: 1.2rem; background:#0ff; color:#000; border:none; border-radius:4px; cursor:pointer; font-weight:bold; margin-top:1rem; }
  button:hover { background:#0cc; }
  #hud { position:absolute; top:10px; left:10px; right:10px; display:flex; justify-content:space-between; font-size:1.2rem; font-weight:bold; color:#0ff; pointer-events:none; text-shadow:0 0 4px #000; }
</style>
</head>
<body>
<div id="wrap">
  <canvas id="c" width="800" height="600"></canvas>
  <div id="hud"><div id="score">Score: 0</div><div id="lives">Lives: 3</div></div>
  <div id="ui">
    <h1>BREAKOUT</h1>
    <div class="hint" id="startHint">Click or press any key to start</div>
  </div>
  <div id="overlay">
    <h1 id="overlayTitle">GAME OVER</h1>
    <div id="overlaySub"></div>
    <button id="restartBtn">Play Again</button>
  </div>
</div>
<script>
  // code
</script>
</body>
</html>
```

JS details:

Constants:
- W=800, H=600
- PADDLE_W=100, PADDLE_H=14, PADDLE_Y=H-30
- BALL_R=7
- BRICK_ROWS=7, BRICK_COLS=10, BRICK_W=70, BRICK_H=24, GAP=6, OFFSET_X=45, OFFSET_Y=60
- Colors: normal #4cc9f0, tough #f72585? Or something. Crack visual: draw two diagonal lines on tough brick after first hit.

State:
- `state = 'MENU'` (or DEMO), `score=0`, `lives=3`
- `paddle = {x, y, w, h, speed, dx}`
- `balls = []`
- `bricks = []`
- `particles = []`
- `powerups = []`
- `activePowerups = { wide: {end:0}, multi: ... }` actually multi-ball is instant.
- `paddleWideTimer = 0`
- `demo = true`

Init bricks:
For r in rows, c in cols:
  x = OFFSET_X + c*(BRICK_W+GAP)
  y = OFFSET_Y + r*(BRICK_H+GAP)
  type = Math.random() < 0.3 ? 'tough' : 'normal'
  powerup = Math.random() < 0.15 ? (Math.random()<0.5?'wide':'multi') : null
  hits = type==='tough' ? 2 : 1
  color based on row or type.

Ball reset:
- Start ball at paddle center, y = paddle.y - BALL_R - 1.
- velocity: random angle between -60 and -120? Actually upward. `angle = Math.random()*60 + 60` degrees? Let's do radians: `-Math.PI/2 + random(-0.6, 0.6)`. Speed = 5.
- In menu/demo, ball is "stuck" to paddle until start? Classic breakout: ball sits on paddle. But demo mode behind start screen should show action. Let's have demo balls bouncing freely, paddle AI follows. On start click, serve ball from paddle.

Maybe simpler: Menu state shows one ball bouncing and paddle AI. When user clicks, switch to PLAYING, reset score/lives, create ball on paddle, wait for click? Actually we can just launch automatically after start. Let's do: MENU -> click/key -> PLAYING, reset everything, ball in play.

Game loop:
```
function update(dt) {
  if state === MENU: demoUpdate(); return;
  if state === GAMEOVER/VICTORY: return;
  // substeps
  const steps = 4;
  const step = dt/steps;
  for(let i=0;i<steps;i++){
    movePaddle(step);
    for ball: ball.x += vx*step; ball.y += vy*step;
    checkWalls(ball);
    checkPaddle(ball);
    checkBricks(ball);
    if ball.y > H: handleBallLost(ball);
  }
  updateParticles(dt);
  updatePowerups(dt);
  checkPowerupCollisions();
  if lives<=0: state=GAMEOVER;
  if bricks all broken: state=VICTORY;
}
```

Wall collision: left/right reverse vx clamp position. Top reverse vy clamp. Bottom: ball lost.

Paddle collision:
- Only if ball moving down? Actually if ball hits top of paddle. Condition: `ball.y + r >= paddle.y && ball.y - r <= paddle.y + paddle.h` and x overlap, and previous y was above paddle? Simpler: check overlap and `vy > 0`. Then compute offset.
```
const paddleCenter = paddle.x + paddle.w/2;
const offset = (ball.x - paddleCenter) / (paddle.w/2);
const maxAngle = 70 * Math.PI/180;
let angle = offset * maxAngle; // -max to +max
// ensure upward
const speed = Math.hypot(vx, vy);
vx = Math.sin(angle) * speed;
vy = -Math.cos(angle) * speed;
ball.y = paddle.y - ball.r; // push out
```
But if ball hits side of paddle? We can ignore sides or treat as wall. Requirement says bounce angle depends on where hits paddle. We'll just handle top collision.

Ball never pass through bricks: sub-stepping helps. Also in brick collision, after reversing, push ball out of brick to avoid sticking.

Brick collision:
```
for each brick not destroyed:
  if rect-circle collision:
    // determine side
    // simple: check overlap on x and y
    const dx = Math.abs(ball.x - (brick.x+brick.w/2));
    const dy = Math.abs(ball.y - (brick.y+brick.h/2));
    if dx < brick.w/2 + ball.r && dy < brick.h/2 + ball.r:
       // determine which side
       const overlapX = brick.w/2 + ball.r - dx;
       const overlapY = brick.h/2 + ball.r - dy;
       if overlapX < overlapY: vx*=-1; ball.x += (ball.x<brick.center? -overlapX : overlapX);
       else: vy*=-1; ball.y += ...
       brick.hits--;
       if brick.hits<=0: destroy brick (score, particles, maybe powerup)
       else: mark cracked, maybe change color
       break? or handle one brick per step to avoid double. Let's break after first brick per sub-step per ball to keep simple. But multiple balls could hit multiple bricks. That's fine.
```

Particles: on brick break, spawn 12 particles at brick center with random velocities, colors from brick, life 0.6s.

Power-up drop: when brick breaks and had powerup, spawn `powerup = {x, y, type, vy: 2, r: 6, active:true}`. Falls down. Draw: circle with letter W or M inside.

Power-up catch: if paddle intersects powerup:
- wide: paddle.w *= 1.5, timer=10000ms. When expires, revert. If paddle currently wide and timer resets, extend or reset? Just set timer.
- multi-ball: for each existing ball, spawn 2 more with slight angle variations. Or just add 2 balls total? Requirement says multi-ball (3 balls). So if currently 1 ball, add 2. If more, maybe just add 2 anyway or cap at 5. Let's add 2 new balls with current speed but angles spread.

Paddle wide: store original width. On apply: `paddle.w = 150`. On expire: `paddle.w = 100`. Need to clamp paddle.x if now out of bounds.

Controls:
- `mousemove`: `paddle.x = mouseX - paddle.w/2` clamped.
- `keydown` left/right: set `paddle.dx = -speed or +speed`. `keyup`: reset. Also any key starts game from menu.
- `pointerdown`/`click` on canvas starts.

Demo/AI:
- If state===MENU: move paddle toward ball.x with some lag/speed. Keep ball bouncing. Lives/score not relevant.

Rendering:
- Clear screen black.
- Draw bricks: fillRect stroke. For tough brick with 1 hit left, draw crack lines (two thin lines).
- Draw powerups: circle, fill white, text letter.
- Draw particles: small squares.
- Draw paddle: rounded rect maybe.
- Draw balls: white circle with glow? Simple white circle.
- Draw HUD separately in HTML.

Overlay: show when state GAMEOVER/VICTORY. Hidden otherwise.

Edge cases:
- Resize: canvas internal size fixed 800x600. CSS scales it. Mouse coordinates need to be converted from CSS to canvas coordinates. Get boundingClientRect, compute scaleX = canvas.width / rect.width, scaleY = canvas.height / rect.height.
- No horizontal scrollbar: body overflow hidden, wrapper max-width.

Let's write the code carefully.

Canvas drawing functions:
```
function drawRoundedRect(ctx,x,y,w,h,r){
  ctx.beginPath();
  ctx.roundRect(x,y,w,h,r);
  ctx.fill();
}
```
`roundRect` is widely supported now but to be safe use arcTo or manual. Since latest Chrome, roundRect is fine. But let's be compatible: use manual path.

Paddle color: #0ff. Bricks: normal row colors? Let's do gradient by row: `hsl(${r*25}, 80%, 60%)`. Tough: darker `hsl(${r*25}, 80%, 40%)`. Crack: stroke white lines.

Particle color: brick color.

Powerup: yellow circle #ff0 with letter.

UI overlay text: score final etc.

Let's code.

Time management: use `requestAnimationFrame`, timestamp delta.

Variables:
```
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const W = 800, H = 600;
```
Set `canvas.width = W; canvas.height = H;`

State object:
```
let state = 'MENU'; // MENU, PLAYING, GAMEOVER, VICTORY
let score = 0, lives = 3;
let paddle = {x: W/2-50, y: H-30, w: 100, h: 14, speed: 8, dx: 0};
let balls = [];
let bricks = [];
let particles = [];
let powerups = [];
let paddleWideEnd = 0;
let mouseX = W/2;
```

Init bricks:
```
function initBricks(){
  bricks = [];
  const rows=7, cols=10;
  const bw=70, bh=24, gap=6, ox=45, oy=55;
  for(let r=0;r<rows;r++){
    for(let c=0;c<cols;c++){
      const x = ox + c*(bw+gap);
      const y = oy + r*(bh+gap);
      const tough = Math.random() < 0.25;
      const pu = Math.random() < 0.12 ? (Math.random()<0.5?'wide':'multi') : null;
      bricks.push({x,y,w:bw,h:bh, tough, hits: tough?2:1, pu, color: `hsl(${(r/rows)*360}, 70%, ${tough?45:60}%)`});
    }
  }
}
```

Spawn ball:
```
function spawnBall(onPaddle=true){
  const speed = 5;
  let angle = -Math.PI/2 + (Math.random()-0.5)*1.2; // ~ +/- 34 deg from straight up
  if(onPaddle){
    return {x: paddle.x+paddle.w/2, y: paddle.y-8, r:8, vx:0, vy:-speed, stuck:true};
  } else {
    return {x: W/2, y: H/2, r:8, vx: Math.sin(angle)*speed, vy: Math.cos(angle)*speed, stuck:false};
  }
}
```
But for demo, ball shouldn't be stuck. For PLAYING start, ball stuck to paddle until... Actually we can launch immediately. Let's just launch immediately with random angle. No need to wait.

`startGame()`:
```
score=0; lives=3; paddle.w=100; paddle.x=W/2-50; paddleWideEnd=0;
bricks=[]; initBricks(); balls=[spawnBall()]; particles=[]; powerups=[];
state='PLAYING';
overlay.classList.add('hidden');
```

Demo update:
```
function updateDemo(dt){
  // move balls
  for(ball of balls){
    ball.x += ball.vx*dt;
    ball.y += ball.vy*dt;
    // walls
    if(ball.x-ball.r<0){ball.x=ball.r; ball.vx*=-1;}
    if(ball.x+ball.r>W){ball.x=W-ball.r; ball.vx*=-1;}
    if(ball.y-ball.r<0){ball.y=ball.r; ball.vy*=-1;}
    if(ball.y+ball.r>H){ // respawn
      ball.x=W/2; ball.y=H/2; ball.vy=-Math.abs(ball.vy);
    }
    // paddle AI
    const paddleCenter = paddle.x + paddle.w/2;
    if(ball.x < paddleCenter) paddle.x -= paddle.speed*dt*0.8;
    else paddle.x += paddle.speed*dt*0.8;
    clamp paddle
    // paddle collision
    if(ball.y+ball.r >= paddle.y && ball.y-ball.r <= paddle.y+paddle.h && ball.x>=paddle.x && ball.x<=paddle.x+paddle.w && ball.vy>0){
       const offset = (ball.x - (paddle.x+paddle.w/2))/(paddle.w/2);
       const ang = offset * (65*Math.PI/180);
       const sp = Math.hypot(ball.vx,ball.vy);
       ball.vx = Math.sin(ang)*sp;
       ball.vy = -Math.cos(ang)*sp;
       ball.y = paddle.y - ball.r;
    }
  }
  // clamp paddle
  if(paddle.x<0) paddle.x=0;
  if(paddle.x+paddle.w>W) paddle.x=W-paddle.w;
}
```

But in MENU, we might have multiple balls? Let's keep 1 ball in demo.

Sub-step movement for PLAYING:
```
const subSteps = 4;
const step = dt / subSteps;
for(let s=0;s<subSteps;s++){
  // paddle keyboard
  paddle.x += paddle.dx * step;
  clamp paddle
  // mouse handled separately directly setting target, but we can just set paddle.x based on mouse every frame, not sub-step.
  // Actually mouse is absolute, set directly before substeps.
  
  for ball in balls:
    if ball.stuck: ball.x = paddle.x+paddle.w/2; ball.y=paddle.y-8; continue;
    ball.x += ball.vx*step;
    ball.y += ball.vy*step;
    wall collisions...
    paddle collision...
    brick collisions...
    if ball.y > H + 20: mark for removal
  remove dead balls
  if balls.length===0: loseLife();
}
```

Lose life:
```
function loseLife(){
  lives--;
  updateHUD();
  if(lives<=0){ state='GAMEOVER'; showOverlay('GAME OVER','Final Score: '+score); return; }
  // reset ball
  balls=[spawnBall()];
  paddle.x = W/2 - paddle.w/2;
}
```

Brick collision function:
```
function collideBallBrick(ball, brick){
  if(brick.hits<=0) return false;
  const cx = ball.x, cy = ball.y;
  // closest point on rect
  const rx = Math.max(brick.x, Math.min(cx, brick.x+brick.w));
  const ry = Math.max(brick.y, Math.min(cy, brick.y+brick.h));
  const dx = cx - rx, dy = cy - ry;
  const d2 = dx*dx+dy*dy;
  if(d2 > ball.r*ball.r) return false;
  // resolve
  // determine normal
  let nx=0, ny=0;
  if(d2===0){ // center
    nx = (cx < brick.x+brick.w/2) ? -1 : 1;
    ny = -1;
  } else {
    const d = Math.sqrt(d2);
    nx = dx/d; ny = dy/d;
  }
  // reflect
  const dot = ball.vx*nx + ball.vy*ny;
  ball.vx = (ball.vx - 2*dot*nx);
  ball.vy = (ball.vy - 2*dot*ny);
  // push out
  const overlap = ball.r - Math.sqrt(d2);
  ball.x += nx*overlap;
  ball.y += ny*overlap;
  // brick damage
  brick.hits--;
  if(brick.hits<=0){
    destroyBrick(brick);
  }
  return true;
}
```

Destroy brick:
```
function destroyBrick(brick){
  score += brick.tough?40:10;
  // particles
  for(let i=0;i<10;i++) particles.push({...});
  if(brick.pu) powerups.push({x: brick.x+brick.w/2, y: brick.y+brick.h/2, type: brick.pu, r:6, vy: 2.5, active:true});
  // remove brick by setting hits 0 and filter later or mark alive=false
  brick.alive=false;
}
```

Filter bricks each frame: `bricks = bricks.filter(b => b.alive !== false);` or just keep hits<=0 and skip drawing. Better mark `alive=false`.

Powerup update:
```
for pu in powerups:
  pu.y += pu.vy * dt; // dt in seconds? Let's use dt as seconds? In loop I used step which is dt/steps where dt is seconds? Let's use dt in seconds (ms/1000). Speeds per second.
  if pu.y - pu.r > H: pu.active=false;
  // paddle collision
  if rect circle: activate
```
Let's define dt in seconds. `requestAnimationFrame` gives timestamp; delta = (now - last)/1000.

Paddle movement speed: 350 px/s. Mouse: direct.

Paddle collision detailed:
```
function collideBallPaddle(ball){
  if(ball.vy < 0) return; // only moving down
  if(ball.x < paddle.x || ball.x > paddle.x+paddle.w) return;
  if(ball.y + ball.r < paddle.y) return; // not reached yet
  if(ball.y - ball.r > paddle.y + paddle.h) return; // below
  // hit top
  const offset = (ball.x - (paddle.x + paddle.w/2)) / (paddle.w/2);
  const angle = offset * (65 * Math.PI / 180);
  const speed = Math.hypot(ball.vx, ball.vy);
  ball.vx = Math.sin(angle) * speed;
  ball.vy = -Math.cos(angle) * speed;
  ball.y = paddle.y - ball.r;
}
```
But what if ball comes from side? We ignore.

Ball lost condition: `ball.y - ball.r > H` (bottom). Remove ball. If no balls left -> lose life.

Particles:
```
{ x,y, vx,vy, life, maxLife, color }
update: x+=vx*dt; y+=vy*dt; life-=dt; vy += 400*dt (gravity)
draw: fillRect(x,y,3,3) with color and alpha life/maxLife
```

Draw:
```
function draw(){
  ctx.clearRect(0,0,W,H);
  // bricks
  for(b of bricks){
    if(b.hits<=0) continue;
    ctx.fillStyle = b.color;
    ctx.fillRect(b.x,b.y,b.w,b.h);
    ctx.strokeStyle = 'rgba(0,0,0,0.3)';
    ctx.strokeRect(b.x,b.y,b.w,b.h);
    if(b.tough && b.hits===1){
      ctx.strokeStyle='rgba(255,255,255,0.5)';
      ctx.lineWidth=2;
      ctx.beginPath();
      ctx.moveTo(b.x+6,b.y+6); ctx.lineTo(b.x+b.w-6,b.y+b.h-6);
      ctx.moveTo(b.x+b.w-6,b.y+6); ctx.lineTo(b.x+6,b.y+b.h-6);
      ctx.stroke();
    }
  }
  // powerups
  for(p of powerups){
    if(!p.active) continue;
    ctx.fillStyle = p.type==='wide'?'#0f0':'#f0f';
    ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,Math.PI*2); ctx.fill();
    ctx.fillStyle='#000'; ctx.font='bold 10px sans-serif'; ctx.textAlign='center'; ctx.fillText(p.type==='wide'?'W':'M', p.x, p.y+3);
  }
  // particles
  for(p of particles){
    ctx.globalAlpha = p.life/p.maxLife;
    ctx.fillStyle = p.color;
    ctx.fillRect(p.x,p.y,p.size,p.size);
    ctx.globalAlpha=1;
  }
  // paddle
  ctx.fillStyle='#0ff';
  drawRoundRect(paddle.x,paddle.y,paddle.w,paddle.h,4);
  ctx.fill();
  // balls
  ctx.fillStyle='#fff';
  for(b of balls){
    ctx.beginPath(); ctx.arc(b.x,b.y,b.r,0,Math.PI*2); ctx.fill();
    ctx.strokeStyle='#000'; ctx.stroke();
  }
}
```

Overlay:
```
const overlay = document.getElementById('overlay');
const overlayTitle = document.getElementById('overlayTitle');
const overlaySub = document.getElementById('overlaySub');
function showOverlay(title, sub){
  overlayTitle.textContent=title;
  overlaySub.textContent=sub;
  overlay.classList.remove('hidden');
}
```

Restart button click -> startGame.

Start hint: click anywhere or keypress -> startGame.

Mouse move:
```
canvas.addEventListener('mousemove', e=>{
  const rect = canvas.getBoundingClientRect();
  const sx = W / rect.width;
  const sy = H / rect.height;
  mouseX = (e.clientX - rect.left) * sx;
  if(state==='PLAYING'){
    paddle.x = mouseX - paddle.w/2;
    clamp;
  } else if(state==='MENU'){
    // demo paddle follows mouse? Or AI. Let's AI in menu.
  }
});
```
Actually if user moves mouse in menu, maybe we want paddle to follow? Demo AI should play behind message. Let's have AI move paddle in menu, ignore mouse until start? Or mouse moves paddle in menu too. Simpler: mouse always controls paddle if in PLAYING. In MENU, AI controls paddle.

Keyboard:
```
window.addEventListener('keydown', e=>{
  if(state==='MENU' || state==='GAMEOVER' || state==='VICTORY'){
    startGame(); // but if gameover, restart
    return;
  }
  if(e.key==='ArrowLeft') paddle.dx = -paddle.speed;
  if(e.key==='ArrowRight') paddle.dx = paddle.speed;
});
window.addEventListener('keyup', e=>{
  if(e.key==='ArrowLeft' && paddle.dx<0) paddle.dx=0;
  if(e.key==='ArrowRight' && paddle.dx>0) paddle.dx=0;
});
```

Wait: if state GAMEOVER and key pressed, startGame resets. Good.

Loop:
```
let last=0;
function loop(now){
  const dt = Math.min((now-last)/1000, 0.05);
  last=now;
  update(dt);
  draw();
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
```

Update HUD:
```
document.getElementById('score').textContent = 'Score: '+score;
document.getElementById('lives').textContent = 'Lives: '+lives;
```

Victory check: `if(bricks.every(b=>b.hits<=0)) state='VICTORY'; showOverlay('VICTORY','Score: '+score);`

Multi-ball activation:
```
function activateMulti(){
  const existing = balls.slice();
  for(let b of existing){
    for(let i=0;i<2;i++){
      const ang = Math.atan2(b.vy, b.vx) + (Math.random()-0.5)*0.8;
      const sp = Math.hypot(b.vx,b.vy);
      balls.push({x:b.x, y:b.y, r:8, vx:Math.sin(ang)*sp, vy:Math.cos(ang)*sp, stuck:false});
    }
  }
}
```

Wide paddle:
```
function activateWide(){
  paddle.w = 160;
  paddleWideEnd = Date.now() + 10000;
  if(paddle.x + paddle.w > W) paddle.x = W - paddle.w;
}
```
Check expire each frame:
```
if(paddleWideEnd && Date.now() > paddleWideEnd){
  paddle.w = 100;
  paddleWideEnd = 0;
  clamp paddle.x
}
```

Powerup catch rect-circle:
```
function rectCircleOverlap(rect, circle){
  const cx=circle.x, cy=circle.y;
  const rx=Math.max(rect.x, Math.min(cx, rect.x+rect.w));
  const ry=Math.max(rect.y, Math.min(cy, rect.y+rect.h));
  const dx=cx-rx, dy=cy-ry;
  return dx*dx+dy*dy <= circle.r*circle.r;
}
```

Particles colors: use brick color.

Let's ensure at least 6 rows: 7 rows yes.

Tough bricks visibly crack: yes after first hit.

Ball never pass through: substeps + continuous-ish reflection.

Let's consider performance and bugs:
- `paddle.dx` units: px/s. `paddle.x += paddle.dx * step` where step in seconds. speed 400 px/s.
- Mouse direct: `paddle.x = mouseX - paddle.w/2`. This overrides keyboard. Fine.
- Clamp paddle: `paddle.x = Math.max(0, Math.min(W - paddle.w, paddle.x));`

Demo AI in MENU:
```
function updateMenu(dt){
  // one ball demo
  if(balls.length===0) balls=[spawnBallDemo()];
  const b=balls[0];
  b.x += b.vx*dt; b.y += b.vy*dt;
  // walls
  if(b.x-b.r<0){b.x=b.r; b.vx*=-1;}
  if(b.x+b.r>W){b.x=W-b.r; b.vx*=-1;}
  if(b.y-b.r<0){b.y=b.r; b.vy*=-1;}
  if(b.y+b.r>H){ b.x=W/2; b.y=H/2; b.vy=-Math.abs(b.vy); b.vx=(Math.random()-0.5)*4; }
  // paddle AI
  const target = b.x - paddle.w/2;
  if(paddle.x < target) paddle.x += paddle.speed*dt;
  else if(paddle.x > target) paddle.x -= paddle.speed*dt;
  clamp paddle
  // paddle collision
  if(b.y+b.r>=paddle.y && b.y-b.r<=paddle.y+paddle.h && b.x>=paddle.x && b.x<=paddle.x+paddle.w && b.vy>0){
    const offset = (b.x-(paddle.x+paddle.w/2))/(paddle.w/2);
    const ang = offset*(65*Math.PI/180);
    const sp = Math.hypot(b.vx,b.vy);
    b.vx=Math.sin(ang)*sp;
    b.vy=-Math.cos(ang)*sp;
    b.y = paddle.y - b.r;
  }
}
```
This looks good.

Need to make sure hint text says click or press key. It's visible.

Responsive CSS: body flex center, overflow hidden. Wrapper aspect-ratio 4/3. On small screens, 92vw width, height auto via aspect-ratio. Max-height 600px prevents overflow on small vertical screens. Canvas scales.

No horizontal scrollbar: body overflow hidden, no margins.

Everything inline. No external assets.

One concern: `canvas.getContext('2d')` and `roundRect` — I'll avoid roundRect to be safe and manually draw rounded rect.

```
function drawRoundRect(x,y,w,h,r){
  ctx.beginPath();
  ctx.moveTo(x+r,y);
  ctx.lineTo(x+w-r,y);
  ctx.quadraticCurveTo(x+w,y,x+w,y+r);
  ctx.lineTo(x+w,y+h-r);
  ctx.quadraticCurveTo(x+w,y+h,x+w-r,y+h);
  ctx.lineTo(x+r,y+h);
  ctx.quadraticCurveTo(x,y+h,x,y+h-r);
  ctx.lineTo(x,y+r);
  ctx.quadraticCurveTo(x,y,x+r,y);
  ctx.closePath();
}
```

Power-up text: use `fillText`. Need font. Default sans-serif fine.

Brick colors: tough bricks maybe same hue but darker. Crack drawn.

Let's think about power-up icon visibility: "Some bricks visibly carry a power-up icon". How to visibly show on brick? Could draw a small circle or letter on brick when it has powerup. Let's draw a small star or letter on bricks that have pu. E.g., `ctx.fillStyle='rgba(255,255,255,0.6); ctx.fillText('*', b.x+b.w/2, b.y+b.h/2+4);` or letter 'P'. That satisfies visibly carry.

Let's add that in brick draw:
```
if(b.pu){
  ctx.fillStyle='rgba(255,255,255,0.8)';
  ctx.font='bold 14px sans-serif';
  ctx.textAlign='center';
  ctx.textBaseline='middle';
  ctx.fillText(b.pu==='wide'?'W':'M', b.x+b.w/2, b.y+b.h/2);
}
```

Particles when brick breaks: yes.

Score and lives displayed: HUD.

Game over and victory screens with restart option: overlay with button. Also key/click restart.

"Self-playing demo mode behind a Click or press a key to start message": yes.

"Something meaningful must be visible on first paint": Canvas starts drawing immediately, balls bouncing. Good.

"The ball must never pass through bricks or paddle": substep 4, push-out resolution.

Let's verify collision when ball is moving fast upward and hits brick from below. `dy` normal will be positive (ball below brick? Actually if ball moving up hits bottom of brick, closest point is top edge of brick, ball center is below, dy = cy - ry > 0? Wait ry = brick.y (top), cy > ry, so dy positive. normal points from brick to ball = down. Reflect vy: dot = vx*nx + vy*ny. If moving up vy negative, ny positive, dot negative. Reflect: vy_new = vy - 2*dot*ny => negative - 2(negative)(positive) = negative + positive = maybe positive. Good.

Paddle collision only when vy>0. Good.

Ball lost: `ball.y - ball.r > H`. Remove.

Multiple balls: each independent.

Substep loop order: paddle move, then balls. Fine.

Potential bug: if ball gets stuck inside brick due to high speed, push-out may not fully resolve but substeps mitigate.

Let's write complete HTML.

Make sure to include `updateHUD()` calls.

Overlay restart button: `document.getElementById('restartBtn').onclick = startGame;`

Also clicking canvas when in MENU should start. `canvas.addEventListener('click', ()=>{ if(state==='MENU') startGame(); });`

Key press on window: if MENU/GAMEOVER/VICTORY -> startGame. But careful: arrow keys in PLAYING shouldn't trigger start. So check state first.

Let's structure startGame:
```
function startGame(){
  if(state==='PLAYING') return;
  score=0; lives=3; paddle.w=100; paddle.x=W/2-50; paddleWideEnd=0;
  initBricks(); balls=[spawnBall()]; particles=[]; powerups=[]; state='PLAYING';
  overlay.classList.add('hidden');
  updateHUD();
}
```

`spawnBall()` for start:
```
function spawnBall(){
  const speed = 5;
  const ang = -Math.PI/2 + (Math.random()-0.5)*0.8;
  return {x: paddle.x+paddle.w/2, y: paddle.y-10, r:8, vx: Math.sin(ang)*speed, vy: Math.cos(ang)*speed};
}
```
cos(-pi/2) is 0? Actually cos(-pi/2)=0, sin=-1. Wait angle -pi/2 is straight up? In canvas y down, straight up is vy negative. `Math.sin(-pi/2)=-1`, `Math.cos(-pi/2)=0`. So vx=-speed, vy=0? That's left. Let's define properly:
Direction vector: `vx = Math.sin(angle) * speed`, `vy = -Math.cos(angle) * speed`. If angle=0 -> vx=0, vy=-speed (straight up). Good. So angle range `-0.6 to 0.6`.

```
const angle = (Math.random()-0.5)*1.2;
ball.vx = Math.sin(angle)*speed;
ball.vy = -Math.cos(angle)*speed;
```

Good.

Powerup spawn position: brick center.

Powerup fall speed: 150 px/s.

Activate multi-ball: add 2 balls per existing ball? Requirement says multi-ball (3 balls). If 1 ball exists, adding 2 makes 3. If more, adding 2 each could explode. Let's just add 2 balls total regardless, positioned at first ball with spread. Simpler:
```
function activateMulti(){
  const base = balls[0];
  for(let i=0;i<2;i++){
    const ang = Math.atan2(base.vy, base.vx) + (Math.random()-0.5)*1;
    const sp = Math.hypot(base.vx, base.vy);
    balls.push({x:base.x, y:base.y, r:8, vx:Math.sin(ang)*sp, vy:-Math.cos(ang)*sp? wait});
  }
}
```
Use same formula: `vx = Math.sin(ang)*sp; vy = -Math.cos(ang)*sp;` to keep generally upward? But base could be moving any direction. Let's just use angle relative to base:
```
const baseAng = Math.atan2(base.vy, base.vx);
balls.push({... vx: Math.sin(baseAng + offset)*sp, vy: Math.cos(baseAng + offset)*sp ...})
```
Because `Math.atan2(vy, vx)` gives standard angle where y down. Fine.

Actually simpler: push new balls with random upward directions like spawnBall but at current position. Requirement just says multi-ball (3 balls). Let's do:
```
const sp = Math.hypot(base.vx, base.vy);
for(let i=0;i<2;i++){
  const ang = (Math.random()-0.5)*1.2;
  balls.push({x:base.x, y:base.y, r:8, vx: Math.sin(ang)*sp, vy: -Math.cos(ang)*sp});
}
```
This ensures upward.

Activate wide: as above.

Powerup draw: circle with letter.

Let's also add a subtle glow? Not necessary.

Code length: manageable.

Check "No text before or after code block". So final answer must be exactly one fenced code block. No explanation.

Let's write it all.

Double-check HTML/CSS:

```
<style>
  *{box-sizing:border-box;margin:0;padding:0}
  body{background:#0b0b10;color:#fff;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;overflow:hidden;touch-action:manipulation}
  #wrap{aspect-ratio:4/3;width:92vw;max-width:800px;max-height:90vh;position:relative;border:6px solid #1a1a2e;box-shadow:0 0 30px rgba(0,0,0,0.9);border-radius:8px;overflow:hidden}
  canvas{width:100%;height:100%;display:block;background:#000}
  #hud{position:absolute;top:12px;left:12px;right:12px;display:flex;justify-content:space-between;pointer-events:none;font-weight:700;font-size:clamp(1rem,2vw,1.4rem);letter-spacing:1px;text-shadow:0 0 6px #000;z-index:10}
  #score{color:#0ff}
  #lives{color:#f55}
  #ui{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none;z-index:20}
  h1{font-size:clamp(1.4rem,5vw,2.6rem);text-transform:uppercase;letter-spacing:6px;margin-bottom:0.4rem;color:#0ff;text-shadow:0 0 12px rgba(0,255,255,0.6)}
  .hint{font-size:clamp(0.9rem,3vw,1.2rem);color:#ccc;animation:pulse 1.8s infinite alternate}
  @keyframes pulse{from{opacity:.7}to{opacity:1}}
  #overlay{position:absolute;inset:0;background:rgba(10,10,16,0.88);display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:auto;z-index:30}
  #overlay.hidden{display:none}
  #overlay h1{font-size:clamp(1.6rem,6vw,3rem);margin-bottom:0.5rem}
  #overlaySub{font-size:clamp(1rem,3vw,1.4rem);color:#aaa;margin-bottom:1.2rem}
  button{padding:0.9rem 1.8rem;font-size:1.1rem;background:#0ff;color:#000;border:none;border-radius:6px;cursor:pointer;font-weight:700;box-shadow:0 0 15px rgba(0,255,255,0.4)}
  button:hover{background:#6ff}
</style>
```

Wait `margin-bottom` on h1 inside overlay vs h1 inside ui both same selector. Fine.

Overlay hidden initially via CSS? Add `hidden` class in HTML. Yes.

Canvas drawing: use `ctx.lineWidth` etc.

JS:

```
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d', {alpha:false});
const W = 800, H = 600;
canvas.width = W; canvas.height = H;
```

`{alpha:false}` may improve performance.

State vars.

Functions:
- clampPaddle()
- initBricks()
- spawnBall(angle?, x?, y?) maybe generic.
- startGame()
- loseLife()
- activatePowerup(type)
- update(dt)
- draw()
- loop()

Let's write code carefully.

```
let state = 'MENU'; // MENU | PLAYING | GAMEOVER | VICTORY
let score = 0, lives = 3;
let paddle = { x: W/2-50, y: H-34, w: 100, h: 14, speed: 420, dx: 0 };
let balls = [];
let bricks = [];
let particles = [];
let powerups = [];
let paddleWideEnd = 0;
let mouseX = W/2;
let last = 0;
```

`initBricks`:
```
function initBricks(){
  bricks = [];
  const rows=7, cols=10, bw=66, bh=22, gap=8, ox=55, oy=70;
  for(let r=0;r<rows;r++){
    for(let c=0;c<cols;c++){
      const x = ox + c*(bw+gap);
      const y = oy + r*(bh+gap);
      const tough = Math.random() < 0.28;
      let pu = null;
      if(Math.random() < 0.14) pu = Math.random() < 0.5 ? 'wide' : 'multi';
      bricks.push({
        x,y,w:bw,h:bh,tough,
        hits: tough?2:1,
        pu,
        color: `hsl(${(r/rows)*340 + 180}, 75%, ${tough?40:58}%)`
      });
    }
  }
}
```
`hsl(... )` template string fine.

`spawnBall()`:
```
function spawnBall(){
  const speed = 5.5; // px per ms? No, px per second. Since dt in seconds.
  const angle = (Math.random()-0.5)*1.1;
  return {
    x: paddle.x + paddle.w/2,
    y: paddle.y - 10,
    r: 8,
    vx: Math.sin(angle)*speed,
    vy: -Math.cos(angle)*speed
  };
}
```
Wait speed 5.5 px/s is too slow. Paddle speed 420 px/s. Ball speed should be ~300-400 px/s. Let's set `speed = 340`.

In menu demo ball speed maybe 260.

`clampPaddle()`:
```
function clampPaddle(){
  if(paddle.x < 0) paddle.x = 0;
  if(paddle.x + paddle.w > W) paddle.x = W - paddle.w;
}
```

`loseLife()`:
```
function loseLife(){
  lives--;
  updateHUD();
  if(lives <= 0){
    state = 'GAMEOVER';
    showOverlay('GAME OVER', 'Final Score: ' + score);
    return;
  }
  balls = [spawnBall()];
  paddle.w = 100;
  paddle.x = W/2 - paddle.w/2;
  paddleWideEnd = 0;
}
```
Need to reset paddle size on lose life? Not necessarily but okay.

`showOverlay`:
```
function showOverlay(title, sub){
  document.getElementById('overlayTitle').textContent = title;
  document.getElementById('overlaySub').textContent = sub;
  document.getElementById('overlay').classList.remove('hidden');
}
```

`startGame`:
```
function startGame(){
  if(state === 'PLAYING') return;
  score = 0; lives = 3;
  paddle.w = 100; paddle.x = W/2 - 50; paddle.dx = 0; paddleWideEnd = 0;
  balls = []; bricks = []; particles = []; powerups = [];
  initBricks();
  balls.push(spawnBall());
  state = 'PLAYING';
  document.getElementById('overlay').classList.add('hidden');
  updateHUD();
}
```

`updateHUD()`:
```
document.getElementById('score').textContent = 'SCORE ' + score;
document.getElementById('lives').textContent = 'LIVES ' + lives;
```

`activatePowerup(type)`:
```
function activatePowerup(type){
  if(type === 'wide'){
    paddle.w = 160;
    paddleWideEnd = Date.now() + 10000;
    clampPaddle();
  } else if(type === 'multi'){
    const base = balls[0];
    if(!base) return;
    const sp = Math.hypot(base.vx, base.vy) || 340;
    for(let i=0;i<2;i++){
      const ang = (Math.random()-0.5)*1.2;
      balls.push({x:base.x, y:base.y, r:8, vx: Math.sin(ang)*sp, vy: -Math.cos(ang)*sp});
    }
  }
}
```

Update:
```
function update(dt){
  if(state === 'MENU'){
    updateMenu(dt);
    return;
  }
  if(state !== 'PLAYING') return;

  // wide timer
  if(paddleWideEnd && Date.now() > paddleWideEnd){
    paddle.w = 100;
    paddleWideEnd = 0;
    clampPaddle();
  }

  // mouse control
  paddle.x = mouseX - paddle.w/2;
  clampPaddle();
  // keyboard
  paddle.x += paddle.dx * dt;
  clampPaddle();

  const steps = 5;
  const step = dt / steps;
  for(let s=0;s<steps;s++){
    // move paddle slightly for keyboard within substep? Already moved full dt above, not ideal but fine.
    // move balls
    for(let i=balls.length-1;i>=0;i--){
      const b = balls[i];
      b.x += b.vx * step;
      b.y += b.vy * step;

      // walls
      if(b.x - b.r < 0){ b.x = b.r; b.vx *= -1; }
      if(b.x + b.r > W){ b.x = W - b.r; b.vx *= -1; }
      if(b.y - b.r < 0){ b.y = b.r; b.vy *= -1; }

      // paddle
      if(b.vy > 0 && b.y + b.r >= paddle.y && b.y - b.r <= paddle.y + paddle.h && b.x >= paddle.x && b.x <= paddle.x + paddle.w){
        const offset = (b.x - (paddle.x + paddle.w/2)) / (paddle.w/2);
        const ang = offset * (68 * Math.PI / 180);
        const sp = Math.hypot(b.vx, b.vy) || 340;
        b.vx = Math.sin(ang) * sp;
        b.vy = -Math.cos(ang) * sp;
        b.y = paddle.y - b.r;
      }

      // bricks
      let hit = false;
      for(let j=0;j<bricks.length && !hit;j++){
        const br = bricks[j];
        if(br.hits <= 0) continue;
        const rx = Math.max(br.x, Math.min(b.x, br.x + br.w));
        const ry = Math.max(br.y, Math.min(b.y, br.y + br.h));
        const dx = b.x - rx, dy = b.y - ry;
        const d2 = dx*dx + dy*dy;
        if(d2 <= b.r*b.r && d2 > 0){
          const d = Math.sqrt(d2);
          const nx = dx/d, ny = dy/d;
          const dot = b.vx*nx + b.vy*ny;
          b.vx = b.vx - 2*dot*nx;
          b.vy = b.vy - 2*dot*ny;
          const overlap = b.r - d;
          b.x += nx*overlap;
          b.y += ny*overlap;
          br.hits--;
          if(br.hits <= 0){
            br.alive = false;
            score += br.tough ? 40 : 10;
            // particles
            const col = br.color;
            for(let p=0;p<10;p++){
              const pa = Math.random()*Math.PI*2;
              const pv = 40 + Math.random()*160;
              particles.push({x: br.x+br.w/2, y: br.y+br.h/2, vx: Math.cos(pa)*pv, vy: Math.sin(pa)*pv, life: 0.5 + Math.random()*0.4, maxLife: 1, color: col, size: 2+Math.random()*3});
            }
            if(br.pu){
              powerups.push({x: br.x+br.w/2, y: br.y+br.h/2, type: br.pu, r: 6, vy: 170, active: true});
            }
          }
          hit = true;
          updateHUD();
        } else if(d2 === 0){
          // rare center case
        }
      }
      if(b.y - b.r > H){
        balls.splice(i,1);
      }
    }
    if(balls.length === 0){
      loseLife();
      break; // exit substeps
    }
  }
  // cleanup bricks
  for(let i=bricks.length-1;i>=0;i--) if(bricks[i].alive === false) bricks.splice(i,1);
  // victory
  if(bricks.length === 0){
    state = 'VICTORY';
    showOverlay('VICTORY', 'Score: ' + score);
    return;
  }

  // particles
  for(let i=particles.length-1;i>=0;i--){
    const p = particles[i];
    p.x += p.vx * dt;
    p.y += p.vy * dt;
    p.vy += 500 * dt; // gravity
    p.life -= dt;
    if(p.life <= 0) particles.splice(i,1);
  }
  // powerups
  for(let i=powerups.length-1;i>=0;i--){
    const p = powerups[i];
    if(!p.active) continue;
    p.y += p.vy * dt;
    // paddle catch
    const rx = Math.max(paddle.x, Math.min(p.x, paddle.x + paddle.w));
    const ry = Math.max(paddle.y, Math.min(p.y, paddle.y + paddle.h));
    const dx = p.x - rx, dy = p.y - ry;
    if(dx*dx + dy*dy <= p.r*p.r){
      activatePowerup(p.type);
      p.active = false;
      powerups.splice(i,1);
      continue;
    }
    if(p.y - p.r > H){
      p.active = false;
      powerups.splice(i,1);
    }
  }
}
```

Wait: keyboard paddle movement inside substeps: I moved paddle full dt before substeps, then balls move substeps. That's okay but if paddle moves fast, ball may tunnel through paddle? Substeps help ball, paddle moved discretely. Could move paddle per substep too: `paddle.x += paddle.dx * step; clampPaddle();` inside loop. But mouse sets absolute each frame. Let's restructure:

```
for s in steps:
  // paddle keyboard per substep
  paddle.x += paddle.dx * step;
  clampPaddle();
  // balls...
```
And mouse set before loop: `paddle.x = mouseX - paddle.w/2; clampPaddle();` Then keyboard `paddle.dx` could override? If both mouse and keyboard, weird. Fine.

But paddle.x set by mouse before loop, then keyboard adds. Let's just not add keyboard inside loop if mouse is active. Simpler: handle paddle movement once per frame:
```
if (mouseMovedThisFrame) paddle.x = mouseX - paddle.w/2;
else paddle.x += paddle.dx * dt;
clampPaddle();
```
We don't track mouseMovedThisFrame. Let's just use `paddle.dx` for keys and mouse sets target. Simplest: in update, if state playing, set paddle.x based on mouse directly every frame, ignoring keyboard dx? But requirement wants arrow keys too. We can blend: if user presses keys, `paddle.dx` set. On mousemove, set `mouseX` and maybe clear `paddle.dx`. Let's do:

In `mousemove`: `mouseX = ...` and `paddle.dx = 0;` (mouse takes precedence).
In `keydown` arrows: set `paddle.dx` (keyboard takes precedence until mouse moves again).
In update: `paddle.x += paddle.dx * dt; clampPaddle();` Then optionally if mouse moved recently? Without tracking, mouse won't move paddle unless dx=0 and we don't set from mouse. Hmm.

Better: In update, if `paddle.dx !== 0` use keyboard, else use mouse target. But user might hold key and move mouse. Edge case not critical. Let's do:
```
const keyMove = paddle.dx !== 0;
if(!keyMove){
  paddle.x = mouseX - paddle.w/2;
}
paddle.x += paddle.dx * dt;
clampPaddle();
```
If dx is 0, mouse controls. If key pressed, dx nonzero and keyboard controls. Good.

But on `keyup`, set dx=0, then mouse takes over next frame. Fine.

`updateMenu(dt)`:
```
function updateMenu(dt){
  if(balls.length === 0){
    balls.push({x:W/2,y:H/2,r:8,vx:120,vy:-200});
  }
  const b = balls[0];
  b.x += b.vx * dt;
  b.y += b.vy * dt;
  if(b.x - b.r < 0){ b.x = b.r; b.vx *= -1; }
  if(b.x + b.r > W){ b.x = W - b.r; b.vx *= -1; }
  if(b.y - b.r < 0){ b.y = b.r; b.vy *= -1; }
  if(b.y + b.r > H){ b.x = W/2; b.y = H/2; b.vx = (Math.random()-0.5)*200; b.vy = -200; }
  // AI paddle
  const target = b.x - paddle.w/2;
  const diff = target - paddle.x;
  paddle.x += Math.sign(diff) * Math.min(Math.abs(diff), paddle.speed * dt);
  clampPaddle();
  // paddle collision
  if(b.vy > 0 && b.y + b.r >= paddle.y && b.y - b.r <= paddle.y + paddle.h && b.x >= paddle.x && b.x <= paddle.x + paddle.w){
    const offset = (b.x - (paddle.x + paddle.w/2)) / (paddle.w/2);
    const ang = offset * (68 * Math.PI / 180);
    const sp = Math.hypot(b.vx, b.vy) || 260;
    b.vx = Math.sin(ang) * sp;
    b.vy = -Math.cos(ang) * sp;
    b.y = paddle.y - b.r;
  }
}
```

Draw:
```
function draw(){
  ctx.clearRect(0,0,W,H);

  // bricks
  for(const br of bricks){
    if(br.hits <= 0) continue;
    ctx.fillStyle = br.color;
    ctx.fillRect(br.x, br.y, br.w, br.h);
    ctx.strokeStyle = 'rgba(0,0,0,0.45)';
    ctx.lineWidth = 2;
    ctx.strokeRect(br.x, br.y, br.w, br.h);
    if(br.tough && br.hits === 1){
      ctx.strokeStyle = 'rgba(255,255,255,0.55)';
      ctx.beginPath();
      ctx.moveTo(br.x+8, br.y+8);
      ctx.lineTo(br.x+br.w-8, br.y+br.h-8);
      ctx.moveTo(br.x+br.w-8, br.y+8);
      ctx.lineTo(br.x+8, br.y+br.h-8);
      ctx.stroke();
    }
    if(br.pu){
      ctx.fillStyle = 'rgba(255,255,255,0.85)';
      ctx.font = 'bold 13px sans-serif';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(br.pu === 'wide' ? 'W' : 'M', br.x + br.w/2, br.y + br.h/2);
    }
  }

  // powerups
  for(const p of powerups){
    if(!p.active) continue;
    ctx.fillStyle = p.type === 'wide' ? '#4ade80' : '#f472b6';
    ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI*2); ctx.fill();
    ctx.fillStyle = '#fff';
    ctx.font = 'bold 11px sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(p.type === 'wide' ? 'W' : 'M', p.x, p.y);
  }

  // particles
  for(const p of particles){
    ctx.globalAlpha = Math.max(0, p.life / p.maxLife);
    ctx.fillStyle = p.color;
    ctx.fillRect(p.x, p.y, p.size, p.size);
  }
  ctx.globalAlpha = 1;

  // paddle
  ctx.fillStyle = '#22d3ee';
  drawRoundRect(paddle.x, paddle.y, paddle.w, paddle.h, 5);
  ctx.fill();
  ctx.strokeStyle = '#05b8d4';
  ctx.lineWidth = 2;
  ctx.stroke();

  // balls
  ctx.fillStyle = '#fff';
  ctx.lineWidth = 2;
  ctx.strokeStyle = '#000';
  for(const b of balls){
    ctx.beginPath(); ctx.arc(b.x, b.y, b.r, 0, Math.PI*2); ctx.fill(); ctx.stroke();
  }
}
```

`drawRoundRect` as manual.

Controls setup:
```
canvas.addEventListener('mousemove', e=>{
  const rect = canvas.getBoundingClientRect();
  const sx = W / rect.width;
  mouseX = (e.clientX - rect.left) * sx;
  paddle.dx = 0;
});
window.addEventListener('keydown', e=>{
  if(state === 'MENU' || state === 'GAMEOVER' || state === 'VICTORY'){
    startGame();
    return;
  }
  if(e.key === 'ArrowLeft'){ paddle.dx = -paddle.speed; e.preventDefault(); }
  if(e.key === 'ArrowRight'){ paddle.dx = paddle.speed; e.preventDefault(); }
});
window.addEventListener('keyup', e=>{
  if(e.key === 'ArrowLeft' && paddle.dx < 0) paddle.dx = 0;
  if(e.key === 'ArrowRight' && paddle.dx > 0) paddle.dx = 0;
});
canvas.addEventListener('click', ()=>{
  if(state === 'MENU') startGame();
  else if(state === 'GAMEOVER' || state === 'VICTORY') startGame();
});
document.getElementById('restartBtn').addEventListener('click', startGame);
```

Loop:
```
function loop(now){
  if(!last) last = now;
  const dt = Math.min((now - last) / 1000, 0.05);
  last = now;
  update(dt);
  draw();
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
```

Wait: if state MENU, balls demo drawn. HUD visible. Good.

On first paint, MENU state shows ball and paddle. Good.

Potential issue: `balls` in MENU not reset on startGame? startGame resets balls. Fine.

Potential issue: `loseLife` called inside substeps loop breaks, but then code after loop `if(balls.length===0)` not needed because handled. However `loseLife` may set state to GAMEOVER, then we `break`. Good. But after `loseLife`, the outer `update` returns? Not automatically. We break substeps, then cleanup bricks, victory check, particles, powerups run even if GAMEOVER. We should `return` after loseLife if state changed? In code:
```
if(balls.length === 0){
  loseLife();
  break;
}
```
After break, we proceed to cleanup and victory check. `bricks.length === 0` could be true simultaneously? Unlikely. But particles etc run. Better to wrap rest of playing logic in `if(state === 'PLAYING')`. Let's do:
```
if(state !== 'PLAYING') return;
```
after substeps? Actually we can structure:
```
function update(dt){
  if(state === 'MENU'){ updateMenu(dt); return; }
  if(state !== 'PLAYING') return;
  ...
  // inside substeps if balls empty:
  if(balls.length === 0){ loseLife(); break; }
  // after substeps:
  if(state !== 'PLAYING') return; // loseLife may have changed state
  // bricks cleanup, victory check, particles, powerups
}
```
Yes.

`loseLife` sets state GAMEOVER or resets ball and stays PLAYING. If lives>0, state remains PLAYING, balls has new ball. Good. Then continue update.

Need to ensure `updateHUD()` called in loseLife and startGame and brick destroy.

Everything seems good.

Check "tougher bricks that need 2 hits and visibly crack after the one". Yes: `hits===1` draws cracks.

Check "Some bricks visibly carry a power-up icon": draw W/M on brick.

Check "catching it with the paddle gives either wider paddle (10 seconds) or multi-ball (3 balls)": multi-ball adds 2 to existing 1 = 3.

Check "The ball must never pass through bricks or paddle": substeps + pushout.

Check "Particles when a brick breaks": yes.

Check "3 lives, score displayed": yes.

Check "Game over and victory screens with restart option": overlay with button and any key/click.

Check "Click or press any key to start": visible.

Check "self-playing demo mode behind message": updateMenu runs.

Check responsive: CSS aspect-ratio, max-width, max-height, body overflow hidden.

Check no external resources.

One minor: `canvas.getContext('2d', {alpha:false})` — fine.

Let's produce final answer as single code block. No markdown text outside.

Double-check syntax. Template literals with backticks inside string are fine.

The HTML should include `<!DOCTYPE html>`. Good.

I think this is solid. Let's write final.