<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,
        PressureColorMapPreset,
        StageStatusTone,
    } from "$lib/types/hud";

    export let title = "";
    export let hint = "";
    export let locale: LocaleCode = "zh-CN";
    export let statusText = "";
    export let statusTone: StageStatusTone = "idle";
    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 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;
    export let showCalibrationPanel = false;

    type CalibrationMethodId = "coarse";

    interface CalibrationMethodOption {
        id: CalibrationMethodId;
        label: string;
        description: string;
    }

    let stagePlaneEl: HTMLDivElement | undefined;
    let topOverlayEl: 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";
    let calibrationRoundsByMethod: Record<CalibrationMethodId, number> = {
        coarse: 3,
    };

    const minRailScale = 0.2;
    const dispatch = createEventDispatcher<{
        configclose: void;
        calibrationclose: void;
        calibrationstart: {
            methodId: CalibrationMethodId;
            rounds: number;
        };
        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,
    );
    $: splitMatrixTitle = locale === "zh-CN" ? "数字矩阵" : "Matrix";
    $: splitMatrixHint =
        locale === "zh-CN" ? "实时压力数据 / 数字矩阵" : "Live pressure matrix";
    $: calibrationMethodLabel = locale === "zh-CN" ? "标定方法" : "Calibration Method";
    $: calibrationRoundsLabel = locale === "zh-CN" ? "期望标定轮次" : "Target Rounds";
    $: calibrationStartLabel = locale === "zh-CN" ? "启动标定" : "Start Calibration";
    $: calibrationPanelHint =
        locale === "zh-CN"
            ? "先选择标定方法并设置轮次，再启动标定。"
            : "Select a calibration method, set target rounds, then start.";
    $: calibrationMethodOptions = [
        {
            id: "coarse",
            label: locale === "zh-CN" ? "粗标定" : "Coarse Calibration",
            description:
                locale === "zh-CN"
                    ? "快速分轮采样，适合初步校准。"
                    : "Fast multi-round sampling for initial calibration.",
        },
    ] satisfies CalibrationMethodOption[];

    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 || !topOverlayEl) {
            return;
        }

        const planeRect = stagePlaneEl.getBoundingClientRect();
        const overlayRect = topOverlayEl.getBoundingClientRect();
        const overlayBottom = overlayRect.bottom - planeRect.top;
        const upperTopLimit = Math.max(
            72,
            Math.round(stagePlaneEl.clientHeight * 0.34),
        );
        panelZoneTopPx = clamp(
            Math.round(overlayBottom + 8),
            56,
            upperTopLimit,
        );

        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");
    }

    function normalizeCalibrationRounds(value: number): number {
        const safeValue = Number.isFinite(value) ? Math.round(value) : 1;
        return clamp(safeValue, 1, 20);
    }

    function calibrationRounds(methodId: CalibrationMethodId): number {
        return calibrationRoundsByMethod[methodId] ?? 1;
    }

    function handleCalibrationRoundsInput(
        event: Event,
        methodId: CalibrationMethodId,
    ): void {
        const target = event.currentTarget as HTMLInputElement;
        const nextValue = Number(target.value);
        calibrationRoundsByMethod = {
            ...calibrationRoundsByMethod,
            [methodId]: normalizeCalibrationRounds(nextValue),
        };
    }

    function emitCalibrationStart(methodId: CalibrationMethodId): void {
        const rounds = normalizeCalibrationRounds(calibrationRounds(methodId));
        calibrationRoundsByMethod = {
            ...calibrationRoundsByMethod,
            [methodId]: rounds,
        };

        dispatch("calibrationstart", {
            methodId,
            rounds,
        });
    }

    onMount(() => {
        recomputePanelLayout();

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

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

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

        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 && !showCalibrationPanel}
        <div class="stage-top-overlay" bind:this={topOverlayEl}>
          <div class="stage-meta">
            <p class="meta-label">WebGL2 Stage</p>
            <h2>{title}</h2>
            <p class="meta-hint">{hint}</p>
          </div>
          <p class="runtime-status" class:is-ok={statusTone === "ok"} class:is-warn={statusTone === "warn"}>
            {statusText}
          </p>
        </div>
      {/if}

      {#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
                  {pressureMatrix}
                  {matrixRows}
                  {matrixCols}
                  {rangeMin}
                  {rangeMax}
                  {colorMapPreset}
                  showStatsPanel={true}
                />
              {/key}
            </div>
          </section>

          <section class="split-panel split-breakout-panel">
            <NeonBreakoutArena
              {locale}
              {pressureMatrix}
              {matrixRows}
              {matrixCols}
              {rangeMin}
              {rangeMax}
              {colorMapPreset}
            />
          </section>
        </div>
      {:else if showCalibrationPanel}
        <div class="split-calibration-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}:calibration-split`}
                <PressureMatrixViewer
                  {pressureMatrix}
                  {matrixRows}
                  {matrixCols}
                  {rangeMin}
                  {rangeMax}
                  {colorMapPreset}
                  showStatsPanel={true}
                />
              {/key}
            </div>
          </section>

          <section class="split-panel split-calibration-panel">
            <header class="split-panel-head is-interactive">
              <div class="split-panel-title">
                <p>{locale === "zh-CN" ? "校准控制" : "Calibration Control"}</p>
                <span>{calibrationPanelHint}</span>
              </div>
              <button
                type="button"
                class="split-close-btn"
                on:click={() => dispatch("calibrationclose")}
                aria-label={locale === "zh-CN" ? "关闭校准" : "Close calibration"}
              >
                ×
              </button>
            </header>
            <div class="split-panel-body calibration-panel-body">
              <div class="calibration-content">
                <p class="calibration-label">{calibrationMethodLabel}</p>
                <div class="calibration-method-list">
                  {#each calibrationMethodOptions as method (method.id)}
                    <section class="calibration-method-row">
                      <div class="calibration-method-main">
                        <p class="calibration-method-name">{method.label}</p>
                        <p class="calibration-method-desc">{method.description}</p>
                      </div>

                      <div class="calibration-param-group">
                        <label
                          class="calibration-label"
                          for={`calibration-rounds-${method.id}`}
                        >
                          {calibrationRoundsLabel}
                        </label>
                        <input
                          id={`calibration-rounds-${method.id}`}
                          class="calibration-input"
                          type="number"
                          min="1"
                          max="20"
                          value={calibrationRounds(method.id)}
                          on:change={(event) =>
                            handleCalibrationRoundsInput(event, method.id)}
                          on:input={(event) =>
                            handleCalibrationRoundsInput(event, method.id)}
                        />
                      </div>

                      <button
                        type="button"
                        class="calibration-button"
                        on:click={() => emitCalibrationStart(method.id)}
                      >
                        {calibrationStartLabel}
                      </button>
                    </section>
                  {/each}
                </div>
              </div>
            </div>
          </section>
        </div>
      {:else}
        <div class="canvas-wrap">
          {#key `${matrixRows}x${matrixCols}:${colorMapPreset}`}
            <PressureMatrixViewer
              {pressureMatrix}
              {matrixRows}
              {matrixCols}
              {rangeMin}
              {rangeMax}
              {colorMapPreset}
              showStatsPanel={true}
            />
          {/key}
        </div>
      {/if}

            {#if showConfigPanel && !showPrecisionTestPanel && !showCalibrationPanel}
                <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 && !showCalibrationPanel}
                <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 summary.points.length > 0 && 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 summary.points.length > 0 && 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 && !showCalibrationPanel}
                <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 && !showCalibrationPanel}
                <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-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%;
  }

  .stage-top-overlay {
    position: absolute;
    top: clamp(0.55rem, 1.1vw, 0.9rem);
    left: calc(var(--rail-width) + var(--safe-gap) + var(--rail-edge-inset));
    right: calc(var(--rail-width) + var(--safe-gap) + var(--rail-edge-inset));
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 0.7rem;
    z-index: 7;
    pointer-events: none;
  }

  .stage-meta {
    min-width: 0;
    max-inline-size: min(22rem, 62%);
    padding: 0.3rem 0.5rem 0.35rem;
    border: 1px solid rgb(var(--hud-border-rgb) / 0.2);
    border-radius: 0.45rem;
    background: rgb(var(--hud-surface-deep-rgb) / 0.45);
    backdrop-filter: blur(2px);
  }

  .meta-label {
    margin: 0;
    font-size: 0.56rem;
    color: rgb(var(--hud-text-dim-rgb) / 0.8);
    text-transform: uppercase;
    letter-spacing: 0.1em;
  }

  h2 {
    margin: 0.08rem 0 0;
    font-size: clamp(0.75rem, 1.1vw, 0.92rem);
    color: rgb(var(--hud-text-main-rgb) / 0.96);
    letter-spacing: 0.03em;
    font-weight: 500;
    line-height: 1.2;
  }

  .meta-hint {
    margin: 0.09rem 0 0;
    font-size: 0.62rem;
    color: rgb(var(--hud-text-dim-rgb) / 0.76);
    line-height: 1.15;
  }

  .runtime-status {
    margin: 0;
    align-self: center;
    border: 1px solid rgb(var(--hud-border-rgb) / 0.35);
    border-radius: 999px;
    padding: 0.3rem 0.66rem;
    font-size: 0.66rem;
    letter-spacing: 0.08em;
    color: rgb(var(--hud-text-dim-rgb) / 0.9);
    text-transform: uppercase;
    white-space: nowrap;
    background: rgb(var(--hud-surface-deep-rgb) / 0.62);
  }

  .runtime-status.is-ok {
    color: rgb(var(--hud-lime-rgb) / 0.94);
  }

  .runtime-status.is-warn {
    color: rgb(var(--hud-orange-rgb) / 0.92);
  }

  .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-calibration-wrap {
    position: absolute;
    inset: clamp(0.46rem, 1vw, 0.82rem);
    z-index: 6;
    display: grid;
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
    gap: clamp(0.45rem, 1vw, 0.9rem);
  }

  .split-calibration-panel {
    padding: 1rem;
    display: flex;
    flex-direction: column;
    gap: 1rem;
  }

  .calibration-panel-body {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 2.1rem 0.2rem 0.2rem;
  }

  .calibration-content {
    display: grid;
    gap: 0.66rem;
  }

  .calibration-label {
    font-size: 0.7rem;
    color: rgb(var(--hud-text-dim-rgb) / 0.82);
    letter-spacing: 0.05em;
    text-transform: uppercase;
  }

  .calibration-input {
    inline-size: 100%;
    padding: 0.5rem 0.75rem;
    background: rgb(var(--hud-surface-rgb) / 0.8);
    border: 1px solid rgb(var(--hud-border-rgb) / 0.3);
    border-radius: 0.375rem;
    color: rgb(var(--hud-text-main-rgb) / 0.96);
    font-size: 0.875rem;
    outline: none;
    transition: border-color 0.2s ease;
  }

  .calibration-input:focus {
    border-color: rgb(var(--hud-cyan-rgb) / 0.6);
  }

  .calibration-button {
    min-inline-size: 8.3rem;
    min-block-size: 2.45rem;
    padding: 0.625rem 1rem;
    background: linear-gradient(135deg, rgb(var(--hud-cyan-rgb) / 0.9), rgb(var(--hud-lime-rgb) / 0.9));
    border: none;
    border-radius: 0.375rem;
    color: rgb(0 0 0 / 0.9);
    font-size: 0.875rem;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.2s ease;
    letter-spacing: 0.03em;
  }

  .calibration-button:hover {
    background: linear-gradient(135deg, rgb(var(--hud-cyan-rgb) / 1), rgb(var(--hud-lime-rgb) / 1));
    transform: translateY(-1px);
    box-shadow: 0 0 20px rgb(var(--hud-cyan-rgb) / 0.4);
  }

  .calibration-button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
    transform: none;
  }

  .calibration-method-list {
    display: grid;
    gap: 0.5rem;
  }

  .calibration-method-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(7.6rem, 9.2rem) auto;
    align-items: end;
    gap: 0.62rem;
    padding: 0.64rem 0.66rem;
    background: rgb(var(--hud-surface-rgb) / 0.56);
    border: 1px solid rgb(var(--hud-border-rgb) / 0.25);
    border-radius: 0.375rem;
    transition: border-color 0.2s ease, background-color 0.2s ease;
  }

  .calibration-method-row:hover {
    background: rgb(var(--hud-surface-alt-rgb) / 0.8);
    border-color: rgb(var(--hud-border-rgb) / 0.4);
  }

  .calibration-method-main {
    min-inline-size: 0;
  }

  .calibration-method-name {
    margin: 0;
    font-size: 0.85rem;
    font-weight: 500;
    color: rgb(var(--hud-text-main-rgb) / 0.96);
  }

  .calibration-method-desc {
    margin: 0.2rem 0 0;
    font-size: 0.7rem;
    color: rgb(var(--hud-text-dim-rgb) / 0.82);
    line-height: 1.35;
  }

  .calibration-param-group {
    display: grid;
    gap: 0.32rem;
  }

  .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.is-interactive {
    left: 0.52rem;
    right: 0.52rem;
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.6rem;
    pointer-events: auto;
  }

  .split-panel-title {
    display: grid;
    gap: 0.1rem;
    min-width: 0;
  }

  .split-close-btn {
    inline-size: 1.74rem;
    block-size: 1.74rem;
    border: 1px solid rgb(var(--hud-orange-rgb) / 0.4);
    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;
  }

  .split-close-btn:hover {
    border-color: rgb(var(--hud-orange-rgb) / 0.64);
    color: rgb(var(--hud-text-main-rgb) / 0.98);
    box-shadow: 0 0 10px rgb(var(--hud-orange-rgb) / 0.18);
  }

  .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);
        }

        .split-calibration-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);
        }

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

        .calibration-method-row {
            grid-template-columns: 1fr;
            align-items: stretch;
        }

        .calibration-button {
            inline-size: 100%;
        }
    }
</style>
