<script lang="ts">
  import { createEventDispatcher } from "svelte";
  import { flip } from "svelte/animate";
  import { cubicIn, cubicOut } from "svelte/easing";
  import { onMount } from "svelte";
  import { fly } from "svelte/transition";
  import ConfigPanel from "$lib/components/ConfigPanel.svelte";
  import NeonBreakoutArena from "$lib/components/NeonBreakoutArena.svelte";
  import PressureMatrixViewer from "$lib/components/PressureMatrixViewer.svelte";
  import SignalChart from "$lib/components/SignalChart.svelte";
  import SummaryCurve from "$lib/components/SummaryCurve.svelte";
  import type {
    HudColorMapOption,
    HudSignalPanel,
    HudSummary,
    LocaleCode,
    MatrixDisplayMode,
    PressureColorMapPreset
  } from "$lib/types/hud";

  export let locale: LocaleCode = "zh-CN";
  export let leftPanels: HudSignalPanel[] = [];
  export let rightPanels: HudSignalPanel[] = [];
  export let summary: HudSummary;
  export let pressureMatrix: number[] | null = null;
  export let showConfigPanel = false;
  export let configPanelTitle = "";
  export let configPanelHint = "";
  export let matrixSizeLabel = "";
  export let matrixRowsLabel = "";
  export let matrixColsLabel = "";
  export let rangeLabel = "";
  export let rangeMinLabel = "";
  export let rangeMaxLabel = "";
  export let colorMapLabel = "";
  export let resetConfigLabel = "";
  export let applyLiveHint = "";
  export let matrixRows = 12;
  export let matrixCols = 7;
  export let rangeMin = 0;
  export let rangeMax = 16000;
  export let colorMapPreset: PressureColorMapPreset = "emerald";
  export let matrixDisplayMode: MatrixDisplayMode = "dots";
  export let colorMapOptions: HudColorMapOption[] = [];
  export let replaySectionLabel = "";
  export let replayPlayLabel = "";
  export let replayPauseLabel = "";
  export let replayStopLabel = "";
  export let replaySpeedLabel = "";
  export let replayProgressLabel = "";
  export let replayHasData = false;
  export let replayIsPlaying = false;
  export let replaySpeed = 1;
  export let replayProgress = 0;
  export let replayFileName = "";
  export let replayFrameInfo = "";
  export let showPrecisionTestPanel = false;

  let stagePlaneEl: HTMLDivElement | undefined;
  let panelZoneEl: HTMLDivElement | undefined;
  let leftStackEl: HTMLDivElement | undefined;
  let rightStackEl: HTMLDivElement | undefined;

  let panelZoneTopPx = 88;
  let leftRailScale = 1;
  let rightRailScale = 1;
  let summarySide: "left" | "right" = "left";
  let replaySide: "left" | "right" = "right";

  const minRailScale = 0.2;
  const dispatch = createEventDispatcher<{
    configclose: void;
    replaytoggle: void;
    replaystop: void;
    replayseek: number;
    replayspeed: number;
    replayclose: void;
  }>();

  $: summarySide = leftPanels.length <= rightPanels.length ? "left" : "right";
  $: replaySide = summarySide === "left" ? "right" : "left";
  $: replayToggleButtonText = replayIsPlaying ? replayPauseLabel : replayPlayLabel;
  $: replayProgressPercent = Math.round(Math.min(1, Math.max(0, replayProgress)) * 100);
  $: summaryCurveVisible = summary.points.length > 0 && summary.points.some((value) => Number.isFinite(value) && Math.abs(value) >= 0.0001);
  $: splitMatrixTitle = locale === "zh-CN" ? "数字矩阵" : "Matrix";
  $: splitMatrixHint = locale === "zh-CN" ? "实时压力数据 / 数字矩阵" : "Live pressure matrix";

  function toPxNumber(rawValue: string): number {
    const value = Number.parseFloat(rawValue);
    return Number.isFinite(value) ? value : 0;
  }

  function clamp(value: number, min: number, max: number): number {
    return Math.min(max, Math.max(min, value));
  }

  function calculateScale(zoneHeight: number, stackHeight: number): number {
    if (zoneHeight <= 0 || stackHeight <= 0) {
      return 1;
    }

    const rawScale = (zoneHeight - 6) / stackHeight;
    return clamp(Math.round(rawScale * 1000) / 1000, minRailScale, 1);
  }

  function recomputePanelLayout(): void {
    if (!stagePlaneEl) {
      return;
    }

    panelZoneTopPx = showPrecisionTestPanel ? 24 : 16;

    const panelZoneBottomPx = panelZoneEl ? toPxNumber(getComputedStyle(panelZoneEl).bottom) : 0;
    const zoneHeight = Math.max(0, stagePlaneEl.clientHeight - panelZoneTopPx - panelZoneBottomPx);

    leftRailScale = calculateScale(zoneHeight, leftStackEl?.scrollHeight ?? 0);
    rightRailScale = calculateScale(zoneHeight, rightStackEl?.scrollHeight ?? 0);
  }

  function emitReplayToggle(): void {
    dispatch("replaytoggle");
  }

  function emitReplayStop(): void {
    dispatch("replaystop");
  }

  function emitReplaySeek(event: Event): void {
    const target = event.currentTarget as HTMLInputElement;
    const normalized = Number(target.value) / 100;
    dispatch("replayseek", Number.isFinite(normalized) ? normalized : 0);
  }

  function emitReplaySpeed(event: Event): void {
    const target = event.currentTarget as HTMLSelectElement;
    const nextSpeed = Number(target.value);
    dispatch("replayspeed", Number.isFinite(nextSpeed) ? nextSpeed : 1);
  }

  function emitReplayClose(): void {
    dispatch("replayclose");
  }

  onMount(() => {
    recomputePanelLayout();

    const resizeObserver = new ResizeObserver(() => {
      recomputePanelLayout();
    });

    if (stagePlaneEl) {
      resizeObserver.observe(stagePlaneEl);
    }

    if (leftStackEl) {
      resizeObserver.observe(leftStackEl);
    }

    if (rightStackEl) {
      resizeObserver.observe(rightStackEl);
    }

    window.addEventListener("resize", recomputePanelLayout);

    return () => {
      resizeObserver.disconnect();
      window.removeEventListener("resize", recomputePanelLayout);
    };
  });
