<script lang="ts">
  import { onMount } from "svelte";
  import * as THREE from "three";
  import { pressureColorPalettes } from "$lib/config/color-map";
  import type { LocaleCode, PressureColorMapPreset } from "$lib/types/hud";

  type CornerId = "tl" | "tr" | "bl" | "br";

  interface CornerMap {
    tl: number;
    tr: number;
    bl: number;
    br: number;
  }

  interface Brick {
    mesh: THREE.Mesh<THREE.BoxGeometry, THREE.MeshStandardMaterial>;
    alive: boolean;
    kind: "normal" | "bomb";
    left: number;
    right: number;
    top: number;
    bottom: number;
  }

  type GameState = "ready" | "running" | "paused" | "draft" | "over";

  type UpgradeId = "overdrive" | "demolition" | "surge" | "stabilizer";

  interface Copy {
    title: string;
    start: string;
    restart: string;
    pause: string;
    resume: string;
    running: string;
    paused: string;
    over: string;
    score: string;
    combo: string;
    lives: string;
    level: string;
    bricks: string;
    chase: string;
    pausedOverlay: string;
    ready: string;
    draft: string;
    startOverlayTitle: string;
    startOverlayBody: string;
    startOverlayHint: string;
    overHint: string;
    draftOverlayTitle: string;
    draftOverlayHint: string;
    skills: string;
  }

  interface UpgradeCardCopy {
    id: UpgradeId;
    title: string;
    body: string;
  }

  export let locale: LocaleCode = "zh-CN";
  export let pressureMatrix: number[] | null = null;
  export let matrixRows = 12;
  export let matrixCols = 7;
  export let rangeMin = 0;
  export let rangeMax = 16000;
  export let colorMapPreset: PressureColorMapPreset = "emerald";

  const FIELD_HALF_W = 46;
  const FIELD_HALF_H = 62;
  const PADDLE_Y = -53;
  const PADDLE_W = 16;
  const PADDLE_H = 2.6;
  const PADDLE_SPEED = 74;
  const BALL_RADIUS = 1.45;
  const BASE_BALL_SPEED = 58;
  const MAX_BALL_SPEED = 106;
  const BRICK_COLS = 12;
  const BRICK_ROWS = 7;
  const BRICK_W = 6.2;
  const BRICK_H = 2.9;
  const BRICK_GAP_X = 0.8;
  const BRICK_GAP_Y = 0.9;
  const BRICK_TOP = 44;
  const START_DELAY_MS = 520;
  const PAUSE_COOLDOWN_MS = 640;
  const UPPER_STALL_THRESHOLD_MS = 1700;
  const UPPER_STALL_Y = 14;
  const ANTI_STALL_COOLDOWN_MS = 420;
  const MAX_BRICK_HITS_WITHOUT_PADDLE = 5;
  const DRAFT_EVERY_BRICKS = 14;
  const BASE_BOMB_CHANCE = 0.08;
  const BASE_BOMB_RADIUS = 8.8;
  const RUN_TARGET_LEVEL = 4;

  const legacyCopyByLocale = {
    "zh-CN": {
      title: "NEON BREAKOUT",
      start: "开始",
      restart: "重开",
      pause: "暂停",
      resume: "继续",
      running: "运行中",
      paused: "已暂停",
      over: "结束",
      score: "得分",
      combo: "连击",
      lives: "生命",
      level: "关卡",
      bricks: "剩余",
      chase: "追击",
      pausedOverlay: "已暂停 / 顶部双角施压继续"
    },
    "en-US": {
      title: "NEON BREAKOUT",
      start: "Start",
      restart: "Restart",
      pause: "Pause",
      resume: "Resume",
      running: "Running",
      paused: "Paused",
      over: "Game Over",
      score: "Score",
      combo: "Combo",
      lives: "Lives",
      level: "Level",
      bricks: "Bricks",
      chase: "Chase",
      pausedOverlay: "Paused / Top corners to resume"
    }
  };

  const copyByLocale: Record<LocaleCode, Copy> = {
    "zh-CN": {
      title: "NEON BREAKOUT",
      start: "开始",
      restart: "重开",
      pause: "暂停",
      resume: "继续",
      running: "进行中",
      paused: "已暂停",
      over: "本局结束",
      score: "得分",
      combo: "连击",
      lives: "生命",
      level: "阶段",
      bricks: "剩余",
      chase: "模式",
      pausedOverlay: "已暂停 / 按上继续",
      ready: "待开始",
      draft: "选技能",
      startOverlayTitle: "按上开始",
      startOverlayBody: "左右移动挡板，别让球掉出场地。打掉砖块会触发更快的肉鸽强化，炸弹砖能连锁清场。",
      startOverlayHint: "顶部双角施压或键盘 ArrowUp 开局；对局中同样用上方控制暂停 / 继续。",
      overHint: "按上重新开一局，或者直接点重开。",
      draftOverlayTitle: "选择强化",
      draftOverlayHint: "这一局会越打越快。点一个技能，或者按数字 1 / 2 / 3 选择。",
      skills: "技能"
    },
    "en-US": {
      title: "NEON BREAKOUT",
      start: "Start",
      restart: "Restart",
      pause: "Pause",
      resume: "Resume",
      running: "Running",
      paused: "Paused",
      over: "Game Over",
      score: "Score",
      combo: "Combo",
      lives: "Lives",
      level: "Level",
      bricks: "Bricks",
      chase: "Mode",
      pausedOverlay: "Paused / Press up to resume",
      ready: "Ready",
      draft: "Draft",
      startOverlayTitle: "Press Up To Start",
      startOverlayBody: "Move the paddle left and right, keep the ball alive, and chain through bomb bricks to clear each run fast.",
      startOverlayHint: "Use top pressure or ArrowUp to start. The same up control pauses and resumes mid-run.",
      overHint: "Press up to jump right back in, or click restart.",
      draftOverlayTitle: "Choose An Upgrade",
      draftOverlayHint: "Each run speeds up quickly. Pick one perk, or press 1 / 2 / 3.",
      skills: "Perks"
    }
  };

  const upgradeCopyByLocale: Record<LocaleCode, Record<UpgradeId, UpgradeCardCopy>> = {
    "zh-CN": {
      overdrive: {
        id: "overdrive",
        title: "超频发球",
        body: "发球更快，球速上限更高，整局节奏立刻提起来。"
      },
      demolition: {
        id: "demolition",
        title: "爆破砖块",
        body: "炸弹砖出现更多，爆炸半径更大，更容易一口气清一片。"
      },
      surge: {
        id: "surge",
        title: "速度滚雪球",
        body: "每打掉一块砖，球速涨得更多，越打越疯。"
      },
      stabilizer: {
        id: "stabilizer",
        title: "宽幅挡板",
        body: "挡板更宽并回复 1 点生命，容错更稳，不容易断节奏。"
      }
    },
    "en-US": {
      overdrive: {
        id: "overdrive",
        title: "Overdrive",
        body: "Faster launches and a higher speed cap so the whole run ramps up immediately."
      },
      demolition: {
        id: "demolition",
        title: "Demolition",
        body: "More bomb bricks and a larger blast radius for faster board clears."
      },
      surge: {
        id: "surge",
        title: "Surge",
        body: "Each brick hit adds more speed, turning every rally into a snowball."
      },
      stabilizer: {
        id: "stabilizer",
        title: "Stabilizer",
        body: "A wider paddle plus one life to keep the run alive while the pace rises."
      }
    }
  };
  void legacyCopyByLocale;

  let hostEl: HTMLElement | undefined;
  let canvasEl: HTMLCanvasElement | undefined;
  let rafId: number | null = null;
  let renderer: THREE.WebGLRenderer | null = null;
  let scene: THREE.Scene | null = null;
  let camera: THREE.OrthographicCamera | null = null;
  let paddle: THREE.Mesh<THREE.BoxGeometry, THREE.MeshStandardMaterial> | null = null;
  let ball: THREE.Mesh<THREE.SphereGeometry, THREE.MeshStandardMaterial> | null = null;
  let brickGroup: THREE.Group | null = null;
  let borderLine: THREE.LineLoop | null = null;
  let bgPlane: THREE.Mesh<THREE.PlaneGeometry, THREE.MeshBasicMaterial> | null = null;
  let ambientLight: THREE.AmbientLight | null = null;
  let keyLight: THREE.PointLight | null = null;
  let rimLight: THREE.PointLight | null = null;
  let flashSprite: THREE.Sprite | null = null;
  let flashLife = 0;
  let flashMaxLife = 0.2;

  let keyLeft = false;
  let keyRight = false;
  let ballLaunched = false;
  let nextLaunchAt = 0;
  let paddleX = 0;
  const ballPos = new THREE.Vector2(0, 0);
  const ballVel = new THREE.Vector2(0, 0);
  let bricks: Brick[] = [];

  let gameState: GameState = "ready";
  let score = 0;
  let combo = 0;
  let lives = 3;
  let level = 1;
  let bricksLeft = 0;
  let lastFrameTs = 0;
  let prevPauseGesture = false;
  let pauseGestureLockUntil = 0;
  let themedBgTexture: THREE.CanvasTexture | null = null;
  let lastPaddleContactAt = 0;
  let bricksSincePaddle = 0;
  let lastAntiStallAt = 0;
  let paddleWidth = PADDLE_W;
  let launchSpeedBonus = 0;
  let maxSpeedBonus = 0;
  let brickSpeedGain = 0.014;
  let bombBrickChance = BASE_BOMB_CHANCE;
  let bombExplosionRadius = BASE_BOMB_RADIUS;
  let bricksUntilDraft = DRAFT_EVERY_BRICKS;
  let activeDraftOptions: UpgradeCardCopy[] = [];
  let selectedUpgrades: UpgradeId[] = [];

  let cornerForce: CornerMap = { tl: 0, tr: 0, bl: 0, br: 0 };
  $: cornerForce = readCorners(pressureMatrix, matrixRows, matrixCols);
  $: themePalette = pressureColorPalettes[colorMapPreset] ?? pressureColorPalettes.emerald;
  $: ui = copyByLocale[locale];
  $: leftForce = cornerForce.tl + cornerForce.bl;
  $: rightForce = cornerForce.tr + cornerForce.br;
  $: topForce = cornerForce.tl + cornerForce.tr;
  $: pauseGestureThreshold = Math.max(420, Math.round(Math.max(1000, rangeMax - rangeMin) * 0.07));
  $: upgradeCopy = upgradeCopyByLocale[locale] ?? upgradeCopyByLocale["en-US"];
  $: statusText =
    gameState === "running"
      ? ui.running
      : gameState === "paused"
        ? ui.paused
        : gameState === "draft"
          ? ui.draft
          : gameState === "over"
            ? ui.over
            : ui.ready;

  $: if (scene && renderer) {
    applyTheme();
  }

  function rgbTripletToColor(rgbTriplet: string): THREE.Color {
    return new THREE.Color(`rgb(${rgbTriplet.replace(/\s+/g, ", ")})`);
  }

  function rgbaFromTriplet(rgbTriplet: string, alpha: number): string {
    return `rgba(${rgbTriplet.replace(/\s+/g, ",")},${alpha})`;
  }

  function themedBrickPalette(): string[] {
    const { rangeStops } = themePalette;
    return [rangeStops[1], rangeStops[2], rangeStops[3], rangeStops[4], rangeStops[5], rangeStops[2], rangeStops[3]];
  }

  function applyTheme(): void {
    if (!scene || !renderer) return;
    const uiTheme = themePalette.uiTheme;
    scene.background = new THREE.Color(uiTheme.bg30);

    ambientLight?.color.copy(rgbTripletToColor(uiTheme.textMainRgb));
    keyLight?.color.copy(rgbTripletToColor(uiTheme.glowAltRgb));
    rimLight?.color.copy(rgbTripletToColor(uiTheme.glowRgb));

    const borderMaterial = borderLine?.material as THREE.LineBasicMaterial | undefined;
    if (borderMaterial) {
      borderMaterial.color.copy(rgbTripletToColor(uiTheme.glowRgb));
      borderMaterial.opacity = 0.48;
      borderMaterial.needsUpdate = true;
    }

    if (paddle) {
      paddle.material.color.copy(new THREE.Color(themePalette.rangeStops[2]));
      paddle.material.emissive.copy(new THREE.Color(themePalette.rangeStops[2]));
      paddle.material.emissiveIntensity = 0.72;
      paddle.material.needsUpdate = true;
    }

    if (ball) {
      ball.material.color.copy(new THREE.Color(themePalette.rangeStops[0]));
      ball.material.emissive.copy(new THREE.Color(themePalette.rangeStops[3]));
      ball.material.emissiveIntensity = 0.92;
      ball.material.needsUpdate = true;
    }

    if (bgPlane) {
      const material = bgPlane.material;
      themedBgTexture?.dispose();
      themedBgTexture = createGridTexture(themePalette);
      material.map = themedBgTexture;
      material.opacity = 0.78;
      material.needsUpdate = true;
    }

    const colors = themedBrickPalette();
    for (let index = 0; index < bricks.length; index += 1) {
      const brick = bricks[index];
      const color = brick.kind === "bomb" ? "#ff9d5c" : colors[index % colors.length] ?? "#7ad0ff";
      brick.mesh.material.color.set(color);
      brick.mesh.material.emissive.set(color);
      brick.mesh.material.emissiveIntensity = brick.kind === "bomb" ? 0.96 : 0.7;
      brick.mesh.material.needsUpdate = true;
    }
  }

  onMount(() => {
    if (!hostEl || !canvasEl) return;
    const uiTheme = themePalette.uiTheme;
    const paddleColor = themePalette.rangeStops[2];
    const ballColor = themePalette.rangeStops[0];
    const ballGlow = themePalette.rangeStops[3];

    const nextScene = new THREE.Scene();
    nextScene.background = new THREE.Color(uiTheme.bg30);
    scene = nextScene;

    const nextCamera = new THREE.OrthographicCamera(-60, 60, 70, -70, 0.1, 200);
    nextCamera.position.set(0, 0, 54);
    nextCamera.lookAt(0, 0, 0);
    camera = nextCamera;

    const nextRenderer = new THREE.WebGLRenderer({
      canvas: canvasEl,
      antialias: true,
      alpha: true,
      powerPreference: "high-performance"
    });
    renderer = nextRenderer;
    nextRenderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));

    const ambient = new THREE.AmbientLight(rgbTripletToColor(uiTheme.textMainRgb), 0.44);
    const key = new THREE.PointLight(rgbTripletToColor(uiTheme.glowAltRgb), 1.2, 180, 1.8);
    key.position.set(0, 20, 38);
    const rim = new THREE.PointLight(rgbTripletToColor(uiTheme.glowRgb), 0.65, 160, 1.7);
    rim.position.set(-22, -14, 26);
    nextScene.add(ambient, key, rim);
    ambientLight = ambient;
    keyLight = key;
    rimLight = rim;

    const bgTexture = createGridTexture(themePalette);
    themedBgTexture = bgTexture;
    const bg = new THREE.Mesh(
      new THREE.PlaneGeometry(160, 170),
      new THREE.MeshBasicMaterial({
        map: bgTexture,
        transparent: true,
        opacity: 0.78,
        depthWrite: false
      })
    );
    bg.position.set(0, 0, -12);
    nextScene.add(bg);
    bgPlane = bg;

    const border = new THREE.LineLoop(
      new THREE.BufferGeometry().setFromPoints([
        new THREE.Vector3(-FIELD_HALF_W, -FIELD_HALF_H, 0),
        new THREE.Vector3(FIELD_HALF_W, -FIELD_HALF_H, 0),
        new THREE.Vector3(FIELD_HALF_W, FIELD_HALF_H, 0),
        new THREE.Vector3(-FIELD_HALF_W, FIELD_HALF_H, 0)
      ]),
      new THREE.LineBasicMaterial({ color: rgbTripletToColor(uiTheme.glowRgb), transparent: true, opacity: 0.46 })
    );
    nextScene.add(border);
    borderLine = border;

    const nextBrickGroup = new THREE.Group();
    brickGroup = nextBrickGroup;
    nextScene.add(nextBrickGroup);

    const paddleMesh = new THREE.Mesh(
      new THREE.BoxGeometry(PADDLE_W, PADDLE_H, 2.1),
      new THREE.MeshStandardMaterial({
        color: paddleColor,
        emissive: paddleColor,
        emissiveIntensity: 0.72,
        roughness: 0.34,
        metalness: 0.18
      })
    );
    paddleMesh.position.set(0, PADDLE_Y, 0.4);
    nextScene.add(paddleMesh);
    paddle = paddleMesh;

    const ballMesh = new THREE.Mesh(
      new THREE.SphereGeometry(BALL_RADIUS, 22, 16),
      new THREE.MeshStandardMaterial({
        color: ballColor,
        emissive: ballGlow,
        emissiveIntensity: 0.88,
        roughness: 0.2,
        metalness: 0.04
      })
    );
    nextScene.add(ballMesh);
    ball = ballMesh;

    const flashTexture = createFlashTexture(themePalette);
    const flash = new THREE.Sprite(
      new THREE.SpriteMaterial({
        map: flashTexture,
        color: "#74ffff",
        transparent: true,
        blending: THREE.AdditiveBlending,
        opacity: 0,
        depthWrite: false
      })
    );
    flash.scale.set(1, 1, 1);
    flash.visible = false;
    nextScene.add(flash);
    flashSprite = flash;

    const onKeyDown = (event: KeyboardEvent) => {
      if (event.code === "ArrowLeft" || event.code === "KeyA") keyLeft = true;
      if (event.code === "ArrowRight" || event.code === "KeyD") keyRight = true;
      if (event.code === "ArrowUp" || event.code === "KeyW") handleUpControl();
      if (event.code === "Space" && gameState !== "running" && gameState !== "draft") restartGame();
      if (gameState === "draft") {
        const option1 = activeDraftOptions[0];
        const option2 = activeDraftOptions[1];
        const option3 = activeDraftOptions[2];
        if (event.code === "Digit1" && option1) applyUpgrade(option1.id);
        if (event.code === "Digit2" && option2) applyUpgrade(option2.id);
        if (event.code === "Digit3" && option3) applyUpgrade(option3.id);
      }
    };
    const onKeyUp = (event: KeyboardEvent) => {
      if (event.code === "ArrowLeft" || event.code === "KeyA") keyLeft = false;
      if (event.code === "ArrowRight" || event.code === "KeyD") keyRight = false;
    };

    window.addEventListener("keydown", onKeyDown);
    window.addEventListener("keyup", onKeyUp);

    const resize = () => {
      if (!hostEl || !renderer || !camera) return;
      const width = hostEl.clientWidth;
      const height = hostEl.clientHeight;
      if (width <= 0 || height <= 0) return;
      renderer.setSize(width, height, false);
      const aspect = width / Math.max(1, height);
      const halfH = 70;
      const halfW = halfH * aspect;
      camera.left = -halfW;
      camera.right = halfW;
      camera.top = halfH;
      camera.bottom = -halfH;
      camera.updateProjectionMatrix();
    };

    const observer = new ResizeObserver(() => resize());
    observer.observe(hostEl);
    resize();

    enterReadyState();
    lastFrameTs = performance.now();
    const loop = (ts: number) => {
      const dt = Math.min((ts - lastFrameTs) / 1000, 0.034);
      lastFrameTs = ts;
      update(dt, ts);
      renderer?.render(nextScene, nextCamera);
      rafId = window.requestAnimationFrame(loop);
    };
    rafId = window.requestAnimationFrame(loop);

    return () => {
      if (rafId != null) window.cancelAnimationFrame(rafId);
      window.removeEventListener("keydown", onKeyDown);
      window.removeEventListener("keyup", onKeyUp);
      observer.disconnect();
      disposeBricks();
      border.geometry.dispose();
      (border.material as THREE.LineBasicMaterial).dispose();
      bg.geometry.dispose();
      (bg.material as THREE.MeshBasicMaterial).map?.dispose();
      (bg.material as THREE.MeshBasicMaterial).dispose();
      paddleMesh.geometry.dispose();
      paddleMesh.material.dispose();
      ballMesh.geometry.dispose();
      ballMesh.material.dispose();
      (flash.material as THREE.SpriteMaterial).map?.dispose();
      (flash.material as THREE.SpriteMaterial).dispose();
      renderer?.dispose();
    };
  });

  function createGridTexture(palette = themePalette): THREE.CanvasTexture {
    const canvas = document.createElement("canvas");
    canvas.width = 512;
    canvas.height = 512;
    const ctx = canvas.getContext("2d");
    if (!ctx) return new THREE.CanvasTexture(canvas);

    const { uiTheme } = palette;
    const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
    gradient.addColorStop(0, uiTheme.bg20);
    gradient.addColorStop(0.5, uiTheme.bg10);
    gradient.addColorStop(1, uiTheme.bg30);
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    ctx.strokeStyle = rgbaFromTriplet(uiTheme.glowRgb, 0.18);
    ctx.lineWidth = 1;
    for (let y = 0; y < canvas.height; y += 24) {
      ctx.beginPath();
      ctx.moveTo(0, y);
      ctx.lineTo(canvas.width, y);
      ctx.stroke();
    }
    ctx.strokeStyle = rgbaFromTriplet(uiTheme.glowAltRgb, 0.08);
    for (let x = 0; x < canvas.width; x += 24) {
      ctx.beginPath();
      ctx.moveTo(x, 0);
      ctx.lineTo(x, canvas.height);
      ctx.stroke();
    }

    const texture = new THREE.CanvasTexture(canvas);
    texture.colorSpace = THREE.SRGBColorSpace;
    texture.needsUpdate = true;
    return texture;
  }

  function createFlashTexture(palette = themePalette): THREE.CanvasTexture {
    const canvas = document.createElement("canvas");
    canvas.width = 128;
    canvas.height = 128;
    const ctx = canvas.getContext("2d");
    if (!ctx) return new THREE.CanvasTexture(canvas);
    const { uiTheme } = palette;
    const gradient = ctx.createRadialGradient(64, 64, 8, 64, 64, 64);
    gradient.addColorStop(0, "rgba(255,255,255,1)");
    gradient.addColorStop(0.25, rgbaFromTriplet(uiTheme.glowAltRgb, 0.86));
      gradient.addColorStop(1, rgbaFromTriplet(uiTheme.glowRgb, 0));
      ctx.fillStyle = gradient;
      ctx.fillRect(0, 0, 128, 128);
    const texture = new THREE.CanvasTexture(canvas);
    texture.colorSpace = THREE.SRGBColorSpace;
    texture.needsUpdate = true;
    return texture;
  }

  function rand(min: number, max: number): number {
    return Math.random() * (max - min) + min;
  }

  function disposeBricks(): void {
    if (!brickGroup) return;
    for (const brick of bricks) {
      brick.mesh.geometry.dispose();
      brick.mesh.material.dispose();
      brickGroup.remove(brick.mesh);
    }
    bricks = [];
    bricksLeft = 0;
  }

  function buildBricks(): void {
    if (!brickGroup) return;
    disposeBricks();
    const palette = themedBrickPalette();
    const totalWidth = BRICK_COLS * BRICK_W + (BRICK_COLS - 1) * BRICK_GAP_X;
    const startX = -totalWidth / 2 + BRICK_W / 2;
    for (let row = 0; row < BRICK_ROWS; row += 1) {
      for (let col = 0; col < BRICK_COLS; col += 1) {
        const kind: Brick["kind"] = Math.random() < bombBrickChance ? "bomb" : "normal";
        const color = kind === "bomb" ? "#ff9d5c" : palette[(row + col + level) % palette.length] ?? "#7ad0ff";
        const material = new THREE.MeshStandardMaterial({
          color,
          emissive: color,
          emissiveIntensity: kind === "bomb" ? 0.96 : 0.7,
          roughness: 0.35,
          metalness: 0.15
        });
        const mesh = new THREE.Mesh(new THREE.BoxGeometry(BRICK_W, BRICK_H, 2.4), material);
        const x = startX + col * (BRICK_W + BRICK_GAP_X);
        const y = BRICK_TOP - row * (BRICK_H + BRICK_GAP_Y);
        mesh.position.set(x, y, 0.2);
        brickGroup.add(mesh);
        bricks.push({
          mesh,
          alive: true,
          kind,
          left: x - BRICK_W / 2,
          right: x + BRICK_W / 2,
          top: y + BRICK_H / 2,
          bottom: y - BRICK_H / 2
        });
      }
    }
    bricksLeft = bricks.length;
  }

  function launchBall(nowMs: number): void {
    const speed = Math.min(MAX_BALL_SPEED + maxSpeedBonus, BASE_BALL_SPEED + launchSpeedBonus + (level - 1) * 4.8);
    const directionX = rand(-0.72, 0.72);
    ballVel.set(directionX, 1).normalize().multiplyScalar(speed);
    ballLaunched = true;
    nextLaunchAt = nowMs;
    lastPaddleContactAt = nowMs;
    lastAntiStallAt = nowMs;
    bricksSincePaddle = 0;
  }

  function prepareBall(nowMs: number, delayMs: number): void {
    ballLaunched = false;
    ballVel.set(0, 0);
    ballPos.set(paddleX, PADDLE_Y + PADDLE_H * 0.5 + BALL_RADIUS + 0.4);
    nextLaunchAt = nowMs + delayMs;
    bricksSincePaddle = 0;
  }

  function syncPaddleVisual(): void {
    if (!paddle) return;
    paddle.scale.x = paddleWidth / PADDLE_W;
  }

  function resetRunState(): void {
    score = 0;
    combo = 0;
    lives = 3;
    level = 1;
    launchSpeedBonus = 0;
    maxSpeedBonus = 0;
    brickSpeedGain = 0.014;
    bombBrickChance = BASE_BOMB_CHANCE;
    bombExplosionRadius = BASE_BOMB_RADIUS;
    bricksUntilDraft = DRAFT_EVERY_BRICKS;
    activeDraftOptions = [];
    selectedUpgrades = [];
    paddleWidth = PADDLE_W;
    syncPaddleVisual();
  }

  function enterReadyState(): void {
    if (!paddle || !ball) return;
    resetRunState();
    gameState = "ready";
    prevPauseGesture = false;
    pauseGestureLockUntil = 0;
    lastPaddleContactAt = performance.now();
    lastAntiStallAt = 0;
    bricksSincePaddle = 0;
    paddleX = 0;
    paddle.position.x = 0;
    buildBricks();
    prepareBall(performance.now(), START_DELAY_MS);
    ball.position.set(ballPos.x, ballPos.y, 1.2);
  }

  function startRun(): void {
    if (gameState !== "ready" || !paddle || !ball) return;
    gameState = "running";
    prepareBall(performance.now(), 260);
  }

  function restartGame(): void {
    enterReadyState();
  }

  function togglePause(): void {
    if (gameState === "running") {
      gameState = "paused";
      return;
    }
    if (gameState === "paused") {
      gameState = "running";
      lastFrameTs = performance.now();
    }
  }

  function readCorners(matrix: number[] | null, rows: number, cols: number): CornerMap {
    if (!matrix || matrix.length === 0) return { tl: 0, tr: 0, bl: 0, br: 0 };
    const r = Math.max(Math.floor(rows), 1);
    const c = Math.max(Math.floor(cols), 1);
    const rs = Math.min(2, r);
    const cs = Math.min(2, c);
    const avg = (r0: number, r1: number, c0: number, c1: number): number => {
      let sum = 0;
      let count = 0;
      for (let i = r0; i < r1; i += 1) {
        for (let j = c0; j < c1; j += 1) {
          const v = Number(matrix[i * c + j] ?? 0);
          if (!Number.isFinite(v)) continue;
          sum += Math.max(0, v);
          count += 1;
        }
      }
      return count ? sum / count : 0;
    };
    return {
      tl: avg(0, rs, 0, cs),
      tr: avg(0, rs, c - cs, c),
      bl: avg(r - rs, r, 0, cs),
      br: avg(r - rs, r, c - cs, c)
    };
  }

  function sensorControlAxis(): number {
    const span = Math.max(1200, (rangeMax - rangeMin) * 0.22);
    const raw = (rightForce - leftForce) / span;
    if (Math.abs(raw) < 0.045) return 0;
    return Math.min(1, Math.max(-1, raw));
  }

  function handleUpControl(): void {
    if (gameState === "ready") {
      startRun();
      return;
    }
    if (gameState === "running" || gameState === "paused") {
      togglePause();
      return;
    }
    if (gameState === "over") {
      restartGame();
    }
  }

  function sampleUpgradeOptions(): UpgradeCardCopy[] {
    const pool = Object.values(upgradeCopy);
    const shuffled = [...pool].sort(() => Math.random() - 0.5);
    return shuffled.slice(0, 3);
  }

  function applyUpgrade(id: UpgradeId): void {
    if (gameState !== "draft") return;

    if (id === "overdrive") {
      launchSpeedBonus += 5;
      maxSpeedBonus += 8;
      if (ballLaunched) {
        ballVel.multiplyScalar(1.08);
      }
    } else if (id === "demolition") {
      bombBrickChance = Math.min(0.32, bombBrickChance + 0.07);
      bombExplosionRadius += 2.8;
    } else if (id === "surge") {
      brickSpeedGain += 0.01;
      if (ballLaunched) {
        ballVel.multiplyScalar(1.05);
      }
    } else if (id === "stabilizer") {
      lives += 1;
      paddleWidth = Math.min(PADDLE_W + 8, paddleWidth + 2.8);
      syncPaddleVisual();
    }

    selectedUpgrades = [...selectedUpgrades, id];
    activeDraftOptions = [];
    bricksUntilDraft = DRAFT_EVERY_BRICKS;
    gameState = "running";
    lastFrameTs = performance.now();
  }

  function openUpgradeDraft(): void {
    if (gameState !== "running") return;
    activeDraftOptions = sampleUpgradeOptions();
    gameState = "draft";
  }

  function handleSensorPauseGesture(nowMs: number): void {
    if (gameState !== "ready" && gameState !== "running" && gameState !== "paused" && gameState !== "over") {
      prevPauseGesture = false;
      return;
    }

    const active = topForce >= pauseGestureThreshold;
    if (active && !prevPauseGesture && nowMs >= pauseGestureLockUntil) {
      handleUpControl();
      pauseGestureLockUntil = nowMs + PAUSE_COOLDOWN_MS;
    }
    prevPauseGesture = active;
  }

  function update(dt: number, nowMs: number): void {
    if (!paddle || !ball) return;
    handleSensorPauseGesture(nowMs);

    const keyAxis = (keyRight ? 1 : 0) - (keyLeft ? 1 : 0);
    const axis = keyAxis !== 0 ? keyAxis : sensorControlAxis();
    paddleX += axis * PADDLE_SPEED * dt;
    paddleX = Math.min(FIELD_HALF_W - paddleWidth * 0.5, Math.max(-FIELD_HALF_W + paddleWidth * 0.5, paddleX));
    paddle.position.x = paddleX;

    if (gameState !== "running") {
      ball.position.set(ballPos.x, ballPos.y, 1.2);
      updateFlash(dt);
      return;
    }

    if (!ballLaunched) {
      ballPos.set(paddleX, PADDLE_Y + PADDLE_H * 0.5 + BALL_RADIUS + 0.4);
      if (nowMs >= nextLaunchAt) launchBall(nowMs);
    } else {
      ballPos.addScaledVector(ballVel, dt);
      resolveWallCollision(nowMs);
      resolvePaddleCollision();
      resolveBrickCollision(nowMs);
      applyAntiStall(nowMs);
    }

    ball.position.set(ballPos.x, ballPos.y, 1.2);
    updateFlash(dt);
  }

  function resolveWallCollision(nowMs: number): void {
    if (ballPos.x - BALL_RADIUS <= -FIELD_HALF_W) {
      ballPos.x = -FIELD_HALF_W + BALL_RADIUS;
      ballVel.x = Math.abs(ballVel.x);
    } else if (ballPos.x + BALL_RADIUS >= FIELD_HALF_W) {
      ballPos.x = FIELD_HALF_W - BALL_RADIUS;
      ballVel.x = -Math.abs(ballVel.x);
    }

    if (ballPos.y + BALL_RADIUS >= FIELD_HALF_H) {
      ballPos.y = FIELD_HALF_H - BALL_RADIUS;
      ballVel.y = -Math.abs(ballVel.y);
      if (nowMs - lastPaddleContactAt > UPPER_STALL_THRESHOLD_MS * 0.6) {
        forceBallDownward(0.62);
      }
    }

    if (ballPos.y - BALL_RADIUS < -FIELD_HALF_H) {
      lives -= 1;
      combo = 0;
      if (lives <= 0) {
        gameState = "over";
        ballLaunched = false;
      } else {
        prepareBall(nowMs, 440);
      }
    }
  }

  function resolvePaddleCollision(): void {
    if (!ballLaunched || ballVel.y >= 0) return;
    const paddleTop = PADDLE_Y + PADDLE_H * 0.5;
    const paddleBottom = PADDLE_Y - PADDLE_H * 0.5;
    const left = paddleX - paddleWidth * 0.5;
    const right = paddleX + paddleWidth * 0.5;
    const hit =
      ballPos.y - BALL_RADIUS <= paddleTop &&
      ballPos.y + BALL_RADIUS >= paddleBottom &&
      ballPos.x >= left - BALL_RADIUS &&
      ballPos.x <= right + BALL_RADIUS;

    if (!hit) return;
    ballPos.y = paddleTop + BALL_RADIUS + 0.04;
    const offset = (ballPos.x - paddleX) / (paddleWidth * 0.5);
    const speed = Math.min(MAX_BALL_SPEED + maxSpeedBonus, Math.max(BASE_BALL_SPEED + launchSpeedBonus * 0.4, ballVel.length()));
    ballVel.set(offset * speed * 0.9, Math.abs(ballVel.y) + 4).normalize().multiplyScalar(speed);
    combo = 0;
    lastPaddleContactAt = performance.now();
    bricksSincePaddle = 0;
  }

  function destroyBrick(brick: Brick, nowMs: number, scaleBoost = 0): number {
    if (!brick.alive) return 0;
    brick.alive = false;
    brick.mesh.visible = false;
    bricksLeft -= 1;
    combo += 1;
    score += 40 + combo * 5;
    bricksSincePaddle += 1;
    bricksUntilDraft -= 1;
    triggerFlash(brick.mesh.position.x, brick.mesh.position.y, 10 + combo * 0.4 + scaleBoost);

    const nextSpeed = Math.min(MAX_BALL_SPEED + maxSpeedBonus, ballVel.length() * (1 + brickSpeedGain));
    if (ballLaunched && ballVel.lengthSq() > 0) {
      ballVel.normalize().multiplyScalar(nextSpeed);
    }

    if (bricksSincePaddle >= MAX_BRICK_HITS_WITHOUT_PADDLE && ballPos.y > UPPER_STALL_Y) {
      forceBallDownward(1);
      lastAntiStallAt = nowMs;
    }

    return 1;
  }

  function detonateBomb(x: number, y: number, nowMs: number): number {
    let destroyed = 0;
    for (const candidate of bricks) {
      if (!candidate.alive) continue;
      const dx = candidate.mesh.position.x - x;
      const dy = candidate.mesh.position.y - y;
      if (Math.hypot(dx, dy) > bombExplosionRadius) continue;
      destroyed += destroyBrick(candidate, nowMs, 2.8);
    }
    triggerFlash(x, y, 18);
    return destroyed;
  }

  function handleLevelClear(nowMs: number): void {
    if (level >= RUN_TARGET_LEVEL) {
      gameState = "over";
      ballLaunched = false;
      return;
    }

    level += 1;
    buildBricks();
    prepareBall(nowMs, 420);
    if (level > 1) {
      openUpgradeDraft();
    }
  }

  function resolveBrickCollision(nowMs: number): void {
    if (!ballLaunched) return;
    for (const brick of bricks) {
      if (!brick.alive) continue;
      const closestX = Math.max(brick.left, Math.min(ballPos.x, brick.right));
      const closestY = Math.max(brick.bottom, Math.min(ballPos.y, brick.top));
      const dx = ballPos.x - closestX;
      const dy = ballPos.y - closestY;
      if (dx * dx + dy * dy > BALL_RADIUS * BALL_RADIUS) continue;

      const overlapX = Math.min(ballPos.x + BALL_RADIUS - brick.left, brick.right - (ballPos.x - BALL_RADIUS));
      const overlapY = Math.min(ballPos.y + BALL_RADIUS - brick.bottom, brick.top - (ballPos.y - BALL_RADIUS));
      if (overlapX < overlapY) {
        ballVel.x *= -1;
      } else {
        ballVel.y *= -1;
      }

      if (brick.kind === "bomb") {
        detonateBomb(brick.mesh.position.x, brick.mesh.position.y, nowMs);
      } else {
        destroyBrick(brick, nowMs);
      }

      if (bricksLeft <= 0) {
        handleLevelClear(nowMs);
      } else if (bricksUntilDraft <= 0) {
        openUpgradeDraft();
      }
      break;
    }
  }

  function triggerFlash(x: number, y: number, scale: number): void {
    if (!flashSprite) return;
    flashLife = flashMaxLife;
    flashSprite.visible = true;
    flashSprite.position.set(x, y, 2.5);
    flashSprite.scale.set(scale, scale, 1);
    const material = flashSprite.material as THREE.SpriteMaterial;
    material.opacity = 0.76;
  }

  function updateFlash(dt: number): void {
    if (!flashSprite || flashLife <= 0) return;
    flashLife -= dt;
    const t = Math.max(0, flashLife / flashMaxLife);
    const material = flashSprite.material as THREE.SpriteMaterial;
    material.opacity = t * 0.78;
    flashSprite.scale.multiplyScalar(1 + dt * 2.6);
    if (flashLife <= 0) flashSprite.visible = false;
  }

  function forceBallDownward(forceScale: number): void {
    const speed = Math.min(
      MAX_BALL_SPEED + maxSpeedBonus,
      Math.max(BASE_BALL_SPEED + launchSpeedBonus * 0.4, ballVel.length())
    );
    let dirX = ballVel.x + rand(-8, 8);
    if (Math.abs(dirX) < 6) dirX = (Math.random() < 0.5 ? -1 : 1) * 6;
    const dirY = -Math.abs(ballVel.y) - 6 * forceScale;
    ballVel.set(dirX, dirY).normalize().multiplyScalar(speed);
  }

  function applyAntiStall(nowMs: number): void {
    if (!ballLaunched) return;
    if (ballPos.y < UPPER_STALL_Y) return;
    if (nowMs - lastPaddleContactAt < UPPER_STALL_THRESHOLD_MS) return;
    if (nowMs - lastAntiStallAt < ANTI_STALL_COOLDOWN_MS) return;
    forceBallDownward(0.85);
    lastAntiStallAt = nowMs;
  }
