feat:add game!
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import { fly } from "svelte/transition";
|
import { fly } from "svelte/transition";
|
||||||
import ConfigPanel from "$lib/components/ConfigPanel.svelte";
|
import ConfigPanel from "$lib/components/ConfigPanel.svelte";
|
||||||
|
import NeonBreakoutArena from "$lib/components/NeonBreakoutArena.svelte";
|
||||||
import PressureMatrixViewer from "$lib/components/PressureMatrixViewer.svelte";
|
import PressureMatrixViewer from "$lib/components/PressureMatrixViewer.svelte";
|
||||||
import SignalChart from "$lib/components/SignalChart.svelte";
|
import SignalChart from "$lib/components/SignalChart.svelte";
|
||||||
import SummaryCurve from "$lib/components/SummaryCurve.svelte";
|
import SummaryCurve from "$lib/components/SummaryCurve.svelte";
|
||||||
@@ -12,12 +13,14 @@
|
|||||||
HudColorMapOption,
|
HudColorMapOption,
|
||||||
HudSignalPanel,
|
HudSignalPanel,
|
||||||
HudSummary,
|
HudSummary,
|
||||||
|
LocaleCode,
|
||||||
PressureColorMapPreset,
|
PressureColorMapPreset,
|
||||||
StageStatusTone
|
StageStatusTone
|
||||||
} from "$lib/types/hud";
|
} from "$lib/types/hud";
|
||||||
|
|
||||||
export let title = "";
|
export let title = "";
|
||||||
export let hint = "";
|
export let hint = "";
|
||||||
|
export let locale: LocaleCode = "zh-CN";
|
||||||
export let statusText = "";
|
export let statusText = "";
|
||||||
export let statusTone: StageStatusTone = "idle";
|
export let statusTone: StageStatusTone = "idle";
|
||||||
export let leftPanels: HudSignalPanel[] = [];
|
export let leftPanels: HudSignalPanel[] = [];
|
||||||
@@ -54,6 +57,7 @@
|
|||||||
export let replayProgress = 0;
|
export let replayProgress = 0;
|
||||||
export let replayFileName = "";
|
export let replayFileName = "";
|
||||||
export let replayFrameInfo = "";
|
export let replayFrameInfo = "";
|
||||||
|
export let showPrecisionTestPanel = false;
|
||||||
|
|
||||||
let stagePlaneEl: HTMLDivElement | undefined;
|
let stagePlaneEl: HTMLDivElement | undefined;
|
||||||
let topOverlayEl: HTMLDivElement | undefined;
|
let topOverlayEl: HTMLDivElement | undefined;
|
||||||
@@ -81,6 +85,8 @@
|
|||||||
$: replaySide = summarySide === "left" ? "right" : "left";
|
$: replaySide = summarySide === "left" ? "right" : "left";
|
||||||
$: replayToggleButtonText = replayIsPlaying ? replayPauseLabel : replayPlayLabel;
|
$: replayToggleButtonText = replayIsPlaying ? replayPauseLabel : replayPlayLabel;
|
||||||
$: replayProgressPercent = Math.round(Math.min(1, Math.max(0, replayProgress)) * 100);
|
$: replayProgressPercent = Math.round(Math.min(1, Math.max(0, replayProgress)) * 100);
|
||||||
|
$: splitMatrixTitle = locale === "zh-CN" ? "数字矩阵" : "Matrix";
|
||||||
|
$: splitMatrixHint = locale === "zh-CN" ? "实时压力数据 / 数字矩阵" : "Live pressure matrix";
|
||||||
|
|
||||||
function toPxNumber(rawValue: string): number {
|
function toPxNumber(rawValue: string): number {
|
||||||
const value = Number.parseFloat(rawValue);
|
const value = Number.parseFloat(rawValue);
|
||||||
@@ -181,31 +187,70 @@
|
|||||||
bind:this={stagePlaneEl}
|
bind:this={stagePlaneEl}
|
||||||
style="--panel-zone-top-dyn: {panelZoneTopPx}px; --rail-scale-left: {leftRailScale}; --rail-scale-right: {rightRailScale};"
|
style="--panel-zone-top-dyn: {panelZoneTopPx}px; --rail-scale-left: {leftRailScale}; --rail-scale-right: {rightRailScale};"
|
||||||
>
|
>
|
||||||
<div class="stage-top-overlay" bind:this={topOverlayEl}>
|
{#if !showPrecisionTestPanel}
|
||||||
<div class="stage-meta">
|
<div class="stage-top-overlay" bind:this={topOverlayEl}>
|
||||||
<p class="meta-label">WebGL2 Stage</p>
|
<div class="stage-meta">
|
||||||
<h2>{title}</h2>
|
<p class="meta-label">WebGL2 Stage</p>
|
||||||
<p class="meta-hint">{hint}</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>
|
</div>
|
||||||
<p class="runtime-status" class:is-ok={statusTone === "ok"} class:is-warn={statusTone === "warn"}>
|
{/if}
|
||||||
{statusText}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="canvas-wrap">
|
{#if showPrecisionTestPanel}
|
||||||
{#key `${matrixRows}x${matrixCols}:${colorMapPreset}`}
|
<div class="split-game-wrap">
|
||||||
<PressureMatrixViewer
|
<section class="split-panel split-matrix-panel">
|
||||||
{pressureMatrix}
|
<header class="split-panel-head">
|
||||||
{matrixRows}
|
<p>{splitMatrixTitle}</p>
|
||||||
{matrixCols}
|
<span>{splitMatrixHint}</span>
|
||||||
{rangeMin}
|
</header>
|
||||||
{rangeMax}
|
<div class="split-panel-body">
|
||||||
{colorMapPreset}
|
{#key `${matrixRows}x${matrixCols}:${colorMapPreset}:split`}
|
||||||
/>
|
<PressureMatrixViewer
|
||||||
{/key}
|
{pressureMatrix}
|
||||||
</div>
|
{matrixRows}
|
||||||
|
{matrixCols}
|
||||||
|
{rangeMin}
|
||||||
|
{rangeMax}
|
||||||
|
{colorMapPreset}
|
||||||
|
showStatsPanel={true}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
{#if showConfigPanel}
|
<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
|
||||||
|
{pressureMatrix}
|
||||||
|
{matrixRows}
|
||||||
|
{matrixCols}
|
||||||
|
{rangeMin}
|
||||||
|
{rangeMax}
|
||||||
|
{colorMapPreset}
|
||||||
|
showStatsPanel={true}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if showConfigPanel && !showPrecisionTestPanel}
|
||||||
<div class="config-panel-wrap">
|
<div class="config-panel-wrap">
|
||||||
<ConfigPanel
|
<ConfigPanel
|
||||||
bind:matrixRows
|
bind:matrixRows
|
||||||
@@ -230,71 +275,73 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="panel-zone" bind:this={panelZoneEl}>
|
{#if !showPrecisionTestPanel}
|
||||||
<aside class="side-rail left-rail">
|
<div class="panel-zone" bind:this={panelZoneEl}>
|
||||||
<div class="rail-stack" bind:this={leftStackEl}>
|
<aside class="side-rail left-rail">
|
||||||
{#each leftPanels as panel, index (panel.id)}
|
<div class="rail-stack" bind:this={leftStackEl}>
|
||||||
<div
|
{#each leftPanels as panel, index (panel.id)}
|
||||||
class="panel-motion-shell"
|
<div
|
||||||
animate:flip={{ duration: 280 }}
|
class="panel-motion-shell"
|
||||||
in:fly={{ x: -180, duration: 340, opacity: 0.08, easing: cubicOut }}
|
animate:flip={{ duration: 280 }}
|
||||||
out:fly={{ x: -180, duration: 280, opacity: 0.06, easing: cubicIn }}
|
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>
|
<SignalChart {panel} panelIndex={index} />
|
||||||
{/each}
|
</div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
{#if summary.points.length > 0 && summarySide === "left"}
|
{#if summary.points.length > 0 && summarySide === "left"}
|
||||||
<div
|
<div
|
||||||
class="panel-motion-shell"
|
class="panel-motion-shell"
|
||||||
in:fly={{ x: -180, duration: 340, opacity: 0.08, easing: cubicOut }}
|
in:fly={{ x: -180, duration: 340, opacity: 0.08, easing: cubicOut }}
|
||||||
out:fly={{ x: -180, duration: 280, opacity: 0.06, easing: cubicIn }}
|
out:fly={{ x: -180, duration: 280, opacity: 0.06, easing: cubicIn }}
|
||||||
>
|
>
|
||||||
<SummaryCurve
|
<SummaryCurve
|
||||||
{summary}
|
{summary}
|
||||||
xValues={summary.xValues ?? null}
|
xValues={summary.xValues ?? null}
|
||||||
yValues={summary.points}
|
yValues={summary.points}
|
||||||
side="left"
|
side="left"
|
||||||
panelIndex={leftPanels.length}
|
panelIndex={leftPanels.length}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<aside class="side-rail right-rail">
|
<aside class="side-rail right-rail">
|
||||||
<div class="rail-stack" bind:this={rightStackEl}>
|
<div class="rail-stack" bind:this={rightStackEl}>
|
||||||
{#each rightPanels as panel, index (panel.id)}
|
{#each rightPanels as panel, index (panel.id)}
|
||||||
<div
|
<div
|
||||||
class="panel-motion-shell"
|
class="panel-motion-shell"
|
||||||
animate:flip={{ duration: 280 }}
|
animate:flip={{ duration: 280 }}
|
||||||
in:fly={{ x: 180, duration: 340, opacity: 0.08, easing: cubicOut }}
|
in:fly={{ x: 180, duration: 340, opacity: 0.08, easing: cubicOut }}
|
||||||
out:fly={{ x: 180, duration: 280, opacity: 0.06, easing: cubicIn }}
|
out:fly={{ x: 180, duration: 280, opacity: 0.06, easing: cubicIn }}
|
||||||
>
|
>
|
||||||
<SignalChart {panel} panelIndex={index} />
|
<SignalChart {panel} panelIndex={index} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
{#if summary.points.length > 0 && summarySide === "right"}
|
{#if summary.points.length > 0 && summarySide === "right"}
|
||||||
<div
|
<div
|
||||||
class="panel-motion-shell"
|
class="panel-motion-shell"
|
||||||
in:fly={{ x: 180, duration: 340, opacity: 0.08, easing: cubicOut }}
|
in:fly={{ x: 180, duration: 340, opacity: 0.08, easing: cubicOut }}
|
||||||
out:fly={{ x: 180, duration: 280, opacity: 0.06, easing: cubicIn }}
|
out:fly={{ x: 180, duration: 280, opacity: 0.06, easing: cubicIn }}
|
||||||
>
|
>
|
||||||
<SummaryCurve
|
<SummaryCurve
|
||||||
{summary}
|
{summary}
|
||||||
xValues={summary.xValues ?? null}
|
xValues={summary.xValues ?? null}
|
||||||
yValues={summary.points}
|
yValues={summary.points}
|
||||||
side="right"
|
side="right"
|
||||||
panelIndex={rightPanels.length}
|
panelIndex={rightPanels.length}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if replayHasData}
|
{#if replayHasData && !showPrecisionTestPanel}
|
||||||
<aside class="replay-floating-panel" class:is-left={replaySide === "left"} class:is-right={replaySide === "right"}>
|
<aside class="replay-floating-panel" class:is-left={replaySide === "left"} class:is-right={replaySide === "right"}>
|
||||||
<div class="replay-panel-head">
|
<div class="replay-panel-head">
|
||||||
<div class="replay-panel-title-group">
|
<div class="replay-panel-title-group">
|
||||||
@@ -332,9 +379,11 @@
|
|||||||
</aside>
|
</aside>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="stage-bottom-overlay">
|
{#if !showPrecisionTestPanel}
|
||||||
<slot />
|
<div class="stage-bottom-overlay">
|
||||||
</div>
|
<slot />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
@@ -463,6 +512,70 @@
|
|||||||
max-inline-size: min(24rem, 40vw);
|
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 {
|
.panel-zone {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: var(--panel-zone-top-dyn, var(--panel-zone-top));
|
top: var(--panel-zone-top-dyn, var(--panel-zone-top));
|
||||||
@@ -744,6 +857,10 @@
|
|||||||
.replay-floating-panel {
|
.replay-floating-panel {
|
||||||
inline-size: min(var(--rail-width), 20.8rem);
|
inline-size: min(var(--rail-width), 20.8rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.split-game-wrap {
|
||||||
|
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-height: 900px) {
|
@media (max-height: 900px) {
|
||||||
@@ -787,5 +904,10 @@
|
|||||||
right: calc(var(--rail-edge-inset) + 0.1rem);
|
right: calc(var(--rail-edge-inset) + 0.1rem);
|
||||||
inline-size: auto;
|
inline-size: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.split-game-wrap {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-template-rows: minmax(0, 0.92fr) minmax(0, 1.08fr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
1027
src/lib/components/NeonBreakoutArena.svelte
Normal file
1027
src/lib/components/NeonBreakoutArena.svelte
Normal file
File diff suppressed because it is too large
Load Diff
@@ -28,6 +28,7 @@
|
|||||||
export let rangeMin = 0;
|
export let rangeMin = 0;
|
||||||
export let rangeMax = 16000;
|
export let rangeMax = 16000;
|
||||||
export let colorMapPreset: PressureColorMapPreset = "emerald";
|
export let colorMapPreset: PressureColorMapPreset = "emerald";
|
||||||
|
export let showStatsPanel = true;
|
||||||
|
|
||||||
let viewerEl: HTMLDivElement | undefined;
|
let viewerEl: HTMLDivElement | undefined;
|
||||||
let canvasEl: HTMLCanvasElement | undefined;
|
let canvasEl: HTMLCanvasElement | undefined;
|
||||||
@@ -608,26 +609,28 @@
|
|||||||
<div class="viewer-vignette" aria-hidden="true"></div>
|
<div class="viewer-vignette" aria-hidden="true"></div>
|
||||||
<div class="viewer-noise" aria-hidden="true"></div>
|
<div class="viewer-noise" aria-hidden="true"></div>
|
||||||
|
|
||||||
<div class="viewer-controls">
|
{#if showStatsPanel}
|
||||||
<section class="stats-panel" aria-label="Pressure Summary">
|
<div class="viewer-controls">
|
||||||
<p class="stats-label">Pressure Matrix</p>
|
<section class="stats-panel" aria-label="Pressure Summary">
|
||||||
<div class="stats-grid">
|
<p class="stats-label">Pressure Matrix</p>
|
||||||
<article class="stats-card stats-card-wide">
|
<div class="stats-grid">
|
||||||
<span class="stats-key">Total Pressure</span>
|
<article class="stats-card stats-card-wide">
|
||||||
<strong class="stats-value">{stats.total.toFixed(0)}</strong>
|
<span class="stats-key">Total Pressure</span>
|
||||||
</article>
|
<strong class="stats-value">{stats.total.toFixed(0)}</strong>
|
||||||
<article class="stats-card">
|
</article>
|
||||||
<span class="stats-key">Max</span>
|
<article class="stats-card">
|
||||||
<strong class="stats-value">{stats.max.toFixed(0)}</strong>
|
<span class="stats-key">Max</span>
|
||||||
</article>
|
<strong class="stats-value">{stats.max.toFixed(0)}</strong>
|
||||||
<article class="stats-card">
|
</article>
|
||||||
<span class="stats-key">Avg</span>
|
<article class="stats-card">
|
||||||
<strong class="stats-value">{stats.avg.toFixed(0)}</strong>
|
<span class="stats-key">Avg</span>
|
||||||
</article>
|
<strong class="stats-value">{stats.avg.toFixed(0)}</strong>
|
||||||
</div>
|
</article>
|
||||||
<p class="stats-note">{statsNote}</p>
|
</div>
|
||||||
</section>
|
<p class="stats-note">{statsNote}</p>
|
||||||
</div>
|
</section>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -204,6 +204,7 @@
|
|||||||
let isWindowMaximized = false;
|
let isWindowMaximized = false;
|
||||||
let activeConfigLinkId = "stream-on";
|
let activeConfigLinkId = "stream-on";
|
||||||
let isConfigPanelOpen = false;
|
let isConfigPanelOpen = false;
|
||||||
|
let isPrecisionTestOpen = false;
|
||||||
let hasSignalData = false;
|
let hasSignalData = false;
|
||||||
let signalPanels: HudSignalPanel[] = buildInactivePanels();
|
let signalPanels: HudSignalPanel[] = buildInactivePanels();
|
||||||
let summary: HudSummary = buildEmptySummary();
|
let summary: HudSummary = buildEmptySummary();
|
||||||
@@ -232,7 +233,7 @@
|
|||||||
let fileExplorerFileName = "";
|
let fileExplorerFileName = "";
|
||||||
|
|
||||||
$: uiCopy = copyByLocale[locale];
|
$: uiCopy = copyByLocale[locale];
|
||||||
$: configLinks = buildConfigLinks(locale, activeConfigLinkId, isConfigPanelOpen);
|
$: configLinks = buildConfigLinks(locale, activeConfigLinkId, isConfigPanelOpen, isPrecisionTestOpen);
|
||||||
$: stageStatusText = webglStatusTone === "ok" ? uiCopy.runtimeReady : uiCopy.runtimeFallback;
|
$: stageStatusText = webglStatusTone === "ok" ? uiCopy.runtimeReady : uiCopy.runtimeFallback;
|
||||||
$: leftSignalPanels = signalPanels.filter((panel) => panel.side === "left");
|
$: leftSignalPanels = signalPanels.filter((panel) => panel.side === "left");
|
||||||
$: rightSignalPanels = signalPanels.filter((panel) => panel.side === "right");
|
$: rightSignalPanels = signalPanels.filter((panel) => panel.side === "right");
|
||||||
@@ -970,19 +971,26 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildConfigLinks(currentLocale: LocaleCode, activeId: string, isSettingsOpen: boolean): HudConfigLink[] {
|
function buildConfigLinks(
|
||||||
|
currentLocale: LocaleCode,
|
||||||
|
activeId: string,
|
||||||
|
isSettingsOpen: boolean,
|
||||||
|
isPrecisionOpen: boolean
|
||||||
|
): HudConfigLink[] {
|
||||||
const labels =
|
const labels =
|
||||||
currentLocale === "zh-CN"
|
currentLocale === "zh-CN"
|
||||||
? {
|
? {
|
||||||
streamOn: "打开",
|
streamOn: "打开",
|
||||||
streamOff: "关闭",
|
streamOff: "关闭",
|
||||||
calibrate: "校准",
|
calibrate: "校准",
|
||||||
|
precisionTest: "游戏",
|
||||||
settings: "参数"
|
settings: "参数"
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
streamOn: "Open",
|
streamOn: "Open",
|
||||||
streamOff: "Close",
|
streamOff: "Close",
|
||||||
calibrate: "Calib",
|
calibrate: "Calib",
|
||||||
|
precisionTest: "Game",
|
||||||
settings: "Setup"
|
settings: "Setup"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1005,6 +1013,12 @@
|
|||||||
tone: "cyan",
|
tone: "cyan",
|
||||||
active: activeId === "calibrate"
|
active: activeId === "calibrate"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "precision-test",
|
||||||
|
label: labels.precisionTest,
|
||||||
|
tone: "lime",
|
||||||
|
active: isPrecisionOpen
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "settings",
|
id: "settings",
|
||||||
label: labels.settings,
|
label: labels.settings,
|
||||||
@@ -1443,11 +1457,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleConfigLink(event: CustomEvent<string>): void {
|
function handleConfigLink(event: CustomEvent<string>): void {
|
||||||
|
if (event.detail === "precision-test") {
|
||||||
|
isPrecisionTestOpen = !isPrecisionTestOpen;
|
||||||
|
isConfigPanelOpen = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.detail === "settings") {
|
if (event.detail === "settings") {
|
||||||
|
isPrecisionTestOpen = false;
|
||||||
isConfigPanelOpen = !isConfigPanelOpen;
|
isConfigPanelOpen = !isConfigPanelOpen;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isPrecisionTestOpen = false;
|
||||||
isConfigPanelOpen = false;
|
isConfigPanelOpen = false;
|
||||||
activeConfigLinkId = event.detail;
|
activeConfigLinkId = event.detail;
|
||||||
console.info("[hud] config link clicked:", event.detail);
|
console.info("[hud] config link clicked:", event.detail);
|
||||||
@@ -1562,6 +1584,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<CenterStage
|
<CenterStage
|
||||||
|
{locale}
|
||||||
bind:matrixRows
|
bind:matrixRows
|
||||||
bind:matrixCols
|
bind:matrixCols
|
||||||
bind:rangeMin
|
bind:rangeMin
|
||||||
@@ -1599,6 +1622,7 @@
|
|||||||
rightPanels={rightSignalPanels}
|
rightPanels={rightSignalPanels}
|
||||||
{pressureMatrix}
|
{pressureMatrix}
|
||||||
showConfigPanel={isConfigPanelOpen}
|
showConfigPanel={isConfigPanelOpen}
|
||||||
|
showPrecisionTestPanel={isPrecisionTestOpen}
|
||||||
{summary}
|
{summary}
|
||||||
on:replaytoggle={handleReplayToggle}
|
on:replaytoggle={handleReplayToggle}
|
||||||
on:replaystop={handleReplayStop}
|
on:replaystop={handleReplayStop}
|
||||||
@@ -1607,14 +1631,16 @@
|
|||||||
on:replayclose={handleReplayClose}
|
on:replayclose={handleReplayClose}
|
||||||
on:configclose={() => (isConfigPanelOpen = false)}
|
on:configclose={() => (isConfigPanelOpen = false)}
|
||||||
>
|
>
|
||||||
<section class="range-scale" aria-label="Signal Range">
|
{#if !isPrecisionTestOpen}
|
||||||
<p class="range-label">Range</p>
|
<section class="range-scale" aria-label="Signal Range">
|
||||||
<div class="range-track">
|
<p class="range-label">Range</p>
|
||||||
{#each rangeTicks as tick}
|
<div class="range-track">
|
||||||
<span class="range-tick">{tick}</span>
|
{#each rangeTicks as tick}
|
||||||
{/each}
|
<span class="range-tick">{tick}</span>
|
||||||
</div>
|
{/each}
|
||||||
</section>
|
</div>
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
</CenterStage>
|
</CenterStage>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user