hypeorcode
Network offOpen alone (new tab)

Run details

Model
deepseek/deepseek-v4.1-flash
Route
Command Code CLI
Reasoning
provider default
Limits
60 min · 943,718 output tokens
Date
2 Oct 2026
Time
4 min 13 s
Size
39 kB
Tokens
711,776 in, 42,806 out
List price
$0.051 ($0.049 in / $0.375 out per M)
Paid
Not billed per run (CLI plan)

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). Save it as index.html in the current directory, then stop. Do not ask questions.

Source

39 kB 1,238 lines Raw

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
<title>NEON BREAKOUT</title>
<style>
  :root { color-scheme: dark; }
  * { box-sizing: border-box; }
  html, body {
    margin: 0; padding: 0; height: 100%; width: 100%;
    overflow: hidden; background: #05060f;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "DejaVu Sans Mono", monospace;
  }
  body {
    display: flex; align-items: center; justify-content: center;
    background: radial-gradient(circle at 50% 0%, #131a3a 0%, #05060f 60%);
    -webkit-tap-highlight-color: transparent;
    user-select: none; -webkit-user-select: none;
  }
  #stage {
    position: relative;
    width: min(100vw, calc(100vh * 900 / 680));
    height: min(100vh, calc(100vw * 680 / 900));
    box-shadow: 0 0 90px rgba(56,189,248,.16), 0 0 200px rgba(168,85,247,.08);
  }
  canvas { display: block; width: 100%; height: 100%; touch-action: none; cursor: none; }