</script>

<section class="breakout-root" bind:this={hostEl}>
  <canvas class="breakout-canvas" bind:this={canvasEl} aria-label="Neon Breakout"></canvas>

  <div class="breakout-overlay">
    <header class="overlay-head">
      <div class="title-block">
        <p class="title">{ui.title}</p>
      </div>
      <div class="action-group">
        <button type="button" on:click={gameState === "ready" ? startRun : restartGame}>
          {gameState === "ready" ? ui.start : ui.restart}
        </button>
        <button
          type="button"
          on:click={handleUpControl}
          disabled={gameState === "draft" || gameState === "over"}
        >
          {gameState === "paused" ? ui.resume : ui.pause}
        </button>
      </div>
    </header>

    <div class="hud-grid">
      <article><span>{ui.score}</span><strong>{Math.round(score)}</strong></article>
      <article><span>{ui.combo}</span><strong>{combo}</strong></article>
      <article><span>{ui.lives}</span><strong>{lives}</strong></article>
      <article><span>{ui.level}</span><strong>{level}</strong></article>
      <article><span>{ui.bricks}</span><strong>{Math.max(0, bricksLeft)}</strong></article>
    </div>

    <footer class="overlay-foot">
      <p class="status">{ui.chase} / {statusText}</p>
      {#if selectedUpgrades.length > 0}
        <p class="status skill-status">{ui.skills} / {selectedUpgrades.length}</p>
      {/if}
    </footer>
  </div>

  {#if gameState === "ready"}
    <div class="pause-mask is-ready" aria-live="polite">
      <div class="pause-panel is-ready">
        <p>{ui.startOverlayTitle}</p>
        <div class="overlay-copy">
          <span>{ui.startOverlayBody}</span>
          <span>{ui.startOverlayHint}</span>
        </div>
      </div>
    </div>
  {/if}

  {#if gameState === "paused"}
    <div class="pause-mask" aria-live="polite">
      <div class="pause-panel">
        <p>{ui.pausedOverlay}</p>
      </div>
    </div>
  {/if}

  {#if gameState === "draft"}
    <div class="pause-mask is-draft" aria-live="polite">
      <div class="pause-panel is-draft">
        <p>{ui.draftOverlayTitle}</p>
        <div class="overlay-copy">
          <span>{ui.draftOverlayHint}</span>
        </div>
        <div class="draft-grid">
          {#each activeDraftOptions as option, index}
            <button type="button" class="draft-card" on:click={() => applyUpgrade(option.id)}>
              <strong>{index + 1}. {option.title}</strong>
              <span>{option.body}</span>
            </button>
          {/each}
        </div>
      </div>
    </div>
  {/if}

  {#if gameState === "over"}
    <div class="pause-mask is-over" aria-live="assertive">
      <div class="pause-panel is-over">
        <p>{ui.over}</p>
        <div class="overlay-copy">
          <span>{ui.overHint}</span>
        </div>
        <button type="button" class="overlay-restart-btn" on:click={restartGame}>{ui.restart}</button>
      </div>
    </div>
  {/if}
</section>

<style>
  .breakout-root {
    position: absolute;
    inset: 0;
    overflow: hidden;
    border-radius: 0.58rem;
    border: 1px solid rgb(var(--hud-border-rgb) / 0.28);
    background: linear-gradient(180deg, rgb(var(--hud-surface-deep-rgb) / 0.92), rgb(var(--hud-surface-rgb) / 0.86));
    box-shadow:
      inset 0 1px 0 rgb(var(--hud-border-strong-rgb) / 0.08),
      0 0 24px rgb(var(--hud-glow-rgb) / 0.12);
  }

  .breakout-canvas {
    position: absolute;
    inset: 0;
    inline-size: 100%;
    block-size: 100%;
    display: block;
  }

  .breakout-overlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
    display: grid;
    grid-template-rows: auto auto 1fr auto;
    gap: 0.42rem;
    padding: 0.5rem 0.58rem 0.42rem;
  }

  .overlay-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 0.5rem;
  }

  .title {
    margin: 0;
    color: rgb(var(--hud-text-main-rgb) / 0.96);
    font-size: 0.68rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
  }

  .action-group {
    display: inline-flex;
    gap: 0.28rem;
    pointer-events: auto;
  }

  .action-group button {
    min-block-size: 1.65rem;
    border: 1px solid rgb(var(--hud-cyan-rgb) / 0.56);
    border-radius: 999px;
    padding: 0.14rem 0.52rem;
    background: linear-gradient(180deg, rgb(var(--hud-surface-alt-rgb) / 0.92), rgb(var(--hud-surface-rgb) / 0.86));
    color: rgb(var(--hud-text-main-rgb) / 0.96);
    font: inherit;
    font-size: 0.6rem;
    letter-spacing: 0.06em;
    cursor: pointer;
  }

  .action-group button:disabled {
    opacity: 0.42;
    cursor: not-allowed;
  }

  .hud-grid {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: 0.24rem;
  }

  .hud-grid article {
    min-block-size: 1.88rem;
    border: 1px solid rgb(var(--hud-border-rgb) / 0.24);
    border-radius: 0.42rem;
    padding: 0.16rem 0.34rem 0.24rem;
    background: rgb(var(--hud-surface-rgb) / 0.68);
  }

  .hud-grid span {
    display: block;
    color: rgb(var(--hud-text-dim-rgb) / 0.82);
    font-size: 0.5rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }

  .hud-grid strong {
    color: rgb(var(--hud-text-main-rgb) / 0.98);
    font-size: 0.84rem;
    line-height: 1;
  }

  .overlay-foot {
    display: flex;
    justify-content: flex-start;
    align-items: center;
    gap: 0.52rem;
  }

  .status {
    margin: 0;
    border: 1px solid rgb(var(--hud-border-rgb) / 0.3);
    border-radius: 999px;
    padding: 0.18rem 0.56rem;
    background: rgb(var(--hud-surface-deep-rgb) / 0.66);
    color: rgb(var(--hud-text-main-rgb) / 0.94);
    font-size: 0.56rem;
    letter-spacing: 0.07em;
    text-transform: uppercase;
  }

  .skill-status {
    border-color: rgb(var(--hud-orange-rgb) / 0.34);
    background: rgb(var(--hud-surface-rgb) / 0.58);
  }

  .pause-mask {
    position: absolute;
    inset: 0;
    z-index: 12;
    pointer-events: none;
    display: grid;
    place-items: center;
    background:
      radial-gradient(circle at center, rgb(var(--hud-surface-rgb) / 0.24), rgb(var(--hud-bg-30) / 0.72)),
      repeating-linear-gradient(
        180deg,
        rgb(var(--hud-glow-alt-rgb) / 0.08) 0,
        rgb(var(--hud-glow-alt-rgb) / 0.08) 1px,
        transparent 1px,
        transparent 4px
      );
    backdrop-filter: blur(3px);
  }

  .pause-panel {
    border: 1px solid rgb(var(--hud-lime-rgb) / 0.56);
    border-radius: 0.66rem;
    padding: 0.62rem 0.92rem;
    background: rgb(var(--hud-surface-deep-rgb) / 0.8);
    box-shadow:
      inset 0 1px 0 rgb(var(--hud-border-strong-rgb) / 0.1),
      0 0 26px rgb(var(--hud-lime-rgb) / 0.22);
    pointer-events: auto;
  }

  .pause-panel p {
    margin: 0;
    color: rgb(var(--hud-text-main-rgb) / 0.98);
    font-size: clamp(0.76rem, 1.5vw, 1.08rem);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    text-shadow: 0 0 10px rgb(var(--hud-lime-rgb) / 0.28);
  }

  .overlay-copy {
    display: grid;
    gap: 0.46rem;
    margin-top: 0.7rem;
  }

  .overlay-copy span {
    color: rgb(var(--hud-text-main-rgb) / 0.84);
    font-size: clamp(0.68rem, 1.2vw, 0.86rem);
    line-height: 1.55;
    letter-spacing: 0.03em;
    text-transform: none;
  }

  .pause-mask.is-ready {
    background:
      radial-gradient(circle at center, rgb(var(--hud-surface-rgb) / 0.18), rgb(var(--hud-bg-30) / 0.84)),
      linear-gradient(135deg, rgb(var(--hud-cyan-rgb) / 0.08), transparent 42%, rgb(var(--hud-lime-rgb) / 0.08));
  }

  .pause-panel.is-ready {
    border-color: rgb(var(--hud-cyan-rgb) / 0.64);
    box-shadow:
      inset 0 1px 0 rgb(var(--hud-border-strong-rgb) / 0.1),
      0 0 32px rgb(var(--hud-cyan-rgb) / 0.22);
    max-inline-size: min(34rem, 82%);
  }

  .pause-mask.is-draft {
    background:
      radial-gradient(circle at center, rgb(var(--hud-surface-rgb) / 0.18), rgb(var(--hud-bg-30) / 0.86)),
      linear-gradient(160deg, rgb(var(--hud-orange-rgb) / 0.08), transparent 48%, rgb(var(--hud-cyan-rgb) / 0.08));
  }

  .pause-panel.is-draft {
    border-color: rgb(var(--hud-orange-rgb) / 0.58);
    box-shadow:
      inset 0 1px 0 rgb(var(--hud-border-strong-rgb) / 0.1),
      0 0 32px rgb(var(--hud-orange-rgb) / 0.22);
    inline-size: min(42rem, 88%);
  }

  .draft-grid {
    margin-top: 0.92rem;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.62rem;
    pointer-events: auto;
  }

  .draft-card {
    border: 1px solid rgb(var(--hud-border-rgb) / 0.38);
    border-radius: 0.78rem;
    padding: 0.8rem 0.86rem;
    background:
      linear-gradient(180deg, rgb(var(--hud-surface-alt-rgb) / 0.96), rgb(var(--hud-surface-rgb) / 0.88));
    color: rgb(var(--hud-text-main-rgb) / 0.96);
    font: inherit;
    text-align: left;
    display: grid;
    gap: 0.48rem;
    cursor: pointer;
    transition: transform 120ms ease, border-color 120ms ease, box-shadow 120ms ease;
  }

  .draft-card:hover {
    transform: translateY(-2px);
    border-color: rgb(var(--hud-cyan-rgb) / 0.56);
    box-shadow: 0 0 18px rgb(var(--hud-cyan-rgb) / 0.16);
  }

  .draft-card strong {
    font-size: 0.78rem;
    line-height: 1.2;
    letter-spacing: 0.04em;
  }

  .draft-card span {
    color: rgb(var(--hud-text-main-rgb) / 0.82);
    font-size: 0.66rem;
    line-height: 1.5;
  }

  .pause-mask.is-over {
    background:
      radial-gradient(circle at center, rgb(var(--hud-surface-rgb) / 0.22), rgb(var(--hud-bg-30) / 0.8)),
      repeating-linear-gradient(
        180deg,
        rgb(var(--hud-orange-rgb) / 0.1) 0,
        rgb(var(--hud-orange-rgb) / 0.1) 1px,
        transparent 1px,
        transparent 4px
      );
  }

  .pause-panel.is-over {
    border-color: rgb(var(--hud-orange-rgb) / 0.62);
    box-shadow:
      inset 0 1px 0 rgb(var(--hud-border-strong-rgb) / 0.1),
      0 0 32px rgb(var(--hud-orange-rgb) / 0.24);
    min-inline-size: min(24rem, 72%);
    display: grid;
    gap: 0.72rem;
    justify-items: center;
  }

  .pause-panel.is-over p {
    color: rgb(var(--hud-orange-rgb) / 0.98);
    text-shadow: 0 0 12px rgb(var(--hud-orange-rgb) / 0.36);
  }

  .overlay-restart-btn {
    min-block-size: clamp(2.2rem, 4.2vh, 2.8rem);
    min-inline-size: clamp(8.8rem, 18vw, 13rem);
    border: 1px solid rgb(var(--hud-cyan-rgb) / 0.62);
    border-radius: 999px;
    padding: 0.24rem 1rem;
    background: linear-gradient(180deg, rgb(var(--hud-surface-alt-rgb) / 0.96), rgb(var(--hud-surface-rgb) / 0.9));
    color: rgb(var(--hud-text-main-rgb) / 0.98);
    font: inherit;
    font-size: clamp(0.74rem, 1.3vw, 0.96rem);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    cursor: pointer;
    box-shadow: 0 0 16px rgb(var(--hud-cyan-rgb) / 0.2);
    transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease;
  }

  .overlay-restart-btn:hover {
    transform: translateY(-1px);
    border-color: rgb(var(--hud-lime-rgb) / 0.66);
    box-shadow: 0 0 20px rgb(var(--hud-lime-rgb) / 0.28);
  }

  @media (max-width: 1120px) {
    .hud-grid {
      grid-template-columns: repeat(3, minmax(0, 1fr));
    }

    .draft-grid {
      grid-template-columns: 1fr;
    }
  }
</style>