</script>

<section class="center-stage" aria-label="WebGL2 Area">
  <article class="stage-shell">
    <div
      class="stage-canvas-plane"
      bind:this={stagePlaneEl}
      style="--panel-zone-top-dyn: {panelZoneTopPx}px; --rail-scale-left: {leftRailScale}; --rail-scale-right: {rightRailScale};"
    >
      {#if showPrecisionTestPanel}
        <div class="split-game-wrap">
          <section class="split-panel split-matrix-panel">
            <header class="split-panel-head">
              <p>{splitMatrixTitle}</p>
              <span>{splitMatrixHint}</span>
            </header>
            <div class="split-panel-body">
              {#key `${matrixRows}x${matrixCols}:${colorMapPreset}:split`}
                <PressureMatrixViewer
                  {summary}
                  {pressureMatrix}
                  {matrixRows}
                  {matrixCols}
                  {rangeMin}
                  {rangeMax}
                  {colorMapPreset}
                  {matrixDisplayMode}
                  showStatsPanel={true}
                />
              {/key}
            </div>
          </section>

          <section class="split-panel split-breakout-panel">
            <NeonBreakoutArena
              {locale}
              {pressureMatrix}
              {matrixRows}
              {matrixCols}
              {rangeMin}
              {rangeMax}
              {colorMapPreset}
            />
          </section>
        </div>
      {:else}
        <div class="canvas-wrap">
          {#key `${matrixRows}x${matrixCols}:${colorMapPreset}`}
            <PressureMatrixViewer
              {summary}
              {pressureMatrix}
              {matrixRows}
              {matrixCols}
              {rangeMin}
              {rangeMax}
              {colorMapPreset}
              {matrixDisplayMode}
              showStatsPanel={true}
            />
          {/key}
        </div>
      {/if}

      {#if showConfigPanel && !showPrecisionTestPanel}
        <div class="config-panel-wrap">
          <ConfigPanel
            bind:matrixRows
            bind:matrixCols
            bind:rangeMin
            bind:rangeMax
            title={configPanelTitle}
            hint={configPanelHint}
            {matrixSizeLabel}
            {matrixRowsLabel}
            {matrixColsLabel}
            {rangeLabel}
            {rangeMinLabel}
            {rangeMaxLabel}
            {colorMapLabel}
            bind:colorMapPreset
            {colorMapOptions}
            resetLabel={resetConfigLabel}
            {applyLiveHint}
            on:close={() => dispatch("configclose")}
          />
        </div>
      {/if}

      {#if !showPrecisionTestPanel}
        <div class="panel-zone" bind:this={panelZoneEl}>
          <aside class="side-rail left-rail">
            <div class="rail-stack" bind:this={leftStackEl}>
              {#each leftPanels as panel, index (panel.id)}
                <div
                  class="panel-motion-shell"
                  animate:flip={{ duration: 280 }}
                  in:fly={{ x: -180, duration: 340, opacity: 0.08, easing: cubicOut }}
                  out:fly={{ x: -180, duration: 280, opacity: 0.06, easing: cubicIn }}
                >
                  <SignalChart {panel} panelIndex={index} />
                </div>
              {/each}

              {#if summaryCurveVisible && summarySide === "left"}
                <div
                  class="panel-motion-shell"
                  in:fly={{ x: -180, duration: 340, opacity: 0.08, easing: cubicOut }}
                  out:fly={{ x: -180, duration: 280, opacity: 0.06, easing: cubicIn }}
                >
                  <SummaryCurve
                    {summary}
                    xValues={summary.xValues ?? null}
                    yValues={summary.points}
                    side="left"
                    panelIndex={leftPanels.length}
                  />
                </div>
              {/if}
            </div>
          </aside>

          <aside class="side-rail right-rail">
            <div class="rail-stack" bind:this={rightStackEl}>
              {#each rightPanels as panel, index (panel.id)}
                <div
                  class="panel-motion-shell"
                  animate:flip={{ duration: 280 }}
                  in:fly={{ x: 180, duration: 340, opacity: 0.08, easing: cubicOut }}
                  out:fly={{ x: 180, duration: 280, opacity: 0.06, easing: cubicIn }}
                >
                  <SignalChart {panel} panelIndex={index} />
                </div>
              {/each}

              {#if summaryCurveVisible && summarySide === "right"}
                <div
                  class="panel-motion-shell"
                  in:fly={{ x: 180, duration: 340, opacity: 0.08, easing: cubicOut }}
                  out:fly={{ x: 180, duration: 280, opacity: 0.06, easing: cubicIn }}
                >
                  <SummaryCurve
                    {summary}
                    xValues={summary.xValues ?? null}
                    yValues={summary.points}
                    side="right"
                    panelIndex={rightPanels.length}
                  />
                </div>
              {/if}
            </div>
          </aside>
        </div>
      {/if}

      {#if replayHasData && !showPrecisionTestPanel}
        <aside class="replay-floating-panel" class:is-left={replaySide === "left"} class:is-right={replaySide === "right"}>
          <div class="replay-panel-head">
            <div class="replay-panel-title-group">
              <p class="replay-panel-label">{replaySectionLabel}</p>
              <p class="replay-panel-file" title={replayFileName}>{replayFileName}</p>
            </div>
            <div class="replay-panel-head-actions">
              {#if replayFrameInfo}
                <p class="replay-panel-frame">{replayFrameInfo}</p>
              {/if}
              <button type="button" class="replay-close-btn" aria-label="Close replay" on:click={emitReplayClose}>×</button>
            </div>
          </div>

          <div class="replay-panel-controls">
            <div class="replay-panel-actions">
              <button type="button" class="replay-action-btn" on:click={emitReplayToggle}>{replayToggleButtonText}</button>
              <button type="button" class="replay-action-btn is-stop" on:click={emitReplayStop}>{replayStopLabel}</button>
              <label class="replay-speed-select">
                <span>{replaySpeedLabel}</span>
                <select value={replaySpeed} on:change={emitReplaySpeed}>
                  <option value={0.5}>0.5x</option>
                  <option value={1}>1x</option>
                  <option value={1.5}>1.5x</option>
                  <option value={2}>2x</option>
                </select>
              </label>
            </div>

            <label class="replay-progress-slider">
              <span>{replayProgressLabel}</span>
              <input type="range" min="0" max="100" step="1" value={replayProgressPercent} on:input={emitReplaySeek} />
            </label>
          </div>
        </aside>
      {/if}

      {#if !showPrecisionTestPanel}
        <div class="stage-bottom-overlay">
          <slot />
        </div>
      {/if}
    </div>
  </article>
</section>

<style>
  .center-stage {
    block-size: 100%;
    min-height: 0;
  }

  .stage-shell {
    position: relative;
    block-size: 100%;
    min-height: 0;
    overflow: hidden;
    border-radius: 0.72rem;
    border: 1px solid rgb(var(--hud-border-rgb) / 0.2);
    background:
      linear-gradient(
        170deg,
        rgb(var(--hud-surface-rgb) / 0.86) 0%,
        rgb(var(--hud-surface-deep-rgb) / 0.96) 58%,
        rgb(var(--hud-surface-alt-rgb) / 0.9) 100%
      ),
      radial-gradient(circle at 50% 0, rgb(var(--hud-glow-rgb) / 0.04), transparent 48%);
    box-shadow:
      inset 0 1px 0 rgb(var(--hud-border-strong-rgb) / 0.08),
      inset 0 -36px 72px rgb(0 0 0 / 0.4);
  }

  .stage-canvas-plane {
    --rail-width: clamp(17.5rem, 23vw, 21.5rem);
    --rail-edge-inset: clamp(0.35rem, 1vw, 0.9rem);
    --safe-gap: clamp(0.35rem, 0.9vw, 0.85rem);
    --panel-zone-top: clamp(6.4rem, 11.8vh, 8rem);
    --panel-zone-bottom: clamp(1.8rem, 3.6vh, 2.8rem);
    --rail-gap: clamp(0.55rem, 1.4vh, 1rem);
    --bottom-inset: clamp(0.35rem, 1.2vw, 0.9rem);
    position: relative;
    inline-size: 100%;
    block-size: 100%;
  }

  .canvas-wrap {
    position: absolute;
    inset: 0;
    min-height: 0;
    overflow: hidden;
  }

  .config-panel-wrap {
    position: absolute;
    top: clamp(5rem, 11.4vh, 6.8rem);
    right: calc(var(--rail-width) + var(--safe-gap) + var(--rail-edge-inset));
    z-index: 8;
    pointer-events: auto;
    max-inline-size: min(24rem, 40vw);
  }

  .split-game-wrap {
    position: absolute;
    inset: clamp(0.46rem, 1vw, 0.82rem);
    z-index: 6;
    display: grid;
    grid-template-columns: minmax(0, 0.98fr) minmax(0, 1.02fr);
    gap: clamp(0.45rem, 1vw, 0.9rem);
  }

  .split-panel {
    position: relative;
    min-block-size: 0;
    overflow: hidden;
    border: 1px solid rgb(var(--hud-border-rgb) / 0.26);
    border-radius: 0.58rem;
    background:
      linear-gradient(180deg, rgb(var(--hud-surface-rgb) / 0.84), rgb(var(--hud-surface-deep-rgb) / 0.9)),
      radial-gradient(circle at 50% 0, rgb(var(--hud-glow-rgb) / 0.06), transparent 56%);
    box-shadow:
      inset 0 1px 0 rgb(var(--hud-border-strong-rgb) / 0.07),
      0 0 20px rgb(var(--hud-glow-rgb) / 0.08);
  }

  .split-panel-head {
    position: absolute;
    top: 0.42rem;
    left: 0.52rem;
    z-index: 5;
    display: grid;
    gap: 0.1rem;
    margin: 0;
    pointer-events: none;
  }

  .split-panel-head p {
    margin: 0;
    color: rgb(var(--hud-text-main-rgb) / 0.96);
    font-size: 0.62rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
  }

  .split-panel-head span {
    color: rgb(var(--hud-text-dim-rgb) / 0.82);
    font-size: 0.52rem;
    letter-spacing: 0.05em;
    text-transform: uppercase;
  }

  .split-panel-body {
    position: absolute;
    inset: 0;
  }

  .split-matrix-panel :global(.viewer-controls) {
    left: clamp(0.7rem, 1.7vw, 1.15rem);
    top: clamp(3.8rem, 8.8vh, 4.9rem);
    max-inline-size: min(13.2rem, 65%);
  }

  .split-matrix-panel :global(.stats-panel) {
    padding: 0.62rem 0.68rem 0.72rem;
  }

  .panel-zone {
    position: absolute;
    top: var(--panel-zone-top-dyn, var(--panel-zone-top));
    right: 0;
    bottom: var(--panel-zone-bottom);
    left: 0;
    z-index: 6;
    pointer-events: none;
  }

  .side-rail {
    position: absolute;
    top: 0;
    bottom: 0;
    inline-size: var(--rail-width);
    min-height: 0;
    pointer-events: none;
    display: flex;
    align-items: flex-end;
  }

  .left-rail {
    left: var(--rail-edge-inset);
  }

  .right-rail {
    right: var(--rail-edge-inset);
  }

  .rail-stack {
    display: flex;
    flex-direction: column;
    gap: var(--rail-gap);
    transform: scale(var(--rail-scale));
    pointer-events: auto;
  }

  .left-rail .rail-stack {
    align-items: flex-start;
    transform: scale(var(--rail-scale-left, 1));
    transform-origin: left bottom;
  }

  .right-rail .rail-stack {
    align-items: flex-end;
    transform: scale(var(--rail-scale-right, 1));
    transform-origin: right bottom;
  }

  .left-rail :global(.signal-panel),
  .right-rail :global(.signal-panel) {
    pointer-events: auto;
  }

  .panel-motion-shell {
    inline-size: 100%;
    display: flex;
  }

  .left-rail .panel-motion-shell {
    justify-content: flex-start;
  }

  .right-rail .panel-motion-shell {
    justify-content: flex-end;
  }

  .replay-floating-panel {
    position: absolute;
    top: calc(var(--panel-zone-top-dyn, var(--panel-zone-top)) + 0.2rem);
    inline-size: min(var(--rail-width), 23.5rem);
    z-index: 8;
    pointer-events: auto;
    display: grid;
    gap: 0.52rem;
    border: 1px solid rgb(var(--hud-border-rgb) / 0.34);
    border-radius: 0.66rem;
    padding: 0.66rem 0.72rem;
    background:
      linear-gradient(180deg, rgb(var(--hud-surface-rgb) / 0.86), rgb(var(--hud-surface-deep-rgb) / 0.8)),
      radial-gradient(circle at 50% 0, rgb(var(--hud-glow-rgb) / 0.07), transparent 56%);
    box-shadow:
      inset 0 1px 0 rgb(var(--hud-border-strong-rgb) / 0.08),
      0 0 18px rgb(var(--hud-glow-rgb) / 0.1);
  }

  .replay-floating-panel.is-right {
    right: var(--rail-edge-inset);
  }

  .replay-floating-panel.is-left {
    left: var(--rail-edge-inset);
  }

  .replay-panel-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
  }

  .replay-panel-head-actions {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
  }

  .replay-panel-title-group {
    min-inline-size: 0;
    display: grid;
    gap: 0.24rem;
  }

  .replay-panel-label,
  .replay-panel-file,
  .replay-panel-frame {
    margin: 0;
    line-height: 1.12;
  }

  .replay-panel-label {
    font-size: 0.58rem;
    letter-spacing: 0.11em;
    text-transform: uppercase;
    color: rgb(var(--hud-text-dim-rgb) / 0.86);
  }

  .replay-panel-file {
    font-size: 0.73rem;
    letter-spacing: 0.03em;
    color: rgb(var(--hud-text-main-rgb) / 0.94);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .replay-panel-frame {
    border: 1px solid rgb(var(--hud-lime-rgb) / 0.36);
    border-radius: 999px;
    padding: 0.16rem 0.52rem;
    background: rgb(var(--hud-surface-alt-rgb) / 0.64);
    color: rgb(var(--hud-lime-rgb) / 0.94);
    font-size: 0.67rem;
    letter-spacing: 0.07em;
    white-space: nowrap;
  }

  .replay-close-btn {
    inline-size: 1.82rem;
    block-size: 1.82rem;
    border: 1px solid rgb(var(--hud-orange-rgb) / 0.44);
    border-radius: 0.32rem;
    background: rgb(var(--hud-surface-deep-rgb) / 0.88);
    color: rgb(var(--hud-orange-rgb) / 0.96);
    font-size: 1rem;
    line-height: 1;
    display: grid;
    place-items: center;
    cursor: pointer;
    transition:
      border-color 180ms ease,
      box-shadow 180ms ease,
      color 180ms ease;
  }

  .replay-close-btn:hover {
    border-color: rgb(var(--hud-orange-rgb) / 0.66);
    color: rgb(var(--hud-text-main-rgb) / 0.98);
    box-shadow: 0 0 12px rgb(var(--hud-orange-rgb) / 0.2);
  }

  .replay-panel-controls {
    display: grid;
    gap: 0.46rem;
  }

  .replay-panel-actions {
    display: flex;
    align-items: center;
    gap: 0.48rem;
    flex-wrap: wrap;
  }

  .replay-action-btn {
    min-block-size: 1.82rem;
    border: 1px solid rgb(var(--hud-cyan-rgb) / 0.36);
    border-radius: 999px;
    padding: 0.2rem 0.66rem;
    background: rgb(var(--hud-surface-alt-rgb) / 0.9);
    color: rgb(var(--hud-text-main-rgb) / 0.96);
    font-size: 0.7rem;
    letter-spacing: 0.05em;
    cursor: pointer;
    transition:
      border-color 180ms ease,
      box-shadow 180ms ease;
  }

  .replay-action-btn:hover {
    border-color: rgb(var(--hud-cyan-rgb) / 0.58);
    box-shadow: 0 0 10px rgb(var(--hud-cyan-rgb) / 0.14);
  }

  .replay-action-btn.is-stop {
    border-color: rgb(var(--hud-orange-rgb) / 0.44);
    color: rgb(var(--hud-orange-rgb) / 0.94);
    background: rgb(var(--hud-surface-deep-rgb) / 0.86);
  }

  .replay-action-btn.is-stop:hover {
    border-color: rgb(var(--hud-orange-rgb) / 0.64);
    box-shadow: 0 0 10px rgb(var(--hud-orange-rgb) / 0.18);
  }

  .replay-speed-select,
  .replay-progress-slider {
    display: inline-flex;
    align-items: center;
    gap: 0.36rem;
    min-block-size: 1.92rem;
    border: 1px solid rgb(var(--hud-border-rgb) / 0.32);
    border-radius: 999px;
    padding: 0.16rem 0.2rem 0.16rem 0.48rem;
    background: rgb(var(--hud-surface-rgb) / 0.78);
  }

  .replay-speed-select span,
  .replay-progress-slider span {
    color: rgb(var(--hud-text-dim-rgb) / 0.84);
    font-size: 0.62rem;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    white-space: nowrap;
  }

  .replay-speed-select select {
    border: 1px solid rgb(var(--hud-border-rgb) / 0.34);
    border-radius: 999px;
    padding: 0.22rem 0.48rem;
    background: rgb(var(--hud-surface-deep-rgb) / 0.88);
    color: rgb(var(--hud-text-main-rgb) / 0.96);
    font-size: 0.72rem;
    letter-spacing: 0.05em;
    outline: none;
  }

  .replay-progress-slider {
    inline-size: 100%;
  }

  .replay-progress-slider input {
    inline-size: 100%;
    accent-color: rgb(var(--hud-lime-rgb) / 0.92);
  }

  .stage-bottom-overlay {
    position: absolute;
    left: calc(var(--rail-width) + var(--safe-gap) + var(--rail-edge-inset));
    right: calc(var(--rail-width) + var(--safe-gap) + var(--rail-edge-inset));
    bottom: var(--bottom-inset);
    z-index: 7;
    pointer-events: none;
  }

  @media (max-width: 1180px) {
    .stage-canvas-plane {
      --rail-width: clamp(14.2rem, 28vw, 16.4rem);
      --rail-edge-inset: clamp(0.25rem, 0.7vw, 0.55rem);
      --safe-gap: clamp(0.2rem, 0.75vw, 0.45rem);
      --panel-zone-top: clamp(6rem, 11.2vh, 7.2rem);
      --panel-zone-bottom: clamp(1.1rem, 2.7vh, 2rem);
      --rail-gap: clamp(0.45rem, 1vh, 0.75rem);
    }

    .config-panel-wrap {
      max-inline-size: min(21rem, 46vw);
    }

    .replay-floating-panel {
      inline-size: min(var(--rail-width), 20.8rem);
    }

    .split-game-wrap {
      grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    }
  }

  @media (max-height: 900px) {
    .stage-canvas-plane {
      --panel-zone-top: clamp(5.6rem, 10vh, 6.8rem);
      --panel-zone-bottom: clamp(0.9rem, 1.8vh, 1.4rem);
    }

    .config-panel-wrap {
      top: clamp(4.7rem, 10vh, 6rem);
    }
  }

  @media (max-height: 760px) {
    .stage-canvas-plane {
      --panel-zone-top: clamp(5.1rem, 9.2vh, 6.2rem);
      --panel-zone-bottom: clamp(0.45rem, 1vh, 0.85rem);
      --rail-gap: clamp(0.35rem, 0.8vh, 0.62rem);
    }

    .config-panel-wrap {
      top: clamp(4.4rem, 9vh, 5.5rem);
    }

    .replay-floating-panel {
      top: calc(var(--panel-zone-top-dyn, var(--panel-zone-top)) + 0.1rem);
      padding: 0.58rem 0.64rem;
      gap: 0.44rem;
    }
  }

  @media (max-width: 920px) {
    .config-panel-wrap {
      left: calc(var(--rail-width) + var(--safe-gap) + var(--rail-edge-inset));
      right: auto;
      max-inline-size: min(21rem, 54vw);
    }

    .replay-floating-panel {
      left: calc(var(--rail-edge-inset) + 0.1rem);
      right: calc(var(--rail-edge-inset) + 0.1rem);
      inline-size: auto;
    }

    .split-game-wrap {
      grid-template-columns: 1fr;
      grid-template-rows: minmax(0, 0.92fr) minmax(0, 1.08fr);
    }
  }
</style>