</style>
</head>
<body>
<div id="stage"><canvas id="game"></canvas></div>
<script>
(function () {
  'use strict';

  var TAU = Math.PI * 2;
  var W = 900, H = 680, TOP = 64;
  var STEP = 1 / 120;
  var BASE_SPEED = 400;
  var MAX_SPEED = 830;
  var COLS = 11;
  var SIDE = 40, GAP = 6, BH = 26, ROWGAP = 8, BRICK_TOP = 104;

  var canvas = document.getElementById('game');
  var ctx = canvas.getContext('2d', { alpha: false });
  var DPR = 1;

  var bg = document.createElement('canvas');
  var bgx = bg.getContext('2d');

  function clamp(v, a, b) { return v < a ? a : (v > b ? b : v); }
  function rand(a, b) { return a + Math.random() * (b - a); }

  function rr(c, x, y, w, h, r) {
    r = Math.min(r, w / 2, h / 2);
    c.beginPath();
    c.moveTo(x + r, y);
    c.arcTo(x + w, y, x + w, y + h, r);
    c.arcTo(x + w, y + h, x, y + h, r);
    c.arcTo(x, y + h, x, y, r);
    c.arcTo(x, y, x + w, y, r);
    c.closePath();
  }

  /* ------------------------------------------------------------------ audio */
  var actx = null, master = null, muted = false;

  function audioOn() {
    if (actx) { if (actx.state === 'suspended') actx.resume(); return; }
    try {
      var AC = window.AudioContext || window.webkitAudioContext;
      if (!AC) return;
      actx = new AC();
      master = actx.createGain();
      master.gain.value = 0.3;
      master.connect(actx.destination);
    } catch (e) { actx = null; }
  }

  function tone(f1, f2, dur, type, vol) {
    if (!actx || muted) return;
    var t = actx.currentTime;
    var o = actx.createOscillator();
    var g = actx.createGain();
    o.type = type || 'square';
    o.frequency.setValueAtTime(f1, t);
    if (f2 && f2 !== f1) o.frequency.exponentialRampToValueAtTime(Math.max(20, f2), t + dur);
    g.gain.setValueAtTime(0.0001, t);
    g.gain.exponentialRampToValueAtTime(vol || 0.25, t + 0.008);
    g.gain.exponentialRampToValueAtTime(0.0001, t + dur);
    o.connect(g); g.connect(master);
    o.start(t); o.stop(t + dur + 0.03);
  }

  var SFX = {
    hit: function (row) { tone(320 + row * 55, 480 + row * 55, 0.06, 'square', 0.16); },
    brk: function (row) { tone(520 + row * 60, 180, 0.13, 'triangle', 0.24); },
    paddle: function () { tone(180, 300, 0.07, 'sine', 0.24); },
    wall: function () { tone(140, 110, 0.05, 'sine', 0.13); },
    launch: function () { tone(300, 720, 0.12, 'triangle', 0.2); },
    power: function () { tone(520, 1040, 0.16, 'square', 0.18); setTimeout(function () { tone(780, 1560, 0.14, 'square', 0.14); }, 90); },
    laser: function () { tone(900, 220, 0.06, 'sawtooth', 0.1); },
    lose: function () { tone(320, 60, 0.5, 'sawtooth', 0.24); },
    level: function () {
      var n = [523, 659, 784, 1047];
      for (var i = 0; i < n.length; i++) (function (f, i) { setTimeout(function () { tone(f, f, 0.18, 'triangle', 0.2); }, i * 100); })(n[i], i);
    },
    over: function () {
      var n = [440, 349, 262, 175];
      for (var i = 0; i < n.length; i++) (function (f, i) { setTimeout(function () { tone(f, f * 0.9, 0.28, 'sawtooth', 0.18); }, i * 150); })(n[i], i);
    }
  };

  /* ------------------------------------------------------------------ levels */
  var LEVELS = [
    { name: 'WARM UP', rows: [
      '11111111111',
      '11111111111',
      '1.1.1.1.1.1',
      '11111111111'
    ] },
    { name: 'CHECKERED', rows: [
      '2.2.2.2.2.2',
      '.1.1.1.1.1.',
      '2.2.2.2.2.2',
      '.1.1.1.1.1.',
      '2.2.2.2.2.2'
    ] },
    { name: 'PYRAMID', rows: [
      '.....3.....',
      '....232....',
      '...12221...',
      '..1122211..',
      '.111222111.',
      '11112222111'
    ] },
    { name: 'FORTRESS', rows: [
      '33333333333',
      '3.........3',
      '3.2222222.3',
      '3.2.....2.3',
      '3.2222222.3',
      '3.........3',
      '33333333333'
    ] },
    { name: 'DIAMOND', rows: [
      '.....2.....',
      '....121....',
      '...12121...',
      '..1212121..',
      '.121212121.',
      '..1212121..',
      '...12121...',
      '....121....',
      '.....1.....'
    ] },
    { name: 'OVERDRIVE', rows: [
      '3.3.3.3.3.3',
      '23232323232',
      '3.3.3.3.3.3',
      '23232323232',
      '3.3.3.3.3.3',
      '23232323232'
    ] }
  ];

  var BRICK = {
    1: { a: '#22d3ee', b: '#0e7490', glow: '#22d3ee', pts: 60 },
    2: { a: '#a855f7', b: '#5b21b6', glow: '#a855f7', pts: 130 },
    3: { a: '#fb7185', b: '#9f1239', glow: '#fb7185', pts: 220 }
  };

  var PU = {
    W: { color: '#38bdf8', label: 'W', name: 'WIDE PADDLE' },
    M: { color: '#34d399', label: 'M', name: 'MULTI BALL' },
    S: { color: '#a78bfa', label: 'S', name: 'SLOW MOTION' },
    L: { color: '#fbbf24', label: 'L', name: 'LASER' },
    P: { color: '#f472b6', label: '+', name: 'EXTRA LIFE' }
  };

  /* ------------------------------------------------------------------ state */
  var state = {
    mode: 'menu', prevMode: 'play',
    score: 0, best: 0, lives: 3, level: 0,
    combo: 0, time: 0,
    shake: 0, laserTime: 0, fireCd: 0,
    msg: '', msgTimer: 0, levelTimer: 0
  };

  var paddle = {
    x: W / 2, y: H - 54, w: 122, baseW: 122, wTarget: 122, wTime: 0,
    h: 16, vx: 0, squash: 0, glow: 0
  };

  var balls = [], bricks = [], particles = [], powerups = [], bullets = [], floaters = [];

  var input = { left: false, right: false, pointerX: null, usePointer: false, fire: false };

  /* ------------------------------------------------------------------ setup */
  function buildBG() {
    var g = bgx.createLinearGradient(0, 0, 0, H);
    g.addColorStop(0, '#0b1026');
    g.addColorStop(0.45, '#070a1a');
    g.addColorStop(1, '#03040c');
    bgx.fillStyle = g;
    bgx.fillRect(0, 0, W, H);

    bgx.strokeStyle = 'rgba(96,150,255,0.055)';
    bgx.lineWidth = 1;
    var x, y;
    for (x = 0; x <= W; x += 45) { bgx.beginPath(); bgx.moveTo(x + 0.5, 0); bgx.lineTo(x + 0.5, H); bgx.stroke(); }
    for (y = 0; y <= H; y += 45) { bgx.beginPath(); bgx.moveTo(0, y + 0.5); bgx.lineTo(W, y + 0.5); bgx.stroke(); }

    var v = bgx.createRadialGradient(W / 2, H / 2, H * 0.22, W / 2, H / 2, H * 0.8);
    v.addColorStop(0, 'rgba(0,0,0,0)');
    v.addColorStop(1, 'rgba(0,0,0,0.7)');
    bgx.fillStyle = v;
    bgx.fillRect(0, 0, W, H);
  }

  function resize() {
    DPR = Math.min(window.devicePixelRatio || 1, 2);
    canvas.width = Math.round(W * DPR);
    canvas.height = Math.round(H * DPR);
    bg.width = Math.round(W * DPR);
    bg.height = Math.round(H * DPR);
    bgx.setTransform(DPR, 0, 0, DPR, 0, 0);
    buildBG();
  }

  var glowGrad = null;
  function makeGlowGrad() {
    var g = ctx.createRadialGradient(0, 0, 0, 0, 0, 360);
    g.addColorStop(0, 'rgba(56,189,248,0.20)');
    g.addColorStop(0.45, 'rgba(168,85,247,0.10)');
    g.addColorStop(1, 'rgba(0,0,0,0)');
    return g;
  }

  /* ------------------------------------------------------------------ level */
  function buildLevel(idx) {
    bricks.length = 0;
    var lvl = LEVELS[Math.min(idx, LEVELS.length - 1)];
    var bw = (W - SIDE * 2 - GAP * (COLS - 1)) / COLS;
    for (var r = 0; r < lvl.rows.length; r++) {
      var s = (lvl.rows[r] + '...........').slice(0, COLS);
      for (var c = 0; c < COLS; c++) {
        var ch = s.charAt(c);
        if (ch === '.' || ch === ' ') continue;
        var hp = parseInt(ch, 10) || 1;
        bricks.push({
          x: SIDE + c * (bw + GAP),
          y: BRICK_TOP + r * (BH + ROWGAP),
          w: bw, h: BH,
          hp: hp, maxHp: hp,
          alive: true, row: r, col: c,
          flash: 0, pop: 0
        });
      }
    }
  }

  function makeBall(stuck) {
    return { x: paddle.x, y: paddle.y - 9, r: 8, vx: 0, vy: 0, stuck: !!stuck, offset: 0, trail: [] };
  }

  function resetBall() {
    balls.length = 0;
    balls.push(makeBall(true));
    bullets.length = 0;
  }

  function startLevel(i) {
    state.level = i;
    buildLevel(i);
    paddle.w = paddle.baseW;
    paddle.wTarget = paddle.baseW;
    paddle.wTime = 0;
    paddle.x = W / 2;
    state.laserTime = 0;
    state.combo = 0;
    resetBall();
    state.mode = 'serve';
    state.msg = 'LEVEL ' + (i + 1) + ' \u2014 ' + LEVELS[i].name;
    state.msgTimer = 2.4;
  }

  function startGame() {
    state.score = 0;
    state.lives = 3;
    state.combo = 0;
    particles.length = 0;
    powerups.length = 0;
    floaters.length = 0;
    startLevel(0);
  }

  function victory() {
    state.mode = 'victory';
    state.score += 2500 + state.lives * 500;
    saveBest();
    SFX.level();
    confetti();
  }

  function gameOver() {
    state.mode = 'gameover';
    saveBest();
    SFX.over();
  }

  function saveBest() {
    if (state.score > state.best) {
      state.best = state.score;
      try { localStorage.setItem('neon-breakout-best', String(state.best)); } catch (e) {}
    }
  }

  function comboMult() { return 1 + Math.min(state.combo, 25) * 0.12; }

  /* ------------------------------------------------------------------ fx */
  function addShake(a) { state.shake = Math.min(26, state.shake + a); }

  function spawnParticles(x, y, n, color, opts) {
    opts = opts || {};
    var spd = opts.speed || 200;
    var life = opts.life || 0.6;
    var size = opts.size || 4;
    for (var i = 0; i < n; i++) {
      var a = opts.dir !== undefined ? opts.dir + rand(-0.9, 0.9) : rand(0, TAU);
      var s = rand(spd * 0.25, spd);
      particles.push({
        x: x, y: y,
        vx: Math.cos(a) * s,
        vy: Math.sin(a) * s,
        life: rand(life * 0.55, life),
        max: life,
        size: rand(size * 0.55, size * 1.4),
        color: color,
        grav: opts.grav !== undefined ? opts.grav : 420,
        rot: rand(0, TAU),
        spin: rand(-9, 9)
      });
    }
  }

  function addFloater(x, y, text, color, size) {
    floaters.push({ x: x, y: y, vy: -60, life: 0.9, max: 0.9, text: text, color: color, size: size || 18 });
  }

  function confetti() {
    var cols = ['#38bdf8', '#a855f7', '#fb7185', '#fbbf24', '#34d399'];
    for (var i = 0; i < 160; i++) {
      particles.push({
        x: rand(0, W), y: rand(-120, 120),
        vx: rand(-90, 90), vy: rand(60, 260),
        life: rand(1.6, 3), max: 3,
        size: rand(3, 7),
        color: cols[(Math.random() * cols.length) | 0],
        grav: 60, rot: rand(0, TAU), spin: rand(-12, 12)
      });
    }
  }

  /* ------------------------------------------------------------------ input */
  function pointerToX(clientX) {
    var rect = canvas.getBoundingClientRect();
    return clamp((clientX - rect.left) / rect.width * W, 0, W);
  }

  function primaryAction() {
    audioOn();
    if (state.mode === 'menu') startGame();
    else if (state.mode === 'serve') launch();
    else if (state.mode === 'paused') resume();
    else if (state.mode === 'gameover' || state.mode === 'victory') startGame();
    else if (state.mode === 'levelclear') { state.levelTimer = 0.01; }
  }

  function launch() {
    var sp = BASE_SPEED + state.level * 22;
    for (var i = 0; i < balls.length; i++) {
      var b = balls[i];
      if (b.stuck) {
        b.stuck = false;
        var a = -Math.PI / 2 + rand(-0.4, 0.4);
        b.vx = Math.cos(a) * sp;
        b.vy = Math.sin(a) * sp;
      }
    }
    state.mode = 'play';
    state.msg = '';
    SFX.launch();
  }

  function pause() {
    if (state.mode === 'play' || state.mode === 'serve') {
      state.prevMode = state.mode;
      state.mode = 'paused';
      input.fire = false;
    }
  }
  function resume() {
    if (state.mode === 'paused') state.mode = state.prevMode;
  }

  window.addEventListener('keydown', function (e) {
    var k = e.code;
    if (k === 'ArrowLeft' || k === 'ArrowRight' || k === 'Space' || k === 'ArrowUp' || k === 'ArrowDown') e.preventDefault();
    audioOn();
    if (k === 'ArrowLeft' || k === 'KeyA') { input.left = true; input.usePointer = false; }
    if (k === 'ArrowRight' || k === 'KeyD') { input.right = true; input.usePointer = false; }
    if (k === 'Space') {
      input.fire = true;
      if (state.mode === 'paused') resume();
      else if (state.mode === 'play') { /* fire handled in step */ }
      else primaryAction();
    }
    if (k === 'KeyP' || k === 'Escape') { if (state.mode === 'paused') resume(); else pause(); }
    if (k === 'KeyM') { muted = !muted; }
    if (k === 'KeyR') { if (state.mode === 'gameover' || state.mode === 'victory' || state.mode === 'paused') startGame(); }
  });

  window.addEventListener('keyup', function (e) {
    var k = e.code;
    if (k === 'ArrowLeft' || k === 'KeyA') input.left = false;
    if (k === 'ArrowRight' || k === 'KeyD') input.right = false;
    if (k === 'Space') input.fire = false;
  });

  canvas.addEventListener('mousemove', function (e) {
    input.pointerX = pointerToX(e.clientX);
    input.usePointer = true;
  });

  canvas.addEventListener('mousedown', function (e) {
    e.preventDefault();
    input.pointerX = pointerToX(e.clientX);
    input.usePointer = true;
    audioOn();
    if (state.mode === 'play') input.fire = true;
    else primaryAction();
  });
  window.addEventListener('mouseup', function () { input.fire = false; });

  canvas.addEventListener('touchstart', function (e) {
    e.preventDefault();
    var t = e.changedTouches[0];
    input.pointerX = pointerToX(t.clientX);
    input.usePointer = true;
    audioOn();
    if (state.mode === 'play') input.fire = true;
    else primaryAction();
  }, { passive: false });

  canvas.addEventListener('touchmove', function (e) {
    e.preventDefault();
    var t = e.changedTouches[0];
    input.pointerX = pointerToX(t.clientX);
    input.usePointer = true;
  }, { passive: false });

  canvas.addEventListener('touchend', function () { input.fire = false; });

  window.addEventListener('blur', function () {
    input.left = input.right = input.fire = false;
    if (state.mode === 'play' || state.mode === 'serve') pause();
  });

  window.addEventListener('resize', resize);

  /* ------------------------------------------------------------------ brick hit */
  function damageBrick(br, dmg) {
    br.hp -= dmg;
    br.flash = 1;
    br.pop = 1;
    if (br.hp > 0) {
      spawnParticles(br.x + br.w / 2, br.y + br.h / 2, 5, BRICK[br.maxHp].a, { speed: 170, life: 0.4, size: 3 });
      addShake(1.6);
      SFX.hit(br.row);
      state.score += 10;
      return;
    }
    br.alive = false;
    state.combo++;
    var mult = comboMult();
    var pts = Math.round(BRICK[br.maxHp].pts * mult);
    state.score += pts;
    addFloater(br.x + br.w / 2, br.y + br.h / 2, '+' + pts, BRICK[br.maxHp].a, 16);
    spawnParticles(br.x + br.w / 2, br.y + br.h / 2, 18, BRICK[br.maxHp].a, { speed: 300, life: 0.75, size: 5 });
    spawnParticles(br.x + br.w / 2, br.y + br.h / 2, 6, '#ffffff', { speed: 220, life: 0.4, size: 3 });
    addShake(3.6);
    SFX.brk(br.row);
    maybeDrop(br);
    checkLevelClear();
  }

  function checkLevelClear() {
    for (var i = 0; i < bricks.length; i++) if (bricks[i].alive) return;
    if (state.mode === 'levelclear' || state.mode === 'gameover' || state.mode === 'victory') return;
    state.mode = 'levelclear';
    state.levelTimer = 2.4;
    input.fire = false;
    var bonus = 500 + state.level * 250 + state.lives * 100;
    state.score += bonus;
    addFloater(W / 2, H / 2 + 40, '+' + bonus + ' CLEAR BONUS', '#fde047', 24);
    addShake(10);
    SFX.level();
    for (var j = 0; j < bricks.length; j++) {
      spawnParticles(bricks[j].x + bricks[j].w / 2, bricks[j].y + bricks[j].h / 2, 3, '#fbbf24', { speed: 260, life: 0.9, size: 4 });
    }
  }

  function maybeDrop(br) {
    if (Math.random() > 0.17) return;
    var pool = ['W', 'W', 'M', 'M', 'S', 'S', 'L', 'P'];
    var t = pool[(Math.random() * pool.length) | 0];
    powerups.push({
      x: br.x + br.w / 2, y: br.y + br.h / 2,
      vy: 130, rot: 0, t: t, size: 34, life: 14
    });
  }

  function applyPowerup(t) {
    var info = PU[t];
    addFloater(paddle.x, paddle.y - 30, info.name, info.color, 20);
    SFX.power();
    spawnParticles(paddle.x, paddle.y, 22, info.color, { speed: 280, life: 0.7, size: 4 });
    addShake(5);
    if (t === 'W') {
      paddle.wTarget = Math.min(230, paddle.wTarget + 48);
      paddle.wTime = 16;
    } else if (t === 'M') {
      multiball();
    } else if (t === 'S') {
      for (var i = 0; i < balls.length; i++) {
        var b = balls[i];
        if (b.stuck) continue;
        var sp = Math.hypot(b.vx, b.vy);
        var ns = Math.max(250, sp * 0.76);
        var k = ns / (sp || 1);
        b.vx *= k; b.vy *= k;
      }
    } else if (t === 'L') {
      state.laserTime = 12;
    } else if (t === 'P') {
      state.lives = Math.min(6, state.lives + 1);
    }
  }

  function multiball() {
    var src = balls.slice(0, 3);
    for (var i = 0; i < src.length; i++) {
      var b = src[i];
      var baseA = b.stuck ? -Math.PI / 2 : Math.atan2(b.vy, b.vx);
      var sp = b.stuck ? (BASE_SPEED + state.level * 22) : Math.hypot(b.vx, b.vy);
      for (var k = 0; k < 2; k++) {
        if (balls.length >= 6) break;
        var a = baseA + (k === 0 ? 0.45 : -0.45);
        balls.push({
          x: b.x, y: b.y, r: b.r,
          vx: Math.cos(a) * sp, vy: Math.sin(a) * sp,
          stuck: false, offset: 0, trail: []
        });
      }
    }
    if (balls.length > 1) { /* keep serving if still stuck elsewhere */ }
  }

  function loseLife() {
    state.lives--;
    state.combo = 0;
    state.shake = 22;
    state.laserTime = 0;
    SFX.lose();
    spawnParticles(clamp(paddle.x, 20, W - 20), H - 30, 30, '#fb7185', { speed: 340, life: 0.9, size: 5, grav: 260 });
    if (state.lives <= 0) { gameOver(); return; }
    resetBall();
    state.mode = 'serve';
    state.msg = '';
  }

  /* ------------------------------------------------------------------ physics */
  function updatePaddle(h) {
    var move = (input.right ? 1 : 0) - (input.left ? 1 : 0);
    var prevX = paddle.x;
    if (move !== 0) {
      paddle.x += move * 760 * h;
    } else if (input.usePointer && input.pointerX != null) {
      var d = input.pointerX - paddle.x;
      paddle.x += d * Math.min(1, 17 * h);
    }
    var lim = W - paddle.w / 2 - 2;
    paddle.x = clamp(paddle.x, paddle.w / 2 + 2, lim);
    paddle.vx = (paddle.x - prevX) / h;

    if (paddle.wTime > 0) {
      paddle.wTime -= h;
      if (paddle.wTime <= 0) { paddle.wTime = 0; paddle.wTarget = paddle.baseW; }
    }
    paddle.w += (paddle.wTarget - paddle.w) * Math.min(1, 14 * h);

    if (state.laserTime > 0) {
      state.laserTime = Math.max(0, state.laserTime - h);
      state.fireCd -= h;
      if (input.fire && state.fireCd <= 0) {
        state.fireCd = 0.17;
        bullets.push({ x: paddle.x - paddle.w / 2 + 9, y: paddle.y - 4, vy: -980 });
        bullets.push({ x: paddle.x + paddle.w / 2 - 9, y: paddle.y - 4, vy: -980 });
        SFX.laser();
      }
    } else {
      state.fireCd = 0;
    }
  }

  function hitWall() { SFX.wall(); addShake(0.8); }

  function collidePaddle(b) {
    if (b.vy <= 0) return;
    var px = paddle.x - paddle.w / 2, pw = paddle.w;
    if (b.y + b.r < paddle.y || b.y - b.r > paddle.y + paddle.h) return;
    if (b.x + b.r < px || b.x - b.r > px + pw) return;

    var rel = clamp((b.x - paddle.x) / (pw / 2), -1, 1);
    var ang = rel * (Math.PI / 3);
    var sp = clamp(Math.hypot(b.vx, b.vy), 240, MAX_SPEED);
    var vx = Math.sin(ang) * sp + paddle.vx * 0.16;
    var minVx = sp * 0.11;
    if (vx > -minVx && vx < minVx) {
      var sgn = b.vx !== 0 ? (b.vx > 0 ? 1 : -1) : (Math.random() < 0.5 ? -1 : 1);
      vx = sgn * minVx;
    }
    vx = clamp(vx, -0.92 * sp, 0.92 * sp);
    b.vx = vx;
    b.vy = -Math.sqrt(Math.max(1, sp * sp - vx * vx));
    b.y = paddle.y - b.r - 0.5;

    paddle.squash = 1;
    paddle.glow = 1;
    if (state.combo > 2) addFloater(paddle.x, paddle.y - 34, 'COMBO LOST', 'rgba(255,255,255,0.5)', 14);
    state.combo = 0;
    spawnParticles(b.x, paddle.y, 9, '#67e8f9', { speed: 240, life: 0.4, size: 4, grav: 300, dir: -Math.PI / 2 });
    addShake(2.4);
    SFX.paddle();
  }

  function collideBricks(b) {
    for (var i = 0; i < bricks.length; i++) {
      var br = bricks[i];
      if (!br.alive) continue;
      var nx = clamp(b.x, br.x, br.x + br.w);
      var ny = clamp(b.y, br.y, br.y + br.h);
      var dx = b.x - nx, dy = b.y - ny;
      if (dx * dx + dy * dy > b.r * b.r) continue;

      var bcx = br.x + br.w / 2, bcy = br.y + br.h / 2;
      var ox = (br.w / 2 + b.r) - Math.abs(b.x - bcx);
      var oy = (br.h / 2 + b.r) - Math.abs(b.y - bcy);
      if (ox < oy) {
        b.x += (b.x < bcx ? -ox : ox);
        b.vx = (b.x < bcx ? -Math.abs(b.vx) : Math.abs(b.vx));
      } else {
        b.y += (b.y < bcy ? -oy : oy);
        b.vy = (b.y < bcy ? -Math.abs(b.vy) : Math.abs(b.vy));
      }
      damageBrick(br, 1);
      return;
    }
  }

  function stepPhysics(h) {
    updatePaddle(h);

    for (var i = balls.length - 1; i >= 0; i--) {
      var b = balls[i];
      if (b.stuck) {
        b.x = paddle.x + b.offset;
        b.y = paddle.y - b.r - 1;
        continue;
      }
      b.x += b.vx * h;
      b.y += b.vy * h;

      b.trail.push({ x: b.x, y: b.y });
      if (b.trail.length > 14) b.trail.shift();

      if (b.x - b.r < 0) { b.x = b.r; b.vx = Math.abs(b.vx); hitWall(); }
      else if (b.x + b.r > W) { b.x = W - b.r; b.vx = -Math.abs(b.vx); hitWall(); }
      if (b.y - b.r < TOP) { b.y = TOP + b.r; b.vy = Math.abs(b.vy); hitWall(); }

      collidePaddle(b);
      collideBricks(b);

      if (b.y - b.r > H) { balls.splice(i, 1); }
    }

    // powerups
    for (var p = powerups.length - 1; p >= 0; p--) {
      var pu = powerups[p];
      pu.y += pu.vy * h;
      pu.rot += h * 2.2;
      pu.life -= h;
      var px = paddle.x - paddle.w / 2, pw = paddle.w;
      if (pu.y + pu.size / 2 > paddle.y && pu.y - pu.size / 2 < paddle.y + paddle.h &&
          pu.x + pu.size / 2 > px && pu.x - pu.size / 2 < px + pw) {
        applyPowerup(pu.t);
        powerups.splice(p, 1);
        continue;
      }
      if (pu.y - pu.size > H || pu.life <= 0) powerups.splice(p, 1);
    }

    // bullets
    for (var k = bullets.length - 1; k >= 0; k--) {
      var bl = bullets[k];
      bl.y += bl.vy * h;
      if (bl.y < TOP) { bullets.splice(k, 1); continue; }
      var hit = false;
      for (var m = 0; m < bricks.length; m++) {
        var bb = bricks[m];
        if (!bb.alive) continue;
        if (bl.x > bb.x && bl.x < bb.x + bb.w && bl.y < bb.y + bb.h && bl.y > bb.y) {
          damageBrick(bb, 1);
          hit = true;
          break;
        }
      }
      if (hit) bullets.splice(k, 1);
    }

    if (state.mode === 'play' && balls.length === 0) loseLife();
  }

  /* ------------------------------------------------------------------ update */
  function update(dt) {
    state.time += dt;

    paddle.squash = Math.max(0, paddle.squash - dt * 4.2);
    paddle.glow = Math.max(0, paddle.glow - dt * 3);
    state.shake = Math.max(0, state.shake - dt * 46);
    if (state.msgTimer > 0) state.msgTimer -= dt;

    var i, k;
    for (i = particles.length - 1; i >= 0; i--) {
      var pt = particles[i];
      pt.life -= dt;
      if (pt.life <= 0) { particles.splice(i, 1); continue; }
      var damp = Math.max(0, 1 - 2.0 * dt);
      pt.vx *= damp; pt.vy *= damp;
      pt.vy += pt.grav * dt;
      pt.x += pt.vx * dt;
      pt.y += pt.vy * dt;
      pt.rot += pt.spin * dt;
    }

    for (i = floaters.length - 1; i >= 0; i--) {
      var f = floaters[i];
      f.life -= dt;
      if (f.life <= 0) { floaters.splice(i, 1); continue; }
      f.y += f.vy * dt;
      f.vy *= Math.max(0, 1 - 1.6 * dt);
    }

    for (i = 0; i < bricks.length; i++) {
      var br = bricks[i];
      if (br.flash > 0) br.flash = Math.max(0, br.flash - dt * 6);
      if (br.pop > 0) br.pop = Math.max(0, br.pop - dt * 5);
    }

    if (state.mode === 'menu') {
      if (Math.random() < dt * 8) {
        spawnParticles(rand(0, W), TOP + rand(0, 300), 1, ['#38bdf8', '#a855f7', '#fb7185'][(Math.random() * 3) | 0],
          { speed: 40, life: 2.2, size: 3, grav: -14 });
      }
      return;
    }

    if (state.mode === 'serve' || state.mode === 'play' || state.mode === 'levelclear') {
      var acc = state.acc || 0;
      acc += dt;
      var guard = 0;
      while (acc >= STEP && guard < 10) { stepPhysics(STEP); acc -= STEP; guard++; }
      if (acc > STEP) acc = 0;
      state.acc = acc;
    }

    if (state.mode === 'levelclear') {
      state.levelTimer -= dt;
      // keep balls alive while transitioning
      if (state.levelTimer <= 0) {
        var next = state.level + 1;
        if (next >= LEVELS.length) victory();
        else startLevel(next);
      }
    }
  }

  /* ------------------------------------------------------------------ render */
  function drawGlow() {
    if (!glowGrad) glowGrad = makeGlowGrad();
    var t = state.time;
    ctx.save();
    ctx.globalCompositeOperation = 'lighter';
    ctx.globalAlpha = 0.9;
    ctx.translate(W / 2 + Math.sin(t * 0.34) * 150, TOP + 170 + Math.cos(t * 0.26) * 70);
    ctx.fillStyle = glowGrad;
    ctx.beginPath(); ctx.arc(0, 0, 360, 0, TAU); ctx.fill();
    ctx.restore();
  }

  function drawWalls() {
    var pulse = 0.55 + Math.sin(state.time * 3) * 0.1 + Math.min(state.combo, 12) * 0.03;
    ctx.save();
    ctx.shadowColor = '#38bdf8';
    ctx.shadowBlur = 16;
    ctx.fillStyle = 'rgba(56,189,248,' + pulse.toFixed(3) + ')';
    ctx.fillRect(0, TOP - 3, W, 3);
    ctx.fillRect(0, TOP, 3, H - TOP);
    ctx.fillRect(W - 3, TOP, 3, H - TOP);
    ctx.restore();

    var off = (state.time * 60) % 24;
    ctx.save();
    ctx.globalAlpha = 0.22;
    ctx.fillStyle = '#67e8f9';
    for (var x = -off; x < W; x += 24) ctx.fillRect(x, TOP - 6, 10, 2);
    ctx.restore();
  }

  function drawBricks() {
    for (var i = 0; i < bricks.length; i++) {
      var b = bricks[i];
      if (!b.alive) continue;
      var c = BRICK[b.hp] || BRICK[1];
      var e = b.pop * 3;
      var x = b.x - e, y = b.y - e, w = b.w + e * 2, h = b.h + e * 2;

      ctx.save();
      ctx.shadowColor = c.glow;
      ctx.shadowBlur = 10 + b.flash * 18;
      rr(ctx, x, y, w, h, 6);
      ctx.fillStyle = c.b;
      ctx.fill();
      ctx.restore();

      ctx.save();
      rr(ctx, x + 2, y + 2, w - 4, h - 6, 4);
      ctx.fillStyle = c.a;
      ctx.fill();
      ctx.restore();

      ctx.save();
      rr(ctx, x + 4, y + 3, w - 8, (h - 6) * 0.35, 3);
      ctx.fillStyle = 'rgba(255,255,255,0.25)';
      ctx.fill();
      ctx.restore();

      if (b.hp < b.maxHp) {
        ctx.save();
        ctx.globalAlpha = 0.4;
        ctx.strokeStyle = '#04060f';
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.moveTo(x + w * 0.25, y + 2);
        ctx.lineTo(x + w * 0.42, y + h * 0.5);
        ctx.lineTo(x + w * 0.3, y + h - 2);
        ctx.moveTo(x + w * 0.68, y + 2);
        ctx.lineTo(x + w * 0.55, y + h * 0.45);
        ctx.lineTo(x + w * 0.75, y + h - 2);
        ctx.stroke();
        ctx.restore();
      }

      if (b.flash > 0) {
        ctx.save();
        ctx.globalAlpha = b.flash * 0.7;
        rr(ctx, x, y, w, h, 6);
        ctx.fillStyle = '#ffffff';
        ctx.fill();
        ctx.restore();
      }
    }
  }

  function drawPaddle() {
    var sq = paddle.squash;
    var w = paddle.w * (1 + sq * 0.08);
    var h = paddle.h * (1 - sq * 0.3);
    var x = paddle.x - w / 2;
    var y = paddle.y + (paddle.h - h) / 2;

    ctx.save();
    ctx.shadowColor = '#22d3ee';
    ctx.shadowBlur = 18 + paddle.glow * 26;
    rr(ctx, x, y, w, h, h / 2);
    var g = ctx.createLinearGradient(x, y, x, y + h);
    g.addColorStop(0, '#a5f3fc');
    g.addColorStop(0.5, '#22d3ee');
    g.addColorStop(1, '#0e7490');
    ctx.fillStyle = g;
    ctx.fill();
    ctx.restore();

    ctx.save();
    ctx.globalAlpha = 0.55;
    rr(ctx, x + 5, y + 2, w - 10, h * 0.32, h * 0.2);
    ctx.fillStyle = '#ffffff';
    ctx.fill();
    ctx.restore();

    if (state.laserTime > 0) {
      var blink = state.laserTime < 3 && Math.floor(state.time * 8) % 2 === 0;
      ctx.save();
      ctx.shadowColor = '#fbbf24';
      ctx.shadowBlur = 14;
      ctx.fillStyle = blink ? 'rgba(251,191,36,0.4)' : '#fbbf24';
      rr(ctx, x + 1, y - 9, 6, 10, 2); ctx.fill();
      rr(ctx, x + w - 7, y - 9, 6, 10, 2); ctx.fill();
      ctx.restore();
    }
  }

  function drawBalls() {
    ctx.save();
    ctx.globalCompositeOperation = 'lighter';
    for (var i = 0; i < balls.length; i++) {
      var b = balls[i];
      for (var j = 0; j < b.trail.length; j++) {
        var p = b.trail[j];
        var f = (j + 1) / b.trail.length;
        ctx.globalAlpha = f * f * 0.4;
        ctx.fillStyle = '#38bdf8';
        ctx.beginPath();
        ctx.arc(p.x, p.y, b.r * f * 0.95, 0, TAU);
        ctx.fill();
      }
    }
    ctx.restore();

    for (var k = 0; k < balls.length; k++) {
      var ball = balls[k];
      ctx.save();
      ctx.shadowColor = '#67e8f9';
      ctx.shadowBlur = 24;
      ctx.fillStyle = '#e0fbff';
      ctx.beginPath();
      ctx.arc(ball.x, ball.y, ball.r, 0, TAU);
      ctx.fill();
      ctx.restore();
      ctx.beginPath();
      ctx.arc(ball.x - ball.r * 0.2, ball.y - ball.r * 0.2, ball.r * 0.45, 0, TAU);
      ctx.fillStyle = '#ffffff';
      ctx.fill();
    }
  }

  function drawPowerups() {
    for (var i = 0; i < powerups.length; i++) {
      var pu = powerups[i];
      var info = PU[pu.t];
      var s = pu.size * (1 + Math.sin(state.time * 7 + i) * 0.06);
      var blink = pu.life < 3 && Math.floor(state.time * 8) % 2 === 0;
      ctx.save();
      ctx.globalAlpha = blink ? 0.35 : 1;
      ctx.translate(pu.x, pu.y);
      ctx.rotate(Math.sin(state.time * 2.4 + i) * 0.22);
      ctx.shadowColor = info.color;
      ctx.shadowBlur = 20;
      rr(ctx, -s / 2, -s / 2, s, s, 8);
      ctx.fillStyle = '#05070f';
      ctx.fill();
      ctx.strokeStyle = info.color;
      ctx.lineWidth = 3;
      ctx.stroke();
      ctx.shadowBlur = 0;
      ctx.fillStyle = info.color;
      ctx.font = '900 ' + Math.round(s * 0.6) + 'px ui-monospace, Menlo, monospace';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(info.label, 0, 1);
      ctx.restore();
    }
  }

  function drawBullets() {
    ctx.save();
    ctx.shadowColor = '#fbbf24';
    ctx.shadowBlur = 14;
    ctx.fillStyle = '#fde68a';
    for (var i = 0; i < bullets.length; i++) {
      var bl = bullets[i];
      rr(ctx, bl.x - 2, bl.y - 14, 4, 18, 2);
      ctx.fill();
    }
    ctx.restore();
  }

  function drawParticles() {
    ctx.save();
    ctx.globalCompositeOperation = 'lighter';
    for (var i = 0; i < particles.length; i++) {
      var p = particles[i];
      var a = Math.max(0, p.life / p.max);
      ctx.globalAlpha = a;
      ctx.fillStyle = p.color;
      ctx.save();
      ctx.translate(p.x, p.y);
      ctx.rotate(p.rot);
      ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size * 0.7);
      ctx.restore();
    }
    ctx.restore();
  }

  function drawFloaters() {
    ctx.save();
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    for (var i = 0; i < floaters.length; i++) {
      var f = floaters[i];
      var a = Math.max(0, f.life / f.max);
      ctx.globalAlpha = a;
      ctx.font = '900 ' + f.size + 'px ui-monospace, Menlo, monospace';
      ctx.shadowColor = f.color;
      ctx.shadowBlur = 12;
      ctx.fillStyle = f.color;
      ctx.fillText(f.text, f.x, f.y);
    }
    ctx.restore();
  }

  function drawHUD() {
    ctx.save();
    ctx.textBaseline = 'middle';

    ctx.textAlign = 'left';
    ctx.font = '700 11px ui-monospace, Menlo, monospace';
    ctx.fillStyle = 'rgba(148,197,255,0.6)';
    ctx.fillText('S C O R E', 26, 20);
    ctx.font = '900 28px ui-monospace, Menlo, monospace';
    ctx.fillStyle = '#e0f2ff';
    ctx.shadowColor = '#38bdf8';
    ctx.shadowBlur = 14;
    ctx.fillText(String(state.score), 26, 44);
    ctx.shadowBlur = 0;

    ctx.textAlign = 'center';
    ctx.font = '700 11px ui-monospace, Menlo, monospace';
    ctx.fillStyle = 'rgba(148,197,255,0.6)';
    ctx.fillText('LEVEL', W / 2, 18);
    ctx.font = '900 20px ui-monospace, Menlo, monospace';
    ctx.fillStyle = '#c4b5fd';
    ctx.shadowColor = '#a855f7';
    ctx.shadowBlur = 12;
    ctx.fillText((state.level + 1) + '  ' + LEVELS[Math.min(state.level, LEVELS.length - 1)].name, W / 2, 42);
    ctx.shadowBlur = 0;

    var lx = W - 26;
    ctx.textAlign = 'right';
    ctx.font = '700 11px ui-monospace, Menlo, monospace';
    ctx.fillStyle = 'rgba(148,197,255,0.6)';
    ctx.fillText('L I V E S', lx, 20);
    for (var i = 0; i < state.lives; i++) {
      ctx.save();
      ctx.shadowColor = '#22d3ee';
      ctx.shadowBlur = 10;
      rr(ctx, lx - 18 - i * 24, 36, 18, 8, 4);
      ctx.fillStyle = '#22d3ee';
      ctx.fill();
      ctx.restore();
    }

    if (state.combo >= 3) {
      var m = comboMult();
      ctx.textAlign = 'center';
      ctx.font = '900 18px ui-monospace, Menlo, monospace';
      ctx.globalAlpha = 0.85;
      ctx.fillStyle = '#fde047';
      ctx.shadowColor = '#fbbf24';
      ctx.shadowBlur = 14;
      ctx.fillText('COMBO  x' + m.toFixed(1), W / 2, H - 22);
      ctx.shadowBlur = 0;
    }

    if (state.laserTime > 0) {
      ctx.globalAlpha = 1;
      ctx.textAlign = 'left';
      ctx.font = '700 12px ui-monospace, Menlo, monospace';
      ctx.fillStyle = '#fbbf24';
      ctx.fillText('LASER ' + state.laserTime.toFixed(1) + 's', 26, H - 22);
    }

    if (muted) {
      ctx.textAlign = 'right';
      ctx.font = '700 12px ui-monospace, Menlo, monospace';
      ctx.fillStyle = 'rgba(148,197,255,0.5)';
      ctx.fillText('MUTED', W - 26, H - 22);
    }
    ctx.restore();
  }

  function drawBanner() {
    if (state.msgTimer <= 0 || !state.msg) return;
    var a = Math.min(1, state.msgTimer / 0.6);
    ctx.save();
    ctx.globalAlpha = a * 0.95;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.font = '900 34px ui-monospace, Menlo, monospace';
    ctx.shadowColor = '#38bdf8';
    ctx.shadowBlur = 24;
    ctx.fillStyle = '#e0f2ff';
    ctx.fillText(state.msg, W / 2, H / 2 - 40);
    ctx.restore();
  }

  function panel(title, titleColor, lines) {
    ctx.save();
    ctx.fillStyle = 'rgba(4,6,15,0.72)';
    ctx.fillRect(0, TOP, W, H - TOP);

    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.font = '900 62px ui-monospace, Menlo, monospace';
    ctx.shadowColor = titleColor;
    ctx.shadowBlur = 30;
    ctx.fillStyle = titleColor;
    ctx.fillText(title, W / 2, H / 2 - 110);
    ctx.shadowBlur = 0;

    for (var i = 0; i < lines.length; i++) {
      var ln = lines[i];
      ctx.font = (ln.big ? '900 ' : '600 ') + (ln.size || 18) + 'px ui-monospace, Menlo, monospace';
      ctx.fillStyle = ln.color || 'rgba(200,225,255,0.8)';
      if (ln.glow) { ctx.shadowColor = ln.color; ctx.shadowBlur = 14; }
      ctx.globalAlpha = ln.alpha != null ? ln.alpha : 1;
      ctx.fillText(ln.text, W / 2, H / 2 - 40 + i * 34);
      ctx.shadowBlur = 0;
      ctx.globalAlpha = 1;
    }
    ctx.restore();
  }

  function drawOverlay() {
    var pulse = 0.65 + Math.sin(state.time * 4) * 0.35;
    if (state.mode === 'menu') {
      panel('NEON BREAKOUT', '#38bdf8', [
        { text: 'PRESS SPACE  /  CLICK TO PLAY', color: 'rgba(224,242,255,' + pulse.toFixed(2) + ')', size: 20 },
        { text: '', size: 10 },
        { text: '\u2190 \u2192  or  A D  or  MOUSE  /  TOUCH  \u2014  MOVE', size: 15, color: 'rgba(148,197,255,0.7)' },
        { text: 'SPACE  \u2014  LAUNCH & FIRE LASERS', size: 15, color: 'rgba(148,197,255,0.7)' },
        { text: 'P  \u2014  PAUSE      M  \u2014  MUTE', size: 15, color: 'rgba(148,197,255,0.7)' },
        { text: 'CATCH DROPS:  W WIDE   M MULTI   S SLOW   L LASER   + LIFE', size: 14, color: 'rgba(251,191,36,0.85)' },
        { text: 'BEST  ' + state.best, size: 16, color: 'rgba(253,224,71,0.9)' }
      ]);
    } else if (state.mode === 'paused') {
      panel('PAUSED', '#a855f7', [
        { text: 'PRESS P OR SPACE TO RESUME', color: 'rgba(224,242,255,' + pulse.toFixed(2) + ')', size: 18 },
        { text: 'R \u2014  RESTART', size: 15, color: 'rgba(148,197,255,0.7)' }
      ]);
    } else if (state.mode === 'levelclear') {
      ctx.save();
      ctx.globalAlpha = 0.35;
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.font = '900 44px ui-monospace, Menlo, monospace';
      ctx.fillStyle = '#fde047';
      ctx.shadowColor = '#fbbf24';
      ctx.shadowBlur = 26;
      ctx.fillText('LEVEL CLEAR', W / 2, H / 2 + 80);
      ctx.restore();
    } else if (state.mode === 'gameover') {
      panel('GAME OVER', '#fb7185', [
        { text: 'SCORE   ' + state.score, color: '#e0f2ff', size: 24 },
        { text: 'BEST    ' + state.best, color: '#fde047', size: 18 },
        { text: 'REACHED LEVEL ' + (state.level + 1), size: 15, color: 'rgba(148,197,255,0.7)' },
        { text: 'PRESS SPACE / R / CLICK TO PLAY AGAIN', color: 'rgba(224,242,255,' + pulse.toFixed(2) + ')', size: 18 }
      ]);
    } else if (state.mode === 'victory') {
      panel('YOU WIN!', '#34d399', [
        { text: 'ALL 6 LEVELS CLEARED', color: '#a7f3d0', size: 20 },
        { text: 'FINAL SCORE   ' + state.score, color: '#fde047', size: 26 },
        { text: 'PRESS SPACE / R / CLICK TO PLAY AGAIN', color: 'rgba(224,242,255,' + pulse.toFixed(2) + ')', size: 18 }
      ]);
    }
  }

  function render() {
    ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
    ctx.drawImage(bg, 0, 0, W, H);
    drawGlow();

    ctx.save();
    if (state.shake > 0.15) {
      ctx.translate((Math.random() - 0.5) * state.shake, (Math.random() - 0.5) * state.shake);
    }
    drawBricks();
    drawPowerups();
    drawBullets();
    drawParticles();
    drawBalls();
    drawPaddle();
    drawFloaters();
    ctx.restore();

    drawWalls();
    drawHUD();
    drawBanner();
    drawOverlay();
  }

  /* ------------------------------------------------------------------ loop */
  var last = 0;
  function loop(now) {
    if (!last) last = now;
    var dt = (now - last) / 1000;
    last = now;
    if (dt > 0.25) dt = 0.25;
    if (dt < 0) dt = 0;
    update(dt);
    render();
    requestAnimationFrame(loop);
  }

  /* ------------------------------------------------------------------ boot */
  try {
    var saved = localStorage.getItem('neon-breakout-best');
    if (saved) state.best = parseInt(saved, 10) || 0;
  } catch (e) {}

  resize();
  buildLevel(0);
  requestAnimationFrame(loop);
})();
</script>
</body>
</html>

Compare with

DeepSeek V4.1 Flash: other attempts