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

Gemini 4 CarbonGoogle

Network offOpen alone (new tab)

Run details

Model
google/gemini-4-carbon
Route
OpenRouter API, one user message
Served by
Google AI Studio
Effort
medium, OpenRouter's default for this model
Limits
60 min · 32,768 output tokens (the model's limit)
Date
10 Nov 2026
Time
2 min 25 s
Size
46 kB
Tokens
109 in, 19,175 out (1,597 of them thinking)
List price
$0.19 at today's price ($2.00 in / $10.00 out per M)
Paid
$0.19

Raw files: output · meta.json

Exact prompt

Make a Breakout game I can play right now with the keyboard or the mouse. Give it several levels, a score and some juice.

It must be one complete, self-contained HTML file: inline all CSS and JavaScript, and make no network requests of any kind (no CDNs, web fonts or remote images). Reply with the file in a single ```html code block.

Source

46 kB 1,622 lines Raw

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NEON BREAKOUT // OVERDRIVE</title>
<style>
  :root {
    --bg: #070812;
    --panel: #101328;
    --cyan: #00f3ff;
    --pink: #ff007f;
    --yellow: #ffe600;
    --green: #00ff88;
    --text: #e8f0ff;
  }

  * {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
    user-select: none;
    -webkit-user-select: none;
  }

  body {
    background: radial-gradient(circle at center, #121633 0%, var(--bg) 100%);
    color: var(--text);
    font-family: ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, monospace;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    overflow: hidden;
  }

  #game-wrapper {
    position: relative;
    width: min(96vw, calc(90vh * (4 / 3)));
    aspect-ratio: 4 / 3;
    box-shadow:
      0 0 40px rgba(0, 243, 255, 0.18),
      0 20px 60px rgba(0, 0, 0, 0.8),
      inset 0 0 2px rgba(255, 255, 255, 0.25);
    border: 2px solid rgba(0, 243, 255, 0.35);
    border-radius: 12px;
    overflow: hidden;
    background: #060710;
  }

  canvas {
    width: 100%;
    height: 100%;
    display: block;
    cursor: none;
  }

  canvas.show-cursor {
    cursor: default;
  }

  #footer-bar {
    margin-top: 10px;
    display: flex;
    gap: 18px;
    align-items: center;
    justify-content: center;
    font-size: 12px;
    color: rgba(232, 240, 255, 0.65);
    letter-spacing: 0.06em;
    flex-wrap: wrap;
    padding: 0 12px;
  }

  .key {
    display: inline-block;
    padding: 2px 6px;
    border-radius: 4px;
    background: rgba(255, 255, 255, 0.09);
    border: 1px solid rgba(255, 255, 255, 0.2);
    color: var(--cyan);
    font-weight: 700;
    font-size: 11px;
  }

  button.hud-btn {
    background: rgba(0, 243, 255, 0.1);
    color: var(--cyan);
    border: 1px solid rgba(0, 243, 255, 0.4);
    border-radius: 5px;
    padding: 4px 10px;
    font: inherit;
    font-size: 11px;
    font-weight: 700;
    cursor: pointer;
    transition: background 0.15s, transform 0.1s;
  }

  button.hud-btn:hover {
    background: rgba(0, 243, 255, 0.25);
  }

  button.hud-btn:active {
    transform: scale(0.95);
  }
</style>
</head>
<body>

<div id="game-wrapper">
  <canvas id="game" width="800" height="600" class="show-cursor"></canvas>
</div>

<div id="footer-bar">
  <span><span class="key">MOUSE</span> or <span class="key">←</span> <span class="key">→</span> / <span class="key">A</span> <span class="key">D</span> Move</span>
  <span><span class="key">CLICK</span> or <span class="key">SPACE</span> Launch</span>
  <span><span class="key">P</span> Pause</span>
  <button class="hud-btn" id="mute-btn" type="button">SOUND: ON (M)</button>
</div>

<script>
(() => {
  'use strict';

  // =========================================================
  // WEB AUDIO PROCEDURAL SYNTHESIZER (Zero external assets)
  // =========================================================
  class SoundFX {
    constructor() {
      this.ctx = null;
      this.muted = false;
      this.noiseBuffer = null;
    }

    init() {
      if (this.ctx) {
        if (this.ctx.state === 'suspended') this.ctx.resume();
        return;
      }
      const AudioCtx = window.AudioContext || window.webkitAudioContext;
      if (!AudioCtx) return;
      this.ctx = new AudioCtx();

      // Pre-generate white noise buffer for explosions & crisp percussion
      const bufferSize = this.ctx.sampleRate * 0.5;
      this.noiseBuffer = this.ctx.createBuffer(1, bufferSize, this.ctx.sampleRate);
      const data = this.noiseBuffer.getChannelData(0);
      for (let i = 0; i < bufferSize; i++) {
        data[i] = Math.random() * 2 - 1;
      }
    }

    toggleMute() {
      this.muted = !this.muted;
      return this.muted;
    }

    tone(freq, endFreq, duration, type = 'sine', vol = 0.18, delay = 0) {
      if (this.muted || !this.ctx) return;
      const now = this.ctx.currentTime + delay;
      const osc = this.ctx.createOscillator();
      const gain = this.ctx.createGain();
      osc.type = type;
      osc.frequency.setValueAtTime(freq, now);
      if (endFreq !== freq) {
        osc.frequency.exponentialRampToValueAtTime(Math.max(20, endFreq), now + duration);
      }
      gain.gain.setValueAtTime(0.0001, now);
      gain.gain.linearRampToValueAtTime(vol, now + 0.008);
      gain.gain.exponentialRampToValueAtTime(0.0001, now + duration);
      osc.connect(gain);
      gain.connect(this.ctx.destination);
      osc.start(now);
      osc.stop(now + duration + 0.01);
    }

    noise(duration, filterFreq = 1200, vol = 0.22) {
      if (this.muted || !this.ctx || !this.noiseBuffer) return;
      const now = this.ctx.currentTime;
      const src = this.ctx.createBufferSource();
      src.buffer = this.noiseBuffer;

      const filter = this.ctx.createBiquadFilter();
      filter.type = 'lowpass';
      filter.frequency.setValueAtTime(filterFreq, now);
      filter.frequency.exponentialRampToValueAtTime(120, now + duration);

      const gain = this.ctx.createGain();
      gain.gain.setValueAtTime(vol, now);
      gain.gain.exponentialRampToValueAtTime(0.0001, now + duration);

      src.connect(filter);
      filter.connect(gain);
      gain.connect(this.ctx.destination);
      src.start(now);
      src.stop(now + duration);
    }

    launch() {
      this.tone(220, 660, 0.16, 'triangle', 0.2);
    }

    paddle() {
      this.tone(260, 420, 0.09, 'triangle', 0.22);
      this.tone(130, 95, 0.08, 'sine', 0.18);
    }

    wall() {
      this.tone(190, 150, 0.055, 'sine', 0.14);
    }

    metal() {
      this.tone(880, 620, 0.09, 'square', 0.11);
      this.tone(1320, 980, 0.07, 'triangle', 0.1);
    }

    brick(combo, destroyed) {
      // Musical pentatonic-ish pitch climb as combo increases
      const scale = [261.63, 293.66, 329.63, 392.00, 440.00, 523.25, 587.33, 659.25, 783.99, 880.00, 1046.5, 1174.7, 1318.5];
      const idx = Math.min(combo, scale.length - 1);
      const base = scale[idx];
      if (destroyed) {
        this.tone(base, base * 1.45, 0.13, 'triangle', 0.2);
        this.tone(base * 0.5, base * 0.75, 0.11, 'sine', 0.15);
        this.noise(0.07, 2600, 0.08);
      } else {
        this.tone(base * 0.75, base * 0.65, 0.08, 'sawtooth', 0.12);
      }
    }

    explode() {
      this.noise(0.38, 1600, 0.34);
      this.tone(140, 35, 0.35, 'sawtooth', 0.26);
    }

    powerup() {
      this.tone(440, 554, 0.09, 'triangle', 0.18, 0);
      this.tone(554, 659, 0.09, 'triangle', 0.18, 0.07);
      this.tone(659, 880, 0.16, 'triangle', 0.20, 0.14);
    }

    loseLife() {
      this.tone(340, 110, 0.38, 'sawtooth', 0.22, 0);
      this.tone(240, 70, 0.42, 'square', 0.14, 0.08);
      this.noise(0.28, 800, 0.2);
    }

    levelClear() {
      const notes = [523.25, 659.25, 783.99, 1046.5];
      notes.forEach((n, i) => {
        this.tone(n, n * 1.02, 0.18, 'triangle', 0.2, i * 0.09);
      });
    }
  }

  const sfx = new SoundFX();

  // =========================================================
  // CANVAS & HIGH-DPI SETUP
  // =========================================================
  const canvas = document.getElementById('game');
  const ctx = canvas.getContext('2d');
  const muteBtn = document.getElementById('mute-btn');

  const LOGICAL_W = 800;
  const LOGICAL_H = 600;
  const TOP_BAR_H = 52;

  function setupDPI() {
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    canvas.width = LOGICAL_W * dpr;
    canvas.height = LOGICAL_H * dpr;
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  }
  setupDPI();
  window.addEventListener('resize', setupDPI);

  // =========================================================
  // LEVEL DEFINITIONS
  // Legend:
  //   . = empty
  //   C, P, Y, G, B, O = 1-HP neon bricks (Cyan, Pink, Yellow, Green, Blue, Orange)
  //   2, 3 = 2-HP and 3-HP armored bricks
  //   X = Explosive reactor brick (detonates neighbors!)
  //   # = Indestructible gold bumper brick
  // =========================================================
  const LEVELS = [
    {
      name: 'SECTOR 01 // NEON DAWN',
      speed: 430,
      map: [
        '............',
        '.CCCCCCCCCC.',
        '.PPPPPPPPPP.',
        '.YYYYXXYYYY.',
        '.GGGGGGGGGG.',
        '.BBBBBBBBBB.'
      ]
    },
    {
      name: 'SECTOR 02 // TWIN REACTORS',
      speed: 460,
      map: [
        '.2222..2222.',
        '.2CC2..2PP2.',
        '.2CX2..2XP2.',
        '.2CC2..2PP2.',
        '.YYYYYYYYYY.',
        '..GGGGGGGG..',
        '...BBBBBB...'
      ]
    },
    {
      name: 'SECTOR 03 // PRISM MATRIX',
      speed: 485,
      map: [
        'C.P.Y.G.B.O.',
        '.2.X.2.2.X.2',
        'P.Y.G.B.O.C.',
        '.#.C.P.Y.G.#',
        'Y.G.B.O.C.P.',
        '.2.2.X.X.2.2',
        'G.B.O.C.P.Y.'
      ]
    },
    {
      name: 'SECTOR 04 // CITADEL CORE',
      speed: 510,
      map: [
        '..33333333..',
        '.3CCCCCCCC3.',
        '.3CXX22XXC3.',
        '.3CPPPPPPC3.',
        '.##..YY..##.',
        '..GGGGGGGG..',
        '.BBBBBBBBBB.'
      ]
    },
    {
      name: 'SECTOR 05 // OVERDRIVE SINGULARITY',
      speed: 540,
      map: [
        '3.3.3XX3.3.3',
        '.P2P2PP2P2P.',
        '2CXC.##.CXC2',
        '.YGYGXXGYGY.',
        '2B2B.##.B2B2',
        '.OOOOOOOOOO.',
        'C.C.CCCC.C.C'
      ]
    }
  ];

  const BRICK_PALETTE = {
    C: { main: '#00f3ff', glow: '#00f3ff', score: 50 },
    P: { main: '#ff2a85', glow: '#ff2a85', score: 60 },
    Y: { main: '#ffe600', glow: '#ffe600', score: 70 },
    G: { main: '#00ff88', glow: '#00ff88', score: 60 },
    B: { main: '#6e56ff', glow: '#8b78ff', score: 50 },
    O: { main: '#ff7b00', glow: '#ff7b00', score: 70 },
    2: { main: '#b8c7ff', glow: '#7096ff', score: 120 },
    3: { main: '#ff52e5', glow: '#ff52e5', score: 180 },
    X: { main: '#ff3b30', glow: '#ff6930', score: 150 },
    '#': { main: '#ffd166', glow: '#ffaa00', score: 0 }
  };

  const POWERUP_TYPES = [
    { type: 'wide',  label: 'EXPAND',   color: '#00f3ff' },
    { type: 'multi', label: 'MULTIBALL',color: '#00ff88' },
    { type: 'fire',  label: 'PLASMA',   color: '#ff5500' },
    { type: 'slow',  label: 'CHRONO',   color: '#b967ff' },
    { type: 'life',  label: '+1 LIFE',  color: '#ff2a85' }
  ];

  // =========================================================
  // GAME STATE
  // =========================================================
  let state = 'TITLE'; // TITLE, PLAYING, PAUSED, LEVEL_CLEAR, GAME_OVER
  let score = 0;
  let displayScore = 0;
  let highScore = 0;
  try {
    highScore = parseInt(localStorage.getItem('neon_breakout_hi') || '0', 10) || 0;
  } catch (_) {}

  let lives = 3;
  let levelIndex = 0;
  let loopCycle = 0; // Increments after beating all 5 levels (NG+)
  let combo = 0;
  let maxCombo = 0;

  let shakeTime = 0;
  let shakeMag = 0;
  let gridPulse = 0;
  let levelBannerTimer = 0;

  // Entities
  const paddle = {
    x: LOGICAL_W / 2,
    y: LOGICAL_H - 38,
    w: 112,
    baseW: 112,
    targetW: 112,
    h: 15,
    speed: 680,
    vx: 0,
    recoil: 0,
    flash: 0,
    wideTimer: 0,
    fireTimer: 0
  };

  let balls = [];
  let bricks = [];
  let particles = [];
  let shockwaves = [];
  let popups = [];
  let powerups = [];
  let bgStars = [];

  for (let i = 0; i < 55; i++) {
    bgStars.push({
      x: Math.random() * LOGICAL_W,
      y: TOP_BAR_H + Math.random() * (LOGICAL_H - TOP_BAR_H),
      r: Math.random() * 1.6 + 0.4,
      alpha: Math.random() * 0.5 + 0.15,
      twinkle: Math.random() * Math.PI * 2
    });
  }

  // =========================================================
  // INPUT HANDLING (Keyboard + Mouse + Touch)
  // =========================================================
  const keys = { left: false, right: false };

  function updateCursorVisibility() {
    if (state === 'PLAYING') {
      canvas.classList.remove('show-cursor');
    } else {
      canvas.classList.add('show-cursor');
    }
  }

  window.addEventListener('keydown', (e) => {
    if (['ArrowLeft', 'ArrowRight', 'Space', ' '].includes(e.key)) {
      e.preventDefault();
    }
    sfx.init();

    if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys.left = true;
    if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys.right = true;

    if (e.key === 'm' || e.key === 'M') {
      const muted = sfx.toggleMute();
      muteBtn.textContent = `SOUND: ${muted ? 'OFF' : 'ON'} (M)`;
    }

    if (e.key === 'p' || e.key === 'P' || e.key === 'Escape') {
      if (state === 'PLAYING') {
        state = 'PAUSED';
        updateCursorVisibility();
      } else if (state === 'PAUSED') {
        state = 'PLAYING';
        updateCursorVisibility();
      }
    }

    if (e.key === ' ' || e.key === 'Enter' || e.key === 'ArrowUp') {
      handlePrimaryAction();
    }
  });

  window.addEventListener('keyup', (e) => {
    if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys.left = false;
    if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys.right = false;
  });

  function setPaddleFromClientX(clientX) {
    const rect = canvas.getBoundingClientRect();
    const relX = ((clientX - rect.left) / rect.width) * LOGICAL_W;
    const half = paddle.w / 2;
    const prevX = paddle.x;
    paddle.x = Math.max(half + 8, Math.min(LOGICAL_W - half - 8, relX));
    paddle.vx = (paddle.x - prevX) * 35;
  }

  canvas.addEventListener('mousemove', (e) => {
    if (state === 'PLAYING') {
      setPaddleFromClientX(e.clientX);
    }
  });

  canvas.addEventListener('mousedown', (e) => {
    if (e.button !== 0) return;
    sfx.init();
    handlePrimaryAction();
  });

  canvas.addEventListener('touchstart', (e) => {
    e.preventDefault();
    sfx.init();
    if (e.touches.length > 0) {
      setPaddleFromClientX(e.touches[0].clientX);
    }
    handlePrimaryAction();
  }, { passive: false });

  canvas.addEventListener('touchmove', (e) => {
    e.preventDefault();
    if (state === 'PLAYING' && e.touches.length > 0) {
      setPaddleFromClientX(e.touches[0].clientX);
    }
  }, { passive: false });

  muteBtn.addEventListener('click', () => {
    sfx.init();
    const muted = sfx.toggleMute();
    muteBtn.textContent = `SOUND: ${muted ? 'OFF' : 'ON'} (M)`;
    muteBtn.blur();
  });

  function handlePrimaryAction() {
    if (state === 'TITLE' || state === 'GAME_OVER') {
      startNewGame();
    } else if (state === 'PAUSED') {
      state = 'PLAYING';
      updateCursorVisibility();
    } else if (state === 'LEVEL_CLEAR') {
      nextLevel();
    } else if (state === 'PLAYING') {
      launchStuckBalls();
    }
  }

  // =========================================================
  // GAME FLOW & SPAWNING
  // =========================================================
  function startNewGame() {
    score = 0;
    displayScore = 0;
    lives = 3;
    levelIndex = 0;
    loopCycle = 0;
    combo = 0;
    maxCombo = 0;
    loadLevel(levelIndex);
    state = 'PLAYING';
    updateCursorVisibility();
  }

  function nextLevel() {
    levelIndex++;
    if (levelIndex >= LEVELS.length) {
      levelIndex = 0;
      loopCycle++;
    }
    loadLevel(levelIndex);
    state = 'PLAYING';
    updateCursorVisibility();
  }

  function currentBaseBallSpeed() {
    return LEVELS[levelIndex].speed + loopCycle * 45;
  }

  function loadLevel(idx) {
    bricks = [];
    particles = [];
    shockwaves = [];
    powerups = [];
    popups = [];
    combo = 0;

    paddle.targetW = paddle.baseW;
    paddle.w = paddle.baseW;
    paddle.wideTimer = 0;
    paddle.fireTimer = 0;
    paddle.x = LOGICAL_W / 2;

    const def = LEVELS[idx];
    const cols = 12;
    const padX = 36;
    const gap = 6;
    const totalW = LOGICAL_W - padX * 2;
    const brickW = (totalW - gap * (cols - 1)) / cols;
    const brickH = 24;
    const startY = TOP_BAR_H + 46;

    def.map.forEach((rowStr, r) => {
      for (let c = 0; c < cols; c++) {
        const ch = rowStr[c] || '.';
        if (ch === '.') continue;
        const pal = BRICK_PALETTE[ch] || BRICK_PALETTE.C;
        let hp = 1;
        let type = 'normal';
        if (ch === '2') { hp = 2; type = 'tough'; }
        else if (ch === '3') { hp = 3; type = 'tough'; }
        else if (ch === 'X') { hp = 1; type = 'explosive'; }
        else if (ch === '#') { hp = 999; type = 'indestructible'; }

        bricks.push({
          x: padX + c * (brickW + gap),
          y: startY + r * (brickH + gap),
          w: brickW,
          h: brickH,
          hp,
          maxHp: hp,
          type,
          code: ch,
          color: pal.main,
          glow: pal.glow,
          score: pal.score,
          flash: 0,
          scale: 0,
          spawnDelay: (r * 0.035) + (c * 0.015)
        });
      }
    });

    levelBannerTimer = 2.1;
    resetBallOnPaddle();
  }

  function resetBallOnPaddle() {
    balls = [{
      x: paddle.x,
      y: paddle.y - paddle.h / 2 - 8,
      vx: 0,
      vy: 0,
      r: 7.5,
      speed: currentBaseBallSpeed(),
      stuck: true,
      offsetX: 0,
      trail: []
    }];
  }

  function launchStuckBalls() {
    let launched = false;
    for (const b of balls) {
      if (b.stuck) {
        b.stuck = false;
        const spread = (Math.random() - 0.5) * 0.45;
        b.vx = Math.sin(spread) * b.speed;
        b.vy = -Math.cos(spread) * b.speed;
        launched = true;
      }
    }
    if (launched) sfx.launch();
  }

  // =========================================================
  // JUICE HELPERS (Particles, Shake, Popups, Shockwaves)
  // =========================================================
  function addShake(mag, duration = 0.18) {
    shakeMag = Math.max(shakeMag, mag);
    shakeTime = Math.max(shakeTime, duration);
  }

  function spawnParticles(x, y, color, count = 14, speedMult = 1) {
    for (let i = 0; i < count; i++) {
      const ang = Math.random() * Math.PI * 2;
      const spd = (60 + Math.random() * 230) * speedMult;
      particles.push({
        x,
        y,
        vx: Math.cos(ang) * spd,
        vy: Math.sin(ang) * spd,
        size: 2.2 + Math.random() * 3.8,
        color,
        life: 0.35 + Math.random() * 0.4,
        maxLife: 0.75,
        gravity: 240
      });
    }
  }

  function spawnPopup(x, y, text, color = '#ffffff', scale = 1) {
    popups.push({
      x,
      y,
      vy: -55,
      text,
      color,
      life: 0.85,
      maxLife: 0.85,
      scale
    });
  }

  function spawnShockwave(x, y, color, maxRadius = 110) {
    shockwaves.push({
      x,
      y,
      r: 8,
      maxR: maxRadius,
      color,
      life: 0.35,
      maxLife: 0.35
    });
  }

  function maybeDropPowerup(brick) {
    if (brick.type === 'indestructible') return;
    const chance = brick.type === 'explosive' ? 0.35 : 0.16;
    if (Math.random() > chance) return;

    // Slightly weight multiball/expand/fire over extra life
    const roll = Math.random();
    let def;
    if (roll < 0.28) def = POWERUP_TYPES[0];      // wide
    else if (roll < 0.56) def = POWERUP_TYPES[1]; // multi
    else if (roll < 0.78) def = POWERUP_TYPES[2]; // fire
    else if (roll < 0.92) def = POWERUP_TYPES[3]; // slow
    else def = POWERUP_TYPES[4];                  // life

    powerups.push({
      x: brick.x + brick.w / 2,
      y: brick.y + brick.h / 2,
      vy: 145,
      w: 76,
      h: 20,
      type: def.type,
      label: def.label,
      color: def.color,
      phase: Math.random() * Math.PI * 2
    });
  }

  function applyPowerup(p) {
    sfx.powerup();
    paddle.flash = 0.35;
    spawnParticles(p.x, p.y, p.color, 18, 0.9);
    spawnPopup(paddle.x, paddle.y - 26, p.label, p.color, 1.15);
    addScore(100);

    if (p.type === 'wide') {
      paddle.targetW = 168;
      paddle.wideTimer = 14;
    } else if (p.type === 'fire') {
      paddle.fireTimer = 9;
    } else if (p.type === 'slow') {
      for (const b of balls) {
        b.speed = Math.max(330, b.speed * 0.78);
        const mag = Math.hypot(b.vx, b.vy) || 1;
        b.vx = (b.vx / mag) * b.speed;
        b.vy = (b.vy / mag) * b.speed;
      }
    } else if (p.type === 'life') {
      lives = Math.min(6, lives + 1);
    } else if (p.type === 'multi') {
      // Launch any stuck ball first
      launchStuckBalls();
      const sourceBalls = balls.slice(0, 3); // Cap exponential growth cleanly
      for (const src of sourceBalls) {
        if (balls.length >= 9) break;
        const baseAngle = Math.atan2(src.vy, src.vx);
        [-0.32, 0.32].forEach((offset) => {
          if (balls.length >= 9) return;
          const ang = baseAngle + offset;
          balls.push({
            x: src.x,
            y: src.y,
            vx: Math.cos(ang) * src.speed,
            vy: Math.sin(ang) * src.speed,
            r: src.r,
            speed: src.speed,
            stuck: false,
            offsetX: 0,
            trail: []
          });
        });
      }
    }
  }

  function addScore(pts) {
    score += pts;
    if (score > highScore) {
      highScore = score;
      try {
        localStorage.setItem('neon_breakout_hi', String(highScore));
      } catch (_) {}
    }
  }

  // =========================================================
  // BRICK DAMAGE & CHAIN EXPLOSIONS
  // =========================================================
  function damageBrick(brick, amount = 1, fromExplosion = false) {
    if (brick.dead) return;

    if (brick.type === 'indestructible') {
      brick.flash = 0.2;
      if (!fromExplosion) {
        sfx.metal();
        spawnParticles(brick.x + brick.w / 2, brick.y + brick.h / 2, '#ffd166', 6, 0.6);
      }
      return;
    }

    brick.hp -= amount;
    brick.flash = 0.18;

    const cx = brick.x + brick.w / 2;
    const cy = brick.y + brick.h / 2;

    if (brick.hp <= 0) {
      brick.dead = true;
      combo++;
      if (combo > maxCombo) maxCombo = combo;

      const mult = 1 + Math.floor((combo - 1) / 3) * 0.5;
      const gained = Math.round(brick.score * mult);
      addScore(gained);

      gridPulse = Math.min(1, gridPulse + 0.35);

      const label = combo >= 4 ? `${gained} x${mult.toFixed(1)}` : `+${gained}`;
      spawnPopup(cx, cy, label, brick.color, combo >= 6 ? 1.15 : 0.95);
      spawnParticles(cx, cy, brick.color, 18, 1.05);
      maybeDropPowerup(brick);

      if (brick.type === 'explosive') {
        sfx.explode();
        addShake(11, 0.3);
        spawnShockwave(cx, cy, '#ff5522', 125);
        spawnParticles(cx, cy, '#ffaa00', 28, 1.5);

        // Detonate nearby bricks within blast radius
        for (const other of bricks) {
          if (other.dead || other === brick || other.type === 'indestructible') continue;
          const ox = other.x + other.w / 2;
          const oy = other.y + other.h / 2;
          if (Math.hypot(ox - cx, oy - cy) < 112) {
            damageBrick(other, 3, true);
          }
        }
      } else {
        if (!fromExplosion) sfx.brick(combo - 1, true);
        addShake(Math.min(6, 2.2 + combo * 0.25), 0.12);
        spawnShockwave(cx, cy, brick.color, 46);
      }
    } else {
      if (!fromExplosion) sfx.brick(combo, false);
      addScore(15);
      addShake(2, 0.08);
      spawnParticles(cx, cy, '#ffffff', 7, 0.65);
    }
  }

  // =========================================================
  // PHYSICS & UPDATE LOOP
  // =========================================================
  function enforceMinVerticalAngle(ball) {
    const minVy = ball.speed * 0.24;
    if (Math.abs(ball.vy) < minVy) {
      const sign = ball.vy >= 0 ? 1 : -1;
      ball.vy = sign * minVy;
      const vxSign = ball.vx >= 0 ? 1 : -1;
      ball.vx = vxSign * Math.sqrt(Math.max(0, ball.speed * ball.speed - ball.vy * ball.vy));
    }
  }

  function update(dt) {
    // Smooth rolling score counter
    if (displayScore < score) {
      const diff = score - displayScore;
      displayScore += Math.ceil(diff * Math.min(1, dt * 14));
    } else {
      displayScore = score;
    }

    // Background stars twinkle
    for (const s of bgStars) {
      s.twinkle += dt * 2.5;
    }

    if (gridPulse > 0) gridPulse = Math.max(0, gridPulse - dt * 2.2);
    if (shakeTime > 0) {
      shakeTime -= dt;
      if (shakeTime <= 0) shakeMag = 0;
    }

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

    for (let i = shockwaves.length - 1; i >= 0; i--) {
      const sw = shockwaves[i];
      sw.life -= dt;
      const t = 1 - sw.life / sw.maxLife;
      sw.r = 8 + (sw.maxR - 8) * (1 - Math.pow(1 - t, 3));
      if (sw.life <= 0) shockwaves.splice(i, 1);
    }

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

    if (state !== 'PLAYING') return;

    if (levelBannerTimer > 0) levelBannerTimer = Math.max(0, levelBannerTimer - dt);

    // Brick entry animations & flash decay
    for (const b of bricks) {
      if (b.spawnDelay > 0) {
        b.spawnDelay -= dt;
      } else if (b.scale < 1) {
        b.scale = Math.min(1, b.scale + dt * 6.5);
      }
      if (b.flash > 0) b.flash = Math.max(0, b.flash - dt);
    }

    // Powerup timers on paddle
    if (paddle.wideTimer > 0) {
      paddle.wideTimer -= dt;
      if (paddle.wideTimer <= 0) paddle.targetW = paddle.baseW;
    }
    if (paddle.fireTimer > 0) {
      paddle.fireTimer = Math.max(0, paddle.fireTimer - dt);
    }

    // Smooth paddle width transition & recoil spring
    paddle.w += (paddle.targetW - paddle.w) * Math.min(1, dt * 10);
    paddle.recoil *= Math.pow(0.001, dt);
    if (paddle.flash > 0) paddle.flash = Math.max(0, paddle.flash - dt);

    // Keyboard paddle movement
    const dir = (keys.right ? 1 : 0) - (keys.left ? 1 : 0);
    if (dir !== 0) {
      paddle.vx = dir * paddle.speed;
      paddle.x += paddle.vx * dt;
    } else {
      paddle.vx *= Math.pow(0.01, dt);
    }

    const halfP = paddle.w / 2;
    paddle.x = Math.max(halfP + 8, Math.min(LOGICAL_W - halfP - 8, paddle.x));

    // Falling powerups
    for (let i = powerups.length - 1; i >= 0; i--) {
      const p = powerups[i];
      p.phase += dt * 6;
      p.y += p.vy * dt;

      // Check paddle catch
      if (
        p.x + p.w / 2 >= paddle.x - halfP &&
        p.x - p.w / 2 <= paddle.x + halfP &&
        p.y + p.h / 2 >= paddle.y - paddle.h / 2 &&
        p.y - p.h / 2 <= paddle.y + paddle.h / 2 + 6
      ) {
        applyPowerup(p);
        powerups.splice(i, 1);
        continue;
      }

      if (p.y - p.h > LOGICAL_H) {
        powerups.splice(i, 1);
      }
    }

    // Update balls with physics sub-stepping (prevents tunneling at high speed)
    const isPlasma = paddle.fireTimer > 0;

    for (let bi = balls.length - 1; bi >= 0; bi--) {
      const ball = balls[bi];

      if (ball.stuck) {
        ball.x = paddle.x + ball.offsetX;
        ball.y = paddle.y - paddle.h / 2 - ball.r - 1;
        ball.trail.length = 0;
        continue;
      }

      // Record trail
      ball.trail.push({ x: ball.x, y: ball.y });
      if (ball.trail.length > 11) ball.trail.shift();

      if (isPlasma && Math.random() < 0.55) {
        particles.push({
          x: ball.x + (Math.random() - 0.5) * 6,
          y: ball.y + (Math.random() - 0.5) * 6,
          vx: (Math.random() - 0.5) * 40,
          vy: (Math.random() - 0.5) * 40,
          size: 2.5 + Math.random() * 2.5,
          color: Math.random() < 0.5 ? '#ff6a00' : '#ffe600',
          life: 0.22,
          maxLife: 0.22,
          gravity: 0
        });
      }

      const subSteps = 3;
      const subDt = dt / subSteps;

      for (let s = 0; s < subSteps; s++) {
        ball.x += ball.vx * subDt;
        ball.y += ball.vy * subDt;

        // Left & Right walls
        if (ball.x - ball.r < 6) {
          ball.x = 6 + ball.r;
          ball.vx = Math.abs(ball.vx);
          sfx.wall();
          spawnParticles(ball.x, ball.y, '#00f3ff', 5, 0.5);
        } else if (ball.x + ball.r > LOGICAL_W - 6) {
          ball.x = LOGICAL_W - 6 - ball.r;
          ball.vx = -Math.abs(ball.vx);
          sfx.wall();
          spawnParticles(ball.x, ball.y, '#00f3ff', 5, 0.5);
        }

        // Top ceiling
        if (ball.y - ball.r < TOP_BAR_H + 4) {
          ball.y = TOP_BAR_H + 4 + ball.r;
          ball.vy = Math.abs(ball.vy);
          sfx.wall();
          spawnParticles(ball.x, ball.y, '#00f3ff', 5, 0.5);
        }

        // Paddle collision (only when moving downward)
        const pTop = paddle.y - paddle.h / 2;
        const pBot = paddle.y + paddle.h / 2;
        const pLeft = paddle.x - halfP;
        const pRight = paddle.x + halfP;

        if (
          ball.vy > 0 &&
          ball.x + ball.r >= pLeft &&
          ball.x - ball.r <= pRight &&
          ball.y + ball.r >= pTop &&
          ball.y - ball.r <= pBot
        ) {
          ball.y = pTop - ball.r;

          // Calculate bounce angle (-68 deg to +68 deg) + slight paddle velocity influence
          const hitNorm = Math.max(-1, Math.min(1, (ball.x - paddle.x) / halfP));
          const maxAngle = 1.18; // ~67.5 degrees
          const angle = hitNorm * maxAngle;

          // Gradually ramp ball speed slightly for excitement
          ball.speed = Math.min(760, ball.speed + 4);
          ball.vx = Math.sin(angle) * ball.speed + paddle.vx * 0.08;
          ball.vy = -Math.cos(angle) * ball.speed;

          // Renormalize to ball.speed
          const mag = Math.hypot(ball.vx, ball.vy) || 1;
          ball.vx = (ball.vx / mag) * ball.speed;
          ball.vy = (ball.vy / mag) * ball.speed;
          enforceMinVerticalAngle(ball);

          paddle.recoil = 6.5;
          paddle.flash = 0.16;

          if (combo >= 5) {
            spawnPopup(paddle.x, paddle.y - 22, `${combo} HIT COMBO!`, '#ffe600', 1.05);
          }
          combo = 0; // Reset combo on paddle touch
          sfx.paddle();
          spawnParticles(ball.x, pTop, '#00f3ff', 9, 0.65);
        }

        // Brick collisions
        for (let k = 0; k < bricks.length; k++) {
          const br = bricks[k];
          if (br.dead) continue;

          // Closest point on AABB to circle center
          const closestX = Math.max(br.x, Math.min(ball.x, br.x + br.w));
          const closestY = Math.max(br.y, Math.min(ball.y, br.y + br.h));
          const dx = ball.x - closestX;
          const dy = ball.y - closestY;
          const distSq = dx * dx + dy * dy;

          if (distSq <= ball.r * ball.r) {
            // Plasma ball slices right through breakable bricks without bouncing!
            const sliceThrough = isPlasma && br.type !== 'indestructible' && br.hp <= 2;

            if (!sliceThrough) {
              // Determine collision normal & separate ball cleanly
              const overlapLeft = (ball.x + ball.r) - br.x;
              const overlapRight = (br.x + br.w) - (ball.x - ball.r);
              const overlapTop = (ball.y + ball.r) - br.y;
              const overlapBottom = (br.y + br.h) - (ball.y - ball.r);
              const minOverlapX = Math.min(overlapLeft, overlapRight);
              const minOverlapY = Math.min(overlapTop, overlapBottom);

              if (minOverlapX < minOverlapY) {
                if (overlapLeft < overlapRight) ball.x = br.x - ball.r - 0.5;
                else ball.x = br.x + br.w + ball.r + 0.5;
                ball.vx = -ball.vx;
              } else {
                if (overlapTop < overlapBottom) ball.y = br.y - ball.r - 0.5;
                else ball.y = br.y + br.h + ball.r + 0.5;
                ball.vy = -ball.vy;
              }
              enforceMinVerticalAngle(ball);
            }

            damageBrick(br, isPlasma ? 3 : 1, false);
            break; // Handle one brick per sub-step for crisp reflections
          }
        }
      }

      // Check bottom out-of-bounds
      if (ball.y - ball.r > LOGICAL_H + 12) {
        balls.splice(bi, 1);
      }
    }

    // Remove dead bricks
    for (let i = bricks.length - 1; i >= 0; i--) {
      if (bricks[i].dead) bricks.splice(i, 1);
    }

    // Check if all balls lost
    if (balls.length === 0) {
      lives--;
      combo = 0;
      paddle.fireTimer = 0;
      paddle.wideTimer = 0;
      paddle.targetW = paddle.baseW;
      sfx.loseLife();
      addShake(14, 0.38);
      spawnParticles(paddle.x, LOGICAL_H - 12, '#ff2a85', 30, 1.3);

      if (lives <= 0) {
        state = 'GAME_OVER';
        updateCursorVisibility();
      } else {
        resetBallOnPaddle();
      }
      return;
    }

    // Check level clear (all breakable bricks gone)
    const remainingBreakable = bricks.some(b => b.type !== 'indestructible');
    if (!remainingBreakable) {
      sfx.levelClear();
      addScore(500 + lives * 150);
      state = 'LEVEL_CLEAR';
      updateCursorVisibility();
    }
  }

  // =========================================================
  // RENDERING
  // =========================================================
  function roundRect(x, y, w, h, r) {
    const radius = Math.min(r, w / 2, h / 2);
    ctx.beginPath();
    ctx.moveTo(x + radius, y);
    ctx.arcTo(x + w, y, x + w, y + h, radius);
    ctx.arcTo(x + w, y + h, x, y + h, radius);
    ctx.arcTo(x, y + h, x, y, radius);
    ctx.arcTo(x, y, x + w, y, radius);
    ctx.closePath();
  }

  function drawBackground() {
    ctx.fillStyle = '#060711';
    ctx.fillRect(0, 0, LOGICAL_W, LOGICAL_H);

    // Subtle synthwave perspective/cyber grid
    const gridAlpha = 0.045 + gridPulse * 0.09;
    ctx.strokeStyle = `rgba(0, 243, 255, ${gridAlpha.toFixed(3)})`;
    ctx.lineWidth = 1;
    const step = 40;
    ctx.beginPath();
    for (let x = step; x < LOGICAL_W; x += step) {
      ctx.moveTo(x, TOP_BAR_H);
      ctx.lineTo(x, LOGICAL_H);
    }
    for (let y = TOP_BAR_H + step; y < LOGICAL_H; y += step) {
      ctx.moveTo(0, y);
      ctx.lineTo(LOGICAL_W, y);
    }
    ctx.stroke();

    // Twinkling stars
    for (const s of bgStars) {
      const a = s.alpha * (0.65 + 0.35 * Math.sin(s.twinkle));
      ctx.fillStyle = `rgba(210, 235, 255, ${a.toFixed(3)})`;
      ctx.beginPath();
      ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
      ctx.fill();
    }

    // Arena border glow
    ctx.strokeStyle = 'rgba(0, 243, 255, 0.35)';
    ctx.lineWidth = 2;
    ctx.strokeRect(5, TOP_BAR_H + 3, LOGICAL_W - 10, LOGICAL_H - TOP_BAR_H - 2);
  }

  function drawBricks() {
    for (const b of bricks) {
      if (b.scale <= 0.01) continue;

      ctx.save();
      const cx = b.x + b.w / 2;
      const cy = b.y + b.h / 2;
      if (b.scale < 1) {
        ctx.translate(cx, cy);
        ctx.scale(b.scale, b.scale);
        ctx.translate(-cx, -cy);
      }

      ctx.shadowColor = b.glow;
      ctx.shadowBlur = b.flash > 0 ? 18 : 9;

      const fill = b.flash > 0 ? '#ffffff' : b.color;
      ctx.fillStyle = fill;
      roundRect(b.x, b.y, b.w, b.h, 5);
      ctx.fill();

      ctx.shadowBlur = 0;

      // Top inner highlight bevel
      ctx.fillStyle = 'rgba(255, 255, 255, 0.28)';
      roundRect(b.x + 2, b.y + 2, b.w - 4, 5, 3);
      ctx.fill();

      // Special brick markings
      if (b.type === 'indestructible') {
        ctx.strokeStyle = 'rgba(80, 50, 0, 0.65)';
        ctx.lineWidth = 2;
        roundRect(b.x + 4, b.y + 4, b.w - 8, b.h - 8, 3);
        ctx.stroke();
      } else if (b.type === 'explosive') {
        const pulse = 0.65 + 0.35 * Math.sin(performance.now() * 0.012);
        ctx.fillStyle = `rgba(255, 240, 150, ${pulse.toFixed(2)})`;
        ctx.beginPath();
        ctx.moveTo(cx, b.y + 5);
        ctx.lineTo(cx + 7, cy);
        ctx.lineTo(cx, b.y + b.h - 5);
        ctx.lineTo(cx - 7, cy);
        ctx.closePath();
        ctx.fill();
      } else if (b.maxHp > 1) {
        // Show armor pips / cracks
        ctx.fillStyle = 'rgba(8, 12, 28, 0.65)';
        const pipW = 6;
        const gap = 4;
        const total = b.hp * pipW + (b.hp - 1) * gap;
        let sx = cx - total / 2;
        for (let i = 0; i < b.hp; i++) {
          ctx.fillRect(sx + i * (pipW + gap), cy - 2, pipW, 5);
        }
      }

      ctx.restore();
    }
  }

  function drawPaddle() {
    const px = paddle.x - paddle.w / 2;
    const py = paddle.y - paddle.h / 2 + paddle.recoil;
    const isPlasma = paddle.fireTimer > 0;

    ctx.save();
    const glowCol = isPlasma ? '#ff5e00' : '#00f3ff';
    ctx.shadowColor = glowCol;
    ctx.shadowBlur = paddle.flash > 0 ? 24 : 14;

    ctx.fillStyle = paddle.flash > 0 ? '#ffffff' : (isPlasma ? '#ff7722' : '#00f3ff');
    roundRect(px, py, paddle.w, paddle.h, 8);
    ctx.fill();

    ctx.shadowBlur = 0;
    ctx.fillStyle = '#091024';
    roundRect(px + 12, py + 4, paddle.w - 24, paddle.h - 8, 4);
    ctx.fill();

    // Glowing core bar inside paddle (shows active powerup duration!)
    let barRatio = 1;
    let barColor = '#00f3ff';
    if (isPlasma) {
      barRatio = paddle.fireTimer / 9;
      barColor = '#ffaa00';
    } else if (paddle.wideTimer > 0) {
      barRatio = paddle.wideTimer / 14;
      barColor = '#00ff88';
    }
    ctx.fillStyle = barColor;
    const innerMax = paddle.w - 28;
    roundRect(px + 14, py + 5.5, Math.max(0, innerMax * barRatio), paddle.h - 11, 2);
    ctx.fill();

    ctx.restore();
  }

  function drawBalls() {
    const isPlasma = paddle.fireTimer > 0;
    const mainCol = isPlasma ? '#ff6a00' : '#ffffff';
    const trailCol = isPlasma ? '255, 110, 0' : '0, 243, 255';

    for (const b of balls) {
      // Motion trail
      for (let i = 0; i < b.trail.length; i++) {
        const pt = b.trail[i];
        const t = (i + 1) / b.trail.length;
        ctx.fillStyle = `rgba(${trailCol}, ${(t * 0.42).toFixed(3)})`;
        ctx.beginPath();
        ctx.arc(pt.x, pt.y, b.r * t * 0.92, 0, Math.PI * 2);
        ctx.fill();
      }

      // Launch guide arrow when stuck on paddle
      if (b.stuck && state === 'PLAYING') {
        const bob = Math.sin(performance.now() * 0.008) * 4;
        ctx.strokeStyle = 'rgba(0, 243, 255, 0.55)';
        ctx.lineWidth = 2;
        ctx.setLineDash([4, 4]);
        ctx.beginPath();
        ctx.moveTo(b.x, b.y - 12);
        ctx.lineTo(b.x, b.y - 44 + bob);
        ctx.stroke();
        ctx.setLineDash([]);
      }

      ctx.save();
      ctx.shadowColor = isPlasma ? '#ff5500' : '#00f3ff';
      ctx.shadowBlur = 15;
      ctx.fillStyle = mainCol;
      ctx.beginPath();
      ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();
    }
  }

  function drawPowerups() {
    for (const p of powerups) {
      ctx.save();
      const pulse = 1 + Math.sin(p.phase) * 0.06;
      ctx.translate(p.x, p.y);
      ctx.scale(pulse, pulse);

      ctx.shadowColor = p.color;
      ctx.shadowBlur = 12;
      ctx.fillStyle = '#0b1024';
      ctx.strokeStyle = p.color;
      ctx.lineWidth = 2;
      roundRect(-p.w / 2, -p.h / 2, p.w, p.h, 10);
      ctx.fill();
      ctx.stroke();

      ctx.shadowBlur = 0;
      ctx.fillStyle = p.color;
      ctx.font = 'bold 10px ui-monospace, Menlo, Consolas, monospace';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(p.label, 0, 0.5);
      ctx.restore();
    }
  }

  function drawEffects() {
    // Shockwaves
    for (const sw of shockwaves) {
      const alpha = Math.max(0, sw.life / sw.maxLife);
      ctx.save();
      ctx.strokeStyle = sw.color;
      ctx.globalAlpha = alpha * 0.85;
      ctx.lineWidth = 3 * alpha + 1;
      ctx.beginPath();
      ctx.arc(sw.x, sw.y, sw.r, 0, Math.PI * 2);
      ctx.stroke();
      ctx.restore();
    }

    // Particles
    for (const p of particles) {
      const alpha = Math.max(0, p.life / p.maxLife);
      ctx.save();
      ctx.globalAlpha = alpha;
      ctx.fillStyle = p.color;
      const sz = p.size * (0.4 + 0.6 * alpha);
      ctx.fillRect(p.x - sz / 2, p.y - sz / 2, sz, sz);
      ctx.restore();
    }

    // Floating score/combo popups
    for (const pp of popups) {
      const alpha = Math.min(1, (pp.life / pp.maxLife) * 1.4);
      ctx.save();
      ctx.globalAlpha = alpha;
      ctx.fillStyle = pp.color;
      ctx.font = `bold ${Math.round(13 * pp.scale)}px ui-monospace, Menlo, Consolas, monospace`;
      ctx.textAlign = 'center';
      ctx.shadowColor = '#000';
      ctx.shadowBlur = 6;
      ctx.fillText(pp.text, pp.x, pp.y);
      ctx.restore();
    }
  }

  function drawHUD() {
    ctx.fillStyle = 'rgba(9, 12, 28, 0.92)';
    ctx.fillRect(0, 0, LOGICAL_W, TOP_BAR_H);

    ctx.strokeStyle = 'rgba(0, 243, 255, 0.35)';
    ctx.lineWidth = 1.5;
    ctx.beginPath();
    ctx.moveTo(0, TOP_BAR_H);
    ctx.lineTo(LOGICAL_W, TOP_BAR_H);
    ctx.stroke();

    ctx.textBaseline = 'middle';
    const midY = TOP_BAR_H / 2;

    // Score
    ctx.textAlign = 'left';
    ctx.font = 'bold 11px ui-monospace, Menlo, Consolas, monospace';
    ctx.fillStyle = 'rgba(232, 240, 255, 0.55)';
    ctx.fillText('SCORE', 18, midY - 9);
    ctx.font = 'bold 19px ui-monospace, Menlo, Consolas, monospace';
    ctx.fillStyle = '#00f3ff';
    ctx.fillText(String(displayScore).padStart(7, '0'), 18, midY + 9);

    // High Score
    ctx.font = 'bold 11px ui-monospace, Menlo, Consolas, monospace';
    ctx.fillStyle = 'rgba(232, 240, 255, 0.55)';
    ctx.fillText('BEST', 155, midY - 9);
    ctx.font = 'bold 16px ui-monospace, Menlo, Consolas, monospace';
    ctx.fillStyle = '#ffe600';
    ctx.fillText(String(highScore).padStart(7, '0'), 155, midY + 9);

    // Level & Combo in center
    ctx.textAlign = 'center';
    const lvlNum = loopCycle * LEVELS.length + levelIndex + 1;
    ctx.font = 'bold 14px ui-monospace, Menlo, Consolas, monospace';
    ctx.fillStyle = '#e8f0ff';
    ctx.fillText(`LEVEL ${String(lvlNum).padStart(2, '0')}`, LOGICAL_W / 2, combo > 1 ? midY - 8 : midY);

    if (combo > 1) {
      const mult = 1 + Math.floor((combo - 1) / 3) * 0.5;
      ctx.font = 'bold 12px ui-monospace, Menlo, Consolas, monospace';
      ctx.fillStyle = combo >= 6 ? '#ffe600' : '#00ff88';
      ctx.fillText(`COMBO ${combo}x (${mult.toFixed(1)}x PTS)`, LOGICAL_W / 2, midY + 10);
    }

    // Lives (drawn as glowing energy capsules on right)
    ctx.textAlign = 'right';
    ctx.font = 'bold 11px ui-monospace, Menlo, Consolas, monospace';
    ctx.fillStyle = 'rgba(232, 240, 255, 0.55)';
    ctx.fillText('LIVES', LOGICAL_W - 18, midY - 10);

    for (let i = 0; i < lives; i++) {
      const lx = LOGICAL_W - 22 - i * 24;
      ctx.save();
      ctx.shadowColor = '#ff2a85';
      ctx.shadowBlur = 8;
      ctx.fillStyle = '#ff2a85';
      roundRect(lx - 14, midY + 2, 16, 8, 4);
      ctx.fill();
      ctx.restore();
    }

    // Level intro banner
    if (state === 'PLAYING' && levelBannerTimer > 0) {
      const alpha = Math.min(1, levelBannerTimer * 1.5);
      ctx.save();
      ctx.globalAlpha = alpha;
      ctx.textAlign = 'center';
      ctx.shadowColor = '#00f3ff';
      ctx.shadowBlur = 16;
      ctx.fillStyle = '#ffffff';
      ctx.font = 'bold 24px ui-monospace, Menlo, Consolas, monospace';
      const suffix = loopCycle > 0 ? ` [OVERDRIVE +${loopCycle}]` : '';
      ctx.fillText(LEVELS[levelIndex].name + suffix, LOGICAL_W / 2, LOGICAL_H * 0.62);
      ctx.shadowBlur = 0;
      ctx.fillStyle = '#00f3ff';
      ctx.font = 'bold 13px ui-monospace, Menlo, Consolas, monospace';
      ctx.fillText('CLICK OR PRESS SPACE TO LAUNCH', LOGICAL_W / 2, LOGICAL_H * 0.62 + 28);
      ctx.restore();
    }
  }

  function drawOverlay(title, subtitle, prompt, accent = '#00f3ff') {
    ctx.fillStyle = 'rgba(5, 7, 18, 0.78)';
    ctx.fillRect(0, TOP_BAR_H, LOGICAL_W, LOGICAL_H - TOP_BAR_H);

    const cx = LOGICAL_W / 2;
    const cy = LOGICAL_H / 2 + 10;

    ctx.save();
    ctx.shadowColor = accent;
    ctx.shadowBlur = 25;
    ctx.strokeStyle = accent;
    ctx.lineWidth = 2;
    ctx.fillStyle = 'rgba(13, 17, 38, 0.94)';
    roundRect(cx - 250, cy - 115, 500, 230, 14);
    ctx.fill();
    ctx.stroke();
    ctx.restore();

    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';

    ctx.save();
    ctx.shadowColor = accent;
    ctx.shadowBlur = 18;
    ctx.fillStyle = '#ffffff';
    ctx.font = 'bold 34px ui-monospace, Menlo, Consolas, monospace';
    ctx.fillText(title, cx, cy - 52);
    ctx.restore();

    ctx.fillStyle = 'rgba(232, 240, 255, 0.85)';
    ctx.font = '15px ui-monospace, Menlo, Consolas, monospace';
    subtitle.forEach((line, idx) => {
      ctx.fillText(line, cx, cy - 4 + idx * 24);
    });

    const blink = 0.6 + 0.4 * Math.sin(performance.now() * 0.007);
    ctx.fillStyle = accent;
    ctx.globalAlpha = blink;
    ctx.font = 'bold 16px ui-monospace, Menlo, Consolas, monospace';
    ctx.fillText(prompt, cx, cy + 78);
    ctx.globalAlpha = 1;
  }

  function render() {
    ctx.save();

    // Apply screen shake
    if (shakeTime > 0 && shakeMag > 0) {
      const sx = (Math.random() * 2 - 1) * shakeMag;
      const sy = (Math.random() * 2 - 1) * shakeMag;
      ctx.translate(sx, sy);
    }

    drawBackground();

    if (state !== 'TITLE') {
      drawBricks();
      drawPowerups();
      drawPaddle();
      drawBalls();
      drawEffects();
    } else {
      drawEffects();
    }

    drawHUD();

    if (state === 'TITLE') {
      drawOverlay(
        'NEON BREAKOUT',
        [
          '5 Handcrafted Sectors + Overdrive Loop',
          'Chain brick hits without touching paddle for COMBOS!',
          'Watch out for Red Explosive Cores & Power-up Capsules'
        ],
        '[ CLICK OR PRESS SPACE TO START ]',
        '#00f3ff'
      );
    } else if (state === 'PAUSED') {
      drawOverlay(
        'SYSTEM PAUSED',
        ['Take a breather, pilot.'],
        '[ CLICK OR PRESS P / SPACE TO RESUME ]',
        '#ffe600'
      );
    } else if (state === 'LEVEL_CLEAR') {
      const nextIdx = (levelIndex + 1) % LEVELS.length;
      drawOverlay(
        'SECTOR CLEARED!',
        [
          `Clear Bonus Awarded! Current Score: ${score}`,
          `Next Up: ${LEVELS[nextIdx].name}`
        ],
        '[ CLICK OR PRESS SPACE FOR NEXT SECTOR ]',
        '#00ff88'
      );
    } else if (state === 'GAME_OVER') {
      drawOverlay(
        'GAME OVER',
        [
          `Final Score: ${score}   |   Max Combo: ${maxCombo}x`,
          score >= highScore && score > 0 ? 'NEW HIGH SCORE ACHIEVED!' : `High Score: ${highScore}`
        ],
        '[ CLICK OR PRESS SPACE TO PLAY AGAIN ]',
        '#ff2a85'
      );
    }

    ctx.restore();
  }

  // =========================================================
  // MAIN ANIMATION LOOP
  // =========================================================
  let lastTime = performance.now();

  function frame(now) {
    const dt = Math.min(0.033, Math.max(0.001, (now - lastTime) / 1000));
    lastTime = now;

    update(dt);
    render();
    requestAnimationFrame(frame);
  }

  requestAnimationFrame(frame);
})();
</script>
</body>
</html>

Compare with

Gemini 4 Carbon: other attempts